@veltra/sheet 2.3.3 → 2.4.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 (61) hide show
  1. package/dist/components/report/binding-badge.js +23 -5
  2. package/dist/components/report/designer/drill-dialog.js +258 -0
  3. package/dist/components/report/designer/float-panel.js +26 -14
  4. package/dist/components/report/print.js +39 -0
  5. package/dist/components/report/report-designer.js +56 -21
  6. package/dist/components/report/report-viewer.js +173 -44
  7. package/dist/components/report/style2.css +91 -0
  8. package/dist/components/report/use-report-designer.js +2 -1
  9. package/dist/components/report/use-report-viewer.js +88 -3
  10. package/dist/components/sheet/insert-image.js +1 -1
  11. package/dist/components/sheet/popups/import.worker.js +2 -3
  12. package/dist/components/sheet/use-sheet-state.js +2 -2
  13. package/dist/index.d.ts +5 -3
  14. package/dist/index.js +4 -2
  15. package/dist/report/drill.d.ts +48 -0
  16. package/dist/report/drill.js +78 -0
  17. package/dist/report/export-xlsx.d.ts +3 -7
  18. package/dist/report/export-xlsx.js +6 -85
  19. package/dist/report/index.d.ts +5 -3
  20. package/dist/report/print.d.ts +23 -0
  21. package/dist/report/print.js +197 -0
  22. package/dist/report/render/aggregate.js +25 -1
  23. package/dist/report/types.d.ts +19 -1
  24. package/dist/tools/download.js +2 -2
  25. package/dist/types/report-designer.d.ts +13 -0
  26. package/dist/types/report-viewer.d.ts +19 -0
  27. package/package.json +6 -9
  28. package/src/components/report/__test__/binding-badge.test.ts +92 -0
  29. package/src/components/report/__test__/report-designer.test.ts +98 -3
  30. package/src/components/report/__test__/report-drill.test.ts +692 -0
  31. package/src/components/report/__test__/report-viewer.test.ts +43 -0
  32. package/src/components/report/__test__/use-report-designer.test.ts +108 -1
  33. package/src/components/report/binding-badge.ts +35 -5
  34. package/src/components/report/designer/drill-dialog.vue +245 -0
  35. package/src/components/report/designer/float-panel.vue +12 -0
  36. package/src/components/report/print.ts +44 -0
  37. package/src/components/report/report-designer.vue +35 -2
  38. package/src/components/report/report-viewer.vue +171 -18
  39. package/src/components/report/style-designer.scss +66 -0
  40. package/src/components/report/style-viewer.scss +40 -0
  41. package/src/components/report/use-report-designer.ts +8 -1
  42. package/src/components/report/use-report-viewer.ts +150 -11
  43. package/src/components/sheet/insert-image.ts +1 -1
  44. package/src/components/sheet/popups/export-popup.vue +1 -1
  45. package/src/components/sheet/popups/import.worker.ts +7 -10
  46. package/src/components/sheet/use-sheet-state.ts +2 -2
  47. package/src/report/__test__/drill.test.ts +194 -0
  48. package/src/report/__test__/export-xlsx.test.ts +3 -25
  49. package/src/report/__test__/print.test.ts +170 -0
  50. package/src/report/__test__/render.test.ts +26 -0
  51. package/src/report/drill.ts +112 -0
  52. package/src/report/export-xlsx.ts +5 -103
  53. package/src/report/index.ts +4 -1
  54. package/src/report/print.ts +234 -0
  55. package/src/report/render/aggregate.ts +28 -0
  56. package/src/report/types.ts +20 -0
  57. package/src/tools/__test__/builtin.test.ts +1 -1
  58. package/src/tools/download.ts +2 -2
  59. package/src/tools/export.worker.ts +2 -2
  60. package/src/types/report-designer.ts +18 -1
  61. package/src/types/report-viewer.ts +18 -1
@@ -3,12 +3,13 @@ import { applyGridColWidths, applySheetColWidths, readGridColWidths } from "./de
3
3
  import _sfc_main$2 from "./report-viewer.js";
4
4
  import _sfc_main$3 from "./designer-hub.js";
5
5
  import { resolveGridDropAddress } from "./designer/cell-coords.js";
