@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
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
<span :class="cls.e('hint')">
|
|
37
37
|
在数据中枢配置连接与 SQL
|
|
38
|
-
|
|
38
|
+
数据集,再将左侧字段点击或拖拽到单元格完成绑定;选中已绑定格可通过悬浮卡片调整预设与条件样式。
|
|
39
39
|
</span>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
ref="sheetRef"
|
|
60
60
|
:class="cls.e('sheet')"
|
|
61
61
|
:workbook="workbook"
|
|
62
|
-
:rows="
|
|
63
|
-
:cols="
|
|
62
|
+
:rows="gridRows"
|
|
63
|
+
:cols="gridCols"
|
|
64
64
|
:show-tabs="false"
|
|
65
65
|
:resolve-cell-style="resolveCellStyle"
|
|
66
66
|
:resolve-cell-renderer="resolveCellRenderer"
|
|
@@ -84,6 +84,20 @@
|
|
|
84
84
|
:fallback-label="selectionLabel"
|
|
85
85
|
/>
|
|
86
86
|
|
|
87
|
+
<u-report-drop-highlight-overlay
|
|
88
|
+
v-for="addr in pickCandidateCells"
|
|
89
|
+
:key="`pick-${addr.row}-${addr.col}`"
|
|
90
|
+
:cell="addr"
|
|
91
|
+
:host-el="gridHostEl"
|
|
92
|
+
:get-grid="getDesignGrid"
|
|
93
|
+
:dragging="false"
|
|
94
|
+
fallback-label=""
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<div v-if="parentPick" :class="cls.e('pick-hint')" role="status">
|
|
98
|
+
{{ parentPick.mode === 'row' ? '点击分组头作为行方向父格' : '点击分组头作为列方向父格' }}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
87
101
|
<u-report-float-panel
|
|
88
102
|
:cell="activeCell"
|
|
89
103
|
:binding="activeBinding ?? null"
|
|
@@ -93,6 +107,7 @@
|
|
|
93
107
|
:row-parent-candidates="rowParentCandidates"
|
|
94
108
|
:col-parent-candidates="colParentCandidates"
|
|
95
109
|
:resolve-field-label="resolveFieldLabel"
|
|
110
|
+
:get-binding-at="getBindingAt"
|
|
96
111
|
:host-el="gridHostEl"
|
|
97
112
|
:get-grid="getDesignGrid"
|
|
98
113
|
@patch="patchActiveBinding"
|
|
@@ -111,12 +126,11 @@
|
|
|
111
126
|
:connector="props.connector"
|
|
112
127
|
:template="previewTemplate"
|
|
113
128
|
:workbook="previewWorkbook"
|
|
114
|
-
:col-widths="designColWidths"
|
|
115
129
|
/>
|
|
116
130
|
</div>
|
|
117
131
|
|
|
118
132
|
<u-drawer v-model="hubVisible" :class="cls.e('hub-drawer')" show-close>
|
|
119
|
-
<u-report-dataset-hub :hub="designer" @close="
|
|
133
|
+
<u-report-dataset-hub :hub="designer" @close="onHubClose" />
|
|
120
134
|
</u-drawer>
|
|
121
135
|
|
|
122
136
|
<u-report-rules-dialog
|
|
@@ -135,7 +149,7 @@ import { UButton, UDrawer } from '@veltra/desktop'
|
|
|
135
149
|
import type { CellAddress, SheetGrid } from '@veltra/sheet-core'
|
|
136
150
|
import { Workbook } from '@veltra/sheet-core'
|
|
137
151
|
import { bem } from '@veltra/utils'
|
|
138
|
-
import { computed, nextTick, ref, shallowRef, useTemplateRef, watch } from 'vue'
|
|
152
|
+
import { computed, nextTick, onScopeDispose, ref, shallowRef, useTemplateRef, watch } from 'vue'
|
|
139
153
|
|
|
140
154
|
import type { ReportTemplate } from '../../report/template'
|
|
141
155
|
import type { ConditionalRule } from '../../report/types'
|
|
@@ -149,7 +163,7 @@ import type {
|
|
|
149
163
|
import { USheet } from '../sheet'
|
|
150
164
|
import UReportDatasetHub from './designer-hub.vue'
|
|
151
165
|
import { resolveGridDropAddress } from './designer/cell-coords'
|
|
152
|
-
import { applyGridColWidths, readGridColWidths } from './designer/col-widths'
|
|
166
|
+
import { applyGridColWidths, applySheetColWidths, readGridColWidths } from './designer/col-widths'
|
|
153
167
|
import UReportDropHighlightOverlay from './designer/drop-highlight-overlay.vue'
|
|
154
168
|
import UReportFloatPanel from './designer/float-panel.vue'
|
|
155
169
|
import UReportRulesDialog from './designer/rules-dialog.vue'
|
|
@@ -166,7 +180,7 @@ const emit = defineEmits<ReportDesignerEmits>()
|
|
|
166
180
|
|
|
167
181
|
const cls = bem('report-designer')
|
|
168
182
|
|
|
169
|
-
/**
|
|
183
|
+
/** 设计态网格默认尺寸(新建模板;已声明尺寸后以模型为准,避免删行后被 props 撑回) */
|
|
170
184
|
const DESIGN_ROWS = 24
|
|
171
185
|
const DESIGN_COLS = 10
|
|
172
186
|
|
|
@@ -210,9 +224,38 @@ const {
|
|
|
210
224
|
getTemplate
|
|
211
225
|
} = designer
|
|
212
226
|
|
|
227
|
+
/** 渲染行列数跟随模型(删行后不再被固定 props 经 ensureTableSize 撑回) */
|
|
228
|
+
const gridRows = computed(() => {
|
|
229
|
+
const rows = workbook.value.activeSheet.rows
|
|
230
|
+
return rows > 0 ? rows : DESIGN_ROWS
|
|
231
|
+
})
|
|
232
|
+
const gridCols = computed(() => {
|
|
233
|
+
const cols = workbook.value.activeSheet.cols
|
|
234
|
+
return cols > 0 ? cols : DESIGN_COLS
|
|
235
|
+
})
|
|
236
|
+
|
|
213
237
|
const hubVisible = ref(false)
|
|
214
238
|
const rulesDialogVisible = ref(false)
|
|
215
239
|
|
|
240
|
+
const pickCandidateCells = computed((): CellAddress[] => {
|
|
241
|
+
if (!parentPick.value) return []
|
|
242
|
+
return parentPick.value.mode === 'row' ? rowParentCandidates.value : colParentCandidates.value
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
function onHubClose(): void {
|
|
246
|
+
hubVisible.value = false
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
watch(hubVisible, (open) => {
|
|
250
|
+
if (!open) emit('datasets-change')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
watch(
|
|
254
|
+
() => designer.datasets.value,
|
|
255
|
+
() => emit('datasets-change'),
|
|
256
|
+
{ deep: true }
|
|
257
|
+
)
|
|
258
|
+
|
|
216
259
|
const sheetRef = useTemplateRef<SheetExposed>('sheetRef')
|
|
217
260
|
const previewViewerRef = useTemplateRef<ReportViewerExposed>('previewViewerRef')
|
|
218
261
|
const gridHostRef = useTemplateRef<HTMLElement>('gridHostRef')
|
|
@@ -237,17 +280,21 @@ const isPreview = computed(() => viewMode.value === 'preview')
|
|
|
237
280
|
const previewTemplate = shallowRef<ReportTemplate | null>(null)
|
|
238
281
|
/** 预览工作簿自持(XLSX 导出需要拿到填充后的 sheet,05 票 workbook? prop 先例) */
|
|
239
282
|
const previewWorkbook = new Workbook()
|
|
240
|
-
/**
|
|
283
|
+
/** 当前设计态列宽(与 Sheet 模型同步;getTemplate 经 snapshot.colWidths 吐出) */
|
|
241
284
|
const designColWidths = ref<Array<[number, number]>>([])
|
|
242
285
|
const exporting = ref(false)
|
|
243
286
|
|
|
244
|
-
/**
|
|
287
|
+
/** 从当前设计网格捕获列宽并写回 Sheet 模型(网格未就绪则保留旧值) */
|
|
245
288
|
function captureDesignColWidths(): void {
|
|
246
289
|
const widths = readGridColWidths(
|
|
247
290
|
getDesignGrid(),
|
|
248
|
-
Array.from({ length:
|
|
291
|
+
Array.from({ length: gridCols.value }, (_, col) => col)
|
|
249
292
|
)
|
|
250
|
-
if (widths)
|
|
293
|
+
if (!widths) return
|
|
294
|
+
// 仅保留相对默认列宽有变化的条目,避免快照膨胀
|
|
295
|
+
const custom = widths.filter(([, width]) => width > 0)
|
|
296
|
+
designColWidths.value = custom
|
|
297
|
+
applySheetColWidths(workbook.value.activeSheet, custom)
|
|
251
298
|
}
|
|
252
299
|
|
|
253
300
|
/** 设计网格重建后补刷新与列宽(restore 不发 content-reset 的场景由 grid 自行订阅,这里只管列宽) */
|
|
@@ -268,11 +315,18 @@ function setViewMode(mode: ViewMode): void {
|
|
|
268
315
|
if (mode === 'design') void nextTick(syncDesignGridView)
|
|
269
316
|
}
|
|
270
317
|
|
|
271
|
-
//
|
|
318
|
+
// 模板更换:组合式函数内完成载入;组件层切回设计态并恢复列宽
|
|
272
319
|
watch(
|
|
273
320
|
() => props.template,
|
|
274
|
-
() => {
|
|
321
|
+
(template) => {
|
|
275
322
|
viewMode.value = 'design'
|
|
323
|
+
designColWidths.value = template?.colWidths?.length
|
|
324
|
+
? template.colWidths.map(([col, width]) => [col, width] as [number, number])
|
|
325
|
+
: []
|
|
326
|
+
if (designColWidths.value.length) {
|
|
327
|
+
applySheetColWidths(workbook.value.activeSheet, designColWidths.value)
|
|
328
|
+
}
|
|
329
|
+
void nextTick(syncDesignGridView)
|
|
276
330
|
}
|
|
277
331
|
)
|
|
278
332
|
|
|
@@ -286,6 +340,11 @@ watch(catalog, () => {
|
|
|
286
340
|
void nextTick(() => getDesignGrid()?.refresh())
|
|
287
341
|
})
|
|
288
342
|
|
|
343
|
+
const offStructureChange = workbook.value.activeSheet.on('structure-change', () => {
|
|
344
|
+
void nextTick(syncDesignGridView)
|
|
345
|
+
})
|
|
346
|
+
onScopeDispose(offStructureChange)
|
|
347
|
+
|
|
289
348
|
async function exportPreviewXlsx(): Promise<void> {
|
|
290
349
|
if (!isPreview.value || exporting.value) return
|
|
291
350
|
const viewer = previewViewerRef.value
|
|
@@ -352,5 +411,10 @@ function onGridDrop(event: DragEvent): void {
|
|
|
352
411
|
}
|
|
353
412
|
}
|
|
354
413
|
|
|
355
|
-
defineExpose<_ReportDesignerExposed>({
|
|
414
|
+
defineExpose<_ReportDesignerExposed>({
|
|
415
|
+
getTemplate: () => {
|
|
416
|
+
captureDesignColWidths()
|
|
417
|
+
return getTemplate()
|
|
418
|
+
}
|
|
419
|
+
})
|
|
356
420
|
</script>
|
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
ref="sheetRef"
|
|
21
21
|
:class="cls.e('sheet')"
|
|
22
22
|
:workbook="workbook"
|
|
23
|
-
:rows="
|
|
24
|
-
:cols="
|
|
23
|
+
:rows="renderSize.rows"
|
|
24
|
+
:cols="renderSize.cols"
|
|
25
25
|
:show-toolbar="false"
|
|
26
26
|
:show-formula-bar="false"
|
|
27
27
|
:show-tabs="false"
|
|
28
|
+
:show-row-header="false"
|
|
29
|
+
:show-col-header="false"
|
|
28
30
|
readonly
|
|
29
31
|
/>
|
|
30
32
|
</div>
|
|
@@ -42,8 +44,9 @@ import { exportFilledReportXlsx } from '../../report/export-xlsx'
|
|
|
42
44
|
import type { ReportViewerProps, _ReportViewerExposed } from '../../types'
|
|
43
45
|
import type { SheetExposed } from '../../types'
|
|
44
46
|
import { USheet } from '../sheet'
|
|
45
|
-
import { applyGridColWidths,
|
|
47
|
+
import { applyGridColWidths, applySheetColWidths } from './designer/col-widths'
|
|
46
48
|
import UReportFilterBar from './filter-bar.vue'
|
|
49
|
+
import { previewGridSize, type PreviewGridSizeMode } from './preview-grid-size'
|
|
47
50
|
import { useReportViewer } from './use-report-viewer'
|
|
48
51
|
|
|
49
52
|
defineOptions({ name: 'UReportViewer' })
|
|
@@ -52,10 +55,6 @@ const props = defineProps<ReportViewerProps>()
|
|
|
52
55
|
|
|
53
56
|
const cls = bem('report-viewer')
|
|
54
57
|
|
|
55
|
-
/** 网格最小渲染尺寸(与 playground 预览态一致);Filled Report 超出时按快照尺寸放大 */
|
|
56
|
-
const MIN_RENDER_ROWS = 50
|
|
57
|
-
const MIN_RENDER_COLS = 10
|
|
58
|
-
|
|
59
58
|
/** 取数未完成时拒绝导出的可读错误 */
|
|
60
59
|
const EXPORT_NOT_READY_MESSAGE = '报表数据尚未就绪,请等待取数完成后再导出'
|
|
61
60
|
const EXPORT_LOADING_MESSAGE = '报表数据加载中,请稍后再导出'
|
|
@@ -66,44 +65,54 @@ const workbook = computed(() => props.workbook ?? internalWorkbook)
|
|
|
66
65
|
|
|
67
66
|
const sheetRef = useTemplateRef<SheetExposed>('sheetRef')
|
|
68
67
|
|
|
69
|
-
const { params, values, loading, error, filledSnapshot, refresh, setValues } =
|
|
68
|
+
const { params, values, loading, error, filledSnapshot, filledColWidths, refresh, setValues } =
|
|
70
69
|
useReportViewer(props)
|
|
71
70
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
71
|
+
const renderSize = computed(() => {
|
|
72
|
+
const filled = filledSnapshot.value
|
|
73
|
+
if (filled) return previewGridSize(filled, 'filled')
|
|
74
|
+
return previewGridSize(props.template, 'template')
|
|
75
|
+
})
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
/** 宿主显式传入优先;否则取数完成后用展开映射列宽;再回落模板设计态列宽 */
|
|
78
|
+
const effectiveColWidths = computed(() => {
|
|
79
|
+
if (props.colWidths?.length) return props.colWidths
|
|
80
|
+
if (filledColWidths.value?.length) return filledColWidths.value
|
|
81
|
+
return props.template.colWidths
|
|
82
|
+
})
|
|
82
83
|
|
|
83
|
-
/**
|
|
84
|
+
/** 将运行时列宽写入模型与 VTable(模型为单一事实源;grid 构造/content-reset 也会回放) */
|
|
84
85
|
function applyRuntimeColWidths(): void {
|
|
85
|
-
if (!
|
|
86
|
-
|
|
86
|
+
if (!effectiveColWidths.value?.length) return
|
|
87
|
+
const sheet = workbook.value.activeSheet
|
|
88
|
+
applySheetColWidths(sheet, effectiveColWidths.value)
|
|
89
|
+
applyGridColWidths(sheetRef.value?.getGrid(), effectiveColWidths.value)
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
/**
|
|
90
|
-
* restore
|
|
93
|
+
* restore 负责尺寸/冻结/行高/列宽/选区(静默),restoreContent 发 content-reset
|
|
91
94
|
* 触发网格全量刷新(grid 层订阅直刷,无需手动 flush);history.clear 使填充不进 undo
|
|
92
95
|
*/
|
|
93
|
-
function applySnapshot(snapshot: SheetSnapshot): void {
|
|
96
|
+
function applySnapshot(snapshot: SheetSnapshot, mode: PreviewGridSizeMode): void {
|
|
97
|
+
const size = previewGridSize(snapshot, mode)
|
|
98
|
+
const trimmed: SheetSnapshot = { ...snapshot, rows: size.rows, cols: size.cols }
|
|
94
99
|
const sheet = workbook.value.activeSheet
|
|
95
|
-
sheet.restore(
|
|
96
|
-
sheet.restoreContent(
|
|
100
|
+
sheet.restore(trimmed)
|
|
101
|
+
sheet.restoreContent(trimmed)
|
|
97
102
|
sheet.history.clear()
|
|
98
103
|
void nextTick(applyRuntimeColWidths)
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
// 先铺模板静态结构(取数期间 / 取数失败时可见),取数成功后替换为 Filled Report
|
|
102
|
-
watch(
|
|
107
|
+
watch(
|
|
108
|
+
() => props.template,
|
|
109
|
+
(template) => applySnapshot(template, 'template'),
|
|
110
|
+
{ immediate: true }
|
|
111
|
+
)
|
|
103
112
|
watch(filledSnapshot, (filled) => {
|
|
104
|
-
if (filled) applySnapshot(filled)
|
|
113
|
+
if (filled) applySnapshot(filled, 'filled')
|
|
105
114
|
})
|
|
106
|
-
watch(
|
|
115
|
+
watch(effectiveColWidths, applyRuntimeColWidths)
|
|
107
116
|
|
|
108
117
|
async function exportXlsx(): Promise<void> {
|
|
109
118
|
if (loading.value) {
|
|
@@ -114,8 +123,7 @@ async function exportXlsx(): Promise<void> {
|
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
const sheet = workbook.value.activeSheet
|
|
117
|
-
const
|
|
118
|
-
const buffer = await exportFilledReportXlsx(sheet, colWidths)
|
|
126
|
+
const buffer = await exportFilledReportXlsx(sheet)
|
|
119
127
|
saveBlob(
|
|
120
128
|
new Blob([buffer as unknown as BlobPart], {
|
|
121
129
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
@@ -62,6 +62,22 @@
|
|
|
62
62
|
min-width: 0;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
@include m.e(pick-hint) {
|
|
66
|
+
position: absolute;
|
|
67
|
+
left: 50%;
|
|
68
|
+
bottom: fn.use-var(gap, small);
|
|
69
|
+
z-index: 17;
|
|
70
|
+
transform: translateX(-50%);
|
|
71
|
+
padding: 4px fn.use-var(gap, small);
|
|
72
|
+
border-radius: fn.use-var(radius, default);
|
|
73
|
+
background: color-mix(in srgb, fn.use-var(bg-color, top) 92%, transparent);
|
|
74
|
+
border: 1px dashed fn.use-var(color, primary);
|
|
75
|
+
font-size: fn.use-var(font-size-main, small);
|
|
76
|
+
color: fn.use-var(text-color, second);
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
}
|
|
80
|
+
|
|
65
81
|
/* 数据中枢 drawer 面板宽度(class 经 $attrs 落到 .u-drawer 面板;须压过 .u-drawer.is-right 的 320px) */
|
|
66
82
|
@include m.e(hub-drawer) {
|
|
67
83
|
&.u-drawer {
|
|
@@ -123,7 +139,7 @@
|
|
|
123
139
|
}
|
|
124
140
|
}
|
|
125
141
|
|
|
126
|
-
/* Action Pill
|
|
142
|
+
/* Action Pill:选中绑定格上方的悬浮编辑卡(预设 + 互补的展开/聚合 / 条件样式 / 排序 / 清除绑定) */
|
|
127
143
|
@include m.b(report-float-panel) {
|
|
128
144
|
position: absolute;
|
|
129
145
|
z-index: 18;
|
|
@@ -223,7 +239,7 @@
|
|
|
223
239
|
}
|
|
224
240
|
|
|
225
241
|
@include m.e(parent-select) {
|
|
226
|
-
width:
|
|
242
|
+
width: 180px;
|
|
227
243
|
flex: none;
|
|
228
244
|
}
|
|
229
245
|
|
|
@@ -244,11 +260,12 @@
|
|
|
244
260
|
@include m.b(report-rules-dialog) {
|
|
245
261
|
display: flex;
|
|
246
262
|
flex-direction: column;
|
|
247
|
-
gap:
|
|
263
|
+
gap: 10px;
|
|
248
264
|
|
|
249
265
|
@include m.e(hint) {
|
|
250
266
|
margin: 0;
|
|
251
267
|
font-size: 12px;
|
|
268
|
+
line-height: 1.5;
|
|
252
269
|
color: fn.use-var(text-color, second);
|
|
253
270
|
}
|
|
254
271
|
|
|
@@ -278,17 +295,18 @@
|
|
|
278
295
|
display: flex;
|
|
279
296
|
align-items: flex-start;
|
|
280
297
|
gap: 8px;
|
|
281
|
-
padding: 10px;
|
|
298
|
+
padding: 10px 10px 10px 6px;
|
|
282
299
|
border: fn.use-var(border-muted);
|
|
283
300
|
border-radius: 8px;
|
|
284
|
-
background: fn.use-var(bg-color,
|
|
301
|
+
background: fn.use-var(bg-color, middle);
|
|
285
302
|
|
|
286
303
|
@include m.e(handle) {
|
|
287
304
|
flex: none;
|
|
288
|
-
width:
|
|
289
|
-
margin-top:
|
|
290
|
-
color: fn.use-var(text-color,
|
|
291
|
-
font-size:
|
|
305
|
+
width: 16px;
|
|
306
|
+
margin-top: 6px;
|
|
307
|
+
color: fn.use-var(text-color, placeholder);
|
|
308
|
+
font-size: 12px;
|
|
309
|
+
letter-spacing: -1px;
|
|
292
310
|
line-height: 1;
|
|
293
311
|
cursor: grab;
|
|
294
312
|
user-select: none;
|
|
@@ -307,32 +325,33 @@
|
|
|
307
325
|
gap: 8px;
|
|
308
326
|
}
|
|
309
327
|
|
|
310
|
-
@include m.e(
|
|
328
|
+
@include m.e(when) {
|
|
311
329
|
display: flex;
|
|
312
330
|
align-items: center;
|
|
313
|
-
gap:
|
|
331
|
+
gap: 6px;
|
|
314
332
|
flex-wrap: wrap;
|
|
315
333
|
}
|
|
316
334
|
|
|
317
|
-
@include m.e(
|
|
318
|
-
|
|
319
|
-
|
|
335
|
+
@include m.e(then) {
|
|
336
|
+
display: flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
gap: 6px;
|
|
339
|
+
flex-wrap: wrap;
|
|
320
340
|
}
|
|
321
341
|
|
|
322
|
-
@include m.e(
|
|
323
|
-
width: 72px;
|
|
342
|
+
@include m.e(lead) {
|
|
324
343
|
flex: none;
|
|
344
|
+
font-size: 12px;
|
|
345
|
+
color: fn.use-var(text-color, second);
|
|
325
346
|
}
|
|
326
347
|
|
|
327
|
-
@include m.e(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
align-items: center;
|
|
331
|
-
gap: 8px;
|
|
348
|
+
@include m.e(field-select) {
|
|
349
|
+
width: 128px;
|
|
350
|
+
flex: none;
|
|
332
351
|
}
|
|
333
352
|
|
|
334
353
|
@include m.e(operator) {
|
|
335
|
-
width:
|
|
354
|
+
width: 96px;
|
|
336
355
|
flex: none;
|
|
337
356
|
}
|
|
338
357
|
|
|
@@ -345,12 +364,12 @@
|
|
|
345
364
|
}
|
|
346
365
|
|
|
347
366
|
@include m.e(scalar-input) {
|
|
348
|
-
width:
|
|
367
|
+
width: 120px;
|
|
349
368
|
flex: none;
|
|
350
369
|
}
|
|
351
370
|
|
|
352
371
|
@include m.e(between-input) {
|
|
353
|
-
width:
|
|
372
|
+
width: 88px;
|
|
354
373
|
flex: none;
|
|
355
374
|
}
|
|
356
375
|
|
|
@@ -359,18 +378,6 @@
|
|
|
359
378
|
color: fn.use-var(text-color, second);
|
|
360
379
|
}
|
|
361
380
|
|
|
362
|
-
@include m.e(style) {
|
|
363
|
-
display: flex;
|
|
364
|
-
align-items: center;
|
|
365
|
-
gap: 8px;
|
|
366
|
-
flex-wrap: wrap;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
@include m.e(style-label) {
|
|
370
|
-
font-size: 12px;
|
|
371
|
-
color: fn.use-var(text-color, second);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
381
|
@include m.e(toggle) {
|
|
375
382
|
min-width: 28px;
|
|
376
383
|
padding-inline: 6px;
|
|
@@ -378,14 +385,11 @@
|
|
|
378
385
|
|
|
379
386
|
@include m.e(preview) {
|
|
380
387
|
flex: none;
|
|
381
|
-
margin-top: 2px;
|
|
382
388
|
}
|
|
383
389
|
|
|
384
|
-
@include m.e(
|
|
390
|
+
@include m.e(remove) {
|
|
385
391
|
flex: none;
|
|
386
|
-
|
|
387
|
-
flex-direction: column;
|
|
388
|
-
gap: 2px;
|
|
392
|
+
margin-top: 2px;
|
|
389
393
|
}
|
|
390
394
|
}
|
|
391
395
|
|
|
@@ -8,13 +8,16 @@ import {
|
|
|
8
8
|
applyReportPreset,
|
|
9
9
|
createReportBinding,
|
|
10
10
|
formatCellAddress,
|
|
11
|
-
inferColParentCandidate,
|
|
12
11
|
inferDropPreset,
|
|
13
|
-
|
|
12
|
+
inferParentsForPreset,
|
|
13
|
+
isColGroupBinding,
|
|
14
14
|
isHorizontalExpandBinding,
|
|
15
|
+
isRowGroupBinding,
|
|
15
16
|
isVerticalExpandBinding,
|
|
16
17
|
listColParentCandidates,
|
|
17
|
-
listRowParentCandidates
|
|
18
|
+
listRowParentCandidates,
|
|
19
|
+
prefersGroupParent,
|
|
20
|
+
presetStillApplies
|
|
18
21
|
} from '../../report/binding'
|
|
19
22
|
import type { DataConnection, QueryResult, Result } from '../../report/connector'
|
|
20
23
|
import { buildParamDefs } from '../../report/params'
|
|
@@ -227,10 +230,9 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
227
230
|
let binding = createReportBinding(dataset, fieldName)
|
|
228
231
|
binding = applyReportPreset(binding, preset)
|
|
229
232
|
|
|
230
|
-
const
|
|
231
|
-
if (rowParent) binding.rowParent = rowParent
|
|
232
|
-
|
|
233
|
-
if (colParent) binding.colParent = colParent
|
|
233
|
+
const parents = inferParentsForPreset(target, preset, getBindingAt)
|
|
234
|
+
if (parents.rowParent) binding.rowParent = parents.rowParent
|
|
235
|
+
if (parents.colParent) binding.colParent = parents.colParent
|
|
234
236
|
|
|
235
237
|
workbook.value.activeSheet.setCellMeta(target, REPORT_META_NAMESPACE, binding)
|
|
236
238
|
}
|
|
@@ -276,14 +278,18 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
276
278
|
metaTick.value
|
|
277
279
|
const binding = activeBinding.value
|
|
278
280
|
if (!binding) return []
|
|
279
|
-
return listRowParentCandidates(binding, bindingEntries.value
|
|
281
|
+
return listRowParentCandidates(binding, bindingEntries.value, {
|
|
282
|
+
preferGroup: prefersGroupParent(binding)
|
|
283
|
+
})
|
|
280
284
|
})
|
|
281
285
|
|
|
282
286
|
const colParentCandidates = computed((): CellAddress[] => {
|
|
283
287
|
metaTick.value
|
|
284
288
|
const binding = activeBinding.value
|
|
285
289
|
if (!binding) return []
|
|
286
|
-
return listColParentCandidates(binding, bindingEntries.value
|
|
290
|
+
return listColParentCandidates(binding, bindingEntries.value, {
|
|
291
|
+
preferGroup: prefersGroupParent(binding)
|
|
292
|
+
})
|
|
287
293
|
})
|
|
288
294
|
|
|
289
295
|
const bindingEntries = computed((): TopologyBindingEntry[] => {
|
|
@@ -296,6 +302,12 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
296
302
|
return entries
|
|
297
303
|
})
|
|
298
304
|
|
|
305
|
+
function parentNeedsCorrection(addr: CellAddress | undefined): boolean {
|
|
306
|
+
if (!addr) return true
|
|
307
|
+
const parent = getBindingAt(addr)
|
|
308
|
+
return !parent || parent.aggregate === 'list'
|
|
309
|
+
}
|
|
310
|
+
|
|
299
311
|
function patchActiveBinding(patch: Partial<ReportBinding>): void {
|
|
300
312
|
const cell = activeCell.value
|
|
301
313
|
const binding = activeBinding.value
|
|
@@ -304,7 +316,25 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
304
316
|
const next: ReportBinding = { ...binding, ...patch }
|
|
305
317
|
if ('rowParent' in patch && patch.rowParent === undefined) delete next.rowParent
|
|
306
318
|
if ('colParent' in patch && patch.colParent === undefined) delete next.colParent
|
|
307
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
('aggregate' in patch || 'expand' in patch) &&
|
|
321
|
+
!('preset' in patch) &&
|
|
322
|
+
!presetStillApplies(next)
|
|
323
|
+
) {
|
|
324
|
+
delete next.preset
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (patch.preset === 'subtotal') {
|
|
328
|
+
const inferred = inferParentsForPreset(cell, 'subtotal', getBindingAt)
|
|
329
|
+
if (parentNeedsCorrection(next.rowParent)) {
|
|
330
|
+
if (inferred.rowParent) next.rowParent = inferred.rowParent
|
|
331
|
+
else delete next.rowParent
|
|
332
|
+
}
|
|
333
|
+
if (parentNeedsCorrection(next.colParent)) {
|
|
334
|
+
if (inferred.colParent) next.colParent = inferred.colParent
|
|
335
|
+
else delete next.colParent
|
|
336
|
+
}
|
|
337
|
+
}
|
|
308
338
|
|
|
309
339
|
workbook.value.activeSheet.setCellMeta(cell, REPORT_META_NAMESPACE, next)
|
|
310
340
|
}
|
|
@@ -319,17 +349,36 @@ export function useReportDesigner(options: UseReportDesignerOptions): UseReportD
|
|
|
319
349
|
parentPick.value = null
|
|
320
350
|
}
|
|
321
351
|
|
|
352
|
+
function isValidParentTarget(
|
|
353
|
+
mode: ParentPickMode,
|
|
354
|
+
source: ReportBinding,
|
|
355
|
+
target: ReportBinding | undefined
|
|
356
|
+
): boolean {
|
|
357
|
+
if (!target) return false
|
|
358
|
+
if (target.dataset !== source.dataset) return false
|
|
359
|
+
const groupOnly = prefersGroupParent(source)
|
|
360
|
+
if (mode === 'row') {
|
|
361
|
+
return groupOnly ? isRowGroupBinding(target) : isVerticalExpandBinding(target)
|
|
362
|
+
}
|
|
363
|
+
return groupOnly ? isColGroupBinding(target) : isHorizontalExpandBinding(target)
|
|
364
|
+
}
|
|
365
|
+
|
|
322
366
|
function pickParentAt(target: CellAddress): void {
|
|
323
367
|
const pick = parentPick.value
|
|
324
368
|
if (!pick) return
|
|
325
369
|
if (target.row === pick.source.row && target.col === pick.source.col) return
|
|
326
370
|
|
|
327
371
|
const sourceBinding = getBindingAt(pick.source)
|
|
372
|
+
if (!sourceBinding) {
|
|
373
|
+
parentPick.value = null
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
328
377
|
const targetBinding = getBindingAt(target)
|
|
329
|
-
if (!sourceBinding
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
378
|
+
if (!isValidParentTarget(pick.mode, sourceBinding, targetBinding)) {
|
|
379
|
+
workbook.value.activeSheet.selectCell(pick.source)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
333
382
|
|
|
334
383
|
const patch = pick.mode === 'row' ? { rowParent: { ...target } } : { colParent: { ...target } }
|
|
335
384
|
const next: ReportBinding = { ...sourceBinding, ...patch }
|
|
@@ -2,7 +2,8 @@ import type { SheetSnapshot } from '@veltra/sheet-core'
|
|
|
2
2
|
import { computed, ref, shallowRef, watch, type ComputedRef, type Ref, type ShallowRef } from 'vue'
|
|
3
3
|
|
|
4
4
|
import type { ConnectorError } from '../../report/connector'
|
|
5
|
-
import {
|
|
5
|
+
import type { ReportColWidthEntry } from '../../report/export-xlsx'
|
|
6
|
+
import { renderReport, resolveFilledColWidths } from '../../report/render'
|
|
6
7
|
import {
|
|
7
8
|
fetchTemplateRecords,
|
|
8
9
|
resolveParamDefaults,
|
|
@@ -22,6 +23,8 @@ export interface UseReportViewerReturn {
|
|
|
22
23
|
error: ShallowRef<ConnectorError | null>
|
|
23
24
|
/** 最近一次成功展开渲染的 Filled Report 快照(组件壳据此替换网格内容) */
|
|
24
25
|
filledSnapshot: ShallowRef<SheetSnapshot | null>
|
|
26
|
+
/** 展开后物理列宽(由模板 colWidths 映射;无模板列宽时为 undefined) */
|
|
27
|
+
filledColWidths: ShallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>
|
|
25
28
|
/** 重新取数并展开渲染 */
|
|
26
29
|
refresh: () => Promise<void>
|
|
27
30
|
/** Filter Bar 改值:写入新参数值并重新取数 */
|
|
@@ -36,6 +39,7 @@ export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn
|
|
|
36
39
|
const loading = ref(false)
|
|
37
40
|
const error = shallowRef<ConnectorError | null>(null)
|
|
38
41
|
const filledSnapshot = shallowRef<SheetSnapshot | null>(null)
|
|
42
|
+
const filledColWidths = shallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>(undefined)
|
|
39
43
|
|
|
40
44
|
const params = computed(() => resolveTemplateParams(props.template))
|
|
41
45
|
// 初始值由下方 immediate watch 播种(参数默认值)
|
|
@@ -54,9 +58,13 @@ export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn
|
|
|
54
58
|
loading.value = false
|
|
55
59
|
if (!result.ok) {
|
|
56
60
|
error.value = result.error
|
|
61
|
+
filledColWidths.value = undefined
|
|
57
62
|
return
|
|
58
63
|
}
|
|
59
64
|
filledSnapshot.value = renderReport(template, result.data)
|
|
65
|
+
filledColWidths.value = template.colWidths?.length
|
|
66
|
+
? resolveFilledColWidths(template, result.data, template.colWidths)
|
|
67
|
+
: undefined
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
function setValues(next: ParamValues): void {
|
|
@@ -69,11 +77,12 @@ export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn
|
|
|
69
77
|
() => props.template,
|
|
70
78
|
() => {
|
|
71
79
|
filledSnapshot.value = null
|
|
80
|
+
filledColWidths.value = undefined
|
|
72
81
|
values.value = resolveParamDefaults(params.value)
|
|
73
82
|
void refresh()
|
|
74
83
|
},
|
|
75
84
|
{ immediate: true }
|
|
76
85
|
)
|
|
77
86
|
|
|
78
|
-
return { params, values, loading, error, filledSnapshot, refresh, setValues }
|
|
87
|
+
return { params, values, loading, error, filledSnapshot, filledColWidths, refresh, setValues }
|
|
79
88
|
}
|