diff --git a/lerna.json b/lerna.json index 7c7dd04f5e..5ba3e4104a 100644 --- a/lerna.json +++ b/lerna.json @@ -2,9 +2,7 @@ "version": "1.6.0-alpha.5", "npmClient": "yarn", "useWorkspaces": true, - "npmClientArgs": [ - "--ignore-engines" - ], + "npmClientArgs": ["--ignore-engines"], "command": { "version": { "forcePublish": true, diff --git a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx index 4f25315cd5..03a0042c38 100644 --- a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx @@ -22,6 +22,7 @@ import { } from '../../../../schema-component/antd/form-item/FormItem.Settings'; import { linkageRules, setDefaultSortingRules } from '../SubTable/subTablePopoverComponentFieldSettings'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { allowSelectExistingRecord } from '../SubTable/subTablePopoverComponentFieldSettings'; const allowMultiple: any = { name: 'allowMultiple', @@ -108,6 +109,7 @@ export const subformComponentFieldSettings = new SchemaSettings({ items: [ fieldComponent, allowMultiple, + allowSelectExistingRecord, { name: 'allowDissociate', type: 'switch', diff --git a/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx index a2c60dd972..b49b48a392 100644 --- a/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx @@ -72,7 +72,7 @@ const fieldComponent: any = { }; }, }; -const allowSelectExistingRecord = { +export const allowSelectExistingRecord = { name: 'allowSelectExistingRecord', type: 'switch', useVisible() { 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 0483d86cc8..26efe21856 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 @@ -44,6 +44,7 @@ export const InternalNester = observer( const field = useField(); const fieldSchema = useFieldSchema(); const insertNester = useInsertSchema('Nester'); + const insertSelector = useInsertSchema('Selector'); const { options: collectionField } = useAssociationFieldContext(); const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true; const { actionName } = useACLActionParamsContext(); @@ -58,7 +59,11 @@ export const InternalNester = observer( useEffect(() => { insertNester(schema.Nester); }, []); - + useEffect(() => { + if (field.componentProps?.allowSelectExistingRecord) { + insertSelector(schema.Selector); + } + }, [field.componentProps?.allowSelectExistingRecord]); return ( diff --git a/packages/core/client/src/schema-component/antd/association-field/Nester.tsx b/packages/core/client/src/schema-component/antd/association-field/Nester.tsx index b692e43ed6..5c070ef355 100644 --- a/packages/core/client/src/schema-component/antd/association-field/Nester.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/Nester.tsx @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { CloseOutlined, PlusOutlined } from '@ant-design/icons'; +import { CloseOutlined, PlusOutlined, ZoomInOutlined } from '@ant-design/icons'; import { css } from '@emotion/css'; import { ArrayField } from '@formily/core'; import { spliceArrayState } from '@formily/core/esm/shared/internals'; @@ -15,11 +15,12 @@ import { observer, useFieldSchema } from '@formily/react'; import { action } from '@formily/reactive'; import { each } from '@formily/shared'; import { useUpdate } from 'ahooks'; -import { Button, Card, Divider, Tooltip } from 'antd'; -import React, { useCallback, useContext } from 'react'; +import { Button, Card, Divider, Tooltip, Space } from 'antd'; +import React, { useCallback, useContext, useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { FormActiveFieldsProvider } from '../../../block-provider/hooks/useFormActiveFields'; -import { useCollection } from '../../../data-source'; +import { useCollection, CollectionProvider } from '../../../data-source'; +import { useCreateActionProps } from '../../../block-provider/hooks'; import { useCollectionRecord, useCollectionRecordData, @@ -29,13 +30,25 @@ import { FlagProvider } from '../../../flag-provider'; import { NocoBaseRecursionField, RefreshComponentProvider } from '../../../formily/NocoBaseRecursionField'; import { RecordIndexProvider, RecordProvider } from '../../../record-provider'; import { isPatternDisabled, isSystemField } from '../../../schema-settings'; +import { TableSelectorParamsProvider } from '../../../block-provider/TableSelectorProvider'; import { DefaultValueProvider, IsAllowToSetDefaultValueParams, interfacesOfUnsupportedDefaultValue, } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue'; import { AssociationFieldContext } from './context'; -import { SubFormProvider, useAssociationFieldContext } from './hooks'; +import { SubFormProvider, useAssociationFieldContext, useFieldNames } from './hooks'; +import { Action, ActionContextProvider } from '../action'; +import { useCompile } from '../../hooks'; +import { + FormProvider, + RecordPickerProvider, + SchemaComponentOptions, + useActionContext, + RecordPickerContext, +} from '../..'; +import { useTableSelectorProps } from './InternalPicker'; +import { getLabelFormatValue, useLabelUiSchema } from './util'; export const Nester = (props) => { const { options } = useContext(AssociationFieldContext); @@ -109,14 +122,32 @@ const ToOneNester = (props) => { }; const ToManyNester = observer( - (props) => { + (props: any) => { const fieldSchema = useFieldSchema(); - const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext(); + const { + options: collectionField, + field, + allowMultiple, + allowDissociate, + currentMode, + } = useAssociationFieldContext(); + const { allowSelectExistingRecord } = field.componentProps; const { t } = useTranslation(); const recordData = useCollectionRecordData(); const collection = useCollection(); const update = useUpdate(); - + const [visibleSelector, setVisibleSelector] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + const fieldNames = useFieldNames(props); + const compile = useCompile(); + const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); + const useNesterSelectProps = () => { + return { + run() { + setVisibleSelector(true); + }, + }; + }; if (!Array.isArray(field.value)) { field.value = []; } @@ -146,6 +177,54 @@ const ToManyNester = observer( ); }, []); + const usePickActionProps = () => { + const { setVisible } = useActionContext(); + const { selectedRows, setSelectedRows } = useContext(RecordPickerContext); + return { + onClick() { + selectedRows.map((v) => field.value.push(markRecordAsNew(v))); + field.onInput(field.value); + field.initialValue = field.value; + setSelectedRows([]); + setVisible(false); + }, + }; + }; + 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 getFilter = () => { + const targetKey = collectionField?.targetKey || 'id'; + const list = (field.value || []).map((option) => option?.[targetKey]).filter(Boolean); + const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {}; + return filter; + }; return field.value.length > 0 ? ( { let allowed = allowDissociate; if (!allowDissociate) { - allowed = !value?.[options.targetKey]; + allowed = !value?.[collectionField.targetKey]; } return (
- {field.editable && allowMultiple && ( - - { - action(() => { - if (!Array.isArray(field.value)) { - field.value = []; - } - field.value.splice(index + 1, 0, markRecordAsNew({})); - each(field.form.fields, (targetField, key) => { - if (!targetField) { - delete field.form.fields[key]; - } - }); - return field.onInput(field.value); - }); - }} - /> - - )} {!field.readPretty && allowed && ( + ); })} + + {field.editable && allowMultiple && ( + { + return { + onClick: () => { + action(() => { + if (!Array.isArray(field.value)) { + field.value = []; + } + const index = field.value.length; + field.value.splice(index, 0, markRecordAsNew({})); + each(field.form.fields, (targetField, key) => { + if (!targetField) { + delete field.form.fields[key]; + } + }); + return field.onInput(field.value); + }); + }, + }; + }} + title={ + + {t('Add new')} + + } + /> + )} + {field.editable && allowSelectExistingRecord && currentMode === 'Nester' && ( + + {t('Select record')} + + } + /> + )} + + + + + + + + { + return s['x-component'] === 'AssociationField.Selector'; + }} + /> + + + + + + ) : ( <> 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 7a4c239c3d..b8f4b4d94b 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 @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { PlusSquareOutlined, ZoomInOutlined } from '@ant-design/icons'; +import { PlusOutlined, ZoomInOutlined } from '@ant-design/icons'; import { css } from '@emotion/css'; import { ArrayField } from '@formily/core'; import { exchangeArrayState } from '@formily/core/esm/shared/internals'; @@ -259,7 +259,7 @@ export const SubTable: any = observer( useAction={useSubTableAddNewProps} title={ - {t('Add new')} + {t('Add new')} } /> diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts index cae4f2ca3f..fd2f45e705 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/singleLineText/schemaSettings.test.ts @@ -54,14 +54,10 @@ test.describe('form item & sub form', () => { unsupportedVariables: ['Parent popup record'], variableValue: ['Current user', 'Nickname'], expectVariableValue: async () => { - await page - .getByLabel('block-item-CollectionField-subform-form-subform.manyToMany-manyToMany') - .getByRole('img', { name: 'plus' }) - .first() - .click(); + await page.locator('.nb-sub-form-addNew').first().click(); - // 在第一条数据下面增加一条数据 - await expect(page.getByLabel('block-item-CollectionField-').nth(2).getByRole('textbox')).toHaveValue( + // 在最下面增加一条数据 + await expect(page.getByLabel('block-item-CollectionField-').last().getByRole('textbox')).toHaveValue( 'Super Admin', ); },