From 1be5b2f5787d1f55829c444ff76c72a3c05dfe6f Mon Sep 17 00:00:00 2001
From: jack zhang <1098626505@qq.com>
Date: Mon, 22 Jul 2024 11:25:12 +0800
Subject: [PATCH] refactor: collection fields to initializer items (#4900)
* refactor: add collection-fields-initializer-items
* fix: bug
* fix: bug
* fix: rename and docs
* fix: change dir
* docs: imporve doc
* Update packages/core/client/docs/en-US/core/data-source/collection-fields-to-initializer-items.md
Co-authored-by: Zeke Zhang <958414905@qq.com>
* Update packages/core/client/docs/en-US/core/data-source/collection-fields-to-initializer-items.md
Co-authored-by: Zeke Zhang <958414905@qq.com>
---------
Co-authored-by: Zeke Zhang <958414905@qq.com>
---
packages/core/client/.dumirc.ts | 4 +
.../collection-fields-to-initializer-items.md | 339 ++++++++++++++++++
.../collection-fields-to-initializer-items.md | 309 ++++++++++++++++
...CollectionFieldsToFormInitializerItems.tsx | 78 ++++
.../CollectionFieldsToInitializerItems.tsx | 37 ++
...ollectionFieldsToTableInitializerItems.tsx | 127 +++++++
.../index.ts | 12 +
.../items/AssociationCollectionFields.tsx | 73 ++++
.../items/ParentCollectionFields.tsx | 47 +++
.../items/SelfFields.tsx | 13 +
.../items/index.ts | 3 +
.../utils/getInitializerItemsByFields.ts | 112 ++++++
.../utils/index.ts | 3 +
.../utils/type.ts | 135 +++++++
.../utils/useCollectionFieldContext.ts | 62 ++++
packages/core/client/src/data-source/index.ts | 1 +
.../client/src/schema-initializer/utils.ts | 2 +-
17 files changed, 1356 insertions(+), 1 deletion(-)
create mode 100644 packages/core/client/docs/en-US/core/data-source/collection-fields-to-initializer-items.md
create mode 100644 packages/core/client/docs/zh-CN/core/data-source/collection-fields-to-initializer-items.md
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToFormInitializerItems.tsx
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToInitializerItems.tsx
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToTableInitializerItems.tsx
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/index.ts
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/items/AssociationCollectionFields.tsx
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/items/ParentCollectionFields.tsx
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/items/SelfFields.tsx
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/items/index.ts
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/getInitializerItemsByFields.ts
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/index.ts
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/type.ts
create mode 100644 packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/useCollectionFieldContext.ts
diff --git a/packages/core/client/.dumirc.ts b/packages/core/client/.dumirc.ts
index a4c092e84e..e7e7632580 100644
--- a/packages/core/client/.dumirc.ts
+++ b/packages/core/client/.dumirc.ts
@@ -186,6 +186,10 @@ export default defineConfig({
title: 'ExtendCollectionsProvider',
link: '/core/data-source/extend-collections-provider',
},
+ {
+ title: 'Collection Fields To Initializer Items',
+ link: '/core/data-source/collection-fields-to-initializer-items',
+ },
]
},
{
diff --git a/packages/core/client/docs/en-US/core/data-source/collection-fields-to-initializer-items.md b/packages/core/client/docs/en-US/core/data-source/collection-fields-to-initializer-items.md
new file mode 100644
index 0000000000..3e5df8fe82
--- /dev/null
+++ b/packages/core/client/docs/en-US/core/data-source/collection-fields-to-initializer-items.md
@@ -0,0 +1,339 @@
+# Collection Fields To Initializer Items
+
+## 介绍
+
+![20240718145531](https://static-docs.nocobase.com/20240718145531.png)
+
+页面上有 `Configure columns` 和 `Configure fields` 两个按钮,鼠标悬浮后显示当前表的字段列表,当点击某个字段后,会插入表格列或者表单项到界面中,这个过程就是从 `Collection Fields` 到 `Initializer Items` 的过程。
+
+## Configure fields 分类
+
+`Configure fields` 分为三类:
+
+- `self collection fields`:当前表的字段
+- `parent collection fields`:父表的字段
+- `associated collection fields`:关联表的字段
+
+![20240718151313](https://static-docs.nocobase.com/20240718151313.png)
+
+![20240718151040](https://static-docs.nocobase.com/20240718151040.png)
+
+## CollectionFieldsToInitializerItems
+
+我们将单个 `Collection Field` 转为 `Initializer Item` 的过程抽象为以下三个步骤:
+
+- `filter`:过滤字段
+- `getSchema`:获取字段对应的 schema
+- `getInitializerItem`:获取字段对应的 initializer item
+
+> 关于 Schema 请查看 [UI Schema](https://docs.nocobase.com/development/client/ui-schema/what-is-ui-schema)。
+> 关于 Initializer item,可以参考 [SchemaInitializer](/core/ui-schema/schema-initializer)。
+> 两者的关系是 Initializer item 通过类似 `onClick` 的事件触发,将 Schema 插入到 Schema 树中,并渲染到界面上。
+
+`CollectionFieldsToInitializerItems` 是一个组件,用于将 `Collection Fields` 转换为 `Initializer Items`。
+
+
+```ts
+const someInitializer = new SchemaInitializer({
+ // ...
+ items: [
+ {
+ name: 'collectionFields',
+ Component: CollectionFieldsToInitializerItems,
+ },
+ // ...
+ ]
+})
+```
+
+### Types
+
+```ts
+interface CollectionFieldContext {
+ fieldSchema: ISchema;
+ collection?: InheritanceCollectionMixin & Collection;
+ dataSource: DataSource;
+ form: Form;
+ actionContext: ReturnType;
+ t: TFunction<"translation", undefined>;
+ collectionManager: CollectionManager;
+ dataSourceManager: DataSourceManager;
+ compile: (source: any, ext?: any) => any
+ targetCollection?: Collection;
+}
+
+interface CommonCollectionFieldsProps {
+ block: string;
+ isReadPretty?: (context: CollectionFieldContext) => boolean;
+ filter?: (collectionField: CollectionFieldOptions, context: CollectionFieldContext) => boolean;
+ getSchema: (collectionField: CollectionFieldOptions, context: CollectionFieldContext) => CollectionFieldGetSchemaResult;
+ getInitializerItem?: (collectionField: CollectionFieldOptions, context: CollectionFieldContext) => CollectionFieldGetInitializerItemResult;
+}
+
+interface SelfCollectionFieldsProps extends CommonCollectionFieldsProps {}
+interface ParentCollectionFieldsProps extends CommonCollectionFieldsProps {}
+interface AssociationCollectionFieldsProps extends Omit {
+ filterSelfField?: CommonCollectionFieldsProps['filter'];
+ filterAssociationField?: CommonCollectionFieldsProps['filter'];
+}
+
+interface CollectionFieldsProps {
+ /**
+ * Block name.
+ */
+ block: string;
+ selfField: Omit;
+ parentField?: Omit;
+ associationField?: Omit;
+}
+```
+
+#### CollectionFieldsProps
+
+- `block`:区块名称
+- `selfField`:当前表字段配置
+- `parentField`:父表字段配置
+- `associationField`:关联表字段配置
+
+#### CommonCollectionFieldsProps
+
+- `block`:区块名称
+- `isReadPretty`:是否为只读模式
+- `filter`:过滤字段
+- `getSchema`:获取字段对应的 schema
+- `getInitializerItem`:获取字段对应的 initializer item
+
+##### 公共 Schema
+
+其中 `getSchema` 内部包含了公共的部分,所以并不要求返回整个 Schema,只需要返回差异部分即可。公共部分如下:
+
+```ts
+const defaultSchema: CollectionFieldDefaultSchema = {
+ type: 'string',
+ title: collectionField?.uiSchema?.title || collectionField.name,
+ name: collectionField.name,
+ 'x-component': 'CollectionField',
+ 'x-collection-field': `${collection.name}.${collectionField.name}`,
+ 'x-read-pretty': collectionField?.uiSchema?.['x-read-pretty'],
+};
+```
+
+其中 [CollectionField](/core/data-source/collection-field) 用于动态渲染字段。
+
+##### 公共 Initializer Item
+
+同样 `getInitializerItem` 内部包含了公共的部分,所以并不要求返回整个 Initializer Item,只需要返回 `CollectionFieldInitializer`(文档 TODO)组件对应的 `find` 和 `remove`。
+
+#### AssociationCollectionFieldsProps
+
+- `filterSelfField`:过滤当前表字段
+- `filterAssociationField`:过滤关联表字段
+
+其他属性同 `CommonCollectionFieldsProps`。
+
+#### CollectionFieldContext
+
+- [fieldSchema](/core/ui-schema/designable#usefieldschema):当前 schema
+- [collection](/core/data-source/collection):当前表
+- [dataSource](/core/data-source/data-source-provider#usedatasource):数据源
+- `form`:表单
+- [actionContext](/components/action#actioncontext):操作上下文
+- `t`:国际化
+- [collectionManager](/core/data-source/collection-manager-provider#usecollectionmanager):表管理器
+- [dataSourceManager](/core/data-source/data-source-manager-provider#usedatasourcemanager):数据源管理器
+- `compile`:编译函数
+- `targetCollection`:如果是关联表字段,表示关联表
+
+### Example
+
+我们以 `Collection Field` 转为 `FormItem` 为例:
+
+#### 定义
+
+```tsx | pure
+
+export const CollectionFieldsToFormInitializerItems: FC<{ block?: string }> = (props) => {
+ const block = props?.block || 'Form';
+ const fieldItemSchema = {
+ 'x-toolbar': 'FormItemSchemaToolbar',
+ 'x-settings': 'fieldSettings:FormItem',
+ 'x-decorator': 'FormItem',
+ };
+
+ const initializerItem = {
+ remove: removeGridFormItem,
+ }
+ return !field.treeChildren,
+ getSchema: (field, { targetCollection }) => {
+ const isFileCollection = targetCollection?.template === 'file';
+ const isAssociationField = targetCollection;
+ const fieldNames = field?.uiSchema?.['x-component-props']?.['fieldNames'];
+
+ return {
+ ...fieldItemSchema,
+ 'x-component-props': isFileCollection
+ ? { fieldNames: { label: 'preview', value: 'id' } }
+ : isAssociationField && fieldNames? { fieldNames: { ...fieldNames, label: targetCollection?.titleField || fieldNames.label }}
+ : {},
+ }
+ },
+ getInitializerItem: () => {
+ return {
+ ...initializerItem,
+ find: props?.block === 'Kanban' ? findKanbanFormItem : undefined
+ }
+ }
+ }}
+ parentField={{
+ getSchema: () => fieldItemSchema,
+ getInitializerItem: () => initializerItem,
+ }}
+ associationField={{
+ filterSelfField: (field) => {
+ if (block !== 'Form') return true;
+ return field?.interface === 'm2o'
+ },
+ filterAssociationField(collectionField) {
+ return collectionField?.interface && !['subTable'].includes(collectionField?.interface) && !collectionField.treeChildren
+ },
+ getSchema: () => fieldItemSchema,
+ getInitializerItem: () => initializerItem,
+ }}
+ />
+```
+
+##### selfField
+
+- `filter`:过滤字段
+
+`filter: (field) => !field.treeChildren` 表示过滤掉树形结构的字段。
+
+因为 ?
+
+- `getSchema`:获取字段对应的 schema
+
+参考 [FormItem](/components/form-item) 以及 [Field](/components/checkbox) 文档,希望最终获得的 Schema 如下:
+
+```json
+{
+ "type": "string",
+ "name": "nickname",
+ "x-toolbar": "FormItemSchemaToolbar",
+ "x-settings": "fieldSettings:FormItem",
+ "x-component": "CollectionField",
+ "x-decorator": "FormItem",
+ "x-collection-field": "users.nickname",
+ "x-component-props": {
+ // ...
+ },
+}
+```
+
+其中 [公共部分](/core/data-source/collection-fields-initializer-items#commoncollectionfieldsprops) 如下:
+
+```json
+{
+ "type": "string",
+ "name": "nickname",
+ "x-component": "CollectionField",
+ "x-collection-field": "users.nickname",
+ "x-read-pretty": true
+}
+```
+
+所以我们只需要返回:
+
+```json
+{
+ "x-toolbar": "FormItemSchemaToolbar",
+ "x-settings": "fieldSettings:FormItem",
+ "x-decorator": "FormItem",
+ "x-component-props": {
+ // ...
+ },
+}
+```
+
+- `getInitializerItem`:获取字段对应的 initializer item
+
+因为其对应的 [SchemaInitializer](/core/ui-schema/schema-initializer) 有 wrap 属性,将每个字段包裹在 `Grid` 中,方便布局和拖拽。我们在删除时则不仅需要删除自身的 Schema 还需要删除对应的 `Grid`。所以我们返回:
+
+```ts
+{
+ "remove": removeGridFormItem
+}
+```
+
+##### parentField
+
+略。
+
+##### associationField
+
+- `filterSelfField`:过滤当前表字段
+
+表单这里仅需要展示多对一的关联字段,所以我们过滤掉非多对一关联字段。?
+
+- `filterAssociationField`:过滤关联表字段
+
+同样过滤掉树形结构的字段。
+
+
+#### 使用
+
+```diff
+const formItemInitializers = new CompatibleSchemaInitializer({
+ name: 'form:configureFields',
+ wrap: gridRowColWrap,
+ icon: 'SettingOutlined',
+ title: '{{t("Configure fields")}}',
+ items: [
++ {
++ name: 'collectionFields',
++ Component: CollectionFieldsToFormInitializerItems,
++ },
+ // ...
+ ]
+})
+```
+
+## CollectionFieldsToFormInitializerItems
+
+`CollectionFieldsToFormInitializerItems` 是 `CollectionFieldsToInitializerItems` 的一个封装,用于表单场景。
+
+目前使用在了 `Form`、`List`、`Kanban`、`Grid Card` 和 `Details` 区块中。
+
+```ts
+const someInitializer = new SchemaInitializer({
+ // ...
+ items: [
+ {
+ name: 'collectionFields',
+ Component: CollectionFieldsToFormInitializerItems,
+ },
+ // ...
+ ]
+})
+```
+
+## CollectionFieldsToTableInitializerItems
+
+`CollectionFieldsToTableInitializerItems` 是 `CollectionFieldsToInitializerItems` 的一个封装,用于表格场景。
+
+目前使用在了 `Table` 和 `Gantt` 区块中。
+
+```ts
+const someInitializer = new SchemaInitializer({
+ // ...
+ items: [
+ {
+ name: 'collectionFields',
+ Component: CollectionFieldsToTableInitializerItems,
+ },
+ // ...
+ ]
+})
+```
diff --git a/packages/core/client/docs/zh-CN/core/data-source/collection-fields-to-initializer-items.md b/packages/core/client/docs/zh-CN/core/data-source/collection-fields-to-initializer-items.md
new file mode 100644
index 0000000000..3108839315
--- /dev/null
+++ b/packages/core/client/docs/zh-CN/core/data-source/collection-fields-to-initializer-items.md
@@ -0,0 +1,309 @@
+# Collection Fields To Initializer Items
+
+## 介绍
+
+![20240718145531](https://static-docs.nocobase.com/20240718145531.png)
+
+页面上有 `Configure columns` 和 `Configure fields` 两个按钮,点击后显示当前表的字段列表,当点击某个字段后,会插入表单项或者表格列到界面中,这个过程就是从 `Collection Fields` 到 `Initializer Items` 的过程。
+
+## Configure fields 分类
+
+`Configure fields` 分为三类:
+
+- `self collection fields`:当前表的字段
+- `parent collection fields`:父表的字段
+- `associated collection fields`:关联表的字段
+
+![20240718151313](https://static-docs.nocobase.com/20240718151313.png)
+
+![20240718151040](https://static-docs.nocobase.com/20240718151040.png)
+
+## CollectionFieldsToInitializerItems
+
+我们将单个 `Collection Field` 转为 `Initializer Item` 的过程抽象以下三个步骤:
+
+- `filter`:过滤字段
+- `getSchema`:获取字段对应的 schema
+- `getInitializerItem`:获取字段对应的 initializer item
+
+> 关于 Schema 请查看 [UI Schema](https://docs.nocobase.com/development/client/ui-schema/what-is-ui-schema)。
+> 关于 Initializer item,可以参考 [SchemaInitializer](/core/ui-schema/schema-initializer)。
+> 两者的关系是 Initializer item 通过类似 `onClick` 的事件触发,将 Schema 插入到 Schema 树中,并渲染到界面上。
+
+`CollectionFieldsToInitializerItems` 是一个组件,用于将 `Collection Fields` 转换为 `Initializer Items`。
+
+### Types
+
+```ts
+interface CollectionFieldContext {
+ fieldSchema: ISchema;
+ collection?: InheritanceCollectionMixin & Collection;
+ dataSource: DataSource;
+ form: Form;
+ actionContext: ReturnType;
+ t: TFunction<"translation", undefined>;
+ collectionManager: CollectionManager;
+ dataSourceManager: DataSourceManager;
+ compile: (source: any, ext?: any) => any
+ targetCollection?: Collection;
+}
+
+interface CommonCollectionFieldsProps {
+ block: string;
+ isReadPretty?: (context: CollectionFieldContext) => boolean;
+ filter?: (collectionField: CollectionFieldOptions, context: CollectionFieldContext) => boolean;
+ getSchema: (collectionField: CollectionFieldOptions, context: CollectionFieldContext & {
+ defaultSchema: CollectionFieldDefaultSchema
+ targetCollection?: Collection
+ collectionFieldInterface?: CollectionFieldInterface
+ }) => CollectionFieldGetSchemaResult;
+ getInitializerItem?: (collectionField: CollectionFieldOptions, context: CollectionFieldContext & {
+ schema: ISchema;
+ defaultInitializerItem: CollectionFieldDefaultInitializerItem;
+ targetCollection?: Collection
+ collectionFieldInterface?: CollectionFieldInterface
+ }) => CollectionFieldGetInitializerItemResult;
+}
+
+interface SelfCollectionFieldsProps extends CommonCollectionFieldsProps {}
+interface ParentCollectionFieldsProps extends CommonCollectionFieldsProps {}
+interface AssociationCollectionFieldsProps extends Omit {
+ filterSelfField?: CommonCollectionFieldsProps['filter'];
+ filterAssociationField?: CommonCollectionFieldsProps['filter'];
+}
+
+interface CollectionFieldsProps {
+ /**
+ * Block name.
+ */
+ block: string;
+ selfField: Omit;
+ parentField?: Omit;
+ associationField?: Omit;
+}
+```
+
+#### CollectionFieldsProps
+
+- `block`:区块名称
+- `selfField`:当前表字段配置
+- `parentField`:父表字段配置
+- `associationField`:关联表字段配置
+
+#### CommonCollectionFieldsProps
+
+- `block`:区块名称
+- `isReadPretty`:是否为只读模式
+- `filter`:过滤字段
+- `getSchema`:获取字段对应的 schema
+- `getInitializerItem`:获取字段对应的 initializer item
+
+##### 公共 Schema
+
+其中 `getSchema` 内部包含了公共的部分,所以并不要求返回整个 Schema,只需要返回差异部分即可。公共部分如下:
+
+```ts
+const defaultSchema: CollectionFieldDefaultSchema = {
+ type: 'string',
+ title: collectionField?.uiSchema?.title || collectionField.name,
+ name: collectionField.name,
+ 'x-component': 'CollectionField',
+ 'x-collection-field': `${collection.name}.${collectionField.name}`,
+ 'x-read-pretty': collectionField?.uiSchema?.['x-read-pretty'],
+};
+```
+
+其中 [CollectionField](/core/data-source/collection-field) 用于动态渲染字段。
+
+##### 公共 Initializer Item
+
+同样 `getInitializerItem` 内部包含了公共的部分,所以并不要求返回整个 Initializer Item,只需要返回 `CollectionFieldInitializer`(文档 TODO)组件对应的 `find` 和 `remove`。
+
+#### AssociationCollectionFieldsProps
+
+- `filterSelfField`:过滤当前表字段
+- `filterAssociationField`:过滤关联表字段
+
+其他属性同 `CommonCollectionFieldsProps`。
+
+#### CollectionFieldContext
+
+- [fieldSchema](/core/ui-schema/designable#usefieldschema):当前 schema
+- [collection](/core/data-source/collection):当前表
+- [dataSource](/core/data-source/data-source-provider#usedatasource):数据源
+- `form`:表单
+- [actionContext](/components/action#actioncontext):操作上下文
+- `t`:国际化
+- [collectionManager](/core/data-source/collection-manager-provider#usecollectionmanager):表管理器
+- [dataSourceManager](/core/data-source/data-source-manager-provider#usedatasourcemanager):数据源管理器
+- `compile`:编译函数
+- `targetCollection`:如果是关联表字段,表示关联表
+
+### Example
+
+我们以 `Collection Field` 转为 `FormItem` 为例:
+
+#### 定义
+
+```tsx | pure
+
+export const CollectionFieldsToFormInitializerItems: FC<{ block?: string }> = (props) => {
+ const block = props?.block || 'Form';
+ const fieldItemSchema = {
+ 'x-toolbar': 'FormItemSchemaToolbar',
+ 'x-settings': 'fieldSettings:FormItem',
+ 'x-decorator': 'FormItem',
+ };
+
+ const initializerItem = {
+ remove: removeGridFormItem,
+ }
+ return !field.treeChildren,
+ getSchema: (field, { targetCollection }) => {
+ const isFileCollection = targetCollection?.template === 'file';
+ const isAssociationField = targetCollection;
+ const fieldNames = field?.uiSchema?.['x-component-props']?.['fieldNames'];
+
+ return {
+ ...fieldItemSchema,
+ 'x-component-props': isFileCollection
+ ? { fieldNames: { label: 'preview', value: 'id' } }
+ : isAssociationField && fieldNames? { fieldNames: { ...fieldNames, label: targetCollection?.titleField || fieldNames.label }}
+ : {},
+ }
+ },
+ getInitializerItem: () => {
+ return {
+ ...initializerItem,
+ find: props?.block === 'Kanban' ? findKanbanFormItem : undefined
+ }
+ }
+ }}
+ parentField={{
+ getSchema: () => fieldItemSchema,
+ getInitializerItem: () => initializerItem,
+ }}
+ associationField={{
+ filterSelfField: (field) => {
+ if (block !== 'Form') return true;
+ return field?.interface === 'm2o'
+ },
+ filterAssociationField(collectionField) {
+ return collectionField?.interface && !['subTable'].includes(collectionField?.interface) && !collectionField.treeChildren
+ },
+ getSchema: () => fieldItemSchema,
+ getInitializerItem: () => initializerItem,
+ }}
+ />
+```
+
+##### selfField
+
+- `filter`:过滤字段
+
+`filter: (field) => !field.treeChildren` 表示过滤掉树形结构的字段。
+
+因为 ?
+
+- `getSchema`:获取字段对应的 schema
+
+参考 [FormItem](/components/form-item) 以及 [Field](/components/checkbox) 文档,希望最终获得的 Schema 如下:
+
+```json
+{
+ "type": "string",
+ "name": "nickname",
+ "x-toolbar": "FormItemSchemaToolbar",
+ "x-settings": "fieldSettings:FormItem",
+ "x-component": "CollectionField",
+ "x-decorator": "FormItem",
+ "x-collection-field": "users.nickname",
+ "x-component-props": {
+ // ...
+ },
+}
+```
+
+其中 [公共部分](/core/data-source/collection-fields-initializer-items#commoncollectionfieldsprops) 如下:
+
+```json
+{
+ "type": "string",
+ "name": "nickname",
+ "x-component": "CollectionField",
+ "x-collection-field": "users.nickname",
+ "x-read-pretty": true
+}
+```
+
+所以我们只需要返回:
+
+```json
+{
+ "x-toolbar": "FormItemSchemaToolbar",
+ "x-settings": "fieldSettings:FormItem",
+ "x-decorator": "FormItem",
+ "x-component-props": {
+ // ...
+ },
+}
+```
+
+- `getInitializerItem`:获取字段对应的 initializer item
+
+因为其对应的 [SchemaInitializer](/core/ui-schema/schema-initializer) 有 wrap 属性,将每个字段包裹在 `Grid` 中,方便布局和拖拽。我们在删除时则不仅需要删除自身的 Schema 还需要删除对应的 `Grid`。所以我们返回:
+
+```ts
+{
+ "remove": removeGridFormItem
+}
+```
+
+##### parentField
+
+略。
+
+##### associationField
+
+- `filterSelfField`:过滤当前表字段
+
+表单这里仅需要展示多对一的关联字段,所以我们过滤掉非多对一关联字段。?
+
+- `filterAssociationField`:过滤关联表字段
+
+同样过滤掉树形结构的字段。
+
+
+#### 使用
+
+```diff
+const formItemInitializers = new CompatibleSchemaInitializer({
+ name: 'form:configureFields',
+ wrap: gridRowColWrap,
+ icon: 'SettingOutlined',
+ title: '{{t("Configure fields")}}',
+ items: [
++ {
++ name: 'collectionFields',
++ Component: CollectionFieldsToFormInitializerItems,
++ },
+ // ...
+ ]
+})
+```
+
+## CollectionFieldsToFormInitializerItems
+
+`CollectionFieldsToFormInitializerItems` 是 `CollectionFieldsToInitializerItems` 的一个封装,用于表单场景。
+
+目前使用在了 `Form`、`List`、`Kanban`、`Grid Card` 和 `Details` 区块中。
+
+## CollectionFieldsToTableInitializerItems
+
+`CollectionFieldsToTableInitializerItems` 是 `CollectionFieldsToInitializerItems` 的一个封装,用于表格场景。
+
+目前使用在了 `Table` 和 `Gantt` 区块中。
+
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToFormInitializerItems.tsx b/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToFormInitializerItems.tsx
new file mode 100644
index 0000000000..e3b20df7a3
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToFormInitializerItems.tsx
@@ -0,0 +1,78 @@
+/**
+ * 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 React, { FC } from 'react';
+import { Schema } from '@formily/json-schema';
+import { CollectionFieldsToInitializerItems } from './CollectionFieldsToInitializerItems';
+import { removeGridFormItem, findSchema } from '../../schema-initializer/utils';
+
+export const findKanbanFormItem = (schema: Schema, key: string, action: string) => {
+ const s = findSchema(schema, 'x-component', 'Kanban');
+ return findSchema(s, key, action);
+};
+
+export const CollectionFieldsToFormInitializerItems: FC<{ block?: string }> = (props) => {
+ const block = props?.block || 'Form';
+ const fieldItemSchema = {
+ 'x-toolbar': 'FormItemSchemaToolbar',
+ 'x-settings': 'fieldSettings:FormItem',
+ 'x-decorator': 'FormItem',
+ };
+
+ const initializerItem = {
+ remove: removeGridFormItem,
+ };
+ return (
+ !field.treeChildren,
+ getSchema: (field, { targetCollection }) => {
+ const isFileCollection = targetCollection?.template === 'file';
+ const isAssociationField = targetCollection;
+ const fieldNames = field?.uiSchema?.['x-component-props']?.['fieldNames'];
+
+ return {
+ ...fieldItemSchema,
+ 'x-component-props': isFileCollection
+ ? { fieldNames: { label: 'preview', value: 'id' } }
+ : isAssociationField && fieldNames
+ ? { fieldNames: { ...fieldNames, label: targetCollection?.titleField || fieldNames.label } }
+ : {},
+ };
+ },
+ getInitializerItem: () => {
+ return {
+ ...initializerItem,
+ find: props?.block === 'Kanban' ? findKanbanFormItem : undefined,
+ };
+ },
+ }}
+ parentField={{
+ getSchema: () => fieldItemSchema,
+ getInitializerItem: () => initializerItem,
+ }}
+ associationField={{
+ filterSelfField: (field) => {
+ if (block !== 'Form') return true;
+ return field?.interface === 'm2o';
+ },
+ filterAssociationField(collectionField) {
+ return (
+ collectionField?.interface &&
+ !['subTable'].includes(collectionField?.interface) &&
+ !collectionField.treeChildren
+ );
+ },
+ getSchema: () => fieldItemSchema,
+ getInitializerItem: () => initializerItem,
+ }}
+ />
+ );
+};
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToInitializerItems.tsx b/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToInitializerItems.tsx
new file mode 100644
index 0000000000..631cb797c8
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToInitializerItems.tsx
@@ -0,0 +1,37 @@
+/**
+ * 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 React, { FC } from 'react';
+import { CollectionFieldsProps, useCollectionFieldContext } from './utils';
+import { AssociationCollectionFields, ParentCollectionFields, SelfFields } from './items';
+
+export const CollectionFieldsToInitializerItems: FC = (props) => {
+ const { selfField, parentField, associationField, block } = props;
+ const context = useCollectionFieldContext();
+ if (!context.collection) return null;
+ return (
+ <>
+
+ {parentField && (
+
+ )}
+ {associationField && (
+
+ )}
+ >
+ );
+};
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToTableInitializerItems.tsx b/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToTableInitializerItems.tsx
new file mode 100644
index 0000000000..7c813b2677
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/CollectionFieldsToTableInitializerItems.tsx
@@ -0,0 +1,127 @@
+/**
+ * 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 React, { FC } from 'react';
+import { CollectionFieldsToInitializerItems } from './CollectionFieldsToInitializerItems';
+import { findTableColumn, removeGridFormItem, removeTableColumn } from '../../schema-initializer/utils';
+
+const quickEditField = [
+ 'attachment',
+ 'textarea',
+ 'markdown',
+ 'json',
+ 'richText',
+ 'polygon',
+ 'circle',
+ 'point',
+ 'lineString',
+];
+
+export const CollectionFieldsToTableInitializerItems: FC = (props) => {
+ function isReadPretty({ fieldSchema, form }) {
+ const isSubTable = fieldSchema['x-component'] === 'AssociationField.SubTable';
+ const isReadPretty = isSubTable ? form.readPretty : true;
+
+ return isReadPretty;
+ }
+ return (
+ field.interface !== 'subTable' && !field.treeChildren,
+ getSchema: (field, { targetCollection, fieldSchema, form }) => {
+ const isFileCollection = targetCollection?.template === 'file';
+ const isPreviewComponent = field.uiSchema?.['x-component'] === 'Preview';
+ const isSubTable = fieldSchema['x-component'] === 'AssociationField.SubTable';
+ const readPretty = isReadPretty({ fieldSchema, form });
+
+ return {
+ 'x-component-props': isFileCollection
+ ? {
+ fieldNames: {
+ label: 'preview',
+ value: 'id',
+ },
+ }
+ : isPreviewComponent
+ ? { size: 'small' }
+ : {},
+ 'x-read-pretty': readPretty || field.uiSchema?.['x-read-pretty'],
+ 'x-decorator': isSubTable
+ ? quickEditField.includes(field.interface) || isFileCollection
+ ? 'QuickEdit'
+ : 'FormItem'
+ : null,
+ 'x-decorator-props': {
+ labelStyle: {
+ display: 'none',
+ },
+ },
+ };
+ },
+ getInitializerItem: () => {
+ return {
+ find: findTableColumn,
+ remove: removeTableColumn,
+ };
+ },
+ }}
+ parentField={{
+ isReadPretty,
+ getSchema(field, { targetCollection, fieldSchema, form }) {
+ const isFileCollection = targetCollection?.template === 'file';
+ const isSubTable = fieldSchema['x-component'] === 'AssociationField.SubTable';
+ const readPretty = isReadPretty({ fieldSchema, form });
+
+ return {
+ 'x-component-props': isFileCollection
+ ? {
+ fieldNames: {
+ label: 'preview',
+ value: 'id',
+ },
+ }
+ : {},
+ 'x-read-pretty': readPretty || field.uiSchema?.['x-read-pretty'],
+ 'x-decorator': isSubTable
+ ? quickEditField.includes(field.interface) || isFileCollection
+ ? 'QuickEdit'
+ : 'FormItem'
+ : null,
+ 'x-decorator-props': {
+ labelStyle: {
+ display: 'none',
+ },
+ },
+ };
+ },
+ getInitializerItem() {
+ return {
+ remove: removeGridFormItem,
+ };
+ },
+ }}
+ associationField={{
+ filterAssociationField(collectionField) {
+ return !['subTable'].includes(collectionField.interface) && !collectionField.treeChildren;
+ },
+ getSchema() {
+ return {};
+ },
+ getInitializerItem() {
+ return {
+ find: findTableColumn,
+ remove: removeTableColumn,
+ };
+ },
+ }}
+ />
+ );
+};
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/index.ts b/packages/core/client/src/data-source/collection-fields-to-initializer-items/index.ts
new file mode 100644
index 0000000000..ac04d9928e
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/index.ts
@@ -0,0 +1,12 @@
+/**
+ * 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.
+ */
+
+export * from './CollectionFieldsToInitializerItems';
+export * from './CollectionFieldsToFormInitializerItems';
+export * from './CollectionFieldsToTableInitializerItems';
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/AssociationCollectionFields.tsx b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/AssociationCollectionFields.tsx
new file mode 100644
index 0000000000..6e0462e6af
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/AssociationCollectionFields.tsx
@@ -0,0 +1,73 @@
+import React, { FC } from 'react';
+
+import { InheritanceCollectionMixin } from '../../../collection-manager';
+import { AssociationCollectionFieldsProps, getInitializerItemsByFields } from '../utils';
+import {
+ SchemaInitializerChildren,
+ SchemaInitializerItemGroup,
+ SchemaInitializerItemType,
+} from '../../../application/schema-initializer';
+
+export const AssociationCollectionFields: FC = (props) => {
+ const { filterAssociationField, filterSelfField = () => true, getSchema, ...otherProps } = props;
+ const { collection, t, collectionManager } = props.context;
+ const fields = collection.getFields();
+ const associationInterfaces = ['o2o', 'oho', 'obo', 'm2o']; // 关联字段类型
+ const associationFields = fields
+ .filter((field) => {
+ return associationInterfaces.includes(field.interface);
+ })
+ .filter((field) => filterSelfField(field, props.context));
+
+ if (!associationFields.length) return null;
+
+ const children = associationFields
+ .map((associationField) => {
+ // 获取关联表
+ const associationCollection = collectionManager.getCollection(
+ associationField.target!,
+ )!;
+ if (!associationCollection) return null;
+ // 获取父表
+ const associationCollectionFields = associationCollection?.getAllFields();
+ if (!associationCollectionFields.length) return null;
+ return { associationField, associationCollection, associationCollectionFields };
+ })
+ .filter(Boolean)
+ // 修改数据结构
+ .map(({ associationField, associationCollection, associationCollectionFields }: any) => {
+ const newContext = {
+ ...props.context,
+ collection: associationCollection,
+ };
+
+ const getAssociationFieldSchema: AssociationCollectionFieldsProps['getSchema'] = (field, context) => {
+ const schema = getSchema(field, context);
+ return {
+ ...(schema || {}),
+ 'x-read-pretty': true,
+ name: `${associationField.name}.${field.name}`,
+ 'x-collection-field': `${collection.name}.${associationField.name}.${field.name}`,
+ };
+ };
+
+ return {
+ type: 'subMenu',
+ name: associationField.uiSchema?.title,
+ title: associationField.uiSchema?.title,
+ children: getInitializerItemsByFields(
+ {
+ ...otherProps,
+ filter: filterAssociationField,
+ getSchema: getAssociationFieldSchema,
+ },
+ associationCollectionFields!,
+ newContext,
+ ),
+ } as SchemaInitializerItemType;
+ });
+
+ if (!children.length) return null;
+
+ return {children};
+};
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/ParentCollectionFields.tsx b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/ParentCollectionFields.tsx
new file mode 100644
index 0000000000..f3fa849281
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/ParentCollectionFields.tsx
@@ -0,0 +1,47 @@
+import React, { FC } from 'react';
+
+import { CollectionFieldOptions } from '../../collection/Collection';
+import { InheritanceCollectionMixin } from '../../../collection-manager';
+import { ParentCollectionFieldsProps, getInitializerItemsByFields, useCollectionFieldContext } from '../utils';
+import { SchemaInitializerChildren, SchemaInitializerItemType } from '../../../application/schema-initializer';
+
+export const ParentCollectionFields: FC = (props) => {
+ const context = useCollectionFieldContext();
+ const { collection, t, collectionManager } = context;
+
+ const parentCollectionNames = collection.getParentCollectionsName();
+ if (!parentCollectionNames.length) return null;
+
+ const children = parentCollectionNames
+ .map((parentCollectionName) => {
+ // 获取父表的字段
+ const parentCollectionFields = collection.getParentCollectionFields(parentCollectionName);
+ // 如果没有父表字段,返回 null
+ if (parentCollectionFields.length === 0) return null;
+ // 获取父表
+ const parentCollection = collectionManager.getCollection(parentCollectionName)!;
+ return { parentCollection, parentCollectionFields };
+ })
+ // 过滤掉 null
+ .filter(Boolean)
+ // 修改数据结构
+ .map((options) => {
+ const { parentCollection, parentCollectionFields } = options as {
+ parentCollection: InheritanceCollectionMixin;
+ parentCollectionFields: CollectionFieldOptions[];
+ };
+ const newContext = {
+ ...context,
+ collection: parentCollection,
+ };
+
+ return {
+ type: 'itemGroup',
+ divider: true,
+ title: t(`Parent collection fields`) + '(' + context.compile(parentCollection.title) + ')',
+ children: getInitializerItemsByFields(props, parentCollectionFields, newContext),
+ } as SchemaInitializerItemType;
+ });
+
+ return {children};
+};
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/SelfFields.tsx b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/SelfFields.tsx
new file mode 100644
index 0000000000..2fc3423ccd
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/SelfFields.tsx
@@ -0,0 +1,13 @@
+import React, { FC } from 'react';
+
+import { SelfCollectionFieldsProps, getInitializerItemsByFields, useCollectionFieldContext } from '../utils';
+import { SchemaInitializerItemGroup } from '../../../application/schema-initializer';
+
+export const SelfFields: FC = (props) => {
+ const callbackContext = useCollectionFieldContext();
+ const { t, collection } = callbackContext;
+ const fields = collection.getFields();
+ const children = getInitializerItemsByFields(props, fields, callbackContext);
+
+ return {children};
+};
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/index.ts b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/index.ts
new file mode 100644
index 0000000000..9de9a0ec64
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/items/index.ts
@@ -0,0 +1,3 @@
+export { SelfFields } from './SelfFields';
+export { ParentCollectionFields } from './ParentCollectionFields';
+export { AssociationCollectionFields } from './AssociationCollectionFields';
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/getInitializerItemsByFields.ts b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/getInitializerItemsByFields.ts
new file mode 100644
index 0000000000..42f1081c5c
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/getInitializerItemsByFields.ts
@@ -0,0 +1,112 @@
+/**
+ * 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 {
+ CollectionFieldDefaultSchema,
+ CollectionFieldDefaultInitializerItem,
+ CommonCollectionFieldsProps,
+} from './type';
+import { CollectionFieldOptions } from '../../collection/Collection';
+import { CollectionFieldContext } from './useCollectionFieldContext';
+import { ISchema } from '@formily/json-schema';
+import { SchemaInitializerItemType } from '../../../application/schema-initializer';
+
+export function getInitializerItemsByFields(
+ props: CommonCollectionFieldsProps,
+ fields: CollectionFieldOptions[],
+ context: CollectionFieldContext,
+) {
+ const {
+ block,
+ isReadPretty = ({ form }) => form.readPretty,
+ filter = () => true,
+ getInitializerItem = () => ({}),
+ getSchema = () => ({}),
+ } = props;
+
+ const { collectionManager, collection, dataSourceManager, actionContext } = context;
+ const action = actionContext.fieldSchema?.['x-action'];
+ if (!collection) return [];
+ return fields
+ .map((collectionField) => {
+ const targetCollection = collectionManager.getCollection(collectionField.target!);
+ const collectionFieldInterface = dataSourceManager.collectionFieldInterfaceManager.getFieldInterface(
+ collectionField.interface,
+ );
+ return {
+ collectionField,
+ context: {
+ ...context,
+ targetCollection,
+ collectionFieldInterface,
+ },
+ };
+ })
+ .filter(({ collectionField }) => collectionField.interface)
+ .filter(({ collectionField, context }) => {
+ return filter(collectionField, context);
+ })
+ .map(({ collectionField, context }) => {
+ const defaultSchema: CollectionFieldDefaultSchema = {
+ type: 'string',
+ title: collectionField?.uiSchema?.title || collectionField.name,
+ name: collectionField.name,
+ 'x-component': 'CollectionField',
+ 'x-collection-field': `${collection.name}.${collectionField.name}`,
+ 'x-read-pretty': collectionField?.uiSchema?.['x-read-pretty'],
+ };
+ const customSchema = getSchema(collectionField, { ...context, defaultSchema: defaultSchema });
+ const schema = {
+ ...defaultSchema,
+ ...(customSchema || {}),
+ };
+ return {
+ collectionField,
+ schema,
+ context: {
+ ...context,
+ schema,
+ },
+ };
+ })
+ .map(({ collectionField, context }) => {
+ const defaultInitializerItem = {
+ type: 'item',
+ name: collectionField.name,
+ title: collectionField?.uiSchema?.title || collectionField.name,
+ Component: 'CollectionFieldInitializer',
+ schemaInitialize: (s: ISchema) => {
+ context.collectionFieldInterface?.schemaInitialize?.(s, {
+ field: collectionField,
+ block,
+ readPretty: isReadPretty?.(context),
+ action,
+ targetCollection: context.targetCollection,
+ });
+ },
+ schema: context.schema,
+ } as CollectionFieldDefaultInitializerItem;
+ return {
+ collectionField,
+ context,
+ defaultInitializerItem,
+ };
+ })
+ .map(({ collectionField, defaultInitializerItem, context }) => {
+ const customInitializerItem = getInitializerItem(collectionField, {
+ ...context,
+ defaultInitializerItem,
+ });
+
+ return {
+ ...defaultInitializerItem,
+ ...(customInitializerItem || {}),
+ } as SchemaInitializerItemType;
+ });
+}
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/index.ts b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/index.ts
new file mode 100644
index 0000000000..a68d3561cf
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/index.ts
@@ -0,0 +1,3 @@
+export * from './type';
+export * from './getInitializerItemsByFields';
+export * from './useCollectionFieldContext';
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/type.ts b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/type.ts
new file mode 100644
index 0000000000..6bca81c568
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/type.ts
@@ -0,0 +1,135 @@
+/**
+ * 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 { ISchema, Schema } from '@formily/json-schema';
+
+import { CollectionFieldContext } from './useCollectionFieldContext';
+import { CollectionFieldInterface } from '../../collection-field-interface';
+import { Collection, CollectionFieldOptions } from '../../collection/Collection';
+import { InheritanceCollectionMixin } from '../../../collection-manager';
+
+export interface CollectionFieldDefaultSchema {
+ /**
+ * @default 'string'
+ */
+ type: string;
+ /**
+ * @default collectionField.name
+ */
+ name: string;
+ /**
+ * @default 'CollectionField'
+ */
+ 'x-component': string;
+ /**
+ * @default `${collection.name}.${collectionField.name}`
+ */
+ 'x-collection-field': string;
+ /**
+ * @default collectionField?.uiSchema?.['x-read-pretty']
+ */
+ 'x-read-pretty'?: boolean;
+
+ /**
+ * @default collectionField?.uiSchema?.title || collectionField.name
+ */
+ title: string;
+}
+
+export interface CollectionFieldGetSchemaResult {
+ 'x-toolbar'?: string;
+ 'x-toolbar-props'?: any;
+ 'x-settings'?: string;
+ 'x-decorator'?: string;
+ 'x-decorator-props'?: any;
+ 'x-component-props'?: any;
+ 'x-use-component-props'?: string;
+}
+
+export interface CollectionFieldDefaultInitializerItem {
+ /**
+ * @default 'item'
+ */
+ type: string;
+ /**
+ * @default collectionField.name
+ */
+ name: string;
+ /**
+ * @default collectionField?.uiSchema?.title || collectionField.name
+ */
+ title: string;
+ /**
+ * @default 'CollectionFieldInitializer'
+ */
+ Component: string;
+ schemaInitialize: (s: ISchema) => void;
+ /**
+ * @default schema
+ */
+ schema: ISchema;
+}
+
+export interface CollectionFieldGetInitializerItemResult {
+ find?: (schema: Schema, key: string, action: string) => any;
+ remove?: (schema: Schema, cb: (schema: Schema, stopProps: Record) => void) => void;
+}
+
+export interface CommonCollectionFieldsProps {
+ block: string;
+ getSchema: (
+ collectionField: CollectionFieldOptions,
+ context: CollectionFieldContext & {
+ defaultSchema: CollectionFieldDefaultSchema;
+ targetCollection?: Collection;
+ collectionFieldInterface?: CollectionFieldInterface;
+ },
+ ) => CollectionFieldGetSchemaResult;
+ isReadPretty?: (context: CollectionFieldContext) => boolean;
+ filter?: (collectionField: CollectionFieldOptions, context: CollectionFieldContext) => boolean;
+ getInitializerItem?: (
+ collectionField: CollectionFieldOptions,
+ context: CollectionFieldContext & {
+ schema: ISchema;
+ defaultInitializerItem: CollectionFieldDefaultInitializerItem;
+ targetCollection?: Collection;
+ collectionFieldInterface?: CollectionFieldInterface;
+ },
+ ) => CollectionFieldGetInitializerItemResult;
+}
+
+export interface SelfCollectionFieldsProps extends CommonCollectionFieldsProps {
+ context: Omit & {
+ collection: Collection;
+ };
+}
+
+export interface ParentCollectionFieldsProps extends CommonCollectionFieldsProps {
+ context: Omit & {
+ collection: Collection;
+ };
+}
+
+export interface AssociationCollectionFieldsProps extends Omit {
+ filterSelfField?: CommonCollectionFieldsProps['filter'];
+ filterAssociationField?: CommonCollectionFieldsProps['filter'];
+ context: Omit & {
+ collection: CollectionFieldContext['collection']; // 之前是可选的,这里是必须的
+ };
+}
+
+export interface CollectionFieldsProps {
+ /**
+ * Block name.
+ */
+ block: string;
+ selfField: Omit;
+ parentField?: Omit;
+ associationField?: Omit;
+}
diff --git a/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/useCollectionFieldContext.ts b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/useCollectionFieldContext.ts
new file mode 100644
index 0000000000..1503eefbb6
--- /dev/null
+++ b/packages/core/client/src/data-source/collection-fields-to-initializer-items/utils/useCollectionFieldContext.ts
@@ -0,0 +1,62 @@
+/**
+ * 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 { Form } from '@formily/core';
+import { useFieldSchema, useForm } from '@formily/react';
+import { ISchema } from '@formily/json-schema';
+import { TFunction, useTranslation } from 'react-i18next';
+
+import { DataSource } from '../../data-source/DataSource';
+import { useCollection } from '../../collection/CollectionProvider';
+import { useDataSource } from '../../data-source/DataSourceProvider';
+import { CollectionManager } from '../../collection/CollectionManager';
+import { DataSourceManager } from '../../data-source/DataSourceManager';
+import { useActionContext } from '../../../schema-component/antd/action';
+import { Collection } from '../../collection/Collection';
+import { useCollectionManager } from '../../collection/CollectionManagerProvider';
+import { useDataSourceManager } from '../../data-source/DataSourceManagerProvider';
+import { InheritanceCollectionMixin } from '../../../collection-manager';
+import { useCompile } from '../../../schema-component';
+
+export interface CollectionFieldContext {
+ fieldSchema: ISchema;
+ collection?: InheritanceCollectionMixin & Collection;
+ dataSource: DataSource;
+ form: Form;
+ actionContext: ReturnType;
+ t: TFunction<'translation', undefined>;
+ collectionManager: CollectionManager;
+ dataSourceManager: DataSourceManager;
+ compile: (source: any, ext?: any) => any;
+ targetCollection?: Collection;
+}
+
+export function useCollectionFieldContext(): CollectionFieldContext {
+ const { t } = useTranslation();
+ const collection = useCollection();
+ const dataSourceManager = useDataSourceManager();
+ const actionContext = useActionContext();
+ const dataSource = useDataSource();
+ const form = useForm();
+ const fieldSchema = useFieldSchema();
+ const collectionManager = useCollectionManager();
+ const compile = useCompile();
+
+ return {
+ t,
+ compile,
+ actionContext,
+ fieldSchema,
+ collection,
+ dataSource,
+ form,
+ collectionManager,
+ dataSourceManager,
+ };
+}
diff --git a/packages/core/client/src/data-source/index.ts b/packages/core/client/src/data-source/index.ts
index 5bd59b6cce..788809ebb6 100644
--- a/packages/core/client/src/data-source/index.ts
+++ b/packages/core/client/src/data-source/index.ts
@@ -16,3 +16,4 @@ export * from './data-block';
export * from './data-source';
export * from './collection-record';
export * from './utils';
+export * from './collection-fields-to-initializer-items';
diff --git a/packages/core/client/src/schema-initializer/utils.ts b/packages/core/client/src/schema-initializer/utils.ts
index 9f4661548f..5ac995f49b 100644
--- a/packages/core/client/src/schema-initializer/utils.ts
+++ b/packages/core/client/src/schema-initializer/utils.ts
@@ -714,7 +714,7 @@ export const useCustomFormItemInitializerFields = (options?: any) => {
});
};
-const findSchema = (schema: Schema, key: string, action: string) => {
+export const findSchema = (schema: Schema, key: string, action: string) => {
if (!Schema.isSchemaInstance(schema)) return null;
return schema.reduceProperties((buf, s) => {
if (s[key] === action) {