feat(editor): 数据源字段选择器校验提示细化并支持 fieldConfig 类型校验

This commit is contained in:
roymondchen 2026-07-23 19:03:28 +08:00
parent 745f449396
commit fe9e754b50
4 changed files with 50 additions and 14 deletions

View File

@ -243,7 +243,7 @@ export const resolveFieldByPath = (
fields: DataSchema[] | undefined,
fieldNames: string[],
options: { skipNumberIndices?: boolean } = {},
): { ok: boolean; field?: DataSchema; fields: DataSchema[] } => {
): { ok: boolean; field?: DataSchema; fields: DataSchema[]; failedName?: string } => {
let currentFields = fields || [];
let field: DataSchema | undefined;
@ -252,11 +252,11 @@ export const resolveFieldByPath = (
continue;
}
if (!currentFields.length) {
return { ok: false, fields: currentFields };
return { ok: false, fields: currentFields, failedName: name };
}
field = currentFields.find((item) => item.name === name);
if (!field) {
return { ok: false, fields: currentFields };
return { ok: false, fields: currentFields, failedName: name };
}
currentFields = field.fields || [];
}

View File

@ -20,6 +20,7 @@ import type { DataSourceFieldType, DataSourceSchema, Id } from '@tmagic/core';
import { NodeType } from '@tmagic/core';
import { appendValidateSuggestion } from '@tmagic/design';
import type { TypeMatchValidateContext, TypeMatchValidator } from '@tmagic/form';
import { validateTypeMatch } from '@tmagic/form';
import {
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
DATA_SOURCE_SET_DATA_METHOD_NAME,
@ -261,16 +262,20 @@ const validateDataSourceFieldPath = (
const ds = findDataSource(`${dsId}`);
if (!ds) {
return defaultMessage(options.message, '值不在可选项中', dataSourceIdSuggestion());
return defaultMessage(options.message, `数据源(${dsId})不存在`, dataSourceIdSuggestion());
}
if (!fieldNames.length) {
return undefined;
}
const { field, ok } = resolveFieldByPath(ds.fields, fieldNames);
const { field, ok, fields, failedName } = resolveFieldByPath(ds.fields, fieldNames);
if (!ok) {
return defaultMessage(options.message, '值不在可选项中', dataSourceIdSuggestion());
return defaultMessage(
options.message,
`数据源字段(${failedName})不存在`,
listSuggestion(fields.map((item) => item.name)),
);
}
const allowedTypes = options.dataSourceFieldType || ['any'];
@ -471,11 +476,12 @@ const isDataSourceFieldPathValue = (value: any, config: any): value is string[]
return `${value[0]}`.startsWith(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);
};
const validateDataSourceFieldSelect: TypeMatchValidator = (value, { message, props }) => {
const validateDataSourceFieldSelect: TypeMatchValidator = (value, { mForm, message, props }) => {
const config = props.config || {};
if (config.fieldConfig && !isDataSourceFieldPathValue(value, config)) {
return undefined;
// 值不是数据源字段路径时,按 fieldConfig 的类型校验(与表单项自身 typeMatch 行为一致)
return validateTypeMatch(value, mForm, { ...props, config: { name: config.name, ...config.fieldConfig } }, message);
}
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {

View File

@ -105,8 +105,8 @@ describe('data-source utils', () => {
fields: [],
});
expect(resolveFieldByPath(fields, ['obj']).field?.name).toBe('obj');
expect(resolveFieldByPath(fields, ['unknown'])).toEqual({ ok: false, fields });
expect(resolveFieldByPath(undefined, ['x'])).toEqual({ ok: false, fields: [] });
expect(resolveFieldByPath(fields, ['unknown'])).toEqual({ ok: false, fields, failedName: 'unknown' });
expect(resolveFieldByPath(undefined, ['x'])).toEqual({ ok: false, fields: [], failedName: 'x' });
expect(resolveFieldByPath(fields, []).ok).toBe(true);
});
@ -124,7 +124,9 @@ describe('data-source utils', () => {
expect(result.field?.name).toBe('item');
expect(result.fields).toEqual([{ name: 'n', type: 'string' }]);
expect(resolveFieldByPath(fields, ['arr', '0', 'missing'], { skipNumberIndices: true }).ok).toBe(false);
const failed = resolveFieldByPath(fields, ['arr', '0', 'missing'], { skipNumberIndices: true });
expect(failed.ok).toBe(false);
expect(failed.failedName).toBe('missing');
});
test('getFieldType 沿 path 取最终类型', () => {

View File

@ -207,9 +207,18 @@ describe('editorTypeMatchRules', () => {
expect(firstLine(run('data-source-method-select', ['missing', 'custom']))).toBe('数据源(missing)不存在');
});
test('data-source-field-select 校验路径与 fieldConfig 跳过', () => {
test('data-source-field-select 校验路径与 fieldConfig 类型校验', () => {
expect(firstLine(run('data-source-field-select', 'x'))).toBe('x类型应为字符串数组');
expect(run('data-source-field-select', 'text-value', { fieldConfig: { type: 'text' } })).toBeUndefined();
// 有 fieldConfig 且值不是数据源字段路径时,按 fieldConfig 的类型校验text 允许数字)
expect(run('data-source-field-select', 'text-value', { name: 'f', fieldConfig: { type: 'text' } })).toBeUndefined();
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: { type: 'text' } })).toBeUndefined();
expect(firstLine(run('data-source-field-select', { a: 1 }, { name: 'f', fieldConfig: { type: 'text' } }))).toBe(
'[object Object] 类型应为字符串',
);
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: { type: 'number' } })).toBeUndefined();
// fieldConfig 未声明 type 时不做类型校验
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: {} })).toBeUndefined();
dataSourcesState.value = [
{
@ -225,7 +234,26 @@ describe('editorTypeMatchRules', () => {
expect(run('data-source-field-select', ['ds1', 'title'], { value: 'key' })).toBeUndefined();
expect(run('data-source-field-select', [`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}ds1`, 'title'])).toBeUndefined();
expect(firstLine(run('data-source-field-select', ['ds1', 'missing'], { value: 'key' }))).toBe('值不在可选项中');
// 数据源不存在时直出具体的数据源 id
expect(firstLine(run('data-source-field-select', ['missing', 'title'], { value: 'key' }))).toBe(
'数据源(missing)不存在',
);
expect(firstLine(run('data-source-field-select', ['title'], { dataSourceId: 'missing' }))).toBe(
'数据源(missing)不存在',
);
// 字段路径解析失败时直出具体失败的字段名
expect(firstLine(run('data-source-field-select', ['ds1', 'missing'], { value: 'key' }))).toBe(
'数据源字段(missing)不存在',
);
// 字段路径解析失败时,建议列出失败层级的可用字段名而非数据源 id
expect(run('data-source-field-select', ['ds1', 'missing'], { value: 'key' })).toBe(
'数据源字段(missing)不存在\n\n请使用以下某一个值"title""obj"',
);
expect(run('data-source-field-select', ['ds1', 'obj', 'missing'], { value: 'key' })).toBe(
'数据源字段(missing)不存在\n\n请使用以下某一个值"a"',
);
// 父字段无子字段时无建议
expect(run('data-source-field-select', ['ds1', 'title', 'x'], { value: 'key' })).toBe('数据源字段(x)不存在');
expect(
firstLine(
run('data-source-field-select', ['title'], {