@veltra/sheet 2.4.0 → 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.
Files changed (142) hide show
  1. package/dist/index.d.ts +2 -19
  2. package/dist/index.js +2 -15
  3. package/dist/types/index.d.ts +1 -3
  4. package/package.json +2 -3
  5. package/src/index.ts +1 -27
  6. package/src/types/index.ts +0 -2
  7. package/dist/components/report/binding-badge.js +0 -118
  8. package/dist/components/report/designer/cell-coords.js +0 -46
  9. package/dist/components/report/designer/col-widths.js +0 -29
  10. package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
  11. package/dist/components/report/designer/drill-dialog.js +0 -258
  12. package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
  13. package/dist/components/report/designer/float-panel.js +0 -434
  14. package/dist/components/report/designer/role.js +0 -17
  15. package/dist/components/report/designer/rule-preview.js +0 -33
  16. package/dist/components/report/designer/rule-row.js +0 -253
  17. package/dist/components/report/designer/rules-dialog.js +0 -118
  18. package/dist/components/report/designer/topology-overlay.js +0 -122
  19. package/dist/components/report/designer/topology.js +0 -64
  20. package/dist/components/report/designer/use-grid-overlay.js +0 -75
  21. package/dist/components/report/designer-hub.js +0 -177
  22. package/dist/components/report/field-panel-helpers.js +0 -33
  23. package/dist/components/report/field-panel.js +0 -132
  24. package/dist/components/report/filter-bar.js +0 -74
  25. package/dist/components/report/hub-connection-form.js +0 -145
  26. package/dist/components/report/hub-dataset-editor.js +0 -409
  27. package/dist/components/report/preview-grid-size.js +0 -32
  28. package/dist/components/report/print.js +0 -39
  29. package/dist/components/report/report-designer.js +0 -428
  30. package/dist/components/report/report-viewer.js +0 -253
  31. package/dist/components/report/style.d.ts +0 -17
  32. package/dist/components/report/style.js +0 -18
  33. package/dist/components/report/style2.css +0 -965
  34. package/dist/components/report/use-report-designer.js +0 -387
  35. package/dist/components/report/use-report-viewer.js +0 -140
  36. package/dist/report/binding.d.ts +0 -83
  37. package/dist/report/binding.js +0 -334
  38. package/dist/report/connector.d.ts +0 -66
  39. package/dist/report/connector.js +0 -159
  40. package/dist/report/drill.d.ts +0 -48
  41. package/dist/report/drill.js +0 -78
  42. package/dist/report/export-xlsx.d.ts +0 -13
  43. package/dist/report/export-xlsx.js +0 -13
  44. package/dist/report/filter-bar.d.ts +0 -10
  45. package/dist/report/filter-bar.js +0 -31
  46. package/dist/report/index.d.ts +0 -14
  47. package/dist/report/params.d.ts +0 -13
  48. package/dist/report/params.js +0 -61
  49. package/dist/report/print.d.ts +0 -23
  50. package/dist/report/print.js +0 -197
  51. package/dist/report/render/aggregate.js +0 -193
  52. package/dist/report/render/builder.d.ts +0 -12
  53. package/dist/report/render/builder.js +0 -139
  54. package/dist/report/render/coordinate.d.ts +0 -25
  55. package/dist/report/render/coordinate.js +0 -733
  56. package/dist/report/render/index.d.ts +0 -14
  57. package/dist/report/render/index.js +0 -21
  58. package/dist/report/render/style-resolver.js +0 -50
  59. package/dist/report/render/template-index.d.ts +0 -24
  60. package/dist/report/render/template-index.js +0 -137
  61. package/dist/report/rules/evaluate.d.ts +0 -25
  62. package/dist/report/rules/evaluate.js +0 -82
  63. package/dist/report/rules/index.d.ts +0 -2
  64. package/dist/report/template.d.ts +0 -63
  65. package/dist/report/template.js +0 -83
  66. package/dist/report/types.d.ts +0 -103
  67. package/dist/types/report-designer.d.ts +0 -51
  68. package/dist/types/report-viewer.d.ts +0 -57
  69. package/src/components/report/__test__/binding-badge.test.ts +0 -92
  70. package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
  71. package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
  72. package/src/components/report/__test__/float-panel-position.test.ts +0 -45
  73. package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
  74. package/src/components/report/__test__/report-designer.test.ts +0 -559
  75. package/src/components/report/__test__/report-drill.test.ts +0 -692
  76. package/src/components/report/__test__/report-viewer.test.ts +0 -400
  77. package/src/components/report/__test__/topology.test.ts +0 -143
  78. package/src/components/report/__test__/use-report-designer.test.ts +0 -804
  79. package/src/components/report/binding-badge.ts +0 -122
  80. package/src/components/report/designer/cell-coords.ts +0 -95
  81. package/src/components/report/designer/col-widths.ts +0 -56
  82. package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
  83. package/src/components/report/designer/drill-dialog.vue +0 -245
  84. package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
  85. package/src/components/report/designer/float-panel.vue +0 -444
  86. package/src/components/report/designer/role.ts +0 -32
  87. package/src/components/report/designer/rule-preview.vue +0 -40
  88. package/src/components/report/designer/rule-row.vue +0 -252
  89. package/src/components/report/designer/rules-dialog.vue +0 -110
  90. package/src/components/report/designer/topology-overlay.vue +0 -128
  91. package/src/components/report/designer/topology.ts +0 -124
  92. package/src/components/report/designer/use-grid-overlay.ts +0 -114
  93. package/src/components/report/designer-hub.vue +0 -198
  94. package/src/components/report/field-panel-helpers.ts +0 -44
  95. package/src/components/report/field-panel.vue +0 -136
  96. package/src/components/report/filter-bar.vue +0 -77
  97. package/src/components/report/hub-connection-form.vue +0 -124
  98. package/src/components/report/hub-dataset-editor.vue +0 -387
  99. package/src/components/report/index.ts +0 -2
  100. package/src/components/report/preview-grid-size.ts +0 -33
  101. package/src/components/report/print.ts +0 -44
  102. package/src/components/report/report-designer.vue +0 -454
  103. package/src/components/report/report-viewer.vue +0 -289
  104. package/src/components/report/style-designer.scss +0 -653
  105. package/src/components/report/style-hub.scss +0 -292
  106. package/src/components/report/style-viewer.scss +0 -144
  107. package/src/components/report/style.scss +0 -3
  108. package/src/components/report/style.ts +0 -20
  109. package/src/components/report/use-report-designer.ts +0 -603
  110. package/src/components/report/use-report-viewer.ts +0 -227
  111. package/src/report/__test__/binding.test.ts +0 -320
  112. package/src/report/__test__/connector.test.ts +0 -239
  113. package/src/report/__test__/drill.test.ts +0 -194
  114. package/src/report/__test__/export-xlsx.test.ts +0 -96
  115. package/src/report/__test__/filter-bar.test.ts +0 -58
  116. package/src/report/__test__/params.test.ts +0 -156
  117. package/src/report/__test__/print.test.ts +0 -170
  118. package/src/report/__test__/render.test.ts +0 -856
  119. package/src/report/__test__/rules.test.ts +0 -131
  120. package/src/report/__test__/template.test.ts +0 -366
  121. package/src/report/binding.ts +0 -474
  122. package/src/report/connector.ts +0 -206
  123. package/src/report/drill.ts +0 -112
  124. package/src/report/export-xlsx.ts +0 -15
  125. package/src/report/filter-bar.ts +0 -36
  126. package/src/report/index.ts +0 -16
  127. package/src/report/params.ts +0 -96
  128. package/src/report/print.ts +0 -234
  129. package/src/report/render/__test__/aggregate.test.ts +0 -16
  130. package/src/report/render/__test__/coordinate.test.ts +0 -405
  131. package/src/report/render/aggregate.ts +0 -289
  132. package/src/report/render/builder.ts +0 -211
  133. package/src/report/render/coordinate.ts +0 -1239
  134. package/src/report/render/index.ts +0 -30
  135. package/src/report/render/style-resolver.ts +0 -68
  136. package/src/report/render/template-index.ts +0 -200
  137. package/src/report/rules/evaluate.ts +0 -113
  138. package/src/report/rules/index.ts +0 -7
  139. package/src/report/template.ts +0 -145
  140. package/src/report/types.ts +0 -116
  141. package/src/types/report-designer.ts +0 -55
  142. 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
- })