@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,603 +0,0 @@
1
- import type { CellAddress } from '@veltra/sheet-core'
2
- import { Workbook } from '@veltra/sheet-core'
3
- import type { ResolveCellRenderer, ResolveCellStyleHook } from '@veltra/sheet-core/grid'
4
- import { computed, onScopeDispose, ref, watch, type ComputedRef, type Ref } from 'vue'
5
-
6
- import {
7
- REPORT_META_NAMESPACE,
8
- applyReportPreset,
9
- createReportBinding,
10
- formatCellAddress,
11
- inferDropPreset,
12
- inferParentsForPreset,
13
- isColGroupBinding,
14
- isHorizontalExpandBinding,
15
- isRowGroupBinding,
16
- isVerticalExpandBinding,
17
- listColParentCandidates,
18
- listRowParentCandidates,
19
- prefersGroupParent,
20
- presetStillApplies
21
- } from '../../report/binding'
22
- import type { DataConnection, QueryResult, Result } from '../../report/connector'
23
- import { buildParamDefs } from '../../report/params'
24
- import {
25
- assertCompatibleTemplateVersion,
26
- createReportTemplate,
27
- getTemplateDatasets,
28
- resolveParamDefaults,
29
- type ReportDatasetDef,
30
- type ReportTemplate
31
- } from '../../report/template'
32
- import type { DatasetCatalogItem, DatasetField, ReportBinding } from '../../report/types'
33
- import type { ReportDesignerProps } from '../../types'
34
- import { createBindingBadgeRenderer, createBindingBadgeStyleResolver } from './binding-badge'
35
- import type { TopologyBindingEntry } from './designer/topology'
36
-
37
- /**
38
- * 设计器内部数据集:以 `connectionId` 引用连接(连接列表经 `v-model:connections`
39
- * 注入,是单一事实源);`getTemplate()` 吐出模板时解析为内嵌连接对象(`ReportDatasetDef`)。
40
- */
41
- export interface DesignerDataset {
42
- id: string
43
- label: string
44
- connectionId: string
45
- sql: string
46
- /** 参数元数据覆盖(label / 类型 / 默认值 / 选项) */
47
- paramOverrides?: ReportDatasetDef['paramOverrides']
48
- /** 字段中文名覆盖(name → label) */
49
- fieldOverrides?: ReportDatasetDef['fieldOverrides']
50
- /** describe 解析的字段缓存(字段面板 catalog 的数据源;fieldOverrides 在 catalog 层应用) */
51
- fields?: DatasetField[]
52
- }
53
-
54
- /** 数据中枢 drawer 的消费面(`UseReportDesignerReturn` 的子集) */
55
- export interface DatasetHubController {
56
- connections: Ref<DataConnection[]>
57
- datasets: Ref<DesignerDataset[]>
58
- addConnection: (connection: DataConnection) => void
59
- updateConnection: (connection: DataConnection) => void
60
- /** 删除连接并级联删除其数据集 */
61
- removeConnection: (connectionId: string) => void
62
- addDataset: (connectionId: string) => DesignerDataset
63
- updateDataset: (datasetId: string, patch: Partial<Omit<DesignerDataset, 'id'>>) => void
64
- removeDataset: (datasetId: string) => void
65
- /** 真实测试连接(契约无状态,新建草稿同样可测) */
66
- testConnection: (connection: DataConnection) => Promise<Result<void>>
67
- /** 经连接器 describe 解析字段并写入数据集字段缓存 */
68
- describeDataset: (datasetId: string) => Promise<Result<DatasetField[]>>
69
- /** 按参数默认值经连接器取数(数据集记录预览) */
70
- previewDataset: (datasetId: string) => Promise<Result<QueryResult>>
71
- }
72
-
73
- export type ParentPickMode = 'row' | 'col'
74
-
75
- export interface ParentPickState {
76
- mode: ParentPickMode
77
- source: CellAddress
78
- }
79
-
80
- export interface UseReportDesignerReturn extends DatasetHubController {
81
- workbook: ComputedRef<Workbook>
82
- /** 字段面板目录(数据集 + 应用 fieldOverrides 后的字段 label) */
83
- catalog: ComputedRef<DatasetCatalogItem[]>
84
- /** 已绑定字段键集合:`${datasetId}:${fieldName}` */
85
- boundKeys: ComputedRef<Set<string>>
86
- /** 当前选区地址(跟随模型 selection-change) */
87
- activeCell: Ref<CellAddress | null>
88
- /** 当前选区 A1 标签 */
89
- selectionLabel: ComputedRef<string>
90
- /** 当前选区绑定(跟随 meta-change) */
91
- activeBinding: ComputedRef<ReportBinding | undefined>
92
- /** 当前选区绑定字段的数据类型(条件规则对话框控件映射用;缺省 number) */
93
- activeFieldType: ComputedRef<DatasetField['type']>
94
- /** 当前绑定所属数据集的字段列表(条件规则求值字段选择) */
95
- activeDatasetFields: ComputedRef<DatasetField[]>
96
- /** 当前选区绑定的行方向父格 A1 标签(无父格显示 —) */
97
- resolvedRowParentLabel: ComputedRef<string>
98
- /** 当前选区绑定的列方向父格 A1 标签(无父格显示 —) */
99
- resolvedColParentLabel: ComputedRef<string>
100
- /** 父格点选态(进入后在网格上点目标格写入父格) */
101
- parentPick: Ref<ParentPickState | null>
102
- /** 行方向父格下拉候选(同数据集纵向扩展绑定格) */
103
- rowParentCandidates: ComputedRef<CellAddress[]>
104
- /** 列方向父格下拉候选(同数据集横向扩展绑定格) */
105
- colParentCandidates: ComputedRef<CellAddress[]>
106
- /** 全部绑定条目(拓扑连线数据源;跟随 meta-change) */
107
- bindingEntries: ComputedRef<TopologyBindingEntry[]>
108
- /** meta 变更计数(网格覆层组件的同步信号) */
109
- metaTick: Ref<number>
110
- getBindingAt: (addr: CellAddress) => ReportBinding | undefined
111
- /** 字段 label 解析(catalog O(1) 查找;Action Pill 摘要与徽章共用) */
112
- resolveFieldLabel: (datasetId: string, fieldName: string) => string
113
- /** 拖拽/点击落格写 Cell Meta 绑定(角色推导与 playground 设计器一致) */
114
- bindField: (datasetId: string, fieldName: string, addr?: CellAddress) => void
115
- /** 就地修补当前选区绑定(Action Pill:预设 / 展开方向 / 父格 / 聚合 / 排序 / 条件规则) */
116
- patchActiveBinding: (patch: Partial<ReportBinding>) => void
117
- /** 进入父格点选态 */
118
- startParentPick: (mode: ParentPickMode) => void
119
- /** 取消父格点选态 */
120
- cancelParentPick: () => void
121
- /** 父格点选:将目标格写入当前编辑格的 rowParent / colParent */
122
- pickParentAt: (target: CellAddress) => void
123
- /** 清除行 / 列方向父格 */
124
- clearParent: (mode: ParentPickMode) => void
125
- /** 清除当前选区绑定 */
126
- removeActiveBinding: () => void
127
- /** 绑定格角色底色样式 hook(与徽章 renderer 配套) */
128
- resolveCellStyle: ResolveCellStyleHook
129
- /** 绑定格角色徽章渲染 hook(ADR-0004) */
130
- resolveCellRenderer: ResolveCellRenderer
131
- /** 取回含 meta 绑定与内嵌数据集定义的 Report Template */
132
- getTemplate: () => ReportTemplate
133
- }
134
-
135
- export interface UseReportDesignerOptions {
136
- props: ReportDesignerProps
137
- /** `v-model:connections` 可写代理(get 读 props,set 触发 `update:connections`) */
138
- connections: Ref<DataConnection[]>
139
- }
140
-
141
- /**
142
- * UReportDesigner 的 headless 编排:连接 / 数据集状态、绑定写 Cell Meta、
143
- * 角色徽章渲染 hook、`getTemplate()` 模板吐出。不持有 DOM / 网格引用。
144
- */
145
- export function useReportDesigner(options: UseReportDesignerOptions): UseReportDesignerReturn {
146
- const { props, connections } = options
147
-
148
- /** 设计态工作簿:宿主可注入(USheet / UReportViewer 先例),缺省内部自建 */
149
- const internalWorkbook = new Workbook()
150
- const workbook = computed(() => props.workbook ?? internalWorkbook)
151
-
152
- const datasets = ref<DesignerDataset[]>([])
153
-
154
- /** meta 变更计数:boundKeys 等派生状态的响应式触发(绑定经命令系统写入后由模型发 meta-change) */
155
- const metaTick = ref(0)
156
- const parentPick = ref<ParentPickState | null>(null)
157
- const offMeta = workbook.value.activeSheet.on('meta-change', () => {
158
- metaTick.value++
159
- })
160
- onScopeDispose(offMeta)
161
-
162
- /** 稳定选区地址引用(值未变时不替换对象,避免触发下游 watcher) */
163
- const activeCell = ref<CellAddress | null>(null)
164
- function syncActiveCell(): void {
165
- const next = workbook.value.activeSheet.getSelection().activeCell ?? null
166
- const prev = activeCell.value
167
- if (!next) {
168
- activeCell.value = null
169
- return
170
- }
171
- if (prev && prev.row === next.row && prev.col === next.col) return
172
- activeCell.value = { row: next.row, col: next.col }
173
- }
174
- const offSelection = workbook.value.activeSheet.on('selection-change', syncActiveCell)
175
- onScopeDispose(offSelection)
176
- syncActiveCell()
177
-
178
- const selectionLabel = computed(() =>
179
- activeCell.value ? formatCellAddress(activeCell.value) : '—'
180
- )
181
-
182
- const catalog = computed((): DatasetCatalogItem[] =>
183
- datasets.value.map((dataset) => ({
184
- id: dataset.id,
185
- label: dataset.label,
186
- fields: (dataset.fields ?? []).map((field) => ({
187
- ...field,
188
- label: dataset.fieldOverrides?.[field.name]?.label ?? field.label
189
- }))
190
- }))
191
- )
192
-
193
- /** 字段 label 的 O(1) 查找表(徽章 hook 性能契约) */
194
- const fieldLabelMap = computed(() => {
195
- const map = new Map<string, string>()
196
- for (const dataset of catalog.value) {
197
- for (const field of dataset.fields) map.set(`${dataset.id}:${field.name}`, field.label)
198
- }
199
- return map
200
- })
201
-
202
- function resolveFieldLabel(datasetId: string, fieldName: string): string {
203
- return fieldLabelMap.value.get(`${datasetId}:${fieldName}`) ?? fieldName
204
- }
205
-
206
- const boundKeys = computed(() => {
207
- metaTick.value
208
- const keys = new Set<string>()
209
- for (const [, namespace, payload] of workbook.value.activeSheet.entriesCellMeta()) {
210
- if (namespace !== REPORT_META_NAMESPACE) continue
211
- const binding = payload as ReportBinding
212
- keys.add(`${binding.dataset}:${binding.field}`)
213
- }
214
- return keys
215
- })
216
-
217
- function getBindingAt(addr: CellAddress): ReportBinding | undefined {
218
- return workbook.value.activeSheet.getCellMeta<ReportBinding>(addr, REPORT_META_NAMESPACE)
219
- }
220
-
221
- function bindField(datasetId: string, fieldName: string, addr?: CellAddress): void {
222
- const dataset = catalog.value.find((item) => item.id === datasetId)
223
- if (!dataset) return
224
-
225
- const field = dataset.fields.find((item) => item.name === fieldName)
226
- const target = addr ?? workbook.value.activeSheet.getSelection().activeCell
227
- if (!target) return
228
-
229
- const preset = inferDropPreset(target, field?.type ?? 'string', getBindingAt)
230
- let binding = createReportBinding(dataset, fieldName)
231
- binding = applyReportPreset(binding, preset)
232
-
233
- const parents = inferParentsForPreset(target, preset, getBindingAt)
234
- if (parents.rowParent) binding.rowParent = parents.rowParent
235
- if (parents.colParent) binding.colParent = parents.colParent
236
-
237
- workbook.value.activeSheet.setCellMeta(target, REPORT_META_NAMESPACE, binding)
238
- }
239
-
240
- // ---- Action Pill / 拓扑连线(playground 设计器平移,行为不变) ----
241
-
242
- const activeBinding = computed((): ReportBinding | undefined => {
243
- metaTick.value
244
- const cell = activeCell.value
245
- if (!cell) return undefined
246
- return getBindingAt(cell)
247
- })
248
-
249
- const activeFieldType = computed((): DatasetField['type'] => {
250
- const binding = activeBinding.value
251
- if (!binding) return 'number'
252
- const dataset = catalog.value.find((item) => item.id === binding.dataset)
253
- const field = dataset?.fields.find((item) => item.name === binding.field)
254
- return field?.type ?? 'number'
255
- })
256
-
257
- const activeDatasetFields = computed((): DatasetField[] => {
258
- const binding = activeBinding.value
259
- if (!binding) return []
260
- return catalog.value.find((item) => item.id === binding.dataset)?.fields ?? []
261
- })
262
-
263
- const resolvedRowParentLabel = computed(() => {
264
- metaTick.value
265
- const binding = activeBinding.value
266
- if (!binding?.rowParent) return '—'
267
- return formatCellAddress(binding.rowParent)
268
- })
269
-
270
- const resolvedColParentLabel = computed(() => {
271
- metaTick.value
272
- const binding = activeBinding.value
273
- if (!binding?.colParent) return '—'
274
- return formatCellAddress(binding.colParent)
275
- })
276
-
277
- const rowParentCandidates = computed((): CellAddress[] => {
278
- metaTick.value
279
- const binding = activeBinding.value
280
- if (!binding) return []
281
- return listRowParentCandidates(binding, bindingEntries.value, {
282
- preferGroup: prefersGroupParent(binding)
283
- })
284
- })
285
-
286
- const colParentCandidates = computed((): CellAddress[] => {
287
- metaTick.value
288
- const binding = activeBinding.value
289
- if (!binding) return []
290
- return listColParentCandidates(binding, bindingEntries.value, {
291
- preferGroup: prefersGroupParent(binding)
292
- })
293
- })
294
-
295
- const bindingEntries = computed((): TopologyBindingEntry[] => {
296
- metaTick.value
297
- const entries: TopologyBindingEntry[] = []
298
- for (const [addr, namespace, payload] of workbook.value.activeSheet.entriesCellMeta()) {
299
- if (namespace !== REPORT_META_NAMESPACE) continue
300
- entries.push({ addr, binding: payload as ReportBinding })
301
- }
302
- return entries
303
- })
304
-
305
- function parentNeedsCorrection(addr: CellAddress | undefined): boolean {
306
- if (!addr) return true
307
- const parent = getBindingAt(addr)
308
- return !parent || parent.aggregate === 'list'
309
- }
310
-
311
- function patchActiveBinding(patch: Partial<ReportBinding>): void {
312
- const cell = activeCell.value
313
- const binding = activeBinding.value
314
- if (!cell || !binding) return
315
-
316
- const next: ReportBinding = { ...binding, ...patch }
317
- if ('rowParent' in patch && patch.rowParent === undefined) delete next.rowParent
318
- if ('colParent' in patch && patch.colParent === undefined) delete next.colParent
319
- if ('drill' in patch && patch.drill === undefined) delete next.drill
320
- if (
321
- ('aggregate' in patch || 'expand' in patch) &&
322
- !('preset' in patch) &&
323
- !presetStillApplies(next)
324
- ) {
325
- delete next.preset
326
- }
327
-
328
- if (patch.preset === 'subtotal') {
329
- const inferred = inferParentsForPreset(cell, 'subtotal', getBindingAt)
330
- if (parentNeedsCorrection(next.rowParent)) {
331
- if (inferred.rowParent) next.rowParent = inferred.rowParent
332
- else delete next.rowParent
333
- }
334
- if (parentNeedsCorrection(next.colParent)) {
335
- if (inferred.colParent) next.colParent = inferred.colParent
336
- else delete next.colParent
337
- }
338
- }
339
-
340
- workbook.value.activeSheet.setCellMeta(cell, REPORT_META_NAMESPACE, next)
341
- }
342
-
343
- function startParentPick(mode: ParentPickMode): void {
344
- const cell = activeCell.value
345
- if (!cell || !activeBinding.value) return
346
- parentPick.value = { mode, source: { row: cell.row, col: cell.col } }
347
- }
348
-
349
- function cancelParentPick(): void {
350
- parentPick.value = null
351
- }
352
-
353
- function isValidParentTarget(
354
- mode: ParentPickMode,
355
- source: ReportBinding,
356
- target: ReportBinding | undefined
357
- ): boolean {
358
- if (!target) return false
359
- if (target.dataset !== source.dataset) return false
360
- const groupOnly = prefersGroupParent(source)
361
- if (mode === 'row') {
362
- return groupOnly ? isRowGroupBinding(target) : isVerticalExpandBinding(target)
363
- }
364
- return groupOnly ? isColGroupBinding(target) : isHorizontalExpandBinding(target)
365
- }
366
-
367
- function pickParentAt(target: CellAddress): void {
368
- const pick = parentPick.value
369
- if (!pick) return
370
- if (target.row === pick.source.row && target.col === pick.source.col) return
371
-
372
- const sourceBinding = getBindingAt(pick.source)
373
- if (!sourceBinding) {
374
- parentPick.value = null
375
- return
376
- }
377
-
378
- const targetBinding = getBindingAt(target)
379
- if (!isValidParentTarget(pick.mode, sourceBinding, targetBinding)) {
380
- workbook.value.activeSheet.selectCell(pick.source)
381
- return
382
- }
383
-
384
- const patch = pick.mode === 'row' ? { rowParent: { ...target } } : { colParent: { ...target } }
385
- const next: ReportBinding = { ...sourceBinding, ...patch }
386
- workbook.value.activeSheet.setCellMeta(pick.source, REPORT_META_NAMESPACE, next)
387
- parentPick.value = null
388
- workbook.value.activeSheet.selectCell(pick.source)
389
- }
390
-
391
- function clearParent(mode: ParentPickMode): void {
392
- if (mode === 'row') patchActiveBinding({ rowParent: undefined })
393
- else patchActiveBinding({ colParent: undefined })
394
- }
395
-
396
- function removeActiveBinding(): void {
397
- const cell = activeCell.value
398
- if (!cell) return
399
- workbook.value.activeSheet.clearCellMeta(cell, REPORT_META_NAMESPACE)
400
- }
401
-
402
- // ---- 数据中枢:连接 / 数据集 CRUD 与连接器调用 ----
403
-
404
- function addConnection(connection: DataConnection): void {
405
- connections.value = [...connections.value, connection]
406
- }
407
-
408
- function updateConnection(connection: DataConnection): void {
409
- connections.value = connections.value.map((item) =>
410
- item.id === connection.id ? connection : item
411
- )
412
- }
413
-
414
- function removeConnection(connectionId: string): void {
415
- connections.value = connections.value.filter((item) => item.id !== connectionId)
416
- // 级联删除其数据集(playground 设计器平移)
417
- datasets.value = datasets.value.filter((dataset) => dataset.connectionId !== connectionId)
418
- }
419
-
420
- let datasetSeq = 0
421
- function addDataset(connectionId: string): DesignerDataset {
422
- const dataset: DesignerDataset = {
423
- id: `dataset-${Date.now()}-${datasetSeq++}`,
424
- label: '新数据集',
425
- connectionId,
426
- sql: ''
427
- }
428
- datasets.value = [...datasets.value, dataset]
429
- return dataset
430
- }
431
-
432
- function updateDataset(datasetId: string, patch: Partial<Omit<DesignerDataset, 'id'>>): void {
433
- datasets.value = datasets.value.map((item) =>
434
- item.id === datasetId ? { ...item, ...patch } : item
435
- )
436
- }
437
-
438
- function removeDataset(datasetId: string): void {
439
- datasets.value = datasets.value.filter((item) => item.id !== datasetId)
440
- }
441
-
442
- function resolveDatasetConnection(dataset: DesignerDataset): DataConnection | undefined {
443
- return connections.value.find((item) => item.id === dataset.connectionId)
444
- }
445
-
446
- function datasetNotFound(): Result<never> {
447
- return { ok: false, error: { code: 'DATASET_NOT_FOUND', message: '数据集不存在' } }
448
- }
449
-
450
- function connectionNotFound(): Result<never> {
451
- return { ok: false, error: { code: 'CONNECTION_NOT_FOUND', message: '数据集所属连接不存在' } }
452
- }
453
-
454
- function testConnection(connection: DataConnection): Promise<Result<void>> {
455
- return props.connector.test(connection)
456
- }
457
-
458
- async function describeDataset(datasetId: string): Promise<Result<DatasetField[]>> {
459
- const dataset = datasets.value.find((item) => item.id === datasetId)
460
- if (!dataset) return datasetNotFound()
461
- const connection = resolveDatasetConnection(dataset)
462
- if (!connection) return connectionNotFound()
463
-
464
- const sql = dataset.sql
465
- const result = await props.connector.describe(connection, sql)
466
- // 并发守卫:describe 返回时 SQL 已再变更则丢弃过期结果
467
- if (result.ok) {
468
- const current = datasets.value.find((item) => item.id === datasetId)
469
- if (current && current.sql === sql) updateDataset(datasetId, { fields: result.data })
470
- }
471
- return result
472
- }
473
-
474
- async function previewDataset(datasetId: string): Promise<Result<QueryResult>> {
475
- const dataset = datasets.value.find((item) => item.id === datasetId)
476
- if (!dataset) return datasetNotFound()
477
- const connection = resolveDatasetConnection(dataset)
478
- if (!connection) return connectionNotFound()
479
-
480
- const values = resolveParamDefaults(buildParamDefs(dataset.sql, dataset.paramOverrides))
481
- return props.connector.query(connection, dataset.sql, values)
482
- }
483
-
484
- // ---- 渲染与模板 ----
485
-
486
- const resolveCellStyle = createBindingBadgeStyleResolver(getBindingAt)
487
- // 对齐访问器:hook 为长生命周期闭包而 activeSheet 可变(template 载入 / 宿主换 workbook),
488
- // 每次调用动态取当前 sheet 的有效样式(getEffectiveStyle 为 O(1) 列→行→格叠加)
489
- const resolveCellRenderer = createBindingBadgeRenderer(
490
- getBindingAt,
491
- resolveFieldLabel,
492
- (addr) => workbook.value.activeSheet.getEffectiveStyle(addr)?.align
493
- )
494
-
495
- function getTemplate(): ReportTemplate {
496
- const resolved: ReportDatasetDef[] = datasets.value.flatMap((dataset) => {
497
- const connection = resolveDatasetConnection(dataset)
498
- if (!connection) return []
499
- return [
500
- {
501
- id: dataset.id,
502
- label: dataset.label,
503
- connection: { ...connection },
504
- sql: dataset.sql,
505
- ...(dataset.paramOverrides ? { paramOverrides: dataset.paramOverrides } : {}),
506
- ...(dataset.fieldOverrides ? { fieldOverrides: dataset.fieldOverrides } : {})
507
- }
508
- ]
509
- })
510
- // `Sheet.snapshot()` 不产生 datasets 字段(restore 往返会丢失),吐出时附加内嵌数据集定义
511
- return createReportTemplate(workbook.value.activeSheet.snapshot(), resolved)
512
- }
513
-
514
- /**
515
- * 载入既有 Report Template 继续设计(`template` prop):
516
- * 快照恢复网格绑定(restore + restoreContent 与查看器同路径);
517
- * 内嵌数据集定义还原为设计态(connectionId 引用),内嵌连接按 id 合并进
518
- * v-model 连接列表(仅缺省追加,宿主列表是单一事实源);
519
- * describe 恢复字段缓存(字段面板数据源),业务错误忽略(字段留空,可在数据中枢重试)。
520
- */
521
- function loadTemplate(template: ReportTemplate): void {
522
- assertCompatibleTemplateVersion(template)
523
- const sheet = workbook.value.activeSheet
524
- sheet.restore(template)
525
- sheet.restoreContent(template)
526
- sheet.history.clear()
527
- syncActiveCell()
528
-
529
- const defs = getTemplateDatasets(template)
530
- datasets.value = defs.map((def) => ({
531
- id: def.id,
532
- label: def.label,
533
- connectionId: def.connection.id,
534
- sql: def.sql,
535
- ...(def.paramOverrides ? { paramOverrides: def.paramOverrides } : {}),
536
- ...(def.fieldOverrides ? { fieldOverrides: def.fieldOverrides } : {})
537
- }))
538
-
539
- const missing = defs
540
- .map((def) => def.connection)
541
- .filter((connection) => !connections.value.some((item) => item.id === connection.id))
542
- if (missing.length > 0) {
543
- connections.value = [
544
- ...connections.value,
545
- ...missing.map((connection) => ({ ...connection }))
546
- ]
547
- }
548
-
549
- for (const dataset of datasets.value) {
550
- if (dataset.sql.trim()) void describeDataset(dataset.id)
551
- }
552
- }
553
-
554
- // 模板更换:重新载入(仅在提供了 template 时;缺省为空白设计态)
555
- watch(
556
- () => props.template,
557
- (template) => {
558
- if (template) loadTemplate(template)
559
- },
560
- { immediate: true }
561
- )
562
-
563
- return {
564
- workbook,
565
- connections,
566
- datasets,
567
- catalog,
568
- boundKeys,
569
- activeCell,
570
- selectionLabel,
571
- activeBinding,
572
- activeFieldType,
573
- activeDatasetFields,
574
- resolvedRowParentLabel,
575
- resolvedColParentLabel,
576
- parentPick,
577
- rowParentCandidates,
578
- colParentCandidates,
579
- bindingEntries,
580
- metaTick,
581
- getBindingAt,
582
- resolveFieldLabel,
583
- bindField,
584
- patchActiveBinding,
585
- startParentPick,
586
- cancelParentPick,
587
- pickParentAt,
588
- clearParent,
589
- removeActiveBinding,
590
- resolveCellStyle,
591
- resolveCellRenderer,
592
- getTemplate,
593
- addConnection,
594
- updateConnection,
595
- removeConnection,
596
- addDataset,
597
- updateDataset,
598
- removeDataset,
599
- testConnection,
600
- describeDataset,
601
- previewDataset
602
- }
603
- }