@veltra/sheet 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/components/report/binding-badge.js +45 -21
  2. package/dist/components/report/designer/col-widths.js +18 -4
  3. package/dist/components/report/designer/conditional-rules/helpers.js +3 -3
  4. package/dist/components/report/designer/float-panel.js +73 -36
  5. package/dist/components/report/designer/rule-row.js +111 -127
  6. package/dist/components/report/designer/rules-dialog.js +5 -22
  7. package/dist/components/report/preview-grid-size.js +32 -0
  8. package/dist/components/report/report-designer.js +75 -25
  9. package/dist/components/report/report-viewer.js +38 -25
  10. package/dist/components/report/style2.css +44 -39
  11. package/dist/components/report/use-report-designer.js +34 -12
  12. package/dist/components/report/use-report-viewer.js +6 -1
  13. package/dist/components/sheet/popups/border-popup.js +30 -2
  14. package/dist/components/sheet/popups/fill-color-popup.js +2 -2
  15. package/dist/components/sheet/popups/font-color-popup.js +2 -2
  16. package/dist/components/sheet/popups/font-size-popup.js +2 -2
  17. package/dist/components/sheet/sheet-context-menu.js +6 -3
  18. package/dist/components/sheet/sheet.js +8 -0
  19. package/dist/components/sheet/use-sheet-grid.js +14 -4
  20. package/dist/components/sheet/use-sheet-state.js +3 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +2 -2
  23. package/dist/report/binding.d.ts +49 -11
  24. package/dist/report/binding.js +186 -29
  25. package/dist/report/connector.js +1 -1
  26. package/dist/report/export-xlsx.d.ts +3 -5
  27. package/dist/report/export-xlsx.js +7 -10
  28. package/dist/report/index.d.ts +2 -2
  29. package/dist/report/render/aggregate.js +20 -8
  30. package/dist/report/template.d.ts +8 -4
  31. package/dist/report/template.js +5 -2
  32. package/dist/tools/apply-style.js +29 -0
  33. package/dist/tools/builtin.js +17 -17
  34. package/dist/tools/context.d.ts +38 -5
  35. package/dist/tools/context.js +38 -1
  36. package/dist/types/report-designer.d.ts +2 -0
  37. package/dist/types/report-viewer.d.ts +2 -2
  38. package/dist/types/sheet.d.ts +4 -0
  39. package/package.json +6 -6
  40. package/src/components/report/__test__/conditional-rules-helpers.test.ts +1 -0
  41. package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
  42. package/src/components/report/__test__/report-designer.test.ts +76 -0
  43. package/src/components/report/__test__/report-viewer.test.ts +24 -1
  44. package/src/components/report/__test__/use-report-designer.test.ts +142 -10
  45. package/src/components/report/binding-badge.ts +39 -20
  46. package/src/components/report/designer/col-widths.ts +34 -11
  47. package/src/components/report/designer/conditional-rules/helpers.ts +6 -3
  48. package/src/components/report/designer/float-panel.vue +71 -27
  49. package/src/components/report/designer/rule-row.vue +67 -67
  50. package/src/components/report/designer/rules-dialog.vue +3 -19
  51. package/src/components/report/preview-grid-size.ts +33 -0
  52. package/src/components/report/report-designer.vue +79 -15
  53. package/src/components/report/report-viewer.vue +37 -29
  54. package/src/components/report/style-designer.scss +45 -41
  55. package/src/components/report/use-report-designer.ts +63 -14
  56. package/src/components/report/use-report-viewer.ts +11 -2
  57. package/src/components/sheet/__test__/sheet-component.test.ts +37 -0
  58. package/src/components/sheet/__test__/sheet-context-menu.test.ts +3 -2
  59. package/src/components/sheet/popups/border-popup.vue +31 -3
  60. package/src/components/sheet/popups/fill-color-popup.vue +2 -2
  61. package/src/components/sheet/popups/font-color-popup.vue +2 -2
  62. package/src/components/sheet/popups/font-size-popup.vue +2 -2
  63. package/src/components/sheet/sheet-context-menu.ts +8 -2
  64. package/src/components/sheet/sheet.vue +2 -0
  65. package/src/components/sheet/use-sheet-grid.ts +17 -3
  66. package/src/components/sheet/use-sheet-state.ts +8 -3
  67. package/src/report/__test__/binding.test.ts +103 -1
  68. package/src/report/__test__/connector.test.ts +21 -0
  69. package/src/report/__test__/export-xlsx.test.ts +4 -4
  70. package/src/report/__test__/render.test.ts +102 -0
  71. package/src/report/__test__/template.test.ts +11 -0
  72. package/src/report/binding.ts +278 -47
  73. package/src/report/connector.ts +3 -2
  74. package/src/report/export-xlsx.ts +9 -14
  75. package/src/report/render/aggregate.ts +29 -8
  76. package/src/report/template.ts +8 -4
  77. package/src/tools/__test__/apply-style.test.ts +129 -0
  78. package/src/tools/apply-style.ts +41 -0
  79. package/src/tools/builtin.ts +18 -18
  80. package/src/tools/context.ts +67 -4
  81. package/src/tools/index.ts +1 -0
  82. package/src/types/report-designer.ts +2 -0
  83. package/src/types/report-viewer.ts +2 -2
  84. package/src/types/sheet.ts +4 -0
