@tmagic/form 1.8.0-beta.0 → 1.8.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/Form.vue_vue_type_script_setup_true_lang.js +97 -24
- package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +193 -77
- package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +9 -8
- package/dist/es/containers/Tabs.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/es/index.js +2 -1
- package/dist/es/schema.js +4 -0
- package/dist/es/style.css +4 -1
- package/dist/style.css +4 -1
- package/dist/tmagic-form.umd.cjs +311 -113
- package/package.json +4 -4
- package/src/Form.vue +139 -23
- package/src/containers/Container.vue +174 -52
- package/src/containers/GroupList.vue +1 -1
- package/src/containers/GroupListItem.vue +4 -3
- package/src/containers/Tabs.vue +10 -2
- package/src/containers/table/Table.vue +9 -3
- package/src/schema.ts +47 -0
- package/src/theme/container.scss +4 -1
- package/types/index.d.ts +932 -418
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.2",
|
|
3
3
|
"name": "@tmagic/form",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"vue": "^3.5.34",
|
|
54
54
|
"typescript": "^6.0.3",
|
|
55
|
-
"@tmagic/
|
|
56
|
-
"@tmagic/
|
|
57
|
-
"@tmagic/
|
|
55
|
+
"@tmagic/design": "1.8.0-beta.2",
|
|
56
|
+
"@tmagic/form-schema": "1.8.0-beta.2",
|
|
57
|
+
"@tmagic/utils": "1.8.0-beta.2"
|
|
58
58
|
},
|
|
59
59
|
"peerDependenciesMeta": {
|
|
60
60
|
"typescript": {
|
package/src/Form.vue
CHANGED
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
:step-active="stepActive"
|
|
23
23
|
:size="size"
|
|
24
24
|
@change="changeHandler"
|
|
25
|
-
|
|
25
|
+
>
|
|
26
|
+
<template v-if="$slots.label" #label="labelProps">
|
|
27
|
+
<slot name="label" v-bind="labelProps"></slot>
|
|
28
|
+
</template>
|
|
29
|
+
</Container>
|
|
26
30
|
</template>
|
|
27
31
|
</TMagicForm>
|
|
28
32
|
</template>
|
|
@@ -37,12 +41,23 @@ import { setValueByKeyPath } from '@tmagic/utils';
|
|
|
37
41
|
import Container from './containers/Container.vue';
|
|
38
42
|
import { getConfig } from './utils/config';
|
|
39
43
|
import { initValue } from './utils/form';
|
|
40
|
-
import type {
|
|
44
|
+
import type {
|
|
45
|
+
ChangeRecord,
|
|
46
|
+
ContainerChangeEventData,
|
|
47
|
+
FormConfig,
|
|
48
|
+
FormSlots,
|
|
49
|
+
FormState,
|
|
50
|
+
FormValue,
|
|
51
|
+
ValidateError,
|
|
52
|
+
} from './schema';
|
|
53
|
+
import { FORM_DIFF_CONFIG_KEY } from './schema';
|
|
41
54
|
|
|
42
55
|
defineOptions({
|
|
43
56
|
name: 'MForm',
|
|
44
57
|
});
|
|
45
58
|
|
|
59
|
+
defineSlots<FormSlots>();
|
|
60
|
+
|
|
46
61
|
const props = withDefaults(
|
|
47
62
|
defineProps<{
|
|
48
63
|
/** 表单配置 */
|
|
@@ -65,6 +80,33 @@ const props = withDefaults(
|
|
|
65
80
|
popperClass?: string;
|
|
66
81
|
preventSubmitDefault?: boolean;
|
|
67
82
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
83
|
+
/**
|
|
84
|
+
* 自定义"是否展示对比内容"的判断函数(仅在 `isCompare === true` 时生效)。
|
|
85
|
+
*
|
|
86
|
+
* - 不传:使用默认逻辑 `!isEqual(curValue, lastValue)`;
|
|
87
|
+
* - 传函数:完全以函数返回值为准,返回 `true` 才展示前后两份对比内容。
|
|
88
|
+
*
|
|
89
|
+
* 通过 provide 下发给所有层级的 Container(含嵌套在容器组件内部的 Container),
|
|
90
|
+
* 调用方只需在 MForm 这一层传一次即可对整棵表单生效。
|
|
91
|
+
*
|
|
92
|
+
* 典型场景:某些字段语义上相等但结构不同(例如 `code-select` 字段中 `''` 与
|
|
93
|
+
* `{ hookType: 'code', hookData: [] }` 应视为相等),调用方在此处显式声明,
|
|
94
|
+
* 避免被 lodash `isEqual` 误判为差异。
|
|
95
|
+
*/
|
|
96
|
+
showDiff?: (_data: { curValue: any; lastValue: any; config: any }) => boolean;
|
|
97
|
+
/**
|
|
98
|
+
* 自定义「自接管对比」的字段类型(仅在对比模式下生效)。
|
|
99
|
+
*
|
|
100
|
+
* 自接管对比的字段不会渲染前后两份独立组件,而是只渲染一次并由字段组件内部展示前后差异
|
|
101
|
+
* (如 vs-code 使用 monaco diff 编辑器;event-select / code-select-col 等复合字段逐项展示差异)。
|
|
102
|
+
*
|
|
103
|
+
* 支持两种形式:
|
|
104
|
+
* - 传数组:在内置类型基础上「追加」这些类型;
|
|
105
|
+
* - 传函数:入参为内置类型数组,返回值作为「最终」完整列表(可完全替换内置项)。
|
|
106
|
+
*
|
|
107
|
+
* 通过 provide 下发,对整棵表单的所有层级 Container 生效,只需在 MForm 这一层传一次。
|
|
108
|
+
*/
|
|
109
|
+
selfDiffFieldTypes?: string[] | ((_defaultTypes: string[]) => string[]);
|
|
68
110
|
}>(),
|
|
69
111
|
{
|
|
70
112
|
config: () => [],
|
|
@@ -92,14 +134,45 @@ const fields = new Map<string, any>();
|
|
|
92
134
|
|
|
93
135
|
const requestFuc = getConfig('request') as Function;
|
|
94
136
|
|
|
137
|
+
/**
|
|
138
|
+
* formState 实现说明:
|
|
139
|
+
*
|
|
140
|
+
* 1. 与 props 直接对应的字段(config / initValues / lastValues / isCompare / parentValues /
|
|
141
|
+
* keyProp / popperClass)使用「访问器(getter)」定义,每次读取都会回到 `props.xxx`
|
|
142
|
+
* 取最新值,不存在「props 变了但 formState 还没同步过来」的中间态。
|
|
143
|
+
*
|
|
144
|
+
* 2. `values` / `lastValuesProcessed` 是 ref,Vue 的 `reactive` 会自动解包,因此每次
|
|
145
|
+
* 访问 `formState.values` / `formState.lastValuesProcessed` 也都是当前 ref 值。
|
|
146
|
+
*
|
|
147
|
+
* 3. `extendState` 注入的字段在下方的 `watchEffect` 中合并到 `formState`:
|
|
148
|
+
* - data 描述符(普通字段)通过 `formState[key] = value` 写入,走 reactive proxy 的
|
|
149
|
+
* set,触发依赖通知;`extendState` 同步段读到的响应式数据变化时会自动重跑,
|
|
150
|
+
* 把最新值刷进 formState。
|
|
151
|
+
* - accessor 描述符(`{ get stage() { return ... } }`)按原样写入,调用方可以控制
|
|
152
|
+
* 读时求值,每次读取都会重新执行 getter。
|
|
153
|
+
*/
|
|
95
154
|
const formState: FormState = reactive<FormState>({
|
|
96
|
-
keyProp
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
155
|
+
get keyProp() {
|
|
156
|
+
return props.keyProp;
|
|
157
|
+
},
|
|
158
|
+
get popperClass() {
|
|
159
|
+
return props.popperClass;
|
|
160
|
+
},
|
|
161
|
+
get config() {
|
|
162
|
+
return props.config;
|
|
163
|
+
},
|
|
164
|
+
get initValues() {
|
|
165
|
+
return props.initValues;
|
|
166
|
+
},
|
|
167
|
+
get isCompare() {
|
|
168
|
+
return props.isCompare;
|
|
169
|
+
},
|
|
170
|
+
get lastValues() {
|
|
171
|
+
return props.lastValues;
|
|
172
|
+
},
|
|
173
|
+
get parentValues() {
|
|
174
|
+
return props.parentValues;
|
|
175
|
+
},
|
|
103
176
|
values,
|
|
104
177
|
lastValuesProcessed,
|
|
105
178
|
$emit: emit as (_event: string, ..._args: any[]) => void,
|
|
@@ -119,25 +192,68 @@ const formState: FormState = reactive<FormState>({
|
|
|
119
192
|
},
|
|
120
193
|
});
|
|
121
194
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
195
|
+
/**
|
|
196
|
+
* `extendState` 的同步段(直到第一个 `await` 之前)所访问的任何响应式数据,
|
|
197
|
+
* 都会被 `watchEffect` 自动跟踪。这样可以兼容历史用法 ——
|
|
198
|
+
*
|
|
199
|
+
* extendState: (formState) => ({
|
|
200
|
+
* username: store.username, // 同步读 store,会被跟踪
|
|
201
|
+
* env: store.env,
|
|
202
|
+
* })
|
|
203
|
+
*
|
|
204
|
+
* 当 `store.username` 变化时,整个 effect 重跑,新值会被刷进 `formState`。
|
|
205
|
+
*
|
|
206
|
+
* prop 派生字段(initValues / config / ...)已经在上方用 getter 定义,
|
|
207
|
+
* 这里不再重复同步;因此 `props.initValues` 这类高频变化也不会再触发
|
|
208
|
+
* `extendState` 重跑(旧版的性能问题修复点)。
|
|
209
|
+
*
|
|
210
|
+
* 实现细节:
|
|
211
|
+
* - data 描述符:通过 `formState[key] = value` 走 reactive proxy 的 set,
|
|
212
|
+
* 触发依赖通知;与旧版「逐项赋值」语义完全等价。
|
|
213
|
+
* - accessor 描述符(`{ get stage() {...} }`)按原样写入 formState,调用方
|
|
214
|
+
* 可以自行控制读时求值;强制 `configurable: true` 以便下一次重跑可再 define。
|
|
215
|
+
*/
|
|
216
|
+
watchEffect(async (onCleanup) => {
|
|
217
|
+
const { extendState } = props;
|
|
218
|
+
if (typeof extendState !== 'function') return;
|
|
219
|
+
|
|
220
|
+
let stale = false;
|
|
221
|
+
onCleanup(() => {
|
|
222
|
+
stale = true;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
let state: Record<string, any> = {};
|
|
226
|
+
try {
|
|
227
|
+
state = (await extendState(formState)) || {};
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.error('[MForm] extendState failed:', e);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (stale) return;
|
|
233
|
+
|
|
234
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(state))) {
|
|
235
|
+
if ('value' in descriptor) {
|
|
236
|
+
(formState as any)[key] = (state as any)[key];
|
|
237
|
+
} else {
|
|
238
|
+
descriptor.configurable = true;
|
|
239
|
+
Object.defineProperty(formState, key, descriptor);
|
|
240
|
+
}
|
|
136
241
|
}
|
|
137
242
|
});
|
|
138
243
|
|
|
139
244
|
provide('mForm', formState);
|
|
140
245
|
|
|
246
|
+
// 对比相关配置单独通过 provide 下发,所有层级的 Container 通过 inject 获取,无需逐层透传 prop。
|
|
247
|
+
// 用 getter 对象保证读取时回到最新的 props 值,维持响应式。
|
|
248
|
+
provide(FORM_DIFF_CONFIG_KEY, {
|
|
249
|
+
get showDiff() {
|
|
250
|
+
return props.showDiff;
|
|
251
|
+
},
|
|
252
|
+
get selfDiffFieldTypes() {
|
|
253
|
+
return props.selfDiffFieldTypes;
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
141
257
|
const changeRecords = shallowRef<ChangeRecord[]>([]);
|
|
142
258
|
|
|
143
259
|
watch(
|
|
@@ -25,13 +25,15 @@
|
|
|
25
25
|
<template v-else-if="type && display && !showDiff">
|
|
26
26
|
<TMagicFormItem v-bind="formItemProps" :class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }">
|
|
27
27
|
<template #label>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
|
|
29
|
+
<FormLabel
|
|
30
|
+
:tip="config.tip"
|
|
31
|
+
:type="type"
|
|
32
|
+
:use-label="(config as CheckboxConfig).useLabel"
|
|
33
|
+
:label-title="config.labelTitle"
|
|
34
|
+
:text="text"
|
|
35
|
+
></FormLabel>
|
|
36
|
+
</slot>
|
|
35
37
|
</template>
|
|
36
38
|
|
|
37
39
|
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
|
@@ -71,68 +73,126 @@
|
|
|
71
73
|
|
|
72
74
|
<!-- 对比 -->
|
|
73
75
|
<template v-else-if="type && display && showDiff">
|
|
74
|
-
<!--
|
|
76
|
+
<!-- 自接管对比的字段类型(如 vs-code):只渲染一次组件,由字段内部用 diff 编辑器/视图自行展示前后差异 -->
|
|
75
77
|
<TMagicFormItem
|
|
78
|
+
v-if="isSelfDiffField"
|
|
76
79
|
v-bind="formItemProps"
|
|
77
|
-
:class="{
|
|
80
|
+
:class="{
|
|
81
|
+
'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text,
|
|
82
|
+
'self-diff': true,
|
|
83
|
+
}"
|
|
78
84
|
>
|
|
79
85
|
<template #label>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
|
|
87
|
+
<FormLabel
|
|
88
|
+
:tip="config.tip"
|
|
89
|
+
:type="type"
|
|
90
|
+
:use-label="(config as CheckboxConfig).useLabel"
|
|
91
|
+
:label-title="config.labelTitle"
|
|
92
|
+
:text="text"
|
|
93
|
+
></FormLabel>
|
|
94
|
+
</slot>
|
|
87
95
|
</template>
|
|
88
96
|
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
|
89
|
-
<component
|
|
97
|
+
<component
|
|
98
|
+
v-bind="fieldsProps"
|
|
99
|
+
:is="tagName"
|
|
100
|
+
:model="model"
|
|
101
|
+
:last-values="lastValues"
|
|
102
|
+
:is-compare="isCompare"
|
|
103
|
+
@change="onChangeHandler"
|
|
104
|
+
></component>
|
|
90
105
|
<template #content>
|
|
91
106
|
<div v-html="tooltip.text"></div>
|
|
92
107
|
</template>
|
|
93
108
|
</TMagicTooltip>
|
|
94
109
|
|
|
95
|
-
<component
|
|
110
|
+
<component
|
|
111
|
+
v-else
|
|
112
|
+
v-bind="fieldsProps"
|
|
113
|
+
:is="tagName"
|
|
114
|
+
:model="model"
|
|
115
|
+
:last-values="lastValues"
|
|
116
|
+
:is-compare="isCompare"
|
|
117
|
+
@change="onChangeHandler"
|
|
118
|
+
></component>
|
|
96
119
|
</TMagicFormItem>
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
121
|
+
<!-- 普通字段:渲染前后两份独立的组件用于对比 -->
|
|
122
|
+
<template v-else>
|
|
123
|
+
<!-- 上次内容 -->
|
|
124
|
+
<TMagicFormItem
|
|
125
|
+
v-bind="formItemProps"
|
|
126
|
+
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-before-diff': true }"
|
|
127
|
+
>
|
|
128
|
+
<template #label>
|
|
129
|
+
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
|
|
130
|
+
<FormLabel
|
|
131
|
+
:tip="config.tip"
|
|
132
|
+
:type="type"
|
|
133
|
+
:use-label="(config as CheckboxConfig).useLabel"
|
|
134
|
+
:label-title="config.labelTitle"
|
|
135
|
+
:text="text"
|
|
136
|
+
></FormLabel>
|
|
137
|
+
</slot>
|
|
138
|
+
</template>
|
|
139
|
+
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
|
140
|
+
<component v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
|
|
141
|
+
<template #content>
|
|
142
|
+
<div v-html="tooltip.text"></div>
|
|
143
|
+
</template>
|
|
144
|
+
</TMagicTooltip>
|
|
104
145
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
:label-title="config.labelTitle"
|
|
117
|
-
:text="text"
|
|
118
|
-
></FormLabel>
|
|
119
|
-
</template>
|
|
120
|
-
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
|
121
|
-
<component v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
|
|
146
|
+
<component
|
|
147
|
+
v-else
|
|
148
|
+
v-bind="fieldsProps"
|
|
149
|
+
:is="tagName"
|
|
150
|
+
:model="lastValues"
|
|
151
|
+
@change="onChangeHandler"
|
|
152
|
+
></component>
|
|
153
|
+
</TMagicFormItem>
|
|
154
|
+
|
|
155
|
+
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
|
|
156
|
+
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
|
|
122
157
|
<template #content>
|
|
123
|
-
<div v-html="
|
|
158
|
+
<div v-html="config.tip"></div>
|
|
124
159
|
</template>
|
|
125
160
|
</TMagicTooltip>
|
|
126
161
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
162
|
+
<!-- 当前内容 -->
|
|
163
|
+
<TMagicFormItem
|
|
164
|
+
v-bind="formItemProps"
|
|
165
|
+
:style="config.tip ? 'flex: 1' : ''"
|
|
166
|
+
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-after-diff': true }"
|
|
167
|
+
>
|
|
168
|
+
<template #label>
|
|
169
|
+
<slot name="label" :config="config" :type="type" :text="text" :prop="itemProp" :disabled="disabled">
|
|
170
|
+
<FormLabel
|
|
171
|
+
:tip="config.tip"
|
|
172
|
+
:type="type"
|
|
173
|
+
:use-label="(config as CheckboxConfig).useLabel"
|
|
174
|
+
:label-title="config.labelTitle"
|
|
175
|
+
:text="text"
|
|
176
|
+
></FormLabel>
|
|
177
|
+
</slot>
|
|
178
|
+
</template>
|
|
179
|
+
<TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
|
|
180
|
+
<component v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
|
|
181
|
+
<template #content>
|
|
182
|
+
<div v-html="tooltip.text"></div>
|
|
183
|
+
</template>
|
|
184
|
+
</TMagicTooltip>
|
|
185
|
+
|
|
186
|
+
<component v-else v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
|
|
187
|
+
</TMagicFormItem>
|
|
188
|
+
|
|
189
|
+
<TMagicTooltip v-if="config.tip && type === 'checkbox' && !(config as CheckboxConfig).useLabel" placement="top">
|
|
190
|
+
<TMagicIcon style="line-height: 40px; margin-left: 5px"><warning-filled /></TMagicIcon>
|
|
191
|
+
<template #content>
|
|
192
|
+
<div v-html="config.tip"></div>
|
|
193
|
+
</template>
|
|
194
|
+
</TMagicTooltip>
|
|
195
|
+
</template>
|
|
136
196
|
</template>
|
|
137
197
|
|
|
138
198
|
<template v-else-if="items && display">
|
|
@@ -152,7 +212,11 @@
|
|
|
152
212
|
:prop="itemProp"
|
|
153
213
|
@change="onChangeHandler"
|
|
154
214
|
@addDiffCount="onAddDiffCount"
|
|
155
|
-
|
|
215
|
+
>
|
|
216
|
+
<template v-if="$slots.label" #label="labelProps">
|
|
217
|
+
<slot name="label" v-bind="labelProps"></slot>
|
|
218
|
+
</template>
|
|
219
|
+
</Container>
|
|
156
220
|
</template>
|
|
157
221
|
</template>
|
|
158
222
|
|
|
@@ -178,11 +242,14 @@ import type {
|
|
|
178
242
|
ComponentConfig,
|
|
179
243
|
ContainerChangeEventData,
|
|
180
244
|
ContainerCommonConfig,
|
|
245
|
+
FormDiffConfig,
|
|
181
246
|
FormItemConfig,
|
|
247
|
+
FormSlots,
|
|
182
248
|
FormState,
|
|
183
249
|
FormValue,
|
|
184
250
|
ToolTipConfigType,
|
|
185
251
|
} from '../schema';
|
|
252
|
+
import { FORM_DIFF_CONFIG_KEY } from '../schema';
|
|
186
253
|
import { getField } from '../utils/config';
|
|
187
254
|
import { createObjectProp, display as displayFunction, filterFunction, getRules } from '../utils/form';
|
|
188
255
|
|
|
@@ -192,6 +259,8 @@ defineOptions({
|
|
|
192
259
|
name: 'MFormContainer',
|
|
193
260
|
});
|
|
194
261
|
|
|
262
|
+
defineSlots<FormSlots>();
|
|
263
|
+
|
|
195
264
|
const props = withDefaults(
|
|
196
265
|
defineProps<{
|
|
197
266
|
/** 表单值 */
|
|
@@ -224,14 +293,28 @@ const emit = defineEmits<{
|
|
|
224
293
|
|
|
225
294
|
const mForm = inject<FormState | undefined>('mForm');
|
|
226
295
|
|
|
296
|
+
// 对比相关配置由 MForm 通过 provide 下发,这里直接 inject,无需逐层透传 prop。
|
|
297
|
+
const diffConfig = inject<FormDiffConfig>(FORM_DIFF_CONFIG_KEY, {});
|
|
298
|
+
|
|
227
299
|
const expand = ref(false);
|
|
228
300
|
|
|
229
301
|
const name = computed(() => props.config.name || '');
|
|
302
|
+
|
|
230
303
|
// 是否展示两个版本的对比内容
|
|
304
|
+
//
|
|
305
|
+
// 默认逻辑:在对比模式下用 lodash isEqual 比较当前值与历史值,不相等则展示对比。
|
|
306
|
+
// 若调用方通过 MForm 的 `showDiff` 注入了自定义判断函数,则完全以其返回值为准,
|
|
307
|
+
// 便于业务侧自定义"语义上相等"的特殊场景(例如空字符串与空 hook 结构)。
|
|
231
308
|
const showDiff = computed(() => {
|
|
232
309
|
if (!props.isCompare) return false;
|
|
233
310
|
const curValue = name.value ? props.model[name.value] : props.model;
|
|
234
311
|
const lastValue = name.value ? props.lastValues[name.value] : props.lastValues;
|
|
312
|
+
|
|
313
|
+
const customShowDiff = diffConfig.showDiff;
|
|
314
|
+
if (typeof customShowDiff === 'function') {
|
|
315
|
+
return Boolean(customShowDiff({ curValue, lastValue, config: props.config }));
|
|
316
|
+
}
|
|
317
|
+
|
|
235
318
|
return !isEqual(curValue, lastValue);
|
|
236
319
|
});
|
|
237
320
|
|
|
@@ -268,6 +351,45 @@ const tagName = computed(() => {
|
|
|
268
351
|
return getField(type.value || 'container') || `m-${items.value ? 'form' : 'fields'}-${type.value}`;
|
|
269
352
|
});
|
|
270
353
|
|
|
354
|
+
/**
|
|
355
|
+
* 自接管对比的字段类型白名单。
|
|
356
|
+
*
|
|
357
|
+
* 这类字段在 `isCompare === true` 且存在差异时,不再由 Container 渲染前后两份独立组件来对比,
|
|
358
|
+
* 而是只渲染一次组件,将 `model` / `lastValues` / `isCompare` 一并传给字段组件,
|
|
359
|
+
* 由字段组件内部自行展示前后差异(典型场景:vs-code 字段使用 monaco 自带的 diff 编辑器)。
|
|
360
|
+
*
|
|
361
|
+
* 这样做的好处:
|
|
362
|
+
* 1. 避免重型字段(如 monaco 编辑器)在对比模式下被实例化两次,节省资源;
|
|
363
|
+
* 2. 提供更专业的对比视觉效果(如 monaco diff 的行级高亮、左右滚动同步等)。
|
|
364
|
+
*
|
|
365
|
+
* 注意:像 `event-select` / `code-select-col` 这类内部由列表 / 嵌套子表单组成的复合字段,若按默认逻辑
|
|
366
|
+
* 渲染前后两份独立组件,会出现两套下拉框 + 两份参数表单(或两套「添加事件」按钮、两份完整面板),
|
|
367
|
+
* 体验很差。这类字段在内部把 `is-compare`/`lastValues` 透传给子级容器,由子级逐项展示差异,
|
|
368
|
+
* 因此同样归类为自接管对比字段。
|
|
369
|
+
*/
|
|
370
|
+
const DEFAULT_SELF_DIFF_FIELD_TYPES = ['vs-code', 'event-select', 'code-select-col', 'code-select'];
|
|
371
|
+
|
|
372
|
+
// 最终生效的自接管对比字段类型集合。
|
|
373
|
+
//
|
|
374
|
+
// - 未自定义:使用内置默认类型;
|
|
375
|
+
// - 自定义传数组:在内置类型基础上「追加」;
|
|
376
|
+
// - 自定义传函数:以函数返回值为「最终」完整列表(可完全替换内置项)。
|
|
377
|
+
const effectiveSelfDiffFieldTypes = computed<Set<string>>(() => {
|
|
378
|
+
const custom = diffConfig.selfDiffFieldTypes;
|
|
379
|
+
|
|
380
|
+
if (typeof custom === 'function') {
|
|
381
|
+
return new Set(custom([...DEFAULT_SELF_DIFF_FIELD_TYPES]));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (Array.isArray(custom)) {
|
|
385
|
+
return new Set([...DEFAULT_SELF_DIFF_FIELD_TYPES, ...custom]);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return new Set(DEFAULT_SELF_DIFF_FIELD_TYPES);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const isSelfDiffField = computed(() => effectiveSelfDiffFieldTypes.value.has(type.value));
|
|
392
|
+
|
|
271
393
|
const disabled = computed(() => props.disabled || filterFunction(mForm, props.config.disabled, props));
|
|
272
394
|
|
|
273
395
|
const text = computed(() => filterFunction(mForm, props.config.text, props));
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
@addDiffCount="onAddDiffCount()"
|
|
27
27
|
></MFieldsGroupListItem>
|
|
28
28
|
|
|
29
|
-
<div class="m-fields-group-list-footer">
|
|
29
|
+
<div class="m-fields-group-list-footer" v-if="!isCompare">
|
|
30
30
|
<slot name="toggle-button"></slot>
|
|
31
31
|
<div style="display: flex; justify-content: flex-end; flex: 1">
|
|
32
32
|
<slot name="add-button"></slot>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
</TMagicButton>
|
|
8
8
|
|
|
9
9
|
<TMagicButton
|
|
10
|
+
v-if="!isCompare"
|
|
10
11
|
v-show="showDelete"
|
|
11
12
|
type="danger"
|
|
12
13
|
size="small"
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
></TMagicButton>
|
|
18
19
|
|
|
19
20
|
<TMagicButton
|
|
20
|
-
v-if="copyable"
|
|
21
|
+
v-if="copyable && !isCompare"
|
|
21
22
|
link
|
|
22
23
|
size="small"
|
|
23
24
|
type="primary"
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
>复制</TMagicButton
|
|
28
29
|
>
|
|
29
30
|
|
|
30
|
-
<template v-if="movable">
|
|
31
|
+
<template v-if="movable && !isCompare">
|
|
31
32
|
<TMagicButton
|
|
32
33
|
v-show="index !== 0"
|
|
33
34
|
link
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
</template>
|
|
50
51
|
|
|
51
52
|
<TMagicPopover
|
|
52
|
-
v-if="config.moveSpecifyLocation"
|
|
53
|
+
v-if="config.moveSpecifyLocation && !isCompare"
|
|
53
54
|
trigger="click"
|
|
54
55
|
placement="top"
|
|
55
56
|
width="200"
|
package/src/containers/Tabs.vue
CHANGED
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
:is="tabPaneComponent?.component || 'el-tab-pane'"
|
|
20
20
|
:key="tab[mForm?.keyProp || '__key'] ?? tabIndex"
|
|
21
21
|
v-bind="
|
|
22
|
-
tabPaneComponent?.props({
|
|
22
|
+
tabPaneComponent?.props({
|
|
23
|
+
name: filter(tab.status) || tabIndex.toString(),
|
|
24
|
+
lazy: isCompare ? false : tab.lazy || false,
|
|
25
|
+
}) || {}
|
|
23
26
|
"
|
|
24
27
|
>
|
|
25
28
|
<template #label>
|
|
@@ -76,7 +79,7 @@
|
|
|
76
79
|
</template>
|
|
77
80
|
|
|
78
81
|
<script setup lang="ts">
|
|
79
|
-
import { computed, inject, ref, watchEffect } from 'vue';
|
|
82
|
+
import { computed, inject, ref, watch, watchEffect } from 'vue';
|
|
80
83
|
import { isEmpty } from 'lodash-es';
|
|
81
84
|
|
|
82
85
|
import { getDesignConfig, TMagicBadge } from '@tmagic/design';
|
|
@@ -172,6 +175,11 @@ watchEffect(() => {
|
|
|
172
175
|
}
|
|
173
176
|
});
|
|
174
177
|
|
|
178
|
+
// model 或 lastValues 变化时,重置差异数
|
|
179
|
+
watch([() => props.model, () => props.lastValues], () => {
|
|
180
|
+
diffCount.value = {};
|
|
181
|
+
});
|
|
182
|
+
|
|
175
183
|
const tabItems = (tab: TabPaneConfig) => (props.config.dynamic ? props.config.items : tab.items);
|
|
176
184
|
|
|
177
185
|
const tabClickHandler = (tab: any) => {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
{{ isFullscreen ? '退出全屏' : '全屏编辑' }}
|
|
45
45
|
</TMagicButton>
|
|
46
46
|
<TMagicUpload
|
|
47
|
-
v-if="importable"
|
|
47
|
+
v-if="importable && !isCompare"
|
|
48
48
|
style="display: inline-block"
|
|
49
49
|
ref="excelBtn"
|
|
50
50
|
action="/noop"
|
|
@@ -54,11 +54,17 @@
|
|
|
54
54
|
>
|
|
55
55
|
<TMagicButton size="small" type="success" :disabled="disabled" plain>导入EXCEL</TMagicButton>
|
|
56
56
|
</TMagicUpload>
|
|
57
|
-
<TMagicButton
|
|
57
|
+
<TMagicButton
|
|
58
|
+
v-if="importable && !isCompare"
|
|
59
|
+
size="small"
|
|
60
|
+
type="warning"
|
|
61
|
+
:disabled="disabled"
|
|
62
|
+
plain
|
|
63
|
+
@click="clearHandler"
|
|
58
64
|
>清空</TMagicButton
|
|
59
65
|
>
|
|
60
66
|
</div>
|
|
61
|
-
<slot name="add-button"></slot>
|
|
67
|
+
<slot name="add-button" v-if="!isCompare"></slot>
|
|
62
68
|
</div>
|
|
63
69
|
|
|
64
70
|
<div class="bottom" style="text-align: right" v-if="config.pagination">
|