refactor(editor): 完善内置 typeMatch 校验规则并规范文件名

- 重命名 typeMatchRules.ts 为 type-match-rules.ts
- 为 event-select、display-conds 等字段补充结构校验规则
- advancedTabConfig 的 code-select 字段增加 hookType 校验
- 修复 update 未携带 invalidInfo 时清除对应节点全部来源错误
- 将 code-select/display-conds 字段 typeMatch 触发时机调整为 change
This commit is contained in:
roymondchen 2026-07-14 20:28:03 +08:00
parent 0e4669261f
commit ce43fface8
11 changed files with 66 additions and 42 deletions

View File

@ -93,7 +93,7 @@ const codeConfig = computed<GroupListConfig>(() => ({
{ value: HookCodeType.CODE, text: '代码块' },
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
],
rules: [{ typeMatch: true, trigger: 'blur' }],
rules: [{ typeMatch: true, trigger: 'change' }],
defaultValue: HookCodeType.CODE,
onChange: (_mForm, v: HookCodeType, { setModel }) => {
if (v === HookCodeType.DATA_SOURCE_METHOD) {
@ -112,7 +112,7 @@ const codeConfig = computed<GroupListConfig>(() => ({
labelWidth: 0,
display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
notEditable: () => !codeBlockService.getEditStatus(),
rules: [{ typeMatch: true, trigger: 'blur' }],
rules: [{ typeMatch: true, trigger: 'change' }],
},
{
type: 'data-source-method-select',
@ -121,7 +121,7 @@ const codeConfig = computed<GroupListConfig>(() => ({
labelWidth: 0,
display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
notEditable: () => !dataSourceService.get('editable'),
rules: [{ typeMatch: true }],
rules: [{ typeMatch: true, trigger: 'change' }],
},
],
},

View File

@ -98,9 +98,10 @@ const config = computed<GroupListConfig>(() => ({
label: '字段',
checkStrictly: false,
onChange: fieldOnChange,
defaultValue: () => [],
rules: [
{ required: true, trigger: 'blur', message: '请选择字段' },
{ typeMatch: true, trigger: 'blur' },
{ typeMatch: true, trigger: 'change' },
],
}
: {
@ -111,9 +112,10 @@ const config = computed<GroupListConfig>(() => ({
checkStrictly: false,
dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
onChange: fieldOnChange,
defaultValue: () => [],
rules: [
{ required: true, trigger: 'blur', message: '请选择字段' },
{ typeMatch: true, trigger: 'blur' },
{ typeMatch: true, trigger: 'change' },
],
},
{
@ -124,7 +126,7 @@ const config = computed<GroupListConfig>(() => ({
name: 'op',
rules: [
{ required: true, trigger: 'blur', message: '请选择条件' },
{ typeMatch: true, trigger: 'blur' },
{ typeMatch: true, trigger: 'change' },
],
},
{

View File

@ -167,7 +167,7 @@ const submit = async (
changeRecords: eventData?.changeRecords,
historySource,
// error editorService
// CodeEditor invalidInfo
// CodeEditor invalidInfo editorService update
...(enablePropsFormValidate && error ? { invalidInfo: { id: newValue.id, source, error: error?.message } } : {}),
});
} catch (e: any) {

View File

@ -44,7 +44,7 @@ import StyleSetter from './fields/StyleSetter/Index.vue';
import uiSelect from './fields/UISelect.vue';
import CodeEditor from './layouts/CodeEditor.vue';
import { setEditorConfig } from './utils/config';
import { editorTypeMatchRules } from './utils/typeMatchRules';
import { editorTypeMatchRules } from './utils/type-match-rules';
import Editor from './Editor.vue';
import type { EditorInstallOptions } from './type';

View File

@ -815,7 +815,7 @@ class Editor extends BaseService {
);
// 校验错误信息在 pushOpHistory 之前落库,保证历史快照包含本次变更对应的错误状态。
this.applyInvalidInfo(invalidInfo);
this.applyInvalidInfo(config, invalidInfo);
if (updateData[0].oldNode?.type !== NodeType.ROOT) {
const curNodes = this.get('nodes');
@ -1666,10 +1666,21 @@ class Editor extends BaseService {
/**
* 使
* error
* - invalidInfo
* - error
*/
private applyInvalidInfo(invalidInfo?: { id: Id; source: NodeInvalidSource; error?: string }) {
if (!invalidInfo) return;
private applyInvalidInfo(
config: MNode | MNode[],
invalidInfo?: { id: Id; source: NodeInvalidSource; error?: string },
) {
if (!invalidInfo) {
// 调用方未携带 invalidInfo本次更新对应节点不应再保留校验错误清除其全部来源。
const ids = (Array.isArray(config) ? config : [config])
.map((node) => node.id)
.filter((id): id is Id => id !== undefined);
ids.forEach((id) => this.deleteInvalidNode(id));
return;
}
const { id, source, error } = invalidInfo;
if (error) {
this.setInvalidNode(id, source, error);

View File

@ -30,6 +30,6 @@ export * from './undo-redo';
export * from './indexed-db';
export * from './history';
export * from './const';
export * from './typeMatchRules';
export * from './type-match-rules';
export * from './event';
export { default as loadMonaco } from './monaco-editor';

View File

@ -18,6 +18,7 @@
*/
import {
HookType,
NODE_CONDS_KEY,
NODE_CONDS_RESULT_KEY,
NODE_DISABLE_CODE_BLOCK_KEY,
@ -182,7 +183,17 @@ export const advancedTabConfig: TabPaneConfig = {
labelPosition: 'top',
type: 'code-select',
extra: '组件初始化时执行',
rules: [{ typeMatch: true }],
rules: [
{ typeMatch: true, trigger: 'change' },
{
validator: ({ value, callback }: any) => {
if (value && value.hookType !== HookType.CODE) {
return callback('hookType 必须是 code');
}
callback();
},
},
],
},
{
name: 'mounted',
@ -190,7 +201,17 @@ export const advancedTabConfig: TabPaneConfig = {
labelPosition: 'top',
type: 'code-select',
extra: '组件挂载到dom时执行',
rules: [{ typeMatch: true }],
rules: [
{ typeMatch: true, trigger: 'change' },
{
validator: ({ value, callback }: any) => {
if (value && value.hookType !== HookType.CODE) {
return callback('hookType 必须是 code');
}
callback();
},
},
],
},
{
name: 'display',
@ -198,7 +219,17 @@ export const advancedTabConfig: TabPaneConfig = {
extra: '控制组件是否渲染关系的代码块返回值为false时不渲染',
labelPosition: 'top',
type: 'code-select',
rules: [{ typeMatch: true }],
rules: [
{ typeMatch: true, trigger: 'change' },
{
validator: ({ value, callback }: any) => {
if (value && value.hookType !== HookType.CODE) {
return callback('hookType 必须是 code');
}
callback();
},
},
],
},
],
};

View File

@ -17,7 +17,7 @@
*/
import type { DataSourceFieldType, DataSourceSchema, Id } from '@tmagic/core';
import { HookCodeType, HookType, NodeType } from '@tmagic/core';
import { NodeType } from '@tmagic/core';
import type { TypeMatchValidateContext, TypeMatchValidator } from '@tmagic/form';
import {
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
@ -376,7 +376,7 @@ const validateDataSourceSelect: TypeMatchValidator = (value, { message, props })
};
const validateCodeSelect: TypeMatchValidator = (value, { message }) => {
if (!isPlainObject(value) || value.hookType !== HookType.CODE || !Array.isArray(value.hookData)) {
if (!isPlainObject(value) || !Array.isArray(value.hookData)) {
return defaultMessage(message, `${value}类型不合法`);
}
@ -387,29 +387,9 @@ const validateCodeSelect: TypeMatchValidator = (value, { message }) => {
return defaultMessage(message, '钩子项结构不合法');
}
if (item.codeType !== HookCodeType.CODE && item.codeType !== HookCodeType.DATA_SOURCE_METHOD) {
return defaultMessage(message, '钩子项结构不合法');
}
if (typeof item.params !== 'undefined' && !isPlainObject(item.params)) {
return defaultMessage(message, '钩子项结构不合法');
}
if (item.codeType === HookCodeType.CODE) {
if (typeof item.codeId !== 'string') {
return defaultMessage(message, '钩子项结构不合法');
}
continue;
}
// DATA_SOURCE_METHOD仅校验元组形态存在性交给单元格
if (
!Array.isArray(item.codeId) ||
item.codeId.length !== 2 ||
item.codeId.some((part: any) => typeof part !== 'string')
) {
return defaultMessage(message, '钩子项结构不合法');
}
}
return undefined;

View File

@ -77,7 +77,7 @@ describe('plugin install', () => {
test('install 调用 design/form/table 插件并注册全局组件', async () => {
const { registerTypeMatchRules } = await import('@tmagic/form');
const { editorTypeMatchRules } = await import('@editor/utils/typeMatchRules');
const { editorTypeMatchRules } = await import('@editor/utils/type-match-rules');
const { app, components } = buildApp();
editorPlugin.install(app, { someOption: true } as any);
expect(app.use).toHaveBeenCalledTimes(3);

View File

@ -1235,12 +1235,12 @@ describe('invalidNodeIds 校验错误状态', () => {
expect(editorService.getInvalidNodeInfo(NodeId.NODE_ID)).toBeUndefined();
});
test('update 未携带 invalidInfo 时不改动已有错误状态', async () => {
test('update 未携带 invalidInfo 时清除对应节点的全部来源错误', async () => {
await editorService.select(NodeId.PAGE_ID);
editorService.setInvalidNode(NodeId.NODE_ID, 'props', 'props 错误');
await editorService.update({ id: NodeId.NODE_ID, type: 'text', text: 'z' });
expect(editorService.getInvalidNodeInfo(NodeId.NODE_ID)).toEqual({ props: 'props 错误' });
expect(editorService.getInvalidNodeInfo(NodeId.NODE_ID)).toBeUndefined();
});
});

View File

@ -21,7 +21,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
import { HookCodeType, HookType, NodeType } from '@tmagic/core';
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, DATA_SOURCE_SET_DATA_METHOD_NAME } from '@tmagic/utils';
import { ALL_COND_OPS, editorTypeMatchRules } from '@editor/utils/typeMatchRules';
import { ALL_COND_OPS, editorTypeMatchRules } from '@editor/utils/type-match-rules';
const codeDslState = vi.hoisted(() => ({ value: null as Record<string, any> | null }));
const dataSourcesState = vi.hoisted(() => ({ value: [] as any[] }));