@veltra/sheet 2.1.1 → 2.2.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 +76 -0
- package/dist/components/report/designer/cell-coords.js +46 -0
- package/dist/components/report/designer/col-widths.js +15 -0
- package/dist/components/report/designer/conditional-rules/helpers.js +139 -0
- package/dist/components/report/designer/drop-highlight-overlay.js +76 -0
- package/dist/components/report/designer/float-panel.js +385 -0
- package/dist/components/report/designer/role.js +17 -0
- package/dist/components/report/designer/rule-preview.js +33 -0
- package/dist/components/report/designer/rule-row.js +269 -0
- package/dist/components/report/designer/rules-dialog.js +135 -0
- package/dist/components/report/designer/topology-overlay.js +122 -0
- package/dist/components/report/designer/topology.js +64 -0
- package/dist/components/report/designer/use-grid-overlay.js +75 -0
- package/dist/components/report/designer-hub.js +177 -0
- package/dist/components/report/field-panel-helpers.js +33 -0
- package/dist/components/report/field-panel.js +132 -0
- package/dist/components/report/filter-bar.js +74 -0
- package/dist/components/report/hub-connection-form.js +145 -0
- package/dist/components/report/hub-dataset-editor.js +394 -0
- package/dist/components/report/report-designer.js +343 -0
- package/dist/components/report/report-viewer.js +111 -0
- package/dist/components/report/style.d.ts +17 -0
- package/dist/components/report/style.js +18 -0
- package/dist/components/report/style2.css +869 -0
- package/dist/components/report/use-report-designer.js +360 -0
- package/dist/components/report/use-report-viewer.js +50 -0
- package/dist/{vue → components/sheet}/formula-bar.js +1 -1
- package/dist/{vue → components/sheet}/formula-suggest-list.js +1 -1
- package/dist/{vue → components/sheet}/import-file.js +1 -1
- package/dist/{vue → components/sheet}/insert-count-menu-item.js +1 -1
- package/dist/{vue → components/sheet}/insert-image.js +1 -1
- package/dist/{vue → components/sheet}/popup-helpers.js +1 -1
- package/dist/{vue → components/sheet}/popups/border-popup.js +1 -1
- package/dist/{vue → components/sheet}/popups/border-preset-glyph.js +1 -1
- package/dist/{vue → components/sheet}/popups/export-popup.js +2 -2
- package/dist/{vue → components/sheet}/popups/fill-color-popup.js +1 -1
- package/dist/{vue → components/sheet}/popups/find-popup.js +1 -1
- package/dist/{vue → components/sheet}/popups/font-color-popup.js +1 -1
- package/dist/{vue → components/sheet}/popups/font-size-popup.js +1 -1
- package/dist/{vue → components/sheet}/popups/import.worker.d.ts +1 -1
- package/dist/{vue → components/sheet}/popups/import.worker.js +1 -1
- package/dist/{vue → components/sheet}/popups/insert-image-popup.js +1 -1
- package/dist/{vue → components/sheet}/sheet-context-menu.js +1 -1
- package/dist/{vue → components/sheet}/sheet-tabs.js +1 -1
- package/dist/{vue → components/sheet}/sheet-toolbar.js +1 -1
- package/dist/{vue → components/sheet}/sheet.js +8 -1
- package/dist/{vue → components/sheet}/use-find-replace.js +1 -1
- package/dist/{vue → components/sheet}/use-formula-ref-select.js +1 -1
- package/dist/{vue → components/sheet}/use-sheet-grid.js +10 -2
- package/dist/{vue → components/sheet}/use-sheet-state.js +3 -3
- package/dist/{vue → components/sheet}/use-sheet-tabs-bar.js +1 -1
- package/dist/{vue → components/sheet}/use-tool-groups.js +2 -2
- package/dist/{vue → components/sheet}/use-tool-popup.js +2 -2
- package/dist/{vue → components/sheet}/use-toolbar-scroll.js +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.js +13 -3
- package/dist/report/binding.d.ts +45 -0
- package/dist/report/binding.js +175 -0
- package/dist/report/connector.d.ts +66 -0
- package/dist/report/connector.js +159 -0
- package/dist/report/export-xlsx.d.ts +19 -0
- package/dist/report/export-xlsx.js +95 -0
- package/dist/report/filter-bar.d.ts +10 -0
- package/dist/report/filter-bar.js +31 -0
- package/dist/report/index.d.ts +12 -0
- package/dist/report/params.d.ts +13 -0
- package/dist/report/params.js +61 -0
- package/dist/report/render/aggregate.js +157 -0
- package/dist/report/render/builder.d.ts +12 -0
- package/dist/report/render/builder.js +139 -0
- package/dist/report/render/coordinate.d.ts +25 -0
- package/dist/report/render/coordinate.js +733 -0
- package/dist/report/render/index.d.ts +14 -0
- package/dist/report/render/index.js +21 -0
- package/dist/report/render/style-resolver.js +50 -0
- package/dist/report/render/template-index.d.ts +24 -0
- package/dist/report/render/template-index.js +137 -0
- package/dist/report/rules/evaluate.d.ts +25 -0
- package/dist/report/rules/evaluate.js +82 -0
- package/dist/report/rules/index.d.ts +2 -0
- package/dist/report/template.d.ts +59 -0
- package/dist/report/template.js +80 -0
- package/dist/report/types.d.ts +85 -0
- package/dist/tools/context.d.ts +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/report-designer.d.ts +36 -0
- package/dist/types/report-viewer.d.ts +38 -0
- package/dist/types/sheet.d.ts +17 -1
- package/package.json +3 -5
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +87 -0
- package/src/components/report/__test__/field-panel-helpers.test.ts +65 -0
- package/src/components/report/__test__/float-panel-position.test.ts +45 -0
- package/src/components/report/__test__/report-designer.test.ts +388 -0
- package/src/components/report/__test__/report-viewer.test.ts +334 -0
- package/src/components/report/__test__/topology.test.ts +143 -0
- package/src/components/report/__test__/use-report-designer.test.ts +565 -0
- package/src/components/report/binding-badge.ts +73 -0
- package/src/components/report/designer/cell-coords.ts +94 -0
- package/src/components/report/designer/col-widths.ts +33 -0
- package/src/components/report/designer/conditional-rules/helpers.ts +186 -0
- package/src/components/report/designer/drop-highlight-overlay.vue +74 -0
- package/src/components/report/designer/float-panel.vue +387 -0
- package/src/components/report/designer/role.ts +32 -0
- package/src/components/report/designer/rule-preview.vue +40 -0
- package/src/components/report/designer/rule-row.vue +252 -0
- package/src/components/report/designer/rules-dialog.vue +126 -0
- package/src/components/report/designer/topology-overlay.vue +127 -0
- package/src/components/report/designer/topology.ts +124 -0
- package/src/components/report/designer/use-grid-overlay.ts +113 -0
- package/src/components/report/designer-hub.vue +198 -0
- package/src/components/report/field-panel-helpers.ts +44 -0
- package/src/components/report/field-panel.vue +136 -0
- package/src/components/report/filter-bar.vue +77 -0
- package/src/components/report/hub-connection-form.vue +124 -0
- package/src/components/report/hub-dataset-editor.vue +369 -0
- package/src/components/report/index.ts +2 -0
- package/src/components/report/report-designer.vue +356 -0
- package/src/components/report/report-viewer.vue +128 -0
- package/src/components/report/style-designer.scss +583 -0
- package/src/components/report/style-hub.scss +292 -0
- package/src/components/report/style-viewer.scss +104 -0
- package/src/components/report/style.scss +3 -0
- package/src/components/report/style.ts +20 -0
- package/src/components/report/use-report-designer.ts +547 -0
- package/src/components/report/use-report-viewer.ts +79 -0
- package/src/{vue → components/sheet}/__test__/formula-bar.test.ts +37 -18
- package/src/{vue → components/sheet}/__test__/insert-image-popup.test.ts +1 -1
- package/src/{vue → components/sheet}/__test__/insert-image.test.ts +1 -1
- package/src/{vue → components/sheet}/__test__/sheet-component.test.ts +40 -3
- package/src/{vue → components/sheet}/__test__/sheet-context-menu.test.ts +1 -1
- package/src/{vue → components/sheet}/formula-bar.vue +1 -1
- package/src/{vue → components/sheet}/insert-image.ts +1 -1
- package/src/{vue → components/sheet}/popup-helpers.ts +1 -1
- package/src/{vue → components/sheet}/popups/border-popup.vue +1 -1
- package/src/{vue → components/sheet}/popups/export-popup.vue +2 -2
- package/src/{vue → components/sheet}/popups/fill-color-popup.vue +1 -1
- package/src/{vue → components/sheet}/popups/find-popup.vue +1 -1
- package/src/{vue → components/sheet}/popups/font-color-popup.vue +1 -1
- package/src/{vue → components/sheet}/popups/font-size-popup.vue +1 -1
- package/src/{vue → components/sheet}/popups/insert-image-popup.vue +1 -1
- package/src/{vue → components/sheet}/sheet-context-menu.ts +2 -2
- package/src/{vue → components/sheet}/sheet-toolbar.vue +2 -2
- package/src/{vue → components/sheet}/sheet.vue +4 -3
- package/src/{vue → components/sheet}/use-find-replace.ts +1 -1
- package/src/{vue → components/sheet}/use-sheet-grid.ts +7 -3
- package/src/{vue → components/sheet}/use-sheet-state.ts +3 -3
- package/src/{vue → components/sheet}/use-tool-groups.ts +2 -2
- package/src/{vue → components/sheet}/use-tool-popup.ts +2 -2
- package/src/index.ts +24 -188
- package/src/report/__test__/binding.test.ts +218 -0
- package/src/report/__test__/connector.test.ts +218 -0
- package/src/report/__test__/export-xlsx.test.ts +118 -0
- package/src/report/__test__/filter-bar.test.ts +58 -0
- package/src/report/__test__/params.test.ts +156 -0
- package/src/report/__test__/render.test.ts +728 -0
- package/src/report/__test__/rules.test.ts +131 -0
- package/src/report/__test__/template.test.ts +355 -0
- package/src/report/binding.ts +243 -0
- package/src/report/connector.ts +205 -0
- package/src/report/export-xlsx.ts +118 -0
- package/src/report/filter-bar.ts +36 -0
- package/src/report/index.ts +13 -0
- package/src/report/params.ts +96 -0
- package/src/report/render/__test__/aggregate.test.ts +16 -0
- package/src/report/render/__test__/coordinate.test.ts +405 -0
- package/src/report/render/aggregate.ts +240 -0
- package/src/report/render/builder.ts +211 -0
- package/src/report/render/coordinate.ts +1239 -0
- package/src/report/render/index.ts +30 -0
- package/src/report/render/style-resolver.ts +68 -0
- package/src/report/render/template-index.ts +200 -0
- package/src/report/rules/evaluate.ts +113 -0
- package/src/report/rules/index.ts +7 -0
- package/src/report/template.ts +141 -0
- package/src/report/types.ts +96 -0
- package/src/tools/builtin.ts +2 -2
- package/src/types/index.ts +2 -6
- package/src/types/report-designer.ts +36 -0
- package/src/types/report-viewer.ts +37 -0
- package/src/types/sheet.ts +22 -1
- /package/dist/{vue → components/sheet}/style.d.ts +0 -0
- /package/dist/{vue → components/sheet}/style.js +0 -0
- /package/dist/{vue → components/sheet}/style2.css +0 -0
- /package/dist/{vue → components/sheet}/use-formula-suggest.js +0 -0
- /package/src/{vue → components/sheet}/__test__/import-file.test.ts +0 -0
- /package/src/{vue → components/sheet}/__test__/insert-count-menu-item.test.ts +0 -0
- /package/src/{vue → components/sheet}/__test__/use-formula-ref-select.test.ts +0 -0
- /package/src/{vue → components/sheet}/__test__/use-formula-suggest.test.ts +0 -0
- /package/src/{vue → components/sheet}/formula-suggest-list.vue +0 -0
- /package/src/{vue → components/sheet}/import-file.ts +0 -0
- /package/src/{vue → components/sheet}/index.ts +0 -0
- /package/src/{vue → components/sheet}/insert-count-menu-item.vue +0 -0
- /package/src/{vue → components/sheet}/parsing.ts +0 -0
- /package/src/{vue → components/sheet}/popups/border-preset-glyph.vue +0 -0
- /package/src/{vue → components/sheet}/popups/import.worker.ts +0 -0
- /package/src/{vue → components/sheet}/sheet-tabs.vue +0 -0
- /package/src/{vue → components/sheet}/style.scss +0 -0
- /package/src/{vue → components/sheet}/style.ts +0 -0
- /package/src/{vue → components/sheet}/use-formula-ref-select.ts +0 -0
- /package/src/{vue → components/sheet}/use-formula-suggest.ts +0 -0
- /package/src/{vue → components/sheet}/use-sheet-tabs-bar.ts +0 -0
- /package/src/{vue → components/sheet}/use-toolbar-scroll.ts +0 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import { Workbook } from '@veltra/sheet-core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { effectScope, ref, type EffectScope, type Ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
import { REPORT_META_NAMESPACE } from '../../../report/binding'
|
|
6
|
+
import type { DataConnection, DataConnector } from '../../../report/connector'
|
|
7
|
+
import { IncompatibleTemplateVersionError, type ReportTemplate } from '../../../report/template'
|
|
8
|
+
import type { DatasetField, ParamValues, ReportBinding } from '../../../report/types'
|
|
9
|
+
import { roleBindingDefaults } from '../designer/role'
|
|
10
|
+
import { useReportDesigner, type UseReportDesignerReturn } from '../use-report-designer'
|
|
11
|
+
|
|
12
|
+
// ---- 内联 fixtures:stub connector(实现 DataConnector 接口的内存测试夹具)----
|
|
13
|
+
|
|
14
|
+
const MYSQL: DataConnection = {
|
|
15
|
+
id: 'c1',
|
|
16
|
+
label: 'stub MySQL',
|
|
17
|
+
type: 'mysql',
|
|
18
|
+
host: '127.0.0.1',
|
|
19
|
+
port: 3306,
|
|
20
|
+
database: 'demo',
|
|
21
|
+
username: 'root',
|
|
22
|
+
password: ''
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PG: DataConnection = {
|
|
26
|
+
id: 'c2',
|
|
27
|
+
label: 'stub PG',
|
|
28
|
+
type: 'postgresql',
|
|
29
|
+
host: '127.0.0.1',
|
|
30
|
+
port: 5432,
|
|
31
|
+
database: 'demo',
|
|
32
|
+
username: 'postgres',
|
|
33
|
+
password: ''
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ORDER_FIELDS: DatasetField[] = [
|
|
37
|
+
{ name: 'customer', label: '客户', type: 'string' },
|
|
38
|
+
{ name: 'amount', label: '金额', type: 'number' }
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
interface StubCalls {
|
|
42
|
+
test: DataConnection[]
|
|
43
|
+
describe: Array<{ connection: DataConnection; sql: string }>
|
|
44
|
+
query: Array<{ connection: DataConnection; sql: string; values: ParamValues }>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createStubConnector() {
|
|
48
|
+
const calls: StubCalls = { test: [], describe: [], query: [] }
|
|
49
|
+
const connector: DataConnector = {
|
|
50
|
+
test: (connection) => {
|
|
51
|
+
calls.test.push(connection)
|
|
52
|
+
return Promise.resolve({ ok: true, data: undefined })
|
|
53
|
+
},
|
|
54
|
+
describe: (connection, sql) => {
|
|
55
|
+
calls.describe.push({ connection, sql })
|
|
56
|
+
if (sql.includes('BAD')) {
|
|
57
|
+
return Promise.resolve({
|
|
58
|
+
ok: false,
|
|
59
|
+
error: { code: 'SQL_ERROR', message: 'syntax error near BAD' }
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
return Promise.resolve({ ok: true, data: ORDER_FIELDS })
|
|
63
|
+
},
|
|
64
|
+
query: (connection, sql, values) => {
|
|
65
|
+
calls.query.push({ connection, sql, values })
|
|
66
|
+
return Promise.resolve({
|
|
67
|
+
ok: true,
|
|
68
|
+
data: { fields: ORDER_FIELDS, rows: [{ customer: '甲公司', amount: 100 }] }
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { connector, calls }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface DesignerFixture {
|
|
76
|
+
scope: EffectScope
|
|
77
|
+
designer: UseReportDesignerReturn
|
|
78
|
+
connections: Ref<DataConnection[]>
|
|
79
|
+
workbook: Workbook
|
|
80
|
+
calls: StubCalls
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createDesigner(
|
|
84
|
+
initial: DataConnection[] = [MYSQL],
|
|
85
|
+
options?: { template?: ReportTemplate }
|
|
86
|
+
): DesignerFixture {
|
|
87
|
+
const { connector, calls } = createStubConnector()
|
|
88
|
+
const connections = ref<DataConnection[]>(initial)
|
|
89
|
+
const workbook = new Workbook()
|
|
90
|
+
const scope = effectScope()
|
|
91
|
+
const designer = scope.run(() =>
|
|
92
|
+
useReportDesigner({ props: { connector, workbook, template: options?.template }, connections })
|
|
93
|
+
)!
|
|
94
|
+
return { scope, designer, connections, workbook, calls }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe('useReportDesigner:连接 / 数据集 CRUD 与连接器调用', () => {
|
|
98
|
+
it('连接 CRUD 全走 v-model 代理;删除连接级联删除其数据集', () => {
|
|
99
|
+
const { designer, connections } = createDesigner()
|
|
100
|
+
|
|
101
|
+
designer.addConnection(PG)
|
|
102
|
+
expect(connections.value).toEqual([MYSQL, PG])
|
|
103
|
+
|
|
104
|
+
designer.updateConnection({ ...PG, label: 'renamed' })
|
|
105
|
+
expect(connections.value[1]).toEqual({ ...PG, label: 'renamed' })
|
|
106
|
+
|
|
107
|
+
const dataset = designer.addDataset('c2')
|
|
108
|
+
designer.removeConnection('c2')
|
|
109
|
+
expect(connections.value).toEqual([MYSQL])
|
|
110
|
+
expect(designer.datasets.value.find((item) => item.id === dataset.id)).toBeUndefined()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('测试连接透传 connector.test(新建草稿同样可测)', async () => {
|
|
114
|
+
const { designer, calls } = createDesigner()
|
|
115
|
+
const draft: DataConnection = { ...MYSQL, id: 'draft', host: '10.0.0.1' }
|
|
116
|
+
const result = await designer.testConnection(draft)
|
|
117
|
+
expect(result).toEqual({ ok: true, data: undefined })
|
|
118
|
+
expect(calls.test).toEqual([draft])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('describeDataset 经连接器解析字段并写入数据集字段缓存;fieldOverrides 在 catalog 层应用', async () => {
|
|
122
|
+
const { designer } = createDesigner()
|
|
123
|
+
const dataset = designer.addDataset('c1')
|
|
124
|
+
designer.updateDataset(dataset.id, {
|
|
125
|
+
sql: 'SELECT customer, amount FROM orders',
|
|
126
|
+
fieldOverrides: { customer: { label: '客户名称' } }
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const result = await designer.describeDataset(dataset.id)
|
|
130
|
+
expect(result).toEqual({ ok: true, data: ORDER_FIELDS })
|
|
131
|
+
expect(designer.datasets.value[0]!.fields).toEqual(ORDER_FIELDS)
|
|
132
|
+
|
|
133
|
+
const catalog = designer.catalog.value
|
|
134
|
+
expect(catalog).toHaveLength(1)
|
|
135
|
+
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '金额'])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('describeDataset 业务错误整体透传且不写字段缓存', async () => {
|
|
139
|
+
const { designer } = createDesigner()
|
|
140
|
+
const dataset = designer.addDataset('c1')
|
|
141
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT BAD' })
|
|
142
|
+
|
|
143
|
+
const result = await designer.describeDataset(dataset.id)
|
|
144
|
+
expect(result).toEqual({
|
|
145
|
+
ok: false,
|
|
146
|
+
error: { code: 'SQL_ERROR', message: 'syntax error near BAD' }
|
|
147
|
+
})
|
|
148
|
+
expect(designer.datasets.value[0]!.fields).toBeUndefined()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('previewDataset 按参数默认值(含元数据覆盖)经连接器取数', async () => {
|
|
152
|
+
const { designer, calls } = createDesigner()
|
|
153
|
+
const dataset = designer.addDataset('c1')
|
|
154
|
+
designer.updateDataset(dataset.id, {
|
|
155
|
+
sql: 'SELECT customer FROM orders WHERE customer LIKE ${keyword} AND amount >= ${minAmount}',
|
|
156
|
+
paramOverrides: { minAmount: { defaultValue: 50 } }
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
await designer.previewDataset(dataset.id)
|
|
160
|
+
expect(calls.query).toHaveLength(1)
|
|
161
|
+
expect(calls.query[0]!.connection).toEqual(MYSQL)
|
|
162
|
+
expect(calls.query[0]!.values).toEqual({ keyword: '', minAmount: 50 })
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
describe('useReportDesigner:拖拽落格写 Cell Meta', () => {
|
|
167
|
+
async function seedDataset(designer: UseReportDesignerReturn, connectionId = 'c1') {
|
|
168
|
+
const dataset = designer.addDataset(connectionId)
|
|
169
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
170
|
+
await designer.describeDataset(dataset.id)
|
|
171
|
+
return dataset
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
it('落格写入 createReportBinding 默认绑定(detail/list/down)', async () => {
|
|
175
|
+
const { designer, workbook } = createDesigner()
|
|
176
|
+
const dataset = await seedDataset(designer)
|
|
177
|
+
|
|
178
|
+
designer.bindField(dataset.id, 'customer', { row: 0, col: 0 })
|
|
179
|
+
|
|
180
|
+
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
181
|
+
{ row: 0, col: 0 },
|
|
182
|
+
REPORT_META_NAMESPACE
|
|
183
|
+
)
|
|
184
|
+
expect(binding).toEqual({
|
|
185
|
+
dataset: dataset.id,
|
|
186
|
+
field: 'customer',
|
|
187
|
+
aggregate: 'list',
|
|
188
|
+
expand: 'down',
|
|
189
|
+
preset: 'detail',
|
|
190
|
+
sort: 'none',
|
|
191
|
+
conditionalRules: []
|
|
192
|
+
})
|
|
193
|
+
expect(designer.boundKeys.value.has(`${dataset.id}:customer`)).toBe(true)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('同列向上推断 rowParent 候选', async () => {
|
|
197
|
+
const { designer, workbook } = createDesigner()
|
|
198
|
+
const dataset = await seedDataset(designer)
|
|
199
|
+
|
|
200
|
+
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
201
|
+
designer.bindField(dataset.id, 'orderNo', { row: 2, col: 0 })
|
|
202
|
+
|
|
203
|
+
const detail = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
204
|
+
{ row: 2, col: 0 },
|
|
205
|
+
REPORT_META_NAMESPACE
|
|
206
|
+
)
|
|
207
|
+
expect(detail).toMatchObject({
|
|
208
|
+
dataset: dataset.id,
|
|
209
|
+
field: 'orderNo',
|
|
210
|
+
preset: 'detail',
|
|
211
|
+
aggregate: 'list',
|
|
212
|
+
expand: 'down',
|
|
213
|
+
rowParent: { row: 1, col: 0 }
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('跨数据集拖拽保留字段自己的 dataset,不继承分组数据集', async () => {
|
|
218
|
+
const { designer, workbook } = createDesigner([MYSQL, PG])
|
|
219
|
+
const orders = await seedDataset(designer, 'c1')
|
|
220
|
+
const inventory = await seedDataset(designer, 'c2')
|
|
221
|
+
|
|
222
|
+
designer.bindField(orders.id, 'customer', { row: 1, col: 0 })
|
|
223
|
+
designer.bindField(inventory.id, 'amount', { row: 2, col: 0 })
|
|
224
|
+
|
|
225
|
+
const detail = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
226
|
+
{ row: 2, col: 0 },
|
|
227
|
+
REPORT_META_NAMESPACE
|
|
228
|
+
)
|
|
229
|
+
expect(detail).toMatchObject({
|
|
230
|
+
dataset: inventory.id,
|
|
231
|
+
field: 'amount',
|
|
232
|
+
rowParent: { row: 1, col: 0 }
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('数值字段落至展开带下方相邻行时推断为小计预设', async () => {
|
|
237
|
+
const { designer, workbook } = createDesigner()
|
|
238
|
+
const dataset = await seedDataset(designer)
|
|
239
|
+
|
|
240
|
+
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
241
|
+
designer.bindField(dataset.id, 'amount', { row: 2, col: 0 })
|
|
242
|
+
|
|
243
|
+
const subtotal = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
244
|
+
{ row: 2, col: 0 },
|
|
245
|
+
REPORT_META_NAMESPACE
|
|
246
|
+
)
|
|
247
|
+
expect(subtotal).toMatchObject({
|
|
248
|
+
preset: 'subtotal',
|
|
249
|
+
aggregate: 'sum',
|
|
250
|
+
expand: 'none',
|
|
251
|
+
rowParent: { row: 1, col: 0 }
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('未给落点地址时回退到当前选区', async () => {
|
|
256
|
+
const { designer, workbook } = createDesigner()
|
|
257
|
+
const dataset = await seedDataset(designer)
|
|
258
|
+
workbook.activeSheet.selectCell({ row: 3, col: 2 })
|
|
259
|
+
|
|
260
|
+
designer.bindField(dataset.id, 'amount')
|
|
261
|
+
expect(workbook.activeSheet.getCellMeta({ row: 3, col: 2 }, REPORT_META_NAMESPACE)).toBeTruthy()
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('useReportDesigner:角色徽章渲染 hook(ADR-0004 首个消费者)', () => {
|
|
266
|
+
it('绑定格返回徽章布局(renderDefault: false),未绑定格回落默认渲染', async () => {
|
|
267
|
+
const { designer, workbook } = createDesigner()
|
|
268
|
+
const dataset = designer.addDataset('c1')
|
|
269
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT customer FROM orders' })
|
|
270
|
+
await designer.describeDataset(dataset.id)
|
|
271
|
+
designer.bindField(dataset.id, 'customer', { row: 0, col: 0 })
|
|
272
|
+
|
|
273
|
+
expect(designer.resolveCellRenderer({ row: 0, col: 1 }, undefined)).toBeUndefined()
|
|
274
|
+
|
|
275
|
+
const layout = designer.resolveCellRenderer({ row: 0, col: 0 }, undefined)
|
|
276
|
+
expect(layout).toBeTruthy()
|
|
277
|
+
expect(layout!.renderDefault).toBe(false)
|
|
278
|
+
expect(layout!.rootContainer).toBeTruthy()
|
|
279
|
+
// 根容器不得挂 percentCalc 宽高(VTable 会与 cellSize Math.max 出 NaN)
|
|
280
|
+
expect(layout!.rootContainer.attribute.width).toBeUndefined()
|
|
281
|
+
expect(layout!.rootContainer.attribute.height).toBeUndefined()
|
|
282
|
+
expect(layout!.rootContainer.attribute.fill).toBeUndefined()
|
|
283
|
+
|
|
284
|
+
const style = designer.resolveCellStyle({ row: 0, col: 0 }, undefined)
|
|
285
|
+
expect(style?.fill).toEqual({ color: '#d1fae5' })
|
|
286
|
+
|
|
287
|
+
// 模型与快照无 renderer 残留
|
|
288
|
+
expect(workbook.activeSheet.getCellData({ row: 0, col: 0 })).toBeUndefined()
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('useReportDesigner:getTemplate', () => {
|
|
293
|
+
it('返回含 meta 绑定与内嵌数据集定义的 Report Template', async () => {
|
|
294
|
+
const { designer } = createDesigner([MYSQL, PG])
|
|
295
|
+
const orders = designer.addDataset('c1')
|
|
296
|
+
designer.updateDataset(orders.id, {
|
|
297
|
+
label: '销售明细',
|
|
298
|
+
sql: 'SELECT customer FROM orders WHERE customer LIKE ${keyword}',
|
|
299
|
+
paramOverrides: { keyword: { label: '客户关键词' } },
|
|
300
|
+
fieldOverrides: { customer: { label: '客户名称' } }
|
|
301
|
+
})
|
|
302
|
+
await designer.describeDataset(orders.id)
|
|
303
|
+
designer.bindField(orders.id, 'customer', { row: 1, col: 0 })
|
|
304
|
+
|
|
305
|
+
const template = designer.getTemplate()
|
|
306
|
+
|
|
307
|
+
expect(template.version).toBe(1)
|
|
308
|
+
|
|
309
|
+
// meta 绑定随快照吐出
|
|
310
|
+
const meta = template.meta ?? []
|
|
311
|
+
expect(meta).toHaveLength(1)
|
|
312
|
+
expect(meta[0]).toMatchObject({ row: 1, col: 0, namespace: 'report' })
|
|
313
|
+
expect((meta[0]!.payload as ReportBinding).field).toBe('customer')
|
|
314
|
+
|
|
315
|
+
// 数据集内嵌连接对象(无 connectionId / fields 缓存泄漏)
|
|
316
|
+
expect(template.datasets).toHaveLength(1)
|
|
317
|
+
const dataset = template.datasets![0]!
|
|
318
|
+
expect(dataset).toEqual({
|
|
319
|
+
id: orders.id,
|
|
320
|
+
label: '销售明细',
|
|
321
|
+
connection: MYSQL,
|
|
322
|
+
sql: 'SELECT customer FROM orders WHERE customer LIKE ${keyword}',
|
|
323
|
+
paramOverrides: { keyword: { label: '客户关键词' } },
|
|
324
|
+
fieldOverrides: { customer: { label: '客户名称' } }
|
|
325
|
+
})
|
|
326
|
+
expect(dataset.connection).not.toBe(MYSQL)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('无匹配连接的数据集不吐出(凭据不悬空)', () => {
|
|
330
|
+
const { designer } = createDesigner([MYSQL])
|
|
331
|
+
const orphan = designer.addDataset('c-missing')
|
|
332
|
+
designer.updateDataset(orphan.id, { sql: 'SELECT 1' })
|
|
333
|
+
|
|
334
|
+
expect(designer.getTemplate().datasets).toEqual([])
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
describe('useReportDesigner:Action Pill 就地编辑(角色 / 聚合 / 条件规则 / 清除绑定)', () => {
|
|
339
|
+
async function seedAndBind(
|
|
340
|
+
designer: UseReportDesignerReturn,
|
|
341
|
+
addr: { row: number; col: number }
|
|
342
|
+
) {
|
|
343
|
+
const dataset = designer.addDataset('c1')
|
|
344
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
345
|
+
await designer.describeDataset(dataset.id)
|
|
346
|
+
designer.bindField(dataset.id, 'amount', addr)
|
|
347
|
+
return dataset
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
it('预设切换写入 presetBindingDefaults 组合值;聚合切换同步 expand 缺省', async () => {
|
|
351
|
+
const { designer, workbook } = createDesigner()
|
|
352
|
+
await seedAndBind(designer, { row: 0, col: 0 })
|
|
353
|
+
workbook.activeSheet.selectCell({ row: 0, col: 0 })
|
|
354
|
+
|
|
355
|
+
designer.patchActiveBinding(roleBindingDefaults('subtotal'))
|
|
356
|
+
let binding = designer.activeBinding.value
|
|
357
|
+
expect(binding).toMatchObject({ preset: 'subtotal', aggregate: 'sum', expand: 'none' })
|
|
358
|
+
|
|
359
|
+
designer.patchActiveBinding({ aggregate: 'avg', expand: 'none' })
|
|
360
|
+
binding = designer.activeBinding.value
|
|
361
|
+
expect(binding).toMatchObject({ aggregate: 'avg', expand: 'none' })
|
|
362
|
+
expect(binding?.preset).toBeUndefined()
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('条件规则与排序经 patch 写入绑定;拓扑条目跟随 meta 变更', async () => {
|
|
366
|
+
const { designer, workbook } = createDesigner()
|
|
367
|
+
const dataset = await seedAndBind(designer, { row: 0, col: 0 })
|
|
368
|
+
workbook.activeSheet.selectCell({ row: 0, col: 0 })
|
|
369
|
+
|
|
370
|
+
designer.patchActiveBinding({
|
|
371
|
+
sort: 'desc',
|
|
372
|
+
conditionalRules: [{ operator: 'gt', value: 100, style: { font: { color: '#DC2626' } } }]
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
376
|
+
{ row: 0, col: 0 },
|
|
377
|
+
REPORT_META_NAMESPACE
|
|
378
|
+
)
|
|
379
|
+
expect(binding?.sort).toBe('desc')
|
|
380
|
+
expect(binding?.conditionalRules).toHaveLength(1)
|
|
381
|
+
|
|
382
|
+
const entries = designer.bindingEntries.value
|
|
383
|
+
expect(entries).toHaveLength(1)
|
|
384
|
+
expect(entries[0]).toMatchObject({ addr: { row: 0, col: 0 }, binding: { dataset: dataset.id } })
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('点选式父格编辑写入模板存储的 rowParent', async () => {
|
|
388
|
+
const { designer, workbook } = createDesigner()
|
|
389
|
+
const dataset = designer.addDataset('c1')
|
|
390
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
391
|
+
await designer.describeDataset(dataset.id)
|
|
392
|
+
|
|
393
|
+
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
394
|
+
designer.bindField(dataset.id, 'amount', { row: 2, col: 1 })
|
|
395
|
+
|
|
396
|
+
workbook.activeSheet.selectCell({ row: 2, col: 1 })
|
|
397
|
+
designer.startParentPick('row')
|
|
398
|
+
designer.pickParentAt({ row: 1, col: 0 })
|
|
399
|
+
|
|
400
|
+
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
401
|
+
{ row: 2, col: 1 },
|
|
402
|
+
REPORT_META_NAMESPACE
|
|
403
|
+
)
|
|
404
|
+
expect(binding?.rowParent).toEqual({ row: 1, col: 0 })
|
|
405
|
+
expect(designer.parentPick.value).toBeNull()
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it('清除绑定移除 Cell Meta;行 / 列方向父格标签读取绑定存储值', async () => {
|
|
409
|
+
const { designer, workbook } = createDesigner()
|
|
410
|
+
const dataset = designer.addDataset('c1')
|
|
411
|
+
designer.updateDataset(dataset.id, { sql: 'SELECT customer, amount FROM orders' })
|
|
412
|
+
await designer.describeDataset(dataset.id)
|
|
413
|
+
|
|
414
|
+
// 同列上方纵向扩展绑定 + 下方明细(rowParent 推断到上方格)
|
|
415
|
+
designer.bindField(dataset.id, 'customer', { row: 1, col: 0 })
|
|
416
|
+
designer.bindField(dataset.id, 'amount', { row: 2, col: 0 })
|
|
417
|
+
|
|
418
|
+
workbook.activeSheet.selectCell({ row: 2, col: 0 })
|
|
419
|
+
expect(designer.resolvedRowParentLabel.value).toBe('A2')
|
|
420
|
+
|
|
421
|
+
designer.removeActiveBinding()
|
|
422
|
+
expect(
|
|
423
|
+
workbook.activeSheet.getCellMeta({ row: 2, col: 0 }, REPORT_META_NAMESPACE)
|
|
424
|
+
).toBeUndefined()
|
|
425
|
+
expect(designer.activeBinding.value).toBeUndefined()
|
|
426
|
+
expect(designer.boundKeys.value.has(`${dataset.id}:amount`)).toBe(false)
|
|
427
|
+
})
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
describe('useReportDesigner:template 版本校验', () => {
|
|
431
|
+
it('载入缺少 version 的模板时抛可读错误', () => {
|
|
432
|
+
const { connector } = createStubConnector()
|
|
433
|
+
const connections = ref<DataConnection[]>([MYSQL])
|
|
434
|
+
expect(() =>
|
|
435
|
+
effectScope().run(() =>
|
|
436
|
+
useReportDesigner({
|
|
437
|
+
props: {
|
|
438
|
+
connector,
|
|
439
|
+
template: {
|
|
440
|
+
cells: [],
|
|
441
|
+
styles: [],
|
|
442
|
+
merges: [],
|
|
443
|
+
frozen: { rows: 0, cols: 0 },
|
|
444
|
+
rows: 24,
|
|
445
|
+
cols: 10
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
connections
|
|
449
|
+
})
|
|
450
|
+
)
|
|
451
|
+
).toThrow(IncompatibleTemplateVersionError)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
it('载入高于当前支持 version 的模板时抛可读错误', () => {
|
|
455
|
+
const { connector } = createStubConnector()
|
|
456
|
+
const connections = ref<DataConnection[]>([MYSQL])
|
|
457
|
+
expect(() =>
|
|
458
|
+
effectScope().run(() =>
|
|
459
|
+
useReportDesigner({
|
|
460
|
+
props: {
|
|
461
|
+
connector,
|
|
462
|
+
template: {
|
|
463
|
+
version: 99,
|
|
464
|
+
cells: [],
|
|
465
|
+
styles: [],
|
|
466
|
+
merges: [],
|
|
467
|
+
frozen: { rows: 0, cols: 0 },
|
|
468
|
+
rows: 24,
|
|
469
|
+
cols: 10
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
connections
|
|
473
|
+
})
|
|
474
|
+
)
|
|
475
|
+
).toThrow(/高于当前支持/)
|
|
476
|
+
})
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
describe('useReportDesigner:template prop 载入既有模板', () => {
|
|
480
|
+
function buildTemplate(): ReportTemplate {
|
|
481
|
+
const group = createReportBindingFixture()
|
|
482
|
+
return {
|
|
483
|
+
version: 1,
|
|
484
|
+
cells: [{ row: 0, col: 0, v: '客户' }],
|
|
485
|
+
styles: [],
|
|
486
|
+
merges: [],
|
|
487
|
+
frozen: { rows: 0, cols: 0 },
|
|
488
|
+
rows: 24,
|
|
489
|
+
cols: 10,
|
|
490
|
+
meta: [{ row: 1, col: 0, namespace: REPORT_META_NAMESPACE, payload: group }],
|
|
491
|
+
datasets: [
|
|
492
|
+
{
|
|
493
|
+
id: 'ds-template',
|
|
494
|
+
label: '模板数据集',
|
|
495
|
+
connection: { ...PG, id: 'conn-template' },
|
|
496
|
+
sql: 'SELECT customer, amount FROM orders',
|
|
497
|
+
paramOverrides: { keyword: { label: '客户关键词' } },
|
|
498
|
+
fieldOverrides: { customer: { label: '客户名称' } }
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function createReportBindingFixture(): ReportBinding {
|
|
505
|
+
return {
|
|
506
|
+
dataset: 'ds-template',
|
|
507
|
+
field: 'customer',
|
|
508
|
+
preset: 'groupHeader',
|
|
509
|
+
aggregate: 'group',
|
|
510
|
+
expand: 'down',
|
|
511
|
+
sort: 'none',
|
|
512
|
+
conditionalRules: [{ operator: 'eq', value: '甲公司', style: { fill: { color: '#FFCCCC' } } }]
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
it('恢复网格绑定与设计态数据集;内嵌连接按 id 合并进 v-model 列表;describe 恢复字段缓存', async () => {
|
|
517
|
+
const { designer, connections, workbook, calls } = createDesigner([MYSQL], {
|
|
518
|
+
template: buildTemplate()
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
// 绑定随快照恢复(含条件规则)
|
|
522
|
+
const binding = workbook.activeSheet.getCellMeta<ReportBinding>(
|
|
523
|
+
{ row: 1, col: 0 },
|
|
524
|
+
REPORT_META_NAMESPACE
|
|
525
|
+
)
|
|
526
|
+
expect(binding).toEqual(createReportBindingFixture())
|
|
527
|
+
|
|
528
|
+
// 数据集还原为设计态(connectionId 引用;fields 缓存经 describe 恢复)
|
|
529
|
+
expect(designer.datasets.value).toHaveLength(1)
|
|
530
|
+
expect(designer.datasets.value[0]).toMatchObject({
|
|
531
|
+
id: 'ds-template',
|
|
532
|
+
label: '模板数据集',
|
|
533
|
+
connectionId: 'conn-template',
|
|
534
|
+
sql: 'SELECT customer, amount FROM orders'
|
|
535
|
+
})
|
|
536
|
+
await Promise.resolve()
|
|
537
|
+
expect(calls.describe).toHaveLength(1)
|
|
538
|
+
expect(calls.describe[0]!.connection.id).toBe('conn-template')
|
|
539
|
+
|
|
540
|
+
// 内嵌连接按 id 合并(已有连接不重复追加)
|
|
541
|
+
expect(connections.value.map((item) => item.id)).toEqual(['c1', 'conn-template'])
|
|
542
|
+
|
|
543
|
+
// catalog 应用 fieldOverrides(字段面板与徽章 label 数据源)
|
|
544
|
+
const catalog = designer.catalog.value
|
|
545
|
+
expect(catalog[0]!.fields.map((field) => field.label)).toEqual(['客户名称', '金额'])
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
it('载入后 getTemplate 往返保持绑定与数据集定义;宿主已有同 id 连接时不覆盖', async () => {
|
|
549
|
+
const template = buildTemplate()
|
|
550
|
+
const { designer } = createDesigner(
|
|
551
|
+
[MYSQL, { ...PG, id: 'conn-template', label: '宿主连接' }],
|
|
552
|
+
{ template }
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
// 宿主同 id 连接优先(单一事实源),模板内嵌连接不覆盖
|
|
556
|
+
const roundTrip = designer.getTemplate()
|
|
557
|
+
expect(roundTrip.datasets).toHaveLength(1)
|
|
558
|
+
expect(roundTrip.datasets![0]!.connection.label).toBe('宿主连接')
|
|
559
|
+
|
|
560
|
+
// 绑定完整往返
|
|
561
|
+
const meta = roundTrip.meta ?? []
|
|
562
|
+
expect(meta).toHaveLength(1)
|
|
563
|
+
expect(meta[0]!.payload).toEqual(createReportBindingFixture())
|
|
564
|
+
})
|
|
565
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CellAddress } from '@veltra/sheet-core'
|
|
2
|
+
import {
|
|
3
|
+
CustomLayout,
|
|
4
|
+
type ResolveCellRenderer,
|
|
5
|
+
type ResolveCellStyleHook
|
|
6
|
+
} from '@veltra/sheet-core'
|
|
7
|
+
|
|
8
|
+
import { formatBindingPlaceholder, inferReportPreset } from '../../report/binding'
|
|
9
|
+
import type { ReportBinding, ReportPreset } from '../../report/types'
|
|
10
|
+
|
|
11
|
+
/** 绑定徽章预设配色(设计态富渲染徽章;canvas 绘制需具体色值,无法消费 CSS 变量) */
|
|
12
|
+
export const REPORT_PRESET_BADGE_COLORS: Record<ReportPreset, { bg: string; fg: string }> = {
|
|
13
|
+
groupHeader: { bg: '#dbeafe', fg: '#1d4ed8' },
|
|
14
|
+
detail: { bg: '#d1fae5', fg: '#047857' },
|
|
15
|
+
subtotal: { bg: '#fef3c7', fg: '#b45309' },
|
|
16
|
+
grandTotal: { bg: '#ffe4e6', fg: '#be123c' },
|
|
17
|
+
cross: { bg: '#ede9fe', fg: '#6d28d9' }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CUSTOM_PRESET_BADGE_COLOR = { bg: '#f3f4f6', fg: '#374151' }
|
|
21
|
+
|
|
22
|
+
/** @deprecated 05 将更名 */
|
|
23
|
+
export const REPORT_ROLE_BADGE_COLORS = REPORT_PRESET_BADGE_COLORS
|
|
24
|
+
|
|
25
|
+
function resolveBadgeColor(binding: ReportBinding): { bg: string; fg: string } {
|
|
26
|
+
const preset = inferReportPreset(binding)
|
|
27
|
+
return preset ? REPORT_PRESET_BADGE_COLORS[preset] : CUSTOM_PRESET_BADGE_COLOR
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 绑定格角色底色(走 resolveCellStyle,保留 VTable 默认边框绘制) */
|
|
31
|
+
export function createBindingBadgeStyleResolver(
|
|
32
|
+
getBindingAt: (addr: CellAddress) => ReportBinding | undefined
|
|
33
|
+
): ResolveCellStyleHook {
|
|
34
|
+
return (addr, base) => {
|
|
35
|
+
const binding = getBindingAt(addr)
|
|
36
|
+
if (!binding) return base
|
|
37
|
+
|
|
38
|
+
const { bg } = resolveBadgeColor(binding)
|
|
39
|
+
return { ...base, fill: { color: bg } }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 绑定格富渲染徽章(ADR-0004 `resolveCellRenderer` 首个消费者):
|
|
45
|
+
* 绑定单元格由纯文本占位符升级为带预设色彩的徽章(底色 + 占位文案),
|
|
46
|
+
* 未绑定格返回 `undefined` 回落默认渲染。
|
|
47
|
+
*/
|
|
48
|
+
export function createBindingBadgeRenderer(
|
|
49
|
+
getBindingAt: (addr: CellAddress) => ReportBinding | undefined,
|
|
50
|
+
resolveLabel?: (datasetId: string, fieldName: string) => string
|
|
51
|
+
): ResolveCellRenderer {
|
|
52
|
+
return (addr) => {
|
|
53
|
+
const binding = getBindingAt(addr)
|
|
54
|
+
if (!binding) return undefined
|
|
55
|
+
|
|
56
|
+
const { fg } = resolveBadgeColor(binding)
|
|
57
|
+
const rootContainer = new CustomLayout.Container({
|
|
58
|
+
display: 'flex',
|
|
59
|
+
flexDirection: 'row',
|
|
60
|
+
justifyContent: 'center',
|
|
61
|
+
alignItems: 'center'
|
|
62
|
+
})
|
|
63
|
+
rootContainer.add(
|
|
64
|
+
new CustomLayout.Text({
|
|
65
|
+
text: formatBindingPlaceholder(binding, resolveLabel),
|
|
66
|
+
fontSize: 11,
|
|
67
|
+
fontWeight: 600,
|
|
68
|
+
fill: fg
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
return { rootContainer, renderDefault: false }
|
|
72
|
+
}
|
|
73
|
+
}
|