6
- import _sfc_main$4 from "./designer/drop-highlight-overlay.js";
7
- import _sfc_main$5 from "./designer/float-panel.js";
8
- import _sfc_main$6 from "./designer/rules-dialog.js";
9
- import _sfc_main$7 from "./designer/topology-overlay.js";
6
+ import _sfc_main$4 from "./designer/drill-dialog.js";
7
+ import _sfc_main$5 from "./designer/drop-highlight-overlay.js";
8
+ import _sfc_main$6 from "./designer/float-panel.js";
9
+ import _sfc_main$7 from "./designer/rules-dialog.js";
10
+ import _sfc_main$8 from "./designer/topology-overlay.js";
10
11
  import { FIELD_DRAG_MIME, parseFieldDragPayload } from "./field-panel-helpers.js";
11
- import _sfc_main$8 from "./field-panel.js";
12
+ import _sfc_main$9 from "./field-panel.js";
12
13
  import { useReportDesigner } from "./use-report-designer.js";
13
14
  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
15
  import { UButton, UDrawer } from "@veltra/desktop";
@@ -24,10 +25,13 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
24
25
  connector: {},
25
26
  connections: { default: () => [] },
26
27
  template: {},
27
- workbook: {}
28
+ workbook: {},
29
+ drillTemplates: {},
30
+ resolveTemplate: {}
28
31
  },
29
32
  emits: ["update:connections", "datasets-change"],
30
33
  setup(__props, { expose: __expose, emit: __emit }) {
34
+ /** 与 `ReportDesignerProps` 交叉显式列出下钻 props,避免 SFC 对跨文件 interface 漏抽 runtime 声明 */
31
35
  const props = __props;
32
36
  const emit = __emit;
33
37
  const cls = bem("report-designer");
@@ -54,6 +58,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
54
58
  });
55
59
  const hubVisible = ref(false);
56
60
  const rulesDialogVisible = ref(false);
61
+ const drillDialogVisible = ref(false);
62
+ /** 宿主传入可下钻模板列表时才出现下钻配置入口(不传则无入口,spec 兼容性要求) */
63
+ const hasDrillTemplates = computed(() => (props.drillTemplates?.length ?? 0) > 0);
57
64
  const pickCandidateCells = computed(() => {
58
65
  if (!parentPick.value) return [];
59
66
  return parentPick.value.mode === "row" ? rowParentCandidates.value : colParentCandidates.value;
@@ -138,6 +145,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
138
145
  function saveConditionalRules(rules) {
139
146
  patchActiveBinding({ conditionalRules: rules });
140
147
  }
148
+ function saveDrill(drill) {
149
+ patchActiveBinding({ drill });
150
+ }
151
+ function removeDrill() {
152
+ patchActiveBinding({ drill: void 0 });
153
+ }
141
154
  function clearDropState() {
142
155
  isFieldDragging.value = false;
143
156
  dropTargetAddr.value = null;
@@ -188,7 +201,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
188
201
  plain: "",
189
202
  onClick: _cache[0] || (_cache[0] = ($event) => hubVisible.value = true)
190
203
  }, {
191
- default: withCtx(() => [..._cache[6] || (_cache[6] = [createTextVNode("数据中枢", -1)])]),
204
+ default: withCtx(() => [..._cache[8] || (_cache[8] = [createTextVNode("数据中枢", -1)])]),
192
205
  _: 1
193
206
  }),
194
207
  createElementVNode("div", {
@@ -201,7 +214,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
201
214
  plain: isPreview.value,
202
215
  onClick: _cache[1] || (_cache[1] = ($event) => setViewMode("design"))
203
216
  }, {
204
- default: withCtx(() => [..._cache[7] || (_cache[7] = [createTextVNode(" 设计模式 ", -1)])]),
217
+ default: withCtx(() => [..._cache[9] || (_cache[9] = [createTextVNode(" 设计模式 ", -1)])]),
205
218
  _: 1
206
219
  }, 8, ["type", "plain"]), createVNode(unref(UButton), {
207
220
  size: "small",
@@ -209,7 +222,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
209
222
  plain: !isPreview.value,
210
223
  onClick: _cache[2] || (_cache[2] = ($event) => setViewMode("preview"))
211
224
  }, {
212
- default: withCtx(() => [..._cache[8] || (_cache[8] = [createTextVNode(" 预览模式 ", -1)])]),
225
+ default: withCtx(() => [..._cache[10] || (_cache[10] = [createTextVNode(" 预览模式 ", -1)])]),
213
226
  _: 1
214
227
  }, 8, ["type", "plain"])], 2),
