roymondchen cbc4b25072 feat(editor): 字段对比模式逐项展示差异并补充历史记录面板文档
- CodeSelect/CodeSelectCol/EventSelect/DataSource 等复合字段在对比模式下
  按索引对齐前后值,逐项展示新增/删除/修改高亮,并隐藏写操作按钮
- form 容器/列表/表格支持对比模式只读展示
- 新增「历史记录面板」指南文档,完善表单对比文档及 menu props 说明
- 补充相关单元测试

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 15:51:47 +08:00

147 lines
4.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="m-fields-code-select" :class="config.className">
<TMagicCard>
<MContainer
:config="codeConfig"
:size="size"
:prop="prop"
:disabled="disabled"
:is-compare="isCompareMode"
:last-values="lastValues?.[name]"
:model="model[name]"
@change="changeHandler"
>
</MContainer>
</TMagicCard>
</div>
</template>
<script lang="ts" setup>
import { computed, watch } from 'vue';
import { isEmpty } from 'lodash-es';
import { HookCodeType, HookType } from '@tmagic/core';
import { TMagicCard } from '@tmagic/design';
import type { CodeSelectConfig, ContainerChangeEventData, FieldProps, GroupListConfig } from '@tmagic/form';
import { MContainer } from '@tmagic/form';
import { useServices } from '@editor/hooks/use-services';
defineOptions({
name: 'MFieldsCodeSelect',
});
const emit = defineEmits<{
change: [v: any, eventData: ContainerChangeEventData];
}>();
const { dataSourceService, codeBlockService } = useServices();
const props = withDefaults(defineProps<FieldProps<CodeSelectConfig>>(), {});
/**
* 对比模式判定:
*
* code-select 仅是对内部「钩子列表」group-list 的包裹,本身不渲染叶子字段。父级 `MFormContainer`
* 已将其归入「自接管对比字段」(见 Container.vue 的 `SELF_DIFF_FIELD_TYPES`),即对比时只渲染一次
* 本组件,并把当前值 `model` 与历史值 `lastValues` 一并传入,由本组件把 `is-compare`/`lastValues`
* 透传给内部 MContainer再由 group-list / code-select-col 等子级逐项展示前后差异。
*
* 注意:`model` 传入的是 `model[name]`(钩子值本身),因此 `lastValues` 也必须同层取 `lastValues[name]`
* 否则前后值的嵌套层级不一致会导致对比错位。
*
* 仅当存在历史值时才启用对比,避免 lastValues 缺失时退化为「全部新增」的空对比。
*/
const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues));
const codeConfig = computed<GroupListConfig>(() => ({
type: 'group-list',
name: 'hookData',
enableToggleMode: false,
expandAll: true,
title: (mForm, { model, index }: any) => {
if (model.codeType === HookCodeType.DATA_SOURCE_METHOD) {
if (Array.isArray(model.codeId)) {
if (model.codeId.length < 2) {
return index;
}
const ds = dataSourceService.getDataSourceById(model.codeId[0]);
return `${ds?.title} / ${model.codeId[1]}`;
}
return Array.isArray(model.codeId) ? model.codeId.join('/') : index;
}
const codeContent = codeBlockService.getCodeContentById(model.codeId);
if (codeContent) {
return codeContent.name;
}
return model.codeId || index;
},
items: [
{
type: 'row',
items: [
{
type: 'select',
name: 'codeType',
span: 6,
options: [
{ value: HookCodeType.CODE, text: '代码块' },
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
],
defaultValue: 'code',
onChange: (_mForm, v: HookCodeType, { setModel }) => {
if (v === HookCodeType.DATA_SOURCE_METHOD) {
setModel('codeId', []);
} else {
setModel('codeId', '');
}
return v;
},
},
{
type: 'code-select-col',
name: 'codeId',
span: 18,
labelWidth: 0,
display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
notEditable: () => !codeBlockService.getEditStatus(),
},
{
type: 'data-source-method-select',
name: 'codeId',
span: 18,
labelWidth: 0,
display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
notEditable: () => !dataSourceService.get('editable'),
},
],
},
],
}));
watch(
() => props.model[props.name],
(value) => {
// 兼容旧的数据结构
if (isEmpty(value)) {
// 空值或者空数组
props.model[props.name] = {
hookType: HookType.CODE,
hookData: [],
};
}
},
{
immediate: true,
},
);
const changeHandler = (v: any, eventData: ContainerChangeEventData) => emit('change', v, eventData);
</script>