diff --git a/packages/core/client/src/locale/en_US.json b/packages/core/client/src/locale/en_US.json index cc614f0c9..8f75681ec 100644 --- a/packages/core/client/src/locale/en_US.json +++ b/packages/core/client/src/locale/en_US.json @@ -267,6 +267,7 @@ "Sub-table": "Sub-table", "Sub-details": "Sub-details", "Sub-form(Popover)": "Sub-form(Popover)", + "Sub-form(Drawer)": "Sub-form(Drawer)", "System info": "System info", "Created at": "Created at", "Last updated at": "Last updated at", diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 2a5c4c32c..06e9d54d1 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -331,6 +331,7 @@ "Subtable": "子表格", "Sub-form": "子表单", "Sub-form(Popover)": "子表单(弹窗)", + "Sub-form(Drawer)": "子表单(抽屉)", "Sub-details": "子详情", "Record picker": "数据选择器", "Toggles the subfield mode": "切换子字段模式", diff --git a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx index a7ac1d6ca..250dc7432 100644 --- a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx @@ -4,12 +4,12 @@ import { RecursionField, connect, mapProps, observer, useField, useFieldSchema, import { uid } from '@formily/shared'; import { Space, message } from 'antd'; import { isFunction } from 'mathjs'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { RecordProvider, useAPIClient, useCollectionRecordData } from '../../../'; +import { RecordProvider, useAPIClient, useApp, useCollectionRecordData } from '../../../'; import { isVariable } from '../../../variables/utils/isVariable'; import { getInnermostKeyAndValue } from '../../common/utils/uitls'; -import { RemoteSelect, RemoteSelectProps } from '../remote-select'; +import { RemoteSelectProps } from '../remote-select'; import useServiceOptions, { useAssociationFieldContext } from './hooks'; export type AssociationSelectProps

= RemoteSelectProps

& { @@ -60,6 +60,9 @@ const InternalAssociationSelect = observer( const resource = api.resource(collectionField.target); const linkageFields = filterAnalyses(field.componentProps?.service?.params?.filter); const recordData = useCollectionRecordData(); + const app = useApp(); + const RemoteSelect = useMemo(() => app.getComponent('RemoteSelect'), [app]); + useEffect(() => { const initValue = isVariable(field.value) ? undefined : field.value; const value = Array.isArray(initValue) ? initValue.filter(Boolean) : initValue; @@ -165,6 +168,8 @@ export const AssociationSelect = InternalAssociationSelect as unknown as Associa export const AssociationSelectReadPretty = connect( (props: any) => { const service = useServiceOptions(props); + const app = useApp(); + const RemoteSelect = useMemo(() => app.getComponent('RemoteSelect'), [app]) as any; if (props.fieldNames) { return ; } 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 0cb2c22e4..8d838ad08 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 @@ -14,6 +14,8 @@ import { InternaPopoverNester } from './InternalPopoverNester'; import { InternalSubTable } from './InternalSubTable'; import { CreateRecordAction } from './components/CreateRecordAction'; import { useAssociationFieldContext } from './hooks'; +import { InternalCascader } from './InternalCascader'; +import { InternaDrawerSubTable } from './InternalDrawerSubTable'; const EditableAssociationField = observer( (props: any) => { @@ -57,6 +59,8 @@ const EditableAssociationField = observer( {currentMode === 'SubTable' && } {currentMode === 'FileManager' && } {currentMode === 'CascadeSelect' && } + {currentMode === 'DrawerSubTable' && } + {currentMode === 'Cascader' && } ); }, diff --git a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/InternalDrawerSubTable.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalDrawerSubTable.tsx similarity index 89% rename from packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/InternalDrawerSubTable.tsx rename to packages/core/client/src/schema-component/antd/association-field/InternalDrawerSubTable.tsx index 0ddb3bc95..19bc5e161 100644 --- a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/InternalDrawerSubTable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalDrawerSubTable.tsx @@ -4,9 +4,12 @@ import { observer } from '@formily/react'; import React, { useContext, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { InternalSubTable } from './InternalSubTable'; -import { ActionContext, ActionContextProvider, __UNSAFE__, useGetAriaLabelOfPopover } from '@nocobase/client'; import { Button, Drawer } from 'antd'; -const { useAssociationFieldContext, useSetAriaLabelForPopover, ReadPrettyInternalViewer } = __UNSAFE__; +import { useAssociationFieldContext } from './hooks'; +import { ActionContext, ActionContextProvider } from '../action/context'; +import { useGetAriaLabelOfPopover } from '../action'; +import { ReadPrettyInternalViewer } from './InternalViewer'; +import { useSetAriaLabelForPopover } from '../action/hooks/useSetAriaLabelForPopover'; export const InternaDrawerSubTable = observer( (props) => { diff --git a/packages/core/client/src/schema-component/hooks/useFieldModeOptions.tsx b/packages/core/client/src/schema-component/hooks/useFieldModeOptions.tsx index 12c2fe495..52824c30b 100644 --- a/packages/core/client/src/schema-component/hooks/useFieldModeOptions.tsx +++ b/packages/core/client/src/schema-component/hooks/useFieldModeOptions.tsx @@ -55,6 +55,7 @@ export const useFieldModeOptions = (props?) => { { label: t('Cascade Select'), value: 'CascadeSelect' }, !isTableField && { label: t('Sub-form'), value: 'Nester' }, { label: t('Sub-form(Popover)'), value: 'PopoverNester' }, + { label: t('Sub-form(Drawer)'), value: 'DrawerSubTable' }, { label: t('Cascader'), value: 'Cascader' }, ]; } @@ -72,6 +73,7 @@ export const useFieldModeOptions = (props?) => { { label: t('Record picker'), value: 'Picker' }, !isTableField && { label: t('Sub-form'), value: 'Nester' }, { label: t('Sub-form(Popover)'), value: 'PopoverNester' }, + { label: t('Sub-form(Drawer)'), value: 'DrawerSubTable' }, !isTableField && { label: t('Sub-table'), value: 'SubTable' }, ]; case 'm2m': @@ -88,6 +90,7 @@ export const useFieldModeOptions = (props?) => { !isTableField && { label: t('Sub-table'), value: 'SubTable' }, !isTableField && { label: t('Sub-form'), value: 'Nester' }, { label: t('Sub-form(Popover)'), value: 'PopoverNester' }, + { label: t('Sub-form(Drawer)'), value: 'DrawerSubTable' }, ]; case 'm2o': case 'linkTo': @@ -102,6 +105,7 @@ export const useFieldModeOptions = (props?) => { { label: t('Record picker'), value: 'Picker' }, !isTableField && { label: t('Sub-form'), value: 'Nester' }, { label: t('Sub-form(Popover)'), value: 'PopoverNester' }, + { label: t('Sub-form(Drawer)'), value: 'DrawerSubTable' }, { label: t('Cascader'), value: 'Cascader' }, ]; @@ -117,6 +121,7 @@ export const useFieldModeOptions = (props?) => { { label: t('Record picker'), value: 'Picker' }, !isTableField && { label: t('Sub-form'), value: 'Nester' }, { label: t('Sub-form(Popover)'), value: 'PopoverNester' }, + { label: t('Sub-form(Drawer)'), value: 'DrawerSubTable' }, ]; } }, [t, collectionField?.interface, label]); diff --git a/packages/plugins/@hera/plugin-core/src/client/index.tsx b/packages/plugins/@hera/plugin-core/src/client/index.tsx index 858d950d6..94216ab1e 100644 --- a/packages/plugins/@hera/plugin-core/src/client/index.tsx +++ b/packages/plugins/@hera/plugin-core/src/client/index.tsx @@ -36,7 +36,6 @@ import { isValid } from '@formily/shared'; import { useCreateActionProps } from './hooks/useCreateActionProps'; import { PageLayout } from './pages/PageLayout'; import { useOutboundActionProps } from './hooks/useOutboundActionProps'; -import { ExtendedAssociationField } from './schema-components/association-field/Editable'; import { AssociatedField } from './components/AssociatedField'; import { DatePicker } from './schema-components/date-picker'; import { Page } from './schema-components/page'; @@ -162,7 +161,6 @@ export class PluginCoreClient extends Plugin { DatePicker, RemoteSelect, SignatureInput, - AssociationField: ExtendedAssociationField, OutboundButton, OutboundLinkActionInitializer, PDFViewerBlockInitializer, diff --git a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/AssociationSelect.tsx b/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/AssociationSelect.tsx deleted file mode 100644 index f10f4d7ca..000000000 --- a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/AssociationSelect.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'; -import { onFieldChange, FormPath } from '@formily/core'; -import { RecursionField, connect, mapProps, observer, useField, useFieldSchema, useForm } from '@formily/react'; -import { uid } from '@formily/shared'; -import { Space, message } from 'antd'; -import { isFunction } from 'mathjs2'; -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { RecordProvider, RemoteSelectProps, useAPIClient } from '@nocobase/client'; -import { __UNSAFE__ } from '@nocobase/client'; -import { RemoteSelect } from '../remote-select'; - -const { getInnermostKeyAndValue, useServiceOptions, useAssociationFieldContext, isVariable } = __UNSAFE__; - -export type AssociationSelectProps

= RemoteSelectProps

& { - action?: string; - multiple?: boolean; -}; - -export const filterAnalyses = (filters): any[] => { - if (!filters) { - return; - } - const type = Object.keys(filters)[0] || '$and'; - const conditions = filters[type]; - const results = []; - conditions?.map((c) => { - const jsonlogic = getInnermostKeyAndValue(c); - const operator = jsonlogic?.key; - - if (!operator) { - return true; - } - const regex = /\{\{\$(?:[a-zA-Z_]\w*)\.([a-zA-Z_]\w*)(?:\.id)?\}\}/; - const fieldName = jsonlogic?.value?.match?.(regex)?.[1]; - if (fieldName) { - results.push(fieldName); - } - }); - return results; -}; - -const InternalAssociationSelect = observer((props: AssociationSelectProps) => { - const { objectValue = true } = props; - const field: any = useField(); - const fieldSchema = useFieldSchema(); - const service = useServiceOptions(props); - const { options: collectionField } = useAssociationFieldContext(); - const initValue = isVariable(props.value) ? undefined : props.value; - const value = Array.isArray(initValue) ? initValue.filter(Boolean) : initValue; - // 因为通过 Schema 的形式书写的组件,在值变更的时候 `value` 的值没有改变,所以需要维护一个 `innerValue` 来变更值 - const [innerValue, setInnerValue] = useState(value); - const addMode = fieldSchema['x-component-props']?.addMode; - const isAllowAddNew = fieldSchema['x-add-new']; - const { t } = useTranslation(); - const { multiple } = props; - const form = useForm(); - const api = useAPIClient(); - const resource = api.resource(collectionField.target); - const linkageFields = filterAnalyses(field.componentProps?.service?.params?.filter); - useEffect(() => { - const initValue = isVariable(field.value) ? undefined : field.value; - const value = Array.isArray(initValue) ? initValue.filter(Boolean) : initValue; - setInnerValue(value); - }, [field.value]); - useEffect(() => { - const id = uid(); - form.addEffects(id, () => { - if (linkageFields?.length > 0) { - //支持深层次子表单 - onFieldChange('*', (fieldPath: any) => { - if (linkageFields.includes(fieldPath.props.name) && field.value) { - props.onChange(field.initialValue); - setInnerValue(field.initialValue); - } - }); - } - }); - - return () => { - form.removeEffects(id); - }; - }, []); - - const handleCreateAction = async (props) => { - const { search: value, callBack } = props; - const { - data: { data }, - } = await resource.create({ - values: { - [field?.componentProps?.fieldNames?.label || 'id']: value, - }, - }); - if (data) { - if (['m2m', 'o2m'].includes(collectionField?.interface) && multiple !== false) { - const values = form.getValuesIn(field.path) || []; - values.push(data); - form.setValuesIn(field.path, values); - field.onInput(values); - } else { - form.setValuesIn(field.path, data); - field.onInput(data); - } - isFunction(callBack) && callBack?.(); - message.success(t('Saved successfully')); - } - }; - const QuickAddContent = (props) => { - return ( -

handleCreateAction(props)} - style={{ cursor: 'pointer', padding: '5px 12px', color: '#0d0c0c' }} - > - - {t('Add') + ` “${props.search}” `} -
- ); - }; - return ( -
- - { - const val = value?.length !== 0 ? value : null; - props.onChange?.(val); - }} - CustomDropdownRender={addMode === 'quickAdd' && QuickAddContent} - > - - {(addMode === 'modalAdd' || isAllowAddNew) && ( - - { - return s['x-component'] === 'Action'; - }} - /> - - )} - -
- ); -}); - -interface AssociationSelectInterface { - (props: any): React.ReactElement; - Designer: React.FC; - FilterDesigner: React.FC; -} - -export const AssociationSelect = InternalAssociationSelect as unknown as AssociationSelectInterface; - -export const AssociationSelectReadPretty = connect( - (props: any) => { - const service = useServiceOptions(props); - if (props.fieldNames) { - return ; - } - return null; - }, - mapProps( - { - dataSource: 'options', - loading: true, - }, - (props, field) => { - return { - ...props, - fieldNames: props.fieldNames && { ...props.fieldNames, ...field.componentProps.fieldNames }, - suffixIcon: field?.['loading'] || field?.['validating'] ? : props.suffixIcon, - }; - }, - ), -); diff --git a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/Editable.tsx b/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/Editable.tsx deleted file mode 100644 index 38e04f9b9..000000000 --- a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/Editable.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Field } from '@formily/core'; -import { connect, mapReadPretty, observer, useField, useForm } from '@formily/react'; -import React from 'react'; -import { - SchemaComponentOptions, - useCollection_deprecated, - useAssociationCreateActionProps as useCAP, - Action, - __UNSAFE__, -} from '@nocobase/client'; -import { InternaDrawerSubTable } from './InternalDrawerSubTable'; -import { AssociationSelect } from './AssociationSelect'; -const { - CreateRecordAction, - useAssociationFieldContext, - InternalPicker, - InternalNester, - AssociationFieldProvider, - InternaPopoverNester, - InternalSubTable, - InternalCascader, - InternalFileManager, - InternalCascadeSelect, - SubTable, - ReadPretty, - Nester, -} = __UNSAFE__; - -const EditableAssociationField = observer( - (props: any) => { - const { multiple } = props; - const field: Field = useField(); - const form = useForm(); - const { options: collectionField, currentMode } = useAssociationFieldContext(); - - const useCreateActionProps = () => { - const { onClick } = useCAP(); - const actionField: any = useField(); - const { getPrimaryKey } = useCollection_deprecated(); - const primaryKey = getPrimaryKey(); - return { - async onClick() { - await onClick(); - const { data } = actionField.data?.data?.data || {}; - if (data) { - if (['m2m', 'o2m'].includes(collectionField?.interface) && multiple !== false) { - const values = form.getValuesIn(field.path) || []; - if (!values.find((v) => v[primaryKey] === data[primaryKey])) { - values.push(data); - form.setValuesIn(field.path, values); - field.onInput(values); - } - } else { - form.setValuesIn(field.path, data); - field.onInput(data); - } - } - }, - }; - }; - return ( - - {currentMode === 'Picker' && } - {currentMode === 'Nester' && } - {currentMode === 'PopoverNester' && } - {currentMode === 'Select' && } - {currentMode === 'SubTable' && } - {currentMode === 'FileManager' && } - {currentMode === 'CascadeSelect' && } - {currentMode === 'DrawerSubTable' && } - {currentMode === 'Cascader' && } - - ); - }, - { displayName: 'EditableAssociationField' }, -); - -const ExtendedEditable = observer( - (props) => { - return ( - - - - ); - }, - { displayName: 'ExtendedEditable' }, -); - -export const ExtendedAssociationField: any = connect(ExtendedEditable, mapReadPretty(ReadPretty)); -ExtendedAssociationField.SubTable = SubTable; -ExtendedAssociationField.Nester = Nester; -ExtendedAssociationField.AddNewer = Action.Container; -ExtendedAssociationField.Selector = Action.Container; -ExtendedAssociationField.Viewer = Action.Container; -ExtendedAssociationField.InternalSelect = InternalPicker; diff --git a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/InternalSubTable.tsx b/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/InternalSubTable.tsx deleted file mode 100644 index 9fdab1a00..000000000 --- a/packages/plugins/@hera/plugin-core/src/client/schema-components/association-field/InternalSubTable.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { css, cx } from '@emotion/css'; -import { FormLayout } from '@formily/antd-v5'; -import { RecursionField, useField, useFieldSchema, observer } from '@formily/react'; -import React, { useEffect } from 'react'; -import { useInsertSchema } from '../../components/AssociatedField'; -import { - ACLCollectionProvider, - CollectionProvider_deprecated, - __UNSAFE__, - useACLActionParamsContext, -} from '@nocobase/client'; -const { useAssociationFieldContext, schema } = __UNSAFE__; - -export const InternalSubTable = observer( - () => { - const field = useField(); - const fieldSchema = useFieldSchema(); - const insertNester = useInsertSchema('SubTable'); - const { options: collectionField } = useAssociationFieldContext(); - const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true; - const { actionName } = useACLActionParamsContext(); - useEffect(() => { - insertNester(schema.SubTable); - }, []); - return ( - - - -
.ant-card-bordered { - border: none; - } - `]: showTitle === false, - }, - )} - > - { - return s['x-component'] === 'AssociationField.SubTable'; - }} - /> -
-
-
-
- ); - }, - { displayName: 'InternalSubTable' }, -);