@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.
- package/dist/index.d.ts +2 -19
- package/dist/index.js +2 -15
- package/dist/types/index.d.ts +1 -3
- package/package.json +2 -3
- package/src/index.ts +1 -27
- package/src/types/index.ts +0 -2
- package/dist/components/report/binding-badge.js +0 -118
- package/dist/components/report/designer/cell-coords.js +0 -46
- package/dist/components/report/designer/col-widths.js +0 -29
- package/dist/components/report/designer/conditional-rules/helpers.js +0 -139
- package/dist/components/report/designer/drill-dialog.js +0 -258
- package/dist/components/report/designer/drop-highlight-overlay.js +0 -76
- package/dist/components/report/designer/float-panel.js +0 -434
- package/dist/components/report/designer/role.js +0 -17
- package/dist/components/report/designer/rule-preview.js +0 -33
- package/dist/components/report/designer/rule-row.js +0 -253
- package/dist/components/report/designer/rules-dialog.js +0 -118
- package/dist/components/report/designer/topology-overlay.js +0 -122
- package/dist/components/report/designer/topology.js +0 -64
- package/dist/components/report/designer/use-grid-overlay.js +0 -75
- package/dist/components/report/designer-hub.js +0 -177
- package/dist/components/report/field-panel-helpers.js +0 -33
- package/dist/components/report/field-panel.js +0 -132
- package/dist/components/report/filter-bar.js +0 -74
- package/dist/components/report/hub-connection-form.js +0 -145
- package/dist/components/report/hub-dataset-editor.js +0 -409
- package/dist/components/report/preview-grid-size.js +0 -32
- package/dist/components/report/print.js +0 -39
- package/dist/components/report/report-designer.js +0 -428
- package/dist/components/report/report-viewer.js +0 -253
- package/dist/components/report/style.d.ts +0 -17
- package/dist/components/report/style.js +0 -18
- package/dist/components/report/style2.css +0 -965
- package/dist/components/report/use-report-designer.js +0 -387
- package/dist/components/report/use-report-viewer.js +0 -140
- package/dist/report/binding.d.ts +0 -83
- package/dist/report/binding.js +0 -334
- package/dist/report/connector.d.ts +0 -66
- package/dist/report/connector.js +0 -159
- package/dist/report/drill.d.ts +0 -48
- package/dist/report/drill.js +0 -78
- package/dist/report/export-xlsx.d.ts +0 -13
- package/dist/report/export-xlsx.js +0 -13
- package/dist/report/filter-bar.d.ts +0 -10
- package/dist/report/filter-bar.js +0 -31
- package/dist/report/index.d.ts +0 -14
- package/dist/report/params.d.ts +0 -13
- package/dist/report/params.js +0 -61
- package/dist/report/print.d.ts +0 -23
- package/dist/report/print.js +0 -197
- package/dist/report/render/aggregate.js +0 -193
- package/dist/report/render/builder.d.ts +0 -12
- package/dist/report/render/builder.js +0 -139
- package/dist/report/render/coordinate.d.ts +0 -25
- package/dist/report/render/coordinate.js +0 -733
- package/dist/report/render/index.d.ts +0 -14
- package/dist/report/render/index.js +0 -21
- package/dist/report/render/style-resolver.js +0 -50
- package/dist/report/render/template-index.d.ts +0 -24
- package/dist/report/render/template-index.js +0 -137
- package/dist/report/rules/evaluate.d.ts +0 -25
- package/dist/report/rules/evaluate.js +0 -82
- package/dist/report/rules/index.d.ts +0 -2
- package/dist/report/template.d.ts +0 -63
- package/dist/report/template.js +0 -83
- package/dist/report/types.d.ts +0 -103
- package/dist/types/report-designer.d.ts +0 -51
- package/dist/types/report-viewer.d.ts +0 -57
- package/src/components/report/__test__/binding-badge.test.ts +0 -92
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +0 -88
- package/src/components/report/__test__/field-panel-helpers.test.ts +0 -65
- package/src/components/report/__test__/float-panel-position.test.ts +0 -45
- package/src/components/report/__test__/preview-grid-size.test.ts +0 -63
- package/src/components/report/__test__/report-designer.test.ts +0 -559
- package/src/components/report/__test__/report-drill.test.ts +0 -692
- package/src/components/report/__test__/report-viewer.test.ts +0 -400
- package/src/components/report/__test__/topology.test.ts +0 -143
- package/src/components/report/__test__/use-report-designer.test.ts +0 -804
- package/src/components/report/binding-badge.ts +0 -122
- package/src/components/report/designer/cell-coords.ts +0 -95
- package/src/components/report/designer/col-widths.ts +0 -56
- package/src/components/report/designer/conditional-rules/helpers.ts +0 -189
- package/src/components/report/designer/drill-dialog.vue +0 -245
- package/src/components/report/designer/drop-highlight-overlay.vue +0 -75
- package/src/components/report/designer/float-panel.vue +0 -444
- package/src/components/report/designer/role.ts +0 -32
- package/src/components/report/designer/rule-preview.vue +0 -40
- package/src/components/report/designer/rule-row.vue +0 -252
- package/src/components/report/designer/rules-dialog.vue +0 -110
- package/src/components/report/designer/topology-overlay.vue +0 -128
- package/src/components/report/designer/topology.ts +0 -124
- package/src/components/report/designer/use-grid-overlay.ts +0 -114
- package/src/components/report/designer-hub.vue +0 -198
- package/src/components/report/field-panel-helpers.ts +0 -44
- package/src/components/report/field-panel.vue +0 -136
- package/src/components/report/filter-bar.vue +0 -77
- package/src/components/report/hub-connection-form.vue +0 -124
- package/src/components/report/hub-dataset-editor.vue +0 -387
- package/src/components/report/index.ts +0 -2
- package/src/components/report/preview-grid-size.ts +0 -33
- package/src/components/report/print.ts +0 -44
- package/src/components/report/report-designer.vue +0 -454
- package/src/components/report/report-viewer.vue +0 -289
- package/src/components/report/style-designer.scss +0 -653
- package/src/components/report/style-hub.scss +0 -292
- package/src/components/report/style-viewer.scss +0 -144
- package/src/components/report/style.scss +0 -3
- package/src/components/report/style.ts +0 -20
- package/src/components/report/use-report-designer.ts +0 -603
- package/src/components/report/use-report-viewer.ts +0 -227
- package/src/report/__test__/binding.test.ts +0 -320
- package/src/report/__test__/connector.test.ts +0 -239
- package/src/report/__test__/drill.test.ts +0 -194
- package/src/report/__test__/export-xlsx.test.ts +0 -96
- package/src/report/__test__/filter-bar.test.ts +0 -58
- package/src/report/__test__/params.test.ts +0 -156
- package/src/report/__test__/print.test.ts +0 -170
- package/src/report/__test__/render.test.ts +0 -856
- package/src/report/__test__/rules.test.ts +0 -131
- package/src/report/__test__/template.test.ts +0 -366
- package/src/report/binding.ts +0 -474
- package/src/report/connector.ts +0 -206
- package/src/report/drill.ts +0 -112
- package/src/report/export-xlsx.ts +0 -15
- package/src/report/filter-bar.ts +0 -36
- package/src/report/index.ts +0 -16
- package/src/report/params.ts +0 -96
- package/src/report/print.ts +0 -234
- package/src/report/render/__test__/aggregate.test.ts +0 -16
- package/src/report/render/__test__/coordinate.test.ts +0 -405
- package/src/report/render/aggregate.ts +0 -289
- package/src/report/render/builder.ts +0 -211
- package/src/report/render/coordinate.ts +0 -1239
- package/src/report/render/index.ts +0 -30
- package/src/report/render/style-resolver.ts +0 -68
- package/src/report/render/template-index.ts +0 -200
- package/src/report/rules/evaluate.ts +0 -113
- package/src/report/rules/index.ts +0 -7
- package/src/report/template.ts +0 -145
- package/src/report/types.ts +0 -116
- package/src/types/report-designer.ts +0 -55
- 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
|
-
}
|