@tmagic/form 1.8.0-beta.27 → 1.8.0-beta.28

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.
@@ -1,15 +1,15 @@
1
+ import { getGroupListHeaderVars } from "../utils/tableGroupList.js";
1
2
  import GroupListItem_default from "./GroupListItem.js";
2
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, defineComponent, mergeProps, normalizeClass, openBlock, renderList, renderSlot, toDisplayString, withCtx } from "vue";
3
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, defineComponent, mergeProps, normalizeClass, normalizeStyle, openBlock, renderList, renderSlot, toDisplayString, withCtx } from "vue";
3
4
  import { cloneDeep } from "lodash-es";
4
5
  //#region packages/form/src/containers/GroupList.vue?vue&type=script&setup=true&lang.ts
5
- var _hoisted_1 = { class: "m-fields-group-list" };
6
- var _hoisted_2 = ["innerHTML"];
7
- var _hoisted_3 = {
6
+ var _hoisted_1 = ["innerHTML"];
7
+ var _hoisted_2 = {
8
8
  key: 1,
9
9
  class: "el-table__empty-block"
10
10
  };
11
- var _hoisted_4 = { class: "el-table__empty-text t-table__empty" };
12
- var _hoisted_5 = { style: {
11
+ var _hoisted_3 = { class: "el-table__empty-text t-table__empty" };
12
+ var _hoisted_4 = { style: {
13
13
  "display": "flex",
14
14
  "justify-content": "flex-end",
15
15
  "flex": "1"
@@ -54,6 +54,14 @@ var GroupList_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
54
54
  };
55
55
  const onAddDiffCount = () => emit("addDiffCount");
56
56
  const asList = (value) => Array.isArray(value) ? value : [];
57
+ const headerVars = computed(() => getGroupListHeaderVars(props.config.header));
58
+ /**
59
+ * 取 `$slots` 而不是 `useSlots()`:宿主切换 `addable` 时插槽会增删,
60
+ * computed 缓存不随插槽变化失效,只有在渲染期读才拿得到最新的。
61
+ */
62
+ const hasFooter = (slots) => !props.isCompare && Boolean(slots["toggle-button"] || slots["add-button"]);
63
+ /** 只有真的渲染出吸底 footer,内层列表才需要为它让位 */
64
+ const isFooterSticky = (slots) => hasFooter(slots) && Boolean(props.config.addButtonConfig?.sticky);
57
65
  const currentList = computed(() => asList(props.model[props.name]));
58
66
  /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
59
67
  const displayItems = computed(() => {
@@ -71,13 +79,19 @@ var GroupList_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
71
79
  }));
72
80
  });
73
81
  return (_ctx, _cache) => {
74
- return openBlock(), createElementBlock("div", _hoisted_1, [
82
+ return openBlock(), createElementBlock("div", {
83
+ class: normalizeClass(["m-fields-group-list", {
84
+ "is-header-sticky": Boolean(__props.config.header?.sticky),
85
+ "is-footer-sticky": isFooterSticky(_ctx.$slots)
86
+ }]),
87
+ style: normalizeStyle(headerVars.value)
88
+ }, [
75
89
  __props.config.extra ? (openBlock(), createElementBlock("div", {
76
90
  key: 0,
77
91
  innerHTML: __props.config.extra,
78
92
  style: { "color": "rgba(0, 0, 0, 0.45)" }
79
- }, null, 8, _hoisted_2)) : createCommentVNode("v-if", true),
80
- !displayItems.value.length ? (openBlock(), createElementBlock("div", _hoisted_3, [createElementVNode("span", _hoisted_4, "暂无" + toDisplayString(__props.config.titlePrefix || "") + "数据", 1)])) : (openBlock(true), createElementBlock(Fragment, { key: 2 }, renderList(displayItems.value, (entry) => {
93
+ }, null, 8, _hoisted_1)) : createCommentVNode("v-if", true),
94
+ !displayItems.value.length ? (openBlock(), createElementBlock("div", _hoisted_2, [createElementVNode("span", _hoisted_3, "暂无" + toDisplayString(__props.config.titlePrefix || "") + "数据", 1)])) : (openBlock(true), createElementBlock(Fragment, { key: 2 }, renderList(displayItems.value, (entry) => {
81
95
  return openBlock(), createBlock(GroupListItem_default, {
82
96
  key: entry.index,
83
97
  model: entry.item,
@@ -114,11 +128,11 @@ var GroupList_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
114
128
  "group-model"
115
129
  ]);
116
130
  }), 128)),
117
- !__props.isCompare && (_ctx.$slots["toggle-button"] || _ctx.$slots["add-button"]) ? (openBlock(), createElementBlock("div", {
131
+ hasFooter(_ctx.$slots) ? (openBlock(), createElementBlock("div", {
118
132
  key: 3,
119
133
  class: normalizeClass(["m-fields-group-list-footer", { "is-sticky-full": Boolean(__props.config.addButtonConfig?.sticky) }])
120
- }, [renderSlot(_ctx.$slots, "toggle-button"), createElementVNode("div", _hoisted_5, [renderSlot(_ctx.$slots, "add-button")])], 2)) : createCommentVNode("v-if", true)
121
- ]);
134
+ }, [renderSlot(_ctx.$slots, "toggle-button"), createElementVNode("div", _hoisted_4, [renderSlot(_ctx.$slots, "add-button")])], 2)) : createCommentVNode("v-if", true)
135
+ ], 6);
122
136
  };
123
137
  }
124
138
  });
