refactor: form data templates and depulicate action support sync from form fields (#2314)
* refactor: sync from form fields * refactor: sync from form fields * refactor: sync from form fields * refactor: data fields * refactor: traverseFields * refactor: traverseFields * refactor: locale improve * fix: merge bug * refactor: code improve * refactor: code improve * refactor: code improve * refactor: depulicate action support sync form form fields * refactor: code refactor * refactor: direct duplicate support select all * refactor: code improve * refactor: code improve * refactor: hasOne and hasMany avaliable for deplicate * refactor: code improve * refactor: locale improve * refactor: code improve * refactor: code improve
This commit is contained in:
parent
c743d66b8e
commit
8f1d0d80af
@ -1100,8 +1100,7 @@ export const useAssociationFilterBlockProps = () => {
|
|||||||
labelKey,
|
labelKey,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export function getAssociationPath(str) {
|
||||||
function getAssociationPath(str) {
|
|
||||||
const lastIndex = str.lastIndexOf('.');
|
const lastIndex = str.lastIndexOf('.');
|
||||||
if (lastIndex !== -1) {
|
if (lastIndex !== -1) {
|
||||||
return str.substring(0, lastIndex);
|
return str.substring(0, lastIndex);
|
||||||
|
@ -710,5 +710,8 @@ export default {
|
|||||||
"Allow add new, update and delete actions":"Allow add new, update and delete actions",
|
"Allow add new, update and delete actions":"Allow add new, update and delete actions",
|
||||||
"Date display format":"Date display format",
|
"Date display format":"Date display format",
|
||||||
"Assign data scope for the template":"Assign data scope for the template",
|
"Assign data scope for the template":"Assign data scope for the template",
|
||||||
"Table selected records":"Table selected records"
|
"Table selected records":"Table selected records",
|
||||||
|
"Sync successfully":"Sync successfully",
|
||||||
|
"Sync from form fields":"Sync from form fields",
|
||||||
|
"Select all":"Select all"
|
||||||
};
|
};
|
||||||
|
@ -621,4 +621,7 @@ export default {
|
|||||||
"Allow add new, update and delete actions":"削除変更操作の許可",
|
"Allow add new, update and delete actions":"削除変更操作の許可",
|
||||||
"Date display format":"日付表示形式",
|
"Date display format":"日付表示形式",
|
||||||
"Assign data scope for the template":"テンプレートのデータ範囲の指定",
|
"Assign data scope for the template":"テンプレートのデータ範囲の指定",
|
||||||
|
"Sync successfully":"同期成功",
|
||||||
|
"Sync from form fields":"フォームフィールドの同期",
|
||||||
|
"Select all":"すべて選択"
|
||||||
}
|
}
|
||||||
|
@ -795,5 +795,8 @@ export default {
|
|||||||
"Allow add new, update and delete actions":"允许增删改操作",
|
"Allow add new, update and delete actions":"允许增删改操作",
|
||||||
"Date display format":"日期显示格式",
|
"Date display format":"日期显示格式",
|
||||||
"Assign data scope for the template":"为模板指定数据范围",
|
"Assign data scope for the template":"为模板指定数据范围",
|
||||||
"Table selected records":"表格中选中的记录"
|
"Table selected records":"表格中选中的记录",
|
||||||
|
"Sync successfully":"同步成功",
|
||||||
|
"Sync from form fields":"同步表单字段",
|
||||||
|
"Select all":"全选"
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { connect, ISchema, mapProps, useField, useFieldSchema } from '@formily/react';
|
import { connect, ISchema, mapProps, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { isValid, uid } from '@formily/shared';
|
import { isValid, uid } from '@formily/shared';
|
||||||
import { Tree as AntdTree } from 'antd';
|
import { Tree as AntdTree } from 'antd';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDesignable } from '../..';
|
import { useDesignable } from '../..';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
@ -12,15 +12,21 @@ import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks
|
|||||||
import { useLinkageAction } from './hooks';
|
import { useLinkageAction } from './hooks';
|
||||||
import { requestSettingsSchema } from './utils';
|
import { requestSettingsSchema } from './utils';
|
||||||
import { useRecord } from '../../../record-provider';
|
import { useRecord } from '../../../record-provider';
|
||||||
|
import { useSyncFromForm } from '../../../schema-settings/DataTemplates/utils';
|
||||||
|
|
||||||
const Tree = connect(
|
const Tree = connect(
|
||||||
AntdTree,
|
AntdTree,
|
||||||
mapProps((props, field: any) => {
|
mapProps((props, field: any) => {
|
||||||
|
const [checkedKeys, setCheckedKeys] = useState(props.defaultCheckedKeys || []);
|
||||||
|
const onCheck = (checkedKeys) => {
|
||||||
|
setCheckedKeys(checkedKeys);
|
||||||
|
field.value = checkedKeys;
|
||||||
|
};
|
||||||
|
field.onCheck = onCheck;
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
onCheck: (checkedKeys) => {
|
checkedKeys,
|
||||||
field.value = checkedKeys;
|
onCheck,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -218,6 +224,28 @@ function SaveMode() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findFormBlock = (schema) => {
|
||||||
|
const formSchema = schema.reduceProperties((_, s) => {
|
||||||
|
if (s['x-decorator'] === 'FormBlockProvider') {
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
return findFormBlock(s);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
return formSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllkeys = (data, result) => {
|
||||||
|
for (let i = 0; i < data?.length; i++) {
|
||||||
|
const { children, ...rest } = data[i];
|
||||||
|
result.push(rest.key);
|
||||||
|
if (children) {
|
||||||
|
getAllkeys(children, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
function DuplicationMode() {
|
function DuplicationMode() {
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -227,7 +255,27 @@ function DuplicationMode() {
|
|||||||
const { collectionList, getEnableFieldTree, getOnLoadData, getOnCheck } = useCollectionState(name);
|
const { collectionList, getEnableFieldTree, getOnLoadData, getOnCheck } = useCollectionState(name);
|
||||||
const duplicateValues = cloneDeep(fieldSchema['x-component-props'].duplicateFields || []);
|
const duplicateValues = cloneDeep(fieldSchema['x-component-props'].duplicateFields || []);
|
||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
|
const syncCallBack = useCallback((treeData, selectFields, form) => {
|
||||||
|
form.query('duplicateFields').take((f) => {
|
||||||
|
f.componentProps.treeData = treeData;
|
||||||
|
f.componentProps.defaultCheckedKeys = selectFields;
|
||||||
|
f.setInitialValue(selectFields);
|
||||||
|
f?.onCheck(selectFields);
|
||||||
|
form.setValues({ ...form.values, treeData });
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
const useSelectAllFields = (form) => {
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
form.query('duplicateFields').take((f) => {
|
||||||
|
const selectFields = getAllkeys(f.componentProps.treeData, []);
|
||||||
|
f.componentProps.defaultCheckedKeys = selectFields;
|
||||||
|
f.setInitialValue(selectFields);
|
||||||
|
f?.onCheck(selectFields);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
title={t('Duplicate mode')}
|
title={t('Duplicate mode')}
|
||||||
@ -238,6 +286,7 @@ function DuplicationMode() {
|
|||||||
currentCollection: record?.__collection || name,
|
currentCollection: record?.__collection || name,
|
||||||
getOnLoadData,
|
getOnLoadData,
|
||||||
getOnCheck,
|
getOnCheck,
|
||||||
|
treeData: fieldSchema['x-component-props']?.treeData,
|
||||||
}}
|
}}
|
||||||
schema={
|
schema={
|
||||||
{
|
{
|
||||||
@ -278,11 +327,60 @@ function DuplicationMode() {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
syncFromForm: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Sync from form fields") }}',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
style: { float: 'right', position: 'relative', zIndex: 1200 },
|
||||||
|
useAction: () => {
|
||||||
|
const formSchema = useMemo(() => findFormBlock(fieldSchema), [fieldSchema]);
|
||||||
|
return useSyncFromForm(
|
||||||
|
formSchema,
|
||||||
|
fieldSchema['x-component-props']?.duplicateCollection || record?.__collection || name,
|
||||||
|
syncCallBack,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
dependencies: ['.duplicateMode'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: `{{ $deps[0]!=="quickDulicate" }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
selectAll: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Select all") }}',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
dependencies: ['.duplicateMode'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: `{{ $deps[0]==="quickDulicate" }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
style: { float: 'right', position: 'relative', zIndex: 1200 },
|
||||||
|
useAction: () => {
|
||||||
|
const from = useForm();
|
||||||
|
return useSelectAllFields(from);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
duplicateFields: {
|
duplicateFields: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
title: '{{ t("Data fields") }}',
|
title: '{{ t("Data fields") }}',
|
||||||
required: true,
|
required: true,
|
||||||
default: duplicateValues,
|
|
||||||
description: t('Only the selected fields will be used as the initialization data for the form'),
|
description: t('Only the selected fields will be used as the initialization data for the form'),
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': Tree,
|
'x-component': Tree,
|
||||||
@ -310,7 +408,7 @@ function DuplicationMode() {
|
|||||||
state: {
|
state: {
|
||||||
disabled: '{{ !$deps[0] }}',
|
disabled: '{{ !$deps[0] }}',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
treeData: '{{ getEnableFieldTree($deps[0], $self) }}',
|
treeData: '{{ getEnableFieldTree($deps[0], $self,treeData) }}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -320,7 +418,7 @@ function DuplicationMode() {
|
|||||||
},
|
},
|
||||||
} as ISchema
|
} as ISchema
|
||||||
}
|
}
|
||||||
onSubmit={({ duplicateMode, collection, duplicateFields }) => {
|
onSubmit={({ duplicateMode, collection, duplicateFields, treeData }) => {
|
||||||
const fields = Array.isArray(duplicateFields) ? duplicateFields : duplicateFields.checked || [];
|
const fields = Array.isArray(duplicateFields) ? duplicateFields : duplicateFields.checked || [];
|
||||||
field.componentProps.duplicateMode = duplicateMode;
|
field.componentProps.duplicateMode = duplicateMode;
|
||||||
field.componentProps.duplicateFields = fields;
|
field.componentProps.duplicateFields = fields;
|
||||||
@ -328,6 +426,7 @@ function DuplicationMode() {
|
|||||||
fieldSchema['x-component-props'].duplicateMode = duplicateMode;
|
fieldSchema['x-component-props'].duplicateMode = duplicateMode;
|
||||||
fieldSchema['x-component-props'].duplicateFields = fields;
|
fieldSchema['x-component-props'].duplicateFields = fields;
|
||||||
fieldSchema['x-component-props'].duplicateCollection = collection;
|
fieldSchema['x-component-props'].duplicateCollection = collection;
|
||||||
|
fieldSchema['x-component-props'].treeData = treeData;
|
||||||
dn.emit('patch', {
|
dn.emit('patch', {
|
||||||
schema: {
|
schema: {
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
@ -163,7 +163,7 @@ export const Templates = ({ style = {}, form }) => {
|
|||||||
{targetTemplate !== 'none' && (
|
{targetTemplate !== 'none' && (
|
||||||
<RemoteSelect
|
<RemoteSelect
|
||||||
style={{ width: 220 }}
|
style={{ width: 220 }}
|
||||||
fieldNames={{ label: template.titleField, value: 'id' }}
|
fieldNames={{ label: template?.titleField, value: 'id' }}
|
||||||
target={template?.collection}
|
target={template?.collection}
|
||||||
value={targetTemplateData}
|
value={targetTemplateData}
|
||||||
objectValue
|
objectValue
|
||||||
|
@ -13,6 +13,7 @@ import { AsDefaultTemplate } from './components/AsDefaultTemplate';
|
|||||||
import { ArrayCollapse } from './components/DataTemplateTitle';
|
import { ArrayCollapse } from './components/DataTemplateTitle';
|
||||||
import { getSelectedIdFilter } from './components/Designer';
|
import { getSelectedIdFilter } from './components/Designer';
|
||||||
import { useCollectionState } from './hooks/useCollectionState';
|
import { useCollectionState } from './hooks/useCollectionState';
|
||||||
|
import { useSyncFromForm } from './utils';
|
||||||
|
|
||||||
const Tree = connect(
|
const Tree = connect(
|
||||||
AntdTree,
|
AntdTree,
|
||||||
@ -48,7 +49,6 @@ export const FormDataTemplates = observer(
|
|||||||
} = useCollectionState(collectionName);
|
} = useCollectionState(collectionName);
|
||||||
const { getCollection, getCollectionField } = useCollectionManager();
|
const { getCollection, getCollectionField } = useCollectionManager();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// 不要在后面的数组中依赖 defaultValues,否则会因为 defaultValues 的变化导致 activeData 响应性丢失
|
// 不要在后面的数组中依赖 defaultValues,否则会因为 defaultValues 的变化导致 activeData 响应性丢失
|
||||||
const activeData = useMemo<ITemplate>(
|
const activeData = useMemo<ITemplate>(
|
||||||
() =>
|
() =>
|
||||||
@ -61,7 +61,6 @@ export const FormDataTemplates = observer(
|
|||||||
),
|
),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
console.log(activeData);
|
|
||||||
const getTargetField = (collectionName: string) => {
|
const getTargetField = (collectionName: string) => {
|
||||||
const collection = getCollection(collectionName);
|
const collection = getCollection(collectionName);
|
||||||
return getCollectionField(
|
return getCollectionField(
|
||||||
@ -170,6 +169,16 @@ export const FormDataTemplates = observer(
|
|||||||
required: true,
|
required: true,
|
||||||
'x-reactions': '{{useTitleFieldDataSource}}',
|
'x-reactions': '{{useTitleFieldDataSource}}',
|
||||||
},
|
},
|
||||||
|
syncFromForm: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Sync from form fields") }}',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
style: { float: 'right', position: 'relative', zIndex: 1200 },
|
||||||
|
useAction: () => useSyncFromForm(formSchema),
|
||||||
|
},
|
||||||
|
},
|
||||||
fields: {
|
fields: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
title: '{{ t("Data fields") }}',
|
title: '{{ t("Data fields") }}',
|
||||||
|
@ -48,8 +48,7 @@ const DataTemplateTitle = observer<{ index: number; item: any }>((props) => {
|
|||||||
export interface IArrayCollapseProps extends CollapseProps {
|
export interface IArrayCollapseProps extends CollapseProps {
|
||||||
defaultOpenPanelCount?: number;
|
defaultOpenPanelCount?: number;
|
||||||
}
|
}
|
||||||
type ComposedArrayCollapse =
|
type ComposedArrayCollapse = React.FC<React.PropsWithChildren<IArrayCollapseProps>> & {
|
||||||
| React.FC<React.PropsWithChildren<IArrayCollapseProps>> & {
|
|
||||||
CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>>;
|
CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -218,6 +217,9 @@ export const ArrayCollapse: ComposedArrayCollapse = observer(
|
|||||||
onAdd={(index) => {
|
onAdd={(index) => {
|
||||||
setActiveKeys(insertActiveKeys(activeKeys, index));
|
setActiveKeys(insertActiveKeys(activeKeys, index));
|
||||||
}}
|
}}
|
||||||
|
onRemove={() => {
|
||||||
|
field.initialValue = field.value;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{renderEmpty()}
|
{renderEmpty()}
|
||||||
{renderItems()}
|
{renderItems()}
|
||||||
|
@ -1,22 +1,12 @@
|
|||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
|
||||||
import { useCompile } from '../../../schema-component';
|
import { useCompile } from '../../../schema-component';
|
||||||
import { TreeNode } from '../TreeLabel';
|
import { TreeNode } from '../TreeLabel';
|
||||||
|
|
||||||
export const useCollectionState = (currentCollectionName: string) => {
|
|
||||||
const { getCollectionFields, getAllCollectionsInheritChain, getCollection, getInterface } = useCollectionManager();
|
|
||||||
const [collectionList] = useState(getCollectionList);
|
|
||||||
const compile = useCompile();
|
|
||||||
|
|
||||||
function getCollectionList() {
|
|
||||||
const collections = getAllCollectionsInheritChain(currentCollectionName);
|
|
||||||
return collections.map((name) => ({ label: getCollection(name)?.title, value: name }));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤掉系统字段
|
// 过滤掉系统字段
|
||||||
const systemKeys = [
|
export const systemKeys = [
|
||||||
// 'id',
|
// 'id',
|
||||||
'sort',
|
'sort',
|
||||||
'createdById',
|
'createdById',
|
||||||
@ -25,7 +15,19 @@ export const useCollectionState = (currentCollectionName: string) => {
|
|||||||
'updatedById',
|
'updatedById',
|
||||||
'updatedBy',
|
'updatedBy',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
|
'password',
|
||||||
|
'sequence',
|
||||||
];
|
];
|
||||||
|
export const useCollectionState = (currentCollectionName: string) => {
|
||||||
|
const { getCollectionFields, getAllCollectionsInheritChain, getCollection, getInterface } = useCollectionManager();
|
||||||
|
const [collectionList] = useState(getCollectionList);
|
||||||
|
const compile = useCompile();
|
||||||
|
const templateField: any = useField();
|
||||||
|
|
||||||
|
function getCollectionList() {
|
||||||
|
const collections = getAllCollectionsInheritChain(currentCollectionName);
|
||||||
|
return collections.map((name) => ({ label: getCollection(name)?.title, value: name }));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* maxDepth: 从 0 开始,0 表示一层,1 表示两层,以此类推
|
* maxDepth: 从 0 开始,0 表示一层,1 表示两层,以此类推
|
||||||
@ -115,12 +117,25 @@ export const useCollectionState = (currentCollectionName: string) => {
|
|||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
};
|
};
|
||||||
|
const parseTreeData = (data) => {
|
||||||
|
return data.map((v) => {
|
||||||
|
return {
|
||||||
|
...v,
|
||||||
|
title: React.createElement(TreeNode, { ...v, type: v.type }),
|
||||||
|
children: v.children ? parseTreeData(v.children) : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getEnableFieldTree = useCallback((collectionName: string) => {
|
const getEnableFieldTree = useCallback((collectionName: string, field, treeData?) => {
|
||||||
|
const index = field.index;
|
||||||
|
const targetTemplate = templateField.initialValue?.items?.[index];
|
||||||
if (!collectionName) {
|
if (!collectionName) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
if (targetTemplate?.treeData || treeData) {
|
||||||
|
return parseTreeData(treeData || targetTemplate.treeData);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return traverseFields(collectionName, { exclude: ['id', ...systemKeys], maxDepth: 1 });
|
return traverseFields(collectionName, { exclude: ['id', ...systemKeys], maxDepth: 1 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -242,9 +257,8 @@ function findNode(treeData, item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadChildren({ node, traverseAssociations, traverseFields, systemKeys, fields }) {
|
function loadChildren({ node, traverseAssociations, traverseFields, systemKeys, fields }) {
|
||||||
const activeNode = findNode(fields.componentProps.treeData, node);
|
const activeNode = findNode(fields.dataSource || fields.componentProps.treeData, node);
|
||||||
let children = [];
|
let children = [];
|
||||||
|
|
||||||
// 多对多和多对一只展示关系字段
|
// 多对多和多对一只展示关系字段
|
||||||
if (['belongsTo', 'belongsToMany'].includes(node.field.type)) {
|
if (['belongsTo', 'belongsToMany'].includes(node.field.type)) {
|
||||||
children = traverseAssociations(node.field.target, {
|
children = traverseAssociations(node.field.target, {
|
||||||
|
232
packages/core/client/src/schema-settings/DataTemplates/utils.tsx
Normal file
232
packages/core/client/src/schema-settings/DataTemplates/utils.tsx
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import { ArrayBase } from '@formily/antd-v5';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { getAssociationPath } from '../../block-provider/hooks';
|
||||||
|
import { useCollectionManager } from '../../collection-manager';
|
||||||
|
import { useCompile } from '../../schema-component';
|
||||||
|
import { TreeNode } from './TreeLabel';
|
||||||
|
import { systemKeys } from './hooks/useCollectionState';
|
||||||
|
import LRUCache from 'lru-cache';
|
||||||
|
|
||||||
|
export const useSyncFromForm = (fieldSchema, collection?, callBack?) => {
|
||||||
|
const { getCollectionJoinField, getCollectionFields } = useCollectionManager();
|
||||||
|
const array = ArrayBase.useArray();
|
||||||
|
const index = ArrayBase.useIndex();
|
||||||
|
const record = ArrayBase.useRecord();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const from = useForm();
|
||||||
|
|
||||||
|
const traverseFields = ((cache) => {
|
||||||
|
return (collectionName, { exclude = [], depth = 0, maxDepth, prefix = '', disabled = false }, formData) => {
|
||||||
|
const cacheKey = `${collectionName}-${exclude.join(',')}-${depth}-${maxDepth}-${prefix}`;
|
||||||
|
const cachedResult = cache.get(cacheKey);
|
||||||
|
if (cachedResult) {
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
if (depth > maxDepth) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result = getCollectionFields(collectionName)
|
||||||
|
.map((field) => {
|
||||||
|
if (exclude.includes(field.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!field.interface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (['sort', 'password', 'sequence'].includes(field.type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = {
|
||||||
|
type: 'duplicate',
|
||||||
|
tag: compile(field.uiSchema?.title) || field.name,
|
||||||
|
};
|
||||||
|
const option = {
|
||||||
|
...node,
|
||||||
|
title: React.createElement(TreeNode, node),
|
||||||
|
key: prefix ? `${prefix}.${field.name}` : field.name,
|
||||||
|
isLeaf: true,
|
||||||
|
field,
|
||||||
|
disabled,
|
||||||
|
};
|
||||||
|
const tatgetFormField = formData.find((v) => v.name === field.name);
|
||||||
|
if (
|
||||||
|
['belongsTo', 'belongsToMany'].includes(field.type) &&
|
||||||
|
(!tatgetFormField || ['Select', 'Picker'].includes(tatgetFormField?.fieldMode))
|
||||||
|
) {
|
||||||
|
node['type'] = 'reference';
|
||||||
|
option['type'] = 'reference';
|
||||||
|
option['title'] = React.createElement(TreeNode, { ...node, type: 'reference' });
|
||||||
|
option.isLeaf = false;
|
||||||
|
option['children'] = traverseAssociations(field.target, {
|
||||||
|
depth: depth + 1,
|
||||||
|
maxDepth,
|
||||||
|
prefix: option.key,
|
||||||
|
exclude: systemKeys,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
['hasOne', 'hasMany'].includes(field.type) ||
|
||||||
|
['Nester', 'SubTable'].includes(tatgetFormField?.fieldMode)
|
||||||
|
) {
|
||||||
|
let childrenDisabled = false;
|
||||||
|
if (
|
||||||
|
['hasOne', 'hasMany'].includes(field.type) &&
|
||||||
|
['Select', 'Picker'].includes(tatgetFormField?.fieldMode)
|
||||||
|
) {
|
||||||
|
childrenDisabled = true;
|
||||||
|
}
|
||||||
|
option.disabled = true;
|
||||||
|
option.isLeaf = false;
|
||||||
|
option['children'] = traverseFields(
|
||||||
|
field.target,
|
||||||
|
{
|
||||||
|
depth: depth + 1,
|
||||||
|
maxDepth,
|
||||||
|
prefix: option.key,
|
||||||
|
exclude: ['id', ...systemKeys],
|
||||||
|
disabled: childrenDisabled,
|
||||||
|
},
|
||||||
|
formData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
cache.set(cacheKey, result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})(
|
||||||
|
new LRUCache<string, any>({ max: 100 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const traverseAssociations = ((cache) => {
|
||||||
|
return (collectionName, { prefix, maxDepth, depth = 0, exclude = [] }) => {
|
||||||
|
const cacheKey = `${collectionName}-${exclude.join(',')}-${depth}-${maxDepth}-${prefix}`;
|
||||||
|
const cachedResult = cache.get(cacheKey);
|
||||||
|
if (cachedResult) {
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth > maxDepth) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = getCollectionFields(collectionName)
|
||||||
|
.map((field) => {
|
||||||
|
if (!field.target || !field.interface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exclude.includes(field.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
type: 'preloading',
|
||||||
|
tag: compile(field.uiSchema?.title) || field.name,
|
||||||
|
};
|
||||||
|
const value = prefix ? `${prefix}.${field.name}` : field.name;
|
||||||
|
return {
|
||||||
|
type: 'preloading',
|
||||||
|
tag: compile(field.uiSchema?.title) || field.name,
|
||||||
|
title: React.createElement(TreeNode, option),
|
||||||
|
key: value,
|
||||||
|
isLeaf: false,
|
||||||
|
field,
|
||||||
|
children: traverseAssociations(field.target, {
|
||||||
|
prefix: value,
|
||||||
|
depth: depth + 1,
|
||||||
|
maxDepth,
|
||||||
|
exclude,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
cache.set(cacheKey, result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})(
|
||||||
|
new LRUCache<string, any>({ max: 100 }),
|
||||||
|
);
|
||||||
|
const getEnableFieldTree = useCallback((collectionName: string, formData) => {
|
||||||
|
if (!collectionName) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return traverseFields(collectionName, { exclude: ['id', ...systemKeys], maxDepth: 1, disabled: false }, formData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
const formData = new Set([]);
|
||||||
|
const selectFields = 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']);
|
||||||
|
const isAssociationSubfield = s.name.includes('.');
|
||||||
|
const isAssociationField =
|
||||||
|
collectionfield && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(collectionfield.type);
|
||||||
|
const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name;
|
||||||
|
const path = prefix === '' || !prefix ? fieldPath : prefix + '.' + fieldPath;
|
||||||
|
if (
|
||||||
|
collectionfield &&
|
||||||
|
!(
|
||||||
|
['hasOne', 'hasMany'].includes(collectionfield.type) ||
|
||||||
|
['SubForm', 'Nester'].includes(s['x-component-props']?.mode)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
selectFields.add(path);
|
||||||
|
}
|
||||||
|
if (collectionfield && (isAssociationField || isAssociationSubfield) && s['x-component'] !== 'TableField') {
|
||||||
|
formData.add({ name: path, fieldMode: s['x-component-props']['mode'] || 'Select' });
|
||||||
|
if (['Nester', 'SubTable'].includes(s['x-component-props']?.mode)) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}, str);
|
||||||
|
};
|
||||||
|
getAssociationAppends(fieldSchema, '');
|
||||||
|
const treeData = getEnableFieldTree(record?.collection || collection, [...formData]);
|
||||||
|
if (callBack) {
|
||||||
|
callBack(treeData, [...selectFields], from);
|
||||||
|
} else {
|
||||||
|
array?.field.form.query(`fieldReaction.items.${index}.layout.fields`).take((f: any) => {
|
||||||
|
f.componentProps.treeData = [];
|
||||||
|
setTimeout(() => (f.componentProps.treeData = treeData));
|
||||||
|
});
|
||||||
|
array?.field.value.splice(index, 1, {
|
||||||
|
...array?.field?.value[index],
|
||||||
|
fields: [...selectFields],
|
||||||
|
treeData: treeData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
message.success(t('Sync successfully'));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -1146,7 +1146,6 @@ SchemaSettings.DataTemplates = function DataTemplates(props) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const formSchema = findFormBlock(fieldSchema) || fieldSchema;
|
const formSchema = findFormBlock(fieldSchema) || fieldSchema;
|
||||||
const { templateData } = useDataTemplates();
|
const { templateData } = useDataTemplates();
|
||||||
|
|
||||||
const schema = useMemo(
|
const schema = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -1171,7 +1170,6 @@ SchemaSettings.DataTemplates = function DataTemplates(props) {
|
|||||||
);
|
);
|
||||||
const onSubmit = useCallback((v) => {
|
const onSubmit = useCallback((v) => {
|
||||||
const data = { ...(formSchema['x-data-templates'] || {}), ...v.fieldReaction };
|
const data = { ...(formSchema['x-data-templates'] || {}), ...v.fieldReaction };
|
||||||
|
|
||||||
// 当 Tree 组件开启 checkStrictly 属性时,会导致 checkedKeys 的值是一个对象,而不是数组,所以这里需要转换一下以支持旧版本
|
// 当 Tree 组件开启 checkStrictly 属性时,会导致 checkedKeys 的值是一个对象,而不是数组,所以这里需要转换一下以支持旧版本
|
||||||
data.items.forEach((item) => {
|
data.items.forEach((item) => {
|
||||||
item.fields = Array.isArray(item.fields) ? item.fields : item.fields.checked;
|
item.fields = Array.isArray(item.fields) ? item.fields : item.fields.checked;
|
||||||
|
Loading…
Reference in New Issue
Block a user