215
228
  isPreview.value ? (openBlock(), createBlock(unref(UButton), {
@@ -220,7 +233,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
220
233
  loading: exporting.value,
221
234
  onClick: exportPreviewXlsx
222
235
  }, {
223
- default: withCtx(() => [..._cache[9] || (_cache[9] = [createTextVNode(" 导出 XLSX ", -1)])]),
236
+ default: withCtx(() => [..._cache[11] || (_cache[11] = [createTextVNode(" 导出 XLSX ", -1)])]),
224
237
  _: 1
225
238
  }, 8, ["loading"])) : createCommentVNode("v-if", true),
226
239
  createElementVNode("span", { class: normalizeClass(unref(cls).e("hint")) }, " 在数据中枢配置连接与 SQL 数据集,再将左侧字段点击或拖拽到单元格完成绑定;选中已绑定格可通过悬浮卡片调整预设与条件样式。 ", 2)
@@ -228,7 +241,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
228
241
  !isPreview.value ? (openBlock(), createElementBlock("div", {
229
242
  key: 0,
230
243
  class: normalizeClass(unref(cls).e("body"))
231
- }, [createVNode(_sfc_main$8, {
244
+ }, [createVNode(_sfc_main$9, {
232
245
  class: normalizeClass(unref(cls).e("field-panel")),
233
246
  datasets: unref(catalog),
234
247
  "selection-label": unref(selectionLabel),
@@ -266,7 +279,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
266
279
  "resolve-cell-style",
267
280
  "resolve-cell-renderer"
268
281
  ]),
269
- createVNode(_sfc_main$7, {
282
+ createVNode(_sfc_main$8, {
270
283
  cell: unref(activeCell),
271
284
  binding: unref(activeBinding) ?? null,
272
285
  entries: unref(bindingEntries),
@@ -282,7 +295,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
282
295
  "host-el",
283
296
  "get-binding-at"
284
297
  ]),
285
- createVNode(_sfc_main$4, {
298
+ createVNode(_sfc_main$5, {
286
299
  cell: dropTargetAddr.value,
287
300
  "host-el": gridHostEl.value,
288
301
  "get-grid": getDesignGrid,
@@ -295,7 +308,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
295
308
  "fallback-label"
296
309
  ]),
297
310
  (openBlock(true), createElementBlock(Fragment, null, renderList(pickCandidateCells.value, (addr) => {
298
- return openBlock(), createBlock(_sfc_main$4, {
311
+ return openBlock(), createBlock(_sfc_main$5, {
299
312
  key: `pick-${addr.row}-${addr.col}`,
300
313
  cell: addr,
301
314
  "host-el": gridHostEl.value,
@@ -309,7 +322,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
309
322
  class: normalizeClass(unref(cls).e("pick-hint")),
310
323
  role: "status"
311
324
  }, toDisplayString(unref(parentPick).mode === "row" ? "点击分组头作为行方向父格" : "点击分组头作为列方向父格"), 3)) : createCommentVNode("v-if", true),
312
- createVNode(_sfc_main$5, {
325
+ createVNode(_sfc_main$6, {
313
326
  cell: unref(activeCell),
314
327
  binding: unref(activeBinding) ?? null,
315
328
  "resolved-row-parent-label": unref(resolvedRowParentLabel),
@@ -317,6 +330,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
317
330
  "parent-pick-mode": unref(parentPick)?.mode ?? null,
318
331
  "row-parent-candidates": unref(rowParentCandidates),
319
332
  "col-parent-candidates": unref(colParentCandidates),
333
+ "show-drill": hasDrillTemplates.value,
320
334
  "resolve-field-label": unref(resolveFieldLabel),
321
335
  "get-binding-at": unref(getBindingAt),
322
336
  "host-el": gridHostEl.value,
@@ -324,6 +338,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
324
338
  onPatch: unref(patchActiveBinding),
325
339
  onRemove: unref(removeActiveBinding),
326
340
  onOpenRules: _cache[3] || (_cache[3] = ($event) => rulesDialogVisible.value = true),
341
+ onOpenDrill: _cache[4] || (_cache[4] = ($event) => drillDialogVisible.value = true),
327
342
  onStartParentPick: unref(startParentPick),
328
343
  onCancelParentPick: unref(cancelParentPick),
329
344
  onClearParent: unref(clearParent)
@@ -335,6 +350,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
335
350
  "parent-pick-mode",
336
351
  "row-parent-candidates",
337
352
  "col-parent-candidates",
353
+ "show-drill",
338
354
  "resolve-field-label",
339
355
  "get-binding-at",
340
356
  "host-el",
@@ -352,15 +368,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
352
368
  ref: previewViewerRef,
353
369
  connector: props.connector,
354
370
  template: previewTemplate.value,
355
- workbook: unref(previewWorkbook)
371
+ workbook: unref(previewWorkbook),
372
+ "resolve-template": props.resolveTemplate
356
373
  }, null, 8, [
357
374
  "connector",
358
375
  "template",
359
- "workbook"
376
+ "workbook",
377
+ "resolve-template"
360
378
  ])], 2)) : createCommentVNode("v-if", true),
361
379
  createVNode(unref(UDrawer), {
362
380
  modelValue: hubVisible.value,
363
- "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => hubVisible.value = $event),
381
+ "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => hubVisible.value = $event),
364
382
  class: normalizeClass(unref(cls).e("hub-drawer")),
365
383
  "show-close": ""
366
384
  }, {
@@ -370,9 +388,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
370
388
  }, null, 8, ["hub"])]),
371
389
  _: 1
372
390
  }, 8, ["modelValue", "class"]),
373
- createVNode(_sfc_main$6, {
391
+ createVNode(_sfc_main$7, {
374
392
  modelValue: rulesDialogVisible.value,
375
- "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => rulesDialogVisible.value = $event),
393
+ "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => rulesDialogVisible.value = $event),
376
394
  rules: unref(activeBinding)?.conditionalRules ?? EMPTY_CONDITIONAL_RULES,
377
395
  "binding-field": unref(activeBinding)?.field ?? "",
378
396
  "dataset-fields": unref(activeDatasetFields),
@@ -384,7 +402,24 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
384
402
  "binding-field",
385
403
  "dataset-fields",
386
404
  "field-type"
387
- ])
405
+ ]),
406
+ hasDrillTemplates.value ? (openBlock(), createBlock(_sfc_main$4, {
407
+ key: 2,
408
+ modelValue: drillDialogVisible.value,
409
+ "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => drillDialogVisible.value = $event),
410
+ templates: props.drillTemplates ?? [],
411
+ fields: unref(activeDatasetFields),
412
+ drill: unref(activeBinding)?.drill,
413
+ "resolve-template": props.resolveTemplate,
414
+ onSave: saveDrill,
415
+ onRemove: removeDrill
416
+ }, null, 8, [
417
+ "modelValue",
418
+ "templates",
419
+ "fields",
420
+ "drill",
421
+ "resolve-template"
422
+ ])) : createCommentVNode("v-if", true)
388
423
  ], 2);
389
424
  };
390
425
  }
@@ -1,16 +1,22 @@
1
1
  import _sfc_main$1 from "../sheet/sheet.js";
2
2
  import { exportFilledReportXlsx } from "../../report/export-xlsx.js";
3
+ import { buildFilledReportPrintHtml } from "../../report/print.js";
3
4
  import { applyGridColWidths, applySheetColWidths } from "./designer/col-widths.js";
4
5
  import _sfc_main$2 from "./filter-bar.js";
5
6
  import { previewGridSize } from "./preview-grid-size.js";
7
+ import { printHtmlDocument } from "./print.js";
6
8
  import { useReportViewer } from "./use-report-viewer.js";
7
- import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, toDisplayString, unref, useTemplateRef, watch } from "vue";
9
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, resolveComponent, shallowRef, toDisplayString, unref, useTemplateRef, watch, withCtx } from "vue";
10
+ import { UButton, UDialog } from "@veltra/desktop";
8
11
  import { bem } from "@veltra/utils";
9
12
  import { saveBlob } from "@cat-kit/fe";
10
13
  import { Workbook } from "@veltra/sheet-core";
11
14
  //#region src/components/report/report-viewer.vue
12
15
  const EXPORT_NOT_READY_MESSAGE = "报表数据尚未就绪,请等待取数完成后再导出";
13
16
  const EXPORT_LOADING_MESSAGE = "报表数据加载中,请稍后再导出";
17
+ /** 取数未完成时拒绝打印的可读错误 */
18
+ const PRINT_NOT_READY_MESSAGE = "报表数据尚未就绪,请等待取数完成后再打印";
19
+ const PRINT_LOADING_MESSAGE = "报表数据加载中,请稍后再打印";
14
20
  /** 查看器工作簿:宿主可注入(同 USheet `workbook?` 先例),缺省内部自建;模板 / 填充结果都 restore 进活动 sheet 只读展示 */
15
21
  const _sfc_main = /*@__PURE__*/ defineComponent({
16
22
  name: "UReportViewer",
@@ -19,26 +25,45 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
19
25
  connector: {},
20
26
  template: {},
21
27
  workbook: {},
22
- colWidths: {}
28
+ colWidths: {},
29
+ resolveTemplate: { type: Function },
30
+ initialValues: {}
23
31
  },
24
32
  setup(__props, { expose: __expose }) {
33
+ /** 与 `ReportViewerProps` 交叉显式列出下钻 props,避免 SFC 对跨文件 interface 漏抽 runtime 声明 */
25
34
  const props = __props;
26
35
  const cls = bem("report-viewer");
27
36
  /** 取数未完成时拒绝导出的可读错误 */
28
37
  const internalWorkbook = new Workbook();
29
38
  const workbook = computed(() => props.workbook ?? internalWorkbook);
30
39
  const sheetRef = useTemplateRef("sheetRef");
31
- const { params, values, loading, error, filledSnapshot, filledColWidths, refresh, setValues } = useReportViewer(props);
40
+ const { params, values, loading, error, filledSnapshot, filledColWidths, currentTemplate, canDrillBack, resolveDrillHit, resolveDrillTarget, drillInto, drillBack, refresh, setValues } = useReportViewer(props);
41
+ /** 配了下钻的单元格在查看态展示可点击链接样式(蓝字 + 下划线),便于用户直观感知 */
42
+ const resolveViewerCellStyle = (addr, base) => {
43
+ if (!props.resolveTemplate) return base;
44
+ if (!resolveDrillHit(addr)) return base;
45
+ return {
46
+ ...base,
47
+ font: {
48
+ ...base?.font,
49
+ color: base?.font?.color ?? "#2f54eb",
50
+ underline: base?.font?.underline ?? true
51
+ }
52
+ };
53
+ };
54
+ /** 弹框下钻:先挂 UDialog 再打开,避免 modelValue 初始为 true 时 overlay 不进入 */
55
+ const drillDialogVisible = ref(false);
56
+ const drillDialog = shallowRef(null);
32
57
  const renderSize = computed(() => {
33
58
  const filled = filledSnapshot.value;
34
59
  if (filled) return previewGridSize(filled, "filled");
35
- return previewGridSize(props.template, "template");
60
+ return previewGridSize(currentTemplate.value, "template");
36
61
  });
37
- /** 宿主显式传入优先;否则取数完成后用展开映射列宽;再回落模板设计态列宽 */
62
+ /** 宿主显式传入优先;否则取数完成后用展开映射列宽;再回落当前层模板设计态列宽 */
38
63
  const effectiveColWidths = computed(() => {
39
64
  if (props.colWidths?.length) return props.colWidths;
40
65
  if (filledColWidths.value?.length) return filledColWidths.value;
41
- return props.template.colWidths;
66
+ return currentTemplate.value.colWidths;
42
67
  });
43
68
  /** 将运行时列宽写入模型与 VTable(模型为单一事实源;grid 构造/content-reset 也会回放) */
44
69
  function applyRuntimeColWidths() {
@@ -64,11 +89,65 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
64
89
  sheet.history.clear();
65
90
  nextTick(applyRuntimeColWidths);
66
91
  }
67
- watch(() => props.template, (template) => applySnapshot(template, "template"), { immediate: true });
92
+ watch(currentTemplate, (template) => applySnapshot(template, "template"), { immediate: true });
68
93
  watch(filledSnapshot, (filled) => {
69
94
  if (filled) applySnapshot(filled, "filled");
70
95
  });
71
96
  watch(effectiveColWidths, applyRuntimeColWidths);
97
+ /** 悬停命中下钻格时给网格加 cursor 提示 */
98
+ const drillHover = ref(false);
99
+ let pointerStart = null;
100
+ watch(drillDialogVisible, (visible) => {
101
+ if (!visible) drillDialog.value = null;
102
+ });
103
+ async function openDrillDialog(config, record) {
104
+ const next = await resolveDrillTarget(config, record);
105
+ if (!next) return;
106
+ drillDialog.value = next;
107
+ await nextTick();
108
+ drillDialogVisible.value = true;
109
+ }
110
+ /** 事件坐标 → 模型地址(经当前 SheetGrid 命中测试;网格随内容尺寸重建,实例按事件时刻取) */
111
+ function resolveEventAddr(event) {
112
+ const gridEl = event.currentTarget.querySelector(".u-sheet__grid-instance");
113
+ const grid = sheetRef.value?.getGrid();
114
+ if (!gridEl || !grid) return null;
115
+ const rect = gridEl.getBoundingClientRect();
116
+ return grid.hitTestSheetAddr(event.clientX - rect.left, event.clientY - rect.top);
117
+ }
118
+ function handleGridPointerDown(event) {
119
+ pointerStart = {
120
+ x: event.clientX,
121
+ y: event.clientY
122
+ };
123
+ }
124
+ function handleGridClick(event) {
125
+ if (!props.resolveTemplate) return;
126
+ const start = pointerStart;
127
+ pointerStart = null;
128
+ if (start) {
129
+ const dx = event.clientX - start.x;
130
+ const dy = event.clientY - start.y;
131
+ if (dx * dx + dy * dy > 16) return;
132
+ }
133
+ const addr = resolveEventAddr(event);
134
+ if (!addr) return;
135
+ const hit = resolveDrillHit(addr);
136
+ if (!hit) return;
137
+ if (hit.config.openMode === "dialog") {
138
+ openDrillDialog(hit.config, hit.record);
139
+ return;
140
+ }
141
+ drillInto(hit.config, hit.record);
142
+ }
143
+ function handleGridPointerMove(event) {
144
+ const addr = props.resolveTemplate ? resolveEventAddr(event) : null;
145
+ const next = !!(addr ? resolveDrillHit(addr) : null);
146
+ if (next !== drillHover.value) drillHover.value = next;
147
+ }
148
+ function handleGridPointerLeave() {
149
+ drillHover.value = false;
150
+ }
72
151
  async function exportXlsx() {
73
152
  if (loading.value) throw new Error(EXPORT_LOADING_MESSAGE);
74
153
  if (!filledSnapshot.value) throw new Error(EXPORT_NOT_READY_MESSAGE);
@@ -76,47 +155,97 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
76
155
  const buffer = await exportFilledReportXlsx(sheet);
77
156
  saveBlob(new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }), `${sheet.name || "报表"}.xlsx`);
78
157
  }
158
+ /**
159
+ * 打印填充报表(纯前端):填充 sheet 模型 → 打印专用 HTML(buildFilledReportPrintHtml)
160
+ * → 隐藏 iframe 调起浏览器打印。守卫语义与 exportXlsx 一致(取数完成前拒绝)。
161
+ */
162
+ function print(options) {
163
+ if (loading.value) throw new Error(PRINT_LOADING_MESSAGE);
164
+ if (!filledSnapshot.value) throw new Error(PRINT_NOT_READY_MESSAGE);
165
+ printHtmlDocument(buildFilledReportPrintHtml(workbook.value.activeSheet, options));
166
+ }
79
167
  __expose({
80
168
  refresh,
81
- exportXlsx
169
+ exportXlsx,
170
+ print
82
171
  });
83
172
  return (_ctx, _cache) => {
84
- return openBlock(), createElementBlock("div", { class: normalizeClass(unref(cls).b) }, [unref(params).length ? (openBlock(), createBlock(_sfc_main$2, {
85
- key: 0,
86
- "query-params": unref(params),
87
- values: unref(values),
88
- "onUpdate:values": unref(setValues)
89
- }, null, 8, [
90
- "query-params",
91
- "values",
92
- "onUpdate:values"
93
- ])) : createCommentVNode("v-if", true), createElementVNode("div", { class: normalizeClass(unref(cls).e("body")) }, [unref(loading) ? (openBlock(), createElementBlock("div", {
94
- key: 0,
95
- class: normalizeClass(unref(cls).e("loading")),
96
- "aria-live": "polite"
97
- }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("loading-spinner")) }, null, 2), _cache[0] || (_cache[0] = createElementVNode("span", null, "正在加载报表数据…", -1))], 2)) : unref(error) ? (openBlock(), createElementBlock("div", {
98
- key: 1,
99
- class: normalizeClass(unref(cls).e("error")),
100
- role: "alert"
101
- }, toDisplayString(unref(error).message), 3)) : createCommentVNode("v-if", true), createVNode(unref(_sfc_main$1), {
102
- ref_key: "sheetRef",
103
- ref: sheetRef,
104
- class: normalizeClass(unref(cls).e("sheet")),
105
- workbook: workbook.value,
106
- rows: renderSize.value.rows,
107
- cols: renderSize.value.cols,
108
- "show-toolbar": false,
109
- "show-formula-bar": false,
110
- "show-tabs": false,
111
- "show-row-header": false,
112
- "show-col-header": false,
113
- readonly: ""
114
- }, null, 8, [
115
- "class",
116
- "workbook",
117
- "rows",
118
- "cols"
119
- ])], 2)], 2);
173
+ const _component_u_report_viewer = resolveComponent("u-report-viewer");
174
+ return openBlock(), createElementBlock("div", { class: normalizeClass(unref(cls).b) }, [
175
+ unref(params).length ? (openBlock(), createBlock(_sfc_main$2, {
176
+ key: 0,
177
+ "query-params": unref(params),
178
+ values: unref(values),
179
+ "onUpdate:values": unref(setValues)
180
+ }, null, 8, [
181
+ "query-params",
182
+ "values",
183
+ "onUpdate:values"
184
+ ])) : createCommentVNode("v-if", true),
185
+ unref(canDrillBack) ? (openBlock(), createElementBlock("div", {
186
+ key: 1,
187
+ class: normalizeClass(unref(cls).e("drill-bar"))
188
+ }, [createVNode(unref(UButton), {
189
+ size: "small",
190
+ plain: "",
191
+ onClick: unref(drillBack)
192
+ }, {
193
+ default: withCtx(() => [..._cache[1] || (_cache[1] = [createTextVNode("返回上一层", -1)])]),
194
+ _: 1
195
+ }, 8, ["onClick"])], 2)) : createCommentVNode("v-if", true),
196
+ createElementVNode("div", { class: normalizeClass(unref(cls).e("body")) }, [unref(loading) ? (openBlock(), createElementBlock("div", {
197
+ key: 0,
198
+ class: normalizeClass(unref(cls).e("loading")),
199
+ "aria-live": "polite"
200
+ }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("loading-spinner")) }, null, 2), _cache[2] || (_cache[2] = createElementVNode("span", null, "正在加载报表数据…", -1))], 2)) : unref(error) ? (openBlock(), createElementBlock("div", {
201
+ key: 1,
202
+ class: normalizeClass(unref(cls).e("error")),
203
+ role: "alert"
204
+ }, toDisplayString(unref(error).message), 3)) : createCommentVNode("v-if", true), createVNode(unref(_sfc_main$1), {
205
+ ref_key: "sheetRef",
206
+ ref: sheetRef,
207
+ class: normalizeClass([unref(cls).e("sheet"), drillHover.value ? unref(cls).em("sheet", "drill-hover") : ""]),
208
+ workbook: workbook.value,
209
+ rows: renderSize.value.rows,
210
+ cols: renderSize.value.cols,
211
+ "show-toolbar": false,
212
+ "show-formula-bar": false,
213
+ "show-tabs": false,
214
+ "show-row-header": false,
215
+ "show-col-header": false,
216
+ "resolve-cell-style": resolveViewerCellStyle,
217
+ readonly: "",
218
+ onPointerdown: handleGridPointerDown,
219
+ onPointermove: handleGridPointerMove,
220
+ onPointerleave: handleGridPointerLeave,
221
+ onClick: handleGridClick
222
+ }, null, 8, [
223
+ "class",
224
+ "workbook",
225
+ "rows",
226
+ "cols"
227
+ ])], 2),
228
+ drillDialog.value ? (openBlock(), createBlock(unref(UDialog), {
229
+ key: 2,
230
+ modelValue: drillDialogVisible.value,
231
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => drillDialogVisible.value = $event),
232
+ title: "详情报表",
233
+ class: normalizeClass(unref(cls).e("drill-dialog"))
234
+ }, {
235
+ default: withCtx(() => [createVNode(_component_u_report_viewer, {
236
+ connector: __props.connector,
237
+ template: drillDialog.value.template,
238
+ "initial-values": drillDialog.value.params,
239
+ "resolve-template": __props.resolveTemplate
240
+ }, null, 8, [
241
+ "connector",
242
+ "template",
243
+ "initial-values",
244
+ "resolve-template"
245
+ ])]),
246
+ _: 1
247
+ }, 8, ["modelValue", "class"])) : createCommentVNode("v-if", true)
248
+ ], 2);
120
249
  };
