@veltra/sheet 2.4.0 → 2.5.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/sheet/formula-bar.js +50 -7
- package/dist/components/sheet/style2.css +2 -2
- package/dist/index.d.ts +2 -19
- package/dist/index.js +2 -15
- package/dist/types/index.d.ts +1 -3
- package/package.json +5 -6
- package/src/components/sheet/__test__/formula-bar.test.ts +29 -0
- package/src/components/sheet/formula-bar.vue +67 -6
- package/src/components/sheet/style.scss +1 -1
- package/src/index.ts +1 -27
- package/src/types/index.ts +0 -2
- package/dist/components/report/binding-badge.js +0 -118
- 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/drill-dialog.js +0 -258
- package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
- package/dist/components/report/designer/float-panel.js +0 -434
- 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/print.js +0 -39
- package/dist/components/report/report-designer.js +0 -428
- package/dist/components/report/report-viewer.js +0 -253
- package/dist/components/report/style.d.ts +0 -17
- package/dist/components/report/style.js +0 -18
- package/dist/components/report/style2.css +0 -965
- package/dist/components/report/use-report-designer.js +0 -387
- package/dist/components/report/use-report-viewer.js +0 -140
- 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/drill.d.ts +0 -48
- package/dist/report/drill.js +0 -78
- 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 -14
- package/dist/report/params.d.ts +0 -13
- package/dist/report/params.js +0 -61
- package/dist/report/print.d.ts +0 -23
- package/dist/report/print.js +0 -197
- package/dist/report/render/aggregate.js +0 -193
- 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 -103
- package/dist/types/report-designer.d.ts +0 -51
- package/dist/types/report-viewer.d.ts +0 -57
- 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 -559
- package/src/components/report/__test__/report-drill.test.ts +0 -692
- package/src/components/report/__test__/report-viewer.test.ts +0 -400
- package/src/components/report/__test__/topology.test.ts +0 -143
- package/src/components/report/__test__/use-report-designer.test.ts +0 -804
- package/src/components/report/binding-badge.ts +0 -122
- 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/drill-dialog.vue +0 -245
- package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
- package/src/components/report/designer/float-panel.vue +0 -444
- 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/print.ts +0 -44
- package/src/components/report/report-designer.vue +0 -454
- package/src/components/report/report-viewer.vue +0 -289
- package/src/components/report/style-designer.scss +0 -653
- package/src/components/report/style-hub.scss +0 -292
- package/src/components/report/style-viewer.scss +0 -144
- 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 -603
- package/src/components/report/use-report-viewer.ts +0 -227
- package/src/report/__test__/binding.test.ts +0 -320
- package/src/report/__test__/connector.test.ts +0 -239
- package/src/report/__test__/drill.test.ts +0 -194
- 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__/print.test.ts +0 -170
- 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/drill.ts +0 -112
- package/src/report/export-xlsx.ts +0 -15
- package/src/report/filter-bar.ts +0 -36
- package/src/report/index.ts +0 -16
- package/src/report/params.ts +0 -96
- package/src/report/print.ts +0 -234
- 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 -289
- 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 -116
- package/src/types/report-designer.ts +0 -55
- package/src/types/report-viewer.ts +0 -54
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import type { CellAddress, SheetSnapshot } from '@veltra/sheet-core'
|
|
2
|
-
import { cellKey } from '@veltra/sheet-core'
|
|
3
|
-
import { computed, ref, shallowRef, watch, type ComputedRef, type Ref, type ShallowRef } from 'vue'
|
|
4
|
-
|
|
5
|
-
import type { ConnectorError } from '../../report/connector'
|
|
6
|
-
import {
|
|
7
|
-
buildDrillHitMap,
|
|
8
|
-
createDrillStack,
|
|
9
|
-
currentDrillLayer,
|
|
10
|
-
popDrillLayer,
|
|
11
|
-
pushDrillLayer,
|
|
12
|
-
resolveDrillParams,
|
|
13
|
-
type DrillHit,
|
|
14
|
-
type DrillStack
|
|
15
|
-
} from '../../report/drill'
|
|
16
|
-
import type { ReportColWidthEntry } from '../../report/export-xlsx'
|
|
17
|
-
import { renderReport, resolveFilledColWidths } from '../../report/render'
|
|
18
|
-
import {
|
|
19
|
-
fetchTemplateRecords,
|
|
20
|
-
resolveParamDefaults,
|
|
21
|
-
resolveTemplateParams,
|
|
22
|
-
type ReportTemplate
|
|
23
|
-
} from '../../report/template'
|
|
24
|
-
import type { ParamValues, QueryParamDef, ReportDrillConfig } from '../../report/types'
|
|
25
|
-
import type { ReportViewerProps } from '../../types'
|
|
26
|
-
|
|
27
|
-
export interface UseReportViewerReturn {
|
|
28
|
-
/** Filter Bar 参数(当前层模板实际绑定的数据集的查询参数并集) */
|
|
29
|
-
params: ComputedRef<QueryParamDef[]>
|
|
30
|
-
/** 当前运行时参数值(初始为参数默认值;下钻时写入映射值) */
|
|
31
|
-
values: Ref<ParamValues>
|
|
32
|
-
/** 取数中 */
|
|
33
|
-
loading: Ref<boolean>
|
|
34
|
-
/** 最近一次取数的业务错误或下钻解析错误(可读提示;成功或重新取数时清空) */
|
|
35
|
-
error: ShallowRef<ConnectorError | null>
|
|
36
|
-
/** 最近一次成功展开渲染的 Filled Report 快照(组件壳据此替换网格内容) */
|
|
37
|
-
filledSnapshot: ShallowRef<SheetSnapshot | null>
|
|
38
|
-
/** 展开后物理列宽(由模板 colWidths 映射;无模板列宽时为 undefined) */
|
|
39
|
-
filledColWidths: ShallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>
|
|
40
|
-
/** 当前展示模板:下钻栈顶;未下钻时为 props.template */
|
|
41
|
-
currentTemplate: ComputedRef<ReportTemplate>
|
|
42
|
-
/** 下钻栈深(0 = 根层,未下钻) */
|
|
43
|
-
drillDepth: ComputedRef<number>
|
|
44
|
-
/** 可回退(栈深 > 0) */
|
|
45
|
-
canDrillBack: ComputedRef<boolean>
|
|
46
|
-
/** 物理格下钻命中查询(无命中返回 null);供组件壳点击 / 悬停判定 */
|
|
47
|
-
resolveDrillHit: (addr: CellAddress) => DrillHit | null
|
|
48
|
-
/**
|
|
49
|
-
* 解析下钻目标:宿主契约取模板 + 默认值叠加映射参数。
|
|
50
|
-
* 失败设可读错误、返回 null(栈不变)。成功时清除先前的下钻解析错误。
|
|
51
|
-
*/
|
|
52
|
-
resolveDrillTarget: (
|
|
53
|
-
config: ReportDrillConfig,
|
|
54
|
-
record: Record<string, unknown>
|
|
55
|
-
) => Promise<{ template: ReportTemplate; params: ParamValues } | null>
|
|
56
|
-
/**
|
|
57
|
-
* 下钻进入(openMode 'switch'):解析目标模板 → 映射参数带入 Filter Bar → 压栈切换并取数。
|
|
58
|
-
* 解析失败设可读错误提示、停留当前报、栈不变。
|
|
59
|
-
*/
|
|
60
|
-
drillInto: (config: ReportDrillConfig, record: Record<string, unknown>) => Promise<void>
|
|
61
|
-
/** 回退上一层:恢复该层模板与当时 Filter Bar 参数并重新取数渲染 */
|
|
62
|
-
drillBack: () => Promise<void>
|
|
63
|
-
/** 重新取数并展开渲染 */
|
|
64
|
-
refresh: () => Promise<void>
|
|
65
|
-
/** Filter Bar 改值:写入新参数值并重新取数 */
|
|
66
|
-
setValues: (next: ParamValues) => void
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* UReportViewer 的 headless 编排:参数提取 → 取数 → renderReport 展开。
|
|
71
|
-
* 不持有 DOM / 网格引用;填充快照经 `filledSnapshot` 交给组件壳应用。
|
|
72
|
-
* 下钻:openMode 'switch' 栈式切换当前层;'dialog' 由组件壳解析目标后嵌套独立查看器。
|
|
73
|
-
* 导出 / 打印始终作用于当前可见层。
|
|
74
|
-
*/
|
|
75
|
-
export function useReportViewer(props: ReportViewerProps): UseReportViewerReturn {
|
|
76
|
-
const loading = ref(false)
|
|
77
|
-
const error = shallowRef<ConnectorError | null>(null)
|
|
78
|
-
const filledSnapshot = shallowRef<SheetSnapshot | null>(null)
|
|
79
|
-
const filledColWidths = shallowRef<ReadonlyArray<ReportColWidthEntry> | undefined>(undefined)
|
|
80
|
-
|
|
81
|
-
// 下钻栈:null = 未下钻(当前层即 props.template);命中索引随每次成功取数重建
|
|
82
|
-
const drillStack = shallowRef<DrillStack | null>(null)
|
|
83
|
-
const drillHits = shallowRef<Map<number, DrillHit>>(new Map())
|
|
84
|
-
|
|
85
|
-
const currentTemplate = computed(() => {
|
|
86
|
-
const stack = drillStack.value
|
|
87
|
-
return stack ? currentDrillLayer(stack).template : props.template
|
|
88
|
-
})
|
|
89
|
-
const drillDepth = computed(() => (drillStack.value?.length ?? 1) - 1)
|
|
90
|
-
const canDrillBack = computed(() => drillDepth.value > 0)
|
|
91
|
-
|
|
92
|
-
const params = computed(() => resolveTemplateParams(currentTemplate.value))
|
|
93
|
-
// 初始值由下方 immediate watch 播种(参数默认值)
|
|
94
|
-
const values = shallowRef<ParamValues>({})
|
|
95
|
-
|
|
96
|
-
// 并发守卫:只应用最后一次取数结果(Filter Bar 连续改值时前序结果作废)
|
|
97
|
-
let fetchSeq = 0
|
|
98
|
-
|
|
99
|
-
async function refresh(): Promise<void> {
|
|
100
|
-
const current = ++fetchSeq
|
|
101
|
-
const template = currentTemplate.value
|
|
102
|
-
loading.value = true
|
|
103
|
-
error.value = null
|
|
104
|
-
const result = await fetchTemplateRecords(props.connector, template, values.value)
|
|
105
|
-
if (current !== fetchSeq) return
|
|
106
|
-
loading.value = false
|
|
107
|
-
if (!result.ok) {
|
|
108
|
-
error.value = result.error
|
|
109
|
-
filledColWidths.value = undefined
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
filledSnapshot.value = renderReport(template, result.data)
|
|
113
|
-
filledColWidths.value = template.colWidths?.length
|
|
114
|
-
? resolveFilledColWidths(template, result.data, template.colWidths)
|
|
115
|
-
: undefined
|
|
116
|
-
drillHits.value = props.resolveTemplate ? buildDrillHitMap(template, result.data) : new Map()
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** 切层共有收尾:清填充结果与命中索引,写入新层参数并重新取数 */
|
|
120
|
-
function transitionTo(layerParams: ParamValues): Promise<void> {
|
|
121
|
-
filledSnapshot.value = null
|
|
122
|
-
filledColWidths.value = undefined
|
|
123
|
-
drillHits.value = new Map()
|
|
124
|
-
values.value = layerParams
|
|
125
|
-
return refresh()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function resolveDrillTarget(
|
|
129
|
-
config: ReportDrillConfig,
|
|
130
|
-
record: Record<string, unknown>
|
|
131
|
-
): Promise<{ template: ReportTemplate; params: ParamValues } | null> {
|
|
132
|
-
const resolve = props.resolveTemplate
|
|
133
|
-
if (!resolve) return null
|
|
134
|
-
|
|
135
|
-
let target: ReportTemplate | null | undefined
|
|
136
|
-
try {
|
|
137
|
-
target = await resolve(config.target)
|
|
138
|
-
} catch (err) {
|
|
139
|
-
error.value = {
|
|
140
|
-
code: 'DRILL_RESOLVE_FAILED',
|
|
141
|
-
message: err instanceof Error ? err.message : String(err)
|
|
142
|
-
}
|
|
143
|
-
return null
|
|
144
|
-
}
|
|
145
|
-
if (!target) {
|
|
146
|
-
error.value = {
|
|
147
|
-
code: 'DRILL_RESOLVE_FAILED',
|
|
148
|
-
message: `下钻目标模板解析失败:${config.target}`
|
|
149
|
-
}
|
|
150
|
-
return null
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
error.value = null
|
|
154
|
-
return {
|
|
155
|
-
template: target,
|
|
156
|
-
params: {
|
|
157
|
-
...resolveParamDefaults(resolveTemplateParams(target)),
|
|
158
|
-
...resolveDrillParams(config, record)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function drillInto(
|
|
164
|
-
config: ReportDrillConfig,
|
|
165
|
-
record: Record<string, unknown>
|
|
166
|
-
): Promise<void> {
|
|
167
|
-
const next = await resolveDrillTarget(config, record)
|
|
168
|
-
if (!next) return
|
|
169
|
-
const base =
|
|
170
|
-
drillStack.value ?? createDrillStack({ template: props.template, params: values.value })
|
|
171
|
-
drillStack.value = pushDrillLayer(
|
|
172
|
-
base,
|
|
173
|
-
{ template: next.template, params: next.params },
|
|
174
|
-
values.value
|
|
175
|
-
)
|
|
176
|
-
await transitionTo(next.params)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async function drillBack(): Promise<void> {
|
|
180
|
-
const stack = drillStack.value
|
|
181
|
-
if (!stack || stack.length <= 1) return
|
|
182
|
-
const next = popDrillLayer(stack)
|
|
183
|
-
drillStack.value = next
|
|
184
|
-
await transitionTo({ ...currentDrillLayer(next).params })
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function resolveDrillHit(addr: CellAddress): DrillHit | null {
|
|
188
|
-
return drillHits.value.get(cellKey(addr)) ?? null
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function setValues(next: ParamValues): void {
|
|
192
|
-
values.value = next
|
|
193
|
-
void refresh()
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// 宿主更换根模板:下钻栈作废;Filter Bar 播种为默认值叠加 initialValues
|
|
197
|
-
watch(
|
|
198
|
-
() => props.template,
|
|
199
|
-
() => {
|
|
200
|
-
drillStack.value = null
|
|
201
|
-
drillHits.value = new Map()
|
|
202
|
-
filledSnapshot.value = null
|
|
203
|
-
filledColWidths.value = undefined
|
|
204
|
-
values.value = { ...resolveParamDefaults(params.value), ...props.initialValues }
|
|
205
|
-
void refresh()
|
|
206
|
-
},
|
|
207
|
-
{ immediate: true }
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
return {
|
|
211
|
-
params,
|
|
212
|
-
values,
|
|
213
|
-
loading,
|
|
214
|
-
error,
|
|
215
|
-
filledSnapshot,
|
|
216
|
-
filledColWidths,
|
|
217
|
-
currentTemplate,
|
|
218
|
-
drillDepth,
|
|
219
|
-
canDrillBack,
|
|
220
|
-
resolveDrillHit,
|
|
221
|
-
resolveDrillTarget,
|
|
222
|
-
drillInto,
|
|
223
|
-
drillBack,
|
|
224
|
-
refresh,
|
|
225
|
-
setValues
|
|
226
|
-
}
|
|
227
|
-
}
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import type { CellAddress } from '@veltra/sheet-core'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
applyReportPreset,
|
|
6
|
-
createReportBinding,
|
|
7
|
-
formatBindingPlaceholder,
|
|
8
|
-
formatCellAddress,
|
|
9
|
-
inferColParentCandidate,
|
|
10
|
-
inferDropPreset,
|
|
11
|
-
inferParentsForPreset,
|
|
12
|
-
inferReportPreset,
|
|
13
|
-
inferRowParentCandidate,
|
|
14
|
-
isExpansionBandRow,
|
|
15
|
-
parseCellAddress,
|
|
16
|
-
presetBindingPatch,
|
|
17
|
-
presetStillApplies,
|
|
18
|
-
setBindingCatalog
|
|
19
|
-
} from '../binding'
|
|
20
|
-
import type { DatasetCatalogItem, ReportBinding } from '../types'
|
|
21
|
-
|
|
22
|
-
/** 内联 catalog fixture(单一事实源在测试自身,不依赖 playground mock) */
|
|
23
|
-
const ORDERS_DATASET: DatasetCatalogItem = {
|
|
24
|
-
id: 'orders',
|
|
25
|
-
label: '销售明细',
|
|
26
|
-
fields: [
|
|
27
|
-
{ name: 'customer', label: '客户', type: 'string' },
|
|
28
|
-
{ name: 'orderNo', label: '订单号', type: 'string' },
|
|
29
|
-
{ name: 'region', label: '地区', type: 'string' },
|
|
30
|
-
{ name: 'amount', label: '金额', type: 'number' },
|
|
31
|
-
{ name: 'orderDate', label: '下单日期', type: 'date' }
|
|
32
|
-
]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
setBindingCatalog([ORDERS_DATASET])
|
|
36
|
-
|
|
37
|
-
describe('report binding', () => {
|
|
38
|
-
it('createReportBinding 默认 list + 纵向扩展 + 明细预设', () => {
|
|
39
|
-
const binding = createReportBinding(ORDERS_DATASET, 'amount')
|
|
40
|
-
expect(binding).toEqual({
|
|
41
|
-
dataset: 'orders',
|
|
42
|
-
field: 'amount',
|
|
43
|
-
aggregate: 'list',
|
|
44
|
-
expand: 'down',
|
|
45
|
-
preset: 'detail',
|
|
46
|
-
sort: 'none',
|
|
47
|
-
conditionalRules: []
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('formatBindingPlaceholder 中文标签(含 list / max / min)', () => {
|
|
52
|
-
expect(formatBindingPlaceholder(createReportBinding(ORDERS_DATASET, 'amount'))).toBe(
|
|
53
|
-
'明细 · 金额'
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
const group = createReportBinding(ORDERS_DATASET, 'customer')
|
|
57
|
-
group.aggregate = 'group'
|
|
58
|
-
expect(formatBindingPlaceholder(group)).toBe('分组 · 客户')
|
|
59
|
-
|
|
60
|
-
const sum = createReportBinding(ORDERS_DATASET, 'amount')
|
|
61
|
-
sum.aggregate = 'sum'
|
|
62
|
-
sum.expand = 'none'
|
|
63
|
-
expect(formatBindingPlaceholder(sum)).toBe('求和 · 金额')
|
|
64
|
-
|
|
65
|
-
const max = createReportBinding(ORDERS_DATASET, 'amount')
|
|
66
|
-
max.aggregate = 'max'
|
|
67
|
-
max.expand = 'none'
|
|
68
|
-
expect(formatBindingPlaceholder(max)).toBe('最大 · 金额')
|
|
69
|
-
|
|
70
|
-
const min = createReportBinding(ORDERS_DATASET, 'amount')
|
|
71
|
-
min.aggregate = 'min'
|
|
72
|
-
min.expand = 'none'
|
|
73
|
-
expect(formatBindingPlaceholder(min)).toBe('最小 · 金额')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('presetBindingPatch / applyReportPreset 映射表', () => {
|
|
77
|
-
const rowParent = { row: 1, col: 0 }
|
|
78
|
-
const colParent = { row: 0, col: 1 }
|
|
79
|
-
const base: ReportBinding = {
|
|
80
|
-
dataset: 'orders',
|
|
81
|
-
field: 'amount',
|
|
82
|
-
expand: 'down',
|
|
83
|
-
aggregate: 'list',
|
|
84
|
-
rowParent,
|
|
85
|
-
colParent
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
expect(presetBindingPatch('groupHeader')).toEqual({
|
|
89
|
-
preset: 'groupHeader',
|
|
90
|
-
expand: 'down',
|
|
91
|
-
aggregate: 'group'
|
|
92
|
-
})
|
|
93
|
-
expect(presetBindingPatch('groupHeader', { transpose: true })).toMatchObject({
|
|
94
|
-
expand: 'right',
|
|
95
|
-
aggregate: 'group'
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
expect(presetBindingPatch('detail')).toEqual({
|
|
99
|
-
preset: 'detail',
|
|
100
|
-
expand: 'down',
|
|
101
|
-
aggregate: 'list'
|
|
102
|
-
})
|
|
103
|
-
expect(presetBindingPatch('detail', { transpose: true })).toMatchObject({
|
|
104
|
-
expand: 'right',
|
|
105
|
-
aggregate: 'list'
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
expect(presetBindingPatch('subtotal')).toEqual({
|
|
109
|
-
preset: 'subtotal',
|
|
110
|
-
expand: 'none',
|
|
111
|
-
aggregate: 'sum'
|
|
112
|
-
})
|
|
113
|
-
expect(applyReportPreset(base, 'subtotal').rowParent).toEqual(rowParent)
|
|
114
|
-
|
|
115
|
-
expect(presetBindingPatch('grandTotal')).toEqual({
|
|
116
|
-
preset: 'grandTotal',
|
|
117
|
-
expand: 'none',
|
|
118
|
-
aggregate: 'sum',
|
|
119
|
-
rowParent: undefined,
|
|
120
|
-
colParent: undefined
|
|
121
|
-
})
|
|
122
|
-
expect(applyReportPreset(base, 'grandTotal').rowParent).toBeUndefined()
|
|
123
|
-
expect(applyReportPreset(base, 'grandTotal').colParent).toBeUndefined()
|
|
124
|
-
|
|
125
|
-
expect(presetBindingPatch('cross')).toEqual({
|
|
126
|
-
preset: 'cross',
|
|
127
|
-
expand: 'none',
|
|
128
|
-
aggregate: 'sum'
|
|
129
|
-
})
|
|
130
|
-
expect(applyReportPreset(base, 'cross').rowParent).toEqual(rowParent)
|
|
131
|
-
expect(applyReportPreset(base, 'cross').colParent).toEqual(colParent)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('inferReportPreset 从字段组合推断预设', () => {
|
|
135
|
-
expect(
|
|
136
|
-
inferReportPreset({
|
|
137
|
-
dataset: 'orders',
|
|
138
|
-
field: 'customer',
|
|
139
|
-
aggregate: 'group',
|
|
140
|
-
expand: 'down'
|
|
141
|
-
})
|
|
142
|
-
).toBe('groupHeader')
|
|
143
|
-
|
|
144
|
-
expect(
|
|
145
|
-
inferReportPreset({
|
|
146
|
-
dataset: 'orders',
|
|
147
|
-
field: 'orderNo',
|
|
148
|
-
aggregate: 'list',
|
|
149
|
-
expand: 'down',
|
|
150
|
-
rowParent: { row: 1, col: 0 }
|
|
151
|
-
})
|
|
152
|
-
).toBe('detail')
|
|
153
|
-
|
|
154
|
-
expect(
|
|
155
|
-
inferReportPreset({
|
|
156
|
-
dataset: 'orders',
|
|
157
|
-
field: 'amount',
|
|
158
|
-
aggregate: 'sum',
|
|
159
|
-
expand: 'none',
|
|
160
|
-
rowParent: { row: 1, col: 0 }
|
|
161
|
-
})
|
|
162
|
-
).toBe('subtotal')
|
|
163
|
-
|
|
164
|
-
expect(
|
|
165
|
-
inferReportPreset({ dataset: 'orders', field: 'amount', aggregate: 'sum', expand: 'none' })
|
|
166
|
-
).toBe('grandTotal')
|
|
167
|
-
|
|
168
|
-
expect(
|
|
169
|
-
inferReportPreset({
|
|
170
|
-
dataset: 'orders',
|
|
171
|
-
field: 'amount',
|
|
172
|
-
aggregate: 'sum',
|
|
173
|
-
expand: 'none',
|
|
174
|
-
rowParent: { row: 1, col: 0 },
|
|
175
|
-
colParent: { row: 0, col: 1 }
|
|
176
|
-
})
|
|
177
|
-
).toBe('cross')
|
|
178
|
-
|
|
179
|
-
expect(
|
|
180
|
-
inferReportPreset({
|
|
181
|
-
dataset: 'orders',
|
|
182
|
-
field: 'amount',
|
|
183
|
-
aggregate: 'avg',
|
|
184
|
-
expand: 'none',
|
|
185
|
-
rowParent: { row: 1, col: 0 }
|
|
186
|
-
})
|
|
187
|
-
).toBe('subtotal')
|
|
188
|
-
|
|
189
|
-
expect(
|
|
190
|
-
inferReportPreset({
|
|
191
|
-
dataset: 'orders',
|
|
192
|
-
field: 'amount',
|
|
193
|
-
aggregate: 'sum',
|
|
194
|
-
expand: 'none',
|
|
195
|
-
colParent: { row: 0, col: 1 }
|
|
196
|
-
})
|
|
197
|
-
).toBe('subtotal')
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('presetStillApplies 同族内改聚合仍成立', () => {
|
|
201
|
-
const subtotal: ReportBinding = {
|
|
202
|
-
dataset: 'orders',
|
|
203
|
-
field: 'amount',
|
|
204
|
-
preset: 'subtotal',
|
|
205
|
-
aggregate: 'avg',
|
|
206
|
-
expand: 'none',
|
|
207
|
-
rowParent: { row: 1, col: 0 }
|
|
208
|
-
}
|
|
209
|
-
expect(presetStillApplies(subtotal)).toBe(true)
|
|
210
|
-
|
|
211
|
-
const broken = { ...subtotal, aggregate: 'list' as const, expand: 'down' as const }
|
|
212
|
-
expect(presetStillApplies(broken)).toBe(false)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('inferDropPreset / isExpansionBandRow 落格推断', () => {
|
|
216
|
-
const groupAddr = { row: 1, col: 0 }
|
|
217
|
-
const group = createReportBinding(ORDERS_DATASET, 'customer')
|
|
218
|
-
group.aggregate = 'group'
|
|
219
|
-
group.expand = 'down'
|
|
220
|
-
|
|
221
|
-
const getBindingAt = (addr: CellAddress) => {
|
|
222
|
-
if (addr.row === groupAddr.row && addr.col === groupAddr.col) return group
|
|
223
|
-
return undefined
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
expect(isExpansionBandRow(1, getBindingAt)).toBe(true)
|
|
227
|
-
expect(isExpansionBandRow(0, getBindingAt)).toBe(false)
|
|
228
|
-
expect(inferDropPreset({ row: 2, col: 0 }, 'number', getBindingAt)).toBe('subtotal')
|
|
229
|
-
expect(inferDropPreset({ row: 2, col: 0 }, 'string', getBindingAt)).toBe('detail')
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('inferDropPreset:数值字段落在横向展开带正右相邻列也推断为小计', () => {
|
|
233
|
-
const colGroup = createReportBinding(ORDERS_DATASET, 'region')
|
|
234
|
-
colGroup.aggregate = 'group'
|
|
235
|
-
colGroup.expand = 'right'
|
|
236
|
-
|
|
237
|
-
const getBindingAt = (addr: CellAddress) => {
|
|
238
|
-
if (addr.row === 0 && addr.col === 1) return colGroup
|
|
239
|
-
return undefined
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
expect(inferDropPreset({ row: 0, col: 2 }, 'number', getBindingAt)).toBe('subtotal')
|
|
243
|
-
expect(inferDropPreset({ row: 0, col: 2 }, 'string', getBindingAt)).toBe('detail')
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
it('inferRowParentCandidate:同行向左优先分组头', () => {
|
|
247
|
-
const group = createReportBinding(ORDERS_DATASET, 'customer')
|
|
248
|
-
group.aggregate = 'group'
|
|
249
|
-
group.expand = 'down'
|
|
250
|
-
const detail = createReportBinding(ORDERS_DATASET, 'orderNo')
|
|
251
|
-
|
|
252
|
-
const getBindingAt = (addr: CellAddress) => {
|
|
253
|
-
if (addr.row === 1 && addr.col === 0) return group
|
|
254
|
-
if (addr.row === 1 && addr.col === 1) return detail
|
|
255
|
-
return undefined
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
expect(inferRowParentCandidate({ row: 1, col: 2 }, getBindingAt)).toEqual({ row: 1, col: 0 })
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('inferRowParentCandidate:下方跨列小计推到行分组头而不是同列 list', () => {
|
|
262
|
-
const group = createReportBinding(ORDERS_DATASET, 'customer')
|
|
263
|
-
group.aggregate = 'group'
|
|
264
|
-
group.expand = 'down'
|
|
265
|
-
const detail = createReportBinding(ORDERS_DATASET, 'amount')
|
|
266
|
-
|
|
267
|
-
const getBindingAt = (addr: CellAddress) => {
|
|
268
|
-
if (addr.row === 1 && addr.col === 0) return group
|
|
269
|
-
if (addr.row === 1 && addr.col === 2) return detail
|
|
270
|
-
return undefined
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
expect(
|
|
274
|
-
inferRowParentCandidate({ row: 2, col: 2 }, getBindingAt, { preferGroup: true })
|
|
275
|
-
).toEqual({ row: 1, col: 0 })
|
|
276
|
-
expect(inferParentsForPreset({ row: 2, col: 2 }, 'subtotal', getBindingAt)).toEqual({
|
|
277
|
-
rowParent: { row: 1, col: 0 }
|
|
278
|
-
})
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
it('inferColParentCandidate:右侧列小计同列向上推到列分组头', () => {
|
|
282
|
-
const colGroup = createReportBinding(ORDERS_DATASET, 'region')
|
|
283
|
-
colGroup.aggregate = 'group'
|
|
284
|
-
colGroup.expand = 'right'
|
|
285
|
-
const cross = createReportBinding(ORDERS_DATASET, 'amount')
|
|
286
|
-
cross.preset = 'cross'
|
|
287
|
-
cross.aggregate = 'sum'
|
|
288
|
-
cross.expand = 'none'
|
|
289
|
-
cross.colParent = { row: 0, col: 1 }
|
|
290
|
-
|
|
291
|
-
const getBindingAt = (addr: CellAddress) => {
|
|
292
|
-
if (addr.row === 0 && addr.col === 1) return colGroup
|
|
293
|
-
if (addr.row === 1 && addr.col === 1) return cross
|
|
294
|
-
return undefined
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
expect(
|
|
298
|
-
inferColParentCandidate({ row: 2, col: 1 }, getBindingAt, { preferGroup: true })
|
|
299
|
-
).toEqual({ row: 0, col: 1 })
|
|
300
|
-
expect(inferParentsForPreset({ row: 2, col: 1 }, 'subtotal', getBindingAt)).toEqual({
|
|
301
|
-
colParent: { row: 0, col: 1 }
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
it('formatCellAddress / parseCellAddress 支持多字母列互逆', () => {
|
|
306
|
-
const single = { row: 2, col: 1 }
|
|
307
|
-
expect(formatCellAddress(single)).toBe('B3')
|
|
308
|
-
expect(parseCellAddress('B3')).toEqual(single)
|
|
309
|
-
|
|
310
|
-
const wide = { row: 0, col: 26 }
|
|
311
|
-
expect(formatCellAddress(wide)).toBe('AA1')
|
|
312
|
-
expect(parseCellAddress('AA1')).toEqual(wide)
|
|
313
|
-
|
|
314
|
-
const wider = { row: 99, col: 701 }
|
|
315
|
-
expect(formatCellAddress(wider)).toBe('ZZ100')
|
|
316
|
-
expect(parseCellAddress('ZZ100')).toEqual(wider)
|
|
317
|
-
|
|
318
|
-
expect(parseCellAddress('bad')).toBeNull()
|
|
319
|
-
})
|
|
320
|
-
})
|