mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
1933 lines
59 KiB
TypeScript
1933 lines
59 KiB
TypeScript
/**
|
|
* This file is part of the NocoBase (R) project.
|
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
* Authors: NocoBase Team.
|
|
*
|
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
*/
|
|
|
|
import { Field, Form } from '@formily/core';
|
|
import { ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
|
|
import { uid } from '@formily/shared';
|
|
import _ from 'lodash';
|
|
import { useMemo } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
DataBlockInitializer,
|
|
DataSource,
|
|
SchemaInitializerItemType,
|
|
useAssociationName,
|
|
useCollection,
|
|
useCollectionManager,
|
|
useDataSourceKey,
|
|
} from '../';
|
|
import { useFormBlockContext } from '../block-provider/FormBlockProvider';
|
|
import { useFormActiveFields } from '../block-provider/hooks/useFormActiveFields';
|
|
import {
|
|
CollectionFieldOptions_deprecated,
|
|
FieldOptions,
|
|
useCollectionManager_deprecated,
|
|
useCollection_deprecated,
|
|
} from '../collection-manager';
|
|
import { Collection, CollectionFieldOptions, CollectionOptions } from '../data-source/collection/Collection';
|
|
import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider';
|
|
import { isAssocField } from '../filter-provider/utils';
|
|
import { useActionContext, useCompile, useDesignable } from '../schema-component';
|
|
import { useSchemaTemplateManager } from '../schema-templates';
|
|
import { useBlockTemplateContext } from '../schema-templates/BlockTemplateProvider';
|
|
import { DeprecatedTemplateTitleElement } from './components/DeprecatedTemplateTitle';
|
|
|
|
export const itemsMerge = (items1) => {
|
|
return items1;
|
|
};
|
|
|
|
export const gridRowColWrap = (schema: ISchema) => {
|
|
return {
|
|
type: 'void',
|
|
'x-component': 'Grid.Row',
|
|
properties: {
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-component': 'Grid.Col',
|
|
properties: {
|
|
[schema?.name || uid()]: schema,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
export const removeTableColumn = (schema, cb) => {
|
|
cb(schema.parent);
|
|
};
|
|
|
|
export const removeGridFormItem = (schema, cb) => {
|
|
cb(schema, {
|
|
removeParentsIfNoChildren: true,
|
|
breakRemoveOn: {
|
|
'x-component': 'Grid',
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useRemoveGridFormItem = () => {
|
|
const form = useForm();
|
|
return (schema, cb) => {
|
|
cb(schema, {
|
|
removeParentsIfNoChildren: true,
|
|
breakRemoveOn: {
|
|
'x-component': 'Grid',
|
|
},
|
|
});
|
|
delete form.values?.[schema.name];
|
|
};
|
|
};
|
|
|
|
export const findTableColumn = (schema: Schema, key: string, action: string, name: string) => {
|
|
return schema.reduceProperties((buf, s) => {
|
|
if (s[key] === action && (!name || s.name === name)) {
|
|
return s;
|
|
}
|
|
const c = s.reduceProperties((buf, s) => {
|
|
if (s[key] === action && (!name || s.name === name)) {
|
|
return s;
|
|
}
|
|
return buf;
|
|
});
|
|
if (c) {
|
|
return c;
|
|
}
|
|
return buf;
|
|
});
|
|
};
|
|
const quickEditField = [
|
|
'attachment',
|
|
'textarea',
|
|
'markdown',
|
|
'json',
|
|
'richText',
|
|
'polygon',
|
|
'circle',
|
|
'point',
|
|
'lineString',
|
|
];
|
|
|
|
export function useTableColumnInitializerFields() {
|
|
const { name, currentFields = [] } = useCollection_deprecated();
|
|
const { getInterface, getCollection } = useCollectionManager_deprecated();
|
|
const fieldSchema = useFieldSchema();
|
|
const isSubTable = fieldSchema['x-component'] === 'AssociationField.SubTable';
|
|
const form = useForm();
|
|
const isReadPretty = isSubTable ? form.readPretty : true;
|
|
|
|
return currentFields
|
|
.filter((v) => !v.collectionName || v.collectionName === name)
|
|
.filter((field) => field?.interface && field?.interface !== 'subTable' && !field?.treeChildren)
|
|
.map((field) => {
|
|
const interfaceConfig = getInterface(field.interface);
|
|
const isFileCollection = field?.target && getCollection(field?.target)?.template === 'file';
|
|
const isPreviewComponent = field?.uiSchema?.['x-component'] === 'Preview';
|
|
|
|
const schema = {
|
|
name: field.name,
|
|
'x-collection-field': `${name}.${field.name}`,
|
|
'x-component': 'CollectionField',
|
|
'x-component-props': isFileCollection
|
|
? {
|
|
fieldNames: {
|
|
label: 'preview',
|
|
value: 'id',
|
|
},
|
|
}
|
|
: isPreviewComponent
|
|
? { size: 'small' }
|
|
: {},
|
|
'x-read-pretty': isReadPretty || field.uiSchema?.['x-read-pretty'],
|
|
'x-decorator': isSubTable
|
|
? quickEditField.includes(field.interface) || isFileCollection
|
|
? 'QuickEdit'
|
|
: 'FormItem'
|
|
: null,
|
|
'x-decorator-props': {
|
|
labelStyle: {
|
|
display: 'none',
|
|
},
|
|
},
|
|
};
|
|
// interfaceConfig?.schemaInitialize?.(schema, { field, readPretty: true, block: 'Table' });
|
|
return {
|
|
type: 'item',
|
|
name: field.name,
|
|
title: field?.uiSchema?.title || field.name,
|
|
Component: 'TableCollectionFieldInitializer',
|
|
find: findTableColumn,
|
|
remove: removeTableColumn,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field,
|
|
readPretty: isReadPretty,
|
|
block: 'Table',
|
|
targetCollection: getCollection(field.target),
|
|
});
|
|
},
|
|
field,
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
});
|
|
}
|
|
|
|
export function useAssociatedTableColumnInitializerFields() {
|
|
const { fields } = useCollection_deprecated();
|
|
const { t } = useTranslation();
|
|
const { getInterface, getCollectionFields, getCollection } = useCollectionManager_deprecated();
|
|
const groups = fields
|
|
?.filter((field) => {
|
|
return ['o2o', 'oho', 'obo', 'm2o'].includes(field.interface);
|
|
})
|
|
?.map((field) => {
|
|
return getGroupItemForTable({
|
|
getCollectionFields,
|
|
field,
|
|
getInterface,
|
|
getCollection,
|
|
schemaName: field.name,
|
|
maxDepth: 2,
|
|
depth: 1,
|
|
t,
|
|
});
|
|
});
|
|
|
|
return groups;
|
|
}
|
|
|
|
function getGroupItemForTable({
|
|
getCollectionFields,
|
|
field,
|
|
getInterface,
|
|
getCollection,
|
|
schemaName,
|
|
maxDepth,
|
|
depth,
|
|
t,
|
|
}: {
|
|
getCollectionFields: (name: any, customDataSource?: string) => CollectionFieldOptions_deprecated[];
|
|
field: CollectionFieldOptions;
|
|
getInterface: (name: string) => any;
|
|
getCollection: (name: any, customDataSource?: string) => CollectionOptions;
|
|
schemaName: string;
|
|
maxDepth: number;
|
|
depth: number;
|
|
t: any;
|
|
}) {
|
|
const subFields = getCollectionFields(field.target);
|
|
const items = subFields
|
|
?.filter(
|
|
(subField) => subField?.interface && !['subTable'].includes(subField?.interface) && !subField?.treeChildren,
|
|
)
|
|
?.map((subField) => {
|
|
const interfaceConfig = getInterface(subField.interface);
|
|
const newSchemaName = `${schemaName}.${subField.name}`;
|
|
const schema = {
|
|
// type: 'string',
|
|
name: newSchemaName,
|
|
// title: subField?.uiSchema?.title || subField.name,
|
|
'x-component': 'CollectionField',
|
|
'x-read-pretty': true,
|
|
'x-collection-field': `${field.target}.${subField.name}`,
|
|
'x-component-props': {},
|
|
};
|
|
|
|
return {
|
|
type: 'item',
|
|
key: `${field.target}_${subField.name}_${newSchemaName}`,
|
|
name: newSchemaName,
|
|
title: subField?.uiSchema?.title || subField.name,
|
|
Component: 'TableCollectionFieldInitializer',
|
|
find: findTableColumn,
|
|
remove: removeTableColumn,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field: subField,
|
|
readPretty: true,
|
|
block: 'Table',
|
|
targetCollection: getCollection(field.target),
|
|
});
|
|
},
|
|
field: subField,
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
});
|
|
|
|
const displayCollectionFields = {
|
|
type: 'itemGroup',
|
|
key: `${field.target}_${schemaName}_displayFields`,
|
|
name: `${schemaName}-displayCollectionFields`,
|
|
title: t('Display fields'),
|
|
children: items,
|
|
};
|
|
|
|
const children = [displayCollectionFields];
|
|
|
|
if (depth < maxDepth) {
|
|
const subChildren = subFields
|
|
?.filter((subField) => {
|
|
return ['o2o', 'oho', 'obo', 'm2o'].includes(subField.interface);
|
|
})
|
|
.map((subField) => {
|
|
return getGroupItemForTable({
|
|
getCollectionFields,
|
|
field: subField,
|
|
getInterface,
|
|
getCollection,
|
|
schemaName: `${schemaName}.${subField.name}`,
|
|
maxDepth,
|
|
depth: depth + 1,
|
|
t,
|
|
});
|
|
});
|
|
|
|
if (subChildren.length) {
|
|
const group: any = {
|
|
type: 'itemGroup',
|
|
key: `${field.target}_${schemaName}_associationFields`,
|
|
name: `${schemaName}-associationFields`,
|
|
title: t('Display association fields'),
|
|
children: subChildren,
|
|
};
|
|
|
|
children.push(group);
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'subMenu',
|
|
key: `${field.target}_${schemaName}_submenu`,
|
|
name: schemaName,
|
|
title: field.uiSchema?.title,
|
|
children,
|
|
} as SchemaInitializerItemType;
|
|
}
|
|
|
|
export function useInheritsTableColumnInitializerFields() {
|
|
const { name } = useCollection_deprecated();
|
|
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } =
|
|
useCollectionManager_deprecated();
|
|
const fieldSchema = useFieldSchema();
|
|
const isSubTable = fieldSchema['x-component'] === 'AssociationField.SubTable';
|
|
const form = useForm();
|
|
const isReadPretty = isSubTable ? form.readPretty : true;
|
|
const inherits = getInheritCollections(name);
|
|
return inherits?.map((v) => {
|
|
const fields = getParentCollectionFields(v, name);
|
|
const targetCollection = getCollection(v);
|
|
return {
|
|
[targetCollection?.title]: fields
|
|
?.filter((field) => {
|
|
return field?.interface;
|
|
})
|
|
.map((k) => {
|
|
const interfaceConfig = getInterface(k.interface);
|
|
const isFileCollection = k?.target && getCollection(k?.target)?.template === 'file';
|
|
const schema = {
|
|
name: `${k.name}`,
|
|
'x-component': 'CollectionField',
|
|
'x-read-pretty': isReadPretty || k.uiSchema?.['x-read-pretty'],
|
|
'x-collection-field': `${name}.${k.name}`,
|
|
'x-component-props': isFileCollection
|
|
? {
|
|
fieldNames: {
|
|
label: 'preview',
|
|
value: 'id',
|
|
},
|
|
}
|
|
: {},
|
|
'x-decorator': isSubTable
|
|
? quickEditField.includes(k.interface) || isFileCollection
|
|
? 'QuickEdit'
|
|
: 'FormItem'
|
|
: null,
|
|
'x-decorator-props': {
|
|
labelStyle: {
|
|
display: 'none',
|
|
},
|
|
},
|
|
};
|
|
return {
|
|
name: k?.uiSchema?.title || k.name,
|
|
type: 'item',
|
|
title: k?.uiSchema?.title || k.name,
|
|
Component: 'TableCollectionFieldInitializer',
|
|
find: findTableColumn,
|
|
remove: removeTableColumn,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field: k,
|
|
readPretty: true,
|
|
block: 'Table',
|
|
targetCollection: getCollection(k?.target),
|
|
});
|
|
},
|
|
field: k,
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
}),
|
|
};
|
|
});
|
|
}
|
|
|
|
export const useFormItemInitializerFields = (options?: any) => {
|
|
const { name, currentFields } = useCollection_deprecated();
|
|
const { getInterface, getCollection } = useCollectionManager_deprecated();
|
|
const form = useForm();
|
|
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
|
const { fieldSchema } = useActionContext();
|
|
const action = fieldSchema?.['x-action'];
|
|
|
|
return currentFields
|
|
.filter((v) => !v.collectionName || v.collectionName === name)
|
|
?.filter((field) => field?.interface && !field?.treeChildren)
|
|
?.map((field) => {
|
|
const interfaceConfig = getInterface(field.interface);
|
|
const targetCollection = getCollection(field.target);
|
|
const isFileCollection = field?.target && getCollection(field?.target)?.template === 'file';
|
|
const isAssociationField = targetCollection;
|
|
const fieldNames = field?.uiSchema?.['x-component-props']?.['fieldNames'];
|
|
const schema = {
|
|
type: 'string',
|
|
name: field.name,
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FormItem',
|
|
'x-component': 'CollectionField',
|
|
'x-decorator': 'FormItem',
|
|
'x-collection-field': `${name}.${field.name}`,
|
|
'x-component-props': isFileCollection
|
|
? {
|
|
fieldNames: {
|
|
label: 'preview',
|
|
value: 'id',
|
|
},
|
|
}
|
|
: isAssociationField && fieldNames
|
|
? {
|
|
fieldNames: { ...fieldNames, label: targetCollection?.titleField || fieldNames.label },
|
|
}
|
|
: {},
|
|
'x-read-pretty': field?.uiSchema?.['x-read-pretty'],
|
|
};
|
|
const resultItem = {
|
|
type: 'item',
|
|
name: field.name,
|
|
title: field?.uiSchema?.title || field.name,
|
|
Component: 'CollectionFieldInitializer',
|
|
remove: removeGridFormItem,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field,
|
|
block,
|
|
readPretty,
|
|
action,
|
|
targetCollection,
|
|
});
|
|
},
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
if (block == 'Kanban') {
|
|
resultItem['find'] = (schema: Schema, key: string, action: string) => {
|
|
const s = findSchema(schema, 'x-component', block);
|
|
return findSchema(s, key, action);
|
|
};
|
|
}
|
|
|
|
return resultItem;
|
|
});
|
|
};
|
|
|
|
// 筛选表单相关
|
|
export const useFilterFormItemInitializerFields = (options?: any) => {
|
|
const { name, currentFields } = useCollection_deprecated();
|
|
const { getInterface, getCollection } = useCollectionManager_deprecated();
|
|
const form = useForm();
|
|
const { readPretty = form.readPretty, block = 'FilterForm' } = options || {};
|
|
const { snapshot, fieldSchema } = useActionContext();
|
|
const action = fieldSchema?.['x-action'];
|
|
|
|
return currentFields
|
|
.filter((v) => !v.collectionName || v.collectionName === name)
|
|
?.filter((field) => field?.interface && getInterface(field.interface)?.filterable)
|
|
?.map((field) => {
|
|
const interfaceConfig = getInterface(field.interface);
|
|
const targetCollection = getCollection(field.target);
|
|
let schema = {
|
|
type: 'string',
|
|
name: field.name,
|
|
required: false,
|
|
// 'x-designer': 'FormItem.FilterFormDesigner',
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FilterFormItem',
|
|
'x-component': 'CollectionField',
|
|
'x-decorator': 'FormItem',
|
|
'x-use-decorator-props': 'useFormItemProps',
|
|
'x-collection-field': `${name}.${field.name}`,
|
|
'x-component-props': {
|
|
utc: false,
|
|
},
|
|
};
|
|
if (isAssocField(field)) {
|
|
schema = {
|
|
type: 'string',
|
|
name: `${field.name}`,
|
|
required: false,
|
|
// 'x-designer': 'FormItem.FilterFormDesigner',
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FilterFormItem',
|
|
'x-component': 'CollectionField',
|
|
'x-decorator': 'FormItem',
|
|
'x-use-decorator-props': 'useFormItemProps',
|
|
'x-collection-field': `${name}.${field.name}`,
|
|
'x-component-props': { ...field.uiSchema?.['x-component-props'], utc: false },
|
|
};
|
|
}
|
|
const resultItem = {
|
|
name: field?.uiSchema?.title || field.name,
|
|
type: 'item',
|
|
title: field?.uiSchema?.title || field.name,
|
|
Component: 'CollectionFieldInitializer',
|
|
remove: removeGridFormItem,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field,
|
|
block,
|
|
readPretty,
|
|
action,
|
|
targetCollection,
|
|
});
|
|
},
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
|
|
return resultItem;
|
|
});
|
|
};
|
|
|
|
export const useAssociatedFormItemInitializerFields = (options?: any) => {
|
|
const { name, fields } = useCollection_deprecated();
|
|
const { getInterface, getCollectionFields, getCollection } = useCollectionManager_deprecated();
|
|
const form = useForm();
|
|
const { t } = useTranslation();
|
|
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
|
const interfaces = block === 'Form' ? ['m2o', 'obo', 'oho'] : ['o2o', 'oho', 'obo', 'm2o'];
|
|
|
|
const groups = fields
|
|
?.filter((field) => {
|
|
return interfaces.includes(field.interface);
|
|
})
|
|
?.map((field) => {
|
|
return getGroupItemForForm({
|
|
getCollectionFields,
|
|
field,
|
|
getInterface,
|
|
getCollection,
|
|
readPretty,
|
|
block,
|
|
schemaName: field.name,
|
|
maxDepth: 2,
|
|
depth: 1,
|
|
t,
|
|
});
|
|
});
|
|
return groups;
|
|
};
|
|
|
|
const associationFieldToMenu = (
|
|
field: FieldOptions,
|
|
schemaName: string,
|
|
collectionName: string,
|
|
getCollectionFields,
|
|
processedCollections: string[],
|
|
) => {
|
|
if (field.target && field.uiSchema) {
|
|
if (processedCollections.includes(field.target) || processedCollections.length >= 1) return;
|
|
|
|
const subFields = getCollectionFields(field.target);
|
|
|
|
if (!subFields?.length) return;
|
|
|
|
return {
|
|
type: 'subMenu',
|
|
name: schemaName,
|
|
title: field.uiSchema?.title,
|
|
children: subFields
|
|
.map((subField) =>
|
|
associationFieldToMenu(subField, `${schemaName}.${subField.name}`, collectionName, getCollectionFields, [
|
|
...processedCollections,
|
|
field.target,
|
|
]),
|
|
)
|
|
.filter(Boolean),
|
|
} as SchemaInitializerItemType;
|
|
}
|
|
|
|
if (!field.uiSchema) return;
|
|
|
|
const schema = {
|
|
type: 'string',
|
|
name: schemaName,
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FilterFormItem',
|
|
'x-designer-props': {
|
|
// 在 useOperatorList 中使用,用于获取对应的操作符列表
|
|
interface: field.interface,
|
|
},
|
|
'x-component': 'CollectionField',
|
|
'x-component-props': { utc: false },
|
|
'x-read-pretty': false,
|
|
'x-decorator': 'FormItem',
|
|
'x-collection-field': `${collectionName}.${schemaName}`,
|
|
};
|
|
|
|
return {
|
|
name: field.uiSchema?.title || field.name,
|
|
type: 'item',
|
|
title: field.uiSchema?.title || field.name,
|
|
Component: 'CollectionFieldInitializer',
|
|
remove: removeGridFormItem,
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
};
|
|
|
|
// 筛选表单相关
|
|
export const useFilterAssociatedFormItemInitializerFields = () => {
|
|
const { name, fields } = useCollection_deprecated();
|
|
const { getCollectionFields } = useCollectionManager_deprecated();
|
|
const interfaces = ['o2o', 'oho', 'obo', 'm2o', 'm2m'];
|
|
return fields
|
|
?.filter((field) => field.target && field.uiSchema && interfaces.includes(field.interface))
|
|
.map((field) => associationFieldToMenu(field, field.name, name, getCollectionFields, []))
|
|
.filter(Boolean);
|
|
};
|
|
|
|
export const useInheritsFormItemInitializerFields = (options?) => {
|
|
const { name } = useCollection_deprecated();
|
|
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } =
|
|
useCollectionManager_deprecated();
|
|
const inherits = getInheritCollections(name);
|
|
const { snapshot } = useActionContext();
|
|
const form = useForm();
|
|
|
|
return inherits?.map((v) => {
|
|
const fields = getParentCollectionFields(v, name);
|
|
const { readPretty = form.readPretty, block = 'Form', component = 'CollectionField' } = options || {};
|
|
const targetCollection = getCollection(v);
|
|
return {
|
|
[targetCollection?.title]: fields
|
|
?.filter((field) => field?.interface)
|
|
?.map((field) => {
|
|
const interfaceConfig = getInterface(field.interface);
|
|
const targetCollection = getCollection(field.target);
|
|
// const component =
|
|
// field.interface === 'o2m' && targetCollection?.template !== 'file' && !snapshot
|
|
// ? 'TableField'
|
|
// : 'CollectionField';
|
|
const schema = {
|
|
type: 'string',
|
|
name: field.name,
|
|
title: field?.uiSchema?.title || field.name,
|
|
// 'x-designer': 'FormItem.Designer',
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FormItem',
|
|
'x-component': component,
|
|
'x-decorator': 'FormItem',
|
|
'x-collection-field': `${name}.${field.name}`,
|
|
'x-component-props': {},
|
|
'x-read-pretty': field?.uiSchema?.['x-read-pretty'],
|
|
};
|
|
return {
|
|
name: field?.uiSchema?.title || field.name,
|
|
type: 'item',
|
|
title: field?.uiSchema?.title || field.name,
|
|
Component: 'CollectionFieldInitializer',
|
|
remove: removeGridFormItem,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field,
|
|
block,
|
|
readPretty,
|
|
targetCollection,
|
|
});
|
|
},
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
}),
|
|
};
|
|
});
|
|
};
|
|
|
|
// 筛选表单相关
|
|
export const useFilterInheritsFormItemInitializerFields = (options?) => {
|
|
const { name } = useCollection_deprecated();
|
|
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } =
|
|
useCollectionManager_deprecated();
|
|
const inherits = getInheritCollections(name);
|
|
const { snapshot } = useActionContext();
|
|
const form = useForm();
|
|
|
|
return inherits?.map((v) => {
|
|
const fields = getParentCollectionFields(v, name);
|
|
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
|
const targetCollection = getCollection(v);
|
|
return {
|
|
[targetCollection.title]: fields
|
|
?.filter((field) => field?.interface && getInterface(field.interface)?.filterable)
|
|
?.map((field) => {
|
|
const interfaceConfig = getInterface(field.interface);
|
|
const targetCollection = getCollection(field.target);
|
|
// const component =
|
|
// field.interface === 'o2m' && targetCollection?.template !== 'file' && !snapshot
|
|
// ? 'TableField'
|
|
// : 'CollectionField';
|
|
const schema = {
|
|
type: 'string',
|
|
name: field.name,
|
|
title: field?.uiSchema?.title || field.name,
|
|
required: false,
|
|
// 'x-designer': 'FormItem.FilterFormDesigner',
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FilterFormItem',
|
|
'x-component': 'CollectionField',
|
|
'x-decorator': 'FormItem',
|
|
'x-collection-field': `${name}.${field.name}`,
|
|
'x-component-props': { utc: false },
|
|
'x-read-pretty': field?.uiSchema?.['x-read-pretty'],
|
|
};
|
|
return {
|
|
name: field?.uiSchema?.title || field.name,
|
|
type: 'item',
|
|
title: field?.uiSchema?.title || field.name,
|
|
Component: 'CollectionFieldInitializer',
|
|
remove: removeGridFormItem,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field,
|
|
block,
|
|
readPretty,
|
|
targetCollection,
|
|
});
|
|
},
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
}),
|
|
};
|
|
});
|
|
};
|
|
export const useCustomFormItemInitializerFields = (options?: any) => {
|
|
const { name, currentFields } = useCollection_deprecated();
|
|
const { getInterface, getCollection } = useCollectionManager_deprecated();
|
|
const form = useForm();
|
|
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
|
const remove = useRemoveGridFormItem();
|
|
return currentFields
|
|
?.filter((field) => {
|
|
return !field.inherit && field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
|
|
})
|
|
?.map((field) => {
|
|
const interfaceConfig = getInterface(field.interface);
|
|
const schema = {
|
|
type: 'string',
|
|
name: field.name,
|
|
title: field?.uiSchema?.title || field.name,
|
|
// 'x-designer': 'FormItem.Designer',
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FormItem',
|
|
'x-component': 'AssignedField',
|
|
'x-decorator': 'FormItem',
|
|
'x-collection-field': `${name}.${field.name}`,
|
|
};
|
|
return {
|
|
name: field?.uiSchema?.title || field.name,
|
|
type: 'item',
|
|
title: field?.uiSchema?.title || field.name,
|
|
Component: 'CollectionFieldInitializer',
|
|
remove: remove,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field,
|
|
block,
|
|
readPretty,
|
|
targetCollection: getCollection(field.target),
|
|
});
|
|
},
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
});
|
|
};
|
|
|
|
export const findSchema = (schema: Schema, key: string, action: string, name?: string) => {
|
|
if (!Schema.isSchemaInstance(schema)) return null;
|
|
return schema.reduceProperties((buf, s) => {
|
|
if (s[key] === action && (!name || s.name === name)) {
|
|
return s;
|
|
}
|
|
if (s['x-component'] !== 'Action.Container' && !s['x-component'].includes('AssociationField')) {
|
|
const c = findSchema(s, key, action, name);
|
|
if (c) {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
});
|
|
};
|
|
|
|
const removeSchema = (schema, cb) => {
|
|
return cb(schema);
|
|
};
|
|
|
|
const recursiveParent = (schema: Schema) => {
|
|
if (!schema.parent) return null;
|
|
|
|
if (schema.parent['x-initializer']) return schema.parent;
|
|
|
|
return recursiveParent(schema.parent);
|
|
};
|
|
|
|
export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema, name?: string) => {
|
|
const { removeActiveFieldName } = useFormActiveFields() || {};
|
|
const { form }: { form?: Form } = useFormBlockContext();
|
|
let fieldSchema = useFieldSchema();
|
|
if (!fieldSchema?.['x-initializer'] && fieldSchema?.['x-decorator'] === 'FormItem') {
|
|
const recursiveInitializerSchema = recursiveParent(fieldSchema);
|
|
if (recursiveInitializerSchema) {
|
|
fieldSchema = recursiveInitializerSchema;
|
|
}
|
|
}
|
|
const { remove } = useDesignable();
|
|
const schema = find(fieldSchema, key, action, name);
|
|
return {
|
|
schema,
|
|
exists: !!schema,
|
|
remove() {
|
|
removeActiveFieldName?.(schema.name);
|
|
form?.query(new RegExp(`${schema.parent.name}.${schema.name}$`)).forEach((field: Field) => {
|
|
// 如果字段被删掉,那么在提交的时候不应该提交这个字段
|
|
field.setValue?.(undefined);
|
|
field.setInitialValue?.(undefined);
|
|
});
|
|
schema && rm(schema, remove);
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @deprecated
|
|
* 待统一区块的创建之后,将废弃该方法
|
|
*/
|
|
export const useRecordCollectionDataSourceItems = (
|
|
componentName,
|
|
item = null,
|
|
collectionName = null,
|
|
resourceName = null,
|
|
) => {
|
|
const { t } = useTranslation();
|
|
const collection = useCollection_deprecated();
|
|
const { getTemplatesByCollection } = useSchemaTemplateManager();
|
|
const templates = getTemplatesByCollection(collection.dataSource, collectionName || collection.name)
|
|
.filter((template) => {
|
|
return componentName && template.componentName === componentName;
|
|
})
|
|
.filter((template) => {
|
|
return ['FormItem', 'ReadPrettyFormItem'].includes(componentName) || template.resourceName === resourceName;
|
|
});
|
|
const extralCollectionMenuItems = Array.from(initializerMenusGenerators.values())
|
|
.map((generator) => generator({ collection, componentName }))
|
|
.filter(Boolean)
|
|
.flat();
|
|
if ((!templates.length && !extralCollectionMenuItems.length) || isInTemplateSettingPage()) {
|
|
return [];
|
|
}
|
|
const index = 0;
|
|
const deprecatedTemplatesMenuItems = [];
|
|
if (templates.length) {
|
|
deprecatedTemplatesMenuItems.push(
|
|
{
|
|
type: 'divider',
|
|
},
|
|
{
|
|
type: 'itemGroup',
|
|
title: DeprecatedTemplateTitleElement,
|
|
children: [
|
|
{
|
|
key: `${collectionName || componentName}_table_subMenu_${index}_copy`,
|
|
type: 'subMenu',
|
|
name: 'copy',
|
|
title: t('Duplicate template'),
|
|
children: templates.map((template) => {
|
|
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
|
? `${template?.name} ${t('(Fields only)')}`
|
|
: template?.name;
|
|
return {
|
|
type: 'item',
|
|
mode: 'copy',
|
|
name: collection.name,
|
|
template,
|
|
item,
|
|
title: templateName || t('Untitled'),
|
|
};
|
|
}),
|
|
},
|
|
{
|
|
key: `${collectionName || componentName}_table_subMenu_${index}_ref`,
|
|
type: 'subMenu',
|
|
name: 'ref',
|
|
title: t('Reference template'),
|
|
children: templates.map((template) => {
|
|
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
|
? `${template?.name} ${t('(Fields only)')}`
|
|
: template?.name;
|
|
return {
|
|
type: 'item',
|
|
mode: 'reference',
|
|
name: collection.name,
|
|
template,
|
|
item,
|
|
title: templateName || t('Untitled'),
|
|
};
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
);
|
|
}
|
|
return [
|
|
{
|
|
key: `${collectionName || componentName}_table_blank`,
|
|
type: 'item',
|
|
name: collection.name,
|
|
title: t('Blank block'),
|
|
item,
|
|
},
|
|
...extralCollectionMenuItems,
|
|
...deprecatedTemplatesMenuItems,
|
|
];
|
|
};
|
|
|
|
export const useCollectionDataSourceItems = ({
|
|
name,
|
|
componentName,
|
|
filter = () => true,
|
|
onlyCurrentDataSource = false,
|
|
showAssociationFields,
|
|
filterDataSource,
|
|
dataBlockInitializerProps,
|
|
hideOtherRecordsInPopup,
|
|
onClick,
|
|
filterOtherRecordsCollection,
|
|
currentText,
|
|
otherText,
|
|
}: {
|
|
name: string;
|
|
componentName: string;
|
|
filter?: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
|
|
onlyCurrentDataSource?: boolean;
|
|
showAssociationFields?: boolean;
|
|
filterDataSource?: (dataSource?: DataSource) => boolean;
|
|
dataBlockInitializerProps?: any;
|
|
/**
|
|
* 隐藏弹窗中的 Other records 选项
|
|
*/
|
|
hideOtherRecordsInPopup?: boolean;
|
|
onClick?: (options: any) => void;
|
|
/**
|
|
* 用来筛选弹窗中的 “Other records” 选项中的数据表
|
|
*/
|
|
filterOtherRecordsCollection?: (collection: Collection) => boolean;
|
|
currentText?: string;
|
|
otherText?: string;
|
|
}) => {
|
|
const { componentNamePrefix } = useBlockTemplateContext();
|
|
const { t } = useTranslation();
|
|
const dm = useDataSourceManager();
|
|
const dataSourceKey = useDataSourceKey();
|
|
const collection = useCollection();
|
|
const associationFields = useAssociationFields({
|
|
componentName: componentNamePrefix + componentName,
|
|
filterCollections: filter,
|
|
showAssociationFields,
|
|
componentNamePrefix,
|
|
name,
|
|
});
|
|
const association = useAssociationName();
|
|
|
|
let allCollections = dm.getAllCollections({
|
|
filterCollection: (collection) => {
|
|
if (onlyCurrentDataSource && collection.dataSource !== dataSourceKey) {
|
|
return false;
|
|
}
|
|
return filter({ collection });
|
|
},
|
|
filterDataSource,
|
|
});
|
|
if (onlyCurrentDataSource) {
|
|
allCollections = allCollections.filter((collection) => collection.key === dataSourceKey);
|
|
}
|
|
|
|
const { getTemplatesByCollection } = useSchemaTemplateManager();
|
|
|
|
const noAssociationMenu = useMemo(() => {
|
|
return allCollections.map(({ key, displayName, collections }) => ({
|
|
name: key,
|
|
label: displayName,
|
|
type: 'subMenu',
|
|
children: [
|
|
...getChildren({
|
|
name,
|
|
association,
|
|
collections,
|
|
componentName: componentNamePrefix + componentName,
|
|
searchValue: '',
|
|
dataSource: key,
|
|
getTemplatesByCollection,
|
|
t,
|
|
componentNamePrefix,
|
|
}).sort((item) => {
|
|
// fix https://nocobase.height.app/T-3551
|
|
const inherits = _.toArray(collection?.inherits || []);
|
|
if (item.name === collection?.name || inherits.some((inheritName) => inheritName === item.name)) return -1;
|
|
}),
|
|
],
|
|
}));
|
|
}, [allCollections, collection?.inherits, collection?.name, componentName, getTemplatesByCollection, t]);
|
|
|
|
// https://nocobase.height.app/T-3821
|
|
// showAssociationFields 的值是固定不变的,所以在 if 语句里使用 hooks 是安全的
|
|
if (showAssociationFields) {
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
return useMemo(() => {
|
|
const currentRecord = {
|
|
name: 'currentRecord',
|
|
collectionName: collection.name,
|
|
dataSource: collection.dataSource,
|
|
Component: DataBlockInitializer,
|
|
// 目的是使点击无效
|
|
onClick() {},
|
|
componentProps: {
|
|
...dataBlockInitializerProps,
|
|
icon: null,
|
|
title: currentText || t('Current record'),
|
|
name: 'currentRecord',
|
|
hideSearch: false,
|
|
hideChildrenIfSingleCollection: true,
|
|
items: noAssociationMenu,
|
|
},
|
|
};
|
|
const associationRecords = {
|
|
name: 'associationRecords',
|
|
Component: DataBlockInitializer,
|
|
// 目的是使点击无效
|
|
onClick() {},
|
|
componentProps: {
|
|
...dataBlockInitializerProps,
|
|
icon: null,
|
|
title: t('Associated records'),
|
|
name: 'associationRecords',
|
|
hideSearch: false,
|
|
items: [
|
|
{
|
|
name: 'associationFields',
|
|
label: t('Association fields'),
|
|
type: 'subMenu', // 这里套一层 subMenu 是因为 DataBlockInitializer 组件需要这样的数据结构,其实这层 subMenu 最终是不会渲染出来的
|
|
children: associationFields,
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const componentTypeMap = {
|
|
ReadPrettyFormItem: 'Details',
|
|
};
|
|
const otherRecords = {
|
|
name: 'otherRecords',
|
|
Component: DataBlockInitializer,
|
|
// 目的是使点击无效
|
|
onClick() {},
|
|
componentProps: {
|
|
...dataBlockInitializerProps,
|
|
hideSearch: false,
|
|
icon: null,
|
|
title: otherText || t('Other records'),
|
|
name: 'otherRecords',
|
|
showAssociationFields: false,
|
|
onlyCurrentDataSource: false,
|
|
hideChildrenIfSingleCollection: false,
|
|
fromOthersInPopup: true,
|
|
componentType: componentTypeMap[componentName] || componentName,
|
|
filter({ collection, associationField }) {
|
|
if (filterOtherRecordsCollection) {
|
|
return filterOtherRecordsCollection(collection);
|
|
}
|
|
return true;
|
|
},
|
|
onClick(options) {
|
|
onClick({ ...options, fromOthersInPopup: true });
|
|
},
|
|
},
|
|
};
|
|
|
|
let children;
|
|
|
|
const _associationRecords = associationFields.length ? associationRecords : null;
|
|
if (noAssociationMenu[0].children.length && associationFields.length) {
|
|
if (hideOtherRecordsInPopup) {
|
|
children = [currentRecord, _associationRecords];
|
|
} else {
|
|
children = [currentRecord, _associationRecords, otherRecords];
|
|
}
|
|
} else if (noAssociationMenu[0].children.length) {
|
|
if (hideOtherRecordsInPopup) {
|
|
// 当可选数据表只有一个时,实现只点击一次区块 menu 就能创建区块
|
|
if (noAssociationMenu[0].children.length <= 1) {
|
|
noAssociationMenu[0].children = (noAssociationMenu[0].children[0]?.children as any) || [];
|
|
return noAssociationMenu;
|
|
}
|
|
children = [currentRecord];
|
|
} else {
|
|
children = [currentRecord, otherRecords];
|
|
}
|
|
} else {
|
|
if (hideOtherRecordsInPopup) {
|
|
children = [_associationRecords];
|
|
} else {
|
|
children = [_associationRecords, otherRecords];
|
|
}
|
|
}
|
|
|
|
return [
|
|
{
|
|
name: 'records',
|
|
label: t('Records'),
|
|
type: 'subMenu',
|
|
children: children.filter(Boolean),
|
|
},
|
|
];
|
|
}, [
|
|
associationFields,
|
|
collection.dataSource,
|
|
collection.name,
|
|
componentName,
|
|
dataBlockInitializerProps,
|
|
filterOtherRecordsCollection,
|
|
hideOtherRecordsInPopup,
|
|
noAssociationMenu,
|
|
onClick,
|
|
t,
|
|
]);
|
|
}
|
|
|
|
return noAssociationMenu;
|
|
};
|
|
|
|
/**
|
|
* @deprecated
|
|
* 已弃用,请使用 createDetailsUISchema 和 createDetailsWithPaginationUISchema 替代
|
|
* @param options
|
|
* @returns
|
|
*/
|
|
export const createDetailsBlockSchema = (options: {
|
|
collection: string;
|
|
dataSource: string;
|
|
rowKey?: string;
|
|
formItemInitializers?: string;
|
|
actionInitializers?: string;
|
|
association?: string;
|
|
template?: any;
|
|
settings?: string;
|
|
action?: string;
|
|
[key: string]: any;
|
|
}) => {
|
|
const {
|
|
formItemInitializers = 'details:configureFields',
|
|
actionInitializers = 'detailsWithPaging:configureActions',
|
|
collection,
|
|
dataSource,
|
|
association,
|
|
template,
|
|
settings,
|
|
action = 'list',
|
|
...others
|
|
} = options;
|
|
const resourceName = association || collection;
|
|
const schema: ISchema = {
|
|
type: 'void',
|
|
'x-acl-action': action === 'get' ? `${resourceName}:get` : `${resourceName}:view`,
|
|
'x-decorator': 'DetailsBlockProvider',
|
|
'x-decorator-props': {
|
|
dataSource,
|
|
collection,
|
|
association,
|
|
readPretty: true,
|
|
action,
|
|
...(action === 'list'
|
|
? {
|
|
params: {
|
|
pageSize: 1,
|
|
},
|
|
}
|
|
: {}),
|
|
...others,
|
|
},
|
|
'x-toolbar': 'BlockSchemaToolbar',
|
|
'x-settings': settings,
|
|
'x-component': 'CardItem',
|
|
properties: {
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-component': 'Details',
|
|
'x-use-component-props': 'useDetailsBlockProps',
|
|
'x-read-pretty': true,
|
|
properties: {
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-initializer': actionInitializers,
|
|
'x-component': 'ActionBar',
|
|
'x-component-props': {
|
|
style: {
|
|
marginBottom: 24,
|
|
},
|
|
},
|
|
properties: {},
|
|
},
|
|
grid: template || {
|
|
type: 'void',
|
|
'x-component': 'Grid',
|
|
'x-initializer': formItemInitializers,
|
|
properties: {},
|
|
},
|
|
...(action === 'list'
|
|
? {
|
|
pagination: {
|
|
type: 'void',
|
|
'x-component': 'Pagination',
|
|
'x-use-component-props': 'useDetailsPaginationProps',
|
|
},
|
|
}
|
|
: {}),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
return schema;
|
|
};
|
|
|
|
/**
|
|
* @deprecated
|
|
* 已弃用,请使用 createCreateFormBlockUISchema 或者 createEditFormBlockUISchema 替代
|
|
* @param options
|
|
* @returns
|
|
*/
|
|
export const createFormBlockSchema = (options: {
|
|
formItemInitializers?: string;
|
|
actionInitializers?: string;
|
|
collection: string;
|
|
resource?: string;
|
|
dataSource?: string;
|
|
association?: string;
|
|
action?: string;
|
|
actions?: Record<string, any>;
|
|
template?: any;
|
|
title?: string;
|
|
settings?: any;
|
|
'x-designer'?: string;
|
|
[key: string]: any;
|
|
}) => {
|
|
const {
|
|
formItemInitializers = 'form:configureFields',
|
|
actionInitializers = 'createForm:configureActions',
|
|
collection,
|
|
resource,
|
|
dataSource,
|
|
association,
|
|
action,
|
|
actions = {},
|
|
'x-designer': designer = 'FormV2.Designer',
|
|
template,
|
|
title,
|
|
settings,
|
|
...others
|
|
} = options;
|
|
const resourceName = resource || association || collection;
|
|
const schema: ISchema = {
|
|
type: 'void',
|
|
'x-acl-action-props': {
|
|
skipScopeCheck: !action,
|
|
},
|
|
'x-acl-action': action ? `${resourceName}:update` : `${resourceName}:create`,
|
|
'x-decorator': 'FormBlockProvider',
|
|
'x-decorator-props': {
|
|
...others,
|
|
action,
|
|
dataSource,
|
|
resource: resourceName,
|
|
collection,
|
|
association,
|
|
},
|
|
'x-toolbar': 'BlockSchemaToolbar',
|
|
...(settings ? { 'x-settings': settings } : { 'x-designer': designer }),
|
|
'x-component': 'CardItem',
|
|
'x-component-props': {
|
|
title,
|
|
},
|
|
properties: {
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-component': 'FormV2',
|
|
'x-use-component-props': 'useFormBlockProps',
|
|
properties: {
|
|
grid: template || {
|
|
type: 'void',
|
|
'x-component': 'Grid',
|
|
'x-initializer': formItemInitializers,
|
|
properties: {},
|
|
},
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-initializer': actionInitializers,
|
|
'x-component': 'ActionBar',
|
|
'x-component-props': {
|
|
layout: 'one-column',
|
|
style: {
|
|
marginTop: 24,
|
|
},
|
|
},
|
|
properties: actions,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
return schema;
|
|
};
|
|
|
|
/**
|
|
* @deprecated
|
|
* 已弃用,可以使用 createDetailsBlockSchema 替换
|
|
* @param options
|
|
* @returns
|
|
*/
|
|
export const createReadPrettyFormBlockSchema = (options) => {
|
|
const {
|
|
formItemInitializers = 'details:configureFields',
|
|
actionInitializers = 'details:configureActions',
|
|
collection,
|
|
association,
|
|
dataSource,
|
|
resource,
|
|
template,
|
|
settings,
|
|
...others
|
|
} = options;
|
|
const resourceName = resource || association || collection;
|
|
const schema: ISchema = {
|
|
type: 'void',
|
|
'x-acl-action': `${resourceName}:get`,
|
|
'x-decorator': 'FormBlockProvider',
|
|
'x-decorator-props': {
|
|
resource: resourceName,
|
|
collection,
|
|
association,
|
|
dataSource,
|
|
readPretty: true,
|
|
action: 'get',
|
|
useParams: '{{ useParamsFromRecord }}',
|
|
...others,
|
|
},
|
|
'x-toolbar': 'BlockSchemaToolbar',
|
|
'x-settings': settings,
|
|
'x-component': 'CardItem',
|
|
properties: {
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-component': 'FormV2',
|
|
'x-use-component-props': 'useFormBlockProps',
|
|
'x-read-pretty': true,
|
|
properties: {
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-initializer': actionInitializers,
|
|
'x-component': 'ActionBar',
|
|
'x-component-props': {
|
|
style: {
|
|
marginBottom: 24,
|
|
},
|
|
},
|
|
properties: {},
|
|
},
|
|
grid: template || {
|
|
type: 'void',
|
|
'x-component': 'Grid',
|
|
'x-initializer': formItemInitializers,
|
|
properties: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
return schema;
|
|
};
|
|
|
|
/**
|
|
* @deprecated
|
|
* 已弃用,可以使用 createTableBlockUISchema 替换
|
|
* @param options
|
|
* @returns
|
|
*/
|
|
export const createTableBlockSchema = (options) => {
|
|
const {
|
|
collection,
|
|
rowKey,
|
|
tableActionInitializers,
|
|
tableColumnInitializers,
|
|
tableActionColumnInitializers,
|
|
tableBlockProvider,
|
|
disableTemplate,
|
|
dataSource,
|
|
blockType,
|
|
pageSize = 20,
|
|
...others
|
|
} = options;
|
|
const schema: ISchema = {
|
|
type: 'void',
|
|
'x-decorator': tableBlockProvider ?? 'TableBlockProvider',
|
|
'x-acl-action': `${collection}:list`,
|
|
'x-decorator-props': {
|
|
collection,
|
|
dataSource,
|
|
action: 'list',
|
|
params: {
|
|
pageSize,
|
|
},
|
|
rowKey,
|
|
showIndex: true,
|
|
dragSort: false,
|
|
disableTemplate: disableTemplate ?? false,
|
|
blockType,
|
|
...others,
|
|
},
|
|
'x-toolbar': 'BlockSchemaToolbar',
|
|
'x-settings': 'blockSettings:table',
|
|
'x-component': 'CardItem',
|
|
'x-filter-targets': [],
|
|
properties: {
|
|
actions: {
|
|
type: 'void',
|
|
'x-initializer': tableActionInitializers ?? 'table:configureActions',
|
|
'x-component': 'ActionBar',
|
|
'x-component-props': {
|
|
style: {
|
|
marginBottom: 'var(--nb-spacing)',
|
|
},
|
|
},
|
|
properties: {},
|
|
},
|
|
[uid()]: {
|
|
type: 'array',
|
|
'x-initializer': tableColumnInitializers ?? 'table:configureColumns',
|
|
'x-component': 'TableV2',
|
|
'x-use-component-props': 'useTableBlockProps',
|
|
'x-component-props': {
|
|
rowKey: 'id',
|
|
rowSelection: {
|
|
type: 'checkbox',
|
|
},
|
|
},
|
|
properties: {
|
|
actions: {
|
|
type: 'void',
|
|
title: '{{ t("Actions") }}',
|
|
'x-action-column': 'actions',
|
|
'x-decorator': 'TableV2.Column.ActionBar',
|
|
'x-component': 'TableV2.Column',
|
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
|
'x-initializer': tableActionColumnInitializers ?? 'table:configureItemActions',
|
|
properties: {
|
|
[uid()]: {
|
|
type: 'void',
|
|
'x-decorator': 'DndContext',
|
|
'x-component': 'Space',
|
|
'x-component-props': {},
|
|
properties: {},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
// console.log(JSON.stringify(schema, null, 2));
|
|
return schema;
|
|
};
|
|
|
|
const getChildren = ({
|
|
name,
|
|
association,
|
|
collections,
|
|
dataSource,
|
|
componentName,
|
|
searchValue,
|
|
getTemplatesByCollection,
|
|
t,
|
|
componentNamePrefix,
|
|
}: {
|
|
name: string;
|
|
association: string;
|
|
collections: any[];
|
|
componentName: string;
|
|
searchValue: string;
|
|
dataSource: string;
|
|
getTemplatesByCollection: (dataSource: string, collectionName: string, resourceName?: string) => any;
|
|
t: any;
|
|
componentNamePrefix: string;
|
|
}) => {
|
|
return collections
|
|
?.filter((item) => {
|
|
if (item.inherit) {
|
|
return false;
|
|
}
|
|
if (!item.filterTargetKey) {
|
|
return false;
|
|
} else if (
|
|
[componentNamePrefix + 'Kanban', componentNamePrefix + 'FormItem'].includes(componentName) &&
|
|
((item.template === 'view' && !item.writableView) || item.template === 'sql')
|
|
) {
|
|
return false;
|
|
} else if (
|
|
item.template === 'file' &&
|
|
[componentNamePrefix + 'Kanban', componentNamePrefix + 'FormItem', componentNamePrefix + 'Calendar'].includes(
|
|
componentName,
|
|
)
|
|
) {
|
|
return false;
|
|
} else {
|
|
const title = item.title || item.tableName;
|
|
if (!title) {
|
|
return false;
|
|
}
|
|
return title.toUpperCase().includes(searchValue.toUpperCase()) && !(item?.isThrough && item?.autoCreate);
|
|
}
|
|
})
|
|
?.map((item, index) => {
|
|
const title = item.title || item.tableName || item.label;
|
|
const templates = getTemplatesByCollection(dataSource, item.name).filter((template) => {
|
|
// 弹窗中的 Current record 选项
|
|
const isCurrentRecordOption = name !== 'otherRecords' && association;
|
|
|
|
if (isCurrentRecordOption) {
|
|
if (template.resourceName !== association) {
|
|
return false;
|
|
}
|
|
return componentName && template.componentName === componentName;
|
|
}
|
|
|
|
if (!isCurrentRecordOption && template?.resourceName?.includes('.')) {
|
|
return false;
|
|
}
|
|
|
|
return componentName && template.componentName === componentName;
|
|
});
|
|
const extralCollectionMenuItems = Array.from(initializerMenusGenerators.values())
|
|
.map((generator) => {
|
|
return generator({ item, index, componentName, association });
|
|
})
|
|
.filter(Boolean)
|
|
.flat();
|
|
if ((!templates.length && !extralCollectionMenuItems.length) || isInTemplateSettingPage()) {
|
|
return {
|
|
type: 'item',
|
|
name: item.name,
|
|
title,
|
|
dataSource,
|
|
};
|
|
}
|
|
const deprecatedTemplatesMenuItems = [];
|
|
if (templates.length) {
|
|
deprecatedTemplatesMenuItems.push(
|
|
{
|
|
type: 'divider',
|
|
},
|
|
{
|
|
type: 'itemGroup',
|
|
title: DeprecatedTemplateTitleElement,
|
|
children: [
|
|
{
|
|
key: `${componentName}_table_subMenu_${index}_copy`,
|
|
type: 'subMenu',
|
|
name: 'copy',
|
|
dataSource,
|
|
title: t('Duplicate template'),
|
|
children: templates.map((template) => {
|
|
const templateName = [
|
|
componentNamePrefix + 'FormItem',
|
|
componentNamePrefix + 'ReadPrettyFormItem',
|
|
].includes(template?.componentName)
|
|
? `${template?.name} ${t('(Fields only)')}`
|
|
: template?.name;
|
|
return {
|
|
type: 'item',
|
|
mode: 'copy',
|
|
name: item.name,
|
|
template,
|
|
dataSource,
|
|
title: templateName || t('Untitled'),
|
|
};
|
|
}),
|
|
},
|
|
{
|
|
key: `${componentName}_table_subMenu_${index}_ref`,
|
|
type: 'subMenu',
|
|
name: 'ref',
|
|
dataSource,
|
|
title: t('Reference template'),
|
|
children: templates.map((template) => {
|
|
const templateName = [
|
|
componentNamePrefix + 'FormItem',
|
|
componentNamePrefix + 'ReadPrettyFormItem',
|
|
].includes(template?.componentName)
|
|
? `${template?.name} ${t('(Fields only)')}`
|
|
: template?.name;
|
|
return {
|
|
type: 'item',
|
|
mode: 'reference',
|
|
name: item.name,
|
|
template,
|
|
dataSource,
|
|
title: templateName || t('Untitled'),
|
|
};
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
);
|
|
}
|
|
return {
|
|
key: `${componentName}_table_subMenu_${index}`,
|
|
type: 'subMenu',
|
|
name: `${item.name}_${index}`,
|
|
title,
|
|
dataSource,
|
|
children: [
|
|
{
|
|
type: 'item',
|
|
name: item.name,
|
|
dataSource,
|
|
title: t('Blank block'),
|
|
},
|
|
...extralCollectionMenuItems,
|
|
...deprecatedTemplatesMenuItems,
|
|
],
|
|
};
|
|
});
|
|
};
|
|
|
|
function getGroupItemForForm({
|
|
getCollectionFields,
|
|
field,
|
|
getInterface,
|
|
getCollection,
|
|
readPretty,
|
|
block,
|
|
maxDepth,
|
|
depth,
|
|
schemaName,
|
|
t,
|
|
}: {
|
|
getCollectionFields: (name: any, customDataSource?: string) => CollectionFieldOptions_deprecated[];
|
|
field: CollectionFieldOptions;
|
|
getInterface: (name: string) => any;
|
|
getCollection: (name: any, customDataSource?: string) => CollectionOptions;
|
|
readPretty: any;
|
|
block: any;
|
|
maxDepth: number;
|
|
depth: number;
|
|
schemaName: string;
|
|
t: any;
|
|
}) {
|
|
const subFields = getCollectionFields(field.target);
|
|
const items = subFields
|
|
?.filter((subField) => subField?.interface && !['subTable'].includes(subField?.interface) && !subField.treeChildren)
|
|
?.map((subField) => {
|
|
const interfaceConfig = getInterface(subField.interface);
|
|
const isFileCollection = field?.target && getCollection(field?.target)?.template === 'file';
|
|
const newSchemaName = `${schemaName}.${subField.name}`;
|
|
const schema = {
|
|
type: 'string',
|
|
name: newSchemaName,
|
|
// 'x-designer': 'FormItem.Designer',
|
|
'x-toolbar': 'FormItemSchemaToolbar',
|
|
'x-settings': 'fieldSettings:FormItem',
|
|
'x-component': 'CollectionField',
|
|
'x-read-pretty': readPretty,
|
|
'x-component-props': {
|
|
'pattern-disable': block === 'Form' && readPretty,
|
|
fieldNames: isFileCollection
|
|
? {
|
|
label: 'preview',
|
|
value: 'id',
|
|
}
|
|
: undefined,
|
|
},
|
|
'x-decorator': 'FormItem',
|
|
'x-collection-field': `${field.target}.${subField.name}`,
|
|
};
|
|
return {
|
|
name: newSchemaName,
|
|
type: 'item',
|
|
title: subField?.uiSchema?.title || subField.name,
|
|
Component: 'CollectionFieldInitializer',
|
|
remove: removeGridFormItem,
|
|
schemaInitialize: (s) => {
|
|
interfaceConfig?.schemaInitialize?.(s, {
|
|
field: subField,
|
|
block,
|
|
readPretty,
|
|
targetCollection: getCollection(field.target),
|
|
});
|
|
},
|
|
schema,
|
|
} as SchemaInitializerItemType;
|
|
});
|
|
|
|
const displayCollectionFields = {
|
|
type: 'itemGroup',
|
|
name: `${schemaName}-displayCollectionFields`,
|
|
title: t('Display fields'),
|
|
children: items,
|
|
};
|
|
|
|
const children = [displayCollectionFields];
|
|
|
|
if (depth < maxDepth) {
|
|
const subChildren = subFields
|
|
?.filter((subField) => {
|
|
return ['o2o', 'oho', 'obo', 'm2o'].includes(subField.interface);
|
|
})
|
|
.map((subField) => {
|
|
return getGroupItemForForm({
|
|
getCollectionFields,
|
|
field: subField,
|
|
getInterface,
|
|
getCollection,
|
|
schemaName: `${schemaName}.${subField.name}`,
|
|
readPretty,
|
|
block,
|
|
maxDepth,
|
|
depth: depth + 1,
|
|
t,
|
|
});
|
|
});
|
|
|
|
if (subChildren.length) {
|
|
const group: any = {
|
|
type: 'itemGroup',
|
|
name: `${schemaName}-associationFields`,
|
|
title: t('Display association fields'),
|
|
children: subChildren,
|
|
};
|
|
|
|
children.push(group);
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'subMenu',
|
|
name: `${schemaName}.${field.name}`,
|
|
title: field.uiSchema?.title,
|
|
children,
|
|
} as SchemaInitializerItemType;
|
|
}
|
|
|
|
function useAssociationFields({
|
|
componentName,
|
|
filterCollections,
|
|
showAssociationFields,
|
|
componentNamePrefix,
|
|
name,
|
|
}: {
|
|
componentName: string;
|
|
filterCollections: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
|
|
componentNamePrefix: string;
|
|
showAssociationFields?: boolean;
|
|
name: string;
|
|
}) {
|
|
const fieldSchema = useFieldSchema();
|
|
const { getCollectionFields } = useCollectionManager_deprecated();
|
|
const collection = useCollection_deprecated();
|
|
const cm = useCollectionManager();
|
|
const dataSource = useDataSourceKey();
|
|
const { getTemplatesByCollection } = useSchemaTemplateManager();
|
|
const { t } = useTranslation();
|
|
const compile = useCompile();
|
|
|
|
return useMemo(() => {
|
|
if (!showAssociationFields) {
|
|
return [];
|
|
}
|
|
|
|
let fields: CollectionFieldOptions[] = [];
|
|
|
|
if (fieldSchema['x-initializer']) {
|
|
fields = collection.fields;
|
|
} else {
|
|
const collection = recursiveParent(fieldSchema.parent);
|
|
if (collection) {
|
|
fields = getCollectionFields(collection);
|
|
}
|
|
}
|
|
|
|
return fields
|
|
.filter((field) => ['linkTo', 'subTable', 'o2m', 'm2m', 'obo', 'oho', 'o2o', 'm2o'].includes(field.interface))
|
|
.filter((field) => filterCollections({ associationField: field }))
|
|
.map((field, index) => {
|
|
const title = compile(field.uiSchema.title || field.name);
|
|
const templates = getTemplatesByCollection(dataSource, field.target).filter((template) => {
|
|
if (template.resourceName !== `${field.collectionName}.${field.name}`) {
|
|
return false;
|
|
}
|
|
|
|
// 针对弹窗中的详情区块
|
|
if (componentName === componentNamePrefix + 'ReadPrettyFormItem') {
|
|
if (['hasOne', 'belongsTo'].includes(field.type)) {
|
|
return template.componentName === componentNamePrefix + 'ReadPrettyFormItem';
|
|
} else {
|
|
return template.componentName === componentNamePrefix + 'Details';
|
|
}
|
|
}
|
|
|
|
return template.componentName === componentName;
|
|
});
|
|
const keyPrefix = `associationFiled_table_subMenu`;
|
|
const extralCollectionMenuItems = Array.from(initializerMenusGenerators.values())
|
|
.map((generator) => generator({ collection, index, field, componentName, keyPrefix, name }))
|
|
.filter(Boolean)
|
|
.flat();
|
|
if ((!templates.length && !extralCollectionMenuItems.length) || isInTemplateSettingPage()) {
|
|
return {
|
|
type: 'item',
|
|
name: `${field.collectionName}.${field.name}`,
|
|
collectionName: field.target,
|
|
title,
|
|
dataSource,
|
|
associationField: field,
|
|
};
|
|
}
|
|
const deprecatedTemplatesMenuItems = [];
|
|
if (templates.length) {
|
|
deprecatedTemplatesMenuItems.push(
|
|
{
|
|
type: 'divider',
|
|
},
|
|
{
|
|
type: 'itemGroup',
|
|
title: DeprecatedTemplateTitleElement,
|
|
children: [
|
|
{
|
|
key: `associationFiled_${componentName}_table_subMenu_${index}_copy`,
|
|
type: 'subMenu',
|
|
name: 'copy',
|
|
dataSource,
|
|
title: t('Duplicate template'),
|
|
children: templates.map((template) => {
|
|
const templateName = [
|
|
componentNamePrefix + 'FormItem',
|
|
componentNamePrefix + 'ReadPrettyFormItem',
|
|
].includes(template?.componentName)
|
|
? `${template?.name} ${t('(Fields only)')}`
|
|
: template?.name;
|
|
return {
|
|
type: 'item',
|
|
mode: 'copy',
|
|
name: `${field.collectionName}.${field.name}`,
|
|
collectionName: field.target,
|
|
template,
|
|
dataSource,
|
|
title: templateName || t('Untitled'),
|
|
associationField: field,
|
|
};
|
|
}),
|
|
},
|
|
{
|
|
key: `associationFiled_${componentName}_table_subMenu_${index}_ref`,
|
|
type: 'subMenu',
|
|
name: 'ref',
|
|
dataSource,
|
|
title: t('Reference template'),
|
|
children: templates.map((template) => {
|
|
const templateName = [
|
|
componentNamePrefix + 'FormItem',
|
|
componentNamePrefix + 'ReadPrettyFormItem',
|
|
].includes(template?.componentName)
|
|
? `${template?.name} ${t('(Fields only)')}`
|
|
: template?.name;
|
|
return {
|
|
type: 'item',
|
|
mode: 'reference',
|
|
name: `${field.collectionName}.${field.name}`,
|
|
collectionName: field.target,
|
|
template,
|
|
dataSource,
|
|
title: templateName || t('Untitled'),
|
|
associationField: field,
|
|
};
|
|
}),
|
|
},
|
|
],
|
|
},
|
|
);
|
|
}
|
|
return {
|
|
key: `associationFiled_${componentName}_table_subMenu_${index}`,
|
|
type: 'subMenu',
|
|
name: `${field.target}_${index}`,
|
|
title,
|
|
dataSource,
|
|
children: [
|
|
{
|
|
type: 'item',
|
|
name: `${field.collectionName}.${field.name}`,
|
|
collectionName: field.target,
|
|
dataSource,
|
|
title: t('Blank block'),
|
|
associationField: field,
|
|
},
|
|
...extralCollectionMenuItems,
|
|
...deprecatedTemplatesMenuItems,
|
|
],
|
|
};
|
|
});
|
|
}, [
|
|
collection.fields,
|
|
compile,
|
|
componentName,
|
|
dataSource,
|
|
fieldSchema,
|
|
filterCollections,
|
|
getCollectionFields,
|
|
getTemplatesByCollection,
|
|
showAssociationFields,
|
|
t,
|
|
componentNamePrefix,
|
|
]);
|
|
}
|
|
|
|
const isInTemplateSettingPage = () => window.location.pathname.includes('/block-templates/');
|
|
|
|
const initializerMenusGenerators = new Map<
|
|
string,
|
|
(options: any) => SchemaInitializerItemType | SchemaInitializerItemType[]
|
|
>();
|
|
|
|
export function registerInitializerMenusGenerator(
|
|
key: string,
|
|
generator: (options: any) => SchemaInitializerItemType | SchemaInitializerItemType[],
|
|
) {
|
|
initializerMenusGenerators.set(key, generator);
|
|
}
|