121
250
  }
122
251
  });
@@ -37,6 +37,40 @@
37
37
  flex: 1;
38
38
  min-height: 0;
39
39
  min-width: 0;
40
+ /* 下钻格可点提示:VTable pointermove 会把容器内联 cursor 重置为 default,需 !important 覆盖 */
41
+ }
42
+ .u-report-viewer__sheet--drill-hover,
43
+ .u-report-viewer__sheet--drill-hover .u-sheet__grid-instance,
44
+ .u-report-viewer__sheet--drill-hover .u-sheet__grid-instance canvas,
45
+ .u-report-viewer__sheet--drill-hover .u-sheet__grid-instance * {
46
+ cursor: pointer !important;
47
+ }
48
+ .u-report-viewer__drill-bar {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: var(--u-gap-small);
52
+ }
53
+ .u-report-viewer {
54
+ /* 下钻弹框挂在 UDialog 根(Teleport 到 body),给内嵌查看器明确高度 */
55
+ }
56
+ .u-report-viewer__drill-dialog {
57
+ width: min(960px, 92vw);
58
+ height: min(80vh, 720px);
59
+ }
60
+ .u-report-viewer__drill-dialog .u-dialog__body {
61
+ flex: 1;
62
+ min-height: 0;
63
+ display: flex;
64
+ flex-direction: column;
65
+ }
66
+ .u-report-viewer__drill-dialog .u-scroll__container,
67
+ .u-report-viewer__drill-dialog .u-scroll__content {
68
+ height: 100%;
69
+ min-height: 0;
70
+ }
71
+ .u-report-viewer__drill-dialog .u-report-viewer {
72
+ height: 100%;
73
+ min-height: 0;
40
74
  }
