From d333ad201ab6efe3cd5820a92d98b6e8318d6ffa Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Fri, 4 Aug 2023 09:48:03 +0800 Subject: [PATCH] feat(association-field): association field support sub-form(popover) (#2373) * refactor: association field support sub-form(popover) * refactor: code improve * refactor: code improve * refactor: sub-table support field component cinfig * refactor: sub-table support field component cinfig * refactor: sub-table support field component cinfig * refactor: code improve * refactor: code improve * refactor: useAssociationNames * refactor: locale improve * refactor: locale improve * refactor: code improve * refactor: code improve * refactor: code improve * fix: code improve * refactor: code refactor * style: style improve * Update database.ts * refactor: code imporve * refactor: code imporve * style: style improve * style: style improve * refactor: code imporve * style: style improve * refactor: code imporve --------- Co-authored-by: chenos --- .../client/src/block-provider/hooks/index.ts | 40 +++----- packages/core/client/src/locale/en_US.ts | 1 + packages/core/client/src/locale/ja_JP.ts | 1 + packages/core/client/src/locale/zh_CN.ts | 1 + .../AssociationFieldProvider.tsx | 19 +--- .../antd/association-field/Editable.tsx | 2 + .../antd/association-field/FileManager.tsx | 14 +-- .../antd/association-field/InternalNester.tsx | 99 ++++++++++--------- .../antd/association-field/InternalPicker.tsx | 20 ++-- .../InternalPopoverNester.tsx | 87 ++++++++++++++++ .../association-field/InternalSubTable.tsx | 8 +- .../antd/association-field/InternalViewer.tsx | 4 +- .../antd/association-field/Nester.tsx | 1 + .../antd/table-v2/Table.Column.Designer.tsx | 62 ++++++------ .../hooks/useFieldModeOptions.tsx | 28 +++--- packages/core/database/src/database.ts | 11 +++ 16 files changed, 239 insertions(+), 159 deletions(-) create mode 100644 packages/core/client/src/schema-component/antd/association-field/InternalPopoverNester.tsx diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 0fcddc7b7..6082e0c71 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -151,12 +151,8 @@ export const useCreateActionProps = () => { return { async onClick() { const fieldNames = fields.map((field) => field.name); - const { - assignedValues: originalAssignedValues = {}, - onSuccess, - overwriteValues, - skipValidator, - } = actionSchema?.['x-action-settings'] ?? {}; + const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } = + actionSchema?.['x-action-settings'] ?? {}; const addChild = fieldSchema?.['x-component-props']?.addChild; const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser }); if (!skipValidator) { @@ -226,12 +222,8 @@ export const useAssociationCreateActionProps = () => { return { async onClick() { const fieldNames = fields.map((field) => field.name); - const { - assignedValues: originalAssignedValues = {}, - onSuccess, - overwriteValues, - skipValidator, - } = actionSchema?.['x-action-settings'] ?? {}; + const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } = + actionSchema?.['x-action-settings'] ?? {}; const addChild = fieldSchema?.['x-component-props']?.addChild; const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser }); if (!skipValidator) { @@ -415,11 +407,8 @@ export const useCustomizeUpdateActionProps = () => { return { async onClick() { - const { - assignedValues: originalAssignedValues = {}, - onSuccess, - skipValidator, - } = actionSchema?.['x-action-settings'] ?? {}; + const { assignedValues: originalAssignedValues = {}, onSuccess, skipValidator } = + actionSchema?.['x-action-settings'] ?? {}; const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser }); if (skipValidator === false) { await form.submit(); @@ -473,11 +462,8 @@ export const useCustomizeBulkUpdateActionProps = () => { return { async onClick() { - const { - assignedValues: originalAssignedValues = {}, - onSuccess, - updateMode, - } = actionSchema?.['x-action-settings'] ?? {}; + const { assignedValues: originalAssignedValues = {}, onSuccess, updateMode } = + actionSchema?.['x-action-settings'] ?? {}; actionField.data = field.data || {}; actionField.data.loading = true; const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentUser }); @@ -732,12 +718,8 @@ export const useUpdateActionProps = () => { return { async onClick() { - const { - assignedValues: originalAssignedValues = {}, - onSuccess, - overwriteValues, - skipValidator, - } = actionSchema?.['x-action-settings'] ?? {}; + const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } = + actionSchema?.['x-action-settings'] ?? {}; const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser }); if (!skipValidator) { await form.submit(); @@ -1124,7 +1106,7 @@ export const useAssociationNames = () => { const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name; const path = prefix === '' || !prefix ? fieldPath : prefix + '.' + fieldPath; appends.add(path); - if (['Nester', 'SubTable'].includes(s['x-component-props']?.mode)) { + if (['Nester', 'SubTable', 'PopoverNester'].includes(s['x-component-props']?.mode)) { updateAssociationValues.add(path); const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name; getAssociationAppends(s, bufPrefix); diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 73e707a81..bc430a7ba 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -251,6 +251,7 @@ export default { "Link to description": "Used to create collection relationships quickly and compatible with most common scenarios. Suitable for non-developer use. When present as a field, it is a drop-down selection used to select records from the target collection. Once created, it will simultaneously generate the associated fields of the current collection in the target collection.", "Sub-table": "Sub-table", "Sub-details":"Sub-details", + "Sub-form(Popover)":"Sub-form(Popover)", "System info": "System info", "Created at": "Created at", "Last updated at": "Last updated at", diff --git a/packages/core/client/src/locale/ja_JP.ts b/packages/core/client/src/locale/ja_JP.ts index 0760d6d8f..2a80965a7 100644 --- a/packages/core/client/src/locale/ja_JP.ts +++ b/packages/core/client/src/locale/ja_JP.ts @@ -222,6 +222,7 @@ export default { "Link to": "リンク", "Link to description": "コレクションの関連付けを素早く作成するためにしようされ、ほとんどの一般的なシナリオに対応しています。開発者以外の方のしようにも適しています。フィールドとして存在する場合、参照元コレクションのレコードを選択するために使用されるドロップダウンです。一度作成されると、参照先コレクションに現在のコレクションの関連フィールドが同時に生成されます。", "Sub-table": "サブテーブル", + "Sub-form(Popover)":"サブフォーム(ポップアップ窓)", "System info": "システム情報", "Created at": "作成日", "Last updated at": "最終更新日", diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index 3eef3c722..b04397473 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -317,6 +317,7 @@ export default { "Select file": "选择文件", "Subtable": "子表格", "Sub-form": "子表单", + "Sub-form(Popover)":"子表单(弹窗)", "Sub-details":"子详情", "Record picker": "数据选择器", "Toggles the subfield mode": "切换子字段模式", diff --git a/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx b/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx index 0a6cd461a..5487733f5 100644 --- a/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx @@ -28,19 +28,6 @@ export const AssociationFieldProvider = observer( [fieldSchema['x-component-props']?.mode], ); - // const targetKeyValue = useMemo(() => { - // if (!field.value) return ''; - // if (['belongsTo', 'hasOne'].includes(collectionField.type)) { - // return field.value[collectionField.targetKey] ?? ''; - // } - // if (['belongsToMany', 'hasMany'].includes(collectionField.type)) { - // if (Array.isArray(field.value)) { - // return field.value.map((v) => v[collectionField.targetKey] ?? '').join(','); - // } - // } - // return ''; - // }, [collectionField, field.value]); - const fieldValue = useMemo(() => JSON.stringify(field.value), [field.value]); const [loading, setLoading] = useState(true); @@ -52,7 +39,7 @@ export const AssociationFieldProvider = observer( return; } // 如果是表单模板数据,使用子表单和子表格组件时,过滤掉关系 ID - if (field.value && field.form['__template'] && ['Nester', 'SubTable'].includes(currentMode)) { + if (field.value && field.form['__template'] && ['Nester', 'SubTable', 'PopoverNester'].includes(currentMode)) { if (['belongsTo', 'hasOne'].includes(collectionField.type)) { if (field.value?.[collectionField.targetKey]) { delete field.value[collectionField.targetKey]; @@ -69,7 +56,7 @@ export const AssociationFieldProvider = observer( } if (field.value !== null && field.value !== undefined) { // Nester 子表单时,如果没数据初始化一个 [null] 的占位 - if (currentMode === 'Nester' && Array.isArray(field.value)) { + if (['Nester', 'PopoverNester'].includes(currentMode) && Array.isArray(field.value)) { if (field.value.length === 0 && ['belongsToMany', 'hasMany'].includes(collectionField.type)) { field.value = [{}]; } @@ -77,7 +64,7 @@ export const AssociationFieldProvider = observer( setLoading(false); return; } - if (currentMode === 'Nester') { + if (['Nester', 'PopoverNester'].includes(currentMode)) { if (['belongsTo', 'hasOne'].includes(collectionField.type)) { field.value = {}; } else if (['belongsToMany', 'hasMany'].includes(collectionField.type)) { diff --git a/packages/core/client/src/schema-component/antd/association-field/Editable.tsx b/packages/core/client/src/schema-component/antd/association-field/Editable.tsx index fce5c5e5e..eace48a76 100644 --- a/packages/core/client/src/schema-component/antd/association-field/Editable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/Editable.tsx @@ -9,6 +9,7 @@ import { InternalFileManager } from './FileManager'; import { InternalNester } from './InternalNester'; import { InternalPicker } from './InternalPicker'; import { InternalSubTable } from './InternalSubTable'; +import { InternaPopoverNester } from './InternalPopoverNester'; import { CreateRecordAction } from './components/CreateRecordAction'; import { useAssociationFieldContext } from './hooks'; @@ -45,6 +46,7 @@ const EditableAssociationField = observer( {currentMode === 'Picker' && } {currentMode === 'Nester' && } + {currentMode === 'PopoverNester' && } {currentMode === 'Select' && } {currentMode === 'SubTable' && } {currentMode === 'FileManager' && } diff --git a/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx b/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx index a8b937d99..7958ea5f8 100644 --- a/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx @@ -23,13 +23,9 @@ import { flatData, getLabelFormatValue, isShowFilePicker, useLabelUiSchema } fro const useTableSelectorProps = () => { const field: any = useField(); - const { - multiple, - options = [], - setSelectedRows, - selectedRows: rcSelectRows = [], - onChange, - } = useContext(RecordPickerContext); + const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext( + RecordPickerContext, + ); const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp(); const { setVisible } = useActionContext(); return { @@ -72,6 +68,7 @@ const InternalFileManager = (props) => { const collectionField = getField(field.props.name); const labelUiSchema = useLabelUiSchema(collectionField?.target, fieldNames?.label || 'label'); const compile = useCompile(); + const { setVisible, modalProps } = useActionContext(); const getFilter = () => { const targetKey = collectionField?.targetKey || 'id'; const list = options.map((option) => option[targetKey]).filter(Boolean); @@ -119,7 +116,6 @@ const InternalFileManager = (props) => { collectionField, }; const usePickActionProps = () => { - const { setVisible } = useActionContext(); const { multiple, selectedRows, onChange, options, collectionField } = useContext(RecordPickerContext); return { onClick() { @@ -159,7 +155,7 @@ const InternalFileManager = (props) => { visible: visibleSelector, setVisible: setVisibleSelector, modalProps: { - getContainer: others?.getContainer, + getContainer: others?.getContainer || modalProps?.getContainer, }, formValueChanged: false, }} diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx index c885e1970..bec008eb9 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx @@ -1,58 +1,61 @@ import { css, cx } from '@emotion/css'; import { FormLayout } from '@formily/antd-v5'; -import { RecursionField, useField, useFieldSchema } from '@formily/react'; +import { RecursionField, useField, useFieldSchema, observer } from '@formily/react'; import React, { useEffect } from 'react'; import { CollectionProvider } from '../../../collection-manager'; import { useAssociationFieldContext, useInsertSchema } from './hooks'; import schema from './schema'; -export const InternalNester = () => { - const field = useField(); - const fieldSchema = useFieldSchema(); - const insertNester = useInsertSchema('Nester'); - const { options: collectionField } = useAssociationFieldContext(); - const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true; - useEffect(() => { - insertNester(schema.Nester); - }, []); - return ( - - -
{ + const field = useField(); + const fieldSchema = useFieldSchema(); + const insertNester = useInsertSchema('Nester'); + const { options: collectionField } = useAssociationFieldContext(); + const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true; + useEffect(() => { + insertNester(schema.Nester); + }, []); + return ( + + +
.ant-card-bordered { - border: none; + .ant-divider-horizontal { + margin: 10px 0; } - `]: showTitle === false, - }, - )} - > - { - return s['x-component'] === 'AssociationField.Nester'; - }} - /> -
-
-
- ); -}; + `, + { + [css` + .ant-card-body { + padding: 0px 20px 20px 0px; + } + > .ant-card-bordered { + border: none; + } + `]: showTitle === false, + }, + )} + > + { + return s['x-component'] === 'AssociationField.Nester'; + }} + /> +
+
+
+ ); + }, + { displayName: 'InternalNester' }, +); diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx index 67ae3708e..7c14633f0 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx @@ -23,13 +23,9 @@ import { flatData, getLabelFormatValue, useLabelUiSchema } from './util'; const useTableSelectorProps = () => { const field: any = useField(); - const { - multiple, - options = [], - setSelectedRows, - selectedRows: rcSelectRows = [], - onChange, - } = useContext(RecordPickerContext); + const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext( + RecordPickerContext, + ); const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp(); const { setVisible } = useActionContext(); return { @@ -62,7 +58,7 @@ const useTableSelectorProps = () => { export const InternalPicker = observer( (props: any) => { - const { value, multiple, onChange, quickUpload, selectFile, ...others } = props; + const { value, multiple, onChange, quickUpload, selectFile, shouldMountElement, ...others } = props; const field: any = useField(); const fieldNames = useFieldNames(props); const [visibleSelector, setVisibleSelector] = useState(false); @@ -176,7 +172,13 @@ export const InternalPicker = observer( )} - + diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalPopoverNester.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalPopoverNester.tsx new file mode 100644 index 000000000..ec090f5c9 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/association-field/InternalPopoverNester.tsx @@ -0,0 +1,87 @@ +import { Popover } from 'antd'; +import { css } from '@emotion/css'; +import { EditOutlined } from '@ant-design/icons'; +import { observer } from '@formily/react'; +import React, { useContext, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ReadPrettyInternalViewer } from './InternalViewer'; +import { InternalNester } from './InternalNester'; +import { useAssociationFieldContext } from './hooks'; +import { ActionContextProvider, ActionContext } from '../action/context'; + +export const InternaPopoverNester = observer( + (props) => { + const { options } = useAssociationFieldContext(); + const [visible, setVisible] = useState(false); + const { t } = useTranslation(); + const ref = useRef(); + const nesterProps = { + ...props, + shouldMountElement: true, + }; + const content = ( +
+ +
+ ); + const titleProps = { + ...props, + enableLink: true, + }; + const getContainer = () => ref.current; + const ctx = useContext(ActionContext); + const modalProps = { + getContainer: getContainer, + }; + return ( + + setVisible(open)} + title={t(options?.uiSchema?.rawTitle)} + > + +
+ +
+ +
+
+ {visible && ( +
setVisible(false)} + className={css` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: transparent; + z-index: 9999; + `} + /> + )} + + ); + }, + { displayName: 'InternaPopoverNester' }, +); diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalSubTable.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalSubTable.tsx index 7775f27b5..e33344910 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalSubTable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalSubTable.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { FormItem, FormLayout } from '@formily/antd-v5'; +import { FormLayout } from '@formily/antd-v5'; import { RecursionField, observer, useField, useFieldSchema, SchemaOptionsContext } from '@formily/react'; import React, { useEffect } from 'react'; import { CollectionProvider } from '../../../collection-manager'; @@ -22,7 +22,6 @@ export const InternalSubTable = observer( const option = useSchemaOptionsContext(); const components = { ...option.components, - FormItem: (props) => , 'Radio.Group': Select, 'Checkbox.Group': (props) =>