From 3a0cb77a495ac3170eddc06fd9341f5d0a3d4911 Mon Sep 17 00:00:00 2001 From: aaaaaajie Date: Sat, 28 Jun 2025 01:45:42 +0800 Subject: [PATCH] refactor: define and compute rules for field interface, field type, and data type --- .../Configuration/AddFieldAction.tsx | 18 +- .../collection-manager/interfaces/input.ts | 38 ++-- .../collection-manager/interfaces/integer.ts | 49 +++-- .../collection-manager/interfaces/number.ts | 9 +- .../collection-manager/interfaces/textarea.ts | 22 +-- .../CollectionFieldInterface.ts | 170 ++++++++---------- .../Configuration/CollectionFields.tsx | 28 +-- 7 files changed, 136 insertions(+), 198 deletions(-) diff --git a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx index d1b0fe204c..6a73012e73 100644 --- a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx @@ -43,23 +43,7 @@ const getSchema = (schema: CollectionFieldInterface, record: any, compile) => { initialValue.reverseField.name = `f_${uid()}`; } - // 基于当前选中的 fieldInterface 构建 fieldType 选项 - const fieldTypeOptions = []; - const fieldType = schema.default?.type; - const availableFieldTypes = schema.availableOptions?.all[schema.name][fieldType] || []; - - if (fieldType && availableFieldTypes.length > 0) { - fieldTypeOptions.push({ - label: fieldType, - value: fieldType, - children: availableFieldTypes.map((availableType) => ({ - label: availableType, - value: availableType, - })), - }); - } - - // 设置 fieldType 默认值为第一组的最深层级路径 + const fieldTypeOptions = schema.getSecondaryDataTypeOptions(); if (fieldTypeOptions && fieldTypeOptions.length > 0) { const getFirstLeafPath = (options) => { const path = []; diff --git a/packages/core/client/src/collection-manager/interfaces/input.ts b/packages/core/client/src/collection-manager/interfaces/input.ts index 95b2dcc587..98f4682d02 100644 --- a/packages/core/client/src/collection-manager/interfaces/input.ts +++ b/packages/core/client/src/collection-manager/interfaces/input.ts @@ -11,7 +11,7 @@ import { ISchema } from '@formily/react'; import { isArr, isEmpty, isValid } from '@formily/shared'; import { registerValidateRules } from '@formily/validator'; import { - AvailableFieldOptions, + AllowedFieldOptions, CollectionFieldInterface, } from '../../data-source/collection-field-interface/CollectionFieldInterface'; import { i18n } from '../../i18n'; @@ -62,28 +62,10 @@ export class InputFieldInterface extends CollectionFieldInterface { }, }; fieldType = 'string'; - availableOptions: AvailableFieldOptions = { - all: { - input: { - string: ['varchar', 'char'], - }, - textarea: { - text: ['text'], - }, - }, - available: { - input: { - string: { - varchar: ['varchar'], - char: ['varchar', 'char'], - }, - }, - textarea: { - text: { - text: ['text'], - }, - }, - }, + allowedOptions: AllowedFieldOptions = { + interfaces: ['textarea'], + types: ['string'], + dataTypes: ['varchar', 'char'], }; availableTypes = ['varchar', 'char']; hasDefaultValue = true; @@ -245,4 +227,14 @@ export class InputFieldInterface extends CollectionFieldInterface { }, }; } + + getAllowDataTypesBySelected(selectedValue: string): string[] { + if (selectedValue === 'varchar') { + return ['varchar']; + } + if (selectedValue === 'char') { + return ['varchar', 'char']; + } + return []; + } } diff --git a/packages/core/client/src/collection-manager/interfaces/integer.ts b/packages/core/client/src/collection-manager/interfaces/integer.ts index 31af3d0c34..9c12000444 100644 --- a/packages/core/client/src/collection-manager/interfaces/integer.ts +++ b/packages/core/client/src/collection-manager/interfaces/integer.ts @@ -11,7 +11,7 @@ import { registerValidateFormats } from '@formily/core'; import { i18n } from '../../i18n'; import { defaultProps, operators, unique, autoIncrement, primaryKey } from './properties'; import { - AvailableFieldOptions, + AllowedFieldOptions, CollectionFieldInterface, } from '../../data-source/collection-field-interface/CollectionFieldInterface'; @@ -40,34 +40,10 @@ export class IntegerFieldInterface extends CollectionFieldInterface { }, }; availableTypes = ['bigInt', 'integer', 'sort']; - availableOptions: AvailableFieldOptions = { - all: { - integer: { - bigInt: ['bigInt', 'tinyint', 'integer'], - }, - number: { - double: ['double', 'float', 'decimal'], - }, - input: { - string: ['varchar', 'char'], - }, - textarea: { - text: ['text'], - }, - }, - available: { - input: { - string: { - varchar: ['varchar'], - char: ['varchar', 'char'], - }, - }, - textarea: { - text: { - text: ['text'], - }, - }, - }, + allowedOptions: AllowedFieldOptions = { + interfaces: ['number', 'input'], + types: ['bigInt'], + dataTypes: ['bigInt', 'integer', 'tinyint', 'sort'], }; hasDefaultValue = true; properties = { @@ -161,4 +137,19 @@ export class IntegerFieldInterface extends CollectionFieldInterface { }, }; }; + getAllowDataTypesBySelected(selectedValue: string): string[] { + if (selectedValue === 'bigInt') { + return ['bigInt', 'sort']; + } + if (selectedValue === 'integer') { + return ['bigInt', 'integer', 'sort']; + } + if (selectedValue === 'sort') { + return ['bigInt', 'integer', 'tinyint', 'sort']; + } + if (selectedValue === 'tinyint') { + return ['bigInt', 'integer', 'tinyint', 'sort']; + } + return [selectedValue]; + } } diff --git a/packages/core/client/src/collection-manager/interfaces/number.ts b/packages/core/client/src/collection-manager/interfaces/number.ts index 13b534dbf4..ad5cc6be8a 100644 --- a/packages/core/client/src/collection-manager/interfaces/number.ts +++ b/packages/core/client/src/collection-manager/interfaces/number.ts @@ -7,7 +7,10 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface'; +import { + AllowedFieldOptions, + CollectionFieldInterface, +} from '../../data-source/collection-field-interface/CollectionFieldInterface'; import { i18n } from '../../i18n'; import { defaultProps, operators, unique } from './properties'; @@ -30,6 +33,10 @@ export class NumberFieldInterface extends CollectionFieldInterface { }, }; availableTypes = ['double', 'float', 'decimal']; + allowedOptions: AllowedFieldOptions = { + types: ['double'], + dataTypes: ['double', 'float', 'decimal'], + }; hasDefaultValue = true; properties = { ...defaultProps, diff --git a/packages/core/client/src/collection-manager/interfaces/textarea.ts b/packages/core/client/src/collection-manager/interfaces/textarea.ts index 7ff595862c..04ce7c4bbf 100644 --- a/packages/core/client/src/collection-manager/interfaces/textarea.ts +++ b/packages/core/client/src/collection-manager/interfaces/textarea.ts @@ -11,7 +11,7 @@ import { ISchema } from '@formily/react'; import { i18n } from '../../i18n'; import { defaultProps, operators } from './properties'; import { - AvailableFieldOptions, + AllowedFieldOptions, CollectionFieldInterface, } from '../../data-source/collection-field-interface/CollectionFieldInterface'; @@ -30,19 +30,10 @@ export class TextareaFieldInterface extends CollectionFieldInterface { }, }; availableTypes = ['text', 'json', 'string']; - availableOptions: AvailableFieldOptions = { - all: { - textarea: { - text: ['text'], - }, - }, - available: { - textarea: { - text: { - text: ['text'], - }, - }, - }, + allowedOptions: AllowedFieldOptions = { + interfaces: ['textarea'], + types: ['text'], + dataTypes: ['text'], }; hasDefaultValue = true; titleUsable = true; @@ -105,4 +96,7 @@ export class TextareaFieldInterface extends CollectionFieldInterface { filterable = { operators: operators.string, }; + getAllowDataTypesBySelected(selectedValue: string): string[] { + return ['text']; + } } 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 60dee64d58..b33c9fddd5 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 @@ -29,19 +29,10 @@ export interface CollectionFieldInterfaceComponentOption { type FieldInterfaceName = string; type FieldTypeName = string; type FieldDataType = string; -export interface AvailableFieldOptions { - all: { - [key: FieldInterfaceName]: { - [key: FieldTypeName]: FieldDataType[]; - }; - }; - available: { - [key: FieldInterfaceName]: { - [key: FieldTypeName]: { - [key: FieldDataType]: FieldDataType[]; - }; - }; - }; +export interface AllowedFieldOptions { + interfaces?: FieldInterfaceName[]; + types: FieldTypeName[]; + dataTypes: FieldDataType[]; } export abstract class CollectionFieldInterface { @@ -51,7 +42,7 @@ export abstract class CollectionFieldInterface { title?: string; description?: string; order?: number; - availableOptions?: AvailableFieldOptions; + abstract allowedOptions: AllowedFieldOptions; default?: { type: string; uiSchema?: ISchema; @@ -193,98 +184,77 @@ export abstract class CollectionFieldInterface { this.filterable.operators.push(operatorOption); } - getAllAvailableTypes(): string[] { - if (!this.availableOptions?.all || !this.name) { - return []; - } - - const interfaceOptions = this.availableOptions.all[this.name]; - if (!interfaceOptions) { - return []; - } - - const allTypes: string[] = []; - Object.values(interfaceOptions).forEach((typeArray) => { - if (Array.isArray(typeArray)) { - allTypes.push(...typeArray); - } - }); - - return [...new Set(allTypes)]; + getAllowDataTypesBySelected(selectedValue: FieldDataType) { + return this.allowedOptions?.dataTypes || []; } - getAvailableOptions(options: { - currentValue: [FieldInterfaceName, ...FieldTypeName[], FieldDataType]; - interfaces: Record; - compile: (v: string) => string; - }): CascaderOptionType[] { - const { currentValue, interfaces, compile } = options; - const dataType = currentValue[currentValue.length - 1]; - const allAvailableOptions = this.availableOptions?.all || {}; - const availableOptions = this.availableOptions?.available || {}; - - const result = this.buildTree({ - all: allAvailableOptions, - available: availableOptions, - dataType, - interfaceMap: interfaces, - compile, - }); - return result; - } - - private buildTree(options: { - all; - available; - dataType; - compile: (v: string) => string; - interfaceMap?: Record; - }) { - const { all, available, dataType, compile, interfaceMap } = options; - if (Array.isArray(all)) { - const allowed = Array.isArray(available?.[dataType]) ? available[dataType] : []; - return all.map((item) => ({ - label: item, - value: item, - disabled: !allowed.includes(item), - })); - } - - return Object.entries(all).map(([key, value]) => { - const next = available?.[key]; - const children = this.buildTree({ all: value, available: next, dataType, compile }); - const disabled = Array.isArray(value) ? children.every((child) => child.disabled) : !next; - const label = interfaceMap ? compile(interfaceMap[key]?.title) : key; + getSecondaryDataTypeOptions(): CascaderOptionType[] { + return (this.allowedOptions?.types || []).map((type) => { return { - label, - value: key, - disabled, - children, + label: type, + value: type, + disabled: false, + children: this.allowedOptions.dataTypes.map((dataType) => { + return { + label: dataType, + value: dataType, + disabled: false, + }; + }), }; }); } - // validateFieldType(value: string): keyof T { - // const fieldTypes = this.availableOptions[this.name]; - // if (!(value in Object.keys(fieldTypes))) { - // throw new Error(`Field type "${value}" is not supported by interface "${this.name}".`); - // } - - // return value; - // } - - validateFieldDataType(fieldType: string, value: string) { - const fieldTypes = this.availableOptions[this.name]; - if (!fieldTypes || !Array.isArray(fieldTypes)) { - throw new Error(`Field type "${value}" is not supported by interface "${this.name}".`); - } - const dataTypes = Object.keys(fieldTypes).find((x) => x === fieldType); - if (!dataTypes || !Array.isArray(dataTypes)) { - throw new Error(`Field type "${dataTypes}" is not supported by interface "${this.name}".`); - } - const newValue = dataTypes.find((x) => x === value); - newValue; - - return value; + getCascaderOptionType(options: { + dataType: string; + interfaces: Record; + compile: (value: string) => string; + }): CascaderOptionType[] { + const { dataType: selectedDataType = this.allowedOptions?.dataTypes[0], interfaces, compile } = options; + const allowedDataTypes = this.getAllowDataTypesBySelected(selectedDataType); + const otherOptionTypes = (this.allowedOptions?.interfaces || []).map((allowedInterface) => { + const newInterface = interfaces[allowedInterface]; + return { + label: compile(newInterface.title), + value: newInterface.name, + disabled: false, + children: (newInterface.allowedOptions?.types || []).map((type) => { + return { + label: type, + value: type, + disabled: false, + children: (newInterface.allowedOptions?.dataTypes || []).map((dataType) => { + return { + label: dataType, + value: dataType, + disabled: false, + }; + }), + }; + }), + }; + }); + return [ + { + label: compile(this.title), + value: this.name, + disabled: false, + children: (this.allowedOptions?.types || []).map((type) => { + return { + label: type, + value: type, + disabled: false, + children: (this.allowedOptions?.dataTypes || []).map((dataType) => { + return { + label: dataType, + value: dataType, + disabled: !allowedDataTypes.includes(dataType), + }; + }), + }; + }), + }, + ...otherOptionTypes, + ]; } } diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/CollectionFields.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/CollectionFields.tsx index 0daa5819f3..4f5a523889 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/CollectionFields.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/CollectionFields.tsx @@ -108,14 +108,12 @@ const titlePrompt = 'Default title for each record'; // Field Interface -> Field Type -> Data Type const createFieldTypeOptions = ( - currentValue: [string, ...string[], string], - getInterface: (name: string) => CollectionFieldInterface, + dataType: string, + currentInterface: CollectionFieldInterface, interfaces: Record, compile, ) => { - const [interfaceName] = currentValue; - const currentInterface = getInterface(interfaceName); - const options = currentInterface.getAvailableOptions({ currentValue, interfaces, compile }); + const options = currentInterface.getCascaderOptionType({ dataType, interfaces, compile }); return options; }; @@ -174,6 +172,7 @@ const CurrentFields = (props) => { }; const currentValue: any = []; // [interface, fieldType, dataType] + const currentInterface = getInterface(value); if (value) { currentValue.push(value); if (record.fieldType && Array.isArray(record.fieldType) && record.fieldType.length > 0) { @@ -184,9 +183,9 @@ const CurrentFields = (props) => { return ( 0 ? currentValue : undefined} - options={createFieldTypeOptions(currentValue, getInterface, interfaces, compile)} + options={createFieldTypeOptions(currentValue[2], currentInterface, interfaces, compile)} onChange={handleChange} - placeholder={t('Select field type')} + placeholder={t('Select field interface')} expandTrigger="hover" changeOnSelect={false} style={{ width: '100%', minWidth: 100 }} @@ -308,20 +307,21 @@ const InheritFields = (props) => { title: t('Field name'), }, { - dataIndex: 'fieldType', - title: t('Field type'), + dataIndex: 'interface', + title: t('Field interface'), render: (value, record) => { const currentValue: any = []; // [interface, fieldType, dataType] - if (record.interface) { - currentValue.push(record.interface); - if (value && Array.isArray(value) && value.length > 0) { - currentValue.push(...value); + const currentInterface = getInterface(value); + if (value) { + currentValue.push(value); + if (value && Array.isArray(record.fieldType) && value.length > 0) { + currentValue.push(...record.fieldType); } } return ( 0 ? currentValue : undefined} - options={createFieldTypeOptions(currentValue, getInterface, interfaces, compile)} + options={createFieldTypeOptions(currentValue[2], currentInterface, interfaces, compile)} placeholder={t('Select field type')} expandTrigger="hover" changeOnSelect={false}