mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
refactor: define and compute rules for field interface, field type, and data type
This commit is contained in:
parent
f6982f5dfe
commit
3a0cb77a49
@ -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 = [];
|
||||
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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'];
|
||||
}
|
||||
}
|
||||
|
@ -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<string, CollectionFieldInterface>;
|
||||
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<string, CollectionFieldInterface>;
|
||||
}) {
|
||||
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<T extends AvailableFieldOptions>(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<string, CollectionFieldInterface>;
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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<string, CollectionFieldInterface>,
|
||||
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 (
|
||||
<Cascader
|
||||
value={currentValue.length > 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 (
|
||||
<Cascader
|
||||
value={currentValue.length > 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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user