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()}`;
|
initialValue.reverseField.name = `f_${uid()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基于当前选中的 fieldInterface 构建 fieldType 选项
|
const fieldTypeOptions = schema.getSecondaryDataTypeOptions();
|
||||||
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 默认值为第一组的最深层级路径
|
|
||||||
if (fieldTypeOptions && fieldTypeOptions.length > 0) {
|
if (fieldTypeOptions && fieldTypeOptions.length > 0) {
|
||||||
const getFirstLeafPath = (options) => {
|
const getFirstLeafPath = (options) => {
|
||||||
const path = [];
|
const path = [];
|
||||||
|
@ -11,7 +11,7 @@ import { ISchema } from '@formily/react';
|
|||||||
import { isArr, isEmpty, isValid } from '@formily/shared';
|
import { isArr, isEmpty, isValid } from '@formily/shared';
|
||||||
import { registerValidateRules } from '@formily/validator';
|
import { registerValidateRules } from '@formily/validator';
|
||||||
import {
|
import {
|
||||||
AvailableFieldOptions,
|
AllowedFieldOptions,
|
||||||
CollectionFieldInterface,
|
CollectionFieldInterface,
|
||||||
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
@ -62,28 +62,10 @@ export class InputFieldInterface extends CollectionFieldInterface {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
fieldType = 'string';
|
fieldType = 'string';
|
||||||
availableOptions: AvailableFieldOptions = {
|
allowedOptions: AllowedFieldOptions = {
|
||||||
all: {
|
interfaces: ['textarea'],
|
||||||
input: {
|
types: ['string'],
|
||||||
string: ['varchar', 'char'],
|
dataTypes: ['varchar', 'char'],
|
||||||
},
|
|
||||||
textarea: {
|
|
||||||
text: ['text'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
available: {
|
|
||||||
input: {
|
|
||||||
string: {
|
|
||||||
varchar: ['varchar'],
|
|
||||||
char: ['varchar', 'char'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
textarea: {
|
|
||||||
text: {
|
|
||||||
text: ['text'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
availableTypes = ['varchar', 'char'];
|
availableTypes = ['varchar', 'char'];
|
||||||
hasDefaultValue = true;
|
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 { i18n } from '../../i18n';
|
||||||
import { defaultProps, operators, unique, autoIncrement, primaryKey } from './properties';
|
import { defaultProps, operators, unique, autoIncrement, primaryKey } from './properties';
|
||||||
import {
|
import {
|
||||||
AvailableFieldOptions,
|
AllowedFieldOptions,
|
||||||
CollectionFieldInterface,
|
CollectionFieldInterface,
|
||||||
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||||
|
|
||||||
@ -40,34 +40,10 @@ export class IntegerFieldInterface extends CollectionFieldInterface {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
availableTypes = ['bigInt', 'integer', 'sort'];
|
availableTypes = ['bigInt', 'integer', 'sort'];
|
||||||
availableOptions: AvailableFieldOptions = {
|
allowedOptions: AllowedFieldOptions = {
|
||||||
all: {
|
interfaces: ['number', 'input'],
|
||||||
integer: {
|
types: ['bigInt'],
|
||||||
bigInt: ['bigInt', 'tinyint', 'integer'],
|
dataTypes: ['bigInt', 'integer', 'tinyint', 'sort'],
|
||||||
},
|
|
||||||
number: {
|
|
||||||
double: ['double', 'float', 'decimal'],
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
string: ['varchar', 'char'],
|
|
||||||
},
|
|
||||||
textarea: {
|
|
||||||
text: ['text'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
available: {
|
|
||||||
input: {
|
|
||||||
string: {
|
|
||||||
varchar: ['varchar'],
|
|
||||||
char: ['varchar', 'char'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
textarea: {
|
|
||||||
text: {
|
|
||||||
text: ['text'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
properties = {
|
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.
|
* 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 { i18n } from '../../i18n';
|
||||||
import { defaultProps, operators, unique } from './properties';
|
import { defaultProps, operators, unique } from './properties';
|
||||||
|
|
||||||
@ -30,6 +33,10 @@ export class NumberFieldInterface extends CollectionFieldInterface {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
availableTypes = ['double', 'float', 'decimal'];
|
availableTypes = ['double', 'float', 'decimal'];
|
||||||
|
allowedOptions: AllowedFieldOptions = {
|
||||||
|
types: ['double'],
|
||||||
|
dataTypes: ['double', 'float', 'decimal'],
|
||||||
|
};
|
||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
properties = {
|
properties = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
|
@ -11,7 +11,7 @@ import { ISchema } from '@formily/react';
|
|||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
import { defaultProps, operators } from './properties';
|
import { defaultProps, operators } from './properties';
|
||||||
import {
|
import {
|
||||||
AvailableFieldOptions,
|
AllowedFieldOptions,
|
||||||
CollectionFieldInterface,
|
CollectionFieldInterface,
|
||||||
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||||
|
|
||||||
@ -30,19 +30,10 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
availableTypes = ['text', 'json', 'string'];
|
availableTypes = ['text', 'json', 'string'];
|
||||||
availableOptions: AvailableFieldOptions = {
|
allowedOptions: AllowedFieldOptions = {
|
||||||
all: {
|
interfaces: ['textarea'],
|
||||||
textarea: {
|
types: ['text'],
|
||||||
text: ['text'],
|
dataTypes: ['text'],
|
||||||
},
|
|
||||||
},
|
|
||||||
available: {
|
|
||||||
textarea: {
|
|
||||||
text: {
|
|
||||||
text: ['text'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
titleUsable = true;
|
titleUsable = true;
|
||||||
@ -105,4 +96,7 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
|
|||||||
filterable = {
|
filterable = {
|
||||||
operators: operators.string,
|
operators: operators.string,
|
||||||
};
|
};
|
||||||
|
getAllowDataTypesBySelected(selectedValue: string): string[] {
|
||||||
|
return ['text'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,19 +29,10 @@ export interface CollectionFieldInterfaceComponentOption {
|
|||||||
type FieldInterfaceName = string;
|
type FieldInterfaceName = string;
|
||||||
type FieldTypeName = string;
|
type FieldTypeName = string;
|
||||||
type FieldDataType = string;
|
type FieldDataType = string;
|
||||||
export interface AvailableFieldOptions {
|
export interface AllowedFieldOptions {
|
||||||
all: {
|
interfaces?: FieldInterfaceName[];
|
||||||
[key: FieldInterfaceName]: {
|
types: FieldTypeName[];
|
||||||
[key: FieldTypeName]: FieldDataType[];
|
dataTypes: FieldDataType[];
|
||||||
};
|
|
||||||
};
|
|
||||||
available: {
|
|
||||||
[key: FieldInterfaceName]: {
|
|
||||||
[key: FieldTypeName]: {
|
|
||||||
[key: FieldDataType]: FieldDataType[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class CollectionFieldInterface {
|
export abstract class CollectionFieldInterface {
|
||||||
@ -51,7 +42,7 @@ export abstract class CollectionFieldInterface {
|
|||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
availableOptions?: AvailableFieldOptions;
|
abstract allowedOptions: AllowedFieldOptions;
|
||||||
default?: {
|
default?: {
|
||||||
type: string;
|
type: string;
|
||||||
uiSchema?: ISchema;
|
uiSchema?: ISchema;
|
||||||
@ -193,98 +184,77 @@ export abstract class CollectionFieldInterface {
|
|||||||
this.filterable.operators.push(operatorOption);
|
this.filterable.operators.push(operatorOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllAvailableTypes(): string[] {
|
getAllowDataTypesBySelected(selectedValue: FieldDataType) {
|
||||||
if (!this.availableOptions?.all || !this.name) {
|
return this.allowedOptions?.dataTypes || [];
|
||||||
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)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvailableOptions(options: {
|
getSecondaryDataTypeOptions(): CascaderOptionType[] {
|
||||||
currentValue: [FieldInterfaceName, ...FieldTypeName[], FieldDataType];
|
return (this.allowedOptions?.types || []).map((type) => {
|
||||||
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;
|
|
||||||
return {
|
return {
|
||||||
label,
|
label: type,
|
||||||
value: key,
|
value: type,
|
||||||
disabled,
|
disabled: false,
|
||||||
children,
|
children: this.allowedOptions.dataTypes.map((dataType) => {
|
||||||
|
return {
|
||||||
|
label: dataType,
|
||||||
|
value: dataType,
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateFieldType<T extends AvailableFieldOptions>(value: string): keyof T {
|
getCascaderOptionType(options: {
|
||||||
// const fieldTypes = this.availableOptions[this.name];
|
dataType: string;
|
||||||
// if (!(value in Object.keys(fieldTypes))) {
|
interfaces: Record<string, CollectionFieldInterface>;
|
||||||
// throw new Error(`Field type "${value}" is not supported by interface "${this.name}".`);
|
compile: (value: string) => string;
|
||||||
// }
|
}): CascaderOptionType[] {
|
||||||
|
const { dataType: selectedDataType = this.allowedOptions?.dataTypes[0], interfaces, compile } = options;
|
||||||
// return value;
|
const allowedDataTypes = this.getAllowDataTypesBySelected(selectedDataType);
|
||||||
// }
|
const otherOptionTypes = (this.allowedOptions?.interfaces || []).map((allowedInterface) => {
|
||||||
|
const newInterface = interfaces[allowedInterface];
|
||||||
validateFieldDataType(fieldType: string, value: string) {
|
return {
|
||||||
const fieldTypes = this.availableOptions[this.name];
|
label: compile(newInterface.title),
|
||||||
if (!fieldTypes || !Array.isArray(fieldTypes)) {
|
value: newInterface.name,
|
||||||
throw new Error(`Field type "${value}" is not supported by interface "${this.name}".`);
|
disabled: false,
|
||||||
}
|
children: (newInterface.allowedOptions?.types || []).map((type) => {
|
||||||
const dataTypes = Object.keys(fieldTypes).find((x) => x === fieldType);
|
return {
|
||||||
if (!dataTypes || !Array.isArray(dataTypes)) {
|
label: type,
|
||||||
throw new Error(`Field type "${dataTypes}" is not supported by interface "${this.name}".`);
|
value: type,
|
||||||
}
|
disabled: false,
|
||||||
const newValue = dataTypes.find((x) => x === value);
|
children: (newInterface.allowedOptions?.dataTypes || []).map((dataType) => {
|
||||||
newValue;
|
return {
|
||||||
|
label: dataType,
|
||||||
return value;
|
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
|
// Field Interface -> Field Type -> Data Type
|
||||||
const createFieldTypeOptions = (
|
const createFieldTypeOptions = (
|
||||||
currentValue: [string, ...string[], string],
|
dataType: string,
|
||||||
getInterface: (name: string) => CollectionFieldInterface,
|
currentInterface: CollectionFieldInterface,
|
||||||
interfaces: Record<string, CollectionFieldInterface>,
|
interfaces: Record<string, CollectionFieldInterface>,
|
||||||
compile,
|
compile,
|
||||||
) => {
|
) => {
|
||||||
const [interfaceName] = currentValue;
|
const options = currentInterface.getCascaderOptionType({ dataType, interfaces, compile });
|
||||||
const currentInterface = getInterface(interfaceName);
|
|
||||||
const options = currentInterface.getAvailableOptions({ currentValue, interfaces, compile });
|
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -174,6 +172,7 @@ const CurrentFields = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentValue: any = []; // [interface, fieldType, dataType]
|
const currentValue: any = []; // [interface, fieldType, dataType]
|
||||||
|
const currentInterface = getInterface(value);
|
||||||
if (value) {
|
if (value) {
|
||||||
currentValue.push(value);
|
currentValue.push(value);
|
||||||
if (record.fieldType && Array.isArray(record.fieldType) && record.fieldType.length > 0) {
|
if (record.fieldType && Array.isArray(record.fieldType) && record.fieldType.length > 0) {
|
||||||
@ -184,9 +183,9 @@ const CurrentFields = (props) => {
|
|||||||
return (
|
return (
|
||||||
<Cascader
|
<Cascader
|
||||||
value={currentValue.length > 0 ? currentValue : undefined}
|
value={currentValue.length > 0 ? currentValue : undefined}
|
||||||
options={createFieldTypeOptions(currentValue, getInterface, interfaces, compile)}
|
options={createFieldTypeOptions(currentValue[2], currentInterface, interfaces, compile)}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={t('Select field type')}
|
placeholder={t('Select field interface')}
|
||||||
expandTrigger="hover"
|
expandTrigger="hover"
|
||||||
changeOnSelect={false}
|
changeOnSelect={false}
|
||||||
style={{ width: '100%', minWidth: 100 }}
|
style={{ width: '100%', minWidth: 100 }}
|
||||||
@ -308,20 +307,21 @@ const InheritFields = (props) => {
|
|||||||
title: t('Field name'),
|
title: t('Field name'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'fieldType',
|
dataIndex: 'interface',
|
||||||
title: t('Field type'),
|
title: t('Field interface'),
|
||||||
render: (value, record) => {
|
render: (value, record) => {
|
||||||
const currentValue: any = []; // [interface, fieldType, dataType]
|
const currentValue: any = []; // [interface, fieldType, dataType]
|
||||||
if (record.interface) {
|
const currentInterface = getInterface(value);
|
||||||
currentValue.push(record.interface);
|
if (value) {
|
||||||
if (value && Array.isArray(value) && value.length > 0) {
|
currentValue.push(value);
|
||||||
currentValue.push(...value);
|
if (value && Array.isArray(record.fieldType) && value.length > 0) {
|
||||||
|
currentValue.push(...record.fieldType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Cascader
|
<Cascader
|
||||||
value={currentValue.length > 0 ? currentValue : undefined}
|
value={currentValue.length > 0 ? currentValue : undefined}
|
||||||
options={createFieldTypeOptions(currentValue, getInterface, interfaces, compile)}
|
options={createFieldTypeOptions(currentValue[2], currentInterface, interfaces, compile)}
|
||||||
placeholder={t('Select field type')}
|
placeholder={t('Select field type')}
|
||||||
expandTrigger="hover"
|
expandTrigger="hover"
|
||||||
changeOnSelect={false}
|
changeOnSelect={false}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user