@veltra/sheet 2.3.4 → 2.5.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/index.d.ts +2 -17
- package/dist/index.js +2 -13
- package/dist/types/index.d.ts +1 -3
- package/package.json +5 -6
- package/src/index.ts +1 -27
- package/src/types/index.ts +0 -2
- package/dist/components/report/binding-badge.js +0 -112
- package/dist/components/report/designer/cell-coords.js +0 -46
- package/dist/components/report/designer/col-widths.js +0 -29
- package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
- package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
- package/dist/components/report/designer/float-panel.js +0 -422
- package/dist/components/report/designer/role.js +0 -17
- package/dist/components/report/designer/rule-preview.js +0 -33
- package/dist/components/report/designer/rule-row.js +0 -253
- package/dist/components/report/designer/rules-dialog.js +0 -118
- package/dist/components/report/designer/topology-overlay.js +0 -122
- package/dist/components/report/designer/topology.js +0 -64
- package/dist/components/report/designer/use-grid-overlay.js +0 -75
- package/dist/components/report/designer-hub.js +0 -177
- package/dist/components/report/field-panel-helpers.js +0 -33
- package/dist/components/report/field-panel.js +0 -132
- package/dist/components/report/filter-bar.js +0 -74
- package/dist/components/report/hub-connection-form.js +0 -145
- package/dist/components/report/hub-dataset-editor.js +0 -409
- package/dist/components/report/preview-grid-size.js +0 -32
- package/dist/components/report/report-designer.js +0 -393
- package/dist/components/report/report-viewer.js +0 -124
- package/dist/components/report/style.d.ts +0 -17
- package/dist/components/report/style.js +0 -18
- package/dist/components/report/style2.css +0 -874
- package/dist/components/report/use-report-designer.js +0 -386
- package/dist/components/report/use-report-viewer.js +0 -55
- package/dist/report/binding.d.ts +0 -83
- package/dist/report/binding.js +0 -334
- package/dist/report/connector.d.ts +0 -66
- package/dist/report/connector.js +0 -159
- package/dist/report/export-xlsx.d.ts +0 -13
- package/dist/report/export-xlsx.js +0 -13
- package/dist/report/filter-bar.d.ts +0 -10
- package/dist/report/filter-bar.js +0 -31
- package/dist/report/index.d.ts +0 -12
- package/dist/report/params.d.ts +0 -13
- package/dist/report/params.js +0 -61
- package/dist/report/render/aggregate.js +0 -169
- package/dist/report/render/builder.d.ts +0 -12
- package/dist/report/render/builder.js +0 -139
- package/dist/report/render/coordinate.d.ts +0 -25
- package/dist/report/render/coordinate.js +0 -733
- package/dist/report/render/index.d.ts +0 -14
- package/dist/report/render/index.js +0 -21
- package/dist/report/render/style-resolver.js +0 -50
- package/dist/report/render/template-index.d.ts +0 -24
- package/dist/report/render/template-index.js +0 -137
- package/dist/report/rules/evaluate.d.ts +0 -25
- package/dist/report/rules/evaluate.js +0 -82
- package/dist/report/rules/index.d.ts +0 -2
- package/dist/report/template.d.ts +0 -63
- package/dist/report/template.js +0 -83
- package/dist/report/types.d.ts +0 -85
- package/dist/types/report-designer.d.ts +0 -38
- package/dist/types/report-viewer.d.ts +0 -38
- package/src/components/report/__test__/binding-badge.test.ts +0 -92
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
- package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
- package/src/components/report/__test__/float-panel-position.test.ts +0 -45
- package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
- package/src/components/report/__test__/report-designer.test.ts +0 -464
- package/src/components/report/__test__/report-viewer.test.ts +0 -357
- package/src/components/report/__test__/topology.test.ts +0 -143
- package/src/components/report/__test__/use-report-designer.test.ts +0 -697
- package/src/components/report/binding-badge.ts +0 -117
- package/src/components/report/designer/cell-coords.ts +0 -95
- package/src/components/report/designer/col-widths.ts +0 -56
- package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
- package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
- package/src/components/report/designer/float-panel.vue +0 -432
- package/src/components/report/designer/role.ts +0 -32
- package/src/components/report/designer/rule-preview.vue +0 -40
- package/src/components/report/designer/rule-row.vue +0 -252
- package/src/components/report/designer/rules-dialog.vue +0 -110
- package/src/components/report/designer/topology-overlay.vue +0 -128
- package/src/components/report/designer/topology.ts +0 -124
- package/src/components/report/designer/use-grid-overlay.ts +0 -114
- package/src/components/report/designer-hub.vue +0 -198
- package/src/components/report/field-panel-helpers.ts +0 -44
- package/src/components/report/field-panel.vue +0 -136
- package/src/components/report/filter-bar.vue +0 -77
- package/src/components/report/hub-connection-form.vue +0 -124
- package/src/components/report/hub-dataset-editor.vue +0 -387
- package/src/components/report/index.ts +0 -2
- package/src/components/report/preview-grid-size.ts +0 -33
- package/src/components/report/report-designer.vue +0 -421
- package/src/components/report/report-viewer.vue +0 -136
- package/src/components/report/style-designer.scss +0 -587
- package/src/components/report/style-hub.scss +0 -292
- package/src/components/report/style-viewer.scss +0 -104
- package/src/components/report/style.scss +0 -3
- package/src/components/report/style.ts +0 -20
- package/src/components/report/use-report-designer.ts +0 -602
- package/src/components/report/use-report-viewer.ts +0 -88
- package/src/report/__test__/binding.test.ts +0 -320
- package/src/report/__test__/connector.test.ts +0 -239
- package/src/report/__test__/export-xlsx.test.ts +0 -96
- package/src/report/__test__/filter-bar.test.ts +0 -58
- package/src/report/__test__/params.test.ts +0 -156
- package/src/report/__test__/render.test.ts +0 -856
- package/src/report/__test__/rules.test.ts +0 -131
- package/src/report/__test__/template.test.ts +0 -366
- package/src/report/binding.ts +0 -474
- package/src/report/connector.ts +0 -206
- package/src/report/export-xlsx.ts +0 -15
- package/src/report/filter-bar.ts +0 -36
- package/src/report/index.ts +0 -13
- package/src/report/params.ts +0 -96
- package/src/report/render/__test__/aggregate.test.ts +0 -16
- package/src/report/render/__test__/coordinate.test.ts +0 -405
- package/src/report/render/aggregate.ts +0 -261
- package/src/report/render/builder.ts +0 -211
- package/src/report/render/coordinate.ts +0 -1239
- package/src/report/render/index.ts +0 -30
- package/src/report/render/style-resolver.ts +0 -68
- package/src/report/render/template-index.ts +0 -200
- package/src/report/rules/evaluate.ts +0 -113
- package/src/report/rules/index.ts +0 -7
- package/src/report/template.ts +0 -145
- package/src/report/types.ts +0 -96
- package/src/types/report-designer.ts +0 -38
- package/src/types/report-viewer.ts +0 -37
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import type { CellAddress } from '@veltra/sheet-core'
|
|
2
|
-
|
|
3
|
-
import type { DatasetRecords, ReportAggregate, ReportBinding } from '../types'
|
|
4
|
-
import { enumerateExpansionInstances } from './coordinate'
|
|
5
|
-
import type { PhysicalPlacement } from './coordinate'
|
|
6
|
-
import type { TemplateIndex } from './template-index'
|
|
7
|
-
|
|
8
|
-
function filterRows(
|
|
9
|
-
rows: Record<string, unknown>[],
|
|
10
|
-
filter: Record<string, unknown>
|
|
11
|
-
): Record<string, unknown>[] {
|
|
12
|
-
if (Object.keys(filter).length === 0) return rows
|
|
13
|
-
return rows.filter((row) =>
|
|
14
|
-
Object.entries(filter).every(([field, value]) => row[field] === value)
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** 对数值集求聚合;`avg` 空集返回 `undefined`,`sum` 空集仍为 `0` */
|
|
19
|
-
export function aggregateField(
|
|
20
|
-
rows: Record<string, unknown>[],
|
|
21
|
-
field: string,
|
|
22
|
-
aggregate: ReportAggregate
|
|
23
|
-
): unknown {
|
|
24
|
-
if (aggregate === 'count') return rows.length
|
|
25
|
-
if (aggregate === 'list' || aggregate === 'group') {
|
|
26
|
-
return rows[0]?.[field]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const numbers = rows
|
|
30
|
-
.map((row) => row[field])
|
|
31
|
-
.map((value) => (typeof value === 'number' ? value : Number(value)))
|
|
32
|
-
.filter((value) => Number.isFinite(value))
|
|
33
|
-
|
|
34
|
-
if (numbers.length === 0) {
|
|
35
|
-
if (aggregate === 'avg') return undefined
|
|
36
|
-
return 0
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const sum = numbers.reduce((acc, value) => acc + value, 0)
|
|
40
|
-
if (aggregate === 'avg') return sum / numbers.length
|
|
41
|
-
if (aggregate === 'max') return Math.max(...numbers)
|
|
42
|
-
if (aggregate === 'min') return Math.min(...numbers)
|
|
43
|
-
return sum
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function isRowExpanding(binding: ReportBinding): boolean {
|
|
47
|
-
return (
|
|
48
|
-
binding.expand === 'down' && (binding.aggregate === 'group' || binding.aggregate === 'list')
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function isColExpanding(binding: ReportBinding): boolean {
|
|
53
|
-
return (
|
|
54
|
-
binding.expand === 'right' && (binding.aggregate === 'group' || binding.aggregate === 'list')
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function isReductionAggregate(aggregate: ReportAggregate): boolean {
|
|
59
|
-
return (
|
|
60
|
-
aggregate === 'sum' ||
|
|
61
|
-
aggregate === 'avg' ||
|
|
62
|
-
aggregate === 'count' ||
|
|
63
|
-
aggregate === 'max' ||
|
|
64
|
-
aggregate === 'min'
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function rowExpandingAncestors(
|
|
69
|
-
binding: ReportBinding,
|
|
70
|
-
index: TemplateIndex,
|
|
71
|
-
groupsOnly: boolean
|
|
72
|
-
): Array<{ addr: CellAddress; binding: ReportBinding }> {
|
|
73
|
-
const chain: Array<{ addr: CellAddress; binding: ReportBinding }> = []
|
|
74
|
-
let current = binding.rowParent
|
|
75
|
-
while (current) {
|
|
76
|
-
const parentBinding = index.bindingAt(current)
|
|
77
|
-
if (parentBinding) chain.unshift({ addr: current, binding: parentBinding })
|
|
78
|
-
current = parentBinding?.rowParent
|
|
79
|
-
}
|
|
80
|
-
return chain.filter((item) => {
|
|
81
|
-
if (!isRowExpanding(item.binding)) return false
|
|
82
|
-
if (groupsOnly && item.binding.aggregate !== 'group') return false
|
|
83
|
-
return true
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function colExpandingAncestors(
|
|
88
|
-
binding: ReportBinding,
|
|
89
|
-
index: TemplateIndex,
|
|
90
|
-
groupsOnly: boolean
|
|
91
|
-
): Array<{ addr: CellAddress; binding: ReportBinding }> {
|
|
92
|
-
const chain: Array<{ addr: CellAddress; binding: ReportBinding }> = []
|
|
93
|
-
let current = binding.colParent
|
|
94
|
-
while (current) {
|
|
95
|
-
const parentBinding = index.bindingAt(current)
|
|
96
|
-
if (parentBinding) chain.unshift({ addr: current, binding: parentBinding })
|
|
97
|
-
current = parentBinding?.colParent
|
|
98
|
-
}
|
|
99
|
-
return chain.filter((item) => {
|
|
100
|
-
if (!isColExpanding(item.binding)) return false
|
|
101
|
-
if (groupsOnly && item.binding.aggregate !== 'group') return false
|
|
102
|
-
return true
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function mergeFilters(
|
|
107
|
-
rowFilter: Record<string, unknown>,
|
|
108
|
-
colFilter: Record<string, unknown>
|
|
109
|
-
): Record<string, unknown> {
|
|
110
|
-
return { ...rowFilter, ...colFilter }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function resolveAncestorFilters(
|
|
114
|
-
binding: ReportBinding,
|
|
115
|
-
placement: PhysicalPlacement,
|
|
116
|
-
index: TemplateIndex,
|
|
117
|
-
data: DatasetRecords
|
|
118
|
-
): Record<string, unknown> {
|
|
119
|
-
const rowFilter: Record<string, unknown> = {}
|
|
120
|
-
const colFilter: Record<string, unknown> = {}
|
|
121
|
-
let parentFilter: Record<string, unknown> = {}
|
|
122
|
-
const groupsOnly = isReductionAggregate(binding.aggregate)
|
|
123
|
-
|
|
124
|
-
const rowAncestors = rowExpandingAncestors(binding, index, groupsOnly)
|
|
125
|
-
for (let i = 0; i < rowAncestors.length; i++) {
|
|
126
|
-
const ancestor = rowAncestors[i]!
|
|
127
|
-
if (ancestor.binding.dataset !== binding.dataset) continue
|
|
128
|
-
const instances = enumerateExpansionInstances(ancestor.binding, data, parentFilter)
|
|
129
|
-
const pathIndex = placement.rowPath[i]
|
|
130
|
-
if (pathIndex === undefined) continue
|
|
131
|
-
const instance = instances[pathIndex]
|
|
132
|
-
if (!instance) continue
|
|
133
|
-
Object.assign(rowFilter, instance.filter)
|
|
134
|
-
parentFilter = instance.filter
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
parentFilter = {}
|
|
138
|
-
const colAncestors = colExpandingAncestors(binding, index, groupsOnly)
|
|
139
|
-
for (let i = 0; i < colAncestors.length; i++) {
|
|
140
|
-
const ancestor = colAncestors[i]!
|
|
141
|
-
if (ancestor.binding.dataset !== binding.dataset) continue
|
|
142
|
-
const instances = enumerateExpansionInstances(ancestor.binding, data, parentFilter)
|
|
143
|
-
const pathIndex = placement.colPath[i]
|
|
144
|
-
if (pathIndex === undefined) continue
|
|
145
|
-
const instance = instances[pathIndex]
|
|
146
|
-
if (!instance) continue
|
|
147
|
-
Object.assign(colFilter, instance.filter)
|
|
148
|
-
parentFilter = instance.filter
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return mergeFilters(rowFilter, colFilter)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function resolveOwnGroupValue(
|
|
155
|
-
binding: ReportBinding,
|
|
156
|
-
placement: PhysicalPlacement,
|
|
157
|
-
index: TemplateIndex,
|
|
158
|
-
data: DatasetRecords
|
|
159
|
-
): unknown {
|
|
160
|
-
const rowAncestors = rowExpandingAncestors(binding, index, false)
|
|
161
|
-
const colAncestors = colExpandingAncestors(binding, index, false)
|
|
162
|
-
let parentFilter: Record<string, unknown> = {}
|
|
163
|
-
|
|
164
|
-
for (let i = 0; i < rowAncestors.length; i++) {
|
|
165
|
-
const ancestor = rowAncestors[i]!
|
|
166
|
-
if (ancestor.binding.dataset !== binding.dataset) continue
|
|
167
|
-
const instances = enumerateExpansionInstances(ancestor.binding, data, parentFilter)
|
|
168
|
-
const pathIndex = placement.rowPath[i]
|
|
169
|
-
if (pathIndex === undefined) continue
|
|
170
|
-
const instance = instances[pathIndex]
|
|
171
|
-
if (instance) parentFilter = instance.filter
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
for (let i = 0; i < colAncestors.length; i++) {
|
|
175
|
-
const ancestor = colAncestors[i]!
|
|
176
|
-
if (ancestor.binding.dataset !== binding.dataset) continue
|
|
177
|
-
const instances = enumerateExpansionInstances(ancestor.binding, data, parentFilter)
|
|
178
|
-
const pathIndex = placement.colPath[i]
|
|
179
|
-
if (pathIndex === undefined) continue
|
|
180
|
-
const instance = instances[pathIndex]
|
|
181
|
-
if (instance) parentFilter = instance.filter
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const instances = enumerateExpansionInstances(binding, data, parentFilter)
|
|
185
|
-
const pathIndex = isRowExpanding(binding) ? placement.rowPath.at(-1) : placement.colPath.at(-1)
|
|
186
|
-
if (pathIndex === undefined) return undefined
|
|
187
|
-
return instances[pathIndex]?.value
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** 按分组字段建索引,避免交叉展开重复 filterRows */
|
|
191
|
-
export class AggregateIndex {
|
|
192
|
-
private readonly datasets = new Map<string, Record<string, unknown>[]>()
|
|
193
|
-
private readonly filterCache = new Map<string, Record<string, unknown>[]>()
|
|
194
|
-
|
|
195
|
-
constructor(data: DatasetRecords) {
|
|
196
|
-
for (const [datasetId, rows] of Object.entries(data)) {
|
|
197
|
-
this.datasets.set(datasetId, rows)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
rowsFor(dataset: string, filter: Record<string, unknown>): Record<string, unknown>[] {
|
|
202
|
-
const cacheKey = `${dataset}:${JSON.stringify(filter)}`
|
|
203
|
-
const cached = this.filterCache.get(cacheKey)
|
|
204
|
-
if (cached) return cached
|
|
205
|
-
const rows = filterRows(this.datasets.get(dataset) ?? [], filter)
|
|
206
|
-
this.filterCache.set(cacheKey, rows)
|
|
207
|
-
return rows
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
aggregate(
|
|
211
|
-
dataset: string,
|
|
212
|
-
filter: Record<string, unknown>,
|
|
213
|
-
field: string,
|
|
214
|
-
aggregate: ReportAggregate
|
|
215
|
-
): unknown {
|
|
216
|
-
const rows = this.rowsFor(dataset, filter)
|
|
217
|
-
if (aggregate === 'list') {
|
|
218
|
-
return rows[0]?.[field]
|
|
219
|
-
}
|
|
220
|
-
return aggregateField(rows, field, aggregate)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** 解析单个物理落点的绑定取值 */
|
|
225
|
-
export function resolvePlacementValue(
|
|
226
|
-
placement: PhysicalPlacement,
|
|
227
|
-
index: TemplateIndex,
|
|
228
|
-
data: DatasetRecords,
|
|
229
|
-
aggregateIndex: AggregateIndex
|
|
230
|
-
): unknown {
|
|
231
|
-
const binding = placement.binding
|
|
232
|
-
if (!binding) return undefined
|
|
233
|
-
|
|
234
|
-
const filter = resolveAncestorFilters(binding, placement, index, data)
|
|
235
|
-
const rows = aggregateIndex.rowsFor(binding.dataset, filter)
|
|
236
|
-
|
|
237
|
-
if (binding.aggregate === 'group') {
|
|
238
|
-
return filter[binding.field] ?? resolveOwnGroupValue(binding, placement, index, data)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (binding.aggregate === 'list') {
|
|
242
|
-
return rows[placement.listIndex]?.[binding.field]
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return aggregateField(rows, binding.field, binding.aggregate)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** 解析 `list` 明细落点对应的源数据行;非 list 绑定无单条记录上下文 */
|
|
249
|
-
export function resolvePlacementRecord(
|
|
250
|
-
placement: PhysicalPlacement,
|
|
251
|
-
index: TemplateIndex,
|
|
252
|
-
data: DatasetRecords,
|
|
253
|
-
aggregateIndex: AggregateIndex
|
|
254
|
-
): Record<string, unknown> | undefined {
|
|
255
|
-
const binding = placement.binding
|
|
256
|
-
if (!binding || binding.aggregate !== 'list') return undefined
|
|
257
|
-
|
|
258
|
-
const filter = resolveAncestorFilters(binding, placement, index, data)
|
|
259
|
-
const rows = aggregateIndex.rowsFor(binding.dataset, filter)
|
|
260
|
-
return rows[placement.listIndex]
|
|
261
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CellRange,
|
|
3
|
-
CellSnapshotItem,
|
|
4
|
-
CellType,
|
|
5
|
-
CellValue,
|
|
6
|
-
SheetSnapshot
|
|
7
|
-
} from '@veltra/sheet-core'
|
|
8
|
-
import { cellKey } from '@veltra/sheet-core'
|
|
9
|
-
|
|
10
|
-
import type { ReportColWidthEntry } from '../export-xlsx'
|
|
11
|
-
import type { ConditionalEvalContext } from '../rules'
|
|
12
|
-
import type { DatasetRecords } from '../types'
|
|
13
|
-
import { AggregateIndex, resolvePlacementRecord, resolvePlacementValue } from './aggregate'
|
|
14
|
-
import type { ExpansionLayout, PhysicalPlacement } from './coordinate'
|
|
15
|
-
import { physicalColSpan, physicalRowSpan } from './coordinate'
|
|
16
|
-
import { StyleResolver } from './style-resolver'
|
|
17
|
-
import type { TemplateIndex } from './template-index'
|
|
18
|
-
|
|
19
|
-
function toCellValue(value: unknown): { v: CellValue; t?: CellType } {
|
|
20
|
-
if (value === null || value === undefined) return { v: '' }
|
|
21
|
-
if (typeof value === 'number' && !Number.isNaN(value)) return { v: value, t: 'n' }
|
|
22
|
-
if (typeof value === 'boolean') return { v: value, t: 'b' }
|
|
23
|
-
if (typeof value === 'string') return { v: value, t: 's' }
|
|
24
|
-
if (value instanceof Date) return { v: value.toISOString().slice(0, 10), t: 's' }
|
|
25
|
-
return { v: JSON.stringify(value), t: 's' }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function rangeCells(range: CellRange): Array<{ row: number; col: number }> {
|
|
29
|
-
const cells: Array<{ row: number; col: number }> = []
|
|
30
|
-
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
31
|
-
for (let col = range.start.col; col <= range.end.col; col++) {
|
|
32
|
-
cells.push({ row, col })
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return cells
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function placementSourceValue(
|
|
39
|
-
placement: PhysicalPlacement,
|
|
40
|
-
template: SheetSnapshot,
|
|
41
|
-
index: TemplateIndex,
|
|
42
|
-
data: DatasetRecords,
|
|
43
|
-
aggregateIndex: AggregateIndex
|
|
44
|
-
): unknown {
|
|
45
|
-
if (placement.binding) {
|
|
46
|
-
return resolvePlacementValue(placement, index, data, aggregateIndex)
|
|
47
|
-
}
|
|
48
|
-
const staticCell = index.cellAt(placement.logical)
|
|
49
|
-
return staticCell?.v
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function colWidthSourceLogicalCol(placement: PhysicalPlacement): number {
|
|
53
|
-
const binding = placement.binding
|
|
54
|
-
if (binding?.colParent) {
|
|
55
|
-
return binding.colParent.col
|
|
56
|
-
}
|
|
57
|
-
return placement.logical.col
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 将模板列宽映射到填充报表物理列:横向展开列继承列方向父格所在模板列宽。
|
|
62
|
-
*/
|
|
63
|
-
export function mapFilledColWidths(
|
|
64
|
-
templateColWidths: ReadonlyArray<ReportColWidthEntry>,
|
|
65
|
-
layout: ExpansionLayout,
|
|
66
|
-
_index: TemplateIndex
|
|
67
|
-
): ReportColWidthEntry[] {
|
|
68
|
-
if (templateColWidths.length === 0 || layout.colCount === 0) return []
|
|
69
|
-
|
|
70
|
-
const widthByTemplateCol = new Map<number, number>()
|
|
71
|
-
for (const [col, width] of templateColWidths) {
|
|
72
|
-
widthByTemplateCol.set(col, width)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const physicalWidths = new Map<number, number>()
|
|
76
|
-
for (const placement of layout.placements) {
|
|
77
|
-
const sourceCol = colWidthSourceLogicalCol(placement)
|
|
78
|
-
const width = widthByTemplateCol.get(sourceCol)
|
|
79
|
-
if (width === undefined) continue
|
|
80
|
-
|
|
81
|
-
const startCol = placement.physical.start.col
|
|
82
|
-
const endCol = placement.physical.end.col
|
|
83
|
-
for (let col = startCol; col <= endCol; col++) {
|
|
84
|
-
physicalWidths.set(col, width)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return [...physicalWidths.entries()].sort((a, b) => a[0] - b[0])
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function placementEvalContext(
|
|
92
|
-
placement: PhysicalPlacement,
|
|
93
|
-
value: unknown,
|
|
94
|
-
template: SheetSnapshot,
|
|
95
|
-
index: TemplateIndex,
|
|
96
|
-
data: DatasetRecords,
|
|
97
|
-
aggregateIndex: AggregateIndex
|
|
98
|
-
): ConditionalEvalContext {
|
|
99
|
-
const binding = placement.binding
|
|
100
|
-
return {
|
|
101
|
-
cellValue: value,
|
|
102
|
-
bindingField: binding?.field ?? '',
|
|
103
|
-
record: binding ? resolvePlacementRecord(placement, index, data, aggregateIndex) : undefined
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function collectRowConditionalStyles(
|
|
108
|
-
layout: ExpansionLayout,
|
|
109
|
-
template: SheetSnapshot,
|
|
110
|
-
index: TemplateIndex,
|
|
111
|
-
data: DatasetRecords,
|
|
112
|
-
styles: StyleResolver
|
|
113
|
-
): void {
|
|
114
|
-
const aggregateIndex = new AggregateIndex(data)
|
|
115
|
-
|
|
116
|
-
for (const placement of layout.placements) {
|
|
117
|
-
const binding = placement.binding
|
|
118
|
-
if (!binding?.conditionalRules?.some((rule) => rule.scope === 'row')) continue
|
|
119
|
-
|
|
120
|
-
const value = placementSourceValue(placement, template, index, data, aggregateIndex)
|
|
121
|
-
const ctx = placementEvalContext(placement, value, template, index, data, aggregateIndex)
|
|
122
|
-
|
|
123
|
-
for (let row = placement.physical.start.row; row <= placement.physical.end.row; row++) {
|
|
124
|
-
styles.collectRowStyle(row, ctx, binding.conditionalRules)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** 由扩展坐标布局组装 Filled Report 快照 */
|
|
130
|
-
export function buildFilledReport(
|
|
131
|
-
template: SheetSnapshot,
|
|
132
|
-
index: TemplateIndex,
|
|
133
|
-
layout: ExpansionLayout,
|
|
134
|
-
data: DatasetRecords
|
|
135
|
-
): SheetSnapshot {
|
|
136
|
-
const styles = new StyleResolver(template)
|
|
137
|
-
collectRowConditionalStyles(layout, template, index, data, styles)
|
|
138
|
-
|
|
139
|
-
const aggregateIndex = new AggregateIndex(data)
|
|
140
|
-
const cells: CellSnapshotItem[] = []
|
|
141
|
-
const merges: CellRange[] = [...layout.mappedMerges]
|
|
142
|
-
const mergeKeys = new Set(merges.map((m) => cellKey(m.start)))
|
|
143
|
-
|
|
144
|
-
const pushMerge = (range: CellRange): void => {
|
|
145
|
-
const key = cellKey(range.start)
|
|
146
|
-
if (mergeKeys.has(key)) return
|
|
147
|
-
mergeKeys.add(key)
|
|
148
|
-
merges.push(range)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const resolveStyleId = (
|
|
152
|
-
placement: PhysicalPlacement,
|
|
153
|
-
templateRow: number,
|
|
154
|
-
templateCol: number,
|
|
155
|
-
value: unknown,
|
|
156
|
-
physicalRow: number
|
|
157
|
-
): number | undefined => {
|
|
158
|
-
const binding = placement.binding
|
|
159
|
-
const ctx = placementEvalContext(placement, value, template, index, data, aggregateIndex)
|
|
160
|
-
const baseStyleId = binding
|
|
161
|
-
? styles.resolveCell(templateRow, templateCol, ctx, binding.conditionalRules)
|
|
162
|
-
: styles.resolveStatic(templateRow, templateCol)
|
|
163
|
-
return styles.mergeRowStyle(baseStyleId, physicalRow)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
for (const placement of layout.placements) {
|
|
167
|
-
const value = placementSourceValue(placement, template, index, data, aggregateIndex)
|
|
168
|
-
const { row: templateRow, col: templateCol } = placement.logical
|
|
169
|
-
|
|
170
|
-
const rowSpan = physicalRowSpan(placement)
|
|
171
|
-
const colSpan = physicalColSpan(placement)
|
|
172
|
-
const shouldMerge = placement.mergeSpan && (rowSpan > 1 || colSpan > 1)
|
|
173
|
-
|
|
174
|
-
if (shouldMerge) {
|
|
175
|
-
const item: CellSnapshotItem = {
|
|
176
|
-
row: placement.physical.start.row,
|
|
177
|
-
col: placement.physical.start.col,
|
|
178
|
-
...toCellValue(value)
|
|
179
|
-
}
|
|
180
|
-
const styleId = resolveStyleId(
|
|
181
|
-
placement,
|
|
182
|
-
templateRow,
|
|
183
|
-
templateCol,
|
|
184
|
-
value,
|
|
185
|
-
placement.physical.start.row
|
|
186
|
-
)
|
|
187
|
-
if (styleId !== undefined) item.s = styleId
|
|
188
|
-
cells.push(item)
|
|
189
|
-
if (rowSpan > 1 || colSpan > 1) {
|
|
190
|
-
pushMerge(placement.physical)
|
|
191
|
-
}
|
|
192
|
-
continue
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
for (const pos of rangeCells(placement.physical)) {
|
|
196
|
-
const item: CellSnapshotItem = { row: pos.row, col: pos.col, ...toCellValue(value) }
|
|
197
|
-
const styleId = resolveStyleId(placement, templateRow, templateCol, value, pos.row)
|
|
198
|
-
if (styleId !== undefined) item.s = styleId
|
|
199
|
-
cells.push(item)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
cells,
|
|
205
|
-
styles: styles.snapshot(),
|
|
206
|
-
merges,
|
|
207
|
-
frozen: template.frozen,
|
|
208
|
-
rows: layout.rowCount,
|
|
209
|
-
cols: Math.max(layout.colCount, template.cols)
|
|
210
|
-
}
|
|
211
|
-
}
|