@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.
Files changed (147) hide show
  1. package/dist/components/sheet/formula-bar.js +50 -7
  2. package/dist/components/sheet/style2.css +2 -2
  3. package/dist/index.d.ts +2 -19
  4. package/dist/index.js +2 -15
  5. package/dist/types/index.d.ts +1 -3
  6. package/package.json +5 -6
  7. package/src/components/sheet/__test__/formula-bar.test.ts +29 -0
  8. package/src/components/sheet/formula-bar.vue +67 -6
  9. package/src/components/sheet/style.scss +1 -1
  10. package/src/index.ts +1 -27
  11. package/src/types/index.ts +0 -2
  12. package/dist/components/report/binding-badge.js +0 -118
  13. package/dist/components/report/designer/cell-coords.js +0 -46
  14. package/dist/components/report/designer/col-widths.js +0 -29
  15. package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
  16. package/dist/components/report/designer/drill-dialog.js +0 -258
  17. package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
  18. package/dist/components/report/designer/float-panel.js +0 -434
  19. package/dist/components/report/designer/role.js +0 -17
  20. package/dist/components/report/designer/rule-preview.js +0 -33
  21. package/dist/components/report/designer/rule-row.js +0 -253
  22. package/dist/components/report/designer/rules-dialog.js +0 -118
  23. package/dist/components/report/designer/topology-overlay.js +0 -122
  24. package/dist/components/report/designer/topology.js +0 -64
  25. package/dist/components/report/designer/use-grid-overlay.js +0 -75
  26. package/dist/components/report/designer-hub.js +0 -177
  27. package/dist/components/report/field-panel-helpers.js +0 -33
  28. package/dist/components/report/field-panel.js +0 -132
  29. package/dist/components/report/filter-bar.js +0 -74
  30. package/dist/components/report/hub-connection-form.js +0 -145
  31. package/dist/components/report/hub-dataset-editor.js +0 -409
  32. package/dist/components/report/preview-grid-size.js +0 -32
  33. package/dist/components/report/print.js +0 -39
  34. package/dist/components/report/report-designer.js +0 -428
  35. package/dist/components/report/report-viewer.js +0 -253
  36. package/dist/components/report/style.d.ts +0 -17
  37. package/dist/components/report/style.js +0 -18
  38. package/dist/components/report/style2.css +0 -965
  39. package/dist/components/report/use-report-designer.js +0 -387
  40. package/dist/components/report/use-report-viewer.js +0 -140
  41. package/dist/report/binding.d.ts +0 -83
  42. package/dist/report/binding.js +0 -334
  43. package/dist/report/connector.d.ts +0 -66
  44. package/dist/report/connector.js +0 -159
  45. package/dist/report/drill.d.ts +0 -48
  46. package/dist/report/drill.js +0 -78
  47. package/dist/report/export-xlsx.d.ts +0 -13
  48. package/dist/report/export-xlsx.js +0 -13
  49. package/dist/report/filter-bar.d.ts +0 -10
  50. package/dist/report/filter-bar.js +0 -31
  51. package/dist/report/index.d.ts +0 -14
  52. package/dist/report/params.d.ts +0 -13
  53. package/dist/report/params.js +0 -61
  54. package/dist/report/print.d.ts +0 -23
  55. package/dist/report/print.js +0 -197
  56. package/dist/report/render/aggregate.js +0 -193
  57. package/dist/report/render/builder.d.ts +0 -12
  58. package/dist/report/render/builder.js +0 -139
  59. package/dist/report/render/coordinate.d.ts +0 -25
  60. package/dist/report/render/coordinate.js +0 -733
  61. package/dist/report/render/index.d.ts +0 -14
  62. package/dist/report/render/index.js +0 -21
  63. package/dist/report/render/style-resolver.js +0 -50
  64. package/dist/report/render/template-index.d.ts +0 -24
  65. package/dist/report/render/template-index.js +0 -137
  66. package/dist/report/rules/evaluate.d.ts +0 -25
  67. package/dist/report/rules/evaluate.js +0 -82
  68. package/dist/report/rules/index.d.ts +0 -2
  69. package/dist/report/template.d.ts +0 -63
  70. package/dist/report/template.js +0 -83
  71. package/dist/report/types.d.ts +0 -103
  72. package/dist/types/report-designer.d.ts +0 -51
  73. package/dist/types/report-viewer.d.ts +0 -57
  74. package/src/components/report/__test__/binding-badge.test.ts +0 -92
  75. package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
  76. package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
  77. package/src/components/report/__test__/float-panel-position.test.ts +0 -45
  78. package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
  79. package/src/components/report/__test__/report-designer.test.ts +0 -559
  80. package/src/components/report/__test__/report-drill.test.ts +0 -692
  81. package/src/components/report/__test__/report-viewer.test.ts +0 -400
  82. package/src/components/report/__test__/topology.test.ts +0 -143
  83. package/src/components/report/__test__/use-report-designer.test.ts +0 -804
  84. package/src/components/report/binding-badge.ts +0 -122
  85. package/src/components/report/designer/cell-coords.ts +0 -95
  86. package/src/components/report/designer/col-widths.ts +0 -56
  87. package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
  88. package/src/components/report/designer/drill-dialog.vue +0 -245
  89. package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
  90. package/src/components/report/designer/float-panel.vue +0 -444
  91. package/src/components/report/designer/role.ts +0 -32
  92. package/src/components/report/designer/rule-preview.vue +0 -40
  93. package/src/components/report/designer/rule-row.vue +0 -252
  94. package/src/components/report/designer/rules-dialog.vue +0 -110
  95. package/src/components/report/designer/topology-overlay.vue +0 -128
  96. package/src/components/report/designer/topology.ts +0 -124
  97. package/src/components/report/designer/use-grid-overlay.ts +0 -114
  98. package/src/components/report/designer-hub.vue +0 -198
  99. package/src/components/report/field-panel-helpers.ts +0 -44
  100. package/src/components/report/field-panel.vue +0 -136
  101. package/src/components/report/filter-bar.vue +0 -77
  102. package/src/components/report/hub-connection-form.vue +0 -124
  103. package/src/components/report/hub-dataset-editor.vue +0 -387
  104. package/src/components/report/index.ts +0 -2
  105. package/src/components/report/preview-grid-size.ts +0 -33
  106. package/src/components/report/print.ts +0 -44
  107. package/src/components/report/report-designer.vue +0 -454
  108. package/src/components/report/report-viewer.vue +0 -289
  109. package/src/components/report/style-designer.scss +0 -653
  110. package/src/components/report/style-hub.scss +0 -292
  111. package/src/components/report/style-viewer.scss +0 -144
  112. package/src/components/report/style.scss +0 -3
  113. package/src/components/report/style.ts +0 -20
  114. package/src/components/report/use-report-designer.ts +0 -603
  115. package/src/components/report/use-report-viewer.ts +0 -227
  116. package/src/report/__test__/binding.test.ts +0 -320
  117. package/src/report/__test__/connector.test.ts +0 -239
  118. package/src/report/__test__/drill.test.ts +0 -194
  119. package/src/report/__test__/export-xlsx.test.ts +0 -96
  120. package/src/report/__test__/filter-bar.test.ts +0 -58
  121. package/src/report/__test__/params.test.ts +0 -156
  122. package/src/report/__test__/print.test.ts +0 -170
  123. package/src/report/__test__/render.test.ts +0 -856
  124. package/src/report/__test__/rules.test.ts +0 -131
  125. package/src/report/__test__/template.test.ts +0 -366
  126. package/src/report/binding.ts +0 -474
  127. package/src/report/connector.ts +0 -206
  128. package/src/report/drill.ts +0 -112
  129. package/src/report/export-xlsx.ts +0 -15
  130. package/src/report/filter-bar.ts +0 -36
  131. package/src/report/index.ts +0 -16
  132. package/src/report/params.ts +0 -96
  133. package/src/report/print.ts +0 -234
  134. package/src/report/render/__test__/aggregate.test.ts +0 -16
  135. package/src/report/render/__test__/coordinate.test.ts +0 -405
  136. package/src/report/render/aggregate.ts +0 -289
  137. package/src/report/render/builder.ts +0 -211
  138. package/src/report/render/coordinate.ts +0 -1239
  139. package/src/report/render/index.ts +0 -30
  140. package/src/report/render/style-resolver.ts +0 -68
  141. package/src/report/render/template-index.ts +0 -200
  142. package/src/report/rules/evaluate.ts +0 -113
  143. package/src/report/rules/index.ts +0 -7
  144. package/src/report/template.ts +0 -145
  145. package/src/report/types.ts +0 -116
  146. package/src/types/report-designer.ts +0 -55
  147. package/src/types/report-viewer.ts +0 -54
