mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
Merge branch 'main' into next
This commit is contained in:
commit
01477986ee
@ -186,6 +186,10 @@ export default defineConfig({
|
|||||||
title: 'ExtendCollectionsProvider',
|
title: 'ExtendCollectionsProvider',
|
||||||
link: '/core/data-source/extend-collections-provider',
|
link: '/core/data-source/extend-collections-provider',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Collection Fields To Initializer Items',
|
||||||
|
link: '/core/data-source/collection-fields-to-initializer-items',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,339 @@
|
|||||||
|
# Collection Fields To Initializer Items
|
||||||
|
|
||||||
|
## 介绍
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
页面上有 `Configure columns` 和 `Configure fields` 两个按钮,鼠标悬浮后显示当前表的字段列表,当点击某个字段后,会插入表格列或者表单项到界面中,这个过程就是从 `Collection Fields` 到 `Initializer Items` 的过程。
|
||||||
|
|
||||||
|
## Configure fields 分类
|
||||||
|
|
||||||
|
`Configure fields` 分为三类:
|
||||||
|
|
||||||
|
- `self collection fields`:当前表的字段
|
||||||
|
- `parent collection fields`:父表的字段
|
||||||
|
- `associated collection fields`:关联表的字段
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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)。 <br />
|
||||||
|
> 关于 Initializer item,可以参考 [SchemaInitializer](/core/ui-schema/schema-initializer)。<br />
|
||||||
|
> 两者的关系是 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<any>;
|
||||||
|
actionContext: ReturnType<typeof useActionContext>;
|
||||||
|
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<CommonCollectionFieldsProps, 'filter'> {
|
||||||
|
filterSelfField?: CommonCollectionFieldsProps['filter'];
|
||||||
|
filterAssociationField?: CommonCollectionFieldsProps['filter'];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CollectionFieldsProps {
|
||||||
|
/**
|
||||||
|
* Block name.
|
||||||
|
*/
|
||||||
|
block: string;
|
||||||
|
selfField: Omit<SelfCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
parentField?: Omit<ParentCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
associationField?: Omit<AssociationCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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 <CollectionFieldsToInitializerItems
|
||||||
|
block={block}
|
||||||
|
selfField={{
|
||||||
|
filter: (field) => !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,
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
@ -0,0 +1,309 @@
|
|||||||
|
# Collection Fields To Initializer Items
|
||||||
|
|
||||||
|
## 介绍
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
页面上有 `Configure columns` 和 `Configure fields` 两个按钮,点击后显示当前表的字段列表,当点击某个字段后,会插入表单项或者表格列到界面中,这个过程就是从 `Collection Fields` 到 `Initializer Items` 的过程。
|
||||||
|
|
||||||
|
## Configure fields 分类
|
||||||
|
|
||||||
|
`Configure fields` 分为三类:
|
||||||
|
|
||||||
|
- `self collection fields`:当前表的字段
|
||||||
|
- `parent collection fields`:父表的字段
|
||||||
|
- `associated collection fields`:关联表的字段
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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)。 <br />
|
||||||
|
> 关于 Initializer item,可以参考 [SchemaInitializer](/core/ui-schema/schema-initializer)。<br />
|
||||||
|
> 两者的关系是 Initializer item 通过类似 `onClick` 的事件触发,将 Schema 插入到 Schema 树中,并渲染到界面上。
|
||||||
|
|
||||||
|
`CollectionFieldsToInitializerItems` 是一个组件,用于将 `Collection Fields` 转换为 `Initializer Items`。
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CollectionFieldContext {
|
||||||
|
fieldSchema: ISchema;
|
||||||
|
collection?: InheritanceCollectionMixin & Collection;
|
||||||
|
dataSource: DataSource;
|
||||||
|
form: Form<any>;
|
||||||
|
actionContext: ReturnType<typeof useActionContext>;
|
||||||
|
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<CommonCollectionFieldsProps, 'filter'> {
|
||||||
|
filterSelfField?: CommonCollectionFieldsProps['filter'];
|
||||||
|
filterAssociationField?: CommonCollectionFieldsProps['filter'];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CollectionFieldsProps {
|
||||||
|
/**
|
||||||
|
* Block name.
|
||||||
|
*/
|
||||||
|
block: string;
|
||||||
|
selfField: Omit<SelfCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
parentField?: Omit<ParentCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
associationField?: Omit<AssociationCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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 <CollectionFieldsToInitializerItems
|
||||||
|
block={block}
|
||||||
|
selfField={{
|
||||||
|
filter: (field) => !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` 区块中。
|
||||||
|
|
@ -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 (
|
||||||
|
<CollectionFieldsToInitializerItems
|
||||||
|
block={block}
|
||||||
|
selfField={{
|
||||||
|
filter: (field) => !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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -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<CollectionFieldsProps> = (props) => {
|
||||||
|
const { selfField, parentField, associationField, block } = props;
|
||||||
|
const context = useCollectionFieldContext();
|
||||||
|
if (!context.collection) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SelfFields block={block} {...selfField} context={{ ...context, collection: context.collection }} />
|
||||||
|
{parentField && (
|
||||||
|
<ParentCollectionFields
|
||||||
|
block={block}
|
||||||
|
{...parentField}
|
||||||
|
context={{ ...context, collection: context.collection }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{associationField && (
|
||||||
|
<AssociationCollectionFields
|
||||||
|
block={block}
|
||||||
|
{...associationField}
|
||||||
|
context={{ ...context, collection: context.collection }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -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 (
|
||||||
|
<CollectionFieldsToInitializerItems
|
||||||
|
block={'Table'}
|
||||||
|
selfField={{
|
||||||
|
isReadPretty,
|
||||||
|
filter: (field) => 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,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -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';
|
@ -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<AssociationCollectionFieldsProps> = (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<InheritanceCollectionMixin>(
|
||||||
|
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 <SchemaInitializerItemGroup title={t('Display association fields')}>{children}</SchemaInitializerItemGroup>;
|
||||||
|
};
|
@ -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<ParentCollectionFieldsProps> = (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<InheritanceCollectionMixin>(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 <SchemaInitializerChildren>{children}</SchemaInitializerChildren>;
|
||||||
|
};
|
@ -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<SelfCollectionFieldsProps> = (props) => {
|
||||||
|
const callbackContext = useCollectionFieldContext();
|
||||||
|
const { t, collection } = callbackContext;
|
||||||
|
const fields = collection.getFields();
|
||||||
|
const children = getInitializerItemsByFields(props, fields, callbackContext);
|
||||||
|
|
||||||
|
return <SchemaInitializerItemGroup title={t('Display fields')}>{children}</SchemaInitializerItemGroup>;
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
export { SelfFields } from './SelfFields';
|
||||||
|
export { ParentCollectionFields } from './ParentCollectionFields';
|
||||||
|
export { AssociationCollectionFields } from './AssociationCollectionFields';
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from './type';
|
||||||
|
export * from './getInitializerItemsByFields';
|
||||||
|
export * from './useCollectionFieldContext';
|
@ -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<string, any>) => 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<CollectionFieldContext, 'collection'> & {
|
||||||
|
collection: Collection;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParentCollectionFieldsProps extends CommonCollectionFieldsProps {
|
||||||
|
context: Omit<CollectionFieldContext, 'collection'> & {
|
||||||
|
collection: Collection;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssociationCollectionFieldsProps extends Omit<CommonCollectionFieldsProps, 'filter'> {
|
||||||
|
filterSelfField?: CommonCollectionFieldsProps['filter'];
|
||||||
|
filterAssociationField?: CommonCollectionFieldsProps['filter'];
|
||||||
|
context: Omit<CollectionFieldContext, 'collection'> & {
|
||||||
|
collection: CollectionFieldContext['collection']; // 之前是可选的,这里是必须的
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionFieldsProps {
|
||||||
|
/**
|
||||||
|
* Block name.
|
||||||
|
*/
|
||||||
|
block: string;
|
||||||
|
selfField: Omit<SelfCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
parentField?: Omit<ParentCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
associationField?: Omit<AssociationCollectionFieldsProps, 'block' | 'context'>;
|
||||||
|
}
|
@ -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<any>;
|
||||||
|
actionContext: ReturnType<typeof useActionContext>;
|
||||||
|
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<InheritanceCollectionMixin>();
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
@ -16,3 +16,4 @@ export * from './data-block';
|
|||||||
export * from './data-source';
|
export * from './data-source';
|
||||||
export * from './collection-record';
|
export * from './collection-record';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export * from './collection-fields-to-initializer-items';
|
||||||
|
@ -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;
|
if (!Schema.isSchemaInstance(schema)) return null;
|
||||||
return schema.reduceProperties((buf, s) => {
|
return schema.reduceProperties((buf, s) => {
|
||||||
if (s[key] === action) {
|
if (s[key] === action) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user