@veltra/sheet 2.2.0 → 2.3.1

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 (86) hide show
  1. package/dist/components/report/binding-badge.js +45 -21
  2. package/dist/components/report/designer/col-widths.js +18 -4
  3. package/dist/components/report/designer/conditional-rules/helpers.js +3 -3
  4. package/dist/components/report/designer/float-panel.js +73 -36
  5. package/dist/components/report/designer/rule-row.js +111 -127
  6. package/dist/components/report/designer/rules-dialog.js +5 -22
  7. package/dist/components/report/hub-dataset-editor.js +16 -1
  8. package/dist/components/report/preview-grid-size.js +32 -0
  9. package/dist/components/report/report-designer.js +75 -25
  10. package/dist/components/report/report-viewer.js +38 -25
  11. package/dist/components/report/style2.css +44 -39
  12. package/dist/components/report/use-report-designer.js +34 -12
  13. package/dist/components/report/use-report-viewer.js +6 -1
  14. package/dist/components/sheet/popups/border-popup.js +30 -2
  15. package/dist/components/sheet/popups/fill-color-popup.js +4 -3
  16. package/dist/components/sheet/popups/font-color-popup.js +2 -2
  17. package/dist/components/sheet/popups/font-size-popup.js +2 -2
  18. package/dist/components/sheet/sheet-context-menu.js +6 -3
  19. package/dist/components/sheet/sheet.js +8 -0
  20. package/dist/components/sheet/use-sheet-grid.js +14 -4
  21. package/dist/components/sheet/use-sheet-state.js +3 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +2 -2
  24. package/dist/report/binding.d.ts +49 -11
  25. package/dist/report/binding.js +186 -29
  26. package/dist/report/connector.js +1 -1
  27. package/dist/report/export-xlsx.d.ts +3 -5
  28. package/dist/report/export-xlsx.js +7 -10
  29. package/dist/report/index.d.ts +2 -2
  30. package/dist/report/render/aggregate.js +20 -8
  31. package/dist/report/template.d.ts +8 -4
  32. package/dist/report/template.js +5 -2
  33. package/dist/tools/apply-style.js +29 -0
  34. package/dist/tools/builtin.js +17 -17
  35. package/dist/tools/context.d.ts +38 -5
  36. package/dist/tools/context.js +38 -1
  37. package/dist/types/report-designer.d.ts +2 -0
  38. package/dist/types/report-viewer.d.ts +2 -2
  39. package/dist/types/sheet.d.ts +4 -0
  40. package/package.json +15 -6
  41. package/src/components/report/__test__/conditional-rules-helpers.test.ts +1 -0
  42. package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
  43. package/src/components/report/__test__/report-designer.test.ts +76 -0
  44. package/src/components/report/__test__/report-viewer.test.ts +24 -1
  45. package/src/components/report/__test__/use-report-designer.test.ts +142 -10
  46. package/src/components/report/binding-badge.ts +39 -20
  47. package/src/components/report/designer/col-widths.ts +34 -11
  48. package/src/components/report/designer/conditional-rules/helpers.ts +6 -3
  49. package/src/components/report/designer/float-panel.vue +71 -27
  50. package/src/components/report/designer/rule-row.vue +67 -67
  51. package/src/components/report/designer/rules-dialog.vue +3 -19
  52. package/src/components/report/hub-dataset-editor.vue +19 -1
  53. package/src/components/report/preview-grid-size.ts +33 -0
  54. package/src/components/report/report-designer.vue +79 -15
  55. package/src/components/report/report-viewer.vue +37 -29
  56. package/src/components/report/style-designer.scss +45 -41
  57. package/src/components/report/use-report-designer.ts +63 -14
  58. package/src/components/report/use-report-viewer.ts +11 -2
  59. package/src/components/sheet/__test__/sheet-component.test.ts +37 -0
  60. package/src/components/sheet/__test__/sheet-context-menu.test.ts +3 -2
  61. package/src/components/sheet/popups/border-popup.vue +31 -3
  62. package/src/components/sheet/popups/fill-color-popup.vue +5 -4
  63. package/src/components/sheet/popups/font-color-popup.vue +2 -2
  64. package/src/components/sheet/popups/font-size-popup.vue +2 -2
  65. package/src/components/sheet/sheet-context-menu.ts +8 -2
  66. package/src/components/sheet/sheet.vue +2 -0
  67. package/src/components/sheet/use-sheet-grid.ts +17 -3
  68. package/src/components/sheet/use-sheet-state.ts +8 -3
  69. package/src/report/__test__/binding.test.ts +103 -1
  70. package/src/report/__test__/connector.test.ts +21 -0
  71. package/src/report/__test__/export-xlsx.test.ts +4 -4
  72. package/src/report/__test__/render.test.ts +102 -0
  73. package/src/report/__test__/template.test.ts +11 -0
  74. package/src/report/binding.ts +278 -47
  75. package/src/report/connector.ts +3 -2
  76. package/src/report/export-xlsx.ts +9 -14
  77. package/src/report/render/aggregate.ts +29 -8
  78. package/src/report/template.ts +8 -4
  79. package/src/tools/__test__/apply-style.test.ts +129 -0
  80. package/src/tools/apply-style.ts +41 -0
  81. package/src/tools/builtin.ts +18 -18
  82. package/src/tools/context.ts +67 -4
  83. package/src/tools/index.ts +1 -0
  84. package/src/types/report-designer.ts +2 -0
  85. package/src/types/report-viewer.ts +2 -2
  86. package/src/types/sheet.ts +4 -0
