@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.
- package/dist/components/report/binding-badge.js +45 -21
- package/dist/components/report/designer/col-widths.js +18 -4
- package/dist/components/report/designer/conditional-rules/helpers.js +3 -3
- package/dist/components/report/designer/float-panel.js +73 -36
- package/dist/components/report/designer/rule-row.js +111 -127
- package/dist/components/report/designer/rules-dialog.js +5 -22
- package/dist/components/report/hub-dataset-editor.js +16 -1
- package/dist/components/report/preview-grid-size.js +32 -0
- package/dist/components/report/report-designer.js +75 -25
- package/dist/components/report/report-viewer.js +38 -25
- package/dist/components/report/style2.css +44 -39
- package/dist/components/report/use-report-designer.js +34 -12
- package/dist/components/report/use-report-viewer.js +6 -1
- package/dist/components/sheet/popups/border-popup.js +30 -2
- package/dist/components/sheet/popups/fill-color-popup.js +4 -3
- package/dist/components/sheet/popups/font-color-popup.js +2 -2
- package/dist/components/sheet/popups/font-size-popup.js +2 -2
- package/dist/components/sheet/sheet-context-menu.js +6 -3
- package/dist/components/sheet/sheet.js +8 -0
- package/dist/components/sheet/use-sheet-grid.js +14 -4
- package/dist/components/sheet/use-sheet-state.js +3 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/report/binding.d.ts +49 -11
- package/dist/report/binding.js +186 -29
- package/dist/report/connector.js +1 -1
- package/dist/report/export-xlsx.d.ts +3 -5
- package/dist/report/export-xlsx.js +7 -10
- package/dist/report/index.d.ts +2 -2
- package/dist/report/render/aggregate.js +20 -8
- package/dist/report/template.d.ts +8 -4
- package/dist/report/template.js +5 -2
- package/dist/tools/apply-style.js +29 -0
- package/dist/tools/builtin.js +17 -17
- package/dist/tools/context.d.ts +38 -5
- package/dist/tools/context.js +38 -1
- package/dist/types/report-designer.d.ts +2 -0
- package/dist/types/report-viewer.d.ts +2 -2
- package/dist/types/sheet.d.ts +4 -0
- package/package.json +15 -6
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +1 -0
- package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
- package/src/components/report/__test__/report-designer.test.ts +76 -0
- package/src/components/report/__test__/report-viewer.test.ts +24 -1
- package/src/components/report/__test__/use-report-designer.test.ts +142 -10
- package/src/components/report/binding-badge.ts +39 -20
- package/src/components/report/designer/col-widths.ts +34 -11
- package/src/components/report/designer/conditional-rules/helpers.ts +6 -3
- package/src/components/report/designer/float-panel.vue +71 -27
- package/src/components/report/designer/rule-row.vue +67 -67
- package/src/components/report/designer/rules-dialog.vue +3 -19
- package/src/components/report/hub-dataset-editor.vue +19 -1
- package/src/components/report/preview-grid-size.ts +33 -0
- package/src/components/report/report-designer.vue +79 -15
- package/src/components/report/report-viewer.vue +37 -29
- package/src/components/report/style-designer.scss +45 -41
- package/src/components/report/use-report-designer.ts +63 -14
- package/src/components/report/use-report-viewer.ts +11 -2
- package/src/components/sheet/__test__/sheet-component.test.ts +37 -0
- package/src/components/sheet/__test__/sheet-context-menu.test.ts +3 -2
- package/src/components/sheet/popups/border-popup.vue +31 -3
- package/src/components/sheet/popups/fill-color-popup.vue +5 -4
- package/src/components/sheet/popups/font-color-popup.vue +2 -2
- package/src/components/sheet/popups/font-size-popup.vue +2 -2
- package/src/components/sheet/sheet-context-menu.ts +8 -2
- package/src/components/sheet/sheet.vue +2 -0
- package/src/components/sheet/use-sheet-grid.ts +17 -3
- package/src/components/sheet/use-sheet-state.ts +8 -3
- package/src/report/__test__/binding.test.ts +103 -1
- package/src/report/__test__/connector.test.ts +21 -0
- package/src/report/__test__/export-xlsx.test.ts +4 -4
- package/src/report/__test__/render.test.ts +102 -0
- package/src/report/__test__/template.test.ts +11 -0
- package/src/report/binding.ts +278 -47
- package/src/report/connector.ts +3 -2
- package/src/report/export-xlsx.ts +9 -14
- package/src/report/render/aggregate.ts +29 -8
- package/src/report/template.ts +8 -4
- package/src/tools/__test__/apply-style.test.ts +129 -0
- package/src/tools/apply-style.ts +41 -0
- package/src/tools/builtin.ts +18 -18
- package/src/tools/context.ts +67 -4
- package/src/tools/index.ts +1 -0
- package/src/types/report-designer.ts +2 -0
- package/src/types/report-viewer.ts +2 -2
- package/src/types/sheet.ts +4 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import _sfc_main$1 from "../sheet/sheet.js";
|
|
2
|
-
import {
|
|
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
|
-
/**
|
|
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:
|
|
70
|
-
if (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[
|
|
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[
|
|
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[
|
|
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[
|
|
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
|
|
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:
|
|
224
|
-
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[
|
|
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:
|
|
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[
|
|
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,
|
|
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
|
-
/**
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
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 (!
|
|
42
|
-
|
|
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
|
|
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(
|
|
51
|
-
sheet.restoreContent(
|
|
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(
|
|
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
|
|
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:
|
|
96
|
-
cols:
|
|
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:
|
|
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:
|
|
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-
|
|
378
|
+
background: var(--u-bg-color-middle);
|
|
363
379
|
}
|
|
364
380
|
.u-report-rule-row__handle {
|
|
365
381
|
flex: none;
|
|
366
|
-
width:
|
|
367
|
-
margin-top:
|
|
368
|
-
color: var(--u-text-color-
|
|
369
|
-
font-size:
|
|
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-
|
|
402
|
+
.u-report-rule-row__when {
|
|
386
403
|
display: flex;
|
|
387
404
|
align-items: center;
|
|
388
|
-
gap:
|
|
405
|
+
gap: 6px;
|
|
389
406
|
flex-wrap: wrap;
|
|
390
407
|
}
|
|
391
|
-
.u-report-rule-
|
|
392
|
-
|
|
393
|
-
|
|
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-
|
|
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-
|
|
400
|
-
|
|
401
|
-
|
|
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:
|
|
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:
|
|
435
|
+
width: 120px;
|
|
418
436
|
flex: none;
|
|
419
437
|
}
|
|
420
438
|
.u-report-rule-row__between-input {
|
|
421
|
-
width:
|
|
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-
|
|
453
|
+
.u-report-rule-row__remove {
|
|
447
454
|
flex: none;
|
|
448
|
-
|
|
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,
|
|
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
|
|
84
|
-
if (rowParent) binding.rowParent = rowParent;
|
|
85
|
-
|
|
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
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
};
|