From 8809df3f511b77d08a418644935f364e364bd661 Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Wed, 7 Jun 2023 11:18:35 +0800 Subject: [PATCH] refactor(association-field): useAssociationNames hook (#1956) * chore: useAssociationNames * refactor: code improve * refactor: support blocktemplate * refactor: support blocktemplate * refactor: useAssociationNames in block template * fix: cross hierarchical association field layout * fix: normal field of association field should be append * refactor: useAssociationNames * refactor: useCreateActionProps * refactor: useAssociationNames params * fix: cleanup --------- Co-authored-by: chenos --- .../src/block-provider/BlockProvider.tsx | 31 +++- .../block-provider/DetailsBlockProvider.tsx | 11 +- .../src/block-provider/FormBlockProvider.tsx | 14 +- .../src/block-provider/TableBlockProvider.tsx | 108 ++++++------- .../client/src/block-provider/hooks/index.ts | 152 +++++------------- .../association-field/AssociationSelect.tsx | 2 +- .../antd/grid-card/GridCard.Decorator.tsx | 11 +- .../antd/list/List.Decorator.tsx | 10 +- .../src/schema-templates/BlockTemplate.tsx | 1 + .../client/nodes/manual/FormBlockProvider.tsx | 2 +- 10 files changed, 129 insertions(+), 213 deletions(-) diff --git a/packages/core/client/src/block-provider/BlockProvider.tsx b/packages/core/client/src/block-provider/BlockProvider.tsx index 646bcbb35..66b3f43b3 100644 --- a/packages/core/client/src/block-provider/BlockProvider.tsx +++ b/packages/core/client/src/block-provider/BlockProvider.tsx @@ -20,6 +20,9 @@ import { CollectionProvider, useCollection, useCollectionManager } from '../coll import { FilterBlockRecord } from '../filter-provider/FilterProvider'; import { useRecordIndex } from '../record-provider'; import { SharedFilterProvider } from './SharedFilterProvider'; +import _ from 'lodash'; +import { useAssociationNames } from './hooks'; + export const BlockResourceContext = createContext(null); export const BlockAssociationContext = createContext(null); @@ -137,7 +140,6 @@ export const useResourceAction = (props, opts = {}) => { refreshDeps: [runWhenParamsChanged ? null : JSON.stringify(params.appends)], }, ); - // automatic run service when params has changed const firstRun = useRef(false); useEffect(() => { @@ -164,7 +166,7 @@ export const MaybeCollectionProvider = (props) => { ); }; -const BlockRequestProvider = (props) => { +export const BlockRequestProvider = (props) => { const field = useField(); const resource = useBlockResource(); const [allowedActions, setAllowedActions] = useState({}); @@ -188,7 +190,16 @@ const BlockRequestProvider = (props) => { const __parent = useContext(BlockRequestContext); return ( {props.children} @@ -264,13 +275,21 @@ export const RenderChildrenWithAssociationFilter: React.FC = (props) => { export const BlockProvider = (props) => { const { collection, association } = props; const resource = useResource(props); + const params = { ...props.params }; + const { appends, updateAssociationValues } = useAssociationNames(); + if (!Object.keys(params).includes('appends')) { + params['appends'] = appends; + } + return ( - - - {props.children} + + + + {props.children} + diff --git a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx index e7e5c3052..77c18643a 100644 --- a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx +++ b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx @@ -4,7 +4,6 @@ import { Spin } from 'antd'; import React, { createContext, useContext, useEffect, useMemo } from 'react'; import { RecordProvider } from '../record-provider'; import { BlockProvider, useBlockRequestContext } from './BlockProvider'; -import { useAssociationNames } from './hooks'; export const DetailsBlockContext = createContext({}); @@ -39,15 +38,9 @@ const InternalDetailsBlockProvider = (props) => { }; export const DetailsBlockProvider = (props) => { - const params = { ...props.params }; - const { collection } = props; - const { appends } = useAssociationNames(collection); - if (!Object.keys(params).includes('appends')) { - params['appends'] = appends; - } return ( - - + + ); }; diff --git a/packages/core/client/src/block-provider/FormBlockProvider.tsx b/packages/core/client/src/block-provider/FormBlockProvider.tsx index 20c14905b..26dd7ce5a 100644 --- a/packages/core/client/src/block-provider/FormBlockProvider.tsx +++ b/packages/core/client/src/block-provider/FormBlockProvider.tsx @@ -7,12 +7,11 @@ import { RecordProvider, useRecord } from '../record-provider'; import { useActionContext, useDesignable } from '../schema-component'; import { Templates as DataTemplateSelect } from '../schema-component/antd/form-v2/Templates'; import { BlockProvider, useBlockRequestContext } from './BlockProvider'; -import { useAssociationNames } from './hooks'; export const FormBlockContext = createContext({}); const InternalFormBlockProvider = (props) => { - const { action, readPretty, params, updateAssociationValues } = props; + const { action, readPretty, params } = props; const field = useField(); const form = useMemo( () => @@ -21,7 +20,7 @@ const InternalFormBlockProvider = (props) => { }), [], ); - const { resource, service } = useBlockRequestContext(); + const { resource, service, updateAssociationValues } = useBlockRequestContext(); const formBlockRef = useRef(); const record = useRecord(); // if (service.loading) { @@ -68,14 +67,9 @@ export const FormBlockProvider = (props) => { const record = useRecord(); const { collection } = props; const { __collection } = record; - const params = { ...props.params }; const currentCollection = useCollection(); const { designable } = useDesignable(); const isEmptyRecord = useIsEmptyRecord(); - const { appends, updateAssociationValues } = useAssociationNames(collection); - if (!Object.keys(params).includes('appends')) { - params['appends'] = appends; - } let detailFlag = false; if (isEmptyRecord) { detailFlag = true; @@ -87,8 +81,8 @@ export const FormBlockProvider = (props) => { (currentCollection.name === (collection?.name || collection) && !isEmptyRecord) || !currentCollection.name; return ( (detailFlag || createFlag) && ( - - + + ) ); diff --git a/packages/core/client/src/block-provider/TableBlockProvider.tsx b/packages/core/client/src/block-provider/TableBlockProvider.tsx index 9bd7b612f..64c2e4b72 100644 --- a/packages/core/client/src/block-provider/TableBlockProvider.tsx +++ b/packages/core/client/src/block-provider/TableBlockProvider.tsx @@ -1,14 +1,13 @@ import { ArrayField, createForm } from '@formily/core'; -import { FormContext, Schema, useField, useFieldSchema } from '@formily/react'; -import uniq from 'lodash/uniq'; +import { FormContext, useField, useFieldSchema } from '@formily/react'; import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { useCollection, useCollectionManager } from '../collection-manager'; import { useFilterBlock } from '../filter-provider/FilterProvider'; +import { useRecord } from '../record-provider'; import { FixedBlockWrapper, SchemaComponentOptions, removeNullCondition } from '../schema-component'; import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider'; import { mergeFilter } from './SharedFilterProvider'; import { findFilterTargets } from './hooks'; -import { useRecord } from '../record-provider'; export const TableBlockContext = createContext({}); @@ -47,62 +46,60 @@ const InternalTableBlockProvider = (props: Props) => { ); }; -const useAssociationNames = (collection) => { - const { getCollectionFields } = useCollectionManager(); - const collectionFields = getCollectionFields(collection); - const associationFields = new Set(); - for (const collectionField of collectionFields) { - if (collectionField.target) { - associationFields.add(collectionField.name); - const fields = getCollectionFields(collectionField.target); - for (const field of fields) { - if (field.target) { - associationFields.add(`${collectionField.name}.${field.name}`); - } - } - } - } - const fieldSchema = useFieldSchema(); - const tableSchema = fieldSchema.reduceProperties((buf, schema) => { - if (schema['x-component'] === 'TableV2') { - return schema; - } - if (schema['x-component'] === 'Gantt') { - return schema.properties?.table; - } - return buf; - }, new Schema({})); - return uniq( - tableSchema.reduceProperties((buf, schema) => { - if (schema['x-component'] === 'TableV2.Column') { - const s = schema.reduceProperties((buf, s) => { - const [name] = (s.name as string).split('.'); - if (s['x-collection-field'] && associationFields.has(name)) { - return s; - } - return buf; - }, null); - if (s) { - // 关联字段和关联的关联字段 - const [firstName] = s.name.split('.'); - if (associationFields.has(s.name)) { - buf.push(s.name); - } else if (associationFields.has(firstName)) { - buf.push(firstName); - } - } - } - return buf; - }, []), - ); -}; +// export const useAssociationNames = (collection) => { +// const { getCollectionFields } = useCollectionManager(); +// const collectionFields = getCollectionFields(collection); +// const associationFields = new Set(); +// for (const collectionField of collectionFields) { +// if (collectionField.target) { +// associationFields.add(collectionField.name); +// const fields = getCollectionFields(collectionField.target); +// for (const field of fields) { +// if (field.target) { +// associationFields.add(`${collectionField.name}.${field.name}`); +// } +// } +// } +// } +// const fieldSchema = useFieldSchema(); +// const tableSchema = fieldSchema.reduceProperties((buf, schema) => { +// if (schema['x-component'] === 'TableV2') { +// return schema; +// } +// if (schema['x-component'] === 'Gantt') { +// return schema.properties?.table; +// } +// return buf; +// }, new Schema({})); +// return uniq( +// tableSchema.reduceProperties((buf, schema) => { +// if (schema['x-component'] === 'TableV2.Column') { +// const s = schema.reduceProperties((buf, s) => { +// const [name] = (s.name as string).split('.'); +// if (s['x-collection-field'] && associationFields.has(name)) { +// return s; +// } +// return buf; +// }, null); +// if (s) { +// // 关联字段和关联的关联字段 +// const [firstName] = s.name.split('.'); +// if (associationFields.has(s.name)) { +// buf.push(s.name); +// } else if (associationFields.has(firstName)) { +// buf.push(firstName); +// } +// } +// } +// return buf; +// }, []), +// ); +// }; export const TableBlockProvider = (props) => { const resourceName = props.resource; const params = { ...props.params }; const record = useRecord(); - - const appends = useAssociationNames(props.collection); const fieldSchema = useFieldSchema(); const { getCollection, getCollectionField } = useCollectionManager(); const parent = useCollection(); @@ -131,9 +128,6 @@ export const TableBlockProvider = (props) => { params['tree'] = true; } } - if (!Object.keys(params).includes('appends')) { - params['appends'] = appends; - } const form = useMemo(() => createForm(), [treeTable]); return ( diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index ad8246b1e..9940c752e 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -1,7 +1,7 @@ -import { Schema, SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react'; +import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react'; import { parse } from '@nocobase/utils/client'; import { Modal, message } from 'antd'; -import { cloneDeep, uniq } from 'lodash'; +import { cloneDeep } from 'lodash'; import get from 'lodash/get'; import omit from 'lodash/omit'; import { ChangeEvent, useContext, useEffect } from 'react'; @@ -16,7 +16,6 @@ import { transformToFilter } from '../../filter-provider/utils'; import { useRecord } from '../../record-provider'; import { removeNullCondition, useActionContext, useCompile } from '../../schema-component'; import { BulkEditFormItemValueType } from '../../schema-initializer/components'; -import { useSchemaTemplateManager } from '../../schema-templates'; import { useCurrentUserContext } from '../../user'; import { useBlockRequestContext, useFilterByTk } from '../BlockProvider'; import { useDetailsBlockContext } from '../DetailsBlockProvider'; @@ -1075,120 +1074,53 @@ export const useAssociationFilterBlockProps = () => { }; }; -const getTemplateSchema = (schema) => { - const conf = { - url: `/uiSchemas:getJsonSchema/${schema?.uid}`, - }; - const { data, loading, run } = useRequest(conf, { manual: true }); - if (loading) { +function getAssociationPath(str) { + const lastIndex = str.lastIndexOf('.'); + if (lastIndex !== -1) { + return str.substring(0, lastIndex); } - useEffect(() => { - if (schema?.uid) { - run(); - } - }, [schema?.uid]); - return schema?.uid ? new Schema(data?.data) : null; -}; + return str; +} -export const useAssociationNames = (collection) => { +export const useAssociationNames = () => { const { getCollectionJoinField } = useCollectionManager(); - const { getTemplateById } = useSchemaTemplateManager(); const fieldSchema = useFieldSchema(); - const associationValues = []; - const formSchema = fieldSchema.reduceProperties((buf, schema) => { - if (['FormV2', 'Details', 'List', 'GridCard'].includes(schema['x-component'])) { - return schema; - } - return buf; - }, new Schema({})); - - const templateSchema = formSchema.reduceProperties((buf, schema) => { - if (schema['x-component'] === 'BlockTemplate') { - return schema; - } - return buf; - }, null); - - const getAssociationAppends = (schema, arr = []) => { - const data = schema.reduceProperties((buf, s) => { + const updateAssociationValues = new Set([]); + const appends = new Set([]); + const getAssociationAppends = (schema, str) => { + schema.reduceProperties((pre, s) => { + const prefix = pre || str; const collectionfield = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field']); - if ( - collectionfield && - ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(collectionfield.type) && - s['x-component'] !== 'TableField' - ) { - buf.push(s.name); + const isAssociationSubfield = s.name.includes('.'); + const isAssociationField = + collectionfield && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(collectionfield.type); + if (collectionfield && (isAssociationField || isAssociationSubfield) && s['x-component'] !== 'TableField') { + 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)) { - associationValues.push(s.name); - } - if (s['x-component-props']?.mode === 'Nester') { - return getAssociationAppends(s, buf); - } - return buf; - } else { - if (s['x-component'] === 'Grid.Row') { - const kk = buf?.concat?.(); - return getNesterAppends(s, kk || []); - } else { - return !s['x-component']?.includes('Action.') && s['x-component'] !== 'TableField' - ? getAssociationAppends(s, buf) - : buf; + updateAssociationValues.add(path); + const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name; + getAssociationAppends(s, bufPrefix); } + } else if ( + ![ + 'ActionBar', + 'Action', + 'Action.Link', + 'Action.Modal', + 'Selector', + 'Viewer', + 'AddNewer', + 'AssociationField.Selector', + 'AssociationField.AddNewer', + 'TableField', + ].includes(s['x-component']) + ) { + getAssociationAppends(s, str); } - }, arr); - return data || []; + }, str); }; - - function flattenNestedList(nestedList) { - const flattenedList = []; - function flattenHelper(list, prefix) { - for (let i = 0; i < list.length; i++) { - if (Array.isArray(list[i])) { - `${prefix}` !== `${list[i][0]}` && flattenHelper(list[i], `${prefix}.${list[i][0]}`); - } else { - const searchTerm = `.${list[i]}`; - const lastIndex = prefix.lastIndexOf(searchTerm); - let str = ''; - if (lastIndex !== -1) { - str = prefix.slice(0, lastIndex) + prefix.slice(lastIndex + searchTerm.length); - } - if (!str) { - !list.includes(str) && flattenedList.push(`${list[i]}`); - } else { - !list.includes(str) ? flattenedList.push(`${str}.${list[i]}`) : flattenedList.push(str); - } - } - } - } - for (let i = 0; i < nestedList.length; i++) { - flattenHelper(nestedList[i], nestedList[i][0]); - } - return uniq(flattenedList.filter((obj) => !obj?.startsWith('.'))); - } - const getNesterAppends = (gridSchema, data) => { - gridSchema.reduceProperties((buf, s) => { - buf.push(getAssociationAppends(s)); - return buf; - }, data); - return data.filter((g) => g.length); - }; - - const template = getTemplateById(templateSchema?.['x-component-props']?.templateId); - const schema = getTemplateSchema(template); - if (schema) { - const associations = getAssociationAppends(schema); - const appends = flattenNestedList(associations); - return { - appends, - updateAssociationValues: appends.filter((item) => associationValues.some((suffix) => item.endsWith(suffix))), - }; - } - if (!schema) { - const associations = getAssociationAppends(formSchema); - const appends = flattenNestedList(associations); - return { - appends, - updateAssociationValues: appends.filter((item) => associationValues.some((suffix) => item.endsWith(suffix))), - }; - } + getAssociationAppends(fieldSchema, ''); + return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] }; }; 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 2eadb26c9..6f152cb5a 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 @@ -2,9 +2,9 @@ import { LoadingOutlined } from '@ant-design/icons'; import { RecursionField, connect, mapProps, observer, useField, useFieldSchema } from '@formily/react'; import { Input } from 'antd'; import React from 'react'; +import { RecordProvider } from '../../../'; import { RemoteSelect, RemoteSelectProps } from '../remote-select'; import useServiceOptions from './hooks'; -import { RecordProvider } from '../../../'; export type AssociationSelectProps

= RemoteSelectProps

& { action?: string; diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx index d8607ed19..213cd6fe0 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx @@ -4,8 +4,6 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from ' import { BlockProvider, useBlockRequestContext } from '../../../block-provider'; import { FormLayout } from '@formily/antd'; import { css } from '@emotion/css'; -import { useAssociationNames } from '../../../block-provider/hooks'; - export const GridCardBlockContext = createContext({}); const InternalGridCardBlockProvider = (props) => { @@ -57,15 +55,8 @@ const InternalGridCardBlockProvider = (props) => { }; export const GridCardBlockProvider = (props) => { - const params = { ...props.params }; - const { collection } = props; - const { appends } = useAssociationNames(collection); - if (!Object.keys(params).includes('appends')) { - params['appends'] = appends; - } - return ( - + ); diff --git a/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx b/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx index fafe6c315..c3f7c4ab8 100644 --- a/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx +++ b/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx @@ -2,7 +2,6 @@ import { createForm } from '@formily/core'; import { FormContext, useField } from '@formily/react'; import React, { createContext, useContext, useEffect, useMemo } from 'react'; import { FormLayout } from '@formily/antd'; -import { useAssociationNames } from '../../../block-provider/hooks'; import { BlockProvider, useBlockRequestContext } from '../../../block-provider'; export const ListBlockContext = createContext({}); @@ -38,15 +37,8 @@ const InternalListBlockProvider = (props) => { }; export const ListBlockProvider = (props) => { - const params = { ...props.params }; - const { collection } = props; - const { appends } = useAssociationNames(collection); - if (!Object.keys(params).includes('appends')) { - params['appends'] = appends; - } - return ( - + ); diff --git a/packages/core/client/src/schema-templates/BlockTemplate.tsx b/packages/core/client/src/schema-templates/BlockTemplate.tsx index c94040fdc..6e5c320d8 100644 --- a/packages/core/client/src/schema-templates/BlockTemplate.tsx +++ b/packages/core/client/src/schema-templates/BlockTemplate.tsx @@ -21,6 +21,7 @@ export const BlockTemplate = observer( const template = useMemo(() => getTemplateById(templateId), [templateId]); const onSuccess = (data) => { fieldSchema['x-linkage-rules'] = data?.data?.['x-linkage-rules'] || []; + fieldSchema.setProperties(data?.data?.properties); }; return template ? ( diff --git a/packages/plugins/workflow/src/client/nodes/manual/FormBlockProvider.tsx b/packages/plugins/workflow/src/client/nodes/manual/FormBlockProvider.tsx index 10405e9aa..30aa01d20 100644 --- a/packages/plugins/workflow/src/client/nodes/manual/FormBlockProvider.tsx +++ b/packages/plugins/workflow/src/client/nodes/manual/FormBlockProvider.tsx @@ -18,7 +18,7 @@ export function FormBlockProvider(props) { const fieldSchema = useFieldSchema(); const field = useField(); const formBlockRef = useRef(null); - const { appends, updateAssociationValues } = useAssociationNames(props.collection); + const { appends, updateAssociationValues } = useAssociationNames(); const [formKey] = Object.keys(fieldSchema.toJSON().properties ?? {}); const values = userJob?.result?.[formKey];