feat(editor): vs-code 字段对比模式改用 monaco diff 编辑器

- Container.vue 新增「自接管对比」字段类型白名单(当前含 vs-code),命中时只渲染一次组件并透传 model/lastValues/isCompare,由字段内部展示差异
- Code.vue 在 isCompare 模式下切换到 type='diff',使用 monaco 内置 diff 视图替代两个独立编辑器实例
- CodeEditor.vue 补充对 modifiedValues 的 watch,避免 diff 模式下右侧值停留在初始快照
This commit is contained in:
roymondchen 2026-05-28 20:12:46 +08:00
parent 59f4e0edac
commit c854dfa8bf
3 changed files with 138 additions and 41 deletions

View File

@ -1,11 +1,13 @@
<template>
<MagicCodeEditor
:height="config.height"
:init-values="model[name]"
:type="diffMode ? 'diff' : undefined"
:init-values="diffMode ? (lastValues || {})[name] : model[name]"
:modified-values="diffMode ? model[name] : undefined"
:language="config.language"
:options="{
...config.options,
readOnly: disabled,
readOnly: diffMode ? true : disabled,
}"
:autosize="config.autosize"
:parse="config.parse"
@ -15,6 +17,8 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import type { CodeConfig, FieldProps } from '@tmagic/form';
import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
@ -27,10 +31,22 @@ const emit = defineEmits<{
change: [value: string | any];
}>();
withDefaults(defineProps<FieldProps<CodeConfig>>(), {
const props = withDefaults(defineProps<FieldProps<CodeConfig>>(), {
disabled: false,
});
/**
* 对比模式判定
*
* - `isCompare === true` 由父级 `MFormContainer` 统一渲染一次本字段不再渲染前后两份独立组件
* 并把 `model`当前值 `lastValues`历史值一并传入
* - 此时本字段切换到 monaco 自带的 diff 编辑器左侧旧右侧新相比"两个独立 monaco 实例"更直观
* 也避免了同一表单内重复实例化重型编辑器带来的开销
*
* 仅当存在历史值lastValues且开启了对比模式时启用 diff避免在 lastValues 缺失时退化为空对比
*/
const diffMode = computed(() => Boolean(props.isCompare && props.lastValues));
const save = (v: string | any) => {
emit('change', v);
};

View File

@ -330,6 +330,21 @@ watch(
},
);
// diff ""modifiedValues lastValues model
//
watch(
() => props.modifiedValues,
(v, preV) => {
if (props.type !== 'diff') return;
if (v !== preV) {
setEditorValue(props.initValues, props.modifiedValues);
}
},
{
deep: true,
},
);
watch(
() => props.options,
(v) => {

View File

@ -73,10 +73,11 @@
<!-- 对比 -->
<template v-else-if="type && display && showDiff">
<!-- 上次内容 -->
<!-- 自接管对比的字段类型 vs-code只渲染一次组件由字段内部用 diff 编辑器/视图自行展示前后差异 -->
<TMagicFormItem
v-if="isSelfDiffField"
v-bind="formItemProps"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true, 'self-diff': true }"
>
<template #label>
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
@ -90,55 +91,105 @@
</slot>
</template>
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
<component v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
<component
v-bind="fieldsProps"
:is="tagName"
:model="model"
:last-values="lastValues"
:is-compare="isCompare"
@change="onChangeHandler"
></component>
<template #content>
<div v-html="tooltip.text"></div>
</template>
</TMagicTooltip>
<component v-else v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
<component
v-else
v-bind="fieldsProps"
:is="tagName"
:model="model"
:last-values="lastValues"
:is-compare="isCompare"
@change="onChangeHandler"
></component>
</TMagicFormItem>
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
<template #content>
<div v-html="config.tip"></div>
</template>
</TMagicTooltip>
<!-- 普通字段渲染前后两份独立的组件用于对比 -->
<template v-else>
<!-- 上次内容 -->
<TMagicFormItem
v-bind="formItemProps"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
>
<template #label>
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
<FormLabel
:tip="config.tip"
:type="type"
:use-label="(config as CheckboxConfig).useLabel"
:label-title="config.labelTitle"
:text="text"
></FormLabel>
</slot>
</template>
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
<component v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
<template #content>
<div v-html="tooltip.text"></div>
</template>
</TMagicTooltip>
<!-- 当前内容 -->
<TMagicFormItem
v-bind="formItemProps"
:style="config.tip ? 'flex: 1' : ''"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
>
<template #label>
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
<FormLabel
:tip="config.tip"
:type="type"
:use-label="(config as CheckboxConfig).useLabel"
:label-title="config.labelTitle"
:text="text"
></FormLabel>
</slot>
</template>
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
<component v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
<component
v-else
v-bind="fieldsProps"
:is="tagName"
:model="lastValues"
@change="onChangeHandler"
></component>
</TMagicFormItem>
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
<template #content>
<div v-html="tooltip.text"></div>
<div v-html="config.tip"></div>
</template>
</TMagicTooltip>
<component v-else v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
</TMagicFormItem>
<!-- 当前内容 -->
<TMagicFormItem
v-bind="formItemProps"
:style="config.tip ? 'flex: 1' : ''"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
>
<template #label>
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
<FormLabel
:tip="config.tip"
:type="type"
:use-label="(config as CheckboxConfig).useLabel"
:label-title="config.labelTitle"
:text="text"
></FormLabel>
</slot>
</template>
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
<component v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
<template #content>
<div v-html="tooltip.text"></div>
</template>
</TMagicTooltip>
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
<template #content>
<div v-html="config.tip"></div>
</template>
</TMagicTooltip>
<component v-else v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
</TMagicFormItem>
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
<template #content>
<div v-html="config.tip"></div>
</template>
</TMagicTooltip>
</template>
</template>
<template v-else-if="items && display">
@ -281,6 +332,21 @@ const tagName = computed(() => {
return getField(type.value || 'container') || `m-${items.value ? 'form' : 'fields'}-${type.value}`;
});
/**
* 自接管对比的字段类型白名单
*
* 这类字段在 `isCompare === true` 且存在差异时不再由 Container 渲染前后两份独立组件来对比
* 而是只渲染一次组件 `model` / `lastValues` / `isCompare` 一并传给字段组件
* 由字段组件内部自行展示前后差异典型场景vs-code 字段使用 monaco 自带的 diff 编辑器
*
* 这样做的好处
* 1. 避免重型字段 monaco 编辑器在对比模式下被实例化两次节省资源
* 2. 提供更专业的对比视觉效果 monaco diff 的行级高亮左右滚动同步等
*/
const SELF_DIFF_FIELD_TYPES = new Set(['vs-code']);
const isSelfDiffField = computed(() => SELF_DIFF_FIELD_TYPES.has(type.value));
const disabled = computed(() => props.disabled || filterFunction(mForm, props.config.disabled, props));
const text = computed(() => filterFunction(mForm, props.config.text, props));