@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,30 @@
|
|
|
1
|
+
import type { SheetSnapshot } from '@veltra/sheet-core'
|
|
2
|
+
|
|
3
|
+
import type { ReportColWidthEntry } from '../export-xlsx'
|
|
4
|
+
import type { DatasetRecords } from '../types'
|
|
5
|
+
import { buildFilledReport, mapFilledColWidths } from './builder'
|
|
6
|
+
import { computeExpansionLayout } from './coordinate'
|
|
7
|
+
import { buildTemplateIndex } from './template-index'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 将 Report Template 按 Dataset 渲染为 Filled Report 快照。
|
|
11
|
+
* 基于显式展开方向与父格关系展开,不做坐标推断。
|
|
12
|
+
*/
|
|
13
|
+
export function renderReport(template: SheetSnapshot, data: DatasetRecords): SheetSnapshot {
|
|
14
|
+
const index = buildTemplateIndex(template)
|
|
15
|
+
const layout = computeExpansionLayout(index, data)
|
|
16
|
+
return buildFilledReport(template, index, layout, data)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 将设计态模板列宽映射为填充报表物理列宽(横向展开列继承父格列宽) */
|
|
20
|
+
export function resolveFilledColWidths(
|
|
21
|
+
template: SheetSnapshot,
|
|
22
|
+
data: DatasetRecords,
|
|
23
|
+
templateColWidths: ReadonlyArray<ReportColWidthEntry>
|
|
24
|
+
): ReportColWidthEntry[] {
|
|
25
|
+
const index = buildTemplateIndex(template)
|
|
26
|
+
const layout = computeExpansionLayout(index, data)
|
|
27
|
+
return mapFilledColWidths(templateColWidths, layout, index)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { mapFilledColWidths } from './builder'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CellStyle, SheetSnapshot } from '@veltra/sheet-core'
|
|
2
|
+
import { StylePool } from '@veltra/sheet-core'
|
|
3
|
+
import { mergeCellStyle } from '@veltra/sheet-core/core/command/set-cell-style'
|
|
4
|
+
|
|
5
|
+
import { evaluateConditionalStyle, type ConditionalEvalContext } from '../rules'
|
|
6
|
+
import type { ConditionalRule } from '../types'
|
|
7
|
+
|
|
8
|
+
/** 静态样式经 StylePool 复用 + 条件样式打平(含 `scope: 'row'` 两阶段叠加) */
|
|
9
|
+
export class StyleResolver {
|
|
10
|
+
private readonly pool = new StylePool()
|
|
11
|
+
private readonly rowStyleByPhysRow = new Map<number, CellStyle>()
|
|
12
|
+
|
|
13
|
+
constructor(private readonly template: SheetSnapshot) {
|
|
14
|
+
if (template.styles.length > 0) this.pool.restore(template.styles)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 收集 `scope: 'row'` 命中样式,按物理行累加(须在输出静态格之前完成) */
|
|
18
|
+
collectRowStyle(
|
|
19
|
+
physicalRow: number,
|
|
20
|
+
ctx: ConditionalEvalContext,
|
|
21
|
+
rules: readonly ConditionalRule[] | undefined
|
|
22
|
+
): void {
|
|
23
|
+
if (!rules?.some((rule) => rule.scope === 'row')) return
|
|
24
|
+
|
|
25
|
+
const patch = evaluateConditionalStyle(ctx, undefined, rules, 'row')
|
|
26
|
+
if (!patch) return
|
|
27
|
+
|
|
28
|
+
const existing = this.rowStyleByPhysRow.get(physicalRow)
|
|
29
|
+
this.rowStyleByPhysRow.set(physicalRow, mergeCellStyle(existing, patch) ?? patch)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 解析模板格基础样式 + `scope: 'cell'` 条件样式 */
|
|
33
|
+
resolveCell(
|
|
34
|
+
templateRow: number,
|
|
35
|
+
templateCol: number,
|
|
36
|
+
ctx: ConditionalEvalContext,
|
|
37
|
+
rules: readonly ConditionalRule[] | undefined
|
|
38
|
+
): number | undefined {
|
|
39
|
+
const tpl = this.template.cells.find((c) => c.row === templateRow && c.col === templateCol)
|
|
40
|
+
const baseStyle = tpl?.s !== undefined ? this.pool.get(tpl.s) : undefined
|
|
41
|
+
const merged = evaluateConditionalStyle(ctx, baseStyle, rules, 'cell')
|
|
42
|
+
if (!merged) return tpl?.s
|
|
43
|
+
if (merged === baseStyle) return tpl?.s
|
|
44
|
+
return this.pool.intern(merged)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 无绑定的静态格:仅模板样式 */
|
|
48
|
+
resolveStatic(templateRow: number, templateCol: number): number | undefined {
|
|
49
|
+
const tpl = this.template.cells.find((c) => c.row === templateRow && c.col === templateCol)
|
|
50
|
+
return tpl?.s
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 将行级样式叠加到格级样式 id */
|
|
54
|
+
mergeRowStyle(styleId: number | undefined, physicalRow: number): number | undefined {
|
|
55
|
+
const rowPatch = this.rowStyleByPhysRow.get(physicalRow)
|
|
56
|
+
if (!rowPatch) return styleId
|
|
57
|
+
|
|
58
|
+
const base = styleId !== undefined ? this.pool.get(styleId) : undefined
|
|
59
|
+
const merged = mergeCellStyle(base, rowPatch)
|
|
60
|
+
if (!merged) return styleId
|
|
61
|
+
if (merged === base) return styleId
|
|
62
|
+
return this.pool.intern(merged)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
snapshot(): CellStyle[] {
|
|
66
|
+
return this.pool.snapshot()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { CellAddress, CellRange, CellSnapshotItem, SheetSnapshot } from '@veltra/sheet-core'
|
|
2
|
+
import { cellKey, formatAddress } from '@veltra/sheet-core'
|
|
3
|
+
|
|
4
|
+
import { REPORT_META_NAMESPACE, formatCellAddress } from '../binding'
|
|
5
|
+
import type { ReportBinding } from '../types'
|
|
6
|
+
|
|
7
|
+
/** 模板结构约束违反时抛出(附 A1 标签) */
|
|
8
|
+
export class TemplateStructureError extends Error {
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message)
|
|
11
|
+
this.name = 'TemplateStructureError'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IndexedBinding {
|
|
16
|
+
addr: CellAddress
|
|
17
|
+
binding: ReportBinding
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 模板一次性索引:绑定直查、静态格直查、行/列父子树 */
|
|
21
|
+
export interface TemplateIndex {
|
|
22
|
+
readonly bindings: ReadonlyMap<number, IndexedBinding>
|
|
23
|
+
readonly staticCells: ReadonlyMap<number, CellSnapshotItem>
|
|
24
|
+
readonly rowChildren: ReadonlyMap<number, readonly CellAddress[]>
|
|
25
|
+
readonly colChildren: ReadonlyMap<number, readonly CellAddress[]>
|
|
26
|
+
readonly logicalRows: readonly number[]
|
|
27
|
+
readonly logicalCols: readonly number[]
|
|
28
|
+
readonly merges: readonly CellRange[]
|
|
29
|
+
bindingAt(addr: CellAddress): ReportBinding | undefined
|
|
30
|
+
cellAt(addr: CellAddress): CellSnapshotItem | undefined
|
|
31
|
+
bindingsOnRow(row: number): readonly IndexedBinding[]
|
|
32
|
+
bindingsOnCol(col: number): readonly IndexedBinding[]
|
|
33
|
+
isBindingAddr(addr: CellAddress): boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function addressesEqual(a: CellAddress, b: CellAddress): boolean {
|
|
37
|
+
return a.row === b.row && a.col === b.col
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function collectLogicalRows(template: SheetSnapshot): number[] {
|
|
41
|
+
const rows = new Set<number>()
|
|
42
|
+
for (const cell of template.cells) rows.add(cell.row)
|
|
43
|
+
for (const item of template.meta ?? []) rows.add(item.row)
|
|
44
|
+
return [...rows].sort((a, b) => a - b)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function collectLogicalCols(template: SheetSnapshot): number[] {
|
|
48
|
+
const cols = new Set<number>()
|
|
49
|
+
for (const cell of template.cells) cols.add(cell.col)
|
|
50
|
+
for (const item of template.meta ?? []) cols.add(item.col)
|
|
51
|
+
return [...cols].sort((a, b) => a - b)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateParentExists(
|
|
55
|
+
addr: CellAddress,
|
|
56
|
+
parent: CellAddress | undefined,
|
|
57
|
+
bindings: Map<number, IndexedBinding>,
|
|
58
|
+
direction: 'row' | 'col'
|
|
59
|
+
): void {
|
|
60
|
+
if (!parent) return
|
|
61
|
+
const label = formatCellAddress(addr)
|
|
62
|
+
const parentLabel = formatCellAddress(parent)
|
|
63
|
+
if (!bindings.has(cellKey(parent))) {
|
|
64
|
+
throw new TemplateStructureError(
|
|
65
|
+
`单元格 ${label} 的${direction === 'row' ? '行' : '列'}方向父格 ${parentLabel} 不存在绑定`
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function detectParentCycle(
|
|
71
|
+
start: CellAddress,
|
|
72
|
+
bindings: Map<number, IndexedBinding>,
|
|
73
|
+
direction: 'row' | 'col'
|
|
74
|
+
): void {
|
|
75
|
+
const label = formatCellAddress(start)
|
|
76
|
+
const visited = new Set<number>()
|
|
77
|
+
let current: CellAddress | undefined = start
|
|
78
|
+
|
|
79
|
+
while (current) {
|
|
80
|
+
const key = cellKey(current)
|
|
81
|
+
if (visited.has(key)) {
|
|
82
|
+
throw new TemplateStructureError(
|
|
83
|
+
`单元格 ${label} 的${direction === 'row' ? '行' : '列'}方向父链存在环`
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
visited.add(key)
|
|
87
|
+
const entry = bindings.get(key)
|
|
88
|
+
if (!entry) break
|
|
89
|
+
current = direction === 'row' ? entry.binding.rowParent : entry.binding.colParent
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function appendChild(map: Map<number, CellAddress[]>, parentKey: number, child: CellAddress): void {
|
|
94
|
+
const list = map.get(parentKey)
|
|
95
|
+
if (list) {
|
|
96
|
+
list.push(child)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
map.set(parentKey, [child])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 由模板快照构建索引并校验结构约束(ADR-0005 决策 3) */
|
|
103
|
+
export function buildTemplateIndex(template: SheetSnapshot): TemplateIndex {
|
|
104
|
+
const bindings = new Map<number, IndexedBinding>()
|
|
105
|
+
const bindingAddrs = new Set<number>()
|
|
106
|
+
|
|
107
|
+
for (const item of template.meta ?? []) {
|
|
108
|
+
if (item.namespace !== REPORT_META_NAMESPACE) continue
|
|
109
|
+
const addr = { row: item.row, col: item.col }
|
|
110
|
+
const key = cellKey(addr)
|
|
111
|
+
if (bindings.has(key)) {
|
|
112
|
+
throw new TemplateStructureError(`单元格 ${formatCellAddress(addr)} 存在重复绑定`)
|
|
113
|
+
}
|
|
114
|
+
bindings.set(key, { addr, binding: item.payload as ReportBinding })
|
|
115
|
+
bindingAddrs.add(key)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const staticCells = new Map<number, CellSnapshotItem>()
|
|
119
|
+
for (const cell of template.cells) {
|
|
120
|
+
const key = cellKey({ row: cell.row, col: cell.col })
|
|
121
|
+
if (bindingAddrs.has(key)) continue
|
|
122
|
+
staticCells.set(key, cell)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const rowChildren = new Map<number, CellAddress[]>()
|
|
126
|
+
const colChildren = new Map<number, CellAddress[]>()
|
|
127
|
+
|
|
128
|
+
for (const { addr, binding } of bindings.values()) {
|
|
129
|
+
validateParentExists(addr, binding.rowParent, bindings, 'row')
|
|
130
|
+
validateParentExists(addr, binding.colParent, bindings, 'col')
|
|
131
|
+
detectParentCycle(addr, bindings, 'row')
|
|
132
|
+
detectParentCycle(addr, bindings, 'col')
|
|
133
|
+
|
|
134
|
+
if (binding.rowParent) {
|
|
135
|
+
appendChild(rowChildren, cellKey(binding.rowParent), addr)
|
|
136
|
+
}
|
|
137
|
+
if (binding.colParent) {
|
|
138
|
+
appendChild(colChildren, cellKey(binding.colParent), addr)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const logicalRows = collectLogicalRows(template)
|
|
143
|
+
const logicalCols = collectLogicalCols(template)
|
|
144
|
+
const merges = template.merges ?? []
|
|
145
|
+
|
|
146
|
+
const bindingsByRow = new Map<number, IndexedBinding[]>()
|
|
147
|
+
for (const entry of bindings.values()) {
|
|
148
|
+
const list = bindingsByRow.get(entry.addr.row)
|
|
149
|
+
if (list) list.push(entry)
|
|
150
|
+
else bindingsByRow.set(entry.addr.row, [entry])
|
|
151
|
+
}
|
|
152
|
+
for (const list of bindingsByRow.values()) {
|
|
153
|
+
list.sort((a, b) => a.addr.col - b.addr.col)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const bindingsByCol = new Map<number, IndexedBinding[]>()
|
|
157
|
+
for (const entry of bindings.values()) {
|
|
158
|
+
const list = bindingsByCol.get(entry.addr.col)
|
|
159
|
+
if (list) list.push(entry)
|
|
160
|
+
else bindingsByCol.set(entry.addr.col, [entry])
|
|
161
|
+
}
|
|
162
|
+
for (const list of bindingsByCol.values()) {
|
|
163
|
+
list.sort((a, b) => a.addr.row - b.addr.row)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
bindings,
|
|
168
|
+
staticCells,
|
|
169
|
+
rowChildren,
|
|
170
|
+
colChildren,
|
|
171
|
+
logicalRows,
|
|
172
|
+
logicalCols,
|
|
173
|
+
merges,
|
|
174
|
+
bindingAt(addr) {
|
|
175
|
+
return bindings.get(cellKey(addr))?.binding
|
|
176
|
+
},
|
|
177
|
+
cellAt(addr) {
|
|
178
|
+
return staticCells.get(cellKey(addr))
|
|
179
|
+
},
|
|
180
|
+
bindingsOnRow(row) {
|
|
181
|
+
return bindingsByRow.get(row) ?? []
|
|
182
|
+
},
|
|
183
|
+
bindingsOnCol(col) {
|
|
184
|
+
return bindingsByCol.get(col) ?? []
|
|
185
|
+
},
|
|
186
|
+
isBindingAddr(addr) {
|
|
187
|
+
return bindings.has(cellKey(addr))
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** 判断两地址是否相同(供 coordinate 层使用) */
|
|
193
|
+
export function sameAddress(a: CellAddress, b: CellAddress): boolean {
|
|
194
|
+
return addressesEqual(a, b)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** 地址键 → A1 标签 */
|
|
198
|
+
export function addressLabel(addr: CellAddress): string {
|
|
199
|
+
return formatAddress(addr)
|
|
200
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { CellStyle } from '@veltra/sheet-core'
|
|
2
|
+
import { mergeCellStyle } from '@veltra/sheet-core/core/command/set-cell-style'
|
|
3
|
+
|
|
4
|
+
import type { ConditionalRule } from '../types'
|
|
5
|
+
|
|
6
|
+
/** 条件样式求值上下文:当前展示值 + 可选同记录字段 */
|
|
7
|
+
export interface ConditionalEvalContext {
|
|
8
|
+
cellValue: unknown
|
|
9
|
+
bindingField: string
|
|
10
|
+
record?: Record<string, unknown>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ruleScope(rule: ConditionalRule): 'cell' | 'row' {
|
|
14
|
+
return rule.scope ?? 'cell'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 按规则配置的求值字段从上下文取比较值 */
|
|
18
|
+
export function resolveRuleCompareValue(
|
|
19
|
+
ctx: ConditionalEvalContext,
|
|
20
|
+
rule: ConditionalRule
|
|
21
|
+
): unknown {
|
|
22
|
+
if (rule.field !== undefined && ctx.record !== undefined) {
|
|
23
|
+
return ctx.record[rule.field]
|
|
24
|
+
}
|
|
25
|
+
return ctx.cellValue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 将单元格值转为可比较的有限数值;无法转换时返回 null */
|
|
29
|
+
export function toComparableNumber(value: unknown): number | null {
|
|
30
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
31
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
32
|
+
const parsed = Number(value)
|
|
33
|
+
if (Number.isFinite(parsed)) return parsed
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isBetweenValue(value: unknown): value is [unknown, unknown] {
|
|
39
|
+
return Array.isArray(value) && value.length === 2
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 评估单条条件规则是否命中。
|
|
44
|
+
* 数值比较运算符在双方均可解析为有限数时按数值比较;`eq` 在无法数值化时回退字符串相等。
|
|
45
|
+
*/
|
|
46
|
+
export function evaluateCondition(cellValue: unknown, rule: ConditionalRule): boolean {
|
|
47
|
+
const { operator, value: ruleValue } = rule
|
|
48
|
+
|
|
49
|
+
switch (operator) {
|
|
50
|
+
case 'gt':
|
|
51
|
+
case 'gte':
|
|
52
|
+
case 'lt':
|
|
53
|
+
case 'lte':
|
|
54
|
+
case 'eq': {
|
|
55
|
+
const left = toComparableNumber(cellValue)
|
|
56
|
+
const right = toComparableNumber(ruleValue)
|
|
57
|
+
if (left !== null && right !== null) {
|
|
58
|
+
if (operator === 'gt') return left > right
|
|
59
|
+
if (operator === 'gte') return left >= right
|
|
60
|
+
if (operator === 'lt') return left < right
|
|
61
|
+
if (operator === 'lte') return left <= right
|
|
62
|
+
return left === right
|
|
63
|
+
}
|
|
64
|
+
if (operator === 'eq') {
|
|
65
|
+
if (typeof cellValue === 'string' && typeof ruleValue === 'string') {
|
|
66
|
+
return cellValue === ruleValue
|
|
67
|
+
}
|
|
68
|
+
return Object.is(cellValue, ruleValue)
|
|
69
|
+
}
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
case 'between': {
|
|
73
|
+
if (!isBetweenValue(ruleValue)) return false
|
|
74
|
+
const left = toComparableNumber(cellValue)
|
|
75
|
+
const min = toComparableNumber(ruleValue[0])
|
|
76
|
+
const max = toComparableNumber(ruleValue[1])
|
|
77
|
+
if (left === null || min === null || max === null) return false
|
|
78
|
+
return left >= min && left <= max
|
|
79
|
+
}
|
|
80
|
+
case 'contains': {
|
|
81
|
+
if (typeof cellValue !== 'string') return false
|
|
82
|
+
if (typeof ruleValue !== 'string') return false
|
|
83
|
+
return cellValue.includes(ruleValue)
|
|
84
|
+
}
|
|
85
|
+
default:
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 按规则数组顺序评估条件样式,将命中规则的 style 增量依次合并到 baseStyle。
|
|
92
|
+
* 无规则或未命中时原样返回 baseStyle。
|
|
93
|
+
*/
|
|
94
|
+
export function evaluateConditionalStyle(
|
|
95
|
+
ctx: ConditionalEvalContext,
|
|
96
|
+
baseStyle: CellStyle | undefined,
|
|
97
|
+
rules: readonly ConditionalRule[] | undefined,
|
|
98
|
+
scope: 'cell' | 'row' = 'cell'
|
|
99
|
+
): CellStyle | undefined {
|
|
100
|
+
if (!rules || rules.length === 0) return baseStyle
|
|
101
|
+
|
|
102
|
+
let merged = baseStyle
|
|
103
|
+
let matched = false
|
|
104
|
+
|
|
105
|
+
for (const rule of rules) {
|
|
106
|
+
if (ruleScope(rule) !== scope) continue
|
|
107
|
+
if (!evaluateCondition(resolveRuleCompareValue(ctx, rule), rule)) continue
|
|
108
|
+
merged = mergeCellStyle(merged, rule.style)
|
|
109
|
+
matched = true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return matched ? merged : baseStyle
|
|
113
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { SheetSnapshot } from '@veltra/sheet-core'
|
|
2
|
+
|
|
3
|
+
import { REPORT_META_NAMESPACE } from './binding'
|
|
4
|
+
import type { DataConnection, DataConnector, Result } from './connector'
|
|
5
|
+
import { buildParamDefs } from './params'
|
|
6
|
+
import type {
|
|
7
|
+
DatasetField,
|
|
8
|
+
DatasetRecords,
|
|
9
|
+
ParamValues,
|
|
10
|
+
QueryParamDef,
|
|
11
|
+
ReportBinding
|
|
12
|
+
} from './types'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 模板内嵌的数据集定义(Dataset = Data Connection + SQL,ADR-0002 决策 2)。
|
|
16
|
+
* 与 playground DatasetDef 同构,connectionId 替换为内嵌的完整连接对象。
|
|
17
|
+
*/
|
|
18
|
+
export interface ReportDatasetDef {
|
|
19
|
+
id: string
|
|
20
|
+
label: string
|
|
21
|
+
connection: DataConnection
|
|
22
|
+
sql: string
|
|
23
|
+
/** 参数元数据覆盖(label / 类型 / 默认值 / 选项) */
|
|
24
|
+
paramOverrides?: Record<string, Partial<Omit<QueryParamDef, 'id'>>>
|
|
25
|
+
/** 字段中文名覆盖(name → label);查看器不消费,随模板序列化往返供设计器还原 */
|
|
26
|
+
fieldOverrides?: Record<string, Partial<Pick<DatasetField, 'label'>>>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 当前支持的报表模板版本(ADR-0005 决策 4) */
|
|
30
|
+
export const REPORT_TEMPLATE_VERSION = 1
|
|
31
|
+
|
|
32
|
+
/** 模板版本不兼容时抛出 */
|
|
33
|
+
export class IncompatibleTemplateVersionError extends Error {
|
|
34
|
+
constructor(message: string) {
|
|
35
|
+
super(message)
|
|
36
|
+
this.name = 'IncompatibleTemplateVersionError'
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 自包含 Report Template:SheetSnapshot + 内嵌数据集定义 + 版本段。
|
|
42
|
+
* 查看器仅凭 `template` + `connector` 即可完成「参数提取 → 取数 → 展开渲染」闭环;
|
|
43
|
+
* 模板可 JSON 序列化流转(凭据随之流转,持久化与安全存储由下游负责,ADR-0003 决策 4)。
|
|
44
|
+
*
|
|
45
|
+
* 注意:`Sheet.snapshot()` 不产生 `datasets` / `version` 字段,经 `restore()`/`snapshot()` 往返会丢失,
|
|
46
|
+
* 由设计器 `getTemplate()` 在吐出快照时重新附加。
|
|
47
|
+
*/
|
|
48
|
+
export interface ReportTemplate extends SheetSnapshot {
|
|
49
|
+
/** 模板结构版本;当前值为 `REPORT_TEMPLATE_VERSION` */
|
|
50
|
+
version?: number
|
|
51
|
+
datasets?: ReportDatasetDef[]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 载入模板前校验版本:缺失或高于当前支持版本时抛可读错误 */
|
|
55
|
+
export function assertCompatibleTemplateVersion(template: ReportTemplate): void {
|
|
56
|
+
if (template.version === undefined) {
|
|
57
|
+
throw new IncompatibleTemplateVersionError(
|
|
58
|
+
'报表模板缺少 version 字段,请在设计器中重建模板后重试'
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
if (template.version > REPORT_TEMPLATE_VERSION) {
|
|
62
|
+
throw new IncompatibleTemplateVersionError(
|
|
63
|
+
`报表模板版本 ${template.version} 高于当前支持的版本 ${REPORT_TEMPLATE_VERSION},请升级组件库后重试`
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 由工作簿快照构造带版本段的 Report Template */
|
|
69
|
+
export function createReportTemplate(
|
|
70
|
+
snapshot: SheetSnapshot,
|
|
71
|
+
datasets?: ReportDatasetDef[]
|
|
72
|
+
): ReportTemplate {
|
|
73
|
+
return { ...snapshot, version: REPORT_TEMPLATE_VERSION, datasets }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 读取模板内嵌的数据集定义(旧模板未内嵌时回退空数组) */
|
|
77
|
+
export function getTemplateDatasets(template: ReportTemplate): ReportDatasetDef[] {
|
|
78
|
+
return Array.isArray(template.datasets) ? template.datasets : []
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 模板实际绑定的数据集 id(按绑定在 meta 中的出现顺序去重;绑定即真相,ADR-0002 决策 4) */
|
|
82
|
+
export function getBoundDatasetIds(template: SheetSnapshot): string[] {
|
|
83
|
+
const ids: string[] = []
|
|
84
|
+
for (const item of template.meta ?? []) {
|
|
85
|
+
if (item.namespace !== REPORT_META_NAMESPACE) continue
|
|
86
|
+
const dataset = (item.payload as ReportBinding).dataset
|
|
87
|
+
if (typeof dataset === 'string' && dataset && !ids.includes(dataset)) ids.push(dataset)
|
|
88
|
+
}
|
|
89
|
+
return ids
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Filter Bar 参数来源(ADR-0002 决策 4):模板实际绑定的数据集的查询参数并集,
|
|
94
|
+
* 同名参数合并、先见数据集为准;绑定引用了模板未定义的数据集时跳过。
|
|
95
|
+
*/
|
|
96
|
+
export function resolveTemplateParams(template: ReportTemplate): QueryParamDef[] {
|
|
97
|
+
const datasets = getTemplateDatasets(template)
|
|
98
|
+
const map = new Map<string, QueryParamDef>()
|
|
99
|
+
for (const datasetId of getBoundDatasetIds(template)) {
|
|
100
|
+
const dataset = datasets.find((item) => item.id === datasetId)
|
|
101
|
+
if (!dataset) continue
|
|
102
|
+
for (const param of buildParamDefs(dataset.sql, dataset.paramOverrides)) {
|
|
103
|
+
if (!map.has(param.id)) map.set(param.id, param)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Array.from(map.values())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 以参数定义的默认值生成 Filter Bar 初始运行时值 */
|
|
110
|
+
export function resolveParamDefaults(params: readonly QueryParamDef[]): ParamValues {
|
|
111
|
+
const values: ParamValues = {}
|
|
112
|
+
for (const param of params) values[param.id] = param.defaultValue
|
|
113
|
+
return values
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 经数据连接器为模板实际绑定的数据集取数(并行),按数据集 id 折叠为 DatasetRecords。
|
|
118
|
+
* 任一数据集返回业务错误(`ok: false`)则整体透传该错误;绑定但未定义的数据集跳过。
|
|
119
|
+
*/
|
|
120
|
+
export async function fetchTemplateRecords(
|
|
121
|
+
connector: DataConnector,
|
|
122
|
+
template: ReportTemplate,
|
|
123
|
+
values: ParamValues
|
|
124
|
+
): Promise<Result<DatasetRecords>> {
|
|
125
|
+
const datasets = getTemplateDatasets(template)
|
|
126
|
+
const targets = getBoundDatasetIds(template)
|
|
127
|
+
.map((datasetId) => datasets.find((item) => item.id === datasetId))
|
|
128
|
+
.filter((dataset): dataset is ReportDatasetDef => dataset !== undefined)
|
|
129
|
+
|
|
130
|
+
const results = await Promise.all(
|
|
131
|
+
targets.map((dataset) => connector.query(dataset.connection, dataset.sql, values))
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const records: DatasetRecords = {}
|
|
135
|
+
for (let i = 0; i < targets.length; i++) {
|
|
136
|
+
const result = results[i]!
|
|
137
|
+
if (!result.ok) return result
|
|
138
|
+
records[targets[i]!.id] = result.data.rows
|
|
139
|
+
}
|
|
140
|
+
return { ok: true, data: records }
|
|
141
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { CellAddress, CellStylePatch } from '@veltra/sheet-core'
|
|
2
|
+
|
|
3
|
+
/** 报表绑定扩展方向 */
|
|
4
|
+
export type ReportExpand = 'down' | 'right' | 'none'
|
|
5
|
+
|
|
6
|
+
/** 报表绑定聚合方式 */
|
|
7
|
+
export type ReportAggregate = 'list' | 'group' | 'sum' | 'avg' | 'count' | 'max' | 'min'
|
|
8
|
+
|
|
9
|
+
/** 报表绑定排序 */
|
|
10
|
+
export type ReportSort = 'asc' | 'desc' | 'none'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 设计器预设标签:引擎不读,仅用于 UI 展示与一键切换。
|
|
14
|
+
* 缺失时 Action Pill 显示「自定义」。
|
|
15
|
+
*/
|
|
16
|
+
export type ReportPreset = 'groupHeader' | 'detail' | 'subtotal' | 'grandTotal' | 'cross'
|
|
17
|
+
|
|
18
|
+
/** 条件样式比较运算符 */
|
|
19
|
+
export type ConditionalOperator = 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'between' | 'contains'
|
|
20
|
+
|
|
21
|
+
/** 条件样式规则:命中后叠加 CellStyle 增量 */
|
|
22
|
+
export interface ConditionalRule {
|
|
23
|
+
operator: ConditionalOperator
|
|
24
|
+
/** 单值比较用标量;`between` 用 `[min, max]`(含端点) */
|
|
25
|
+
value: unknown
|
|
26
|
+
style: CellStylePatch
|
|
27
|
+
/** 求值字段;缺省取绑定格自身字段 */
|
|
28
|
+
field?: string
|
|
29
|
+
/** 作用范围;缺省 `'cell'` */
|
|
30
|
+
scope?: 'cell' | 'row'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 设计格上的报表绑定(存于 Cell Meta namespace `report`) */
|
|
34
|
+
export interface ReportBinding {
|
|
35
|
+
dataset: string
|
|
36
|
+
field: string
|
|
37
|
+
expand: ReportExpand
|
|
38
|
+
aggregate: ReportAggregate
|
|
39
|
+
/** 纵向从属父格:本格数据受该格当前行实例的值约束 */
|
|
40
|
+
rowParent?: CellAddress
|
|
41
|
+
/** 横向从属父格:本格数据受该格当前列实例的值约束 */
|
|
42
|
+
colParent?: CellAddress
|
|
43
|
+
/** 扩展实例是否合并为单个单元格;缺省 true */
|
|
44
|
+
mergeSpan?: boolean
|
|
45
|
+
/** 分组/明细排序;缺省视为 none */
|
|
46
|
+
sort?: ReportSort
|
|
47
|
+
/** 条件样式规则(按数组顺序优先级依次求值并合并样式) */
|
|
48
|
+
conditionalRules?: ConditionalRule[]
|
|
49
|
+
/** 设计器预设;引擎不读 */
|
|
50
|
+
preset?: ReportPreset
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 字段 schema(来自数据集配置,非运行时推断)。
|
|
55
|
+
* - `name`:绑定键(写入 Binding.field,英文标识)
|
|
56
|
+
* - `label`:中文显示名(字段面板与占位可读名)
|
|
57
|
+
*/
|
|
58
|
+
export interface DatasetField {
|
|
59
|
+
name: string
|
|
60
|
+
label: string
|
|
61
|
+
type: 'string' | 'number' | 'date'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 全局数据集目录项(catalog):系统里可用的数据集定义。
|
|
66
|
+
* 与「本报表选用哪些」无关。
|
|
67
|
+
*/
|
|
68
|
+
export interface DatasetCatalogItem {
|
|
69
|
+
id: string
|
|
70
|
+
label: string
|
|
71
|
+
fields: DatasetField[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Dataset id → 行记录(渲染 Filled Report 用) */
|
|
75
|
+
export type DatasetRecords = Record<string, Record<string, unknown>[]>
|
|
76
|
+
|
|
77
|
+
/** 查询参数控件类型 */
|
|
78
|
+
export type QueryParamType = 'text' | 'number' | 'date' | 'date-range' | 'select'
|
|
79
|
+
|
|
80
|
+
/** 下拉选项 */
|
|
81
|
+
export interface QueryParamOption {
|
|
82
|
+
label: string
|
|
83
|
+
value: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 查询参数定义(从 SQL `${param}` 提取,可在数据集编辑器覆盖元数据) */
|
|
87
|
+
export interface QueryParamDef {
|
|
88
|
+
id: string
|
|
89
|
+
label: string
|
|
90
|
+
type: QueryParamType
|
|
91
|
+
defaultValue: unknown
|
|
92
|
+
options?: QueryParamOption[]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 运行时参数值 */
|
|
96
|
+
export type ParamValues = Record<string, unknown>
|
package/src/tools/builtin.ts
CHANGED
|
@@ -393,8 +393,8 @@ registerTool({
|
|
|
393
393
|
})
|
|
394
394
|
|
|
395
395
|
// ─── file(导入 / 导出)────────────────────────────────────
|
|
396
|
-
// 导出 = 弹层选 xlsx / csv;导入 = 点击直接系统文件选择(
|
|
397
|
-
// sheet.vue 覆盖 onClick;tools 层不 import
|
|
396
|
+
// 导出 = 弹层选 xlsx / csv;导入 = 点击直接系统文件选择(components/sheet/import-file.ts,
|
|
397
|
+
// sheet.vue 覆盖 onClick;tools 层不 import 组件层)。
|
|
398
398
|
// 下载逻辑见 tools/download.ts(core/io 保持纯 TS 无头可测)。
|
|
399
399
|
|
|
400
400
|
registerTool({
|