diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 6d5d6ed31..6c6293b31 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -39,6 +39,7 @@ "react-image-lightbox": "^5.1.4", "react-quill": "^1.3.5", "react-router-dom": "^5.2.0", + "react-to-print": "^2.14.7", "use-deep-compare-effect": "^1.8.1" }, "peerDependencies": { diff --git a/packages/core/client/src/block-provider/FormBlockProvider.tsx b/packages/core/client/src/block-provider/FormBlockProvider.tsx index cd9e795cd..fec1e6111 100644 --- a/packages/core/client/src/block-provider/FormBlockProvider.tsx +++ b/packages/core/client/src/block-provider/FormBlockProvider.tsx @@ -1,9 +1,8 @@ import { createForm } from '@formily/core'; -import { useField, useFieldSchema } from '@formily/react'; +import { useField } from '@formily/react'; import { Spin } from 'antd'; -import React, { createContext, useContext, useEffect, useMemo } from 'react'; +import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react'; import { BlockProvider, useBlockRequestContext } from './BlockProvider'; -import { useCollectionManager } from '../collection-manager'; export const FormBlockContext = createContext({}); @@ -18,6 +17,7 @@ const InternalFormBlockProvider = (props) => { [], ); const { resource, service } = useBlockRequestContext(); + const formBlockRef = useRef(); if (service.loading) { return ; } @@ -30,9 +30,10 @@ const InternalFormBlockProvider = (props) => { service, resource, updateAssociationValues: [], + formBlockRef, }} > - {props.children} +
{props.children}
); }; diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index a2d7277d0..c06d60d48 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -4,6 +4,7 @@ import parse from 'json-templates'; import get from 'lodash/get'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; +import { useReactToPrint } from 'react-to-print'; import { useFormBlockContext } from '../..'; import { useAPIClient } from '../../api-client'; import { useCollection } from '../../collection-manager'; @@ -398,6 +399,28 @@ export const useDestroyActionProps = () => { }; }; +export const useDetailPrintActionProps = () => { + const { formBlockRef } = useFormBlockContext(); + + const printHandler = useReactToPrint({ + content: () => formBlockRef.current, + pageStyle: `@media print { + * { + margin: 0; + } + div.ant-formily-layout>div:first-child { + overflow: hidden; height: 0; + } + + }`, + }); + return { + async onClick() { + printHandler(); + }, + }; +}; + export const useBulkDestroyActionProps = () => { const { field } = useBlockRequestContext(); const { resource, service } = useBlockRequestContext(); @@ -422,7 +445,7 @@ export const useRefreshActionProps = () => { service?.refresh?.(); }, }; -} +}; export const useDetailsPaginationProps = () => { const ctx = useDetailsBlockContext(); diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 03a2141a1..3b8781be1 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -553,4 +553,5 @@ export default { "Regular expression": "Pattern", "Enabled languages": "Enabled languages", "View all plugins": "View all plugins", + "Print": "Print", } diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index ca2461154..2bb63e6c5 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -649,4 +649,5 @@ export default { "Province/city/area name": "省市区名称", "Enabled languages": "启用的语言", "View all plugins": "查看所有插件", + "Print": "打印", } diff --git a/packages/core/client/src/schema-component/antd/grid/Grid.tsx b/packages/core/client/src/schema-component/antd/grid/Grid.tsx index 928ce5057..710bd0500 100644 --- a/packages/core/client/src/schema-component/antd/grid/Grid.tsx +++ b/packages/core/client/src/schema-component/antd/grid/Grid.tsx @@ -3,8 +3,8 @@ import { css } from '@emotion/css'; import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared'; import cls from 'classnames'; -import React, { createContext, useContext, useState } from 'react'; -import { useSchemaInitializer } from '../../../'; +import React, { createContext, useContext, useEffect, useRef, useState } from 'react'; +import { useFormBlockContext, useSchemaInitializer } from '../../../'; import { DndContext } from '../../common/dnd-context'; const GridRowContext = createContext(null); @@ -24,19 +24,18 @@ const ColDivider = (props) => { backgroundColor: isOver ? 'rgba(241, 139, 98, .1)' : undefined, }; - const dndContext = useDndContext() - const activeSchema: Schema | undefined = dndContext.active?.data.current?.schema?.parent + const dndContext = useDndContext(); + const activeSchema: Schema | undefined = dndContext.active?.data.current?.schema?.parent; const blocksLength: number = activeSchema ? Object.keys(activeSchema.properties).length : 0; - - let visible = true + let visible = true; if (blocksLength === 1) { if (props.first) { - visible = activeSchema !== props.cols[0] + visible = activeSchema !== props.cols[0]; } else { - const currentSchema = props.cols[props.index] - const downSchema = props.cols[props.index + 1] - visible = activeSchema !== currentSchema && downSchema !== activeSchema + const currentSchema = props.cols[props.index]; + const downSchema = props.cols[props.index + 1]; + visible = activeSchema !== currentSchema && downSchema !== activeSchema; } } @@ -68,23 +67,25 @@ const RowDivider = (props) => { const [active, setActive] = useState(false); - const dndContext = useDndContext() - const currentSchema = props.rows[props.index] - const activeSchema = dndContext.active?.data.current?.schema?.parent.parent + const dndContext = useDndContext(); + const currentSchema = props.rows[props.index]; + const activeSchema = dndContext.active?.data.current?.schema?.parent.parent; - const colsLength: number = activeSchema?.mapProperties((schema) => { - return schema['x-component'] === 'Grid.Col' - }).filter(Boolean).length + const colsLength: number = activeSchema + ?.mapProperties((schema) => { + return schema['x-component'] === 'Grid.Col'; + }) + .filter(Boolean).length; - let visible = true + let visible = true; // col > 1 时不需要隐藏 if (colsLength === 1) { if (props.first) { - visible = activeSchema !== props.rows[0] + visible = activeSchema !== props.rows[0]; } else { - const downSchema = props.rows[props.index + 1] - visible = activeSchema !== currentSchema && downSchema !== activeSchema + const downSchema = props.rows[props.index + 1]; + visible = activeSchema !== currentSchema && downSchema !== activeSchema; } } @@ -196,15 +197,20 @@ export const useGridRowContext = () => { }; export const Grid: any = observer((props: any) => { + const gridRef = useRef(null); const field = useField(); const fieldSchema = useFieldSchema(); const { render } = useSchemaInitializer(fieldSchema['x-initializer']); const addr = field.address.toString(); const rows = useRowProperties(); + const { setPrintContent } = useFormBlockContext(); + useEffect(() => { + gridRef.current && setPrintContent?.(gridRef.current); + }, [gridRef.current]); return ( - -
+ +
{ const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']); const [record, setRecord] = useState({}); const compile = useCompile(); - + const ellipsisWithTooltipRef = useRef(); const renderRecords = () => toArr(field.value).map((record, index, arr) => { return ( - <> + { @@ -47,7 +47,7 @@ export const ReadPrettyRecordPicker: React.FC = observer((props: any) => { {index < arr.length - 1 ? , : null} - + ); }); @@ -70,16 +70,12 @@ export const ReadPrettyRecordPicker: React.FC = observer((props: any) => { return collectionFieldNames && collectionFieldNames.length > 2 ? ( - - {renderWithoutTableFieldResourceProvider()} - + {renderWithoutTableFieldResourceProvider()} ) : ( - - {renderWithoutTableFieldResourceProvider()} - + {renderWithoutTableFieldResourceProvider()} ); - } + }; return collectionField ? (
diff --git a/packages/core/client/src/schema-component/hooks/useProps.ts b/packages/core/client/src/schema-component/hooks/useProps.ts index f8ec83b37..aa10a0771 100644 --- a/packages/core/client/src/schema-component/hooks/useProps.ts +++ b/packages/core/client/src/schema-component/hooks/useProps.ts @@ -11,6 +11,6 @@ interface Options { export const useProps = (props: any, options?: Options) => { const { useProps, ...props1 } = props; - let props2 = useProps?.() || {}; + let props2 = typeof useProps === 'function' ? useProps() : {}; return merge(props1 || {}, props2, options); }; diff --git a/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx index f2dde2de9..bf4956deb 100644 --- a/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx +++ b/packages/core/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx @@ -31,6 +31,15 @@ export const ReadPrettyFormActionInitializers = { 'x-decorator': 'ACLActionProvider', }, }, + { + type: 'item', + title: '{{t("Print")}}', + component: 'PrintActionInitializer', + schema: { + 'x-component': 'Action', + 'x-decorator': 'ACLActionProvider', + }, + }, ], }, diff --git a/packages/core/client/src/schema-initializer/items/index.tsx b/packages/core/client/src/schema-initializer/items/index.tsx index 6b2712507..e099c1dc9 100644 --- a/packages/core/client/src/schema-initializer/items/index.tsx +++ b/packages/core/client/src/schema-initializer/items/index.tsx @@ -1,6 +1,6 @@ import { FormOutlined, TableOutlined } from '@ant-design/icons'; import { FormDialog, FormLayout } from '@formily/antd'; -import { ISchema, SchemaOptionsContext, useFieldSchema } from '@formily/react'; +import { ISchema, SchemaOptionsContext } from '@formily/react'; import { merge } from '@formily/shared'; import React, { useContext } from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,7 +20,7 @@ import { createTableSelectorSchema, useCollectionDataSourceItems, useCurrentSchema, - useRecordCollectionDataSourceItems + useRecordCollectionDataSourceItems, } from '../utils'; // Block @@ -520,6 +520,20 @@ export const DestroyActionInitializer = (props) => { return ; }; +export const PrintActionInitializer = (props) => { + const schema = { + title: '{{ t("Print") }}', + 'x-action': 'print', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-component-props': { + icon: 'PrinterOutlined', + useProps: '{{ useDetailPrintActionProps }}', + }, + }; + return ; +}; + export const BulkDestroyActionInitializer = (props) => { const schema = { title: '{{ t("Delete") }}', @@ -688,7 +702,7 @@ export const RecordFormBlockInitializer = (props) => { export const RecordReadPrettyFormBlockInitializer = (props) => { const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; - + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); const collection = useCollection(); const association = useBlockAssociationContext(); @@ -749,9 +763,10 @@ export const RecordAssociationFormBlockInitializer = (props) => { icon={} {...others} onClick={async ({ item }) => { - const action = ['hasOne', 'belongsTo'].includes(field.type) ? 'get' : null; - const actionInitializers = ['hasOne', 'belongsTo'].includes(field.type) ? 'UpdateFormActionInitializers' : 'CreateFormActionInitializers'; + const actionInitializers = ['hasOne', 'belongsTo'].includes(field.type) + ? 'UpdateFormActionInitializers' + : 'CreateFormActionInitializers'; if (item.template) { const s = await getTemplateSchemaByMode(item); @@ -875,7 +890,7 @@ export const RecordAssociationDetailsBlockInitializer = (props) => { items={useRecordCollectionDataSourceItems('Details', item, field.target, resource)} /> ); -} +}; export const RecordAssociationCalendarBlockInitializer = (props) => { const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; @@ -896,75 +911,75 @@ export const RecordAssociationCalendarBlockInitializer = (props) => { const s = await getTemplateSchemaByMode(item); insert(s); } else { - const stringFields = collection?.fields - ?.filter((field) => field.type === 'string') - ?.map((field) => { - return { - label: field?.uiSchema?.title, - value: field.name, - }; + const stringFields = collection?.fields + ?.filter((field) => field.type === 'string') + ?.map((field) => { + return { + label: field?.uiSchema?.title, + value: field.name, + }; + }); + const dateFields = collection?.fields + ?.filter((field) => field.type === 'date') + ?.map((field) => { + return { + label: field?.uiSchema?.title, + value: field.name, + }; + }); + const values = await FormDialog(t('Create calendar block'), () => { + return ( + + + + + + ); + }).open({ + initialValues: {}, }); - const dateFields = collection?.fields - ?.filter((field) => field.type === 'date') - ?.map((field) => { - return { - label: field?.uiSchema?.title, - value: field.name, - }; - }); - const values = await FormDialog(t('Create calendar block'), () => { - return ( - - - - - + insert( + createCalendarBlockSchema({ + collection: field.target, + resource, + association: resource, + fieldNames: { + ...values, + }, + }), ); - }).open({ - initialValues: {}, - }); - insert( - createCalendarBlockSchema({ - collection: field.target, - resource, - association: resource, - fieldNames: { - ...values, - }, - }), - ); } }} items={useRecordCollectionDataSourceItems('Calendar', item, field.target, resource)} /> ); -} +}; export const RecordAssociationBlockInitializer = (props) => { const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; diff --git a/yarn.lock b/yarn.lock index c1c842d71..12187409b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17934,7 +17934,7 @@ prop-types-exact@^1.2.0: object.assign "^4.1.0" reflect.ownkeys "^0.2.0" -prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.7.1: +prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.7.1, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -18937,6 +18937,13 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1: react-is "^16.8.6" scheduler "^0.19.1" +react-to-print@^2.14.7: + version "2.14.7" + resolved "https://registry.npmmirror.com/react-to-print/-/react-to-print-2.14.7.tgz#d288de0e573e6e63e203a48d4bad7a1759d18688" + integrity sha512-lWVVAs9Co25uyE0toxcWeFsmaZObwUozXrJD9WMpDPclpBgk+WIzxlt3Q3omL/BCBG/cpf0XNvhayUWa+99YGw== + dependencies: + prop-types "^15.8.1" + react@16.x, react@^16.8.6: version "16.14.0" resolved "https://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"