@@ -1,8 +1,8 @@
1
1
  import _sfc_main$1 from "../sheet/sheet.js";
2
- import { resolveGridDropAddress } from "./designer/cell-coords.js";
3
- import { applyGridColWidths, readGridColWidths } from "./designer/col-widths.js";
2
+ import { applyGridColWidths, applySheetColWidths, readGridColWidths } from "./designer/col-widths.js";
4
3
  import _sfc_main$2 from "./report-viewer.js";
5
4
  import _sfc_main$3 from "./designer-hub.js";
5
+ import { resolveGridDropAddress } from "./designer/cell-coords.js";
6
6
  import _sfc_main$4 from "./designer/drop-highlight-overlay.js";
7
7
  import _sfc_main$5 from "./designer/float-panel.js";
8
8
  import _sfc_main$6 from "./designer/rules-dialog.js";
@@ -10,7 +10,7 @@ import _sfc_main$7 from "./designer/topology-overlay.js";
10
10
  import { FIELD_DRAG_MIME, parseFieldDragPayload } from "./field-panel-helpers.js";
11
11
  import _sfc_main$8 from "./field-panel.js";
12
12
  import { useReportDesigner } from "./use-report-designer.js";
13
- import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, shallowRef, unref, useTemplateRef, watch, withCtx } from "vue";
13
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, onScopeDispose, openBlock, ref, renderList, shallowRef, toDisplayString, unref, useTemplateRef, watch, withCtx } from "vue";
14
14
  import { UButton, UDrawer } from "@veltra/desktop";
15
15
  import { bem } from "@veltra/utils";
16
16
  import { Workbook } from "@veltra/sheet-core";
@@ -26,12 +26,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
26
26
  template: {},
27
27
  workbook: {}
28
28
  },
