fix(editor): 显示条件中选中的字段类型发生时,值对应做类型转换
This commit is contained in:
@@ -33,7 +33,7 @@ import { getDesignConfig, TMagicSelect } from '@tmagic/design';
|
|||||||
import type { CondOpSelectConfig, FieldProps } from '@tmagic/form';
|
import type { CondOpSelectConfig, FieldProps } from '@tmagic/form';
|
||||||
|
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import { arrayOptions, eqOptions, numberOptions } from '@editor/utils';
|
import { arrayOptions, eqOptions, getFieldType, numberOptions } from '@editor/utils';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFieldsCondOpSelect',
|
name: 'MFieldsCondOpSelect',
|
||||||
@@ -54,19 +54,13 @@ const options = computed(() => {
|
|||||||
|
|
||||||
const ds = dataSourceService.getDataSourceById(id);
|
const ds = dataSourceService.getDataSourceById(id);
|
||||||
|
|
||||||
let fields = ds?.fields || [];
|
const type = getFieldType(ds, fieldNames);
|
||||||
let type = '';
|
|
||||||
(fieldNames || []).forEach((fieldName: string) => {
|
|
||||||
const field = fields.find((f) => f.name === fieldName);
|
|
||||||
fields = field?.fields || [];
|
|
||||||
type = field?.type || '';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type === 'array') {
|
if (type === 'array') {
|
||||||
return arrayOptions;
|
return arrayOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'boolean') {
|
if (type === 'boolean' || type === 'null') {
|
||||||
return [
|
return [
|
||||||
{ text: '是', value: 'is' },
|
{ text: '是', value: 'is' },
|
||||||
{ text: '不是', value: 'not' },
|
{ text: '不是', value: 'not' },
|
||||||
|
|||||||
@@ -72,12 +72,12 @@ import { Edit, View } from '@element-plus/icons-vue';
|
|||||||
import type { DataSourceFieldType } from '@tmagic/core';
|
import type { DataSourceFieldType } from '@tmagic/core';
|
||||||
import { getDesignConfig, TMagicButton, TMagicCascader, TMagicSelect, TMagicTooltip } from '@tmagic/design';
|
import { getDesignConfig, TMagicButton, TMagicCascader, TMagicSelect, TMagicTooltip } from '@tmagic/design';
|
||||||
import { type FilterFunction, filterFunction, type FormState, type SelectOption } from '@tmagic/form';
|
import { type FilterFunction, filterFunction, type FormState, type SelectOption } from '@tmagic/form';
|
||||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, removeDataSourceFieldPrefix } from '@tmagic/utils';
|
||||||
|
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import { type EventBus, SideItemKey } from '@editor/type';
|
import { type EventBus, SideItemKey } from '@editor/type';
|
||||||
import { getCascaderOptionsFromFields, removeDataSourceFieldPrefix } from '@editor/utils';
|
import { getCascaderOptionsFromFields } from '@editor/utils';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,11 +48,10 @@ import { Coin } from '@element-plus/icons-vue';
|
|||||||
import { DataSchema } from '@tmagic/core';
|
import { DataSchema } from '@tmagic/core';
|
||||||
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
||||||
import type { ContainerChangeEventData, DataSourceFieldSelectConfig, FieldProps, FormState } from '@tmagic/form';
|
import type { ContainerChangeEventData, DataSourceFieldSelectConfig, FieldProps, FormState } from '@tmagic/form';
|
||||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, removeDataSourceFieldPrefix } from '@tmagic/utils';
|
||||||
|
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import { removeDataSourceFieldPrefix } from '@editor/utils';
|
|
||||||
|
|
||||||
import FieldSelect from './FieldSelect.vue';
|
import FieldSelect from './FieldSelect.vue';
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
} from '@tmagic/form';
|
} from '@tmagic/form';
|
||||||
|
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import { getCascaderOptionsFromFields } from '@editor/utils';
|
import { getCascaderOptionsFromFields, getFieldType } from '@editor/utils';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'm-fields-display-conds',
|
name: 'm-fields-display-conds',
|
||||||
@@ -46,6 +46,22 @@ const mForm = inject<FormState | undefined>('mForm');
|
|||||||
|
|
||||||
const parentFields = computed(() => filterFunction<string[]>(mForm, props.config.parentFields, props) || []);
|
const parentFields = computed(() => filterFunction<string[]>(mForm, props.config.parentFields, props) || []);
|
||||||
|
|
||||||
|
const fieldOnChange = (_formState: FormState | undefined, v: string[], { model }: { model: Record<string, any> }) => {
|
||||||
|
const [id, ...fieldNames] = [...parentFields.value, ...v];
|
||||||
|
const ds = dataSourceService.getDataSourceById(id);
|
||||||
|
const type = getFieldType(ds, fieldNames);
|
||||||
|
if (type === 'number') {
|
||||||
|
model.value = Number(model.value);
|
||||||
|
} else if (type === 'boolean') {
|
||||||
|
model.value = Boolean(model.value);
|
||||||
|
} else if (type === 'null') {
|
||||||
|
model.value = null;
|
||||||
|
} else {
|
||||||
|
model.value = `${model.value}`;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
|
||||||
const config = computed<GroupListConfig>(() => ({
|
const config = computed<GroupListConfig>(() => ({
|
||||||
type: 'groupList',
|
type: 'groupList',
|
||||||
name: props.name,
|
name: props.name,
|
||||||
@@ -80,6 +96,7 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
value: 'key',
|
value: 'key',
|
||||||
label: '字段',
|
label: '字段',
|
||||||
checkStrictly: false,
|
checkStrictly: false,
|
||||||
|
onChange: fieldOnChange,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type: 'data-source-field-select',
|
type: 'data-source-field-select',
|
||||||
@@ -88,6 +105,7 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
label: '字段',
|
label: '字段',
|
||||||
checkStrictly: false,
|
checkStrictly: false,
|
||||||
dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
|
dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
|
||||||
|
onChange: fieldOnChange,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'cond-op-select',
|
type: 'cond-op-select',
|
||||||
@@ -102,18 +120,10 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: 'value',
|
name: 'value',
|
||||||
type: (mForm, { model }) => {
|
type: (_mForm, { model }) => {
|
||||||
const [id, ...fieldNames] = [...parentFields.value, ...model.field];
|
const [id, ...fieldNames] = [...parentFields.value, ...model.field];
|
||||||
|
|
||||||
const ds = dataSourceService.getDataSourceById(id);
|
const ds = dataSourceService.getDataSourceById(id);
|
||||||
|
const type = getFieldType(ds, fieldNames);
|
||||||
let fields = ds?.fields || [];
|
|
||||||
let type = '';
|
|
||||||
(fieldNames || []).forEach((fieldName: string) => {
|
|
||||||
const field = fields.find((f) => f.name === fieldName);
|
|
||||||
fields = field?.fields || [];
|
|
||||||
type = field?.type || '';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (type === 'number') {
|
if (type === 'number') {
|
||||||
return 'number';
|
return 'number';
|
||||||
@@ -123,13 +133,23 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
return 'select';
|
return 'select';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'null') {
|
||||||
|
return 'display';
|
||||||
|
}
|
||||||
|
|
||||||
return 'text';
|
return 'text';
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{ text: 'true', value: true },
|
{ text: 'true', value: true },
|
||||||
{ text: 'false', value: false },
|
{ text: 'false', value: false },
|
||||||
],
|
],
|
||||||
display: (vm, { model }) => !['between', 'not_between'].includes(model.op),
|
display: (_mForm, { model }) => !['between', 'not_between'].includes(model.op),
|
||||||
|
displayText: (_mForm: FormState | undefined, { model }: any) => {
|
||||||
|
if (model.value === null) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
return model.value;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'range',
|
name: 'range',
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/core';
|
import { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/core';
|
||||||
import { CascaderOption, FormConfig, FormState } from '@tmagic/form';
|
import { CascaderOption, FormConfig, FormState } from '@tmagic/form';
|
||||||
import {
|
import { dataSourceTemplateRegExp, getKeysArray, isNumber } from '@tmagic/utils';
|
||||||
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
|
|
||||||
dataSourceTemplateRegExp,
|
|
||||||
getKeysArray,
|
|
||||||
isNumber,
|
|
||||||
} from '@tmagic/utils';
|
|
||||||
|
|
||||||
import BaseFormConfig from './formConfigs/base';
|
import BaseFormConfig from './formConfigs/base';
|
||||||
import HttpFormConfig from './formConfigs/http';
|
import HttpFormConfig from './formConfigs/http';
|
||||||
@@ -211,41 +206,44 @@ export const getCascaderOptionsFromFields = (
|
|||||||
fields: DataSchema[] = [],
|
fields: DataSchema[] = [],
|
||||||
dataSourceFieldType: DataSourceFieldType[] = ['any'],
|
dataSourceFieldType: DataSourceFieldType[] = ['any'],
|
||||||
): CascaderOption[] => {
|
): CascaderOption[] => {
|
||||||
const child: CascaderOption[] = [];
|
const typeSet = new Set(dataSourceFieldType.length ? dataSourceFieldType : ['any']);
|
||||||
fields.forEach((field) => {
|
const includesAny = typeSet.has('any');
|
||||||
if (!dataSourceFieldType.length) {
|
|
||||||
dataSourceFieldType.push('any');
|
|
||||||
}
|
|
||||||
|
|
||||||
let children: CascaderOption[] = [];
|
const result: CascaderOption[] = [];
|
||||||
if (field.type && ['any', 'array', 'object'].includes(field.type)) {
|
|
||||||
children = getCascaderOptionsFromFields(field.fields, dataSourceFieldType);
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = {
|
for (const field of fields) {
|
||||||
|
const fieldType = field.type || 'any';
|
||||||
|
const isContainerType = fieldType === 'any' || fieldType === 'array' || fieldType === 'object';
|
||||||
|
|
||||||
|
const children = isContainerType ? getCascaderOptionsFromFields(field.fields, dataSourceFieldType) : [];
|
||||||
|
|
||||||
|
const matchesType = includesAny || typeSet.has(fieldType);
|
||||||
|
|
||||||
|
if (matchesType || (isContainerType && children.length)) {
|
||||||
|
result.push({
|
||||||
label: `${field.title || field.name}(${field.type})`,
|
label: `${field.title || field.name}(${field.type})`,
|
||||||
value: field.name,
|
value: field.name,
|
||||||
children,
|
children,
|
||||||
};
|
|
||||||
|
|
||||||
const fieldType = field.type || 'any';
|
|
||||||
if (dataSourceFieldType.includes('any') || dataSourceFieldType.includes(fieldType)) {
|
|
||||||
child.push(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dataSourceFieldType.includes(fieldType) && !['array', 'object', 'any'].includes(fieldType)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!children.length && ['object', 'array', 'any'].includes(field.type || '')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
child.push(item);
|
|
||||||
});
|
});
|
||||||
return child;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeDataSourceFieldPrefix = (id?: string) =>
|
export const getFieldType = (ds: DataSourceSchema | undefined, fieldNames: string[]) => {
|
||||||
id?.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, '') || '';
|
let fields = ds?.fields;
|
||||||
|
let type = '';
|
||||||
|
|
||||||
|
for (const fieldName of fieldNames) {
|
||||||
|
if (!fields?.length) return '';
|
||||||
|
|
||||||
|
const field = fields.find((f) => f.name === fieldName);
|
||||||
|
if (!field) return '';
|
||||||
|
|
||||||
|
type = field.type || '';
|
||||||
|
fields = field.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
};
|
||||||
|
|||||||
293
packages/editor/tests/unit/utils/data-source.spec.ts
Normal file
293
packages/editor/tests/unit/utils/data-source.spec.ts
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { DataSchema, DataSourceSchema } from '@tmagic/core';
|
||||||
|
|
||||||
|
import { getCascaderOptionsFromFields, getFieldType } from '@editor/utils/data-source';
|
||||||
|
|
||||||
|
describe('getFieldType', () => {
|
||||||
|
test('返回空字符串当ds为undefined', () => {
|
||||||
|
const type = getFieldType(undefined, ['field1']);
|
||||||
|
expect(type).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回空字符串当ds.fields为空', () => {
|
||||||
|
const ds: DataSourceSchema = { id: 'ds1', type: 'base', fields: [], methods: [], events: [] };
|
||||||
|
const type = getFieldType(ds, ['field1']);
|
||||||
|
expect(type).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回空字符串当fieldNames为空数组', () => {
|
||||||
|
const ds: DataSourceSchema = {
|
||||||
|
id: 'ds1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'field1', type: 'string' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
const type = getFieldType(ds, []);
|
||||||
|
expect(type).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回一级字段类型', () => {
|
||||||
|
const ds: DataSourceSchema = {
|
||||||
|
id: 'ds1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [
|
||||||
|
{ name: 'field1', type: 'string' },
|
||||||
|
{ name: 'field2', type: 'number' },
|
||||||
|
{ name: 'field3', type: 'boolean' },
|
||||||
|
],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFieldType(ds, ['field1'])).toBe('string');
|
||||||
|
expect(getFieldType(ds, ['field2'])).toBe('number');
|
||||||
|
expect(getFieldType(ds, ['field3'])).toBe('boolean');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回嵌套字段类型', () => {
|
||||||
|
const ds: DataSourceSchema = {
|
||||||
|
id: 'ds1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'obj',
|
||||||
|
type: 'object',
|
||||||
|
fields: [
|
||||||
|
{ name: 'nested1', type: 'string' },
|
||||||
|
{ name: 'nested2', type: 'number' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFieldType(ds, ['obj', 'nested1'])).toBe('string');
|
||||||
|
expect(getFieldType(ds, ['obj', 'nested2'])).toBe('number');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回深层嵌套字段类型', () => {
|
||||||
|
const ds: DataSourceSchema = {
|
||||||
|
id: 'ds1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'level1',
|
||||||
|
type: 'object',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'level2',
|
||||||
|
type: 'object',
|
||||||
|
fields: [{ name: 'level3', type: 'boolean' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFieldType(ds, ['level1', 'level2', 'level3'])).toBe('boolean');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回空字符串当字段不存在', () => {
|
||||||
|
const ds: DataSourceSchema = {
|
||||||
|
id: 'ds1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'field1', type: 'string' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFieldType(ds, ['nonexistent'])).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回空字符串当嵌套字段不存在', () => {
|
||||||
|
const ds: DataSourceSchema = {
|
||||||
|
id: 'ds1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'obj',
|
||||||
|
type: 'object',
|
||||||
|
fields: [{ name: 'nested1', type: 'string' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFieldType(ds, ['obj', 'nonexistent'])).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回空字符串当字段type未定义', () => {
|
||||||
|
const ds: DataSourceSchema = {
|
||||||
|
id: 'ds1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'field1' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFieldType(ds, ['field1'])).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCascaderOptionsFromFields', () => {
|
||||||
|
test('返回空数组当fields为空', () => {
|
||||||
|
const result = getCascaderOptionsFromFields([]);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回空数组当fields为undefined', () => {
|
||||||
|
const result = getCascaderOptionsFromFields(undefined);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('返回基本字段选项(默认any类型过滤)', () => {
|
||||||
|
const fields: DataSchema[] = [
|
||||||
|
{ name: 'field1', type: 'string' },
|
||||||
|
{ name: 'field2', type: 'number' },
|
||||||
|
];
|
||||||
|
const result = getCascaderOptionsFromFields(fields);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
|
expect(result[0]).toEqual({
|
||||||
|
label: 'field1(string)',
|
||||||
|
value: 'field1',
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
expect(result[1]).toEqual({
|
||||||
|
label: 'field2(number)',
|
||||||
|
value: 'field2',
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('使用title作为label(如果存在)', () => {
|
||||||
|
const fields: DataSchema[] = [{ name: 'field1', title: '字段1', type: 'string' }];
|
||||||
|
const result = getCascaderOptionsFromFields(fields);
|
||||||
|
|
||||||
|
expect(result[0].label).toBe('字段1(string)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('按类型过滤字段', () => {
|
||||||
|
const fields: DataSchema[] = [
|
||||||
|
{ name: 'field1', type: 'string' },
|
||||||
|
{ name: 'field2', type: 'number' },
|
||||||
|
{ name: 'field3', type: 'boolean' },
|
||||||
|
];
|
||||||
|
const result = getCascaderOptionsFromFields(fields, ['string', 'number']);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
|
expect(result.map((r) => r.value)).toEqual(['field1', 'field2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('递归处理嵌套object字段', () => {
|
||||||
|
const fields: DataSchema[] = [
|
||||||
|
{
|
||||||
|
name: 'obj',
|
||||||
|
type: 'object',
|
||||||
|
fields: [
|
||||||
|
{ name: 'nested1', type: 'string' },
|
||||||
|
{ name: 'nested2', type: 'number' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = getCascaderOptionsFromFields(fields);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].children).toHaveLength(2);
|
||||||
|
expect(result[0].children![0]).toEqual({
|
||||||
|
label: 'nested1(string)',
|
||||||
|
value: 'nested1',
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('递归处理嵌套array字段', () => {
|
||||||
|
const fields: DataSchema[] = [
|
||||||
|
{
|
||||||
|
name: 'arr',
|
||||||
|
type: 'array',
|
||||||
|
fields: [{ name: 'item', type: 'string' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = getCascaderOptionsFromFields(fields);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].children).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('过滤掉不匹配类型且无子项的object/array字段', () => {
|
||||||
|
const fields: DataSchema[] = [
|
||||||
|
{ name: 'obj', type: 'object', fields: [] },
|
||||||
|
{ name: 'str', type: 'string' },
|
||||||
|
];
|
||||||
|
const result = getCascaderOptionsFromFields(fields, ['string']);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].value).toBe('str');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('保留有匹配子项的object字段', () => {
|
||||||
|
const fields: DataSchema[] = [
|
||||||
|
{
|
||||||
|
name: 'obj',
|
||||||
|
type: 'object',
|
||||||
|
fields: [{ name: 'nested', type: 'string' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = getCascaderOptionsFromFields(fields, ['string']);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].value).toBe('obj');
|
||||||
|
expect(result[0].children).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('深层嵌套字段', () => {
|
||||||
|
const fields: DataSchema[] = [
|
||||||
|
{
|
||||||
|
name: 'level1',
|
||||||
|
type: 'object',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'level2',
|
||||||
|
type: 'object',
|
||||||
|
fields: [{ name: 'level3', type: 'string' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = getCascaderOptionsFromFields(fields);
|
||||||
|
|
||||||
|
expect(result[0].children![0].children![0].value).toBe('level3');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('字段type未定义时视为any', () => {
|
||||||
|
const fields: DataSchema[] = [{ name: 'field1' }];
|
||||||
|
const result = getCascaderOptionsFromFields(fields);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].label).toBe('field1(undefined)');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -344,6 +344,7 @@ export interface HtmlField extends FormItem {
|
|||||||
export interface DisplayConfig extends FormItem {
|
export interface DisplayConfig extends FormItem {
|
||||||
type: 'display';
|
type: 'display';
|
||||||
initValue?: string | number | boolean;
|
initValue?: string | number | boolean;
|
||||||
|
displayText: FilterFunction<string> | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 文本输入框 */
|
/** 文本输入框 */
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<span v-if="model">{{ model[name] }}</span>
|
<span v-if="model">{{ text }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DisplayConfig, FieldProps } from '../schema';
|
import { computed, inject } from 'vue';
|
||||||
|
|
||||||
|
import type { DisplayConfig, FieldProps, FormState } from '../schema';
|
||||||
|
import { filterFunction } from '../utils/form';
|
||||||
import { useAddField } from '../utils/useAddField';
|
import { useAddField } from '../utils/useAddField';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -12,9 +15,19 @@ defineOptions({
|
|||||||
|
|
||||||
const props = defineProps<FieldProps<DisplayConfig>>();
|
const props = defineProps<FieldProps<DisplayConfig>>();
|
||||||
|
|
||||||
|
const mForm = inject<FormState | undefined>('mForm');
|
||||||
|
|
||||||
if (props.config.initValue && props.model) {
|
if (props.config.initValue && props.model) {
|
||||||
props.model[props.name] = props.config.initValue;
|
props.model[props.name] = props.config.initValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const text = computed(() => {
|
||||||
|
if (props.config.displayText) {
|
||||||
|
return filterFunction<string>(mForm, props.config.displayText, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.model[props.name];
|
||||||
|
});
|
||||||
|
|
||||||
useAddField(props.prop);
|
useAddField(props.prop);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -635,3 +635,6 @@ export const isValueIncludeDataSource = (value: any) => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeDataSourceFieldPrefix = (id?: string) =>
|
||||||
|
id?.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, '') || '';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, NODE_CONDS_KEY } from '@tmagic/core';
|
import { NODE_CONDS_KEY, removeDataSourceFieldPrefix } from '@tmagic/core';
|
||||||
import { defineFormConfig } from '@tmagic/form-schema';
|
import { defineFormConfig } from '@tmagic/form-schema';
|
||||||
|
|
||||||
export default defineFormConfig([
|
export default defineFormConfig([
|
||||||
@@ -29,7 +29,7 @@ export default defineFormConfig([
|
|||||||
onChange: (_vm: any, v: string[] = [], { setModel }: any) => {
|
onChange: (_vm: any, v: string[] = [], { setModel }: any) => {
|
||||||
if (Array.isArray(v) && v.length > 1) {
|
if (Array.isArray(v) && v.length > 1) {
|
||||||
const [dsId, ...keys] = v;
|
const [dsId, ...keys] = v;
|
||||||
setModel('dsField', [dsId.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, ''), ...keys]);
|
setModel('dsField', [removeDataSourceFieldPrefix(dsId), ...keys]);
|
||||||
} else {
|
} else {
|
||||||
setModel('dsField', []);
|
setModel('dsField', []);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, NODE_CONDS_KEY } from '@tmagic/core';
|
import { NODE_CONDS_KEY, removeDataSourceFieldPrefix } from '@tmagic/core';
|
||||||
import { defineFormConfig } from '@tmagic/form-schema';
|
import { defineFormConfig } from '@tmagic/form-schema';
|
||||||
|
|
||||||
export default defineFormConfig([
|
export default defineFormConfig([
|
||||||
@@ -34,7 +34,7 @@ export default defineFormConfig([
|
|||||||
onChange: (_vm: any, v: string[] = [], { setModel }: any) => {
|
onChange: (_vm: any, v: string[] = [], { setModel }: any) => {
|
||||||
if (Array.isArray(v) && v.length > 1) {
|
if (Array.isArray(v) && v.length > 1) {
|
||||||
const [dsId, ...keys] = v;
|
const [dsId, ...keys] = v;
|
||||||
setModel('dsField', [dsId.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, ''), ...keys]);
|
setModel('dsField', [removeDataSourceFieldPrefix(dsId), ...keys]);
|
||||||
} else {
|
} else {
|
||||||
setModel('dsField', []);
|
setModel('dsField', []);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user