diff --git a/packages/core/client/docs/en-US/core/application/application.md b/packages/core/client/docs/en-US/core/application/application.md index 49bda8b2fa..49d6d6d475 100644 --- a/packages/core/client/docs/en-US/core/application/application.md +++ b/packages/core/client/docs/en-US/core/application/application.md @@ -20,6 +20,7 @@ export interface ApplicationOptions { schemaInitializers?: SchemaInitializer[]; loadRemotePlugins?: boolean; dataSourceManager?: DataSourceManagerOptions; + addFieldInterfaceComponentOption(fieldName: string, componentOption: CollectionFieldInterfaceComponentOption): void; } ``` @@ -35,6 +36,7 @@ export interface ApplicationOptions { - `schemaInitializers`: Schema addition tool. For more information, refer to: [SchemaInitializerManager](/core/ui-schema/schema-initializer-manager) - `loadRemotePlugins`: Used to control whether to load remote plugins. Default is `false`, meaning remote plugins are not loaded (convenient for unit testing and DEMO environments). - `dataSourceManager`: Data source manager. For more details, refer to: [DataSourceManager](/core/data-source/data-source-manager) +- `addFieldInterfaceComponentOption`: Add field interface component options. For more details, refer to: [CollectionFieldInterfaceManager](/core/data-source/collection-field-interface-manager#addfieldinterfacecomponentoption) ## Example @@ -344,6 +346,23 @@ app.getCollectionManager() // Get the default data source collection manager app.getCollectionManager('test') // Get the specified data source collection manager ``` +### app.addFieldInterfaceComponentOption() + +Add field interface component option. + +For a detailed introduction, please refer to: [CollectionFieldInterfaceManager](/core/data-source/collection-field-interface-manager#addfieldinterfacecomponentoption) + +```tsx | pure +class MyPlugin extends Plugin { + async load() { + this.app.addFieldInterfaceComponentOption('url', { + label: 'Preview', + value: 'Input.Preview', + }); + } +} +``` + ## Hooks ### useApp() diff --git a/packages/core/client/docs/en-US/core/data-source/collection-field-interface-manager.md b/packages/core/client/docs/en-US/core/data-source/collection-field-interface-manager.md index 11a03318be..25ae8cd419 100644 --- a/packages/core/client/docs/en-US/core/data-source/collection-field-interface-manager.md +++ b/packages/core/client/docs/en-US/core/data-source/collection-field-interface-manager.md @@ -89,6 +89,41 @@ class MyPlugin extends Plugin { } ``` + +#### addFieldInterfaceComponentOption() + +Add field interface component option. + +![20240725113756](https://static-docs.nocobase.com/20240725113756.png) + +- 类型 + +```tsx | pure +interface CollectionFieldInterfaceComponentOption { + label: string; + value: string; + useVisible?: () => boolean; + useProps?: () => any; +} + +class CollectionFieldInterfaceManager { + addFieldInterfaceComponentOption(interfaceName: string, componentOption: CollectionFieldInterfaceComponentOption): void +} +``` + +- 示例 + +```tsx | pure +class MyPlugin extends Plugin { + async load() { + this.app.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaceComponentOption('url', { + label: 'Preview', + value: 'Input.Preview', + }); + } +} +``` + ### field interface group #### addFieldInterfaceGroups() diff --git a/packages/core/client/docs/en-US/core/data-source/collection-field-interface.md b/packages/core/client/docs/en-US/core/data-source/collection-field-interface.md index a926a17798..392f819951 100644 --- a/packages/core/client/docs/en-US/core/data-source/collection-field-interface.md +++ b/packages/core/client/docs/en-US/core/data-source/collection-field-interface.md @@ -34,10 +34,6 @@ class CollectionFieldInterface { validateSchema(fieldSchema: ISchema): Record usePathOptions(field: CollectionFieldOptions): any schemaInitialize(schema: ISchema, data: any): void - - getOption(key: K): CollectionFieldInterfaceOptions[K] - getOptions(): CollectionFieldInterfaceOptions; - setOptions(options: CollectionFieldInterfaceOptions): void; } ``` diff --git a/packages/core/client/docs/zh-CN/core/application/application.md b/packages/core/client/docs/zh-CN/core/application/application.md index 86f0d6ed3b..de2edc4ab2 100644 --- a/packages/core/client/docs/zh-CN/core/application/application.md +++ b/packages/core/client/docs/zh-CN/core/application/application.md @@ -20,21 +20,23 @@ export interface ApplicationOptions { schemaInitializers?: SchemaInitializer[]; loadRemotePlugins?: boolean; dataSourceManager?: DataSourceManagerOptions; + addFieldInterfaceComponentOption(fieldName: string, componentOption: CollectionFieldInterfaceComponentOption): void; } ``` - 详细信息 - - apiClient:API 请求实例,具体说明请参见:[https://docs.nocobase.com/api/sdk](https://docs.nocobase.com/api/sdk) - - i18n:国际化,具体请参考:[https://www.i18next.com/overview/api#createinstance](https://www.i18next.com/overview/api#createinstance) - - providers:上下文 - - components:全局组件 - - scopes:全局 scopes - - router:配置路由,具体请参考:[RouterManager](/core/application/router-manager) - - pluginSettings: [PluginSettingsManager](/core/application/plugin-settings-manager) - - schemaSettings:Schema 设置工具,具体参考:[SchemaSettingsManager](/core/ui-schema/schema-initializer-manager) - - schemaInitializers:Schema 添加工具,具体参考:[SchemaInitializerManager](/core/ui-schema/schema-initializer-manager) - - loadRemotePlugins:用于控制是否加载远程插件,默认为 `false`,即不加载远程插件(方便单测和 DEMO 环境)。 - - dataSourceManager:数据源管理器,具体参考:[DataSourceManager](/core/data-source/data-source-manager) + - `apiClient`:API 请求实例,具体说明请参见:[https://docs.nocobase.com/api/sdk](https://docs.nocobase.com/api/sdk) + - `i18n`:国际化,具体请参考:[https://www.i18next.com/overview/api#createinstance](https://www.i18next.com/overview/api#createinstance) + - `providers`:上下文 + - `components`:全局组件 + - `scopes`:全局 scopes + - `router`:配置路由,具体请参考:[RouterManager](/core/application/router-manager) + - `pluginSettings`: [PluginSettingsManager](/core/application/plugin-settings-manager) + - `schemaSettings`:Schema 设置工具,具体参考:[SchemaSettingsManager](/core/ui-schema/schema-initializer-manager) + - `schemaInitializers`:Schema 添加工具,具体参考:[SchemaInitializerManager](/core/ui-schema/schema-initializer-manager) + - `loadRemotePlugins`:用于控制是否加载远程插件,默认为 `false`,即不加载远程插件(方便单测和 DEMO 环境)。 + - `dataSourceManager`:数据源管理器,具体参考:[DataSourceManager](/core/data-source/data-source-manager) + - `addFieldInterfaceComponentOption`: 添加 Field interface 组件选项。具体参考: [CollectionFieldInterfaceManager](/core/data-source/collection-field-interface-manager#addfieldinterfacecomponentoption) - 示例 ```tsx @@ -343,6 +345,24 @@ app.getCollectionManager() // 获取默认数据源的 collection manager app.getCollectionManager('test') // 获取指定数据源的 collection manager ``` +### app.addFieldInterfaceComponentOption() + +Add field interface component option. + +添加 Field interface 组件选项。具体参考: [CollectionFieldInterfaceManager](/core/data-source/collection-field-interface-manager#addfieldinterfacecomponentoption) + + +```tsx | pure +class MyPlugin extends Plugin { + async load() { + this.app.addFieldInterfaceComponentOption('url', { + label: 'Preview', + value: 'Input.Preview', + }); + } +} +``` + ## Hooks ### useApp() diff --git a/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface-manager.md b/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface-manager.md index b589784e5a..4fef3313ab 100644 --- a/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface-manager.md +++ b/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface-manager.md @@ -89,6 +89,40 @@ class MyPlugin extends Plugin { } ``` +#### addFieldInterfaceComponentOption() + +添加 Field interface 组件选项。 + +![20240725113756](https://static-docs.nocobase.com/20240725113756.png) + +- 类型 + +```tsx | pure +interface CollectionFieldInterfaceComponentOption { + label: string; + value: string; + useVisible?: () => boolean; + useProps?: () => any; +} + +class CollectionFieldInterfaceManager { + addFieldInterfaceComponentOption(interfaceName: string, componentOption: CollectionFieldInterfaceComponentOption): void +} +``` + +- 示例 + +```tsx | pure +class MyPlugin extends Plugin { + async load() { + this.app.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaceComponentOption('url', { + label: 'Preview', + value: 'Input.Preview', + }); + } +} +``` + ### field interface group #### addFieldInterfaceGroups() diff --git a/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface.md b/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface.md index bb2c1aebfc..7596219e9a 100644 --- a/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface.md +++ b/packages/core/client/docs/zh-CN/core/data-source/collection-field-interface.md @@ -34,10 +34,6 @@ class CollectionFieldInterface { validateSchema(fieldSchema: ISchema): Record usePathOptions(field: CollectionFieldOptions): any schemaInitialize(schema: ISchema, data: any): void - - getOption(key: K): CollectionFieldInterfaceOptions[K] - getOptions(): CollectionFieldInterfaceOptions; - setOptions(options: CollectionFieldInterfaceOptions): void; } ``` diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index 43abc97c54..612989d7f9 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -38,6 +38,7 @@ import { CollectionField } from '../data-source/collection-field/CollectionField import { DataSourceApplicationProvider } from '../data-source/components/DataSourceApplicationProvider'; import { DataBlockProvider } from '../data-source/data-block/DataBlockProvider'; import { DataSourceManager, type DataSourceManagerOptions } from '../data-source/data-source/DataSourceManager'; +import { CollectionFieldInterfaceComponentOption } from '../data-source/collection-field-interface/CollectionFieldInterface'; import { OpenModeProvider } from '../modules/popup/OpenModeProvider'; import { AppSchemaComponentProvider } from './AppSchemaComponentProvider'; @@ -355,4 +356,11 @@ export class Application { root.render(); return root; } + + addFieldInterfaceComponentOption(fieldName: string, componentOption: CollectionFieldInterfaceComponentOption) { + return this.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaceComponentOption( + fieldName, + componentOption, + ); + } } diff --git a/packages/core/client/src/application/__tests__/Application.test.tsx b/packages/core/client/src/application/__tests__/Application.test.tsx index 9f5bc6b1cd..2db784f2d1 100644 --- a/packages/core/client/src/application/__tests__/Application.test.tsx +++ b/packages/core/client/src/application/__tests__/Application.test.tsx @@ -17,6 +17,7 @@ import { OpenModeProvider } from '../../modules/popup/OpenModeProvider'; import { Application } from '../Application'; import { Plugin } from '../Plugin'; import { useApp } from '../hooks'; +import { CollectionFieldInterface } from '../../data-source'; describe('Application', () => { beforeAll(() => { @@ -439,4 +440,43 @@ describe('Application', () => { console.error = originalConsoleWarn; }); }); + + describe('alias', () => { + test('addFieldInterfaceComponentOption', () => { + class TestInterface extends CollectionFieldInterface { + name = 'test'; + default = { + type: 'string', + uiSchema: { + type: 'string', + 'x-component': 'TestComponent', + }, + }; + } + const app = new Application({ + dataSourceManager: { + fieldInterfaces: [TestInterface], + }, + }); + app.addFieldInterfaceComponentOption('test', { + label: 'A', + value: 'a', + }); + + expect(app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterface('test').componentOptions) + .toMatchInlineSnapshot(` + [ + { + "label": "TestComponent", + "useProps": [Function], + "value": "TestComponent", + }, + { + "label": "A", + "value": "a", + }, + ] + `); + }); + }); }); diff --git a/packages/core/client/src/application/schema-settings/utils/createModalSettingsItem.tsx b/packages/core/client/src/application/schema-settings/utils/createModalSettingsItem.tsx index c6a81cf32c..d5feb6c017 100644 --- a/packages/core/client/src/application/schema-settings/utils/createModalSettingsItem.tsx +++ b/packages/core/client/src/application/schema-settings/utils/createModalSettingsItem.tsx @@ -12,9 +12,10 @@ import { ISchema, useFieldSchema } from '@formily/react'; import { TFunction, useTranslation } from 'react-i18next'; import { SchemaSettingsItemType } from '../types'; -import { getNewSchema, useHookDefault } from './util'; +import { getNewSchema, useHookDefault, useSchemaByType } from './util'; import { useCompile } from '../../../schema-component/hooks/useCompile'; import { useDesignable } from '../../../schema-component/hooks/useDesignable'; +import { useColumnSchema } from '../../../schema-component'; export interface CreateModalSchemaSettingsItemProps { name: string; @@ -27,6 +28,10 @@ export interface CreateModalSchemaSettingsItemProps { useVisible?: () => boolean; width?: number | string; useSubmit?: () => (values: any) => void; + /** + * @default 'common' + */ + type?: 'common' | 'field'; } /** @@ -47,26 +52,36 @@ export function createModalSettingsItem(options: CreateModalSchemaSettingsItemPr defaultValue: propsDefaultValue, useDefaultValue = useHookDefault, width, + type = 'common', } = options; return { name, type: 'actionModal', useVisible, useComponentProps() { - const fieldSchema = useFieldSchema(); - const { deepMerge } = useDesignable(); + const fieldSchema = useSchemaByType(type); + const { dn } = useDesignable(); const defaultValue = useDefaultValue(propsDefaultValue); const values = parentSchemaKey ? _.get(fieldSchema, parentSchemaKey) : undefined; const compile = useCompile(); const { t } = useTranslation(); const onSubmit = useSubmit(); + const { fieldSchema: tableColumnSchema } = useColumnSchema() || {}; return { title: typeof title === 'function' ? title(t) : compile(title), width, schema: schema({ ...defaultValue, ...values }), onSubmit(values) { - deepMerge(getNewSchema({ fieldSchema, parentSchemaKey, value: values, valueKeys })); + const newSchema = getNewSchema({ fieldSchema, parentSchemaKey: parentSchemaKey, value: values, valueKeys }); + if (tableColumnSchema) { + dn.emit('patch', { + schema: newSchema, + }); + dn.refresh(); + } else { + dn.deepMerge(newSchema); + } return onSubmit?.(values); }, }; diff --git a/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx b/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx index f4d1be2437..48f6256eae 100644 --- a/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx +++ b/packages/core/client/src/application/schema-settings/utils/createSelectSettingsItem.tsx @@ -8,14 +8,15 @@ */ import _ from 'lodash'; -import { useFieldSchema } from '@formily/react'; +import { useField, useFieldSchema } from '@formily/react'; import { TFunction, useTranslation } from 'react-i18next'; import { SchemaSettingsItemType } from '../types'; -import { getNewSchema, useHookDefault } from './util'; +import { getNewSchema, useHookDefault, useSchemaByType } from './util'; import { SelectProps } from '../../../schema-component/antd/select'; import { useCompile } from '../../../schema-component/hooks/useCompile'; import { useDesignable } from '../../../schema-component/hooks/useDesignable'; +import { useColumnSchema } from '../../../schema-component'; interface CreateSelectSchemaSettingsItemProps { name: string; @@ -26,6 +27,10 @@ interface CreateSelectSchemaSettingsItemProps { defaultValue?: string | number; useDefaultValue?: () => string | number; useVisible?: () => boolean; + /** + * @default 'common' + */ + type?: 'common' | 'field'; } /** @@ -44,6 +49,7 @@ export const createSelectSchemaSettingsItem = ( useOptions = useHookDefault, schemaKey, useVisible, + type = 'common', defaultValue: propsDefaultValue, useDefaultValue = useHookDefault, } = options; @@ -52,8 +58,9 @@ export const createSelectSchemaSettingsItem = ( type: 'select', useVisible, useComponentProps() { - const filedSchema = useFieldSchema(); - const { deepMerge } = useDesignable(); + const fieldSchema = useSchemaByType(type); + const { fieldSchema: tableColumnSchema } = useColumnSchema() || {}; + const { dn } = useDesignable(); const options = useOptions(propsOptions); const defaultValue = useDefaultValue(propsDefaultValue); const compile = useCompile(); @@ -62,9 +69,17 @@ export const createSelectSchemaSettingsItem = ( return { title: typeof title === 'function' ? title(t) : compile(title), options, - value: _.get(filedSchema, schemaKey, defaultValue), + value: _.get(fieldSchema, schemaKey, defaultValue), onChange(v) { - deepMerge(getNewSchema({ fieldSchema: filedSchema, schemaKey, value: v })); + const newSchema = getNewSchema({ fieldSchema, schemaKey, value: v }); + if (tableColumnSchema) { + dn.emit('patch', { + schema: newSchema, + }); + dn.refresh(); + } else { + dn.deepMerge(newSchema); + } }, }; }, diff --git a/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx b/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx index 1dab8683f2..e458eed1e3 100644 --- a/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx +++ b/packages/core/client/src/application/schema-settings/utils/createSwitchSettingsItem.tsx @@ -12,9 +12,10 @@ import { useFieldSchema } from '@formily/react'; import { TFunction, useTranslation } from 'react-i18next'; import { SchemaSettingsItemType } from '../types'; -import { getNewSchema, useHookDefault } from './util'; +import { getNewSchema, useHookDefault, useSchemaByType } from './util'; import { useCompile } from '../../../schema-component/hooks/useCompile'; import { useDesignable } from '../../../schema-component/hooks/useDesignable'; +import { useColumnSchema } from '../../../schema-component'; export interface CreateSwitchSchemaSettingsItemProps { name: string; @@ -23,6 +24,10 @@ export interface CreateSwitchSchemaSettingsItemProps { defaultValue?: boolean; useDefaultValue?: () => boolean; useVisible?: () => boolean; + /** + * @default 'common' + */ + type?: 'common' | 'field'; } /** @@ -37,6 +42,7 @@ export function createSwitchSettingsItem(options: CreateSwitchSchemaSettingsItem useVisible, schemaKey, title, + type = 'common', defaultValue: propsDefaultValue, useDefaultValue = useHookDefault, } = options; @@ -45,17 +51,26 @@ export function createSwitchSettingsItem(options: CreateSwitchSchemaSettingsItem useVisible, type: 'switch', useComponentProps() { - const filedSchema = useFieldSchema(); - const { deepMerge } = useDesignable(); + const fieldSchema = useSchemaByType(type); + const { dn } = useDesignable(); const defaultValue = useDefaultValue(propsDefaultValue); const compile = useCompile(); const { t } = useTranslation(); + const { fieldSchema: tableColumnSchema } = useColumnSchema() || {}; return { title: typeof title === 'function' ? title(t) : compile(title), - checked: !!_.get(filedSchema, schemaKey, defaultValue), + checked: !!_.get(fieldSchema, schemaKey, defaultValue), onChange(v) { - deepMerge(getNewSchema({ fieldSchema: filedSchema, schemaKey, value: v })); + const newSchema = getNewSchema({ fieldSchema, schemaKey, value: v }); + if (tableColumnSchema) { + dn.emit('patch', { + schema: newSchema, + }); + dn.refresh(); + } else { + dn.deepMerge(newSchema); + } }, }; }, diff --git a/packages/core/client/src/application/schema-settings/utils/util.ts b/packages/core/client/src/application/schema-settings/utils/util.ts index ef3538dcc1..41ee6e0aef 100644 --- a/packages/core/client/src/application/schema-settings/utils/util.ts +++ b/packages/core/client/src/application/schema-settings/utils/util.ts @@ -8,28 +8,44 @@ */ import { ISchema } from '@formily/json-schema'; +import { useFieldSchema } from '@formily/react'; import _ from 'lodash'; +import { useColumnSchema } from '../../../schema-component'; type IGetNewSchema = { fieldSchema: ISchema; + // x-component-props.title schemaKey?: string; + // x-component-props parentSchemaKey?: string; value: any; valueKeys?: string[]; }; export function getNewSchema(options: IGetNewSchema) { - const { fieldSchema, schemaKey, value, parentSchemaKey, valueKeys } = options; - - if (value != undefined && typeof value === 'object') { - Object.keys(value).forEach((key) => { - if (valueKeys && !valueKeys.includes(key)) return; - _.set(fieldSchema, `${parentSchemaKey}.${key}`, value[key]); - }); - } else { + const { fieldSchema, schemaKey, parentSchemaKey, value, valueKeys } = options as any; + if (schemaKey) { _.set(fieldSchema, schemaKey, value); + } else if (parentSchemaKey) { + if (value == undefined) return fieldSchema; + + if (typeof value === 'object') { + Object.keys(value).forEach((key) => { + if (valueKeys && !valueKeys.includes(key)) return; + _.set(fieldSchema, `${parentSchemaKey}.${key}`, value[key]); + }); + } else { + console.error('value must be object'); + } } + return fieldSchema; } export const useHookDefault = (defaultValues?: any) => defaultValues; + +export const useSchemaByType = (type: 'common' | 'field' = 'common') => { + const schema = useFieldSchema(); + const { fieldSchema: tableColumnSchema } = useColumnSchema() || {}; + return type === 'field' ? tableColumnSchema || schema : schema; +}; diff --git a/packages/core/client/src/collection-manager/interfaces/url.ts b/packages/core/client/src/collection-manager/interfaces/url.ts index 4c9ea23b48..dc537d95df 100644 --- a/packages/core/client/src/collection-manager/interfaces/url.ts +++ b/packages/core/client/src/collection-manager/interfaces/url.ts @@ -24,6 +24,16 @@ export class UrlFieldInterface extends CollectionFieldInterface { 'x-component': 'Input.URL', }, }; + componentOptions = [ + { + label: 'URL', + value: 'Input.URL', + }, + { + label: 'Preview', + value: 'Input.Preview', + }, + ]; availableTypes = ['string', 'text']; schemaInitialize(schema: ISchema, { block }) {} properties = { diff --git a/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts b/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts index 6a6995ec73..69de8c8f97 100644 --- a/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts +++ b/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts @@ -132,4 +132,191 @@ describe('CollectionFieldInterfaceManager', () => { expect(collectionFieldInterfaceManager.getFieldInterfaceGroup('nonExistentGroup')).toBeUndefined(); }); }); + + describe('addFieldInterfaceComponentOption', () => { + it('should add field interface component option', () => { + class A extends CollectionFieldInterface { + name = 'a'; + default = { + type: 'string', + uiSchema: { + type: 'string', + 'x-component': 'A', + }, + }; + } + + collectionFieldInterfaceManager.addFieldInterfaces([A]); + collectionFieldInterfaceManager.addFieldInterfaceComponentOption('a', { + label: 'Test', + value: 'test', + }); + + const fieldInterface = collectionFieldInterfaceManager.getFieldInterface('a'); + expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` + [ + { + "label": "A", + "useProps": [Function], + "value": "A", + }, + { + "label": "Test", + "value": "test", + }, + ] + `); + }); + + it('async addFieldInterfaceComponentOptions', async () => { + class A extends CollectionFieldInterface { + name = 'a'; + default = { + type: 'string', + uiSchema: { + type: 'string', + 'x-component': 'A', + }, + }; + } + + collectionFieldInterfaceManager.addFieldInterfaceComponentOption('a', { + label: 'Test', + value: 'test', + }); + collectionFieldInterfaceManager.addFieldInterfaces([A]); + + const fieldInterface = collectionFieldInterfaceManager.getFieldInterface('a'); + expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` + [ + { + "label": "A", + "useProps": [Function], + "value": "A", + }, + { + "label": "Test", + "value": "test", + }, + ] + `); + }); + + it('not default properties', () => { + class A extends CollectionFieldInterface { + name = 'a'; + } + + collectionFieldInterfaceManager.addFieldInterfaces([A]); + collectionFieldInterfaceManager.addFieldInterfaceComponentOption('a', { + label: 'Test', + value: 'test', + }); + + const fieldInterface = collectionFieldInterfaceManager.getFieldInterface('a'); + expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` + [ + { + "label": "Test", + "value": "test", + }, + ] + `); + }); + + it('not uiSchema properties', () => { + class A extends CollectionFieldInterface { + name = 'a'; + } + + collectionFieldInterfaceManager.addFieldInterfaces([A]); + collectionFieldInterfaceManager.addFieldInterfaceComponentOption('a', { + label: 'Test', + value: 'test', + }); + + const fieldInterface = collectionFieldInterfaceManager.getFieldInterface('a'); + expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` + [ + { + "label": "Test", + "value": "test", + }, + ] + `); + }); + + it('Label', () => { + class A extends CollectionFieldInterface { + name = 'a'; + default = { + type: 'string', + uiSchema: { + type: 'string', + 'x-component': 'A.B', + }, + }; + } + + collectionFieldInterfaceManager.addFieldInterfaces([A]); + collectionFieldInterfaceManager.addFieldInterfaceComponentOption('a', { + label: 'Test', + value: 'test', + }); + + const fieldInterface = collectionFieldInterfaceManager.getFieldInterface('a'); + expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` + [ + { + "label": "B", + "useProps": [Function], + "value": "A.B", + }, + { + "label": "Test", + "value": "test", + }, + ] + `); + }); + + it('have componentOptions', () => { + class A extends CollectionFieldInterface { + name = 'a'; + default = { + type: 'string', + uiSchema: { + type: 'string', + 'x-component': 'A', + }, + }; + componentOptions = [ + { + label: 'A', + value: 'A', + }, + ]; + } + + collectionFieldInterfaceManager.addFieldInterfaces([A]); + collectionFieldInterfaceManager.addFieldInterfaceComponentOption('a', { + label: 'Test', + value: 'test', + }); + + const fieldInterface = collectionFieldInterfaceManager.getFieldInterface('a'); + expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` + [ + { + "label": "A", + "value": "A", + }, + { + "label": "Test", + "value": "test", + }, + ] + `); + }); + }); }); diff --git a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts index 19f112d829..c063a2a577 100644 --- a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts +++ b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts @@ -15,6 +15,13 @@ export type CollectionFieldInterfaceFactory = new ( collectionFieldInterfaceManager: CollectionFieldInterfaceManager, ) => CollectionFieldInterface; +export interface CollectionFieldInterfaceComponentOption { + label: string; + value: string; + useVisible?: () => boolean; + useProps?: () => any; +} + export abstract class CollectionFieldInterface { constructor(public collectionFieldInterfaceManager: CollectionFieldInterfaceManager) {} name: string; @@ -32,6 +39,7 @@ export abstract class CollectionFieldInterface { supportDataSourceType?: string[]; notSupportDataSourceType?: string[]; hasDefaultValue?: boolean; + componentOptions?: CollectionFieldInterfaceComponentOption[]; isAssociation?: boolean; operators?: any[]; /** @@ -54,4 +62,24 @@ export abstract class CollectionFieldInterface { usePathOptions?(field: CollectionFieldOptions): any; schemaInitialize?(schema: ISchema, data: any): void; hidden?: boolean; + + addComponentOption(componentOption: CollectionFieldInterfaceComponentOption) { + if (!this.componentOptions) { + this.componentOptions = []; + const xComponent = this.default?.uiSchema?.['x-component']; + const componentProps = this.default?.uiSchema?.['x-component-props']; + if (xComponent) { + this.componentOptions = [ + { + label: xComponent.split('.').pop(), + value: xComponent, + useProps() { + return componentProps || {}; + }, + }, + ]; + } + } + this.componentOptions.push(componentOption); + } } diff --git a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterfaceManager.ts b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterfaceManager.ts index ffcb506be7..96000acad5 100644 --- a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterfaceManager.ts +++ b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterfaceManager.ts @@ -8,11 +8,21 @@ */ import type { DataSourceManager } from '../data-source'; -import type { CollectionFieldInterface, CollectionFieldInterfaceFactory } from './CollectionFieldInterface'; +import type { + CollectionFieldInterface, + CollectionFieldInterfaceComponentOption, + CollectionFieldInterfaceFactory, +} from './CollectionFieldInterface'; + +interface ActionType { + type: 'addComponentOption'; + data: any; +} export class CollectionFieldInterfaceManager { protected collectionFieldInterfaceInstances: Record = {}; protected collectionFieldGroups: Record = {}; + protected actionList: Record = {}; constructor( fieldInterfaceClasses: CollectionFieldInterfaceFactory[], @@ -27,11 +37,30 @@ export class CollectionFieldInterfaceManager { const newCollectionFieldInterfaces = fieldInterfaceClasses.reduce((acc, Interface) => { const instance = new Interface(this); acc[instance.name] = instance; + if (Array.isArray(this.actionList[instance.name])) { + this.actionList[instance.name].forEach((item) => { + instance[item.type](item.data); + }); + this.actionList[instance.name] = undefined; + } return acc; }, {}); Object.assign(this.collectionFieldInterfaceInstances, newCollectionFieldInterfaces); } + + addFieldInterfaceComponentOption(interfaceName: string, componentOption: CollectionFieldInterfaceComponentOption) { + const fieldInterface = this.getFieldInterface(interfaceName); + if (!fieldInterface) { + if (!this.actionList[interfaceName]) { + this.actionList[interfaceName] = []; + } + this.actionList[interfaceName].push({ type: 'addComponentOption', data: componentOption }); + return; + } + fieldInterface.addComponentOption(componentOption); + } + getFieldInterface(name: string) { return this.collectionFieldInterfaceInstances[name] as T; } diff --git a/packages/core/client/src/data-source/commonsSettingsItem/fieldComponent.ts b/packages/core/client/src/data-source/commonsSettingsItem/fieldComponent.ts new file mode 100644 index 0000000000..e630fdeb55 --- /dev/null +++ b/packages/core/client/src/data-source/commonsSettingsItem/fieldComponent.ts @@ -0,0 +1,76 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Field } from '@formily/core'; +import { useField, useFieldSchema } from '@formily/react'; +import _ from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { useDataSourceManager } from '../data-source/DataSourceManagerProvider'; +import { useCollectionField } from '../collection-field/CollectionFieldProvider'; +import { useColumnSchema, useCompile, useDesignable } from '../../schema-component'; +import type { SchemaSettingsItemType } from '../../application'; + +export const fieldComponentSettingsItem: SchemaSettingsItemType = { + name: 'fieldComponent', + type: 'select', + useVisible() { + const collectionField = useCollectionField(); + const dm = useDataSourceManager(); + if (!collectionField) return false; + const collectionInterface = dm.collectionFieldInterfaceManager.getFieldInterface(collectionField?.interface); + return ( + Array.isArray(collectionInterface?.componentOptions) && + collectionInterface.componentOptions.length > 1 && + collectionInterface.componentOptions.filter((item) => !item.useVisible || item.useVisible()).length > 1 + ); + }, + useComponentProps() { + const { t } = useTranslation(); + const field = useField(); + const schema = useFieldSchema(); + const collectionField = useCollectionField(); + const dm = useDataSourceManager(); + const collectionInterface = dm.collectionFieldInterfaceManager.getFieldInterface(collectionField?.interface); + const { fieldSchema: tableColumnSchema } = useColumnSchema(); + const fieldSchema = tableColumnSchema || schema; + const { dn } = useDesignable(); + const compile = useCompile(); + + const options = + collectionInterface?.componentOptions + ?.filter((item) => !item.useVisible || item.useVisible()) + ?.map((item) => { + return { + label: compile(item.label), + value: item.value, + useProps: item.useProps, + }; + }) || []; + return { + title: t('Field component'), + options, + value: fieldSchema['x-component-props']?.['component'] || options[0]?.value, + onChange(component) { + const componentOptions = options.find((item) => item.value === component); + const componentProps = { + component, + ...(componentOptions?.useProps?.() || {}), + }; + _.set(fieldSchema, 'x-component-props', componentProps); + field.componentProps = componentProps; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-component-props': componentProps, + }, + }); + }, + }; + }, +}; diff --git a/packages/core/client/src/data-source/commonsSettingsItem/index.ts b/packages/core/client/src/data-source/commonsSettingsItem/index.ts new file mode 100644 index 0000000000..16bdbd4da8 --- /dev/null +++ b/packages/core/client/src/data-source/commonsSettingsItem/index.ts @@ -0,0 +1,10 @@ +/** + * 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 './fieldComponent'; diff --git a/packages/core/client/src/data-source/index.ts b/packages/core/client/src/data-source/index.ts index 788809ebb6..458e0a653f 100644 --- a/packages/core/client/src/data-source/index.ts +++ b/packages/core/client/src/data-source/index.ts @@ -17,3 +17,4 @@ export * from './data-source'; export * from './collection-record'; export * from './utils'; export * from './collection-fields-to-initializer-items'; +export * from './commonsSettingsItem'; diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx b/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx index 94e7ae318e..3c8e65bfe2 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx @@ -25,6 +25,8 @@ import { isPatternDisabled } from '../../../../schema-settings'; import { ActionType } from '../../../../schema-settings/LinkageRules/type'; import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue'; import { useIsAllowToSetDefaultValue } from '../../../../schema-settings/hooks/useIsAllowToSetDefaultValue'; +import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; + import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; import { useIsFieldReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings'; export const fieldSettingsFormItem = new SchemaSettings({ @@ -464,6 +466,7 @@ export const fieldSettingsFormItem = new SchemaSettings({ }; }, }, + fieldComponentSettingsItem, ]; }, }, diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx index 96a1f3f3b6..6c6cf6cfc8 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx @@ -21,6 +21,7 @@ import { useAssociationFieldContext } from '../../../../schema-component/antd/as import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue'; import { isPatternDisabled } from '../../../../schema-settings/isPatternDisabled'; +import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; export const tableColumnSettings = new SchemaSettings({ @@ -378,6 +379,7 @@ export const tableColumnSettings = new SchemaSettings({ }; }, }, + fieldComponentSettingsItem, ], }, { diff --git a/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseItemFieldSettings.ts b/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseItemFieldSettings.ts index 5f81db22b1..8e62a09772 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseItemFieldSettings.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseItemFieldSettings.ts @@ -17,6 +17,7 @@ import { useCollectionManager } from '../../../../data-source/collection/Collect import { useCompile, useDesignable } from '../../../../schema-component'; import { SchemaSettingsDefaultSortingRules } from '../../../../schema-settings'; import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope'; +import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; export const filterCollapseItemFieldSettings = new SchemaSettings({ name: 'fieldSettings:FilterCollapseItem', @@ -197,6 +198,7 @@ export const filterCollapseItemFieldSettings = new SchemaSettings({ }; }, }, + fieldComponentSettingsItem, ]; }, }, diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts index 87d5fa46e9..1d3624f8a6 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts @@ -18,6 +18,7 @@ import { useCollectionManager_deprecated, useCollection_deprecated } from '../.. import { useFieldComponentName } from '../../../../common/useFieldComponentName'; import { EditOperator, useDesignable, useValidateSchema } from '../../../../schema-component'; import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue'; +import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; export const filterFormItemFieldSettings = new SchemaSettings({ name: 'fieldSettings:FilterFormItem', @@ -329,6 +330,7 @@ export const filterFormItemFieldSettings = new SchemaSettings({ name: 'operator', Component: EditOperator, }, + fieldComponentSettingsItem, ]; }, }, diff --git a/packages/core/client/src/modules/fields/component/Input.Preview/settings.ts b/packages/core/client/src/modules/fields/component/Input.Preview/settings.ts index 327ecfb99c..4acc828858 100644 --- a/packages/core/client/src/modules/fields/component/Input.Preview/settings.ts +++ b/packages/core/client/src/modules/fields/component/Input.Preview/settings.ts @@ -12,9 +12,11 @@ import { useField, useFieldSchema } from '@formily/react'; import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { useColumnSchema, useDesignable } from '../../../../schema-component'; -import { fieldComponent } from '../Input.URL/settings'; +import { SchemaSettingsItemType } from '../../../../application/schema-settings'; +// import { createSelectSchemaSettingsItem } from '../../../../application'; +// import { fieldComponent } from '../Input.URL/settings'; -const size = { +const size: SchemaSettingsItemType = { name: 'size', type: 'select', useComponentProps() { @@ -50,7 +52,23 @@ const size = { }, }; +// const size2 = createSelectSchemaSettingsItem({ +// name: 'size2', +// title: 'Size2', +// type: 'field', +// schemaKey: 'x-component-props.size', +// options: [ +// { value: 'small', label: 'Small' }, +// { value: 'middle', label: 'Middle' }, +// { value: 'large', label: 'Large' }, +// { value: 'auto', label: 'Auto' }, +// ], +// }); + export const inputPreviewComponentFieldSettings = new SchemaSettings({ name: 'fieldSettings:component:Input.Preview', - items: [fieldComponent, size], + items: [ + size, + // size2 + ], }); diff --git a/packages/core/client/src/modules/fields/component/Input.URL/settings.ts b/packages/core/client/src/modules/fields/component/Input.URL/settings.ts deleted file mode 100644 index c07c4eb303..0000000000 --- a/packages/core/client/src/modules/fields/component/Input.URL/settings.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { Field } from '@formily/core'; -import { useField, useFieldSchema } from '@formily/react'; -import _ from 'lodash'; -import { useTranslation } from 'react-i18next'; -import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; -import { useColumnSchema, useDesignable } from '../../../../schema-component'; - -export const fieldComponent: any = { - name: 'fieldComponent', - type: 'select', - useComponentProps() { - const { t } = useTranslation(); - const field = useField(); - const schema = useFieldSchema(); - const { fieldSchema: tableColumnSchema } = useColumnSchema(); - const fieldSchema = tableColumnSchema || schema; - const { dn } = useDesignable(); - return { - title: t('Field component'), - options: [ - { value: 'Input.URL', label: 'URL' }, - { value: 'Input.Preview', label: 'Preview' }, - ], - value: fieldSchema['x-component-props']?.['component'] || 'Input.URL', - onChange(component) { - _.set(fieldSchema, 'x-component-props.component', component); - field.componentProps.component = component; - dn.emit('patch', { - schema: { - ['x-uid']: fieldSchema['x-uid'], - 'x-component-props': fieldSchema['x-component-props'], - }, - }); - }, - }; - }, -}; - -export const inputURLComponentFieldSettings = new SchemaSettings({ - name: 'fieldSettings:component:Input.URL', - items: [fieldComponent], -}); diff --git a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts index f15adb76a7..1d03608d13 100644 --- a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts +++ b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts @@ -53,7 +53,7 @@ import { fileManagerComponentFieldSettings } from '../modules/fields/component/F import { previewComponentFieldSettings } from '../modules/fields/component/FileManager/previewComponentFieldSettings'; import { uploadAttachmentComponentFieldSettings } from '../modules/fields/component/FileManager/uploadAttachmentComponentFieldSettings'; import { inputPreviewComponentFieldSettings } from '../modules/fields/component/Input.Preview/settings'; -import { inputURLComponentFieldSettings } from '../modules/fields/component/Input.URL/settings'; +// import { inputURLComponentFieldSettings } from '../modules/fields/component/Input.URL/settings'; import { inputNumberComponentFieldSettings } from '../modules/fields/component/InputNumber/inputNumberComponentFieldSettings'; import { subformComponentFieldSettings } from '../modules/fields/component/Nester/subformComponentFieldSettings'; import { recordPickerComponentFieldSettings } from '../modules/fields/component/Picker/recordPickerComponentFieldSettings'; @@ -120,7 +120,7 @@ export class SchemaSettingsPlugin extends Plugin { this.schemaSettingsManager.add(tagComponentFieldSettings); this.schemaSettingsManager.add(cascadeSelectComponentFieldSettings); this.schemaSettingsManager.add(inputPreviewComponentFieldSettings); - this.schemaSettingsManager.add(inputURLComponentFieldSettings); + // this.schemaSettingsManager.add(inputURLComponentFieldSettings); this.schemaSettingsManager.add(uploadAttachmentComponentFieldSettings); this.schemaSettingsManager.add(previewComponentFieldSettings); } diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts index b6e3fd831c..87069a771f 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts @@ -20,6 +20,7 @@ import { useFormBlockContext, useIsFormReadPretty, useValidateSchema, + fieldComponentSettingsItem, EditValidationRules, } from '@nocobase/client'; import _ from 'lodash'; @@ -201,6 +202,7 @@ export const bulkEditFormItemSettings = new SchemaSettings({ return form && !isFormReadPretty && validateSchema; }, }, + fieldComponentSettingsItem, ]; }, }, diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/pageHeader.test.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/pageHeader.test.ts index 8b2303955b..17aa776c7d 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/pageHeader.test.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/pageHeader.test.ts @@ -233,6 +233,7 @@ test.describe('PageHeader', () => { await page.getByRole('menuitem', { name: 'Edit button' }).click(); await page.getByRole('textbox').fill('Test_changed'); await page.getByRole('button', { name: 'Submit' }).click(); + await expect(page.getByLabel('Edit button')).not.toBeVisible(); await expect(navigationBarPositionElement).toContainText('Test_changed'); // 编辑 URL