29
- emits: ["update:connections"],
29
+ emits: ["update:connections", "datasets-change"],
30
30
  setup(__props, { expose: __expose, emit: __emit }) {
31
31
  const props = __props;
32
32
  const emit = __emit;
33
33
  const cls = bem("report-designer");
34
- /** 设计态网格尺寸(列宽捕获范围与之一致) */
34
+ /** 设计态网格默认尺寸(新建模板;已声明尺寸后以模型为准,避免删行后被 props 撑回) */
35
35
  const EMPTY_CONDITIONAL_RULES = [];
36
36
  /** `v-model:connections` 可写代理(连接为纯序列化对象,仅驻留内存) */
37
37
  const connections = computed({
@@ -43,8 +43,28 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
43
43
  connections
44
44
  });
45
45
  const { workbook, catalog, boundKeys, activeCell, selectionLabel, activeBinding, activeFieldType, activeDatasetFields, resolvedRowParentLabel, resolvedColParentLabel, parentPick, rowParentCandidates, colParentCandidates, bindingEntries, metaTick, getBindingAt, resolveFieldLabel, bindField, patchActiveBinding, startParentPick, cancelParentPick, pickParentAt, clearParent, removeActiveBinding, resolveCellStyle, resolveCellRenderer, getTemplate } = designer;
46
+ /** 渲染行列数跟随模型(删行后不再被固定 props 经 ensureTableSize 撑回) */
47
+ const gridRows = computed(() => {
48
+ const rows = workbook.value.activeSheet.rows;
49
+ return rows > 0 ? rows : DESIGN_ROWS;
50
+ });
51
+ const gridCols = computed(() => {
52
+ const cols = workbook.value.activeSheet.cols;
53
+ return cols > 0 ? cols : DESIGN_COLS;
54
+ });
46
55
  const hubVisible = ref(false);
47
56
  const rulesDialogVisible = ref(false);
57
+ const pickCandidateCells = computed(() => {
58
+ if (!parentPick.value) return [];
59
+ return parentPick.value.mode === "row" ? rowParentCandidates.value : colParentCandidates.value;
60
+ });
61
+ function onHubClose() {
62
+ hubVisible.value = false;
63
+ }
64
+ watch(hubVisible, (open) => {
65
+ if (!open) emit("datasets-change");
66
+ });
67
+ watch(() => designer.datasets.value, () => emit("datasets-change"), { deep: true });
48
68
  const sheetRef = useTemplateRef("sheetRef");
49
69
  const previewViewerRef = useTemplateRef("previewViewerRef");
50
70
  const gridHostRef = useTemplateRef("gridHostRef");
@@ -61,13 +81,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
61
81
  const previewTemplate = shallowRef(null);
62
82
  /** 预览工作簿自持(XLSX 导出需要拿到填充后的 sheet,05 票 workbook? prop 先例) */
63
83
  const previewWorkbook = new Workbook();
64
- /** 当前设计态列宽(sheet-core 列宽未进快照,经 VTable 运行时捕获;导出随快照写宽) */
84
+ /** 当前设计态列宽(与 Sheet 模型同步;getTemplate 经 snapshot.colWidths 吐出) */
65
85
  const designColWidths = ref([]);
66
86
  const exporting = ref(false);
67
- /** 从当前设计网格捕获列宽(网格未就绪则保留旧值) */
87
+ /** 从当前设计网格捕获列宽并写回 Sheet 模型(网格未就绪则保留旧值) */
68
88
  function captureDesignColWidths() {
69
- const widths = readGridColWidths(getDesignGrid(), Array.from({ length: DESIGN_COLS }, (_, col) => col));
70
- if (widths) designColWidths.value = widths;
89
+ const widths = readGridColWidths(getDesignGrid(), Array.from({ length: gridCols.value }, (_, col) => col));
90
+ if (!widths) return;
91
+ const custom = widths.filter(([, width]) => width > 0);
92
+ designColWidths.value = custom;
93
+ applySheetColWidths(workbook.value.activeSheet, custom);
71
94
  }
72
95
  /** 设计网格重建后补刷新与列宽(restore 不发 content-reset 的场景由 grid 自行订阅,这里只管列宽) */
73
96
  function syncDesignGridView() {
@@ -85,8 +108,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
85
108
  viewMode.value = mode;
86
109
  if (mode === "design") nextTick(syncDesignGridView);
87
110
  }
88
- watch(() => props.template, () => {
111
+ watch(() => props.template, (template) => {
89
112
  viewMode.value = "design";
113
+ designColWidths.value = template?.colWidths?.length ? template.colWidths.map(([col, width]) => [col, width]) : [];
114
+ if (designColWidths.value.length) applySheetColWidths(workbook.value.activeSheet, designColWidths.value);
115
+ nextTick(syncDesignGridView);
90
116
  });
91
117
  watch(activeCell, (cell) => {
92
118
  if (cell && parentPick.value) pickParentAt(cell);
@@ -94,6 +120,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
94
120
  watch(catalog, () => {
95
121
  nextTick(() => getDesignGrid()?.refresh());
96
122
  });
123
+ const offStructureChange = workbook.value.activeSheet.on("structure-change", () => {
124
+ nextTick(syncDesignGridView);
125
+ });
126
+ onScopeDispose(offStructureChange);
97
127
  async function exportPreviewXlsx() {
98
128
  if (!isPreview.value || exporting.value) return;
99
129
  const viewer = previewViewerRef.value;
@@ -146,7 +176,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
146
176
  sheetRef.value?.getContext().selectCell(dropAddr);
147
177
  } else bindField(payload.datasetId, payload.fieldName);
148
178
  }
149
- __expose({ getTemplate });
179
+ __expose({ getTemplate: () => {
180
+ captureDesignColWidths();
181
+ return getTemplate();
182
+ } });
150
183
  return (_ctx, _cache) => {
151
184
  return openBlock(), createElementBlock("div", { class: normalizeClass(unref(cls).b) }, [
152
185
  createElementVNode("div", { class: normalizeClass(unref(cls).e("toolbar")) }, [
@@ -155,7 +188,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
155
188
  plain: "",
156
189
  onClick: _cache[0] || (_cache[0] = ($event) => hubVisible.value = true)
157
190
  }, {
158
- default: withCtx(() => [..._cache[7] || (_cache[7] = [createTextVNode("数据中枢", -1)])]),
191
+ default: withCtx(() => [..._cache[6] || (_cache[6] = [createTextVNode("数据中枢", -1)])]),
159
192
  _: 1
160
193
  }),
161
194
  createElementVNode("div", {
@@ -168,7 +201,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
168
201
  plain: isPreview.value,
169
202
  onClick: _cache[1] || (_cache[1] = ($event) => setViewMode("design"))
170
203
  }, {
171
- default: withCtx(() => [..._cache[8] || (_cache[8] = [createTextVNode(" 设计模式 ", -1)])]),
204
+ default: withCtx(() => [..._cache[7] || (_cache[7] = [createTextVNode(" 设计模式 ", -1)])]),
172
205
  _: 1
173
206
  }, 8, ["type", "plain"]), createVNode(unref(UButton), {
174
207
  size: "small",
@@ -176,7 +209,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
176
209
  plain: !isPreview.value,
177
210
  onClick: _cache[2] || (_cache[2] = ($event) => setViewMode("preview"))
178
211
  }, {
179
- default: withCtx(() => [..._cache[9] || (_cache[9] = [createTextVNode(" 预览模式 ", -1)])]),
212
+ default: withCtx(() => [..._cache[8] || (_cache[8] = [createTextVNode(" 预览模式 ", -1)])]),
180
213
  _: 1
181
214
  }, 8, ["type", "plain"])], 2),
182
215
  isPreview.value ? (openBlock(), createBlock(unref(UButton), {
@@ -187,10 +220,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
187
220
  loading: exporting.value,
188
221
  onClick: exportPreviewXlsx
189
222
  }, {
190
- default: withCtx(() => [..._cache[10] || (_cache[10] = [createTextVNode(" 导出 XLSX ", -1)])]),
223
+ default: withCtx(() => [..._cache[9] || (_cache[9] = [createTextVNode(" 导出 XLSX ", -1)])]),
191
224
  _: 1
192
225
  }, 8, ["loading"])) : createCommentVNode("v-if", true),
193
- createElementVNode("span", { class: normalizeClass(unref(cls).e("hint")) }, " 在数据中枢配置连接与 SQL 数据集,再将左侧字段点击或拖拽到单元格完成绑定;选中已绑定格可通过悬浮卡片调整角色、聚合与条件样式。 ", 2)
226
+ createElementVNode("span", { class: normalizeClass(unref(cls).e("hint")) }, " 在数据中枢配置连接与 SQL 数据集,再将左侧字段点击或拖拽到单元格完成绑定;选中已绑定格可通过悬浮卡片调整预设与条件样式。 ", 2)
194
227
  ], 2),
195
228
  !isPreview.value ? (openBlock(), createElementBlock("div", {
196
229
  key: 0,
@@ -220,14 +253,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
220
253
  ref: sheetRef,
221
254
  class: normalizeClass(unref(cls).e("sheet")),
222
255
  workbook: unref(workbook),
223
- rows: DESIGN_ROWS,
224
- cols: DESIGN_COLS,
256
+ rows: gridRows.value,
257
+ cols: gridCols.value,
225
258
  "show-tabs": false,
226
259
  "resolve-cell-style": unref(resolveCellStyle),
227
260
  "resolve-cell-renderer": unref(resolveCellRenderer)
228
261
  }, null, 8, [
229
262
  "class",
230
263
  "workbook",
264
+ "rows",
265
+ "cols",
231
266
  "resolve-cell-style",
232
267
  "resolve-cell-renderer"
233
268
  ]),
@@ -259,6 +294,21 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
259
294
  "dragging",
260
295
  "fallback-label"
261
296
  ]),
297
+ (openBlock(true), createElementBlock(Fragment, null, renderList(pickCandidateCells.value, (addr) => {
298
+ return openBlock(), createBlock(_sfc_main$4, {
299
+ key: `pick-${addr.row}-${addr.col}`,
300
+ cell: addr,
301
+ "host-el": gridHostEl.value,
302
+ "get-grid": getDesignGrid,
303
+ dragging: false,
304
+ "fallback-label": ""
305
+ }, null, 8, ["cell", "host-el"]);
306
+ }), 128)),
307
+ unref(parentPick) ? (openBlock(), createElementBlock("div", {
308
+ key: 0,
309
+ class: normalizeClass(unref(cls).e("pick-hint")),
310
+ role: "status"
311
+ }, toDisplayString(unref(parentPick).mode === "row" ? "点击分组头作为行方向父格" : "点击分组头作为列方向父格"), 3)) : createCommentVNode("v-if", true),
262
312
  createVNode(_sfc_main$5, {
263
313
  cell: unref(activeCell),
264
314
  binding: unref(activeBinding) ?? null,
@@ -268,6 +318,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
268
318
  "row-parent-candidates": unref(rowParentCandidates),
269
319
  "col-parent-candidates": unref(colParentCandidates),
270
320
  "resolve-field-label": unref(resolveFieldLabel),
321
+ "get-binding-at": unref(getBindingAt),
271
322
  "host-el": gridHostEl.value,
272
323
  "get-grid": getDesignGrid,
273
324
  onPatch: unref(patchActiveBinding),
@@ -285,6 +336,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
285
336
  "row-parent-candidates",
286
337
  "col-parent-candidates",
287
338
  "resolve-field-label",
339
+ "get-binding-at",
288
340
  "host-el",
289
341
  "onPatch",
290
342
  "onRemove",
@@ -300,29 +352,27 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
300
352
  ref: previewViewerRef,
301
353
  connector: props.connector,
302
354
  template: previewTemplate.value,
303
- workbook: unref(previewWorkbook),
304
- "col-widths": designColWidths.value
355
+ workbook: unref(previewWorkbook)
305
356
  }, null, 8, [
306
357
  "connector",
307
358
  "template",
308
- "workbook",
309
- "col-widths"
359
+ "workbook"
310
360
  ])], 2)) : createCommentVNode("v-if", true),