@@ -1,559 +0,0 @@
1
- import { Workbook } from '@veltra/sheet-core'
2
- import { SheetGrid } from '@veltra/sheet-core/grid'
3
- import { afterEach, describe, expect, it, vi } from 'vitest'
4
- import { createApp, h, nextTick, ref, type App } from 'vue'
5
-
6
- import { UReportDesigner } from '../../../index'
7
- import { REPORT_META_NAMESPACE } from '../../../report/binding'
8
- import type { DataConnection, DataConnector } from '../../../report/connector'
9
- import type { ReportTemplate } from '../../../report/template'
10
- import type { ParamValues, ReportBinding, ReportTemplateListItem } from '../../../report/types'
11
- import type { ReportDesignerExposed } from '../../../types'
12
- import * as cellCoords from '../designer/cell-coords'
13
- import * as gridOverlay from '../designer/use-grid-overlay'
14
- import { FIELD_DRAG_MIME } from '../field-panel-helpers'
15
-
16
- // ---- 内联 fixtures:stub connector(实现 DataConnector 接口的内存测试夹具)----
17
-
18
- function createStubConnector() {
19
- const calls = {
20
- test: [] as DataConnection[],
21
- describe: [] as Array<{ connection: DataConnection; sql: string }>,
22
- query: [] as Array<{ connection: DataConnection; sql: string; values: ParamValues }>
23
- }
24
- /** test 结果可变(失败路径用例切换为业务错误) */
25
- const state = {
26
- testOutcome: { ok: true, data: undefined } as Awaited<ReturnType<DataConnector['test']>>
27
- }
28
- const connector: DataConnector = {
29
- test: (connection) => {
30
- calls.test.push(connection)
31
- return Promise.resolve(state.testOutcome)
32
- },
33
- describe: (connection, sql) => {
34
- calls.describe.push({ connection, sql })
35
- return Promise.resolve({
36
- ok: true,
37
- data: [
38
- { name: 'customer', label: '客户', type: 'string' as const },
39
- { name: 'amount', label: '金额', type: 'number' as const }
40
- ]
41
- })
42
- },
43
- query: (connection, sql, values) => {
44
- calls.query.push({ connection, sql, values })
45
- return Promise.resolve({
46
- ok: true,
47
- data: {
48
- fields: [
49
- { name: 'customer', label: '客户', type: 'string' as const },
50
- { name: 'amount', label: '金额', type: 'number' as const }
51
- ],
52
- rows: [
53
- { customer: '甲公司', amount: 100 },
54
- { customer: '乙公司', amount: 400 }
55
- ]
56
- }
57
- })
58
- }
59
- }
60
- return { connector, calls, state }
61
- }
62
-
63
- // ---- 挂载与等待 ----
64
-
65
- const apps: App[] = []
66
- const containers: HTMLElement[] = []
67
-
68
- function mountDesigner(
69
- workbook: Workbook,
70
- options?: {
71
- template?: ReportTemplate
72
- drillTemplates?: ReportTemplateListItem[]
73
- resolveTemplate?: (ref: string) => Promise<ReportTemplate>
74
- }
75
- ) {
76
- const { connector, calls, state } = createStubConnector()
77
- const el = document.createElement('div')
78
- el.style.width = '960px'
79
- el.style.height = '600px'
80
- document.body.appendChild(el)
81
- containers.push(el)
82
- const connections = ref<DataConnection[]>([])
83
- const exposedRef = { value: undefined as ReportDesignerExposed | undefined }
84
- const app = createApp({
85
- render: () =>
86
- h(UReportDesigner, {
87
- connector,
88
- workbook,
89
- connections: connections.value,
90
- template: options?.template,
91
- drillTemplates: options?.drillTemplates,
92
- resolveTemplate: options?.resolveTemplate,
93
- 'onUpdate:connections': (value: DataConnection[]) => {
94
- connections.value = value
95
- },
96
- ref: (value: unknown) => {
97
- exposedRef.value = value as ReportDesignerExposed | undefined
98
- }
99
- })
100
- })
101
- app.mount(el)
102
- apps.push(app)
103
- return { app, el, connections, exposedRef, calls, state }
104
- }
105
-
106
- /** 等待抽屉(teleport + transition)与连接器微任务落定 */
107
- async function flush(): Promise<void> {
108
- await nextTick()
109
- await new Promise((resolve) => setTimeout(resolve, 0))
110
- await nextTick()
111
- await nextTick()
112
- }
113
-
114
- /** 按文本定位按钮(drawer 经 Teleport 挂到 body,统一在 document 范围找) */
115
- function findButton(text: string): HTMLButtonElement | undefined {
116
- return [...document.querySelectorAll<HTMLButtonElement>('button')].find((button) =>
117
- button.textContent?.includes(text)
118
- )
119
- }
120
-
121
- async function click(button: HTMLElement): Promise<void> {
122
- button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
123
- await flush()
124
- }
125
-
126
- /** 构造字段拖拽 drop 事件(happy-dom 无 DataTransfer 构造器,注入最小桩) */
127
- function dispatchFieldDrop(target: HTMLElement, payload: string): void {
128
- const event = new Event('drop', { bubbles: true, cancelable: true })
129
- Object.defineProperty(event, 'dataTransfer', {
130
- value: {
131
- types: [FIELD_DRAG_MIME],
132
- getData: (type: string) => (type === FIELD_DRAG_MIME ? payload : '')
133
- }
134
- })
135
- // 落点超出网格视口 → hit-test 落空,回退当前选区(确定性断言)
136
- Object.defineProperty(event, 'clientX', { value: 99999 })
137
- Object.defineProperty(event, 'clientY', { value: 99999 })
138
- target.dispatchEvent(event)
139
- }
140
-
141
- /** 构造字段拖拽 dragover 事件 */
142
- function dispatchFieldDragOver(target: HTMLElement, clientX = 100, clientY = 100): void {
143
- const event = new Event('dragover', { bubbles: true, cancelable: true })
144
- Object.defineProperty(event, 'dataTransfer', {
145
- value: { types: [FIELD_DRAG_MIME], dropEffect: 'copy' }
146
- })
147
- Object.defineProperty(event, 'clientX', { value: clientX })
148
- Object.defineProperty(event, 'clientY', { value: clientY })
149
- target.dispatchEvent(event)
150
- }
151
-
152
- afterEach(() => {
153
- while (apps.length) apps.pop()!.unmount()
154
- while (containers.length) containers.pop()!.remove()
155
- // drawer / dialog Teleport 到 body,卸载后清理残留
156
- document.querySelectorAll('.u-drawer-overlay').forEach((node) => node.remove())
157
- document.querySelectorAll('.u-dialog-overlay').forEach((node) => node.remove())
158
- })
159
-
160
- describe('UReportDesigner 最小闭环(数据中枢 + 拖拽绑定 + getTemplate)', () => {
161
- it('连接 CRUD / 真实测试连接走 v-model:connections 与连接器;落格写 meta;getTemplate 吐出快照', async () => {
162
- const workbook = new Workbook()
163
- const { el, connections, exposedRef, calls } = mountDesigner(workbook)
164
- await flush()
165
-
166
- // 打开数据中枢 drawer(Teleport 到 body)
167
- const hubTrigger = el.querySelector<HTMLButtonElement>('.u-report-designer__toolbar button')!
168
- expect(hubTrigger.textContent).toContain('数据中枢')
169
- await click(hubTrigger)
170
- expect(document.querySelector('.u-report-hub')).toBeTruthy()
171
-
172
- // 新建连接:表单出现 → 真实测试连接(经 stub connector)→ 保存
173
- await click(findButton('新建连接')!)
174
- expect(document.querySelector('.u-report-hub-connection-form')).toBeTruthy()
175
-
176
- await click(findButton('测试连接')!)
177
- expect(calls.test).toHaveLength(1)
178
- expect(calls.test[0]).toMatchObject({ label: '新连接', type: 'mysql' })
179
- expect(document.querySelector('.u-report-hub-connection-form__test')?.textContent).toContain(
180
- '连接成功'
181
- )
182
-
183
- await click(findButton('保存')!)
184
- expect(connections.value).toHaveLength(1)
185
- expect(connections.value[0]).toMatchObject({ label: '新连接', type: 'mysql' })
186
- const connectionId = connections.value[0]!.id
187
-
188
- // 在该连接下新建数据集(编辑器出现,含 SQL 编辑与 schema/preview tabs)
189
- await click(findButton('+ 新建数据集')!)
190
- expect(document.querySelector('.u-report-hub-dataset-editor')).toBeTruthy()
191
- const datasetId = document.querySelector('.u-report-hub__dataset-id')!.textContent!
192
-
193
- // 关闭 drawer,回到设计态
194
- await click(findButton('完成')!)
195
-
196
- // 拖拽字段落格 → 写入 Cell Meta 绑定(落点 hit-test 落空 → 回退当前选区 A1)
197
- const gridHost = el.querySelector<HTMLElement>('.u-report-designer__grid')!
198
- dispatchFieldDrop(gridHost, `${datasetId}:customer`)
199
- await flush()
200
-
201
- const metas = [...workbook.activeSheet.entriesCellMeta()].filter(
202
- ([, namespace]) => namespace === REPORT_META_NAMESPACE
203
- )
204
- expect(metas).toHaveLength(1)
205
- const [addr, , payload] = metas[0]!
206
- expect(addr).toEqual({ row: 0, col: 0 })
207
- expect(payload as ReportBinding).toMatchObject({
208
- dataset: datasetId,
209
- field: 'customer',
210
- preset: 'detail',
211
- aggregate: 'list',
212
- expand: 'down'
213
- })
214
-
215
- // getTemplate() 返回含 meta 绑定与内嵌数据集定义(连接对象内嵌,凭据随模板流转)
216
- const template = exposedRef.value!.getTemplate()
217
- expect(template.version).toBe(1)
218
- expect(template.meta).toHaveLength(1)
219
- expect(template.meta![0]).toMatchObject({ row: 0, col: 0, namespace: 'report' })
220
- expect(template.datasets).toEqual([
221
- {
222
- id: datasetId,
223
- label: '新数据集',
224
- connection: { ...connections.value[0]!, id: connectionId },
225
- sql: ''
226
- }
227
- ])
228
- })
229
-
230
- it('测试连接业务失败(ok:false)时展示可读错误提示', async () => {
231
- const workbook = new Workbook()
232
- const { el, state } = mountDesigner(workbook)
233
- state.testOutcome = {
234
- ok: false,
235
- error: { code: 'CONNECTION_FAILED', message: '数据库连接失败' }
236
- }
237
- await flush()
238
-
239
- await click(el.querySelector<HTMLButtonElement>('.u-report-designer__toolbar button')!)
240
- await click(findButton('新建连接')!)
241
- await click(findButton('测试连接')!)
242
-
243
- const banner = document.querySelector('.u-report-hub-connection-form__test')
244
- expect(banner?.textContent).toContain('数据库连接失败')
245
- expect(banner?.classList.contains('is-ok')).toBe(false)
246
- })
247
- })
248
-
249
- describe('UReportDesigner 全量:template 载入与预览模式(内嵌查看器路径)', () => {
250
- const TEMPLATE_CONNECTION: DataConnection = {
251
- id: 'conn-template',
252
- label: '模板连接',
253
- type: 'mysql',
254
- host: '127.0.0.1',
255
- port: 3306,
256
- database: 'demo',
257
- username: 'root',
258
- password: ''
259
- }
260
-
261
- function buildTemplate(): ReportTemplate {
262
- const group: ReportBinding = {
263
- dataset: 'ds-template',
264
- field: 'customer',
265
- preset: 'groupHeader',
266
- aggregate: 'group',
267
- expand: 'down',
268
- sort: 'none',
269
- conditionalRules: []
270
- }
271
- return {
272
- version: 1,
273
- cells: [{ row: 0, col: 0, v: '客户' }],
274
- styles: [],
275
- merges: [],
276
- frozen: { rows: 0, cols: 0 },
277
- rows: 24,
278
- cols: 10,
279
- meta: [{ row: 1, col: 0, namespace: REPORT_META_NAMESPACE, payload: group }],
280
- datasets: [
281
- {
282
- id: 'ds-template',
283
- label: '订单',
284
- connection: TEMPLATE_CONNECTION,
285
- sql: 'SELECT customer, amount FROM orders'
286
- }
287
- ]
288
- }
289
- }
290
-
291
- function reportMetas(workbook: Workbook) {
292
- return [...workbook.activeSheet.entriesCellMeta()].filter(
293
- ([, namespace]) => namespace === REPORT_META_NAMESPACE
294
- )
295
- }
296
-
297
- it('template prop 载入恢复绑定与连接;预览切换经内嵌查看器取数展开;切回设计态绑定不丢', async () => {
298
- const workbook = new Workbook()
299
- const { el, connections, exposedRef, calls } = mountDesigner(workbook, {
300
- template: buildTemplate()
301
- })
302
- await flush()
303
-
304
- // 模板载入:绑定恢复 + 内嵌连接合并进 v-model 列表
305
- expect(reportMetas(workbook)).toHaveLength(1)
306
- expect(connections.value.map((item) => item.id)).toEqual(['conn-template'])
307
-
308
- // 切预览:内嵌查看器按模板内嵌数据集取数(含 Filter Bar 参数默认值)
309
- await click(findButton('预览模式')!)
310
- expect(el.querySelector('.u-report-viewer')).toBeTruthy()
311
- expect(calls.query).toHaveLength(1)
312
- expect(calls.query[0]!.connection.id).toBe('conn-template')
313
- expect(calls.query[0]!.sql).toBe('SELECT customer, amount FROM orders')
314
- // 取数成功:无业务错误 banner;设计格线已卸载
315
- expect(el.querySelector('.u-report-viewer__error')).toBeFalsy()
316
- expect(el.querySelector('.u-report-designer__grid')).toBeFalsy()
317
-
318
- // 切回设计态:绑定不丢,可继续设计
319
- await click(findButton('设计模式')!)
320
- expect(el.querySelector('.u-report-viewer')).toBeFalsy()
321
- expect(el.querySelector('.u-report-designer__grid')).toBeTruthy()
322
- expect(reportMetas(workbook)).toHaveLength(1)
323
-
324
- // getTemplate 往返:绑定与内嵌数据集定义完整
325
- const roundTrip = exposedRef.value!.getTemplate()
326
- expect(roundTrip.meta).toHaveLength(1)
327
- expect(roundTrip.datasets).toHaveLength(1)
328
- expect(roundTrip.datasets![0]).toMatchObject({ id: 'ds-template', label: '订单' })
329
- expect(roundTrip.datasets![0]!.connection.id).toBe('conn-template')
330
- })
331
-
332
- it('预览态导出 XLSX 按钮可见且转调查看器 exportXlsx()', async () => {
333
- const workbook = new Workbook()
334
- const { el } = mountDesigner(workbook, { template: buildTemplate() })
335
- await flush()
336
-
337
- // 设计态无导出按钮
338
- expect(findButton('导出 XLSX')).toBeUndefined()
339
-
340
- await click(findButton('预览模式')!)
341
- const exportButton = findButton('导出 XLSX')
342
- expect(exportButton).toBeTruthy()
343
- await flush()
344
-
345
- const exportModule = await import('../../../report/export-xlsx')
346
- const catKitFe = await import('@cat-kit/fe')
347
- const exportSpy = vi
348
- .spyOn(exportModule, 'exportFilledReportXlsx')
349
- .mockResolvedValue(new Uint8Array([80, 75, 3, 4]))
350
- const saveBlobSpy = vi.spyOn(catKitFe, 'saveBlob').mockImplementation(() => {})
351
-
352
- await click(exportButton!)
353
- expect(exportSpy).toHaveBeenCalled()
354
- expect(saveBlobSpy).toHaveBeenCalled()
355
- expect(el.querySelector('.u-report-viewer')).toBeTruthy()
356
-
357
- exportSpy.mockRestore()
358
- saveBlobSpy.mockRestore()
359
- })
360
-
361
- it('预览模式向内嵌查看器转发 resolveTemplate,预览内可下钻', async () => {
362
- const workbook = new Workbook()
363
- const template = buildTemplate()
364
- ;(template.meta![0]!.payload as ReportBinding).drill = {
365
- target: 'tpl-preview-detail',
366
- mapping: { customer: 'keyword' },
367
- openMode: 'switch'
368
- }
369
- const resolveTemplate = vi.fn(() => Promise.resolve(template))
370
- const { el } = mountDesigner(workbook, { template, resolveTemplate })
371
- await flush()
372
-
373
- await click(findButton('预览模式')!)
374
- expect(el.querySelector('.u-report-viewer')).toBeTruthy()
375
-
376
- const hitSpy = vi
377
- .spyOn(SheetGrid.prototype, 'hitTestSheetAddr')
378
- .mockReturnValue({ row: 1, col: 0 })
379
- const gridEl = el.querySelector('.u-sheet__grid-instance')!
380
- gridEl.dispatchEvent(
381
- new PointerEvent('pointerdown', { bubbles: true, clientX: 10, clientY: 10 })
382
- )
383
- gridEl.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: 10, clientY: 10 }))
384
- await flush()
385
- await flush()
386
-
387
- expect(resolveTemplate).toHaveBeenCalledWith('tpl-preview-detail')
388
- expect(el.querySelector('.u-report-viewer__drill-bar')).toBeTruthy()
389
- hitSpy.mockRestore()
390
- })
391
-
392
- it('字段拖拽 dragover 时落点高亮随 hit-test 更新;落空时提示回退当前选区', async () => {
393
- const workbook = new Workbook()
394
- workbook.activeSheet.selectCell({ row: 2, col: 1 })
395
- const { el } = mountDesigner(workbook)
396
- await flush()
397
-
398
- const gridHost = el.querySelector<HTMLElement>('.u-report-designer__grid')!
399
- const overlayRect = {
400
- left: 10,
401
- top: 20,
402
- right: 90,
403
- bottom: 44,
404
- width: 80,
405
- height: 24,
406
- centerX: 50,
407
- centerY: 32
408
- }
409
- const hitTestSpy = vi
410
- .spyOn(cellCoords, 'resolveGridDropAddress')
411
- .mockReturnValueOnce({ row: 1, col: 2 })
412
- .mockReturnValue(null)
413
- const overlaySpy = vi.spyOn(gridOverlay, 'readCellOverlayRect').mockReturnValue(overlayRect)
414
-
415
- dispatchFieldDragOver(gridHost, 120, 140)
416
- await flush()
417
- expect(el.querySelector('.u-report-drop-highlight')).toBeTruthy()
418
- expect(el.querySelector('.u-report-drop-highlight__fallback')).toBeNull()
419
-
420
- dispatchFieldDragOver(gridHost, 99999, 99999)
421
- await flush()
422
- expect(el.querySelector('.u-report-drop-highlight')).toBeNull()
423
- const fallback = el.querySelector('.u-report-drop-highlight__fallback')
424
- expect(fallback).toBeTruthy()
425
- expect(fallback!.textContent).toContain('B3')
426
-
427
- hitTestSpy.mockRestore()
428
- overlaySpy.mockRestore()
429
- })
430
- })
431
-
432
- describe('UReportDesigner Action Pill', () => {
433
- const overlayRect = {
434
- left: 10,
435
- top: 80,
436
- right: 90,
437
- bottom: 104,
438
- width: 80,
439
- height: 24,
440
- centerX: 50,
441
- centerY: 92
442
- }
443
-
444
- function spyOverlay() {
445
- const layoutSpy = vi
446
- .spyOn(cellCoords, 'resolveGridOverlayLayout')
447
- .mockReturnValue({
448
- gridEl: document.createElement('div'),
449
- layout: { offsetX: 0, offsetY: 77, viewW: 640, viewH: 400 }
450
- })
451
- const overlaySpy = vi.spyOn(gridOverlay, 'readCellOverlayRect').mockReturnValue(overlayRect)
452
- return () => {
453
- layoutSpy.mockRestore()
454
- overlaySpy.mockRestore()
455
- }
456
- }
457
-
458
- it('小计预设默认条显示聚合、隐藏展开方向,并提供删除绑定', async () => {
459
- const workbook = new Workbook()
460
- workbook.activeSheet.setCellMeta({ row: 0, col: 0 }, REPORT_META_NAMESPACE, {
461
- dataset: 'ds',
462
- field: 'amount',
463
- aggregate: 'sum',
464
- expand: 'none',
465
- preset: 'subtotal'
466
- } satisfies ReportBinding)
467
- workbook.activeSheet.selectCell({ row: 0, col: 0 })
468
-
469
- const restore = spyOverlay()
470
- const { el } = mountDesigner(workbook)
471
- await flush()
472
-
473
- const panel = el.querySelector('.u-report-float-panel')
474
- expect(panel).toBeTruthy()
475
- expect(panel!.querySelector('.u-report-float-panel__body')).toBeNull()
476
- expect(panel!.querySelector('.u-report-float-panel__select--aggregate')).toBeTruthy()
477
- expect(panel!.querySelector('.u-report-float-panel__select--expand')).toBeNull()
478
- const deleteBtn = [...panel!.querySelectorAll('button')].find((button) =>
479
- button.textContent?.includes('删除绑定')
480
- )
481
- expect(deleteBtn).toBeTruthy()
482
- restore()
483
- })
484
-
485
- it('明细预设默认条显示展开方向、隐藏聚合', async () => {
486
- const workbook = new Workbook()
487
- workbook.activeSheet.setCellMeta({ row: 0, col: 0 }, REPORT_META_NAMESPACE, {
488
- dataset: 'ds',
489
- field: 'orderNo',
490
- aggregate: 'list',
491
- expand: 'down',
492
- preset: 'detail'
493
- } satisfies ReportBinding)
494
- workbook.activeSheet.selectCell({ row: 0, col: 0 })
495
-
496
- const restore = spyOverlay()
497
- const { el } = mountDesigner(workbook)
498
- await flush()
499
-
500
- const panel = el.querySelector('.u-report-float-panel')
501
- expect(panel).toBeTruthy()
502
- expect(panel!.querySelector('.u-report-float-panel__select--expand')).toBeTruthy()
503
- expect(panel!.querySelector('.u-report-float-panel__select--aggregate')).toBeNull()
504
- restore()
505
- })
506
-
507
- function seedBindingCell(workbook: Workbook): void {
508
- workbook.activeSheet.setCellMeta({ row: 0, col: 0 }, REPORT_META_NAMESPACE, {
509
- dataset: 'ds',
510
- field: 'customer',
511
- aggregate: 'group',
512
- expand: 'down',
513
- preset: 'groupHeader'
514
- } satisfies ReportBinding)
515
- workbook.activeSheet.selectCell({ row: 0, col: 0 })
516
- }
517
-
518
- function findPanelButton(panel: Element, text: string): HTMLButtonElement | undefined {
519
- return [...panel.querySelectorAll<HTMLButtonElement>('button')].find((button) =>
520
- button.textContent?.includes(text)
521
- )
522
- }
523
-
524
- it('宿主传入模板列表时绑定格 Action Pill 出现下钻入口,点击打开下钻配置对话框', async () => {
525
- const workbook = new Workbook()
526
- seedBindingCell(workbook)
527
-
528
- const restore = spyOverlay()
529
- const { el } = mountDesigner(workbook, {
530
- drillTemplates: [{ ref: 'tpl-detail', label: '订单详情' }]
531
- })
532
- await flush()
533
-
534
- const panel = el.querySelector('.u-report-float-panel')!
535
- expect(panel).toBeTruthy()
536
- const drillButton = findPanelButton(panel, '下钻')
537
- expect(drillButton).toBeTruthy()
538
-
539
- await click(drillButton!)
540
- const dialog = document.querySelector('.u-report-drill-dialog')
541
- expect(dialog).toBeTruthy()
542
- expect(dialog!.textContent).toContain('目标模板')
543
- restore()
544
- })
545
-
546
- it('宿主不传模板列表时下钻配置入口不出现', async () => {
547
- const workbook = new Workbook()
548
- seedBindingCell(workbook)
549
-
550
- const restore = spyOverlay()
551
- const { el } = mountDesigner(workbook)
552
- await flush()
553
-
554
- const panel = el.querySelector('.u-report-float-panel')!
555
- expect(panel).toBeTruthy()
556
- expect(findPanelButton(panel, '下钻')).toBeUndefined()
557
- restore()
558
- })
559
- })