@@ -6,11 +6,15 @@ import {
6
6
  createReportBinding,
7
7
  formatBindingPlaceholder,
8
8
  formatCellAddress,
9
+ inferColParentCandidate,
9
10
  inferDropPreset,
11
+ inferParentsForPreset,
10
12
  inferReportPreset,
13
+ inferRowParentCandidate,
11
14
  isExpansionBandRow,
12
15
  parseCellAddress,
13
16
  presetBindingPatch,
17
+ presetStillApplies,
14
18
  setBindingCatalog
15
19
  } from '../binding'
16
20
  import type { DatasetCatalogItem, ReportBinding } from '../types'
@@ -180,7 +184,32 @@ describe('report binding', () => {
180
184
  expand: 'none',
181
185
  rowParent: { row: 1, col: 0 }
182
186
  })
183
- ).toBeNull()
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)
184
213
  })
185
214
 
186
215
  it('inferDropPreset / isExpansionBandRow 落格推断', () => {
@@ -200,6 +229,79 @@ describe('report binding', () => {
200
229
  expect(inferDropPreset({ row: 2, col: 0 }, 'string', getBindingAt)).toBe('detail')
201
230
  })
202
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
+
203
305
  it('formatCellAddress / parseCellAddress 支持多字母列互逆', () => {
204
306
  const single = { row: 2, col: 1 }
205
307
  expect(formatCellAddress(single)).toBe('B3')
@@ -84,6 +84,27 @@ describe('createHttpConnector', () => {
84
84
  expect(calls[0]!.body).toEqual({ connection: CONNECTION, sql: SQL })
85
85
  })
86
86
 
87
+ it('describe:服务端返回 label 时保留中文显示名', async () => {
88
+ stubFetch(async () => ({
89
+ ok: true,
90
+ fields: [
91
+ { name: 'customer', label: '客户名称', type: 'string' },
92
+ { name: 'amount', label: '金额', type: 'number' }
93
+ ]
94
+ }))
95
+ const connector = createHttpConnector({ endpoint: ENDPOINT })
96
+
97
+ const result = await connector.describe(CONNECTION, SQL)
98
+
99
+ expect(result).toEqual({
100
+ ok: true,
101
+ data: [
102
+ { name: 'customer', label: '客户名称', type: 'string' },
103
+ { name: 'amount', label: '金额', type: 'number' }
104
+ ]
105
+ })
106
+ })
107
+
87
108
  it('query:POST {endpoint}/query,请求体 { connection, sql, values },返回 fields + rows', async () => {
88
109
  const calls = stubFetch(async () => ({
89
110
  ok: true,
@@ -60,10 +60,10 @@ function buildFilledSheet(): Sheet {
60
60
 
61
61
  describe('exportFilledReportXlsx', () => {
62
62
  it('导出保真 XLSX:值 / 表头样式 / 条件样式打平后随快照导出', async () => {
63
- const buffer = await exportFilledReportXlsx(buildFilledSheet(), [
64
- [0, 120],
65
- [1, 140]
66
- ])
63
+ const filled = buildFilledSheet()
64
+ filled.setColWidth(0, 120)
65
+ filled.setColWidth(1, 140)
66
+ const buffer = await exportFilledReportXlsx(filled)
67
67
 
68
68
  // 合法 ZIP(xlsx)字节
69
69
  expect(buffer[0]).toBe(0x50)
@@ -725,4 +725,106 @@ describe('renderReport', () => {
725
725
  ])
726
726
  )
727
727
  })
728
+
729
+ it('跨列行小计按组求和,不是全表合计', () => {
730
+ const sheet = new Sheet()
731
+ sheet.setCells([{ addr: { row: 0, col: 0 }, data: { v: '客户' } }])
732
+ const groupParent = { row: 1, col: 0 }
733
+
734
+ const customerGroup = createReportBinding(ORDERS_DATASET, 'customer')
735
+ customerGroup.preset = 'groupHeader'
736
+ customerGroup.aggregate = 'group'
737
+ customerGroup.expand = 'down'
738
+ sheet.setCellMeta(groupParent, REPORT_META_NAMESPACE, customerGroup)
739
+
740
+ const orderNo = createReportBinding(ORDERS_DATASET, 'orderNo')
741
+ orderNo.rowParent = groupParent
742
+ sheet.setCellMeta({ row: 1, col: 1 }, REPORT_META_NAMESPACE, orderNo)
743
+
744
+ const amount = createReportBinding(ORDERS_DATASET, 'amount')
745
+ amount.rowParent = groupParent
746
+ sheet.setCellMeta({ row: 1, col: 2 }, REPORT_META_NAMESPACE, amount)
747
+
748
+ const subtotal = createReportBinding(ORDERS_DATASET, 'amount')
749
+ subtotal.preset = 'subtotal'
750
+ subtotal.aggregate = 'sum'
751
+ subtotal.expand = 'none'
752
+ subtotal.rowParent = groupParent
753
+ sheet.setCellMeta({ row: 2, col: 2 }, REPORT_META_NAMESPACE, subtotal)
754
+
755
+ const filled = renderReport(sheet.snapshot(), MOCK_DATA_RECORDS)
756
+ expect(cellValue(filled, 5, 2)).toBe(630)
757
+ expect(cellValue(filled, 10, 2)).toBe(1050)
758
+ expect(cellValue(filled, 5, 2)).not.toBe(4180)
759
+ })
760
+
761
+ it('跨列行小计误挂明细时仍按分组祖先过滤', () => {
762
+ const sheet = new Sheet()
763
+ sheet.setCells([{ addr: { row: 0, col: 0 }, data: { v: '客户' } }])
764
+ const groupParent = { row: 1, col: 0 }
765
+ const listAddr = { row: 1, col: 2 }
766
+
767
+ const customerGroup = createReportBinding(ORDERS_DATASET, 'customer')
768
+ customerGroup.preset = 'groupHeader'
769
+ customerGroup.aggregate = 'group'
770
+ customerGroup.expand = 'down'
771
+ sheet.setCellMeta(groupParent, REPORT_META_NAMESPACE, customerGroup)
772
+
773
+ const amount = createReportBinding(ORDERS_DATASET, 'amount')
774
+ amount.rowParent = groupParent
775
+ sheet.setCellMeta(listAddr, REPORT_META_NAMESPACE, amount)
776
+
777
+ const subtotal = createReportBinding(ORDERS_DATASET, 'amount')
778
+ subtotal.preset = 'subtotal'
779
+ subtotal.aggregate = 'sum'
780
+ subtotal.expand = 'none'
781
+ subtotal.rowParent = listAddr
782
+ sheet.setCellMeta({ row: 2, col: 2 }, REPORT_META_NAMESPACE, subtotal)
783
+
784
+ const filled = renderReport(sheet.snapshot(), MOCK_DATA_RECORDS)
785
+ expect(cellValue(filled, 5, 2)).toBe(630)
786
+ expect(cellValue(filled, 10, 2)).toBe(1050)
787
+ })
788
+
789
+ it('交叉列小计仅 colParent 时按列分组求和,不是全表合计', () => {
790
+ const sheet = new Sheet()
791
+ const colGroupAddr = { row: 0, col: 1 }
792
+ const rowGroupAddr = { row: 1, col: 0 }
793
+
794
+ const categoryGroup = createReportBinding(SALES_MATRIX_DATASET, 'category')
795
+ categoryGroup.preset = 'groupHeader'
796
+ categoryGroup.aggregate = 'group'
797
+ categoryGroup.expand = 'right'
798
+ sheet.setCellMeta(colGroupAddr, REPORT_META_NAMESPACE, categoryGroup)
799
+
800
+ const regionGroup = createReportBinding(SALES_MATRIX_DATASET, 'region')
801
+ regionGroup.preset = 'groupHeader'
802
+ regionGroup.aggregate = 'group'
803
+ regionGroup.expand = 'down'
804
+ sheet.setCellMeta(rowGroupAddr, REPORT_META_NAMESPACE, regionGroup)
805
+
806
+ const cross = createReportBinding(SALES_MATRIX_DATASET, 'amount')
807
+ cross.preset = 'cross'
808
+ cross.aggregate = 'sum'
809
+ cross.expand = 'none'
810
+ cross.rowParent = rowGroupAddr
811
+ cross.colParent = colGroupAddr
812
+ sheet.setCellMeta({ row: 1, col: 1 }, REPORT_META_NAMESPACE, cross)
813
+
814
+ const colSubtotal = createReportBinding(SALES_MATRIX_DATASET, 'amount')
815
+ colSubtotal.preset = 'subtotal'
816
+ colSubtotal.aggregate = 'sum'
817
+ colSubtotal.expand = 'none'
818
+ colSubtotal.colParent = colGroupAddr
819
+ sheet.setCellMeta({ row: 2, col: 1 }, REPORT_META_NAMESPACE, colSubtotal)
820
+
821
+ const filled = renderReport(sheet.snapshot(), { 'sales-matrix': SALES_MATRIX_ROWS })
822
+ const officeTotal = SALES_MATRIX_ROWS.filter((row) => row.category === '办公设备').reduce(
823
+ (sum, row) => sum + (row.amount as number),
824
+ 0
825
+ )
826
+ const grandTotal = SALES_MATRIX_ROWS.reduce((sum, row) => sum + (row.amount as number), 0)
827
+ expect(cellValue(filled, 5, 1)).toBe(officeTotal)
828
+ expect(cellValue(filled, 5, 1)).not.toBe(grandTotal)
829
+ })
728
830
  })
@@ -105,6 +105,17 @@ describe('ReportTemplate version', () => {
105
105
  expect(template.version).toBe(REPORT_TEMPLATE_VERSION)
106
106
  })
107
107
 
108
+ it('createReportTemplate 继承 snapshot.colWidths', () => {
109
+ const sheet = new Sheet()
110
+ sheet.setColWidth(0, 120)
111
+ sheet.setColWidth(2, 90)
112
+ const template = createReportTemplate(sheet.snapshot(), [ORDERS_DATASET])
113
+ expect(template.colWidths).toEqual([
114
+ [0, 120],
115
+ [2, 90]
116
+ ])
117
+ })
118
+
108
119
  it('version 缺失时 assertCompatibleTemplateVersion 抛可读错误', () => {
109
120
  const sheet = new Sheet()
110
121
  const template = sheet.snapshot() as ReportTemplate