41
75
  .u-report-viewer__loading {
42
76
  position: absolute;
@@ -368,6 +402,63 @@
368
402
  color: var(--u-text-color-second);
369
403
  }
370
404
 
405
+ /* 下钻配置对话框内容 */
406
+ .u-report-drill-dialog {
407
+ display: flex;
408
+ flex-direction: column;
409
+ gap: 12px;
410
+ }
411
+ .u-report-drill-dialog__section {
412
+ display: flex;
413
+ flex-direction: column;
414
+ gap: 6px;
415
+ }
416
+ .u-report-drill-dialog__label {
417
+ font-size: 12px;
418
+ font-weight: 600;
419
+ color: var(--u-text-color-second);
420
+ }
421
+ .u-report-drill-dialog__hint {
422
+ margin: 0;
423
+ font-size: 12px;
424
+ line-height: 1.5;
425
+ color: var(--u-text-color-second);
426
+ }
427
+ .u-report-drill-dialog__error {
428
+ margin: 0;
429
+ font-size: 12px;
430
+ line-height: 1.5;
431
+ color: var(--u-color-danger);
432
+ }
433
+ .u-report-drill-dialog__list {
434
+ margin: 0;
435
+ padding: 0;
436
+ list-style: none;
437
+ display: flex;
438
+ flex-direction: column;
439
+ gap: 8px;
440
+ max-height: 320px;
441
+ overflow: auto;
442
+ }
443
+ .u-report-drill-dialog__mapping {
444
+ display: flex;
445
+ align-items: center;
446
+ gap: 6px;
447
+ }
448
+ .u-report-drill-dialog__field {
449
+ width: 160px;
450
+ flex: none;
451
+ }
452
+ .u-report-drill-dialog__param {
453
+ width: 200px;
454
+ flex: none;
455
+ }
456
+ .u-report-drill-dialog__arrow {
457
+ flex: none;
458
+ font-size: 12px;
459
+ color: var(--u-text-color-second);
460
+ }
461
+
371
462
  .u-report-rule-row {
372
463
  display: flex;
373
464
  align-items: flex-start;
@@ -152,6 +152,7 @@ function useReportDesigner(options) {
152
152
  };
153
153
  if ("rowParent" in patch && patch.rowParent === void 0) delete next.rowParent;
154
154
  if ("colParent" in patch && patch.colParent === void 0) delete next.colParent;
155
+ if ("drill" in patch && patch.drill === void 0) delete next.drill;
155
156
  if (("aggregate" in patch || "expand" in patch) && !("preset" in patch) && !presetStillApplies(next)) delete next.preset;
156
157
  if (patch.preset === "subtotal") {
157
158
  const inferred = inferParentsForPreset(cell, "subtotal", getBindingAt);
@@ -295,7 +296,7 @@ function useReportDesigner(options) {
295
296
  return props.connector.query(connection, dataset.sql, values);
296
297
  }
297
298
  const resolveCellStyle = createBindingBadgeStyleResolver(getBindingAt);
298
- const resolveCellRenderer = createBindingBadgeRenderer(getBindingAt, resolveFieldLabel);
299
+ const resolveCellRenderer = createBindingBadgeRenderer(getBindingAt, resolveFieldLabel, (addr) => workbook.value.activeSheet.getEffectiveStyle(addr)?.align);
299
300
  function getTemplate() {
300
301
  const resolved = datasets.value.flatMap((dataset) => {
301
302
  const connection = resolveDatasetConnection(dataset);