diff --git a/packages/core/client/src/block-provider/TableSelectorProvider.tsx b/packages/core/client/src/block-provider/TableSelectorProvider.tsx index 8e5f8b626..ff14b422c 100644 --- a/packages/core/client/src/block-provider/TableSelectorProvider.tsx +++ b/packages/core/client/src/block-provider/TableSelectorProvider.tsx @@ -268,10 +268,17 @@ export const useTableSelectorContext = () => { export const useTableSelectorProps = () => { const field = useField(); const ctx = useTableSelectorContext(); + const fieldSchema = useFieldSchema(); + const { getCollectionJoinField } = useCollectionManager(); + const collectionFieldSchema = recursiveParent(fieldSchema, 'CollectionField'); + const collectionField = getCollectionJoinField(collectionFieldSchema?.['x-collection-field']); useEffect(() => { if (!ctx?.service?.loading) { - field.value = ctx?.service?.data?.data; - field?.setInitialValue?.(ctx?.service?.data?.data); + const data = ctx?.service?.data?.data.map((v) => { + return _.omit(v, collectionField?.foreignKey); + }); + field.value = data; + field?.setInitialValue?.(data); field.data = field.data || {}; field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys; field.componentProps.pagination = field.componentProps.pagination || {}; diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 1ab0ce7a8..303cb49ad 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -184,6 +184,7 @@ export const useCreateActionProps = () => { const compile = useCompile(); const { modal } = App.useApp(); const { t } = useTranslation(); + const { updateAssociationValues } = useFormBlockContext(); const collectValues = useCollectValuesToSubmit(); const action = actionField.componentProps.saveMode || 'create'; const filterKeys = actionField.componentProps.filterKeys?.checked || []; @@ -205,6 +206,7 @@ export const useCreateActionProps = () => { triggerWorkflows: triggerWorkflows?.length ? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',') : undefined, + updateAssociationValues, }); setVisible?.(false); actionField.data.loading = false; diff --git a/packages/core/client/src/locale/fr_FR.json b/packages/core/client/src/locale/fr_FR.json index 224d9ab2e..0f01f1572 100644 --- a/packages/core/client/src/locale/fr_FR.json +++ b/packages/core/client/src/locale/fr_FR.json @@ -749,5 +749,7 @@ "Current form": "Formulaire actuel", "Current object": "Objet actuel", "Linkage with form fields": "Lien avec les champs de formulaire", - "Allow add new, update and delete actions": "Autoriser les actions d'ajout, de mise à jour et de suppression" + "Allow add new, update and delete actions": "Autoriser les actions d'ajout, de mise à jour et de suppression", + "Allow add new":"Autoriser les ajouts", + "Allow selection of existing records":"Permet de sélectionner des données existantes" } diff --git a/packages/core/client/src/locale/ja_JP.json b/packages/core/client/src/locale/ja_JP.json index b4ca532c6..5cc4e685b 100644 --- a/packages/core/client/src/locale/ja_JP.json +++ b/packages/core/client/src/locale/ja_JP.json @@ -666,5 +666,7 @@ "Search plugin": "プラグインを検索", "Author": "著者", "Plugin loading failed. Please check the server logs.": "プラグインのロードに失敗しました。サーバーログを確認してください。", - "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "テーブルに依存するオブジェクト、およびそれらに依存するオブジェクトを自動的に削除する" + "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "テーブルに依存するオブジェクト、およびそれらに依存するオブジェクトを自動的に削除する", + "Allow add new":"新規作成を許可", + "Allow selection of existing records":"既存のデータの選択を許可" } diff --git a/packages/core/client/src/locale/pt_BR.json b/packages/core/client/src/locale/pt_BR.json index c44ded9d8..d8bf32994 100644 --- a/packages/core/client/src/locale/pt_BR.json +++ b/packages/core/client/src/locale/pt_BR.json @@ -708,5 +708,7 @@ "Data template": "Modelo de dados", "Not found": "Não encontrado", "Add": "Adicionar", - "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Excluir automaticamente objetos que dependem desta tabela, bem como objetos que dependem desses objetos" + "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Excluir automaticamente objetos que dependem desta tabela, bem como objetos que dependem desses objetos", + "Allow add new":"Permitir novas adições", + "Allow selection of existing records":"Permitir a selecção dos registos existentes" } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index b7fbc8d25..69a48f683 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -839,5 +839,7 @@ "Are you sure you want to perform the {{title}} action?": "你确定执行{{title}}操作吗?", "Sign in with another account": "登录其他账号", "Return to the main application": "返回主应用", - "Permission denied": "没有权限" + "Permission denied": "没有权限", + "Allow add new":"允许新增", + "Allow selection of existing records":"允许选择已有数据" } diff --git a/packages/core/client/src/modules/form-creation/__e2e__/schemaSettings.test.ts b/packages/core/client/src/modules/form-creation/__e2e__/schemaSettings.test.ts index 3837d283e..c7cd813d2 100644 --- a/packages/core/client/src/modules/form-creation/__e2e__/schemaSettings.test.ts +++ b/packages/core/client/src/modules/form-creation/__e2e__/schemaSettings.test.ts @@ -597,7 +597,10 @@ test.describe('creation form block schema settings', () => { await page.mouse.move(300, 0); // 当新增一行时,应该显示默认值 - await page.getByRole('button', { name: 'plus' }).click(); + await page + .getByTestId('drawer-Action.Container-general-Add record') + .getByRole('button', { name: 'Add new' }) + .click(); await expect( page .getByRole('cell', { name: 'block-item-CollectionField-users-form-users.nickname-Nickname' }) 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 2244e2454..0862b66e4 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 @@ -21,7 +21,7 @@ import { useAssociationFieldContext, useFieldNames, useInsertSchema } from './ho import schema from './schema'; import { flatData, getLabelFormatValue, useLabelUiSchema } from './util'; -const useTableSelectorProps = () => { +export const useTableSelectorProps = () => { const field: any = useField(); const { multiple, 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 6807aa979..5512583cf 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 @@ -14,13 +14,18 @@ export const InternalSubTable = observer( const field: any = useField(); const fieldSchema = useFieldSchema(); const insert = useInsertSchema('SubTable'); + const insertSelector = useInsertSchema('Selector'); const { options } = useAssociationFieldContext(); const { actionName } = useACLActionParamsContext(); useEffect(() => { insert(schema.SubTable); field.required = fieldSchema['required']; }, []); - + useEffect(() => { + if (field.componentProps?.allowSelectExistingRecord) { + insertSelector(schema.Selector); + } + }, [field.componentProps?.allowSelectExistingRecord]); const option = useSchemaOptionsContext(); const components = { ...option.components, diff --git a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx index 5ab8c4bcc..d7ec62b96 100644 --- a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx @@ -1,21 +1,44 @@ -import { PlusOutlined } from '@ant-design/icons'; import { css } from '@emotion/css'; import { ArrayField } from '@formily/core'; import { exchangeArrayState } from '@formily/core/esm/shared/internals'; -import { observer } from '@formily/react'; +import { observer, RecursionField, useFieldSchema } from '@formily/react'; import { action } from '@formily/reactive'; import { isArr } from '@formily/shared'; import { Button } from 'antd'; -import React from 'react'; +import { omit, unionBy, uniqBy } from 'lodash'; +import React, { useState, useMemo, useContext } from 'react'; +import { useTranslation } from 'react-i18next'; import { FormActiveFieldsProvider } from '../../../block-provider'; import { FlagProvider } from '../../../flag-provider'; import { Table } from '../table-v2/Table'; -import { useAssociationFieldContext } from './hooks'; +import { useAssociationFieldContext, useFieldNames } from './hooks'; +import { ActionContextProvider } from '../action'; +import { + FormProvider, + RecordPickerProvider, + SchemaComponentOptions, + useActionContext, + RecordPickerContext, +} from '../..'; +import { getLabelFormatValue, useLabelUiSchema } from './util'; +import { CollectionProvider } from '../../../collection-manager'; +import { TableSelectorParamsProvider } from '../../../block-provider/TableSelectorProvider'; +import { useCompile } from '../../hooks'; +import { useTableSelectorProps } from './InternalPicker'; +import { useCreateActionProps } from '../../../block-provider/hooks'; export const SubTable: any = observer( (props: any) => { - const { field } = useAssociationFieldContext(); - // useSubTableSpecialCase({ field }); + const { openSize } = props; + const { field, options: collectionField } = useAssociationFieldContext(); + const { t } = useTranslation(); + const [visibleSelector, setVisibleSelector] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + const fieldNames = useFieldNames(props); + const fieldSchema = useFieldSchema(); + const compile = useCompile(); + const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); + const move = (fromIndex: number, toIndex: number) => { if (toIndex === undefined) return; if (!isArr(field.value)) return; @@ -32,6 +55,55 @@ export const SubTable: any = observer( }); }; field.move = move; + + const options = useMemo(() => { + if (field.value && Object.keys(field.value).length > 0) { + const opts = (Array.isArray(field.value) ? field.value : field.value ? [field.value] : []) + .filter(Boolean) + .map((option) => { + const label = option?.[fieldNames.label]; + return { + ...option, + [fieldNames.label]: getLabelFormatValue(compile(labelUiSchema), compile(label)), + }; + }); + return opts; + } + return []; + }, [field.value, fieldNames?.label]); + + const pickerProps = { + size: 'small', + fieldNames: field.componentProps.fieldNames, + multiple: true, + association: { + target: collectionField?.target, + }, + options, + onChange: props?.onChange, + selectedRows, + setSelectedRows, + collectionField, + }; + const usePickActionProps = () => { + const { setVisible } = useActionContext(); + const { selectedRows, options, collectionField } = useContext(RecordPickerContext); + return { + onClick() { + const selectData = unionBy(selectedRows, options, collectionField?.targetKey || 'id'); + const data = field.value || []; + field.value = uniqBy(data.concat(selectData), collectionField?.targetKey || 'id'); + field.onInput(field.value); + setVisible(false); + }, + }; + }; + const getFilter = () => { + const targetKey = collectionField?.targetKey || 'id'; + const list = options.map((option) => option[targetKey]).filter(Boolean); + const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {}; + return filter; + }; return (
field.editable && ( - + <> + {field.componentProps?.allowAddnew !== false && ( + + )} + {field.componentProps?.allowSelectExistingRecord && ( + + )} + ) } isSubTable={true} /> + + + + + + + { + return s['x-component'] === 'AssociationField.Selector'; + }} + /> + + + + + +
); }, diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx index 42cbb68b9..f3f9acae7 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx @@ -32,6 +32,72 @@ export const formItemSettings = new SchemaSettings({ name: 'FormItemSettings', items: [ ...(generalSettingsItems as any), + { + name: 'allowAddNewData', + type: 'switch', + useVisible() { + const readPretty = useIsFieldReadPretty(); + const isAssociationField = useIsAssociationField(); + const fieldMode = useFieldMode(); + return !readPretty && isAssociationField && ['SubTable'].includes(fieldMode); + }, + useComponentProps() { + const { t } = useTranslation(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn, refresh } = useDesignable(); + return { + title: t('Allow add new'), + checked: fieldSchema['x-component-props']?.allowAddnew !== (false as boolean), + onChange(value) { + const schema = { + ['x-uid']: fieldSchema['x-uid'], + }; + field.componentProps.allowAddnew = value; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].allowAddnew = value; + schema['x-component-props'] = fieldSchema['x-component-props']; + dn.emit('patch', { + schema, + }); + refresh(); + }, + }; + }, + }, + { + name: 'allowSelectExistingRecord', + type: 'switch', + useVisible() { + const readPretty = useIsFieldReadPretty(); + const isAssociationField = useIsAssociationField(); + const fieldMode = useFieldMode(); + return !readPretty && isAssociationField && ['SubTable'].includes(fieldMode); + }, + useComponentProps() { + const { t } = useTranslation(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn, refresh } = useDesignable(); + return { + title: t('Allow selection of existing records'), + checked: fieldSchema['x-component-props']?.allowSelectExistingRecord, + onChange(value) { + const schema = { + ['x-uid']: fieldSchema['x-uid'], + }; + field.componentProps.allowSelectExistingRecord = value; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].allowSelectExistingRecord = value; + schema['x-component-props'] = fieldSchema['x-component-props']; + dn.emit('patch', { + schema, + }); + refresh(); + }, + }; + }, + }, { name: 'quickUpload', type: 'switch', diff --git a/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/manyToMany/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/manyToMany/schemaSettings.test.ts index d4e66140c..8ab817f29 100644 --- a/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/manyToMany/schemaSettings.test.ts +++ b/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/manyToMany/schemaSettings.test.ts @@ -266,9 +266,9 @@ test.describe('form item & create form', () => { // 选择 Sub-form await (async (page: Page, fieldName: string) => { await page.getByLabel(`block-item-CollectionField-general-form-general.${fieldName}-${fieldName}`).hover(); - await page - .getByLabel(`designer-schema-settings-CollectionField-FormItem.Designer-general-general.${fieldName}`) - .hover(); + // await page + // .getByLabel(`designer-schema-settings-CollectionField-FormItem.Designer-general-general.${fieldName}`) + // .hover(); })(page, 'manyToMany'); await page.getByRole('menuitem', { name: 'Field component' }).click(); await page.getByRole('option', { name: 'Sub-form', exact: true }).click(); diff --git a/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts index d9f07122c..6a10e6b98 100644 --- a/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts +++ b/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts @@ -419,7 +419,7 @@ test.describe('table column & sub-table in edit form', () => { ], variableValue: ['Current user', 'Nickname'], expectVariableValue: async () => { - await page.getByRole('button', { name: 'plus' }).click(); + await page.getByRole('button', { name: 'Add new' }).click(); await expect( page .getByLabel('block-item-CollectionField-general-form-general.singleLineText-singleLineText')