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

222 lines
6.5 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-editor-data-source-field-select">
<template v-if="dataSourceId">
<TMagicCascader
:model-value="selectFieldsId"
clearable
filterable
:size="size"
:disabled="disabled"
:options="fieldsOptions"
:props="{
checkStrictly,
}"
@change="fieldChangeHandler"
></TMagicCascader>
</template>
<template v-else-if="checkStrictly">
<TMagicSelect
:model-value="selectDataSourceId"
clearable
filterable
:size="size"
:disabled="disabled"
@change="dsChangeHandler"
>
<component
v-for="option in dataSourcesOptions"
class="tmagic-design-option"
:key="option.value"
:is="optionComponent?.component || 'el-option'"
v-bind="
optionComponent?.props({
label: option.text,
value: option.value,
disabled: option.disabled,
}) || {
label: option.text,
value: option.value,
disabled: option.disabled,
}
"
>
</component>
</TMagicSelect>
<TMagicCascader
:model-value="selectFieldsId"
clearable
filterable
:size="size"
:disabled="disabled"
:options="fieldsOptions"
:props="{
checkStrictly,
}"
@change="fieldChangeHandler"
></TMagicCascader>
</template>
<TMagicCascader
v-else
clearable
filterable
:model-value="modelValue"
:disabled="disabled"
:size="size"
:options="cascaderOptions"
:props="{
checkStrictly,
}"
@change="onChangeHandler"
></TMagicCascader>
<TMagicTooltip
v-if="selectDataSourceId && hasDataSourceSidePanel && !isCompare"
:content="notEditable ? '查看' : '编辑'"
>
<TMagicButton class="m-fields-select-action-button" :size="size" @click="editHandler(selectDataSourceId)"
><MIcon :icon="!notEditable ? Edit : View"></MIcon
></TMagicButton>
</TMagicTooltip>
</div>
</template>
<script lang="ts" setup>
import { computed, inject, ref, watch } from 'vue';
import { Edit, View } from '@element-plus/icons-vue';
import type { DataSourceFieldType } from '@tmagic/core';
import { getDesignConfig, TMagicButton, TMagicCascader, TMagicSelect, TMagicTooltip } from '@tmagic/design';
import { type FilterFunction, filterFunction, type FormState, type SelectOption } from '@tmagic/form';
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, removeDataSourceFieldPrefix } from '@tmagic/utils';
import MIcon from '@editor/components/Icon.vue';
import { useServices } from '@editor/hooks/use-services';
import { type EventBus, SideItemKey } from '@editor/type';
import { getCascaderOptionsFromFields } from '@editor/utils';
const props = defineProps<{
/**
* 是否要编译成数据源的data。
* key: 不编译就是要数据源id和field name;
* value: 要编译数据源data[`${filed}`]
* */
value?: 'key' | 'value';
disabled?: boolean;
checkStrictly?: boolean;
size?: 'large' | 'default' | 'small';
dataSourceFieldType?: DataSourceFieldType[];
/** 是否可以编辑数据源disable表示的是是否可以选择数据源 */
notEditable?: boolean | FilterFunction;
/** 指定数据源ID限定只能选择该数据源的字段 */
dataSourceId?: string;
}>();
const emit = defineEmits<{
change: [v: string[]];
}>();
const modelValue = defineModel<string[] | any>('modelValue', { default: [] });
const optionComponent = getDesignConfig('components')?.option;
const { dataSourceService, uiService } = useServices();
const mForm = inject<FormState | undefined>('mForm');
const eventBus = inject<EventBus>('eventBus');
const allDataSources = computed(() => dataSourceService.get('dataSources') || []);
const dataSources = computed(() => {
if (!props.dataSourceId) return allDataSources.value;
return allDataSources.value.filter((ds) => ds.id === props.dataSourceId);
});
const valueIsKey = computed(() => props.value === 'key');
const notEditable = computed(() => filterFunction(mForm, props.notEditable, props));
/** 对比模式下隐藏查看/编辑操作按钮,仅保留只读展示。 */
const isCompare = computed(() => Boolean(mForm?.isCompare));
const dataSourcesOptions = computed<SelectOption[]>(() =>
dataSources.value.map((ds) => ({
text: ds.title || ds.id,
value: valueIsKey.value ? ds.id : `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${ds.id}`,
})),
);
const selectDataSourceId = ref('');
const selectFieldsId = ref<string[]>([]);
watch(
modelValue,
(value) => {
if (props.dataSourceId) {
const dsIdValue = valueIsKey.value
? props.dataSourceId
: `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${props.dataSourceId}`;
selectDataSourceId.value = dsIdValue;
selectFieldsId.value = Array.isArray(value) ? value : [];
} else if (Array.isArray(value) && value.length) {
const [dsId, ...fields] = value;
selectDataSourceId.value = dsId;
selectFieldsId.value = fields;
} else {
selectDataSourceId.value = '';
selectFieldsId.value = [];
}
},
{
immediate: true,
},
);
const fieldsOptions = computed(() => {
const ds = allDataSources.value.find((ds) => ds.id === removeDataSourceFieldPrefix(selectDataSourceId.value));
if (!ds) return [];
return getCascaderOptionsFromFields(ds.fields, props.dataSourceFieldType);
});
const cascaderOptions = computed(() => {
const options =
dataSources.value?.map((ds) => ({
label: ds.title || ds.id,
value: valueIsKey.value ? ds.id : `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${ds.id}`,
children: getCascaderOptionsFromFields(ds.fields, props.dataSourceFieldType),
})) || [];
return options.filter((option) => option.children.length);
});
const dsChangeHandler = (v: string) => {
modelValue.value = [v];
emit('change', modelValue.value);
};
const fieldChangeHandler = (v: string[] = []) => {
if (props.dataSourceId) {
modelValue.value = v;
emit('change', v);
} else {
modelValue.value = [selectDataSourceId.value, ...v];
emit('change', modelValue.value);
}
};
const onChangeHandler = (v: string[] = []) => {
modelValue.value = v;
emit('change', v);
};
const hasDataSourceSidePanel = computed(() =>
uiService.get('sideBarItems').find((item) => item.$key === SideItemKey.DATA_SOURCE),
);
const editHandler = (id: string) => {
eventBus?.emit('edit-data-source', removeDataSourceFieldPrefix(id));
};
</script>