@veltra/sheet 2.2.0 → 2.3.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.
- 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/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 +2 -2
- 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 +6 -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/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 +2 -2
- 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,27 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="cls.b">
|
|
3
|
-
<span :class="cls.e('handle')" title="拖拽排序"
|
|
3
|
+
<span :class="cls.e('handle')" title="拖拽排序">⋮⋮</span>
|
|
4
4
|
|
|
5
5
|
<div :class="cls.e('main')">
|
|
6
|
-
<div :class="cls.e('
|
|
6
|
+
<div :class="cls.e('when')">
|
|
7
|
+
<span :class="cls.e('lead')">当</span>
|
|
7
8
|
<u-select
|
|
8
9
|
size="small"
|
|
9
10
|
:class="cls.e('field-select')"
|
|
10
|
-
:model-value="rule.field"
|
|
11
|
+
:model-value="rule.field ?? SELF_FIELD_VALUE"
|
|
11
12
|
:options="fieldOptions"
|
|
12
|
-
placeholder="求值字段"
|
|
13
13
|
@update:model-value="onEvalField"
|
|
14
14
|
/>
|
|
15
|
-
<u-select
|
|
16
|
-
size="small"
|
|
17
|
-
:class="cls.e('scope-select')"
|
|
18
|
-
:model-value="rule.scope ?? 'cell'"
|
|
19
|
-
:options="scopeOptions"
|
|
20
|
-
@update:model-value="onScope"
|
|
21
|
-
/>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<div :class="cls.e('condition')">
|
|
25
15
|
<u-select
|
|
26
16
|
size="small"
|
|
27
17
|
:class="cls.e('operator')"
|
|
@@ -29,7 +19,6 @@
|
|
|
29
19
|
:options="operatorOptions"
|
|
30
20
|
@update:model-value="onOperator"
|
|
31
21
|
/>
|
|
32
|
-
|
|
33
22
|
<div :class="cls.e('value')">
|
|
34
23
|
<template v-if="rule.operator === 'between'">
|
|
35
24
|
<u-number-input
|
|
@@ -80,56 +69,76 @@
|
|
|
80
69
|
@update:model-value="(v) => patchValue(v ?? 0)"
|
|
81
70
|
/>
|
|
82
71
|
</div>
|
|
72
|
+
<span :class="cls.e('lead')">时</span>
|
|
73
|
+
</div>
|
|
83
74
|
|
|
75
|
+
<div :class="cls.e('then')">
|
|
76
|
+
<span :class="cls.e('lead')">样式</span>
|
|
77
|
+
<span title="背景">
|
|
78
|
+
<u-palette
|
|
79
|
+
size="small"
|
|
80
|
+
:model-value="rule.style.fill?.color ?? ''"
|
|
81
|
+
@update:model-value="patchFillColor"
|
|
82
|
+
/>
|
|
83
|
+
</span>
|
|
84
|
+
<span title="文字">
|
|
85
|
+
<u-palette
|
|
86
|
+
size="small"
|
|
87
|
+
:model-value="rule.style.font?.color ?? ''"
|
|
88
|
+
@update:model-value="patchFontColor"
|
|
89
|
+
/>
|
|
90
|
+
</span>
|
|
91
|
+
<u-button-group>
|
|
92
|
+
<u-button
|
|
93
|
+
size="small"
|
|
94
|
+
:class="cls.e('toggle')"
|
|
95
|
+
:type="rule.style.font?.bold ? 'primary' : undefined"
|
|
96
|
+
plain
|
|
97
|
+
title="加粗"
|
|
98
|
+
@click="toggleBold"
|
|
99
|
+
>
|
|
100
|
+
B
|
|
101
|
+
</u-button>
|
|
102
|
+
<u-button
|
|
103
|
+
size="small"
|
|
104
|
+
:class="cls.e('toggle')"
|
|
105
|
+
:type="rule.style.font?.italic ? 'primary' : undefined"
|
|
106
|
+
plain
|
|
107
|
+
title="斜体"
|
|
108
|
+
@click="toggleItalic"
|
|
109
|
+
>
|
|
110
|
+
I
|
|
111
|
+
</u-button>
|
|
112
|
+
</u-button-group>
|
|
84
113
|
<u-report-rule-preview :class="cls.e('preview')" :rule="rule" />
|
|
85
|
-
</div>
|
|
86
114
|
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
:type="rule.style.font?.bold ? 'primary' : undefined"
|
|
104
|
-
plain
|
|
105
|
-
@click="toggleBold"
|
|
106
|
-
>
|
|
107
|
-
B
|
|
108
|
-
</u-button>
|
|
109
|
-
<u-button
|
|
110
|
-
size="small"
|
|
111
|
-
:class="cls.e('toggle')"
|
|
112
|
-
:type="rule.style.font?.italic ? 'primary' : undefined"
|
|
113
|
-
plain
|
|
114
|
-
@click="toggleItalic"
|
|
115
|
-
>
|
|
116
|
-
I
|
|
117
|
-
</u-button>
|
|
115
|
+
<span :class="cls.e('lead')">应用到</span>
|
|
116
|
+
<u-button-group>
|
|
117
|
+
<u-button
|
|
118
|
+
v-for="option in RULE_SCOPE_OPTIONS"
|
|
119
|
+
:key="option.value"
|
|
120
|
+
size="small"
|
|
121
|
+
plain
|
|
122
|
+
:type="(rule.scope ?? 'cell') === option.value ? 'primary' : undefined"
|
|
123
|
+
:title="
|
|
124
|
+
option.value === 'row' ? '染满物理输出行(交叉表会盖住同行所有列)' : '只改本格'
|
|
125
|
+
"
|
|
126
|
+
@click="onScope(option.value)"
|
|
127
|
+
>
|
|
128
|
+
{{ option.label }}
|
|
129
|
+
</u-button>
|
|
130
|
+
</u-button-group>
|
|
118
131
|
</div>
|
|
119
132
|
</div>
|
|
120
133
|
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
↓
|
|
125
|
-
</u-button>
|
|
126
|
-
<u-button size="small" text type="danger" @click="emit('remove')">删除</u-button>
|
|
127
|
-
</div>
|
|
134
|
+
<u-button size="small" text type="danger" :class="cls.e('remove')" @click="emit('remove')">
|
|
135
|
+
删除
|
|
136
|
+
</u-button>
|
|
128
137
|
</div>
|
|
129
138
|
</template>
|
|
130
139
|
|
|
131
140
|
<script lang="ts" setup>
|
|
132
|
-
import { UButton, UInput, UNumberInput, UPalette, USelect } from '@veltra/desktop'
|
|
141
|
+
import { UButton, UButtonGroup, UInput, UNumberInput, UPalette, USelect } from '@veltra/desktop'
|
|
133
142
|
import { bem } from '@veltra/utils'
|
|
134
143
|
import { computed } from 'vue'
|
|
135
144
|
|
|
@@ -142,6 +151,7 @@ import {
|
|
|
142
151
|
resolveEvalFieldType,
|
|
143
152
|
RULE_SCOPE_OPTIONS,
|
|
144
153
|
ruleEvalFieldOptions,
|
|
154
|
+
SELF_FIELD_VALUE,
|
|
145
155
|
writeBetweenValue
|
|
146
156
|
} from './conditional-rules/helpers'
|
|
147
157
|
import UReportRulePreview from './rule-preview.vue'
|
|
@@ -152,16 +162,9 @@ const props = defineProps<{
|
|
|
152
162
|
rule: ConditionalRule
|
|
153
163
|
bindingField: string
|
|
154
164
|
datasetFields: readonly DatasetField[]
|
|
155
|
-
index: number
|
|
156
|
-
total: number
|
|
157
165
|
}>()
|
|
158
166
|
|
|
159
|
-
const emit = defineEmits<{
|
|
160
|
-
'update:rule': [rule: ConditionalRule]
|
|
161
|
-
remove: []
|
|
162
|
-
'move-up': []
|
|
163
|
-
'move-down': []
|
|
164
|
-
}>()
|
|
167
|
+
const emit = defineEmits<{ 'update:rule': [rule: ConditionalRule]; remove: [] }>()
|
|
165
168
|
|
|
166
169
|
const cls = bem('report-rule-row')
|
|
167
170
|
|
|
@@ -171,9 +174,6 @@ const evalFieldType = computed(() =>
|
|
|
171
174
|
const operatorOptions = computed(() => operatorsForFieldType(evalFieldType.value))
|
|
172
175
|
const numericField = computed(() => isNumericFieldType(evalFieldType.value))
|
|
173
176
|
const fieldOptions = computed(() => ruleEvalFieldOptions(props.bindingField, props.datasetFields))
|
|
174
|
-
const scopeOptions = computed(() =>
|
|
175
|
-
RULE_SCOPE_OPTIONS.map((option) => ({ value: option.value, label: option.label }))
|
|
176
|
-
)
|
|
177
177
|
|
|
178
178
|
const betweenStart = computed(() => readBetweenValue(props.rule.value, 0, evalFieldType.value))
|
|
179
179
|
const betweenEnd = computed(() => readBetweenValue(props.rule.value, 1, evalFieldType.value))
|
|
@@ -191,7 +191,7 @@ function patchRule(patch: Partial<ConditionalRule>): void {
|
|
|
191
191
|
|
|
192
192
|
function onEvalField(field: string | undefined): void {
|
|
193
193
|
const next: Partial<ConditionalRule> = {}
|
|
194
|
-
if (field
|
|
194
|
+
if (!field) {
|
|
195
195
|
next.field = undefined
|
|
196
196
|
} else {
|
|
197
197
|
next.field = field
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
<u-dialog
|
|
3
3
|
v-model="visible"
|
|
4
4
|
title="条件样式"
|
|
5
|
-
style="width: min(
|
|
5
|
+
style="width: min(600px, 92vw); max-height: 85vh"
|
|
6
6
|
@close="onClose"
|
|
7
7
|
>
|
|
8
8
|
<div :class="cls.b">
|
|
9
|
-
<p :class="cls.e('hint')">
|
|
10
|
-
按列表顺序依次求值并合并样式。可指定求值字段(默认同绑定格字段);作用范围为「整行」时染满物理输出行(含横向展开列与静态格)。交叉表下整行高亮会覆盖同行所有列,明细行报表更合适。
|
|
11
|
-
</p>
|
|
9
|
+
<p :class="cls.e('hint')">规则按顺序叠加。整行高亮适合明细表,交叉表会染满整行。</p>
|
|
12
10
|
|
|
13
11
|
<ul v-if="draftItems.length" ref="parentRef" :class="cls.e('list')">
|
|
14
12
|
<li v-for="(item, index) in draftItems" :key="item.id" :class="cls.e('item')">
|
|
@@ -16,16 +14,12 @@
|
|
|
16
14
|
:rule="item.rule"
|
|
17
15
|
:binding-field="bindingField"
|
|
18
16
|
:dataset-fields="datasetFields"
|
|
19
|
-
:index="index"
|
|
20
|
-
:total="draftItems.length"
|
|
21
17
|
@update:rule="item.rule = $event"
|
|
22
18
|
@remove="removeRule(index)"
|
|
23
|
-
@move-up="moveRule(index, -1)"
|
|
24
|
-
@move-down="moveRule(index, 1)"
|
|
25
19
|
/>
|
|
26
20
|
</li>
|
|
27
21
|
</ul>
|
|
28
|
-
<p v-else :class="cls.e('empty')"
|
|
22
|
+
<p v-else :class="cls.e('empty')">还没有规则。</p>
|
|
29
23
|
|
|
30
24
|
<u-button size="small" plain @click="addRule">添加规则</u-button>
|
|
31
25
|
</div>
|
|
@@ -105,16 +99,6 @@ function removeRule(index: number): void {
|
|
|
105
99
|
draftItems.value = draftItems.value.filter((_, i) => i !== index)
|
|
106
100
|
}
|
|
107
101
|
|
|
108
|
-
function moveRule(index: number, delta: -1 | 1): void {
|
|
109
|
-
const target = index + delta
|
|
110
|
-
if (target < 0 || target >= draftItems.value.length) return
|
|
111
|
-
const next = [...draftItems.value]
|
|
112
|
-
const [item] = next.splice(index, 1)
|
|
113
|
-
if (!item) return
|
|
114
|
-
next.splice(target, 0, item)
|
|
115
|
-
draftItems.value = next
|
|
116
|
-
}
|
|
117
|
-
|
|
118
102
|
function confirm(): void {
|
|
119
103
|
emit('save', cloneRulesFromDraft(draftItems.value))
|
|
120
104
|
visible.value = false
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SheetSnapshot } from '@veltra/sheet-core'
|
|
2
|
+
|
|
3
|
+
export type PreviewGridSizeMode = 'template' | 'filled'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 报表预览网格尺寸:只铺到实际内容,不用设计态 canvas / 50×10 下限。
|
|
7
|
+
*
|
|
8
|
+
* - `template`:取数前的静态结构,按 cells/merges/meta 包围盒(忽略 snapshot.rows/cols,
|
|
9
|
+
* 那是设计网格声明尺寸,会带出空白格)
|
|
10
|
+
* - `filled`:行保留展开布局的 `snapshot.rows`(可能含无存值的空扩展行);列按包围盒收敛
|
|
11
|
+
* (Filled Report 的 `cols` 常被 `max(layout, template.cols)` 撑到设计态列数)
|
|
12
|
+
*/
|
|
13
|
+
export function previewGridSize(
|
|
14
|
+
snapshot: Pick<SheetSnapshot, 'cells' | 'merges' | 'meta' | 'rows'>,
|
|
15
|
+
mode: PreviewGridSizeMode
|
|
16
|
+
): { rows: number; cols: number } {
|
|
17
|
+
let maxRow = -1
|
|
18
|
+
let maxCol = -1
|
|
19
|
+
const grow = (row: number, col: number): void => {
|
|
20
|
+
if (row > maxRow) maxRow = row
|
|
21
|
+
if (col > maxCol) maxCol = col
|
|
22
|
+
}
|
|
23
|
+
for (const cell of snapshot.cells) grow(cell.row, cell.col)
|
|
24
|
+
for (const merge of snapshot.merges) grow(merge.end.row, merge.end.col)
|
|
25
|
+
for (const item of snapshot.meta ?? []) grow(item.row, item.col)
|
|
26
|
+
|
|
27
|
+
const boundRows = maxRow + 1
|
|
28
|
+
const boundCols = maxCol + 1
|
|
29
|
+
if (mode === 'filled') {
|
|
30
|
+
return { rows: Math.max(boundRows, snapshot.rows, 1), cols: Math.max(boundCols, 1) }
|
|
31
|
+
}
|
|
32
|
+
return { rows: Math.max(boundRows, 1), cols: Math.max(boundCols, 1) }
|
|
33
|
+
}
|
|
@@ -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'
|