mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
refactor: define and compute rules for field interface, field type, and data type
This commit is contained in:
parent
ed57a43ec9
commit
f6982f5dfe
@ -27,7 +27,7 @@ import * as components from './components';
|
|||||||
import { useFieldInterfaceOptions } from './interfaces';
|
import { useFieldInterfaceOptions } from './interfaces';
|
||||||
import { ItemType, MenuItemType } from 'antd/es/menu/interface';
|
import { ItemType, MenuItemType } from 'antd/es/menu/interface';
|
||||||
|
|
||||||
const getSchema = (schema: CollectionFieldInterface, record: any, compile, fieldTypeOptions) => {
|
const getSchema = (schema: CollectionFieldInterface, record: any, compile) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -43,6 +43,22 @@ const getSchema = (schema: CollectionFieldInterface, record: any, compile, field
|
|||||||
initialValue.reverseField.name = `f_${uid()}`;
|
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 默认值为第一组的最深层级路径
|
// 设置 fieldType 默认值为第一组的最深层级路径
|
||||||
if (fieldTypeOptions && fieldTypeOptions.length > 0) {
|
if (fieldTypeOptions && fieldTypeOptions.length > 0) {
|
||||||
const getFirstLeafPath = (options) => {
|
const getFirstLeafPath = (options) => {
|
||||||
@ -69,60 +85,62 @@ const getSchema = (schema: CollectionFieldInterface, record: any, compile, field
|
|||||||
|
|
||||||
// initialValue.uiSchema.title = schema.title;
|
// initialValue.uiSchema.title = schema.title;
|
||||||
return {
|
return {
|
||||||
type: 'object',
|
schema: {
|
||||||
properties: {
|
type: 'object',
|
||||||
[uid()]: {
|
properties: {
|
||||||
type: 'void',
|
[uid()]: {
|
||||||
'x-component': 'Action.Drawer',
|
type: 'void',
|
||||||
'x-component-props': {
|
'x-component': 'Action.Drawer',
|
||||||
getContainer: '{{ getContainer }}',
|
'x-component-props': {
|
||||||
},
|
getContainer: '{{ getContainer }}',
|
||||||
'x-decorator': 'Form',
|
|
||||||
'x-decorator-props': {
|
|
||||||
useValues(options) {
|
|
||||||
return useRequest(
|
|
||||||
() =>
|
|
||||||
Promise.resolve({
|
|
||||||
data: initialValue,
|
|
||||||
}),
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
'x-decorator': 'Form',
|
||||||
title: `${compile(record.title)} - ${compile('{{ t("Add field") }}')}`,
|
'x-decorator-props': {
|
||||||
properties: {
|
useValues(options) {
|
||||||
summary: {
|
return useRequest(
|
||||||
type: 'void',
|
() =>
|
||||||
'x-component': 'FieldSummary',
|
Promise.resolve({
|
||||||
'x-component-props': {
|
data: initialValue,
|
||||||
schemaKey: schema.name,
|
}),
|
||||||
|
options,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
title: `${compile(record.title)} - ${compile('{{ t("Add field") }}')}`,
|
||||||
...properties,
|
properties: {
|
||||||
description: {
|
summary: {
|
||||||
type: 'string',
|
type: 'void',
|
||||||
title: '{{t("Description")}}',
|
'x-component': 'FieldSummary',
|
||||||
'x-decorator': 'FormItem',
|
'x-component-props': {
|
||||||
'x-component': 'Input.TextArea',
|
schemaKey: schema.name,
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Action.Drawer.Footer',
|
|
||||||
properties: {
|
|
||||||
action1: {
|
|
||||||
title: '{{ t("Cancel") }}',
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-component-props': {
|
|
||||||
useAction: '{{ useCancelAction }}',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
action2: {
|
},
|
||||||
title: '{{ t("Submit") }}',
|
// @ts-ignore
|
||||||
'x-component': 'Action',
|
...properties,
|
||||||
'x-component-props': {
|
description: {
|
||||||
type: 'primary',
|
type: 'string',
|
||||||
useAction: '{{ useCreateCollectionField }}',
|
title: '{{t("Description")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
properties: {
|
||||||
|
action1: {
|
||||||
|
title: '{{ t("Cancel") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action2: {
|
||||||
|
title: '{{ t("Submit") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ useCreateCollectionField }}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -130,6 +148,7 @@ const getSchema = (schema: CollectionFieldInterface, record: any, compile, field
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fieldTypeOptions,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -193,6 +212,7 @@ export const AddFieldAction = (props) => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [targetScope, setTargetScope] = useState();
|
const [targetScope, setTargetScope] = useState();
|
||||||
const [schema, setSchema] = useState({});
|
const [schema, setSchema] = useState({});
|
||||||
|
const [fieldTypeOptions, setFieldTypeOptions] = useState([]);
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isDialect } = useDialect();
|
const { isDialect } = useDialect();
|
||||||
@ -210,50 +230,6 @@ export const AddFieldAction = (props) => {
|
|||||||
const dm = useDataSourceManager();
|
const dm = useDataSourceManager();
|
||||||
const interfaces = dm.collectionFieldInterfaceManager.getFieldInterfaces();
|
const interfaces = dm.collectionFieldInterfaceManager.getFieldInterfaces();
|
||||||
|
|
||||||
const fieldTypeOptions = useMemo(() => {
|
|
||||||
const { availableFieldInterfaces } = getTemplate(record.template) || {};
|
|
||||||
const { exclude, include } = (availableFieldInterfaces || {}) as any;
|
|
||||||
|
|
||||||
// 根据 fieldType 分组 interfaces
|
|
||||||
const fieldTypeGroups = {};
|
|
||||||
|
|
||||||
interfaces.forEach((fieldInterface) => {
|
|
||||||
// 检查是否符合模板限制
|
|
||||||
if (include?.length && !include.includes(fieldInterface.name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (exclude?.length && exclude.includes(fieldInterface.name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldType = fieldInterface.default?.type;
|
|
||||||
const availableFieldTypes = fieldInterface.availableFieldTypes || [];
|
|
||||||
|
|
||||||
if (fieldType && availableFieldTypes.length > 0) {
|
|
||||||
if (!fieldTypeGroups[fieldType]) {
|
|
||||||
fieldTypeGroups[fieldType] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为每个 availableFieldType 创建选项
|
|
||||||
availableFieldTypes.forEach((availableType) => {
|
|
||||||
if (!fieldTypeGroups[fieldType].find((item) => item.value === availableType)) {
|
|
||||||
fieldTypeGroups[fieldType].push({
|
|
||||||
label: availableType,
|
|
||||||
value: availableType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 转换为 antd 级联组件需要的数据结构
|
|
||||||
return Object.keys(fieldTypeGroups).map((fieldType) => ({
|
|
||||||
label: fieldType,
|
|
||||||
value: fieldType,
|
|
||||||
children: fieldTypeGroups[fieldType],
|
|
||||||
}));
|
|
||||||
}, [interfaces, getTemplate, record, compile]);
|
|
||||||
|
|
||||||
const getFieldOptions = useCallback(() => {
|
const getFieldOptions = useCallback(() => {
|
||||||
const { availableFieldInterfaces } = getTemplate(record.template) || {};
|
const { availableFieldInterfaces } = getTemplate(record.template) || {};
|
||||||
const { exclude, include } = (availableFieldInterfaces || {}) as any;
|
const { exclude, include } = (availableFieldInterfaces || {}) as any;
|
||||||
@ -355,9 +331,10 @@ export const AddFieldAction = (props) => {
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const targetScope = e.item.props['data-targetScope'];
|
const targetScope = e.item.props['data-targetScope'];
|
||||||
targetScope && setTargetScope(targetScope);
|
targetScope && setTargetScope(targetScope);
|
||||||
const schema = getSchema(getInterface(e.key), record, compile, fieldTypeOptions);
|
const result = getSchema(getInterface(e.key), record, compile);
|
||||||
if (schema) {
|
if (result) {
|
||||||
setSchema(schema);
|
setSchema(result.schema);
|
||||||
|
setFieldTypeOptions(result.fieldTypeOptions);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -10,7 +10,10 @@
|
|||||||
import { ISchema } from '@formily/react';
|
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 { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
import {
|
||||||
|
AvailableFieldOptions,
|
||||||
|
CollectionFieldInterface,
|
||||||
|
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
import { defaultProps, operators, primaryKey, unique } from './properties';
|
import { defaultProps, operators, primaryKey, unique } from './properties';
|
||||||
|
|
||||||
@ -59,7 +62,29 @@ export class InputFieldInterface extends CollectionFieldInterface {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
fieldType = 'string';
|
fieldType = 'string';
|
||||||
availableFieldTypes = ['varchar', 'char'];
|
availableOptions: AvailableFieldOptions = {
|
||||||
|
all: {
|
||||||
|
input: {
|
||||||
|
string: ['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;
|
||||||
properties = {
|
properties = {
|
||||||
|
@ -10,7 +10,10 @@
|
|||||||
import { registerValidateFormats } from '@formily/core';
|
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 { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
import {
|
||||||
|
AvailableFieldOptions,
|
||||||
|
CollectionFieldInterface,
|
||||||
|
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||||
|
|
||||||
registerValidateFormats({
|
registerValidateFormats({
|
||||||
odd: /^-?\d*[13579]$/,
|
odd: /^-?\d*[13579]$/,
|
||||||
@ -37,6 +40,35 @@ export class IntegerFieldInterface extends CollectionFieldInterface {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
availableTypes = ['bigInt', 'integer', 'sort'];
|
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'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
properties = {
|
properties = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
|
@ -10,7 +10,10 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
import { defaultProps, operators } from './properties';
|
import { defaultProps, operators } from './properties';
|
||||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
import {
|
||||||
|
AvailableFieldOptions,
|
||||||
|
CollectionFieldInterface,
|
||||||
|
} from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||||
|
|
||||||
export class TextareaFieldInterface extends CollectionFieldInterface {
|
export class TextareaFieldInterface extends CollectionFieldInterface {
|
||||||
name = 'textarea';
|
name = 'textarea';
|
||||||
@ -27,6 +30,20 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
availableTypes = ['text', 'json', 'string'];
|
availableTypes = ['text', 'json', 'string'];
|
||||||
|
availableOptions: AvailableFieldOptions = {
|
||||||
|
all: {
|
||||||
|
textarea: {
|
||||||
|
text: ['text'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
available: {
|
||||||
|
textarea: {
|
||||||
|
text: {
|
||||||
|
text: ['text'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
titleUsable = true;
|
titleUsable = true;
|
||||||
properties = {
|
properties = {
|
||||||
|
@ -13,6 +13,8 @@ import type { CollectionFieldOptions } from '../collection';
|
|||||||
import { CollectionFieldInterfaceManager } from './CollectionFieldInterfaceManager';
|
import { CollectionFieldInterfaceManager } from './CollectionFieldInterfaceManager';
|
||||||
import { defaultProps } from '../../collection-manager/interfaces/properties';
|
import { defaultProps } from '../../collection-manager/interfaces/properties';
|
||||||
import { tval } from '@nocobase/utils/client';
|
import { tval } from '@nocobase/utils/client';
|
||||||
|
import type { DefaultOptionType as CascaderOptionType } from 'antd/lib/cascader';
|
||||||
|
import _ from 'lodash';
|
||||||
export type CollectionFieldInterfaceFactory = new (
|
export type CollectionFieldInterfaceFactory = new (
|
||||||
collectionFieldInterfaceManager: CollectionFieldInterfaceManager,
|
collectionFieldInterfaceManager: CollectionFieldInterfaceManager,
|
||||||
) => CollectionFieldInterface;
|
) => CollectionFieldInterface;
|
||||||
@ -24,7 +26,23 @@ export interface CollectionFieldInterfaceComponentOption {
|
|||||||
useProps?: () => any;
|
useProps?: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AvailableFieldTypes = string[];
|
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 abstract class CollectionFieldInterface {
|
export abstract class CollectionFieldInterface {
|
||||||
constructor(public collectionFieldInterfaceManager: CollectionFieldInterfaceManager) {}
|
constructor(public collectionFieldInterfaceManager: CollectionFieldInterfaceManager) {}
|
||||||
@ -33,7 +51,7 @@ export abstract class CollectionFieldInterface {
|
|||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
availableFieldTypes?: AvailableFieldTypes;
|
availableOptions?: AvailableFieldOptions;
|
||||||
default?: {
|
default?: {
|
||||||
type: string;
|
type: string;
|
||||||
uiSchema?: ISchema;
|
uiSchema?: ISchema;
|
||||||
@ -174,4 +192,99 @@ export abstract class CollectionFieldInterface {
|
|||||||
|
|
||||||
this.filterable.operators.push(operatorOption);
|
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)];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
value: key,
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { FieldContext, FormContext, useField } from '@formily/react';
|
|||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
AddCollectionField,
|
AddCollectionField,
|
||||||
|
CollectionFieldInterface,
|
||||||
EditCollectionField,
|
EditCollectionField,
|
||||||
Input,
|
Input,
|
||||||
isDeleteButtonDisabled,
|
isDeleteButtonDisabled,
|
||||||
@ -79,15 +80,12 @@ const tableContainer = css`
|
|||||||
flex: 1.5;
|
flex: 1.5;
|
||||||
width: 0;
|
width: 0;
|
||||||
&:nth-child(4) {
|
&:nth-child(4) {
|
||||||
flex: 1.8; /* Field interface column */
|
flex: 2.5; /* Combined Field interface + type column */
|
||||||
}
|
}
|
||||||
&:nth-child(5) {
|
&:nth-child(5) {
|
||||||
flex: 1.8; /* Field type column */
|
|
||||||
}
|
|
||||||
&:nth-child(6) {
|
|
||||||
flex: 1.2; /* Title field column */
|
flex: 1.2; /* Title field column */
|
||||||
}
|
}
|
||||||
&:nth-child(7) {
|
&:nth-child(6) {
|
||||||
flex: 2; /* Description column */
|
flex: 2; /* Description column */
|
||||||
}
|
}
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@ -108,48 +106,17 @@ const tableContainer = css`
|
|||||||
|
|
||||||
const titlePrompt = 'Default title for each record';
|
const titlePrompt = 'Default title for each record';
|
||||||
|
|
||||||
const convertFieldTypeToOptions = (fieldType: string[]) => {
|
// Field Interface -> Field Type -> Data Type
|
||||||
if (!fieldType || !Array.isArray(fieldType) || fieldType.length === 0) {
|
const createFieldTypeOptions = (
|
||||||
return [];
|
currentValue: [string, ...string[], string],
|
||||||
}
|
getInterface: (name: string) => CollectionFieldInterface,
|
||||||
|
interfaces: Record<string, CollectionFieldInterface>,
|
||||||
const fieldTypeHierarchy = {
|
compile,
|
||||||
string: [
|
) => {
|
||||||
{ label: 'varchar', value: 'varchar' },
|
const [interfaceName] = currentValue;
|
||||||
{ label: 'char', value: 'char' },
|
const currentInterface = getInterface(interfaceName);
|
||||||
{ label: 'text', value: 'text' },
|
const options = currentInterface.getAvailableOptions({ currentValue, interfaces, compile });
|
||||||
],
|
return options;
|
||||||
integer: [
|
|
||||||
{ label: 'int', value: 'int' },
|
|
||||||
{ label: 'bigint', value: 'bigint' },
|
|
||||||
{ label: 'smallint', value: 'smallint' },
|
|
||||||
],
|
|
||||||
float: [
|
|
||||||
{ label: 'decimal', value: 'decimal' },
|
|
||||||
{ label: 'double', value: 'double' },
|
|
||||||
],
|
|
||||||
boolean: [{ label: 'boolean', value: 'boolean' }],
|
|
||||||
date: [
|
|
||||||
{ label: 'date', value: 'date' },
|
|
||||||
{ label: 'datetime', value: 'datetime' },
|
|
||||||
{ label: 'timestamp', value: 'timestamp' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fieldType.length >= 1) {
|
|
||||||
const firstLevel = fieldType[0];
|
|
||||||
const children = fieldTypeHierarchy[firstLevel] || [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: firstLevel,
|
|
||||||
value: firstLevel,
|
|
||||||
children: children.length > 0 ? children : undefined,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CurrentFields = (props) => {
|
const CurrentFields = (props) => {
|
||||||
@ -178,60 +145,23 @@ const CurrentFields = (props) => {
|
|||||||
{
|
{
|
||||||
dataIndex: 'interface',
|
dataIndex: 'interface',
|
||||||
title: t('Field interface'),
|
title: t('Field interface'),
|
||||||
render: (value, record) => {
|
|
||||||
const handleChange = async (selectedInterface) => {
|
|
||||||
try {
|
|
||||||
await api.request({
|
|
||||||
url: `collections.fields:update?filterByTk=${record.name}&associatedIndex=${filterByTk}`,
|
|
||||||
method: 'post',
|
|
||||||
data: { interface: selectedInterface },
|
|
||||||
});
|
|
||||||
ctx?.refresh?.();
|
|
||||||
await props.refreshAsync();
|
|
||||||
refreshCM();
|
|
||||||
message.success(t('Saved successfully'));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to update field interface:', error);
|
|
||||||
message.error(t('Save failed'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
value={value}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder={t('Select field interface')}
|
|
||||||
size="small"
|
|
||||||
style={{ width: '100%', minWidth: 120 }}
|
|
||||||
disabled={targetTemplate?.forbidDeletion}
|
|
||||||
showSearch
|
|
||||||
filterOption={(input, option) => {
|
|
||||||
const label = option?.children || '';
|
|
||||||
return String(label).toLowerCase().includes(input.toLowerCase());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Object.keys(interfaces).map((interfaceKey) => {
|
|
||||||
const interfaceItem = interfaces[interfaceKey];
|
|
||||||
return (
|
|
||||||
<Select.Option key={interfaceKey} value={interfaceKey}>
|
|
||||||
{compile(interfaceItem.title)}
|
|
||||||
</Select.Option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataIndex: 'fieldType',
|
|
||||||
title: t('Field type'),
|
|
||||||
render: (value, record) => {
|
render: (value, record) => {
|
||||||
const handleChange = async (selectedValues) => {
|
const handleChange = async (selectedValues) => {
|
||||||
try {
|
try {
|
||||||
|
const [interfaceValue, fieldTypeValue, dataTypeValue] = selectedValues || [];
|
||||||
|
|
||||||
|
const updateData: any = {};
|
||||||
|
if (interfaceValue) {
|
||||||
|
updateData.interface = interfaceValue;
|
||||||
|
}
|
||||||
|
if (fieldTypeValue || dataTypeValue) {
|
||||||
|
updateData.fieldType = selectedValues;
|
||||||
|
}
|
||||||
|
|
||||||
// await api.request({
|
// await api.request({
|
||||||
// url: `collections.fields:update?filterByTk=${record.name}&associatedIndex=${filterByTk}`,
|
// url: `collections.fields:update?filterByTk=${record.name}&associatedIndex=${filterByTk}`,
|
||||||
// method: 'post',
|
// method: 'post',
|
||||||
// data: { fieldType: selectedValues },
|
// data: updateData,
|
||||||
// });
|
// });
|
||||||
ctx?.refresh?.();
|
ctx?.refresh?.();
|
||||||
await props.refreshAsync();
|
await props.refreshAsync();
|
||||||
@ -243,20 +173,26 @@ const CurrentFields = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return value && Array.isArray(value) && value.length > 0 ? (
|
const currentValue: any = []; // [interface, fieldType, dataType]
|
||||||
|
if (value) {
|
||||||
|
currentValue.push(value);
|
||||||
|
if (record.fieldType && Array.isArray(record.fieldType) && record.fieldType.length > 0) {
|
||||||
|
currentValue.push(...record.fieldType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Cascader
|
<Cascader
|
||||||
value={value}
|
value={currentValue.length > 0 ? currentValue : undefined}
|
||||||
options={convertFieldTypeToOptions(value)}
|
options={createFieldTypeOptions(currentValue, getInterface, interfaces, compile)}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={t('Select field type')}
|
placeholder={t('Select field type')}
|
||||||
expandTrigger="hover"
|
expandTrigger="hover"
|
||||||
changeOnSelect={true}
|
changeOnSelect={false}
|
||||||
size="small"
|
style={{ width: '100%', minWidth: 100 }}
|
||||||
style={{ width: '100%', minWidth: 120 }}
|
|
||||||
disabled={targetTemplate?.forbidDeletion}
|
disabled={targetTemplate?.forbidDeletion}
|
||||||
|
showSearch
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<span style={{ color: '#ccc' }}>-</span>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -351,7 +287,7 @@ const CurrentFields = (props) => {
|
|||||||
|
|
||||||
const InheritFields = (props) => {
|
const InheritFields = (props) => {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { getInterface } = useCollectionManager_deprecated();
|
const { getInterface, interfaces } = useCollectionManager_deprecated();
|
||||||
const { targetKey } = props.collectionResource || {};
|
const { targetKey } = props.collectionResource || {};
|
||||||
const parentRecord = useRecord();
|
const parentRecord = useRecord();
|
||||||
const [loadingRecord, setLoadingRecord] = React.useState(null);
|
const [loadingRecord, setLoadingRecord] = React.useState(null);
|
||||||
@ -371,29 +307,29 @@ const InheritFields = (props) => {
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: t('Field name'),
|
title: t('Field name'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
dataIndex: 'interface',
|
|
||||||
title: t('Field interface'),
|
|
||||||
render: (value) => <Tag>{compile(getInterface(value)?.title)}</Tag>,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
dataIndex: 'fieldType',
|
dataIndex: 'fieldType',
|
||||||
title: t('Field type'),
|
title: t('Field type'),
|
||||||
render: (value) => {
|
render: (value, record) => {
|
||||||
return value && Array.isArray(value) && value.length > 0 ? (
|
const currentValue: any = []; // [interface, fieldType, dataType]
|
||||||
|
if (record.interface) {
|
||||||
|
currentValue.push(record.interface);
|
||||||
|
if (value && Array.isArray(value) && value.length > 0) {
|
||||||
|
currentValue.push(...value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
<Cascader
|
<Cascader
|
||||||
value={value}
|
value={currentValue.length > 0 ? currentValue : undefined}
|
||||||
options={convertFieldTypeToOptions(value)}
|
options={createFieldTypeOptions(currentValue, getInterface, interfaces, compile)}
|
||||||
placeholder={t('Select field type')}
|
placeholder={t('Select field type')}
|
||||||
expandTrigger="hover"
|
expandTrigger="hover"
|
||||||
changeOnSelect={true}
|
changeOnSelect={false}
|
||||||
size="small"
|
size="small"
|
||||||
style={{ width: '100%', minWidth: 120 }}
|
style={{ width: '100%', minWidth: 150 }}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
open={false}
|
open={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<span style={{ color: '#ccc' }}>-</span>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -498,10 +434,6 @@ const CollectionFieldsInternal = () => {
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: t('Field name'),
|
title: t('Field name'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
dataIndex: 'interface',
|
|
||||||
title: t('Field interface'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
dataIndex: 'fieldType',
|
dataIndex: 'fieldType',
|
||||||
title: t('Field type'),
|
title: t('Field type'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user