package/dist/es/style.css CHANGED
@@ -333,18 +333,48 @@ fieldset.m-fieldset .m-form-tip {
333
333
  flex-shrink: 0;
334
334
  gap: 8px;
335
335
  }
336
- .m-fields-group-list .m-fields-group-list-item > .el-card__header,
337
- .m-fields-group-list .m-fields-group-list-item > .t-card__header {
336
+ .m-fields-group-list > .m-fields-group-list-item {
337
+ --m-group-list-item-footer-bottom: var(
338
+ --m-group-list-nested-footer-bottom,
339
+ 0px
340
+ );
341
+ }
342
+ .m-fields-group-list.is-footer-sticky > .m-fields-group-list-item {
343
+ --m-group-list-item-footer-bottom: calc(
344
+ var(--m-group-list-nested-footer-bottom, 0px) +
345
+ var(--m-group-list-footer-height)
346
+ );
347
+ }
348
+ .m-fields-group-list .m-fields-group-list-item .m-fields-group-list {
349
+ --m-group-list-nested-footer-bottom: var(
350
+ --m-group-list-item-footer-bottom,
351
+ 0px
352
+ );
353
+ }
354
+ .m-fields-group-list.is-header-sticky {
355
+ --m-group-list-nested-sticky-top: var(--m-group-list-child-list-sticky-top);
356
+ --m-group-list-nested-header-z: var(--m-group-list-child-list-header-z);
357
+ }
358
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item {
359
+ --m-group-list-item-sticky-top: var(
360
+ --m-group-list-nested-sticky-top,
361
+ var(--m-group-list-header-sticky-top, 0px)
362
+ );
363
+ --m-group-list-item-header-z: var(--m-group-list-nested-header-z, 7);
364
+ --m-group-list-child-list-sticky-top: calc(
365
+ var(--m-group-list-item-sticky-top) + var(--m-group-list-header-height)
366
+ );
367
+ --m-group-list-child-list-header-z: calc(
368
+ var(--m-group-list-item-header-z) - 1
369
+ );
370
+ }
371
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item > .el-card__header,
372
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item > .t-card__header {
338
373
  position: sticky;
339
- top: var(--m-group-list-header-sticky-top, 0px);
340
- z-index: 7;
374
+ top: var(--m-group-list-item-sticky-top);
375
+ z-index: var(--m-group-list-item-header-z);
341
376
  background-color: var(--m-group-list-header-bg, #fff);
342
377
  }
343
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list-item > .el-card__header,
344
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list-item > .t-card__header {
345
- top: calc(var(--m-group-list-header-sticky-top, 0px) + var(--m-group-list-header-height));
346
- z-index: 6;
347
- }
348
378
  .m-fields-group-list .el-table__empty-block {
349
379
  width: 100%;
350
380
  min-height: 0;
@@ -362,7 +392,7 @@ fieldset.m-fieldset .m-form-tip {
362
392
  }
363
393
  .m-fields-group-list > .m-fields-group-list-footer.is-sticky-full {
364
394
  position: sticky;
365
- bottom: 0;
395
+ bottom: var(--m-group-list-nested-footer-bottom, 0px);
366
396
  z-index: 7;
367
397
  margin-bottom: 0;
368
398
  padding: var(--m-group-list-footer-padding-top) 0 var(--m-group-list-footer-padding-bottom);
@@ -376,10 +406,6 @@ fieldset.m-fieldset .m-form-tip {
376
406
  width: 100%;
377
407
  height: var(--m-group-list-footer-button-height);
378
408
  }
379
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list > .m-fields-group-list-footer.is-sticky-full {
380
- bottom: var(--m-group-list-footer-height);
381
- z-index: 7;
382
- }
383
409
 
384
410
  /** 最外层的groupList需要每个item需要增加空白区域界限时,增加outer-gorup_list可以实现 */
385
411
  .m-container-group-list.outer-gorup_list > .m-fields-group-list > .m-fields-group-list-item {
@@ -1,4 +1,5 @@
1
1
  import { cloneDeep } from "lodash-es";
2
+ import { isNumber } from "@tmagic/utils";
2
3
  //#region packages/form/src/utils/tableGroupList.ts
3
4
  /**
4
5
  * table / group-list 是同一份配置的两种展示形态,可以互相切换(`TableGroupList.vue`)。
@@ -9,6 +10,24 @@ import { cloneDeep } from "lodash-es";
9
10
  */
10
11
  /** group-list 形态的 type(兼容驼峰与中划线两种写法) */
11
12
  var isGroupListType = (type) => type === "groupList" || type === "group-list";
13
+ /**
14
+ * 归一成带单位的 CSS 长度。
15
+ *
16
+ * 配置常常来自 JSON 或表单输入,`'48'` 这类无单位数字串很常见;直接写进 CSS 变量会让
17
+ * 嵌套层的 `calc()` 整体失效、吸顶静默失灵,所以纯数字一律补 `px`。
18
+ */
19
+ var toCssLength = (height) => {
20
+ const value = `${height ?? ""}`.trim();
21
+ if (!value) return void 0;
22
+ if (isNumber(value)) return `${value}px`;
23
+ return typeof height === "number" ? void 0 : value;
24
+ };
25
+ /** header.sticky 开启时把 height 写成 CSS 变量,给嵌套吸顶偏移用 */
26
+ var getGroupListHeaderVars = (header) => {
27
+ if (!header?.sticky) return void 0;
28
+ const height = toCssLength(header.height);
29
+ return height ? { "--m-group-list-header-height": height } : void 0;
30
+ };
12
31
  /** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
13
32
  var calcLabelWidth = (label) => {
14
33
  if (!label) return "0px";
@@ -82,4 +101,4 @@ var makeTableColumnConfig = (column, row) => {
82
101
  return newConfig;
83
102
  };
84
103
  //#endregion
85
- export { calcLabelWidth, getGroupListRowConfig, isGroupListType, isTableColumnRendered, makeTableColumnConfig, toGroupListConfig, toTableConfig };
104
+ export { calcLabelWidth, getGroupListHeaderVars, getGroupListRowConfig, isGroupListType, isTableColumnRendered, makeTableColumnConfig, toGroupListConfig, toTableConfig };
package/dist/style.css CHANGED
@@ -333,18 +333,48 @@ fieldset.m-fieldset .m-form-tip {
333
333
  flex-shrink: 0;
334
334
  gap: 8px;
335
335
  }
336
- .m-fields-group-list .m-fields-group-list-item > .el-card__header,
337
- .m-fields-group-list .m-fields-group-list-item > .t-card__header {
336
+ .m-fields-group-list > .m-fields-group-list-item {
337
+ --m-group-list-item-footer-bottom: var(
338
+ --m-group-list-nested-footer-bottom,
339
+ 0px
340
+ );
341
+ }
342
+ .m-fields-group-list.is-footer-sticky > .m-fields-group-list-item {
343
+ --m-group-list-item-footer-bottom: calc(
344
+ var(--m-group-list-nested-footer-bottom, 0px) +
345
+ var(--m-group-list-footer-height)
346
+ );
347
+ }
348
+ .m-fields-group-list .m-fields-group-list-item .m-fields-group-list {
349
+ --m-group-list-nested-footer-bottom: var(
350
+ --m-group-list-item-footer-bottom,
351
+ 0px
352
+ );
353
+ }
354
+ .m-fields-group-list.is-header-sticky {
355
+ --m-group-list-nested-sticky-top: var(--m-group-list-child-list-sticky-top);
356
+ --m-group-list-nested-header-z: var(--m-group-list-child-list-header-z);
357
+ }
358
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item {
359
+ --m-group-list-item-sticky-top: var(
360
+ --m-group-list-nested-sticky-top,
361
+ var(--m-group-list-header-sticky-top, 0px)
362
+ );
363
+ --m-group-list-item-header-z: var(--m-group-list-nested-header-z, 7);
364
+ --m-group-list-child-list-sticky-top: calc(
365
+ var(--m-group-list-item-sticky-top) + var(--m-group-list-header-height)
366
+ );
367
+ --m-group-list-child-list-header-z: calc(
368
+ var(--m-group-list-item-header-z) - 1
369
+ );
370
+ }
371
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item > .el-card__header,
372
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item > .t-card__header {
338
373
  position: sticky;
339
- top: var(--m-group-list-header-sticky-top, 0px);
340
- z-index: 7;
374
+ top: var(--m-group-list-item-sticky-top);
375
+ z-index: var(--m-group-list-item-header-z);
341
376
  background-color: var(--m-group-list-header-bg, #fff);
342
377
  }
343
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list-item > .el-card__header,
344
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list-item > .t-card__header {
345
- top: calc(var(--m-group-list-header-sticky-top, 0px) + var(--m-group-list-header-height));
346
- z-index: 6;
347
- }
348
378
  .m-fields-group-list .el-table__empty-block {
349
379
  width: 100%;
350
380
  min-height: 0;
@@ -362,7 +392,7 @@ fieldset.m-fieldset .m-form-tip {
362
392
  }
363
393
  .m-fields-group-list > .m-fields-group-list-footer.is-sticky-full {
364
394
  position: sticky;
365
- bottom: 0;
395
+ bottom: var(--m-group-list-nested-footer-bottom, 0px);
366
396
  z-index: 7;
367
397
  margin-bottom: 0;
368
398
  padding: var(--m-group-list-footer-padding-top) 0 var(--m-group-list-footer-padding-bottom);
@@ -376,10 +406,6 @@ fieldset.m-fieldset .m-form-tip {
376
406
  width: 100%;
377
407
  height: var(--m-group-list-footer-button-height);
378
408
  }
379
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list > .m-fields-group-list-footer.is-sticky-full {
380
- bottom: var(--m-group-list-footer-height);
381
- z-index: 7;
382
- }
383
409
 
384
410
  /** 最外层的groupList需要每个item需要增加空白区域界限时,增加outer-gorup_list可以实现 */
385
411
  .m-container-group-list.outer-gorup_list > .m-fields-group-list > .m-fields-group-list-item {
@@ -333,18 +333,48 @@ fieldset.m-fieldset .m-form-tip {
333
333
  flex-shrink: 0;
334
334
  gap: 8px;
335
335
  }
336
- .m-fields-group-list .m-fields-group-list-item > .el-card__header,
337
- .m-fields-group-list .m-fields-group-list-item > .t-card__header {
336
+ .m-fields-group-list > .m-fields-group-list-item {
337
+ --m-group-list-item-footer-bottom: var(
338
+ --m-group-list-nested-footer-bottom,
339
+ 0px
340
+ );
341
+ }
342
+ .m-fields-group-list.is-footer-sticky > .m-fields-group-list-item {
343
+ --m-group-list-item-footer-bottom: calc(
344
+ var(--m-group-list-nested-footer-bottom, 0px) +
345
+ var(--m-group-list-footer-height)
346
+ );
347
+ }
348
+ .m-fields-group-list .m-fields-group-list-item .m-fields-group-list {
349
+ --m-group-list-nested-footer-bottom: var(
350
+ --m-group-list-item-footer-bottom,
351
+ 0px
352
+ );
353
+ }
354
+ .m-fields-group-list.is-header-sticky {
355
+ --m-group-list-nested-sticky-top: var(--m-group-list-child-list-sticky-top);
356
+ --m-group-list-nested-header-z: var(--m-group-list-child-list-header-z);
357
+ }
358
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item {
359
+ --m-group-list-item-sticky-top: var(
360
+ --m-group-list-nested-sticky-top,
361
+ var(--m-group-list-header-sticky-top, 0px)
362
+ );
363
+ --m-group-list-item-header-z: var(--m-group-list-nested-header-z, 7);
364
+ --m-group-list-child-list-sticky-top: calc(
365
+ var(--m-group-list-item-sticky-top) + var(--m-group-list-header-height)
366
+ );
367
+ --m-group-list-child-list-header-z: calc(
368
+ var(--m-group-list-item-header-z) - 1
369
+ );
370
+ }
371
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item > .el-card__header,
372
+ .m-fields-group-list.is-header-sticky > .m-fields-group-list-item > .t-card__header {
338
373
  position: sticky;
339
- top: var(--m-group-list-header-sticky-top, 0px);
340
- z-index: 7;
374
+ top: var(--m-group-list-item-sticky-top);
375
+ z-index: var(--m-group-list-item-header-z);
341
376
  background-color: var(--m-group-list-header-bg, #fff);
342
377
  }
343
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list-item > .el-card__header,
344
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list-item > .t-card__header {
345
- top: calc(var(--m-group-list-header-sticky-top, 0px) + var(--m-group-list-header-height));
346
- z-index: 6;
347
- }
348
378
  .m-fields-group-list .el-table__empty-block {
349
379
  width: 100%;
350
380
  min-height: 0;
@@ -362,7 +392,7 @@ fieldset.m-fieldset .m-form-tip {
362
392
  }
363
393
  .m-fields-group-list > .m-fields-group-list-footer.is-sticky-full {
364
394
  position: sticky;
365
- bottom: 0;
395
+ bottom: var(--m-group-list-nested-footer-bottom, 0px);
366
396
  z-index: 7;
367
397
  margin-bottom: 0;
368
398
  padding: var(--m-group-list-footer-padding-top) 0 var(--m-group-list-footer-padding-bottom);
@@ -376,10 +406,6 @@ fieldset.m-fieldset .m-form-tip {
376
406
  width: 100%;
377
407
  height: var(--m-group-list-footer-button-height);
378
408
  }
379
- .m-fields-group-list .m-fields-group-list-item .m-fields-group-list > .m-fields-group-list-footer.is-sticky-full {
380
- bottom: var(--m-group-list-footer-height);
381
- z-index: 7;
382
- }
383
409
 
384
410
  /** 最外层的groupList需要每个item需要增加空白区域界限时,增加outer-gorup_list可以实现 */
385
411
  .m-container-group-list.outer-gorup_list > .m-fields-group-list > .m-fields-group-list-item {
@@ -3795,6 +3795,24 @@
3795
3795
  */
3796
3796
  /** group-list 形态的 type(兼容驼峰与中划线两种写法) */
3797
3797
  var isGroupListType = (type) => type === "groupList" || type === "group-list";
3798
+ /**
3799
+ * 归一成带单位的 CSS 长度。
3800
+ *
3801
+ * 配置常常来自 JSON 或表单输入,`'48'` 这类无单位数字串很常见;直接写进 CSS 变量会让
3802
+ * 嵌套层的 `calc()` 整体失效、吸顶静默失灵,所以纯数字一律补 `px`。
3803
+ */
3804
+ var toCssLength = (height) => {
3805
+ const value = `${height ?? ""}`.trim();
3806
+ if (!value) return void 0;
3807
+ if ((0, _tmagic_utils.isNumber)(value)) return `${value}px`;
3808
+ return typeof height === "number" ? void 0 : value;
3809
+ };
3810
+ /** header.sticky 开启时把 height 写成 CSS 变量,给嵌套吸顶偏移用 */
3811
+ var getGroupListHeaderVars = (header) => {
3812
+ if (!header?.sticky) return void 0;
3813
+ const height = toCssLength(header.height);
3814
+ return height ? { "--m-group-list-header-height": height } : void 0;
3815
+ };
3798
3816
  /** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
3799
3817
  var calcLabelWidth = (label) => {
3800
3818
  if (!label) return "0px";
@@ -4725,7 +4743,7 @@
4725
4743
  };
4726
4744
  var _hoisted_3$7 = ["innerHTML"];
4727
4745
  var _hoisted_4$6 = ["innerHTML"];
4728
- var _hoisted_5$5 = ["innerHTML"];
4746
+ var _hoisted_5$4 = ["innerHTML"];
4729
4747
  var _hoisted_6$3 = ["innerHTML"];
4730
4748
  var _hoisted_7$2 = ["innerHTML"];
4731
4749
  var _hoisted_8$2 = ["innerHTML"];
@@ -5080,7 +5098,7 @@
5080
5098
  key: 0,
5081
5099
  placement: tooltip.value.placement
5082
5100
  }, {
5083
- content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$5)]),
5101
+ content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$4)]),
5084
5102
  default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)(fieldsProps.value, {
5085
5103
  model: __props.model,
5086
5104
  "last-values": __props.lastValues,
@@ -6478,7 +6496,7 @@
6478
6496
  var _hoisted_2$7 = ["innerHTML"];
6479
6497
  var _hoisted_3$6 = { key: 1 };
6480
6498
  var _hoisted_4$5 = ["innerHTML"];
6481
- var _hoisted_5$4 = ["innerHTML"];
6499
+ var _hoisted_5$3 = ["innerHTML"];
6482
6500
  var _hoisted_6$2 = {
6483
6501
  key: 2,
6484
6502
  style: { "display": "flex" }
@@ -6569,7 +6587,7 @@
6569
6587
  key: 0,
6570
6588
  innerHTML: __props.config.extra,
6571
6589
  class: "m-form-tip"
6572
- }, null, 8, _hoisted_5$4)) : (0, vue.createCommentVNode)("v-if", true)])), __props.config.schematic && show.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$2, [(0, vue.createElementVNode)("div", _hoisted_7$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (item, index) => {
6590
+ }, null, 8, _hoisted_5$3)) : (0, vue.createCommentVNode)("v-if", true)])), __props.config.schematic && show.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$2, [(0, vue.createElementVNode)("div", _hoisted_7$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (item, index) => {
6573
6591
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
6574
6592
  key: key(item, index),
6575
6593
  model: name.value ? __props.model[name.value] : __props.model,
@@ -6707,7 +6725,7 @@
6707
6725
  style: { "display": "flex" }
6708
6726
  };
6709
6727
  var _hoisted_4$4 = { style: { "flex": "1" } };
6710
- var _hoisted_5$3 = ["src"];
6728
+ var _hoisted_5$2 = ["src"];
6711
6729
  //#endregion
6712
6730
  //#region packages/form/src/containers/Panel.vue
6713
6731
  var Panel_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -6800,7 +6818,7 @@
6800
6818
  }), 128))]), (0, vue.createElementVNode)("img", {
6801
6819
  class: "m-form-schematic",
6802
6820
  src: __props.config.schematic
6803
- }, null, 8, _hoisted_5$3)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(items.value, (item, index) => {
6821
+ }, null, 8, _hoisted_5$2)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(items.value, (item, index) => {
6804
6822
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
6805
6823
  key: item[(0, vue.unref)(mForm)?.keyProp || "__key"] ?? index,
6806
6824
  config: item,
@@ -7158,7 +7176,7 @@
7158
7176
  var _hoisted_2$5 = { class: "m-fields-group-list-item-title" };
7159
7177
  var _hoisted_3$4 = ["innerHTML"];
7160
7178
  var _hoisted_4$3 = { class: "m-fields-group-list-item-actions" };
7161
- var _hoisted_5$2 = { style: {
7179
+ var _hoisted_5$1 = { style: {
7162
7180
  "text-align": "right",
7163
7181
  "margin-top": "20px"
7164
7182
  } };
@@ -7323,7 +7341,7 @@
7323
7341
  disabled: __props.disabled
7324
7342
  }, null, 8, ["modelValue", "disabled"]),
7325
7343
  _cache[11] || (_cache[11] = (0, vue.createTextVNode)("行 ", -1))
7326
- ]), (0, vue.createElementVNode)("div", _hoisted_5$2, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7344
+ ]), (0, vue.createElementVNode)("div", _hoisted_5$1, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7327
7345
  size: "small",
7328
7346
  text: "",
7329
7347
  onClick: _cache[4] || (_cache[4] = ($event) => moveSpecifyLocationVisible.value = false)
@@ -7393,14 +7411,13 @@
7393
7411
  });
7394
7412
  //#endregion
7395
7413
  //#region packages/form/src/containers/GroupList.vue?vue&type=script&setup=true&lang.ts
7396
- var _hoisted_1$8 = { class: "m-fields-group-list" };
7397
- var _hoisted_2$4 = ["innerHTML"];
7398
- var _hoisted_3$3 = {
7414
+ var _hoisted_1$8 = ["innerHTML"];
7415
+ var _hoisted_2$4 = {
7399
7416
  key: 1,
7400
7417
  class: "el-table__empty-block"
7401
7418
  };
7402
- var _hoisted_4$2 = { class: "el-table__empty-text t-table__empty" };
7403
- var _hoisted_5$1 = { style: {
7419
+ var _hoisted_3$3 = { class: "el-table__empty-text t-table__empty" };
7420
+ var _hoisted_4$2 = { style: {
7404
7421
  "display": "flex",
7405
7422
  "justify-content": "flex-end",
7406
7423
  "flex": "1"
@@ -7447,6 +7464,14 @@
7447
7464
  };
7448
7465
  const onAddDiffCount = () => emit("addDiffCount");
7449
7466
  const asList = (value) => Array.isArray(value) ? value : [];
7467
+ const headerVars = (0, vue.computed)(() => getGroupListHeaderVars(props.config.header));
7468
+ /**
7469
+ * 取 `$slots` 而不是 `useSlots()`:宿主切换 `addable` 时插槽会增删,
7470
+ * computed 缓存不随插槽变化失效,只有在渲染期读才拿得到最新的。
7471
+ */
7472
+ const hasFooter = (slots) => !props.isCompare && Boolean(slots["toggle-button"] || slots["add-button"]);
7473
+ /** 只有真的渲染出吸底 footer,内层列表才需要为它让位 */
7474
+ const isFooterSticky = (slots) => hasFooter(slots) && Boolean(props.config.addButtonConfig?.sticky);
7450
7475
  const currentList = (0, vue.computed)(() => asList(props.model[props.name]));
7451
7476
  /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
7452
7477
  const displayItems = (0, vue.computed)(() => {
@@ -7464,13 +7489,19 @@
7464
7489
  }));
7465
7490
  });
7466
7491
  return (_ctx, _cache) => {
7467
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$8, [
7492
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7493
+ class: (0, vue.normalizeClass)(["m-fields-group-list", {
7494
+ "is-header-sticky": Boolean(__props.config.header?.sticky),
7495
+ "is-footer-sticky": isFooterSticky(_ctx.$slots)
7496
+ }]),
7497
+ style: (0, vue.normalizeStyle)(headerVars.value)
7498
+ }, [
7468
7499
  __props.config.extra ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7469
7500
  key: 0,
7470
7501
  innerHTML: __props.config.extra,
7471
7502
  style: { "color": "rgba(0, 0, 0, 0.45)" }
7472
- }, null, 8, _hoisted_2$4)) : (0, vue.createCommentVNode)("v-if", true),
7473
- !displayItems.value.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_3$3, [(0, vue.createElementVNode)("span", _hoisted_4$2, "暂无" + (0, vue.toDisplayString)(__props.config.titlePrefix || "") + "数据", 1)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, (0, vue.renderList)(displayItems.value, (entry) => {
7503
+ }, null, 8, _hoisted_1$8)) : (0, vue.createCommentVNode)("v-if", true),
7504
+ !displayItems.value.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$4, [(0, vue.createElementVNode)("span", _hoisted_3$3, "暂无" + (0, vue.toDisplayString)(__props.config.titlePrefix || "") + "数据", 1)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, (0, vue.renderList)(displayItems.value, (entry) => {
7474
7505
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupListItem_default, {
7475
7506
  key: entry.index,
7476
7507
  model: entry.item,
@@ -7507,11 +7538,11 @@
7507
7538
  "group-model"
7508
7539
  ]);
7509
7540
  }), 128)),
7510
- !__props.isCompare && (_ctx.$slots["toggle-button"] || _ctx.$slots["add-button"]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7541
+ hasFooter(_ctx.$slots) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7511
7542
  key: 3,
7512
7543
  class: (0, vue.normalizeClass)(["m-fields-group-list-footer", { "is-sticky-full": Boolean(__props.config.addButtonConfig?.sticky) }])
7513
- }, [(0, vue.renderSlot)(_ctx.$slots, "toggle-button"), (0, vue.createElementVNode)("div", _hoisted_5$1, [(0, vue.renderSlot)(_ctx.$slots, "add-button")])], 2)) : (0, vue.createCommentVNode)("v-if", true)
7514
- ]);
7544
+ }, [(0, vue.renderSlot)(_ctx.$slots, "toggle-button"), (0, vue.createElementVNode)("div", _hoisted_4$2, [(0, vue.renderSlot)(_ctx.$slots, "add-button")])], 2)) : (0, vue.createCommentVNode)("v-if", true)
7545
+ ], 6);
7515
7546
  };