311
361
  createVNode(unref(UDrawer), {
312
362
  modelValue: hubVisible.value,
313
- "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => hubVisible.value = $event),
363
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => hubVisible.value = $event),
314
364
  class: normalizeClass(unref(cls).e("hub-drawer")),
315
365
  "show-close": ""
316
366
  }, {
317
367
  default: withCtx(() => [createVNode(_sfc_main$3, {
318
368
  hub: unref(designer),
319
- onClose: _cache[4] || (_cache[4] = ($event) => hubVisible.value = false)
369
+ onClose: onHubClose
320
370
  }, null, 8, ["hub"])]),
321
371
  _: 1
322
372
  }, 8, ["modelValue", "class"]),
323
373
  createVNode(_sfc_main$6, {
324
374
  modelValue: rulesDialogVisible.value,
325
- "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => rulesDialogVisible.value = $event),
375
+ "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => rulesDialogVisible.value = $event),
326
376
  rules: unref(activeBinding)?.conditionalRules ?? EMPTY_CONDITIONAL_RULES,
327
377
  "binding-field": unref(activeBinding)?.field ?? "",
328
378
  "dataset-fields": unref(activeDatasetFields),
@@ -1,16 +1,14 @@
1
1
  import _sfc_main$1 from "../sheet/sheet.js";
2
2
  import { exportFilledReportXlsx } from "../../report/export-xlsx.js";
3
- import { applyGridColWidths, readGridColWidths } from "./designer/col-widths.js";
3
+ import { applyGridColWidths, applySheetColWidths } from "./designer/col-widths.js";
4
4
  import _sfc_main$2 from "./filter-bar.js";
5
+ import { previewGridSize } from "./preview-grid-size.js";
5
6
  import { useReportViewer } from "./use-report-viewer.js";
6
7
  import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, toDisplayString, unref, useTemplateRef, watch } from "vue";
7
8
  import { bem } from "@veltra/utils";
8
9
  import { saveBlob } from "@cat-kit/fe";
9
10
  import { Workbook } from "@veltra/sheet-core";
10
11
  //#region src/components/report/report-viewer.vue
11
- const MIN_RENDER_ROWS = 50;
12
- const MIN_RENDER_COLS = 10;
13
- /** 取数未完成时拒绝导出的可读错误 */
14
12
  const EXPORT_NOT_READY_MESSAGE = "报表数据尚未就绪,请等待取数完成后再导出";
15
13
  const EXPORT_LOADING_MESSAGE = "报表数据加载中,请稍后再导出";
16
14
  /** 查看器工作簿:宿主可注入(同 USheet `workbook?` 先例),缺省内部自建;模板 / 填充结果都 restore 进活动 sheet 只读展示 */
@@ -26,43 +24,56 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
26
24
  setup(__props, { expose: __expose }) {
27
25
  const props = __props;
28
26
  const cls = bem("report-viewer");
29
- /** 网格最小渲染尺寸(与 playground 预览态一致);Filled Report 超出时按快照尺寸放大 */
27
+ /** 取数未完成时拒绝导出的可读错误 */
30
28
  const internalWorkbook = new Workbook();
31
29
  const workbook = computed(() => props.workbook ?? internalWorkbook);
32
30
  const sheetRef = useTemplateRef("sheetRef");
33
- const { params, values, loading, error, filledSnapshot, refresh, setValues } = useReportViewer(props);
34
- const renderRows = computed(() => Math.max(MIN_RENDER_ROWS, filledSnapshot.value?.rows ?? 0, props.template.rows));
35
- const renderCols = computed(() => Math.max(MIN_RENDER_COLS, filledSnapshot.value?.cols ?? 0, props.template.cols));
36
- function exportColIndexes() {
37
- return Array.from({ length: renderCols.value }, (_, col) => col);
38
- }
39
- /** 将宿主传入的列宽写入 VTable 运行时(sheet-core 列宽未进快照) */
31
+ const { params, values, loading, error, filledSnapshot, filledColWidths, refresh, setValues } = useReportViewer(props);
32
+ const renderSize = computed(() => {
33
+ const filled = filledSnapshot.value;
34
+ if (filled) return previewGridSize(filled, "filled");
35
+ return previewGridSize(props.template, "template");
36
+ });
37
+ /** 宿主显式传入优先;否则取数完成后用展开映射列宽;再回落模板设计态列宽 */
38
+ const effectiveColWidths = computed(() => {
39
+ if (props.colWidths?.length) return props.colWidths;
40
+ if (filledColWidths.value?.length) return filledColWidths.value;
41
+ return props.template.colWidths;
42
+ });
43
+ /** 将运行时列宽写入模型与 VTable(模型为单一事实源;grid 构造/content-reset 也会回放) */
40
44
  function applyRuntimeColWidths() {
41
- if (!props.colWidths?.length) return;
42
- applyGridColWidths(sheetRef.value?.getGrid(), props.colWidths);
45
+ if (!effectiveColWidths.value?.length) return;
46
+ const sheet = workbook.value.activeSheet;
47
+ applySheetColWidths(sheet, effectiveColWidths.value);
48
+ applyGridColWidths(sheetRef.value?.getGrid(), effectiveColWidths.value);
43
49
  }
44
50
  /**
45
- * restore 负责尺寸/冻结/行高/选区(静默),restoreContent 发 content-reset
51
+ * restore 负责尺寸/冻结/行高/列宽/选区(静默),restoreContent 发 content-reset
46
52
  * 触发网格全量刷新(grid 层订阅直刷,无需手动 flush);history.clear 使填充不进 undo
47
53
  */
48
- function applySnapshot(snapshot) {
54
+ function applySnapshot(snapshot, mode) {
55
+ const size = previewGridSize(snapshot, mode);
56
+ const trimmed = {
57
+ ...snapshot,
58
+ rows: size.rows,
59
+ cols: size.cols
60
+ };
49
61
  const sheet = workbook.value.activeSheet;
50
- sheet.restore(snapshot);
51
- sheet.restoreContent(snapshot);
62
+ sheet.restore(trimmed);
63
+ sheet.restoreContent(trimmed);
52
64
  sheet.history.clear();
53
65
  nextTick(applyRuntimeColWidths);
54
66
  }
55
- watch(() => props.template, applySnapshot, { immediate: true });
67
+ watch(() => props.template, (template) => applySnapshot(template, "template"), { immediate: true });
56
68
  watch(filledSnapshot, (filled) => {
57
- if (filled) applySnapshot(filled);
69
+ if (filled) applySnapshot(filled, "filled");
58
70
  });
59
- watch(() => props.colWidths, applyRuntimeColWidths);
71
+ watch(effectiveColWidths, applyRuntimeColWidths);
60
72
  async function exportXlsx() {
61
73
  if (loading.value) throw new Error(EXPORT_LOADING_MESSAGE);
62
74
  if (!filledSnapshot.value) throw new Error(EXPORT_NOT_READY_MESSAGE);
63
75
  const sheet = workbook.value.activeSheet;
64
- const colWidths = readGridColWidths(sheetRef.value?.getGrid(), exportColIndexes()) ?? [];
65
- const buffer = await exportFilledReportXlsx(sheet, colWidths);
76
+ const buffer = await exportFilledReportXlsx(sheet);
66
77
  saveBlob(new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }), `${sheet.name || "报表"}.xlsx`);
67
78
  }
68
79
  __expose({
@@ -92,11 +103,13 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
92
103
  ref: sheetRef,
93
104
  class: normalizeClass(unref(cls).e("sheet")),
94
105
  workbook: workbook.value,
95
- rows: renderRows.value,
96
- cols: renderCols.value,
106
+ rows: renderSize.value.rows,
107
+ cols: renderSize.value.cols,
97
108
  "show-toolbar": false,
98
109
  "show-formula-bar": false,
99
110
  "show-tabs": false,
111
+ "show-row-header": false,
112
+ "show-col-header": false,
100
113
  readonly: ""
101
114
  }, null, 8, [
102
115
  "class",
@@ -166,6 +166,21 @@
166
166
  min-height: 0;
167
167
  min-width: 0;
168
168
  }
169
+ .u-report-designer__pick-hint {
170
+ position: absolute;
171
+ left: 50%;
172
+ bottom: var(--u-gap-small);
173
+ z-index: 17;
174
+ transform: translateX(-50%);
175
+ padding: 4px var(--u-gap-small);
176
+ border-radius: var(--u-radius-default);
177
+ background: color-mix(in srgb, var(--u-bg-color-top) 92%, transparent);
178
+ border: 1px dashed var(--u-color-primary);
179
+ font-size: var(--u-font-size-main-small);
180
+ color: var(--u-text-color-second);
181
+ pointer-events: none;
182
+ white-space: nowrap;
183
+ }
169
184
  .u-report-designer {
170
185
  /* 数据中枢 drawer 面板宽度(class 经 $attrs 落到 .u-drawer 面板;须压过 .u-drawer.is-right 的 320px) */
171
186
  }
@@ -222,7 +237,7 @@
222
237
  white-space: nowrap;
223
238
  }
224
239
 
225
- /* Action Pill:选中绑定格上方的悬浮编辑卡(角色 / 聚合 / 条件样式 / 排序 / 清除绑定) */
240
+ /* Action Pill:选中绑定格上方的悬浮编辑卡(预设 + 互补的展开/聚合 / 条件样式 / 排序 / 清除绑定) */
226
241
  .u-report-float-panel {
227
242
  position: absolute;
228
243
  z-index: 18;
@@ -308,7 +323,7 @@
308
323
  flex-wrap: wrap;
309
324
  }
310
325
  .u-report-float-panel__parent-select {
311
- width: 88px;
326
+ width: 180px;
312
327
  flex: none;
313
328
  }
314
329
  .u-report-float-panel__select--expand {
@@ -326,11 +341,12 @@
326
341
  .u-report-rules-dialog {
327
342
  display: flex;
328
343
  flex-direction: column;
329
- gap: 12px;
344
+ gap: 10px;
330
345
  }
331
346
  .u-report-rules-dialog__hint {
332
347
  margin: 0;
333
348
  font-size: 12px;
349
+ line-height: 1.5;
334
350
  color: var(--u-text-color-second);
335
351
  }
336
352
  .u-report-rules-dialog__list {
@@ -356,17 +372,18 @@
356
372
  display: flex;
357
373
  align-items: flex-start;
358
374
  gap: 8px;
359
- padding: 10px;
375
+ padding: 10px 10px 10px 6px;
360
376
  border: var(--u-border-muted);
361
377
  border-radius: 8px;
362
- background: var(--u-bg-color-top);
378
+ background: var(--u-bg-color-middle);
363
379
  }
364
380
  .u-report-rule-row__handle {
365
381
  flex: none;
366
- width: 20px;
367
- margin-top: 4px;
368
- color: var(--u-text-color-second);
369
- font-size: 16px;
382
+ width: 16px;
383
+ margin-top: 6px;
384
+ color: var(--u-text-color-placeholder);
385
+ font-size: 12px;
386
+ letter-spacing: -1px;
370
387
  line-height: 1;
371
388
  cursor: grab;
372
389
  user-select: none;
@@ -382,28 +399,29 @@
382
399
  flex-direction: column;
383
400
  gap: 8px;
384
401
  }
385
- .u-report-rule-row__meta {
402
+ .u-report-rule-row__when {
386
403
  display: flex;
387
404
  align-items: center;
388
- gap: 8px;
405
+ gap: 6px;
389
406
  flex-wrap: wrap;
390
407
  }
391
- .u-report-rule-row__field-select {
392
- width: 120px;
393
- flex: none;
408
+ .u-report-rule-row__then {
409
+ display: flex;
410
+ align-items: center;
411
+ gap: 6px;
412
+ flex-wrap: wrap;
394
413
  }
395
- .u-report-rule-row__scope-select {
396
- width: 72px;
414
+ .u-report-rule-row__lead {
397
415
  flex: none;
416
+ font-size: 12px;
417
+ color: var(--u-text-color-second);
398
418
  }
399
- .u-report-rule-row__condition {
400
- display: grid;
401
- grid-template-columns: 72px minmax(0, 1fr) auto;
402
- align-items: center;
403
- gap: 8px;
419
+ .u-report-rule-row__field-select {
420
+ width: 128px;
421
+ flex: none;
404
422
  }
405
423
  .u-report-rule-row__operator {
406
- width: 72px;
424
+ width: 96px;
407
425
  flex: none;
408
426
  }
409
427
  .u-report-rule-row__value {
@@ -414,40 +432,27 @@
414
432
  min-width: 0;
415
433
  }
416
434
  .u-report-rule-row__scalar-input {
417
- width: 112px;
435
+ width: 120px;
418
436
  flex: none;
419
437
  }
420
438
  .u-report-rule-row__between-input {
421
- width: 96px;
439
+ width: 88px;
422
440
  flex: none;
423
441
  }
424
442
  .u-report-rule-row__between-sep {
425
443
  font-size: 12px;
426
444
  color: var(--u-text-color-second);
427
445
  }
428
- .u-report-rule-row__style {
429
- display: flex;
430
- align-items: center;
431
- gap: 8px;
432
- flex-wrap: wrap;
433
- }
434
- .u-report-rule-row__style-label {
435
- font-size: 12px;
436
- color: var(--u-text-color-second);
437
- }
438
446
  .u-report-rule-row__toggle {
439
447
  min-width: 28px;
440
448
  padding-inline: 6px;
441
449
  }
442
450
  .u-report-rule-row__preview {
443
451
  flex: none;
444
- margin-top: 2px;
445
452
  }
446
- .u-report-rule-row__actions {
453
+ .u-report-rule-row__remove {
447
454
  flex: none;
448
- display: flex;
449
- flex-direction: column;
450
- gap: 2px;
455
+ margin-top: 2px;
451
456
  }
452
457
 
453
458
  .u-report-rule-preview {
@@ -1,4 +1,4 @@
1
- import { REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatCellAddress, inferColParentCandidate, inferDropPreset, inferRowParentCandidate, isHorizontalExpandBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates } from "../../report/binding.js";
1
+ import { REPORT_META_NAMESPACE, applyReportPreset, createReportBinding, formatCellAddress, inferDropPreset, inferParentsForPreset, isColGroupBinding, isHorizontalExpandBinding, isRowGroupBinding, isVerticalExpandBinding, listColParentCandidates, listRowParentCandidates, prefersGroupParent, presetStillApplies } from "../../report/binding.js";
2
2
  import { buildParamDefs } from "../../report/params.js";
3
3
  import { assertCompatibleTemplateVersion, createReportTemplate, getTemplateDatasets, resolveParamDefaults } from "../../report/template.js";
4
4
  import { createBindingBadgeRenderer, createBindingBadgeStyleResolver } from "./binding-badge.js";
@@ -80,10 +80,9 @@ function useReportDesigner(options) {
80
80
  const preset = inferDropPreset(target, field?.type ?? "string", getBindingAt);
81
81
  let binding = createReportBinding(dataset, fieldName);
82
82
  binding = applyReportPreset(binding, preset);
83
- const rowParent = inferRowParentCandidate(target, getBindingAt);
84
- if (rowParent) binding.rowParent = rowParent;
85
- const colParent = inferColParentCandidate(target, getBindingAt);
86
- if (colParent) binding.colParent = colParent;
83
+ const parents = inferParentsForPreset(target, preset, getBindingAt);
84
+ if (parents.rowParent) binding.rowParent = parents.rowParent;
85
+ if (parents.colParent) binding.colParent = parents.colParent;
87
86
  workbook.value.activeSheet.setCellMeta(target, REPORT_META_NAMESPACE, binding);
88
87
  }
89
88
  const activeBinding = computed(() => {
@@ -118,13 +117,13 @@ function useReportDesigner(options) {
118
117
  metaTick.value;
119
118
  const binding = activeBinding.value;
120
119
  if (!binding) return [];
121
- return listRowParentCandidates(binding, bindingEntries.value);
120
+ return listRowParentCandidates(binding, bindingEntries.value, { preferGroup: prefersGroupParent(binding) });
122
121
  });
123
122
  const colParentCandidates = computed(() => {
124
123
  metaTick.value;
125
124
  const binding = activeBinding.value;
126
125
  if (!binding) return [];
127
- return listColParentCandidates(binding, bindingEntries.value);
126
+ return listColParentCandidates(binding, bindingEntries.value, { preferGroup: prefersGroupParent(binding) });
128
127
  });
129
128
  const bindingEntries = computed(() => {
130
129
  metaTick.value;
@@ -138,6 +137,11 @@ function useReportDesigner(options) {
138
137
  }
139
138
  return entries;
140
139
  });
140
+ function parentNeedsCorrection(addr) {
141
+ if (!addr) return true;
142
+ const parent = getBindingAt(addr);
143
+ return !parent || parent.aggregate === "list";
144
+ }
141
145
  function patchActiveBinding(patch) {
142
146
  const cell = activeCell.value;
143
147
  const binding = activeBinding.value;
@@ -148,7 +152,14 @@ function useReportDesigner(options) {
148
152
  };
149
153
  if ("rowParent" in patch && patch.rowParent === void 0) delete next.rowParent;
150
154
  if ("colParent" in patch && patch.colParent === void 0) delete next.colParent;
151
- if (("aggregate" in patch || "expand" in patch) && !("preset" in patch)) delete next.preset;
155
+ if (("aggregate" in patch || "expand" in patch) && !("preset" in patch) && !presetStillApplies(next)) delete next.preset;
156
+ if (patch.preset === "subtotal") {
157
+ const inferred = inferParentsForPreset(cell, "subtotal", getBindingAt);
158
+ if (parentNeedsCorrection(next.rowParent)) if (inferred.rowParent) next.rowParent = inferred.rowParent;
159
+ else delete next.rowParent;
160
+ if (parentNeedsCorrection(next.colParent)) if (inferred.colParent) next.colParent = inferred.colParent;
161
+ else delete next.colParent;
162
+ }
152
163
  workbook.value.activeSheet.setCellMeta(cell, REPORT_META_NAMESPACE, next);
153
164
  }
154
165
  function startParentPick(mode) {
@@ -165,16 +176,27 @@ function useReportDesigner(options) {
165
176
  function cancelParentPick() {
166
177
  parentPick.value = null;
167
178
  }
179
+ function isValidParentTarget(mode, source, target) {
180
+ if (!target) return false;
181
+ if (target.dataset !== source.dataset) return false;
182
+ const groupOnly = prefersGroupParent(source);
183
+ if (mode === "row") return groupOnly ? isRowGroupBinding(target) : isVerticalExpandBinding(target);
184
+ return groupOnly ? isColGroupBinding(target) : isHorizontalExpandBinding(target);
185
+ }
168
186
  function pickParentAt(target) {
169
187
  const pick = parentPick.value;
170
188
  if (!pick) return;
171
189
  if (target.row === pick.source.row && target.col === pick.source.col) return;
172
190
  const sourceBinding = getBindingAt(pick.source);
191
+ if (!sourceBinding) {
192
+ parentPick.value = null;
193
+ return;
194
+ }
173
195
  const targetBinding = getBindingAt(target);
174
- if (!sourceBinding || !targetBinding) return;
175
- if (targetBinding.dataset !== sourceBinding.dataset) return;
176
- if (pick.mode === "row" && !isVerticalExpandBinding(targetBinding)) return;
177
- if (pick.mode === "col" && !isHorizontalExpandBinding(targetBinding)) return;
196
+ if (!isValidParentTarget(pick.mode, sourceBinding, targetBinding)) {
197
+ workbook.value.activeSheet.selectCell(pick.source);
198
+ return;
199
+ }
178
200
  const patch = pick.mode === "row" ? { rowParent: { ...target } } : { colParent: { ...target } };
179
201
  const next = {
180
202
  ...sourceBinding,
@@ -1,4 +1,4 @@
1
- import { renderReport } from "../../report/render/index.js";
1
+ import { renderReport, resolveFilledColWidths } from "../../report/render/index.js";
2
2
  import { fetchTemplateRecords, resolveParamDefaults, resolveTemplateParams } from "../../report/template.js";
3
3
  import { computed, ref, shallowRef, watch } from "vue";
4
4
  //#region src/components/report/use-report-viewer.ts
@@ -10,6 +10,7 @@ function useReportViewer(props) {
10
10
  const loading = ref(false);
11
11
  const error = shallowRef(null);
12
12
  const filledSnapshot = shallowRef(null);
13
+ const filledColWidths = shallowRef(void 0);
13
14
  const params = computed(() => resolveTemplateParams(props.template));
14
15
  const values = shallowRef({});
15
16
  let fetchSeq = 0;
@@ -23,9 +24,11 @@ function useReportViewer(props) {
23
24
  loading.value = false;
24
25
  if (!result.ok) {
25
26
  error.value = result.error;
27
+ filledColWidths.value = void 0;
26
28
  return;
27
29
  }
28
30
  filledSnapshot.value = renderReport(template, result.data);
31
+ filledColWidths.value = template.colWidths?.length ? resolveFilledColWidths(template, result.data, template.colWidths) : void 0;
29
32
  }
30
33
  function setValues(next) {
31
34
  values.value = next;
@@ -33,6 +36,7 @@ function useReportViewer(props) {
33
36
  }
34
37
  watch(() => props.template, () => {
35
38
  filledSnapshot.value = null;
39
+ filledColWidths.value = void 0;
36
40
  values.value = resolveParamDefaults(params.value);
37
41
  refresh();
38
42
  }, { immediate: true });
@@ -42,6 +46,7 @@ function useReportViewer(props) {
42
46
  loading,
43
47
  error,
44
48
  filledSnapshot,
49
+ filledColWidths,
45
50
  refresh,
46
51
  setValues
47
52
  };