tmagic-editor/packages/editor/src/fields/DataSourceMocks.vue
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

258 lines
6.6 KiB
Vue

<template>
<div class="m-editor-data-source-fields">
<MagicTable :data="model[name]" :columns="displayColumns"></MagicTable>
<div v-if="!isCompare" class="m-editor-data-source-fields-footer">
<TMagicButton size="small" type="primary" :disabled="disabled" plain @click="newHandler()">添加</TMagicButton>
</div>
<FloatingBox
v-model:visible="addDialogVisible"
v-model:width="width"
v-model:height="editorHeight"
:title="drawerTitle"
:position="boxPosition"
>
<template #body>
<MFormBox
label-width="120px"
:config="formConfig"
:values="formValues"
:parentValues="model[name]"
:disabled="disabled"
@submit="formChangeHandler"
></MFormBox>
</template>
</FloatingBox>
</div>
</template>
<script setup lang="ts">
import { computed, inject, Ref, ref } from 'vue';
import type { MockSchema } from '@tmagic/core';
import { TMagicButton, tMagicMessageBox, TMagicSwitch } from '@tmagic/design';
import { type DataSourceMocksConfig, type FieldProps, type FormConfig, type FormState, MFormBox } from '@tmagic/form';
import { type ColumnConfig, MagicTable } from '@tmagic/table';
import { getDefaultValueFromFields } from '@tmagic/utils';
import FloatingBox from '@editor/components/FloatingBox.vue';
import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-position';
import { useServices } from '@editor/hooks/use-services';
import CodeEditor from '@editor/layouts/CodeEditor.vue';
import { useEditorContentHeight } from '..';
defineOptions({
name: 'MFieldsDataSourceMocks',
});
const props = withDefaults(defineProps<FieldProps<DataSourceMocksConfig>>(), {
disabled: false,
});
const emit = defineEmits(['change']);
const { uiService } = useServices();
const mForm = inject<FormState | undefined>('mForm');
/** 对比模式下隐藏新增/编辑/删除等操作按钮,仅保留只读展示。 */
const isCompare = computed(() => Boolean(mForm?.isCompare));
const width = defineModel<number>('width', { default: 670 });
const drawerTitle = ref('');
const formValues = ref<Record<string, any>>({});
const formConfig: FormConfig = [
{ name: 'index', type: 'hidden', filter: 'number', defaultValue: -1 },
{
name: 'title',
text: '名称',
rules: [
{
required: true,
message: '请输入字段名称',
},
{
required: true,
message: '请输入名称',
},
],
},
{
name: 'description',
text: '描述',
},
{
name: 'enable',
text: '启用',
type: 'switch',
},
{
name: 'useInEditor',
text: '编辑器中使用',
type: 'switch',
},
{
name: 'data',
text: 'mock数据',
type: 'vs-code',
language: 'json',
options: inject('codeOptions', {}),
defaultValue: '{}',
autosize: { minRows: 30, maxRows: 50 },
onChange: (formState: FormState | undefined, v: string | any) => {
if (typeof v !== 'string') return v;
return JSON.parse(v);
},
rules: [
{
validator: ({ value, callback }) => {
if (typeof value !== 'string') return callback();
try {
// 检测json是否存在语法错误
JSON.parse(value);
callback();
} catch (error: any) {
callback(error);
}
},
},
],
},
];
const columns: ColumnConfig[] = [
{
type: 'expand',
component: CodeEditor,
props: (row: MockSchema) => ({
initValues: row.data,
language: 'json',
height: '150px',
options: {
readOnly: true,
},
}),
},
{
label: '名称',
prop: 'title',
},
{
label: '描述',
prop: 'description',
},
{
label: '是否启用',
prop: 'enable',
type: 'component',
component: TMagicSwitch,
props: (row: MockSchema) => ({
modelValue: row.enable,
activeValue: true,
inactiveValue: false,
}),
listeners: (row: MockSchema, index: number) => ({
'update:modelValue': (v: boolean) => {
toggleValue(row, 'enable', v, index);
},
}),
},
{
label: '编辑器中使用',
prop: 'useInEditor',
type: 'component',
component: TMagicSwitch,
props: (row: MockSchema) => ({
modelValue: row.useInEditor,
activeValue: true,
inactiveValue: false,
}),
listeners: (row: MockSchema, index: number) => ({
'update:modelValue': (v: boolean) => {
toggleValue(row, 'useInEditor', v, index);
},
}),
},
{
label: '操作',
fixed: 'right',
actions: [
{
text: '编辑',
handler: (row: MockSchema, index: number) => {
formValues.value = {
...row,
index,
};
drawerTitle.value = `编辑${row.title}`;
calcBoxPosition();
addDialogVisible.value = true;
},
},
{
text: '删除',
buttonType: 'danger',
handler: async (row: MockSchema, index: number) => {
await tMagicMessageBox.confirm(`确定删除${row.title}?`, '提示');
props.model[props.name].splice(index, 1);
emit('change', props.model[props.name]);
},
},
],
},
];
/** 对比模式下移除「操作」列(编辑/删除按钮),仅保留只读列。 */
const displayColumns = computed<ColumnConfig[]>(() =>
isCompare.value ? columns.filter((col) => !col.actions) : columns,
);
const newHandler = () => {
const isFirstRow = props.model[props.name].length === 0;
formValues.value = {
data: getDefaultValueFromFields(props.model.fields || []),
useInEditor: isFirstRow,
enable: isFirstRow,
};
drawerTitle.value = '新增Mock';
calcBoxPosition();
addDialogVisible.value = true;
};
const formChangeHandler = ({ index, ...value }: Record<string, any>) => {
if (index > -1) {
props.model[props.name][index] = value;
} else {
props.model[props.name].push(value);
}
addDialogVisible.value = false;
emit('change', props.model[props.name]);
};
const toggleValue = (row: MockSchema, key: 'enable' | 'useInEditor', value: boolean, index: number) => {
if (value) {
props.model[props.name].forEach((item: MockSchema) => {
item[key] = false;
});
}
formChangeHandler({
...row,
[key]: value,
index,
});
};
const addDialogVisible = defineModel<boolean>('visible', { default: false });
const { height: editorHeight } = useEditorContentHeight();
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
</script>