7516
7547
  }
7517
7548
  });
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.27",
2
+ "version": "1.8.0-beta.28",
3
3
  "name": "@tmagic/form",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -58,9 +58,9 @@
58
58
  "peerDependencies": {
59
59
  "typescript": "^6.0.3",
60
60
  "vue": "^3.5.41",
61
- "@tmagic/design": "1.8.0-beta.27",
62
- "@tmagic/form-schema": "1.8.0-beta.27",
63
- "@tmagic/utils": "1.8.0-beta.27"
61
+ "@tmagic/design": "1.8.0-beta.28",
62
+ "@tmagic/utils": "1.8.0-beta.28",
63
+ "@tmagic/form-schema": "1.8.0-beta.28"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "typescript": {
@@ -1,5 +1,12 @@
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
11
  <div v-if="!displayItems.length" class="el-table__empty-block">
5
12
  <span class="el-table__empty-text t-table__empty">暂无{{ config.titlePrefix || '' }}数据</span>
@@ -34,7 +41,7 @@
34
41
  <div
35
42
  class="m-fields-group-list-footer"
36
43
  :class="{ 'is-sticky-full': Boolean(config.addButtonConfig?.sticky) }"
37
- v-if="!isCompare && ($slots['toggle-button'] || $slots['add-button'])"
44
+ v-if="hasFooter($slots)"
38
45
  >
39
46
  <slot name="toggle-button"></slot>
40
47
  <div style="display: flex; justify-content: flex-end; flex: 1">
@@ -49,6 +56,7 @@ import { computed } from 'vue';
49
56
  import { cloneDeep } from 'lodash-es';
50
57
 
51
58
  import type { ContainerChangeEventData, GroupListConfig } from '../schema';
59
+ import { getGroupListHeaderVars } from '../utils/tableGroupList';
52
60
 
53
61
  import MFieldsGroupListItem from './GroupListItem.vue';
54
62
 
@@ -104,6 +112,19 @@ const onAddDiffCount = () => emit('addDiffCount');
104
112
 
105
113
  const asList = (value: unknown): any[] => (Array.isArray(value) ? value : []);
106
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
+
107
128
  const currentList = computed(() => asList(props.model[props.name]));
108
129
 
109
130
  /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
@@ -70,27 +70,60 @@
70
70
  flex-shrink: 0;
71
71
  gap: 8px;
72
72
  }
73
+ }
73
74
 
