@tmagic/form 1.8.0-manmanyu.8 → 1.8.0

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.
Files changed (149) hide show
  1. package/dist/es/Form.vue_vue_type_script_setup_true_lang.js +88 -107
  2. package/dist/es/FormBox.vue_vue_type_script_setup_true_lang.js +21 -5
  3. package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +21 -5
  4. package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +46 -12
  5. package/dist/es/containers/Col.vue_vue_type_script_setup_true_lang.js +6 -2
  6. package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +44 -47
  7. package/dist/es/containers/Fieldset.vue_vue_type_script_setup_true_lang.js +7 -1
  8. package/dist/es/containers/FlexLayout.vue_vue_type_script_setup_true_lang.js +5 -2
  9. package/dist/es/containers/FormLabel.vue_vue_type_script_setup_true_lang.js +1 -1
  10. package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +59 -22
  11. package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +118 -100
  12. package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +8 -3
  13. package/dist/es/containers/Row.vue_vue_type_script_setup_true_lang.js +4 -1
  14. package/dist/es/containers/Step.vue_vue_type_script_setup_true_lang.js +5 -2
  15. package/dist/es/containers/Tabs.vue_vue_type_script_setup_true_lang.js +8 -1
  16. package/dist/es/containers/table/ActionsColumn.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/es/containers/table/SortColumn.vue_vue_type_script_setup_true_lang.js +1 -1
  18. package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +14 -10
  19. package/dist/es/containers/table/useTableColumns.js +10 -15
  20. package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +72 -79
  21. package/dist/es/containers/table-group-list/useAdd.js +26 -16
  22. package/dist/es/containers/table-group-list/useScrollLastItemIntoView.js +72 -0
  23. package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +2 -2
  24. package/dist/es/fields/Checkbox.vue_vue_type_script_setup_true_lang.js +1 -1
  25. package/dist/es/fields/CheckboxGroup/Index.js +5 -0
  26. package/dist/es/fields/{CheckboxGroup.vue_vue_type_script_setup_true_lang.js → CheckboxGroup/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
  27. package/dist/es/fields/CheckboxGroup/effect.js +8 -0
  28. package/dist/es/fields/ColorPicker.vue_vue_type_script_setup_true_lang.js +1 -1
  29. package/dist/es/fields/Date/Index.js +5 -0
  30. package/dist/es/fields/{Date.vue_vue_type_script_setup_true_lang.js → Date/Index.vue_vue_type_script_setup_true_lang.js} +5 -7
  31. package/dist/es/fields/Date/effect.js +10 -0
  32. package/dist/es/fields/DateTime/Index.js +5 -0
  33. package/dist/es/fields/{DateTime.vue_vue_type_script_setup_true_lang.js → DateTime/Index.vue_vue_type_script_setup_true_lang.js} +5 -9
  34. package/dist/es/fields/DateTime/effect.js +15 -0
  35. package/dist/es/fields/Daterange.vue_vue_type_script_setup_true_lang.js +2 -2
  36. package/dist/es/fields/Display/Index.js +5 -0
  37. package/dist/es/fields/{Display.vue_vue_type_script_setup_true_lang.js → Display/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
  38. package/dist/es/fields/Display/effect.js +8 -0
  39. package/dist/es/fields/DynamicField/Index.js +5 -0
  40. package/dist/es/fields/{DynamicField.vue_vue_type_script_setup_true_lang.js → DynamicField/Index.vue_vue_type_script_setup_true_lang.js} +11 -15
  41. package/dist/es/fields/DynamicField/effect.js +40 -0
  42. package/dist/es/fields/Hidden.vue_vue_type_script_setup_true_lang.js +1 -1
  43. package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +4 -4
  44. package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +4 -2
  45. package/dist/es/fields/NumberRange/Index.js +5 -0
  46. package/dist/es/fields/{NumberRange.vue_vue_type_script_setup_true_lang.js → NumberRange/Index.vue_vue_type_script_setup_true_lang.js} +5 -6
  47. package/dist/es/fields/NumberRange/effect.js +8 -0
  48. package/dist/es/fields/RadioGroup.vue_vue_type_script_setup_true_lang.js +5 -2
  49. package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +14 -6
  50. package/dist/es/fields/Switch.vue_vue_type_script_setup_true_lang.js +1 -1
  51. package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +13 -10
  52. package/dist/es/fields/Textarea.vue_vue_type_script_setup_true_lang.js +1 -1
  53. package/dist/es/fields/Time.vue_vue_type_script_setup_true_lang.js +1 -1
  54. package/dist/es/fields/Timerange.vue_vue_type_script_setup_true_lang.js +3 -3
  55. package/dist/es/headless.js +13 -0
  56. package/dist/es/index.js +19 -12
  57. package/dist/es/plugin.js +57 -39
  58. package/dist/es/schema.js +11 -2
  59. package/dist/es/style.css +123 -3
  60. package/dist/es/submitForm.js +270 -72
  61. package/dist/es/utils/builtInFields.js +49 -0
  62. package/dist/es/utils/collectFields.js +407 -0
  63. package/dist/es/utils/config.js +4 -8
  64. package/dist/es/utils/fieldInnerConfig.js +42 -0
  65. package/dist/es/utils/fieldValueEffects.js +80 -0
  66. package/dist/es/utils/form.js +154 -19
  67. package/dist/es/utils/formStateProxy.js +111 -0
  68. package/dist/es/utils/registerField.js +158 -0
  69. package/dist/es/utils/submitHeadless.js +54 -0
  70. package/dist/es/utils/tableGroupList.js +104 -0
  71. package/dist/es/utils/typeMatch.js +532 -0
  72. package/dist/es/utils/validateError.js +57 -0
  73. package/dist/es/utils/validateValues.js +115 -0
  74. package/dist/style.css +123 -3
  75. package/dist/themes/magic-admin.css +576 -14
  76. package/dist/tmagic-form-headless.umd.cjs +4515 -0
  77. package/dist/tmagic-form.umd.cjs +3444 -983
  78. package/package.json +16 -10
  79. package/src/Form.vue +113 -129
  80. package/src/FormBox.vue +16 -4
  81. package/src/FormDialog.vue +16 -5
  82. package/src/FormDrawer.vue +41 -13
  83. package/src/containers/Col.vue +3 -0
  84. package/src/containers/Container.vue +40 -45
  85. package/src/containers/Fieldset.vue +5 -0
  86. package/src/containers/FlexLayout.vue +2 -0
  87. package/src/containers/GroupList.vue +64 -13
  88. package/src/containers/GroupListItem.vue +88 -81
  89. package/src/containers/Panel.vue +3 -0
  90. package/src/containers/Row.vue +2 -0
  91. package/src/containers/Step.vue +2 -0
  92. package/src/containers/Tabs.vue +13 -0
  93. package/src/containers/table/ActionsColumn.vue +1 -1
  94. package/src/containers/table/Table.vue +5 -3
  95. package/src/containers/table/usePagination.ts +1 -1
  96. package/src/containers/table/useSortable.ts +1 -1
  97. package/src/containers/table/useTableColumns.ts +15 -22
  98. package/src/containers/table-group-list/TableGroupList.vue +46 -72
  99. package/src/containers/table-group-list/useAdd.ts +40 -25
  100. package/src/containers/table-group-list/useScrollLastItemIntoView.ts +94 -0
  101. package/src/fields/{CheckboxGroup.vue → CheckboxGroup/Index.vue} +3 -8
  102. package/src/fields/CheckboxGroup/effect.ts +27 -0
  103. package/src/fields/{Date.vue → Date/Index.vue} +2 -5
  104. package/src/fields/Date/effect.ts +29 -0
  105. package/src/fields/{DateTime.vue → DateTime/Index.vue} +2 -16
  106. package/src/fields/DateTime/effect.ts +36 -0
  107. package/src/fields/{Display.vue → Display/Index.vue} +3 -7
  108. package/src/fields/Display/effect.ts +28 -0
  109. package/src/fields/{DynamicField.vue → DynamicField/Index.vue} +11 -11
  110. package/src/fields/DynamicField/effect.ts +74 -0
  111. package/src/fields/Link.vue +3 -4
  112. package/src/fields/Number.vue +2 -1
  113. package/src/fields/{NumberRange.vue → NumberRange/Index.vue} +2 -6
  114. package/src/fields/NumberRange/effect.ts +27 -0
  115. package/src/fields/RadioGroup.vue +13 -1
  116. package/src/fields/Select.vue +13 -4
  117. package/src/headless.ts +78 -0
  118. package/src/index.ts +46 -9
  119. package/src/plugin.ts +68 -37
  120. package/src/schema.ts +13 -2
  121. package/src/submitForm.ts +410 -131
  122. package/src/theme/fieldset.scss +1 -0
  123. package/src/theme/form-box.scss +5 -0
  124. package/src/theme/form-dialog.scss +1 -1
  125. package/src/theme/group-list.scss +143 -2
  126. package/src/theme/table.scss +11 -1
  127. package/src/theme/themes/magic-admin/index.scss +27 -16
  128. package/src/utils/builtInFields.ts +69 -0
  129. package/src/utils/collectFields.ts +561 -0
  130. package/src/utils/config.ts +7 -15
  131. package/src/utils/fieldInnerConfig.ts +127 -0
  132. package/src/utils/fieldValueEffects.ts +140 -0
  133. package/src/utils/form.ts +230 -30
  134. package/src/utils/formStateProxy.ts +149 -0
  135. package/src/utils/registerField.ts +308 -0
  136. package/src/utils/submitHeadless.ts +206 -0
  137. package/src/utils/tableGroupList.ts +147 -0
  138. package/src/utils/typeMatch.ts +948 -0
  139. package/src/utils/validateError.ts +92 -0
  140. package/src/utils/validateValues.ts +177 -0
  141. package/types/headless.d.ts +682 -0
  142. package/types/index.d.ts +900 -180
  143. package/dist/es/_virtual/_rolldown/runtime.js +0 -27
  144. package/dist/es/fields/CheckboxGroup.js +0 -5
  145. package/dist/es/fields/Date.js +0 -5
  146. package/dist/es/fields/DateTime.js +0 -5
  147. package/dist/es/fields/Display.js +0 -5
  148. package/dist/es/fields/DynamicField.js +0 -5
  149. package/dist/es/fields/NumberRange.js +0 -5
@@ -7,11 +7,13 @@
7
7
  :config="config"
8
8
  :prop="prop"
9
9
  :label-width="config.labelWidth || labelWidth"
10
+ :label-position="config.labelPosition || labelPosition"
10
11
  :expand-more="expandMore"
11
12
  :size="size"
12
13
  :disabled="disabled"
13
14
  @change="changeHandler"
14
15
  @add-diff-count="onAddDiffCount"
16
+ :is-in-row="true"
15
17
  ></Container>
16
18
  </TMagicCol>
17
19
  </template>
@@ -36,6 +38,7 @@ const props = defineProps<{
36
38
  isCompare?: boolean;
37
39
  config: FormItemConfig;
38
40
  labelWidth?: string | number;
41
+ labelPosition?: 'top' | 'left' | 'right';
39
42
  expandMore?: boolean;
40
43
  span?: number;
41
44
  size?: string;
@@ -5,7 +5,9 @@
5
5
  :class="`m-form-container m-container-${type || ''} ${config.className || ''}${config.tip ? ' has-tip' : ''}`"
6
6
  :style="config.style"
7
7
  >
8
- <MHidden v-if="type === 'hidden'" :name="`${name}`" :prop="itemProp" :model="model"></MHidden>
8
+ <TMagicFormItem v-if="type === 'hidden'" v-bind="formItemProps" style="display: none">
9
+ <MHidden :name="`${name}`" :prop="itemProp" :model="model"></MHidden>
10
+ </TMagicFormItem>
9
11
 
10
12
  <component
11
13
  v-else-if="items && !text && type && display"
@@ -17,6 +19,7 @@
17
19
  :step-active="stepActive"
18
20
  :expand-more="expand"
19
21
  :label-width="itemLabelWidth"
22
+ :label-position="itemLabelPosition"
20
23
  :style="config.fieldStyle"
21
24
  @change="onChangeHandler"
22
25
  @addDiffCount="onAddDiffCount"
@@ -33,7 +36,7 @@
33
36
  :label-title="config.labelTitle"
34
37
  :text="text"
35
38
  ></FormLabel>
36
- <span class="m-form-tip m-form-title-extra" v-if="config.titleExtra && config.labelPosition === 'top'">
39
+ <span class="m-form-tip m-form-title-extra" v-if="config.titleExtra && itemLabelPosition === 'top'">
37
40
  {{ config.titleExtra }}</span
38
41
  >
39
42
  </slot>
@@ -96,6 +99,7 @@
96
99
  ></FormLabel>
97
100
  </slot>
98
101
  </template>
102
+
99
103
  <TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
100
104
  <component
101
105
  v-bind="fieldsProps"
@@ -139,6 +143,7 @@
139
143
  ></FormLabel>
140
144
  </slot>
141
145
  </template>
146
+
142
147
  <TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
143
148
  <component v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
144
149
  <template #content>
@@ -179,6 +184,7 @@
179
184
  ></FormLabel>
180
185
  </slot>
181
186
  </template>
187
+
182
188
  <TMagicTooltip v-if="tooltip.text" :placement="tooltip.placement">
183
189
  <component v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
184
190
  <template #content>
@@ -212,6 +218,7 @@
212
218
  :step-active="stepActive"
213
219
  :expand-more="expand"
214
220
  :label-width="itemLabelWidth"
221
+ :label-position="itemLabelPosition"
215
222
  :prop="itemProp"
216
223
  @change="onChangeHandler"
217
224
  @addDiffCount="onAddDiffCount"
@@ -238,7 +245,7 @@
238
245
  </template>
239
246
 
240
247
  <script setup lang="ts">
241
- import { computed, inject, readonly, ref, toRaw, watch, watchEffect } from 'vue';
248
+ import { computed, inject, provide, readonly, ref, toRaw, watch, watchEffect } from 'vue';
242
249
  import { ArrowDown, ArrowUp, WarningFilled } from '@element-plus/icons-vue';
243
250
  import { isEqual } from 'lodash-es';
244
251
 
@@ -258,9 +265,17 @@ import type {
258
265
  FormValue,
259
266
  ToolTipConfigType,
260
267
  } from '../schema';
261
- import { FORM_DIFF_CONFIG_KEY } from '../schema';
262
- import { getField } from '../utils/config';
263
- import { createObjectProp, display as displayFunction, filterFunction, getRules } from '../utils/form';
268
+ import { FORM_DIFF_CONFIG_KEY, FORM_TYPE_MATCH_VALID_KEY } from '../schema';
269
+ import {
270
+ createObjectProp,
271
+ display as displayFunction,
272
+ filterFunction,
273
+ getItemProp,
274
+ getRules,
275
+ isValidName as isValidNameOf,
276
+ resolveItemType,
277
+ } from '../utils/form';
278
+ import { getField } from '../utils/registerField';
264
279
 
265
280
  import FormLabel from './FormLabel.vue';
266
281
 
@@ -280,11 +295,14 @@ const props = withDefaults(
280
295
  prop?: string;
281
296
  disabled?: boolean;
282
297
  labelWidth?: string | number;
298
+ labelPosition?: 'top' | 'left' | 'right';
283
299
  expandMore?: boolean;
284
300
  stepActive?: string | number;
285
301
  size?: string;
286
302
  /** 是否开启对比模式 */
287
303
  isCompare?: boolean;
304
+ /** 是否在行容器中 */
305
+ isInRow?: boolean;
288
306
  }>(),
289
307
  {
290
308
  prop: '',
@@ -305,6 +323,8 @@ const mForm = inject<FormState | undefined>('mForm');
305
323
  // 对比相关配置由 MForm 通过 provide 下发,这里直接 inject,无需逐层透传 prop。
306
324
  const diffConfig = inject<FormDiffConfig>(FORM_DIFF_CONFIG_KEY, {});
307
325
 
326
+ const typeMatchValid = inject(FORM_TYPE_MATCH_VALID_KEY);
327
+
308
328
  const expand = ref(false);
309
329
 
310
330
  const name = computed(() => props.config.name || '');
@@ -331,30 +351,14 @@ const showDiff = computed(() => {
331
351
 
332
352
  const items = computed(() => (props.config as ContainerCommonConfig).items);
333
353
 
334
- const itemProp = computed(() => {
335
- let n: string | number = '';
336
- if (name.value) {
337
- n = name.value;
338
- } else {
339
- return props.prop;
340
- }
341
-
342
- if (typeof props.prop !== 'undefined' && props.prop !== '') {
343
- return `${props.prop}.${n}`;
344
- }
354
+ const itemProp = computed(() => getItemProp(props.prop, name.value));
345
355
 
346
- return `${n}`;
347
- });
348
-
349
- const type = computed((): string => {
350
- let type = 'type' in props.config ? props.config.type : '';
351
- type = type && filterFunction<string>(mForm, type, props);
352
- if (type === 'form') return '';
353
- if (type === 'container') return '';
354
- return type?.replace(/([A-Z])/g, '-$1').toLowerCase() || (items.value ? '' : 'text');
355
- });
356
+ const type = computed((): string => resolveItemType(mForm, props.config, props));
356
357
 
357
358
  const tagName = computed(() => {
359
+ // `type: 'component'` 把任意 Vue 组件当字段渲染。无渲染校验把它当叶子、不遍历内部结构,
360
+ // 因此该组件不得再向父表单注册 FormItem(不要在内部挂会 addField 的 MContainer)。
361
+ // 需要嵌套表单项时,应对该具体组件 registerField(type, { innerConfig })。
358
362
  if (type.value === 'component' && (props.config as ComponentConfig).component) {
359
363
  return (props.config as ComponentConfig).component;
360
364
  }
@@ -420,7 +424,7 @@ const tooltip = computed(() => {
420
424
  };
421
425
  });
422
426
 
423
- const rule = computed(() => getRules(mForm, props.config.rules, props));
427
+ const rule = computed(() => getRules(mForm, props.config.rules, props, typeMatchValid));
424
428
 
425
429
  const display = computed((): boolean => {
426
430
  const value = displayFunction(mForm, props.config.display, props);
@@ -444,13 +448,17 @@ const fieldsProps = computed(() => ({
444
448
  const formItemProps = computed(() => ({
445
449
  prop: itemProp.value,
446
450
  labelWidth: itemLabelWidth.value,
447
- labelPosition: props.config.labelPosition,
451
+ labelPosition: itemLabelPosition.value,
452
+
448
453
  rules: rule.value,
449
454
  extra: filterFunction(mForm, props.config.extra, props),
455
+ extraTips: props.config.extraTips,
450
456
  }));
451
457
 
452
458
  const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);
453
459
 
460
+ const itemLabelPosition = computed(() => props.config.labelPosition ?? props.labelPosition);
461
+
454
462
  watchEffect(() => {
455
463
  expand.value = props.expandMore;
456
464
  });
@@ -470,6 +478,8 @@ watch(
470
478
  },
471
479
  );
472
480
 
481
+ provide('isInRow', props.isInRow ?? false);
482
+
473
483
  const expandHandler = () => (expand.value = !expand.value);
474
484
 
475
485
  const key = (config: any) => config[mForm?.keyProps];
@@ -505,22 +515,7 @@ const onAddDiffCount = () => emit('addDiffCount');
505
515
  const hasModifyKey = (eventDataItem: ContainerChangeEventData) =>
506
516
  typeof eventDataItem?.modifyKey !== 'undefined' && eventDataItem.modifyKey !== '';
507
517
 
508
- const isValidName = () => {
509
- const valueType = typeof name.value;
510
- if (valueType !== 'string' && valueType !== 'symbol' && valueType !== 'number') {
511
- return false;
512
- }
513
-
514
- if (name.value === '') {
515
- return false;
516
- }
517
-
518
- if (typeof name.value === 'number') {
519
- return name.value >= 0;
520
- }
521
-
522
- return true;
523
- };
518
+ const isValidName = () => isValidNameOf(name.value);
524
519
 
525
520
  const createModelProxy = (
526
521
  target: any,
@@ -28,6 +28,7 @@
28
28
  :prop="prop"
29
29
  :disabled="disabled"
30
30
  :labelWidth="lWidth"
31
+ :labelPosition="lPosition"
31
32
  :size="size"
32
33
  @change="changeHandler"
33
34
  @add-diff-count="onAddDiffCount()"
@@ -48,6 +49,7 @@
48
49
  :config="item"
49
50
  :prop="prop"
50
51
  :labelWidth="lWidth"
52
+ :labelPosition="lPosition"
51
53
  :size="size"
52
54
  :disabled="disabled"
53
55
  @change="changeHandler"
@@ -74,6 +76,7 @@ defineOptions({
74
76
  const props = withDefaults(
75
77
  defineProps<{
76
78
  labelWidth?: string;
79
+ labelPosition?: 'top' | 'left' | 'right';
77
80
  prop: string;
78
81
  size?: string;
79
82
  model: Record<string, any>;
@@ -140,6 +143,8 @@ const lWidth = computed(() => {
140
143
  return props.config.labelWidth || props.labelWidth || (props.config.text ? undefined : '0');
141
144
  });
142
145
 
146
+ const lPosition = computed(() => props.config.labelPosition || props.labelPosition);
147
+
143
148
  const valueChangeHandler = (value: number | boolean) => {
144
149
  emit('change', value, { modifyKey: checkboxName.value });
145
150
  };
@@ -11,6 +11,7 @@
11
11
  :size="size"
12
12
  :disabled="disabled"
13
13
  :label-width="config.labelWidth || labelWidth"
14
+ :label-position="config.labelPosition || labelPosition"
14
15
  @change="changeHandler"
15
16
  @addDiffCount="onAddDiffCount()"
16
17
  />
@@ -37,6 +38,7 @@ const props = defineProps<{
37
38
  config: FlexLayoutConfig;
38
39
  name?: string;
39
40
  labelWidth?: string;
41
+ labelPosition?: 'top' | 'left' | 'right';
40
42
  prop?: string;
41
43
  size?: string;
42
44
  disabled?: boolean;
@@ -1,32 +1,48 @@
1
1
  <template>
2
- <div class="m-fields-group-list">
2
+ <div
3
+ class="m-fields-group-list"
4
+ :class="{
5
+ 'is-header-sticky': Boolean(config.header?.sticky),
6
+ 'is-footer-sticky': isFooterSticky($slots),
7
+ }"
8
+ :style="headerVars"
9
+ >
3
10
  <div v-if="config.extra" v-html="config.extra" style="color: rgba(0, 0, 0, 0.45)"></div>
4
- <div v-if="!model[name] || !model[name].length" class="el-table__empty-block">
5
- <span class="el-table__empty-text t-table__empty">暂无数据</span>
11
+ <div v-if="!displayItems.length" class="el-table__empty-block">
12
+ <span class="el-table__empty-text t-table__empty">暂无{{ config.titlePrefix || '' }}数据</span>
6
13
  </div>
7
14
 
8
15
  <MFieldsGroupListItem
9
16
  v-else
10
- v-for="(item, index) in model[name]"
11
- :key="index"
12
- :model="item"
13
- :lastValues="getLastValues(lastValues?.[name], Number(index))"
17
+ v-for="entry in displayItems"
18
+ :key="entry.index"
19
+ :model="entry.item"
20
+ :lastValues="entry.last"
14
21
  :is-compare="isCompare"
15
22
  :config="config"
16
23
  :prop="prop"
17
- :index="Number(index)"
24
+ :index="entry.index"
18
25
  :label-width="labelWidth"
26
+ :label-position="labelPosition"
19
27
  :size="size"
20
28
  :disabled="disabled"
21
- :group-model="model[name]"
29
+ :group-model="currentList"
22
30
  @remove-item="removeHandler"
23
31
  @copy-item="copyHandler"
24
32
  @swap-item="swapHandler"
25
33
  @change="changeHandler"
26
34
  @addDiffCount="onAddDiffCount()"
27
- ></MFieldsGroupListItem>
28
-
29
- <div class="m-fields-group-list-footer" v-if="!isCompare">
35
+ >
36
+ <template #title="slotProps" v-if="$slots.title">
37
+ <slot name="title" v-bind="slotProps"></slot>
38
+ </template>
39
+ </MFieldsGroupListItem>
40
+
41
+ <div
42
+ class="m-fields-group-list-footer"
43
+ :class="{ 'is-sticky-full': Boolean(config.addButtonConfig?.sticky) }"
44
+ v-if="hasFooter($slots)"
45
+ >
30
46
  <slot name="toggle-button"></slot>
31
47
  <div style="display: flex; justify-content: flex-end; flex: 1">
32
48
  <slot name="add-button"></slot>
@@ -36,9 +52,11 @@
36
52
  </template>
37
53
 
38
54
  <script setup lang="ts">
55
+ import { computed } from 'vue';
39
56
  import { cloneDeep } from 'lodash-es';
40
57
 
41
58
  import type { ContainerChangeEventData, GroupListConfig } from '../schema';
59
+ import { getGroupListHeaderVars } from '../utils/tableGroupList';
42
60
 
43
61
  import MFieldsGroupListItem from './GroupListItem.vue';
44
62
 
@@ -53,6 +71,7 @@ const props = defineProps<{
53
71
  config: GroupListConfig;
54
72
  name: string;
55
73
  labelWidth?: string;
74
+ labelPosition?: 'top' | 'left' | 'right';
56
75
  prop?: string;
57
76
  size?: string;
58
77
  disabled?: boolean;
@@ -91,5 +110,37 @@ const swapHandler = (idx1: number, idx2: number) => {
91
110
 
92
111
  const onAddDiffCount = () => emit('addDiffCount');
93
112
 
94
- const getLastValues = (item: any, index: number) => item?.[index] || {};
113
+ const asList = (value: unknown): any[] => (Array.isArray(value) ? value : []);
114
+
115
+ const headerVars = computed(() => getGroupListHeaderVars(props.config.header));
116
+
117
+ /**
118
+ * 取 `$slots` 而不是 `useSlots()`:宿主切换 `addable` 时插槽会增删,
119
+ * computed 缓存不随插槽变化失效,只有在渲染期读才拿得到最新的。
120
+ */
121
+ const hasFooter = (slots: Record<string, unknown>) =>
122
+ !props.isCompare && Boolean(slots['toggle-button'] || slots['add-button']);
123
+
124
+ /** 只有真的渲染出吸底 footer,内层列表才需要为它让位 */
125
+ const isFooterSticky = (slots: Record<string, unknown>) =>
126
+ hasFooter(slots) && Boolean(props.config.addButtonConfig?.sticky);
127
+
128
+ const currentList = computed(() => asList(props.model[props.name]));
129
+
130
+ /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
131
+ const displayItems = computed(() => {
132
+ const current = currentList.value;
133
+
134
+ if (!props.isCompare) {
135
+ return current.map((item, index) => ({ item: item ?? {}, last: {}, index }));
136
+ }
137
+
138
+ const last = asList(props.lastValues?.[props.name]);
139
+
140
+ return Array.from({ length: Math.max(current.length, last.length) }, (_, index) => ({
141
+ item: current[index] ?? {},
142
+ last: last[index] ?? {},
143
+ index,
144
+ }));
145
+ });
95
146
  </script>
@@ -6,88 +6,95 @@
6
6
  <TMagicIcon><ArrowDown v-if="expand" /><ArrowRight v-else /></TMagicIcon>
7
7
  </TMagicButton>
8
8
 
9
- <span v-html="title"></span>
10
-
11
- <TMagicButton
12
- v-if="!isCompare"
13
- v-show="showDelete"
14
- size="default"
15
- link
16
- class="delete-button"
17
- :icon="Delete"
18
- :disabled="disabled"
19
- @click="removeHandler"
20
- ></TMagicButton>
21
-
22
- <TMagicButton
23
- v-if="copyable && !isCompare"
24
- link
25
- size="default"
26
- type="primary"
27
- :icon="DocumentCopy"
28
- :disabled="disabled"
29
- @click="copyHandler"
30
- >复制</TMagicButton
31
- >
32
-
33
- <template v-if="movable && !isCompare">
9
+ <div class="m-fields-group-list-item-title">
10
+ <slot name="title" :model="model" :index="index" :last-values="lastValues" :prop="rowProp" :title="title">
11
+ <span v-html="title"></span>
12
+ </slot>
13
+ </div>
14
+ <div class="m-fields-group-list-item-actions">
34
15
  <TMagicButton
35
- v-show="index !== 0"
16
+ v-if="copyable && !isCompare"
36
17
  link
37
18
  size="default"
19
+ type="primary"
20
+ :icon="DocumentCopy"
38
21
  :disabled="disabled"
39
- :icon="Top"
40
- @click="changeOrder(-1)"
41
- >上移</TMagicButton
22
+ @click="copyHandler"
23
+ >复制</TMagicButton
42
24
  >
43
- <TMagicButton
44
- v-show="index !== length - 1"
45
- link
46
- size="default"
47
- :disabled="disabled"
48
- :icon="Bottom"
49
- @click="changeOrder(1)"
50
- >下移</TMagicButton
51
- >
52
- </template>
53
-
54
- <TMagicPopover
55
- v-if="config.moveSpecifyLocation && !isCompare"
56
- trigger="click"
57
- placement="top"
58
- width="200"
59
- :visible="moveSpecifyLocationVisible"
60
- >
61
- <template #reference>
25
+
26
+ <template v-if="movable && !isCompare">
27
+ <TMagicButton
28
+ v-show="index !== 0"
29
+ link
30
+ size="default"
31
+ :disabled="disabled"
32
+ :icon="Top"
33
+ @click="changeOrder(-1)"
34
+ >上移</TMagicButton
35
+ >
62
36
  <TMagicButton
37
+ v-show="index !== length - 1"
63
38
  link
64
- size="small"
65
- type="primary"
66
- :icon="Position"
39
+ size="default"
67
40
  :disabled="disabled"
68
- @click="moveSpecifyLocationVisible = true"
69
- >移动至</TMagicButton
41
+ :icon="Bottom"
42
+ @click="changeOrder(1)"
43
+ >下移</TMagicButton
70
44
  >
71
45
  </template>
72
- <div>
73
- <div>
74
- 第<TMagicInputNumber
75
- style="margin: 0 5px"
76
- v-model="moveSpecifyLocationIndex"
46
+
47
+ <TMagicPopover
48
+ v-if="config.moveSpecifyLocation && !isCompare"
49
+ trigger="click"
50
+ placement="top"
51
+ width="200"
52
+ :visible="moveSpecifyLocationVisible"
53
+ >
54
+ <template #reference>
55
+ <TMagicButton
56
+ link
77
57
  size="small"
78
- :min="1"
58
+ type="primary"
59
+ :icon="Position"
79
60
  :disabled="disabled"
80
- ></TMagicInputNumber
81
- >行
82
- </div>
83
- <div style="text-align: right; margin-top: 20px">
84
- <TMagicButton size="small" text @click="moveSpecifyLocationVisible = false">取消</TMagicButton>
85
- <TMagicButton size="small" type="primary" @click="moveSpecifyLocationHandler">确认</TMagicButton>
61
+ @click="moveSpecifyLocationVisible = true"
62
+ >移动至</TMagicButton
63
+ >
64
+ </template>
65
+ <div>
66
+ <div>
67
+ 第<TMagicInputNumber
68
+ style="margin: 0 5px"
69
+ v-model="moveSpecifyLocationIndex"
70
+ size="small"
71
+ :min="1"
72
+ :disabled="disabled"
73
+ ></TMagicInputNumber
74
+ >行
75
+ </div>
76
+ <div style="text-align: right; margin-top: 20px">
77
+ <TMagicButton size="small" text @click="moveSpecifyLocationVisible = false">取消</TMagicButton>
78
+ <TMagicButton size="small" type="primary" @click="moveSpecifyLocationHandler">确认</TMagicButton>
79
+ </div>
86
80
  </div>
87
- </div>
88
- </TMagicPopover>
81
+ </TMagicPopover>
89
82
 
90
- <span v-if="itemExtra" v-html="itemExtra" class="m-form-tip"></span>
83
+ <!-- v-if 必须挂在 Tooltip 上:子节点为 false 时 ElOnlyChild 会报 no valid child node found -->
84
+ <TMagicTooltip v-if="!isCompare" :content="`删除 ${title}`">
85
+ <TMagicButton
86
+ v-show="showDelete"
87
+ size="default"
88
+ link
89
+ class="delete-button"
90
+ :icon="Delete"
91
+ :disabled="disabled"
92
+ @click="removeHandler"
93
+ ></TMagicButton>
94
+ </TMagicTooltip>
95
+
96
+ <span v-if="itemExtra" v-html="itemExtra" class="m-form-tip"></span>
97
+ </div>
91
98
  </div>
92
99
  </template>
93
100
 
@@ -98,7 +105,8 @@
98
105
  :lastValues="lastValues"
99
106
  :is-compare="isCompare"
100
107
  :labelWidth="labelWidth"
101
- :prop="`${prop}${prop ? '.' : ''}${String(index)}`"
108
+ :labelPosition="labelPosition"
109
+ :prop="rowProp"
102
110
  :size="size"
103
111
  :disabled="disabled"
104
112
  @change="changeHandler"
@@ -111,10 +119,11 @@
111
119
  import { computed, inject, ref } from 'vue';
112
120
  import { ArrowDown, ArrowRight, Bottom, Delete, DocumentCopy, Position, Top } from '@element-plus/icons-vue';
113
121
 
114
- import { TMagicButton, TMagicCard, TMagicIcon, TMagicInputNumber, TMagicPopover } from '@tmagic/design';
122
+ import { TMagicButton, TMagicCard, TMagicIcon, TMagicInputNumber, TMagicPopover, TMagicTooltip } from '@tmagic/design';
115
123
 
116
124
  import type { ContainerChangeEventData, FormState, GroupListConfig } from '../schema';
117
- import { filterFunction } from '../utils/form';
125
+ import { appendProp, filterFunction } from '../utils/form';
126
+ import { getGroupListRowConfig } from '../utils/tableGroupList';
118
127
 
119
128
  import Container from './Container.vue';
120
129
 
@@ -129,6 +138,7 @@ const props = defineProps<{
129
138
  groupModel: any[];
130
139
  config: GroupListConfig;
131
140
  labelWidth?: string;
141
+ labelPosition?: 'top' | 'left' | 'right';
132
142
  prop?: string;
133
143
  size?: string;
134
144
  index: number;
@@ -142,19 +152,16 @@ const mForm = inject<FormState | undefined>('mForm');
142
152
  const defaultExpandQuantity = props.config.defaultExpandQuantity ?? 7;
143
153
  const expand = ref(props.config.expandAll || defaultExpandQuantity > props.index);
144
154
 
145
- const rowConfig = computed(() => ({
146
- type: 'row',
147
- span: props.config.span || 24,
148
- items: props.config.items,
149
- labelWidth: props.config.labelWidth,
150
- [mForm?.keyProp || '__key']: `${(props.config as Record<string, any>)[mForm?.keyProp || '__key']}${String(
151
- props.index,
152
- )}`,
153
- }));
155
+ const rowConfig = computed(() => getGroupListRowConfig(props.config, props.index, mForm?.keyProp));
156
+
157
+ const rowProp = computed(() => appendProp(props.prop, props.index));
154
158
 
155
159
  const title = computed(() => {
156
160
  if (props.config.titleKey && props.model[props.config.titleKey]) {
157
- return props.model[props.config.titleKey];
161
+ const { titlePrefix } = props.config;
162
+ return titlePrefix
163
+ ? `${titlePrefix} ${String(props.index + 1)}: ${props.model[props.config.titleKey]}`
164
+ : `${props.model[props.config.titleKey]}`;
158
165
  }
159
166
 
160
167
  if (props.config.title) {
@@ -39,6 +39,7 @@
39
39
  :size="size"
40
40
  :disabled="disabled"
41
41
  :label-width="config.labelWidth || labelWidth"
42
+ :label-position="config.labelPosition || labelPosition"
42
43
  @change="changeHandler"
43
44
  @addDiffCount="onAddDiffCount()"
44
45
  ></Container>
@@ -59,6 +60,7 @@
59
60
  :size="size"
60
61
  :disabled="disabled"
61
62
  :label-width="config.labelWidth || labelWidth"
63
+ :label-position="config.labelPosition || labelPosition"
62
64
  @change="changeHandler"
63
65
  @addDiffCount="onAddDiffCount()"
64
66
  ></Container>
@@ -89,6 +91,7 @@ const props = defineProps<{
89
91
  config: PanelConfig;
90
92
  name?: string;
91
93
  labelWidth?: string;
94
+ labelPosition?: 'top' | 'left' | 'right';
92
95
  prop?: string;
93
96
  size?: string;
94
97
  disabled?: boolean;
@@ -6,6 +6,7 @@
6
6
  :span="col.span || config.span || 24 / config.items.length"
7
7
  :config="col"
8
8
  :labelWidth="config.labelWidth || labelWidth"
9
+ :labelPosition="config.labelPosition || labelPosition"
9
10
  :expandMore="expandMore"
10
11
  :model="name ? model[name] : model"
11
12
  :lastValues="name ? lastValues[name] : lastValues"
@@ -39,6 +40,7 @@ const props = defineProps<{
39
40
  config: RowConfig;
40
41
  name: string;
41
42
  labelWidth?: string;
43
+ labelPosition?: 'top' | 'left' | 'right';
42
44
  prop?: string;
43
45
  size?: string;
44
46
  expandMore?: boolean;
@@ -24,6 +24,7 @@
24
24
  :size="size"
25
25
  :disabled="disabled"
26
26
  :label-width="config.labelWidth || labelWidth"
27
+ :label-position="config.labelPosition || labelPosition"
27
28
  @change="changeHandler"
28
29
  @addDiffCount="onAddDiffCount()"
29
30
  ></Container>
@@ -54,6 +55,7 @@ const props = withDefaults(
54
55
  config: StepConfig;
55
56
  stepActive?: number;
56
57
  labelWidth?: string;
58
+ labelPosition?: 'top' | 'left' | 'right';
57
59
  size?: string;
58
60
  disabled?: boolean;
59
61
  }>(),