@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.
- package/dist/components/report/binding-badge.js +45 -21
- package/dist/components/report/designer/col-widths.js +18 -4
- package/dist/components/report/designer/conditional-rules/helpers.js +3 -3
- package/dist/components/report/designer/float-panel.js +73 -36
- package/dist/components/report/designer/rule-row.js +111 -127
- package/dist/components/report/designer/rules-dialog.js +5 -22
- package/dist/components/report/preview-grid-size.js +32 -0
- package/dist/components/report/report-designer.js +75 -25
- package/dist/components/report/report-viewer.js +38 -25
- package/dist/components/report/style2.css +44 -39
- package/dist/components/report/use-report-designer.js +34 -12
- package/dist/components/report/use-report-viewer.js +6 -1
- package/dist/components/sheet/popups/border-popup.js +30 -2
- package/dist/components/sheet/popups/fill-color-popup.js +2 -2
- package/dist/components/sheet/popups/font-color-popup.js +2 -2
- package/dist/components/sheet/popups/font-size-popup.js +2 -2
- package/dist/components/sheet/sheet-context-menu.js +6 -3
- package/dist/components/sheet/sheet.js +8 -0
- package/dist/components/sheet/use-sheet-grid.js +14 -4
- package/dist/components/sheet/use-sheet-state.js +3 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/report/binding.d.ts +49 -11
- package/dist/report/binding.js +186 -29
- package/dist/report/connector.js +1 -1
- package/dist/report/export-xlsx.d.ts +3 -5
- package/dist/report/export-xlsx.js +7 -10
- package/dist/report/index.d.ts +2 -2
- package/dist/report/render/aggregate.js +20 -8
- package/dist/report/template.d.ts +8 -4
- package/dist/report/template.js +5 -2
- package/dist/tools/apply-style.js +29 -0
- package/dist/tools/builtin.js +17 -17
- package/dist/tools/context.d.ts +38 -5
- package/dist/tools/context.js +38 -1
- package/dist/types/report-designer.d.ts +2 -0
- package/dist/types/report-viewer.d.ts +2 -2
- package/dist/types/sheet.d.ts +4 -0
- package/package.json +6 -6
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +1 -0
- package/src/components/report/__test__/preview-grid-size.test.ts +63 -0
- package/src/components/report/__test__/report-designer.test.ts +76 -0
- package/src/components/report/__test__/report-viewer.test.ts +24 -1
- package/src/components/report/__test__/use-report-designer.test.ts +142 -10
- package/src/components/report/binding-badge.ts +39 -20
- package/src/components/report/designer/col-widths.ts +34 -11
- package/src/components/report/designer/conditional-rules/helpers.ts +6 -3
- package/src/components/report/designer/float-panel.vue +71 -27
- package/src/components/report/designer/rule-row.vue +67 -67
- package/src/components/report/designer/rules-dialog.vue +3 -19
- package/src/components/report/preview-grid-size.ts +33 -0
- package/src/components/report/report-designer.vue +79 -15
- package/src/components/report/report-viewer.vue +37 -29
- package/src/components/report/style-designer.scss +45 -41
- package/src/components/report/use-report-designer.ts +63 -14
- package/src/components/report/use-report-viewer.ts +11 -2
- package/src/components/sheet/__test__/sheet-component.test.ts +37 -0
- package/src/components/sheet/__test__/sheet-context-menu.test.ts +3 -2
- package/src/components/sheet/popups/border-popup.vue +31 -3
- package/src/components/sheet/popups/fill-color-popup.vue +2 -2
- package/src/components/sheet/popups/font-color-popup.vue +2 -2
- package/src/components/sheet/popups/font-size-popup.vue +2 -2
- package/src/components/sheet/sheet-context-menu.ts +8 -2
- package/src/components/sheet/sheet.vue +2 -0
- package/src/components/sheet/use-sheet-grid.ts +17 -3
- package/src/components/sheet/use-sheet-state.ts +8 -3
- package/src/report/__test__/binding.test.ts +103 -1
- package/src/report/__test__/connector.test.ts +21 -0
- package/src/report/__test__/export-xlsx.test.ts +4 -4
- package/src/report/__test__/render.test.ts +102 -0
- package/src/report/__test__/template.test.ts +11 -0
- package/src/report/binding.ts +278 -47
- package/src/report/connector.ts +3 -2
- package/src/report/export-xlsx.ts +9 -14
- package/src/report/render/aggregate.ts +29 -8
- package/src/report/template.ts +8 -4
- package/src/tools/__test__/apply-style.test.ts +129 -0
- package/src/tools/apply-style.ts +41 -0
- package/src/tools/builtin.ts +18 -18
- package/src/tools/context.ts +67 -4
- package/src/tools/index.ts +1 -0
- package/src/types/report-designer.ts +2 -0
- package/src/types/report-viewer.ts +2 -2
- package/src/types/sheet.ts +4 -0
|
@@ -35,6 +35,7 @@ const PG: DataConnection = {
|
|
|
35
35
|
|
|
36
36
|
const ORDER_FIELDS: DatasetField[] = [
|
|
37
37
|
{ name: 'customer', label: '客户', type: 'string' },
|
|
38
|
+
{ name: 'orderNo', label: '订单号', type: 'string' },
|
|
38
39
|
{ name: 'amount', label: '金额', type: 'number' }
|
|
39
40
|
]
|
|
40
41
|
|
|
@@ -94,6 +95,18 @@ function createDesigner(
|
|
|
94
95
|
return { scope, designer, connections, workbook, calls }
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function bindGroupHeader(
|
|
99
|
+
designer: UseReportDesignerReturn,
|
|
100
|
+
workbook: Workbook,
|
|
101
|
+
datasetId: string,
|
|
102
|
+
addr: { row: number; col: number },
|
|
103
|
+
field = 'customer'
|
|
104
|
+
): void {
|
|
105
|
+
designer.bindField(datasetId, field, addr)
|
|
106
|
+
workbook.activeSheet.selectCell(addr)
|
|
107
|
+
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
describe('useReportDesigner:连接 / 数据集 CRUD 与连接器调用', () => {
|
|
98
111
|
it('连接 CRUD 全走 v-model 代理;删除连接级联删除其数据集', () => {
|
|
99
112
|
const { designer, connections } = createDesigner()
|
|
@@ -132,7 +145,7 @@ describe('useReportDesigner:连接 / 数据集 CRUD 与连接器调用', () =>
|
|
|
132
145
|
|
|
133
146
|
const catalog = designer.catalog.value
|
|
134
147
|
expect(catalog).toHaveLength(1)
|
|
135
|
-
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '金额'])
|
|
148
|
+
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '订单号', '金额'])
|
|
136
149
|
})
|
|
137
150
|
|
|
138
151
|
it('describeDataset 业务错误整体透传且不写字段缓存', async () => {
|
|
@@ -220,7 +233,7 @@ describe('useReportDesigner:拖拽落格写 Cell Meta', () => {
|
|
|
220
233
|
const inventory = await seedDataset(designer, 'c2')
|
|
221
234
|
|
|
222
235
|
designer.bindField(orders.id, 'customer', { row: 1, col: 0 })
|
|
223
|
-
designer.bindField(inventory.id, '
|
|
236
|
+
designer.bindField(inventory.id, 'orderNo', { row: 2, col: 0 })
|
|
224
237
|
|
|
225
238
|
const detail = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
226
239
|
{ row: 2, col: 0 },
|
|
@@ -228,7 +241,7 @@ describe('useReportDesigner:拖拽落格写 Cell Meta', () => {
|
|
|
228
241
|
)
|
|
229
242
|
expect(detail).toMatchObject({
|
|
230
243
|
dataset: inventory.id,
|
|
231
|
-
field: '
|
|
244
|
+
field: 'orderNo',
|
|
232
245
|
rowParent: { row: 1, col: 0 }
|
|
233
246
|
})
|
|
234
247
|
})
|
|
@@ -237,7 +250,7 @@ describe('useReportDesigner:拖拽落格写 Cell Meta', () => {
|
|
|
237
250
|
const { designer, workbook } = createDesigner()
|
|
238
251
|
const dataset = await seedDataset(designer)
|
|
239
252
|
|
|
240
|
-
designer
|
|
253
|
+
bindGroupHeader(designer, workbook, dataset.id, { row: 1, col: 0 })
|
|
241
254
|
designer.bindField(dataset.id, 'amount', { row: 2, col: 0 })
|
|
242
255
|
|
|
243
256
|
const subtotal = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
@@ -252,6 +265,66 @@ describe('useReportDesigner:拖拽落格写 Cell Meta', () => {
|
|
|
252
265
|
})
|
|
253
266
|
})
|
|
254
267
|
|
|
268
|
+
it('同行明细落格把 rowParent 推到左侧分组头', async () => {
|
|
269
|
+
const { designer, workbook } = createDesigner()
|
|
270
|
+
const dataset = await seedDataset(designer)
|
|
271
|
+
|
|
272
|
+
bindGroupHeader(designer, workbook, dataset.id, { row: 1, col: 0 })
|
|
273
|
+
designer.bindField(dataset.id, 'orderNo', { row: 1, col: 2 })
|
|
274
|
+
|
|
275
|
+
const detail = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
276
|
+
{ row: 1, col: 2 },
|
|
277
|
+
REPORT_META_NAMESPACE
|
|
278
|
+
)
|
|
279
|
+
expect(detail).toMatchObject({
|
|
280
|
+
field: 'orderNo',
|
|
281
|
+
preset: 'detail',
|
|
282
|
+
rowParent: { row: 1, col: 0 }
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('下方跨列小计落格推到行分组头而不是同列 list', async () => {
|
|
287
|
+
const { designer, workbook } = createDesigner()
|
|
288
|
+
const dataset = await seedDataset(designer)
|
|
289
|
+
|
|
290
|
+
bindGroupHeader(designer, workbook, dataset.id, { row: 1, col: 0 })
|
|
291
|
+
designer.bindField(dataset.id, 'orderNo', { row: 1, col: 2 })
|
|
292
|
+
designer.bindField(dataset.id, 'amount', { row: 2, col: 2 })
|
|
293
|
+
|
|
294
|
+
const subtotal = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
295
|
+
{ row: 2, col: 2 },
|
|
296
|
+
REPORT_META_NAMESPACE
|
|
297
|
+
)
|
|
298
|
+
expect(subtotal).toMatchObject({
|
|
299
|
+
preset: 'subtotal',
|
|
300
|
+
aggregate: 'sum',
|
|
301
|
+
expand: 'none',
|
|
302
|
+
rowParent: { row: 1, col: 0 }
|
|
303
|
+
})
|
|
304
|
+
expect(subtotal?.colParent).toBeUndefined()
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('右侧列小计落格推到列分组头', async () => {
|
|
308
|
+
const { designer, workbook } = createDesigner()
|
|
309
|
+
const dataset = await seedDataset(designer)
|
|
310
|
+
|
|
311
|
+
designer.bindField(dataset.id, 'customer', { row: 0, col: 1 })
|
|
312
|
+
workbook.activeSheet.selectCell({ row: 0, col: 1 })
|
|
313
|
+
designer.patchActiveBinding({ preset: 'groupHeader', aggregate: 'group', expand: 'right' })
|
|
314
|
+
designer.bindField(dataset.id, 'amount', { row: 0, col: 2 })
|
|
315
|
+
|
|
316
|
+
const subtotal = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
317
|
+
{ row: 0, col: 2 },
|
|
318
|
+
REPORT_META_NAMESPACE
|
|
319
|
+
)
|
|
320
|
+
expect(subtotal).toMatchObject({
|
|
321
|
+
preset: 'subtotal',
|
|
322
|
+
aggregate: 'sum',
|
|
323
|
+
expand: 'none',
|
|
324
|
+
colParent: { row: 0, col: 1 }
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
255
328
|
it('未给落点地址时回退到当前选区', async () => {
|
|
256
329
|
const { designer, workbook } = createDesigner()
|
|
257
330
|
const dataset = await seedDataset(designer)
|
|
@@ -282,7 +355,7 @@ describe('useReportDesigner:角色徽章渲染 hook(ADR-0004 首个消费者
|
|
|
282
355
|
expect(layout!.rootContainer.attribute.fill).toBeUndefined()
|
|
283
356
|
|
|
284
357
|
const style = designer.resolveCellStyle({ row: 0, col: 0 }, undefined)
|
|
285
|
-
expect(style?.fill).toEqual({ color: '#
|
|
358
|
+
expect(style?.fill).toEqual({ color: '#effaf3' })
|
|
286
359
|
|
|
287
360
|
// 模型与快照无 renderer 残留
|
|
288
361
|
expect(workbook.activeSheet.getCellData({ row: 0, col: 0 })).toBeUndefined()
|
|
@@ -347,7 +420,7 @@ describe('useReportDesigner:Action Pill 就地编辑(角色 / 聚合 / 条
|
|
|
347
420
|
return dataset
|
|
348
421
|
}
|
|
349
422
|
|
|
350
|
-
it('预设切换写入 presetBindingDefaults
|
|
423
|
+
it('预设切换写入 presetBindingDefaults 组合值;同族聚合切换保留预设', async () => {
|
|
351
424
|
const { designer, workbook } = createDesigner()
|
|
352
425
|
await seedAndBind(designer, { row: 0, col: 0 })
|
|
353
426
|
workbook.activeSheet.selectCell({ row: 0, col: 0 })
|
|
@@ -358,8 +431,17 @@ describe('useReportDesigner:Action Pill 就地编辑(角色 / 聚合 / 条
|
|
|
358
431
|
|
|
359
432
|
designer.patchActiveBinding({ aggregate: 'avg', expand: 'none' })
|
|
360
433
|
binding = designer.activeBinding.value
|
|
361
|
-
expect(binding).toMatchObject({ aggregate: 'avg', expand: 'none' })
|
|
362
|
-
|
|
434
|
+
expect(binding).toMatchObject({ preset: 'subtotal', aggregate: 'avg', expand: 'none' })
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
it('聚合/展开偏离预设族时清除 preset', async () => {
|
|
438
|
+
const { designer, workbook } = createDesigner()
|
|
439
|
+
await seedAndBind(designer, { row: 0, col: 0 })
|
|
440
|
+
workbook.activeSheet.selectCell({ row: 0, col: 0 })
|
|
441
|
+
|
|
442
|
+
designer.patchActiveBinding(roleBindingDefaults('subtotal'))
|
|
443
|
+
designer.patchActiveBinding({ aggregate: 'list', expand: 'down' })
|
|
444
|
+
expect(designer.activeBinding.value?.preset).toBeUndefined()
|
|
363
445
|
})
|
|
364
446
|
|
|
365
447
|
it('条件规则与排序经 patch 写入绑定;拓扑条目跟随 meta 变更', async () => {
|
|
@@ -393,6 +475,8 @@ describe('useReportDesigner:Action Pill 就地编辑(角色 / 聚合 / 条
|
|
|
393
475
|
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
394
476
|
designer.bindField(dataset.id, 'amount', { row: 2, col: 1 })
|
|
395
477
|
|
|
478
|
+
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
479
|
+
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
396
480
|
workbook.activeSheet.selectCell({ row: 2, col: 1 })
|
|
397
481
|
designer.startParentPick('row')
|
|
398
482
|
designer.pickParentAt({ row: 1, col: 0 })
|
|
@@ -405,14 +489,62 @@ describe('useReportDesigner:Action Pill 就地编辑(角色 / 聚合 / 条
|
|
|
405
489
|
expect(designer.parentPick.value).toBeNull()
|
|
406
490
|
})
|
|
407
491
|
|
|
492
|
+
it('切换小计行时纠正指向 list 的父格', async () => {
|
|
493
|
+
const { designer, workbook } = createDesigner()
|
|
494
|
+
const dataset = designer.addDataset('c1')
|
|
495
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
496
|
+
await designer.describeDataset(dataset.id)
|
|
497
|
+
|
|
498
|
+
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
499
|
+
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
500
|
+
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
501
|
+
designer.bindField(dataset.id, 'orderNo', { row: 1, col: 2 })
|
|
502
|
+
designer.bindField(dataset.id, 'amount', { row: 2, col: 2 })
|
|
503
|
+
|
|
504
|
+
workbook.activeSheet.selectCell({ row: 2, col: 2 })
|
|
505
|
+
designer.patchActiveBinding({ rowParent: { row: 1, col: 2 } })
|
|
506
|
+
expect(designer.activeBinding.value?.rowParent).toEqual({ row: 1, col: 2 })
|
|
507
|
+
|
|
508
|
+
designer.patchActiveBinding(roleBindingDefaults('subtotal'))
|
|
509
|
+
expect(designer.activeBinding.value).toMatchObject({
|
|
510
|
+
preset: 'subtotal',
|
|
511
|
+
rowParent: { row: 1, col: 0 }
|
|
512
|
+
})
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('非法点选父格时选区弹回源格且保持拾取态', async () => {
|
|
516
|
+
const { designer, workbook } = createDesigner()
|
|
517
|
+
const dataset = designer.addDataset('c1')
|
|
518
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
519
|
+
await designer.describeDataset(dataset.id)
|
|
520
|
+
|
|
521
|
+
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
522
|
+
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
523
|
+
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
524
|
+
designer.bindField(dataset.id, 'amount', { row: 2, col: 2 })
|
|
525
|
+
|
|
526
|
+
workbook.activeSheet.selectCell({ row: 2, col: 2 })
|
|
527
|
+
designer.startParentPick('row')
|
|
528
|
+
designer.pickParentAt({ row: 0, col: 3 })
|
|
529
|
+
|
|
530
|
+
expect(designer.parentPick.value).toEqual({ mode: 'row', source: { row: 2, col: 2 } })
|
|
531
|
+
expect(workbook.activeSheet.getSelection().activeCell).toEqual({ row: 2, col: 2 })
|
|
532
|
+
expect(
|
|
533
|
+
workbook.activeSheet.getCellMeta<ReportBinding>({ row: 2, col: 2 }, REPORT_META_NAMESPACE)
|
|
534
|
+
?.rowParent
|
|
535
|
+
).toEqual({ row: 1, col: 0 })
|
|
536
|
+
})
|
|
537
|
+
|
|
408
538
|
it('清除绑定移除 Cell Meta;行 / 列方向父格标签读取绑定存储值', async () => {
|
|
409
539
|
const { designer, workbook } = createDesigner()
|
|
410
540
|
const dataset = designer.addDataset('c1')
|
|
411
541
|
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
412
542
|
await designer.describeDataset(dataset.id)
|
|
413
543
|
|
|
414
|
-
//
|
|
544
|
+
// 同列上方分组头 + 下方小计(rowParent 推断到分组头)
|
|
415
545
|
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
546
|
+
workbook.activeSheet.selectCell({ row: 1, col: 0 })
|
|
547
|
+
designer.patchActiveBinding(roleBindingDefaults('groupHeader'))
|
|
416
548
|
designer.bindField(dataset.id, 'amount', { row: 2, col: 0 })
|
|
417
549
|
|
|
418
550
|
workbook.activeSheet.selectCell({ row: 2, col: 0 })
|
|
@@ -542,7 +674,7 @@ describe('useReportDesigner:template prop 载入既有模板', () => {
|
|
|
542
674
|
|
|
543
675
|
// catalog 应用 fieldOverrides(字段面板与徽章 label 数据源)
|
|
544
676
|
const catalog = designer.catalog.value
|
|
545
|
-
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '金额'])
|
|
677
|
+
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '订单号', '金额'])
|
|
546
678
|
})
|
|
547
679
|
|
|
548
680
|
it('载入后 getTemplate 往返保持绑定与数据集定义;宿主已有同 id 连接时不覆盖', async () => {
|
|
@@ -5,24 +5,35 @@ import {
|
|
|
5
5
|
type ResolveCellStyleHook
|
|
6
6
|
} from '@veltra/sheet-core'
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { formatBindingPlaceholderParts, inferReportPreset } from '../../report/binding'
|
|
9
9
|
import type { ReportBinding, ReportPreset } from '../../report/types'
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 绑定徽章预设配色(设计态富渲染徽章;canvas 绘制需具体色值,无法消费 CSS 变量)。
|
|
13
|
+
* `cell` 为整格极浅底色,`fg` 为强调色(聚合标签徽章底色)。
|
|
14
|
+
*/
|
|
15
|
+
export interface ReportBadgeColors {
|
|
16
|
+
cell: string
|
|
17
|
+
fg: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const REPORT_PRESET_BADGE_COLORS: Record<ReportPreset, ReportBadgeColors> = {
|
|
21
|
+
groupHeader: { cell: '#eef4ff', fg: '#2f54eb' },
|
|
22
|
+
detail: { cell: '#effaf3', fg: '#1f9254' },
|
|
23
|
+
subtotal: { cell: '#fff7e6', fg: '#d46b08' },
|
|
24
|
+
grandTotal: { cell: '#fff1f0', fg: '#cf1322' },
|
|
25
|
+
cross: { cell: '#f8f5ff', fg: '#5f3dc4' }
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
const CUSTOM_PRESET_BADGE_COLOR = {
|
|
28
|
+
const CUSTOM_PRESET_BADGE_COLOR: ReportBadgeColors = { cell: '#f7f8fa', fg: '#495057' }
|
|
29
|
+
|
|
30
|
+
/** 徽章字段标签文字色(中性深灰,与强调色形成层次) */
|
|
31
|
+
const BADGE_LABEL_COLOR = '#343a40'
|
|
21
32
|
|
|
22
33
|
/** @deprecated 05 将更名 */
|
|
23
34
|
export const REPORT_ROLE_BADGE_COLORS = REPORT_PRESET_BADGE_COLORS
|
|
24
35
|
|
|
25
|
-
function resolveBadgeColor(binding: ReportBinding):
|
|
36
|
+
function resolveBadgeColor(binding: ReportBinding): ReportBadgeColors {
|
|
26
37
|
const preset = inferReportPreset(binding)
|
|
27
38
|
return preset ? REPORT_PRESET_BADGE_COLORS[preset] : CUSTOM_PRESET_BADGE_COLOR
|
|
28
39
|
}
|
|
@@ -35,14 +46,14 @@ export function createBindingBadgeStyleResolver(
|
|
|
35
46
|
const binding = getBindingAt(addr)
|
|
36
47
|
if (!binding) return base
|
|
37
48
|
|
|
38
|
-
const {
|
|
39
|
-
return { ...base, fill: { color:
|
|
49
|
+
const { cell } = resolveBadgeColor(binding)
|
|
50
|
+
return { ...base, fill: { color: cell } }
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
/**
|
|
44
55
|
* 绑定格富渲染徽章(ADR-0004 `resolveCellRenderer` 首个消费者):
|
|
45
|
-
*
|
|
56
|
+
* 绑定单元格渲染为左对齐徽章(强调色聚合标签 + 中性色字段标签),
|
|
46
57
|
* 未绑定格返回 `undefined` 回落默认渲染。
|
|
47
58
|
*/
|
|
48
59
|
export function createBindingBadgeRenderer(
|
|
@@ -54,19 +65,27 @@ export function createBindingBadgeRenderer(
|
|
|
54
65
|
if (!binding) return undefined
|
|
55
66
|
|
|
56
67
|
const { fg } = resolveBadgeColor(binding)
|
|
68
|
+
const { tag, label } = formatBindingPlaceholderParts(binding, resolveLabel)
|
|
69
|
+
|
|
57
70
|
const rootContainer = new CustomLayout.Container({
|
|
58
71
|
display: 'flex',
|
|
59
72
|
flexDirection: 'row',
|
|
60
|
-
justifyContent: '
|
|
73
|
+
justifyContent: 'flex-start',
|
|
61
74
|
alignItems: 'center'
|
|
62
75
|
})
|
|
76
|
+
// VTable 布局层 Tag 组件(vrender-components)与其 INode 声明存在结构性类型偏差,
|
|
77
|
+
// 运行时完全兼容(VTable 官方 customLayout 徽章用法即如此),此处仅收敛类型
|
|
78
|
+
const tagEl = new CustomLayout.Tag({
|
|
79
|
+
text: tag,
|
|
80
|
+
textStyle: { fontSize: 10, fontWeight: 600, fill: '#ffffff' },
|
|
81
|
+
panel: { visible: true, fill: fg, cornerRadius: 3 },
|
|
82
|
+
padding: [1, 4, 1, 4],
|
|
83
|
+
marginLeft: 6,
|
|
84
|
+
marginRight: 5
|
|
85
|
+
})
|
|
86
|
+
rootContainer.add(tagEl as unknown as Parameters<typeof rootContainer.add>[0])
|
|
63
87
|
rootContainer.add(
|
|
64
|
-
new CustomLayout.Text({
|
|
65
|
-
text: formatBindingPlaceholder(binding, resolveLabel),
|
|
66
|
-
fontSize: 11,
|
|
67
|
-
fontWeight: 600,
|
|
68
|
-
fill: fg
|
|
69
|
-
})
|
|
88
|
+
new CustomLayout.Text({ text: label, fontSize: 11, fontWeight: 500, fill: BADGE_LABEL_COLOR })
|
|
70
89
|
)
|
|
71
90
|
return { rootContainer, renderDefault: false }
|
|
72
91
|
}
|
|
@@ -1,27 +1,49 @@
|
|
|
1
|
+
import type { Sheet } from '@veltra/sheet-core'
|
|
2
|
+
|
|
1
3
|
import type { ReportColWidthEntry } from '../../../report/export-xlsx'
|
|
2
|
-
import { TABLE_ADDR_OFFSET } from './cell-coords'
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/** VTable 列宽读写所需的最小表接口(含行号列探测,隐藏行头时偏移为 0) */
|
|
6
|
+
type VTableColWidthApi = {
|
|
7
|
+
setColWidth: (col: number, width: number) => void
|
|
8
|
+
getColWidth: (col: number) => number
|
|
9
|
+
readonly colCount: number
|
|
10
|
+
readonly columnHeaderLevelCount: number
|
|
11
|
+
isSeriesNumber: (col: number, row: number) => boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 列宽读写目标:Sheet 模型或 SheetGrid.getTable() 的最小接口(headless 测试可桩) */
|
|
15
|
+
export type ColWidthTarget = { getTable: () => VTableColWidthApi }
|
|
16
|
+
|
|
17
|
+
/** 行号列数(隐藏行头时为 0;与 GridCoords 探测一致) */
|
|
18
|
+
function seriesNumberColCount(table: VTableColWidthApi): number {
|
|
19
|
+
const rowOffset = table.columnHeaderLevelCount
|
|
20
|
+
let col = 0
|
|
21
|
+
while (col < table.colCount && table.isSeriesNumber(col, rowOffset)) col++
|
|
22
|
+
return col
|
|
11
23
|
}
|
|
12
24
|
|
|
13
|
-
/**
|
|
25
|
+
/** 将模型列宽写入 VTable(模型 → grid 同步辅助) */
|
|
14
26
|
export function applyGridColWidths(
|
|
15
27
|
grid: ColWidthTarget | undefined | null,
|
|
16
28
|
widths: ReadonlyArray<ReportColWidthEntry>
|
|
17
29
|
): void {
|
|
18
30
|
if (!grid) return
|
|
19
31
|
const table = grid.getTable()
|
|
32
|
+
const colOffset = seriesNumberColCount(table)
|
|
20
33
|
for (const [sheetCol, width] of widths) {
|
|
21
|
-
table.setColWidth(sheetCol +
|
|
34
|
+
table.setColWidth(sheetCol + colOffset, width)
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
|
|
38
|
+
/** 把列宽条目写入 Sheet 模型(不进 undo,对称 setColWidth) */
|
|
39
|
+
export function applySheetColWidths(
|
|
40
|
+
sheet: Sheet | undefined | null,
|
|
41
|
+
widths: ReadonlyArray<ReportColWidthEntry>
|
|
42
|
+
): void {
|
|
43
|
+
if (!sheet) return
|
|
44
|
+
for (const [col, width] of widths) sheet.setColWidth(col, width)
|
|
45
|
+
}
|
|
46
|
+
|
|
25
47
|
/** 从当前网格读取指定模型列宽度;网格未就绪时返回 null */
|
|
26
48
|
export function readGridColWidths(
|
|
27
49
|
grid: ColWidthTarget | undefined | null,
|
|
@@ -29,5 +51,6 @@ export function readGridColWidths(
|
|
|
29
51
|
): Array<[number, number]> | null {
|
|
30
52
|
if (!grid) return null
|
|
31
53
|
const table = grid.getTable()
|
|
32
|
-
|
|
54
|
+
const colOffset = seriesNumberColCount(table)
|
|
55
|
+
return cols.map((sheetCol) => [sheetCol, table.getColWidth(sheetCol + colOffset)])
|
|
33
56
|
}
|
|
@@ -20,13 +20,16 @@ export const RULE_SCOPE_OPTIONS = [
|
|
|
20
20
|
{ value: 'row' as const, label: '整行' }
|
|
21
21
|
]
|
|
22
22
|
|
|
23
|
+
/** 求值字段下拉的「本格字段」哨兵值(USelect 无法匹配 undefined) */
|
|
24
|
+
export const SELF_FIELD_VALUE = ''
|
|
25
|
+
|
|
23
26
|
/** 求值字段下拉:缺省项表示本格绑定字段 */
|
|
24
27
|
export function ruleEvalFieldOptions(
|
|
25
28
|
bindingField: string,
|
|
26
29
|
datasetFields: readonly DatasetField[]
|
|
27
|
-
): Array<{ value: string
|
|
30
|
+
): Array<{ value: string; label: string }> {
|
|
28
31
|
return [
|
|
29
|
-
{ value:
|
|
32
|
+
{ value: SELF_FIELD_VALUE, label: '本格字段' },
|
|
30
33
|
...datasetFields
|
|
31
34
|
.filter((field) => field.name !== bindingField)
|
|
32
35
|
.map((field) => ({ value: field.name, label: field.label || field.name }))
|
|
@@ -79,7 +82,7 @@ export function defaultRuleValue(
|
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
export function defaultRuleStyle(): ConditionalRule['style'] {
|
|
82
|
-
return { fill: { color: '#
|
|
85
|
+
return { fill: { color: '#FEF3C7' }, font: { color: '#B45309' } }
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
export function createDraftItem(
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
@update:model-value="onPreset"
|
|
22
22
|
/>
|
|
23
23
|
<u-select
|
|
24
|
+
v-if="showAggregateSelect"
|
|
24
25
|
size="small"
|
|
25
26
|
:class="[cls.e('select'), cls.em('select', 'aggregate')]"
|
|
26
27
|
:model-value="binding.aggregate"
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
@update:model-value="onAggregate"
|
|
29
30
|
/>
|
|
30
31
|
<u-select
|
|
32
|
+
v-if="showExpandSelect"
|
|
31
33
|
size="small"
|
|
32
34
|
:class="[cls.e('select'), cls.em('select', 'expand')]"
|
|
33
35
|
:model-value="binding.expand"
|
|
@@ -35,6 +37,7 @@
|
|
|
35
37
|
@update:model-value="onExpand"
|
|
36
38
|
/>
|
|
37
39
|
<u-button size="small" plain @click="emit('open-rules')">条件样式</u-button>
|
|
40
|
+
<u-button size="small" text type="danger" @click="emit('remove')">删除绑定</u-button>
|
|
38
41
|
<u-button
|
|
39
42
|
size="small"
|
|
40
43
|
text
|
|
@@ -138,11 +141,8 @@
|
|
|
138
141
|
</div>
|
|
139
142
|
</section>
|
|
140
143
|
|
|
141
|
-
<footer :class="cls.e('footer')">
|
|
142
|
-
<u-button
|
|
143
|
-
取消点选
|
|
144
|
-
</u-button>
|
|
145
|
-
<u-button size="small" type="danger" plain @click="emit('remove')">删除绑定</u-button>
|
|
144
|
+
<footer v-if="parentPickMode" :class="cls.e('footer')">
|
|
145
|
+
<u-button size="small" plain @click="emit('cancel-parent-pick')">取消点选</u-button>
|
|
146
146
|
</footer>
|
|
147
147
|
</div>
|
|
148
148
|
</div>
|
|
@@ -157,7 +157,9 @@ import { computed, nextTick, ref, toRef, watch } from 'vue'
|
|
|
157
157
|
import {
|
|
158
158
|
formatBindingPlaceholder,
|
|
159
159
|
formatCellAddress,
|
|
160
|
-
|
|
160
|
+
formatParentCandidateLabel,
|
|
161
|
+
inferReportPreset,
|
|
162
|
+
isReductionAggregate
|
|
161
163
|
} from '../../../report/binding'
|
|
162
164
|
import type {
|
|
163
165
|
ReportAggregate,
|
|
@@ -188,6 +190,7 @@ const props = defineProps<{
|
|
|
188
190
|
rowParentCandidates: CellAddress[]
|
|
189
191
|
colParentCandidates: CellAddress[]
|
|
190
192
|
resolveFieldLabel?: (datasetId: string, fieldName: string) => string
|
|
193
|
+
getBindingAt?: (addr: CellAddress) => ReportBinding | undefined
|
|
191
194
|
hostEl: HTMLElement | null
|
|
192
195
|
getGrid: () => SheetGrid | undefined
|
|
193
196
|
}>()
|
|
@@ -216,7 +219,7 @@ const presetOptions = computed(() => {
|
|
|
216
219
|
return options
|
|
217
220
|
})
|
|
218
221
|
|
|
219
|
-
const
|
|
222
|
+
const ALL_AGGREGATE_OPTIONS = [
|
|
220
223
|
{ value: 'list' as const, label: '明细' },
|
|
221
224
|
{ value: 'group' as const, label: '分组' },
|
|
222
225
|
{ value: 'sum' as const, label: '求和' },
|
|
@@ -226,6 +229,10 @@ const aggregateOptions = [
|
|
|
226
229
|
{ value: 'min' as const, label: '最小' }
|
|
227
230
|
]
|
|
228
231
|
|
|
232
|
+
const REDUCTION_AGGREGATE_OPTIONS = ALL_AGGREGATE_OPTIONS.filter((item) =>
|
|
233
|
+
isReductionAggregate(item.value)
|
|
234
|
+
)
|
|
235
|
+
|
|
229
236
|
const expandOptions = [
|
|
230
237
|
{ value: 'down' as const, label: '向下' },
|
|
231
238
|
{ value: 'right' as const, label: '向右' },
|
|
@@ -247,24 +254,59 @@ const currentPresetValue = computed(() => {
|
|
|
247
254
|
return inferReportPreset(props.binding) ?? CUSTOM_PRESET_VALUE
|
|
248
255
|
})
|
|
249
256
|
|
|
257
|
+
const showAggregateSelect = computed(() => {
|
|
258
|
+
const preset = currentPresetValue.value
|
|
259
|
+
return (
|
|
260
|
+
preset === CUSTOM_PRESET_VALUE ||
|
|
261
|
+
preset === 'subtotal' ||
|
|
262
|
+
preset === 'grandTotal' ||
|
|
263
|
+
preset === 'cross'
|
|
264
|
+
)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const showExpandSelect = computed(() => {
|
|
268
|
+
const preset = currentPresetValue.value
|
|
269
|
+
return preset === CUSTOM_PRESET_VALUE || preset === 'groupHeader' || preset === 'detail'
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const aggregateOptions = computed(() =>
|
|
273
|
+
currentPresetValue.value === CUSTOM_PRESET_VALUE
|
|
274
|
+
? ALL_AGGREGATE_OPTIONS
|
|
275
|
+
: REDUCTION_AGGREGATE_OPTIONS
|
|
276
|
+
)
|
|
277
|
+
|
|
250
278
|
const summaryText = computed(() => {
|
|
251
279
|
if (!props.binding) return ''
|
|
252
|
-
|
|
280
|
+
const parts = [formatBindingPlaceholder(props.binding, props.resolveFieldLabel)]
|
|
281
|
+
if (props.binding.rowParent && props.resolvedRowParentLabel !== '—') {
|
|
282
|
+
const parent = props.getBindingAt?.(props.binding.rowParent)
|
|
283
|
+
const name = parent
|
|
284
|
+
? (props.resolveFieldLabel?.(parent.dataset, parent.field) ?? parent.field)
|
|
285
|
+
: props.resolvedRowParentLabel
|
|
286
|
+
parts.push(`行父格 ${name} ${props.resolvedRowParentLabel}`)
|
|
287
|
+
}
|
|
288
|
+
if (props.binding.colParent && props.resolvedColParentLabel !== '—') {
|
|
289
|
+
const parent = props.getBindingAt?.(props.binding.colParent)
|
|
290
|
+
const name = parent
|
|
291
|
+
? (props.resolveFieldLabel?.(parent.dataset, parent.field) ?? parent.field)
|
|
292
|
+
: props.resolvedColParentLabel
|
|
293
|
+
parts.push(`列父格 ${name} ${props.resolvedColParentLabel}`)
|
|
294
|
+
}
|
|
295
|
+
return parts.join(' · ')
|
|
253
296
|
})
|
|
254
297
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
298
|
+
function candidateOption(addr: CellAddress): { value: string; label: string } {
|
|
299
|
+
const value = formatCellAddress(addr)
|
|
300
|
+
const binding = props.getBindingAt?.(addr)
|
|
301
|
+
return {
|
|
302
|
+
value,
|
|
303
|
+
label: binding ? formatParentCandidateLabel(addr, binding, props.resolveFieldLabel) : value
|
|
304
|
+
}
|
|
305
|
+
}
|
|
261
306
|
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
label: formatCellAddress(addr)
|
|
266
|
-
}))
|
|
267
|
-
)
|
|
307
|
+
const rowParentOptions = computed(() => props.rowParentCandidates.map(candidateOption))
|
|
308
|
+
|
|
309
|
+
const colParentOptions = computed(() => props.colParentCandidates.map(candidateOption))
|
|
268
310
|
|
|
269
311
|
const rowParentSelectValue = computed(() =>
|
|
270
312
|
props.binding?.rowParent ? formatCellAddress(props.binding.rowParent) : undefined
|
|
@@ -281,10 +323,18 @@ const panelStyle = computed(() => ({ left: `${panelLeft.value}px`, top: `${panel
|
|
|
281
323
|
watch(
|
|
282
324
|
() => [props.cell?.row, props.cell?.col] as const,
|
|
283
325
|
() => {
|
|
326
|
+
if (props.parentPickMode) return
|
|
284
327
|
expanded.value = false
|
|
285
328
|
}
|
|
286
329
|
)
|
|
287
330
|
|
|
331
|
+
watch(
|
|
332
|
+
() => props.parentPickMode,
|
|
333
|
+
(mode) => {
|
|
334
|
+
if (mode) expanded.value = true
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
|
|
288
338
|
watch(expanded, () => {
|
|
289
339
|
nextTick(() => updatePosition())
|
|
290
340
|
})
|
|
@@ -300,13 +350,7 @@ function onPreset(preset: ReportPreset | typeof CUSTOM_PRESET_VALUE): void {
|
|
|
300
350
|
}
|
|
301
351
|
|
|
302
352
|
function aggregateDefaultExpand(aggregate: ReportAggregate): ReportExpand {
|
|
303
|
-
return aggregate
|
|
304
|
-
aggregate === 'avg' ||
|
|
305
|
-
aggregate === 'count' ||
|
|
306
|
-
aggregate === 'max' ||
|
|
307
|
-
aggregate === 'min'
|
|
308
|
-
? 'none'
|
|
309
|
-
: 'down'
|
|
353
|
+
return isReductionAggregate(aggregate) ? 'none' : 'down'
|
|
310
354
|
}
|
|
311
355
|
|
|
312
356
|
function onAggregate(aggregate: ReportAggregate): void {
|