74
- // 卡片标题吸顶;补不透明背景,避免滚动时正文透出。
75
- // z-index 必须低于吸底 footer,否则标题会盖住「新增」按钮。
76
- > .el-card__header,
77
- > .t-card__header {
78
- position: sticky;
79
- top: var(--m-group-list-header-sticky-top, 0px);
80
- z-index: 7;
81
- background-color: var(--m-group-list-header-bg, #fff);
82
- }
75
+ // 嵌套吸底按钮叠在外层 footer 之上(如「新增条件」叠在「新增条件组」上面)。
76
+ // 每穿过一层「自己渲染了吸底 footer」的列表才多让出一个 footer 高度,外层没有吸底按钮时
77
+ // 内层不能凭空抬高。自定义属性不能自增,所以用 item / list 两个变量交替接力。
78
+ > .m-fields-group-list-item {
79
+ --m-group-list-item-footer-bottom: var(
80
+ --m-group-list-nested-footer-bottom,
81
+ 0px
82
+ );
83
+ }
84
+
85
+ &.is-footer-sticky > .m-fields-group-list-item {
86
+ --m-group-list-item-footer-bottom: calc(
87
+ var(--m-group-list-nested-footer-bottom, 0px) +
88
+ var(--m-group-list-footer-height)
89
+ );
83
90
  }
84
91
 
85
- // 嵌套标题叠在外层标题之下,层级更低以免盖住外层底部分隔线
86
- .m-fields-group-list-item .m-fields-group-list-item {
87
- > .el-card__header,
88
- > .t-card__header {
89
- top: calc(
90
- var(--m-group-list-header-sticky-top, 0px) +
91
- var(--m-group-list-header-height)
92
+ .m-fields-group-list-item .m-fields-group-list {
93
+ --m-group-list-nested-footer-bottom: var(
94
+ --m-group-list-item-footer-bottom,
95
+ 0px
96
+ );
97
+ }
98
+
99
+ // 仅 header.sticky 时标题吸顶;直接子 item,避免外层吸顶带动未开启的内层
100
+ &.is-header-sticky {
101
+ // 从外层 item 接过已累加的偏移;最外层没有该变量时走 fallback
102
+ --m-group-list-nested-sticky-top: var(--m-group-list-child-list-sticky-top);
103
+ --m-group-list-nested-header-z: var(--m-group-list-child-list-header-z);
104
+
105
+ > .m-fields-group-list-item {
106
+ --m-group-list-item-sticky-top: var(
107
+ --m-group-list-nested-sticky-top,
108
+ var(--m-group-list-header-sticky-top, 0px)
92
109
  );
93
- z-index: 6;
110
+ --m-group-list-item-header-z: var(--m-group-list-nested-header-z, 7);
111
+ --m-group-list-child-list-sticky-top: calc(
112
+ var(--m-group-list-item-sticky-top) + var(--m-group-list-header-height)
113
+ );
114
+ --m-group-list-child-list-header-z: calc(
115
+ var(--m-group-list-item-header-z) - 1
116
+ );
117
+
118
+ // 卡片标题吸顶;补不透明背景,避免滚动时正文透出。
119
+ // z-index 必须低于吸底 footer,否则标题会盖住「新增」按钮。
120
+ > .el-card__header,
121
+ > .t-card__header {
122
+ position: sticky;
123
+ top: var(--m-group-list-item-sticky-top);
124
+ z-index: var(--m-group-list-item-header-z);
125
+ background-color: var(--m-group-list-header-bg, #fff);
126
+ }
94
127
  }
95
128
  }
96
129
 
@@ -113,10 +146,10 @@
113
146
  }
114
147
 
115
148
  // 吸底全宽主按钮(event-select / code-select / display-conds 外层列表)
116
- // z-index 高于 item header,避免吸顶标题挡住新增按钮
149
+ // z-index 高于 item header,避免吸顶标题挡住新增按钮;多层时 bottom 由内层 list 累加
117
150
  > .m-fields-group-list-footer.is-sticky-full {
118
151
  position: sticky;
119
- bottom: 0;
152
+ bottom: var(--m-group-list-nested-footer-bottom, 0px);
120
153
  z-index: 7;
121
154
  margin-bottom: 0;
122
155
  padding: var(--m-group-list-footer-padding-top) 0
@@ -133,14 +166,6 @@
133
166
  height: var(--m-group-list-footer-button-height);
134
167
  }
135
168
  }
136
-
137
- // 嵌套吸底按钮叠在外层 footer 之上(如「新增条件」叠在「新增条件组」上面)
138
- .m-fields-group-list-item
139
- .m-fields-group-list
140
- > .m-fields-group-list-footer.is-sticky-full {
141
- bottom: var(--m-group-list-footer-height);
142
- z-index: 7;
143
- }
144
169
  }
145
170
 
146
171
  /** 最外层的groupList需要每个item需要增加空白区域界限时,增加outer-gorup_list可以实现 */
@@ -18,7 +18,9 @@
18
18
 
19
19
  import { cloneDeep } from 'lodash-es';
20
20
 
21
- import type { GroupListConfig, TableColumnConfig, TableConfig } from '../schema';
21
+ import { isNumber } from '@tmagic/utils';
22
+
23
+ import type { GroupListConfig, GroupListHeaderConfig, TableColumnConfig, TableConfig } from '../schema';
22
24
 
23
25
  /**
24
26
  * table / group-list 是同一份配置的两种展示形态,可以互相切换(`TableGroupList.vue`)。
@@ -31,6 +33,31 @@ import type { GroupListConfig, TableColumnConfig, TableConfig } from '../schema'
31
33
  /** group-list 形态的 type(兼容驼峰与中划线两种写法) */
32
34
  export const isGroupListType = (type: unknown): boolean => type === 'groupList' || type === 'group-list';
33
35
 
36
+ /**
37
+ * 归一成带单位的 CSS 长度。
38
+ *
39
+ * 配置常常来自 JSON 或表单输入,`'48'` 这类无单位数字串很常见;直接写进 CSS 变量会让
40
+ * 嵌套层的 `calc()` 整体失效、吸顶静默失灵,所以纯数字一律补 `px`。
41
+ */
42
+ const toCssLength = (height: number | string | undefined): string | undefined => {
43
+ const value = `${height ?? ''}`.trim();
44
+ if (!value) return undefined;
45
+
46
+ if (isNumber(value)) return `${value}px`;
47
+ // 走到这里还是 number 的只剩 NaN / Infinity,不写变量,交给样式默认值兜底
48
+ return typeof height === 'number' ? undefined : value;
49
+ };
50
+
51
+ /** header.sticky 开启时把 height 写成 CSS 变量,给嵌套吸顶偏移用 */
52
+ export const getGroupListHeaderVars = (
53
+ header: GroupListHeaderConfig | undefined,
54
+ ): Record<string, string> | undefined => {
55
+ if (!header?.sticky) return undefined;
56
+
57
+ const height = toCssLength(header.height);
58
+ return height ? { '--m-group-list-header-height': height } : undefined;
59
+ };
60
+
34
61
  /** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
35
62
  export const calcLabelWidth = (label: string): string => {
36
63
  if (!label) return '0px';