@veltra/sheet 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/report/binding-badge.js +100 -0
- package/dist/components/report/designer/cell-coords.js +46 -0
- package/dist/components/report/designer/col-widths.js +29 -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 +422 -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 +253 -0
- package/dist/components/report/designer/rules-dialog.js +118 -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/preview-grid-size.js +32 -0
- package/dist/components/report/report-designer.js +393 -0
- package/dist/components/report/report-viewer.js +124 -0
- package/dist/components/report/style.d.ts +17 -0
- package/dist/components/report/style.js +18 -0
- package/dist/components/report/style2.css +874 -0
- package/dist/components/report/use-report-designer.js +382 -0
- package/dist/components/report/use-report-viewer.js +55 -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 +31 -3
- 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 +3 -3
- package/dist/{vue → components/sheet}/popups/find-popup.js +1 -1
- package/dist/{vue → components/sheet}/popups/font-color-popup.js +3 -3
- package/dist/{vue → components/sheet}/popups/font-size-popup.js +3 -3
- 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 +7 -4
- 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 +16 -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 +22 -4
- package/dist/{vue → components/sheet}/use-sheet-state.js +6 -5
- 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 +83 -0
- package/dist/report/binding.js +332 -0
- package/dist/report/connector.d.ts +66 -0
- package/dist/report/connector.js +159 -0
- package/dist/report/export-xlsx.d.ts +17 -0
- package/dist/report/export-xlsx.js +92 -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 +169 -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 +63 -0
- package/dist/report/template.js +83 -0
- package/dist/report/types.d.ts +85 -0
- package/dist/tools/apply-style.js +29 -0
- package/dist/tools/builtin.js +17 -17
- package/dist/tools/context.d.ts +39 -6
- package/dist/tools/context.js +38 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/report-designer.d.ts +38 -0
- package/dist/types/report-viewer.d.ts +38 -0
- package/dist/types/sheet.d.ts +21 -1
- package/package.json +6 -8
- package/src/components/report/__test__/conditional-rules-helpers.test.ts +88 -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__/preview-grid-size.test.ts +63 -0
- package/src/components/report/__test__/report-designer.test.ts +464 -0
- package/src/components/report/__test__/report-viewer.test.ts +357 -0
- package/src/components/report/__test__/topology.test.ts +143 -0
- package/src/components/report/__test__/use-report-designer.test.ts +697 -0
- package/src/components/report/binding-badge.ts +92 -0
- package/src/components/report/designer/cell-coords.ts +94 -0
- package/src/components/report/designer/col-widths.ts +56 -0
- package/src/components/report/designer/conditional-rules/helpers.ts +189 -0
- package/src/components/report/designer/drop-highlight-overlay.vue +74 -0
- package/src/components/report/designer/float-panel.vue +431 -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 +110 -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/preview-grid-size.ts +33 -0
- package/src/components/report/report-designer.vue +420 -0
- package/src/components/report/report-viewer.vue +136 -0
- package/src/components/report/style-designer.scss +587 -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 +596 -0
- package/src/components/report/use-report-viewer.ts +88 -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 +77 -3
- package/src/{vue → components/sheet}/__test__/sheet-context-menu.test.ts +4 -3
- 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 +32 -4
- package/src/{vue → components/sheet}/popups/export-popup.vue +2 -2
- package/src/{vue → components/sheet}/popups/fill-color-popup.vue +3 -3
- package/src/{vue → components/sheet}/popups/find-popup.vue +1 -1
- package/src/{vue → components/sheet}/popups/font-color-popup.vue +3 -3
- package/src/{vue → components/sheet}/popups/font-size-popup.vue +3 -3
- package/src/{vue → components/sheet}/popups/insert-image-popup.vue +1 -1
- package/src/{vue → components/sheet}/sheet-context-menu.ts +10 -4
- package/src/{vue → components/sheet}/sheet-toolbar.vue +2 -2
- package/src/{vue → components/sheet}/sheet.vue +6 -3
- package/src/{vue → components/sheet}/use-find-replace.ts +1 -1
- package/src/{vue → components/sheet}/use-sheet-grid.ts +23 -5
- package/src/{vue → components/sheet}/use-sheet-state.ts +11 -6
- 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 +320 -0
- package/src/report/__test__/connector.test.ts +239 -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 +830 -0
- package/src/report/__test__/rules.test.ts +131 -0
- package/src/report/__test__/template.test.ts +366 -0
- package/src/report/binding.ts +474 -0
- package/src/report/connector.ts +206 -0
- package/src/report/export-xlsx.ts +113 -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 +261 -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 +145 -0
- package/src/report/types.ts +96 -0
- package/src/tools/__test__/apply-style.test.ts +129 -0
- package/src/tools/apply-style.ts +41 -0
- package/src/tools/builtin.ts +20 -20
- package/src/tools/context.ts +67 -4
- package/src/tools/index.ts +1 -0
- package/src/types/index.ts +2 -6
- package/src/types/report-designer.ts +38 -0
- package/src/types/report-viewer.ts +37 -0
- package/src/types/sheet.ts +26 -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,14 @@
|
|
|
1
|
+
import { DatasetRecords } from "../types.js";
|
|
2
|
+
import { ReportColWidthEntry } from "../export-xlsx.js";
|
|
3
|
+
import { mapFilledColWidths } from "./builder.js";
|
|
4
|
+
import { SheetSnapshot } from "@veltra/sheet-core";
|
|
5
|
+
//#region src/report/render/index.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* 将 Report Template 按 Dataset 渲染为 Filled Report 快照。
|
|
8
|
+
* 基于显式展开方向与父格关系展开,不做坐标推断。
|
|
9
|
+
*/
|
|
10
|
+
declare function renderReport(template: SheetSnapshot, data: DatasetRecords): SheetSnapshot;
|
|
11
|
+
/** 将设计态模板列宽映射为填充报表物理列宽(横向展开列继承父格列宽) */
|
|
12
|
+
declare function resolveFilledColWidths(template: SheetSnapshot, data: DatasetRecords, templateColWidths: ReadonlyArray<ReportColWidthEntry>): ReportColWidthEntry[];
|
|
13
|
+
//#endregion
|
|
14
|
+
export { mapFilledColWidths, renderReport, resolveFilledColWidths };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { buildTemplateIndex } from "./template-index.js";
|
|
2
|
+
import { computeExpansionLayout } from "./coordinate.js";
|
|
3
|
+
import { buildFilledReport, mapFilledColWidths } from "./builder.js";
|
|
4
|
+
//#region src/report/render/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* 将 Report Template 按 Dataset 渲染为 Filled Report 快照。
|
|
7
|
+
* 基于显式展开方向与父格关系展开,不做坐标推断。
|
|
8
|
+
*/
|
|
9
|
+
function renderReport(template, data) {
|
|
10
|
+
const index = buildTemplateIndex(template);
|
|
11
|
+
const layout = computeExpansionLayout(index, data);
|
|
12
|
+
return buildFilledReport(template, index, layout, data);
|
|
13
|
+
}
|
|
14
|
+
/** 将设计态模板列宽映射为填充报表物理列宽(横向展开列继承父格列宽) */
|
|
15
|
+
function resolveFilledColWidths(template, data, templateColWidths) {
|
|
16
|
+
const index = buildTemplateIndex(template);
|
|
17
|
+
const layout = computeExpansionLayout(index, data);
|
|
18
|
+
return mapFilledColWidths(templateColWidths, layout, index);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { mapFilledColWidths, renderReport, resolveFilledColWidths };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { evaluateConditionalStyle } from "../rules/evaluate.js";
|
|
2
|
+
import { StylePool } from "@veltra/sheet-core";
|
|
3
|
+
import { mergeCellStyle } from "@veltra/sheet-core/core/command/set-cell-style";
|
|
4
|
+
//#region src/report/render/style-resolver.ts
|
|
5
|
+
/** 静态样式经 StylePool 复用 + 条件样式打平(含 `scope: 'row'` 两阶段叠加) */
|
|
6
|
+
var StyleResolver = class {
|
|
7
|
+
template;
|
|
8
|
+
pool = new StylePool();
|
|
9
|
+
rowStyleByPhysRow = /* @__PURE__ */ new Map();
|
|
10
|
+
constructor(template) {
|
|
11
|
+
this.template = template;
|
|
12
|
+
if (template.styles.length > 0) this.pool.restore(template.styles);
|
|
13
|
+
}
|
|
14
|
+
/** 收集 `scope: 'row'` 命中样式,按物理行累加(须在输出静态格之前完成) */
|
|
15
|
+
collectRowStyle(physicalRow, ctx, rules) {
|
|
16
|
+
if (!rules?.some((rule) => rule.scope === "row")) return;
|
|
17
|
+
const patch = evaluateConditionalStyle(ctx, void 0, rules, "row");
|
|
18
|
+
if (!patch) return;
|
|
19
|
+
const existing = this.rowStyleByPhysRow.get(physicalRow);
|
|
20
|
+
this.rowStyleByPhysRow.set(physicalRow, mergeCellStyle(existing, patch) ?? patch);
|
|
21
|
+
}
|
|
22
|
+
/** 解析模板格基础样式 + `scope: 'cell'` 条件样式 */
|
|
23
|
+
resolveCell(templateRow, templateCol, ctx, rules) {
|
|
24
|
+
const tpl = this.template.cells.find((c) => c.row === templateRow && c.col === templateCol);
|
|
25
|
+
const baseStyle = tpl?.s !== void 0 ? this.pool.get(tpl.s) : void 0;
|
|
26
|
+
const merged = evaluateConditionalStyle(ctx, baseStyle, rules, "cell");
|
|
27
|
+
if (!merged) return tpl?.s;
|
|
28
|
+
if (merged === baseStyle) return tpl?.s;
|
|
29
|
+
return this.pool.intern(merged);
|
|
30
|
+
}
|
|
31
|
+
/** 无绑定的静态格:仅模板样式 */
|
|
32
|
+
resolveStatic(templateRow, templateCol) {
|
|
33
|
+
return this.template.cells.find((c) => c.row === templateRow && c.col === templateCol)?.s;
|
|
34
|
+
}
|
|
35
|
+
/** 将行级样式叠加到格级样式 id */
|
|
36
|
+
mergeRowStyle(styleId, physicalRow) {
|
|
37
|
+
const rowPatch = this.rowStyleByPhysRow.get(physicalRow);
|
|
38
|
+
if (!rowPatch) return styleId;
|
|
39
|
+
const base = styleId !== void 0 ? this.pool.get(styleId) : void 0;
|
|
40
|
+
const merged = mergeCellStyle(base, rowPatch);
|
|
41
|
+
if (!merged) return styleId;
|
|
42
|
+
if (merged === base) return styleId;
|
|
43
|
+
return this.pool.intern(merged);
|
|
44
|
+
}
|
|
45
|
+
snapshot() {
|
|
46
|
+
return this.pool.snapshot();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { StyleResolver };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ReportBinding } from "../types.js";
|
|
2
|
+
import { CellAddress, CellRange, CellSnapshotItem, SheetSnapshot } from "@veltra/sheet-core";
|
|
3
|
+
//#region src/report/render/template-index.d.ts
|
|
4
|
+
interface IndexedBinding {
|
|
5
|
+
addr: CellAddress;
|
|
6
|
+
binding: ReportBinding;
|
|
7
|
+
}
|
|
8
|
+
/** 模板一次性索引:绑定直查、静态格直查、行/列父子树 */
|
|
9
|
+
interface TemplateIndex {
|
|
10
|
+
readonly bindings: ReadonlyMap<number, IndexedBinding>;
|
|
11
|
+
readonly staticCells: ReadonlyMap<number, CellSnapshotItem>;
|
|
12
|
+
readonly rowChildren: ReadonlyMap<number, readonly CellAddress[]>;
|
|
13
|
+
readonly colChildren: ReadonlyMap<number, readonly CellAddress[]>;
|
|
14
|
+
readonly logicalRows: readonly number[];
|
|
15
|
+
readonly logicalCols: readonly number[];
|
|
16
|
+
readonly merges: readonly CellRange[];
|
|
17
|
+
bindingAt(addr: CellAddress): ReportBinding | undefined;
|
|
18
|
+
cellAt(addr: CellAddress): CellSnapshotItem | undefined;
|
|
19
|
+
bindingsOnRow(row: number): readonly IndexedBinding[];
|
|
20
|
+
bindingsOnCol(col: number): readonly IndexedBinding[];
|
|
21
|
+
isBindingAddr(addr: CellAddress): boolean;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { IndexedBinding, TemplateIndex };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { formatCellAddress } from "../binding.js";
|
|
2
|
+
import { cellKey } from "@veltra/sheet-core";
|
|
3
|
+
//#region src/report/render/template-index.ts
|
|
4
|
+
/** 模板结构约束违反时抛出(附 A1 标签) */
|
|
5
|
+
var TemplateStructureError = class extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "TemplateStructureError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function addressesEqual(a, b) {
|
|
12
|
+
return a.row === b.row && a.col === b.col;
|
|
13
|
+
}
|
|
14
|
+
function collectLogicalRows(template) {
|
|
15
|
+
const rows = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const cell of template.cells) rows.add(cell.row);
|
|
17
|
+
for (const item of template.meta ?? []) rows.add(item.row);
|
|
18
|
+
return [...rows].sort((a, b) => a - b);
|
|
19
|
+
}
|
|
20
|
+
function collectLogicalCols(template) {
|
|
21
|
+
const cols = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const cell of template.cells) cols.add(cell.col);
|
|
23
|
+
for (const item of template.meta ?? []) cols.add(item.col);
|
|
24
|
+
return [...cols].sort((a, b) => a - b);
|
|
25
|
+
}
|
|
26
|
+
function validateParentExists(addr, parent, bindings, direction) {
|
|
27
|
+
if (!parent) return;
|
|
28
|
+
const label = formatCellAddress(addr);
|
|
29
|
+
const parentLabel = formatCellAddress(parent);
|
|
30
|
+
if (!bindings.has(cellKey(parent))) throw new TemplateStructureError(`单元格 ${label} 的${direction === "row" ? "行" : "列"}方向父格 ${parentLabel} 不存在绑定`);
|
|
31
|
+
}
|
|
32
|
+
function detectParentCycle(start, bindings, direction) {
|
|
33
|
+
const label = formatCellAddress(start);
|
|
34
|
+
const visited = /* @__PURE__ */ new Set();
|
|
35
|
+
let current = start;
|
|
36
|
+
while (current) {
|
|
37
|
+
const key = cellKey(current);
|
|
38
|
+
if (visited.has(key)) throw new TemplateStructureError(`单元格 ${label} 的${direction === "row" ? "行" : "列"}方向父链存在环`);
|
|
39
|
+
visited.add(key);
|
|
40
|
+
const entry = bindings.get(key);
|
|
41
|
+
if (!entry) break;
|
|
42
|
+
current = direction === "row" ? entry.binding.rowParent : entry.binding.colParent;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function appendChild(map, parentKey, child) {
|
|
46
|
+
const list = map.get(parentKey);
|
|
47
|
+
if (list) {
|
|
48
|
+
list.push(child);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
map.set(parentKey, [child]);
|
|
52
|
+
}
|
|
53
|
+
/** 由模板快照构建索引并校验结构约束(ADR-0005 决策 3) */
|
|
54
|
+
function buildTemplateIndex(template) {
|
|
55
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
56
|
+
const bindingAddrs = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const item of template.meta ?? []) {
|
|
58
|
+
if (item.namespace !== "report") continue;
|
|
59
|
+
const addr = {
|
|
60
|
+
row: item.row,
|
|
61
|
+
col: item.col
|
|
62
|
+
};
|
|
63
|
+
const key = cellKey(addr);
|
|
64
|
+
if (bindings.has(key)) throw new TemplateStructureError(`单元格 ${formatCellAddress(addr)} 存在重复绑定`);
|
|
65
|
+
bindings.set(key, {
|
|
66
|
+
addr,
|
|
67
|
+
binding: item.payload
|
|
68
|
+
});
|
|
69
|
+
bindingAddrs.add(key);
|
|
70
|
+
}
|
|
71
|
+
const staticCells = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const cell of template.cells) {
|
|
73
|
+
const key = cellKey({
|
|
74
|
+
row: cell.row,
|
|
75
|
+
col: cell.col
|
|
76
|
+
});
|
|
77
|
+
if (bindingAddrs.has(key)) continue;
|
|
78
|
+
staticCells.set(key, cell);
|
|
79
|
+
}
|
|
80
|
+
const rowChildren = /* @__PURE__ */ new Map();
|
|
81
|
+
const colChildren = /* @__PURE__ */ new Map();
|
|
82
|
+
for (const { addr, binding } of bindings.values()) {
|
|
83
|
+
validateParentExists(addr, binding.rowParent, bindings, "row");
|
|
84
|
+
validateParentExists(addr, binding.colParent, bindings, "col");
|
|
85
|
+
detectParentCycle(addr, bindings, "row");
|
|
86
|
+
detectParentCycle(addr, bindings, "col");
|
|
87
|
+
if (binding.rowParent) appendChild(rowChildren, cellKey(binding.rowParent), addr);
|
|
88
|
+
if (binding.colParent) appendChild(colChildren, cellKey(binding.colParent), addr);
|
|
89
|
+
}
|
|
90
|
+
const logicalRows = collectLogicalRows(template);
|
|
91
|
+
const logicalCols = collectLogicalCols(template);
|
|
92
|
+
const merges = template.merges ?? [];
|
|
93
|
+
const bindingsByRow = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const entry of bindings.values()) {
|
|
95
|
+
const list = bindingsByRow.get(entry.addr.row);
|
|
96
|
+
if (list) list.push(entry);
|
|
97
|
+
else bindingsByRow.set(entry.addr.row, [entry]);
|
|
98
|
+
}
|
|
99
|
+
for (const list of bindingsByRow.values()) list.sort((a, b) => a.addr.col - b.addr.col);
|
|
100
|
+
const bindingsByCol = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const entry of bindings.values()) {
|
|
102
|
+
const list = bindingsByCol.get(entry.addr.col);
|
|
103
|
+
if (list) list.push(entry);
|
|
104
|
+
else bindingsByCol.set(entry.addr.col, [entry]);
|
|
105
|
+
}
|
|
106
|
+
for (const list of bindingsByCol.values()) list.sort((a, b) => a.addr.row - b.addr.row);
|
|
107
|
+
return {
|
|
108
|
+
bindings,
|
|
109
|
+
staticCells,
|
|
110
|
+
rowChildren,
|
|
111
|
+
colChildren,
|
|
112
|
+
logicalRows,
|
|
113
|
+
logicalCols,
|
|
114
|
+
merges,
|
|
115
|
+
bindingAt(addr) {
|
|
116
|
+
return bindings.get(cellKey(addr))?.binding;
|
|
117
|
+
},
|
|
118
|
+
cellAt(addr) {
|
|
119
|
+
return staticCells.get(cellKey(addr));
|
|
120
|
+
},
|
|
121
|
+
bindingsOnRow(row) {
|
|
122
|
+
return bindingsByRow.get(row) ?? [];
|
|
123
|
+
},
|
|
124
|
+
bindingsOnCol(col) {
|
|
125
|
+
return bindingsByCol.get(col) ?? [];
|
|
126
|
+
},
|
|
127
|
+
isBindingAddr(addr) {
|
|
128
|
+
return bindings.has(cellKey(addr));
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** 判断两地址是否相同(供 coordinate 层使用) */
|
|
133
|
+
function sameAddress(a, b) {
|
|
134
|
+
return addressesEqual(a, b);
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { TemplateStructureError, buildTemplateIndex, sameAddress };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ConditionalRule } from "../types.js";
|
|
2
|
+
import { CellStyle } from "@veltra/sheet-core";
|
|
3
|
+
//#region src/report/rules/evaluate.d.ts
|
|
4
|
+
/** 条件样式求值上下文:当前展示值 + 可选同记录字段 */
|
|
5
|
+
interface ConditionalEvalContext {
|
|
6
|
+
cellValue: unknown;
|
|
7
|
+
bindingField: string;
|
|
8
|
+
record?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
/** 按规则配置的求值字段从上下文取比较值 */
|
|
11
|
+
declare function resolveRuleCompareValue(ctx: ConditionalEvalContext, rule: ConditionalRule): unknown;
|
|
12
|
+
/** 将单元格值转为可比较的有限数值;无法转换时返回 null */
|
|
13
|
+
declare function toComparableNumber(value: unknown): number | null;
|
|
14
|
+
/**
|
|
15
|
+
* 评估单条条件规则是否命中。
|
|
16
|
+
* 数值比较运算符在双方均可解析为有限数时按数值比较;`eq` 在无法数值化时回退字符串相等。
|
|
17
|
+
*/
|
|
18
|
+
declare function evaluateCondition(cellValue: unknown, rule: ConditionalRule): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* 按规则数组顺序评估条件样式,将命中规则的 style 增量依次合并到 baseStyle。
|
|
21
|
+
* 无规则或未命中时原样返回 baseStyle。
|
|
22
|
+
*/
|
|
23
|
+
declare function evaluateConditionalStyle(ctx: ConditionalEvalContext, baseStyle: CellStyle | undefined, rules: readonly ConditionalRule[] | undefined, scope?: 'cell' | 'row'): CellStyle | undefined;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ConditionalEvalContext, evaluateCondition, evaluateConditionalStyle, resolveRuleCompareValue, toComparableNumber };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mergeCellStyle } from "@veltra/sheet-core/core/command/set-cell-style";
|
|
2
|
+
//#region src/report/rules/evaluate.ts
|
|
3
|
+
function ruleScope(rule) {
|
|
4
|
+
return rule.scope ?? "cell";
|
|
5
|
+
}
|
|
6
|
+
/** 按规则配置的求值字段从上下文取比较值 */
|
|
7
|
+
function resolveRuleCompareValue(ctx, rule) {
|
|
8
|
+
if (rule.field !== void 0 && ctx.record !== void 0) return ctx.record[rule.field];
|
|
9
|
+
return ctx.cellValue;
|
|
10
|
+
}
|
|
11
|
+
/** 将单元格值转为可比较的有限数值;无法转换时返回 null */
|
|
12
|
+
function toComparableNumber(value) {
|
|
13
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
14
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
15
|
+
const parsed = Number(value);
|
|
16
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function isBetweenValue(value) {
|
|
21
|
+
return Array.isArray(value) && value.length === 2;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 评估单条条件规则是否命中。
|
|
25
|
+
* 数值比较运算符在双方均可解析为有限数时按数值比较;`eq` 在无法数值化时回退字符串相等。
|
|
26
|
+
*/
|
|
27
|
+
function evaluateCondition(cellValue, rule) {
|
|
28
|
+
const { operator, value: ruleValue } = rule;
|
|
29
|
+
switch (operator) {
|
|
30
|
+
case "gt":
|
|
31
|
+
case "gte":
|
|
32
|
+
case "lt":
|
|
33
|
+
case "lte":
|
|
34
|
+
case "eq": {
|
|
35
|
+
const left = toComparableNumber(cellValue);
|
|
36
|
+
const right = toComparableNumber(ruleValue);
|
|
37
|
+
if (left !== null && right !== null) {
|
|
38
|
+
if (operator === "gt") return left > right;
|
|
39
|
+
if (operator === "gte") return left >= right;
|
|
40
|
+
if (operator === "lt") return left < right;
|
|
41
|
+
if (operator === "lte") return left <= right;
|
|
42
|
+
return left === right;
|
|
43
|
+
}
|
|
44
|
+
if (operator === "eq") {
|
|
45
|
+
if (typeof cellValue === "string" && typeof ruleValue === "string") return cellValue === ruleValue;
|
|
46
|
+
return Object.is(cellValue, ruleValue);
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
case "between": {
|
|
51
|
+
if (!isBetweenValue(ruleValue)) return false;
|
|
52
|
+
const left = toComparableNumber(cellValue);
|
|
53
|
+
const min = toComparableNumber(ruleValue[0]);
|
|
54
|
+
const max = toComparableNumber(ruleValue[1]);
|
|
55
|
+
if (left === null || min === null || max === null) return false;
|
|
56
|
+
return left >= min && left <= max;
|
|
57
|
+
}
|
|
58
|
+
case "contains":
|
|
59
|
+
if (typeof cellValue !== "string") return false;
|
|
60
|
+
if (typeof ruleValue !== "string") return false;
|
|
61
|
+
return cellValue.includes(ruleValue);
|
|
62
|
+
default: return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 按规则数组顺序评估条件样式,将命中规则的 style 增量依次合并到 baseStyle。
|
|
67
|
+
* 无规则或未命中时原样返回 baseStyle。
|
|
68
|
+
*/
|
|
69
|
+
function evaluateConditionalStyle(ctx, baseStyle, rules, scope = "cell") {
|
|
70
|
+
if (!rules || rules.length === 0) return baseStyle;
|
|
71
|
+
let merged = baseStyle;
|
|
72
|
+
let matched = false;
|
|
73
|
+
for (const rule of rules) {
|
|
74
|
+
if (ruleScope(rule) !== scope) continue;
|
|
75
|
+
if (!evaluateCondition(resolveRuleCompareValue(ctx, rule), rule)) continue;
|
|
76
|
+
merged = mergeCellStyle(merged, rule.style);
|
|
77
|
+
matched = true;
|
|
78
|
+
}
|
|
79
|
+
return matched ? merged : baseStyle;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { evaluateCondition, evaluateConditionalStyle, resolveRuleCompareValue, toComparableNumber };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DatasetField, DatasetRecords, ParamValues, QueryParamDef } from "./types.js";
|
|
2
|
+
import { DataConnection, DataConnector, Result } from "./connector.js";
|
|
3
|
+
import { SheetSnapshot } from "@veltra/sheet-core";
|
|
4
|
+
//#region src/report/template.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 模板内嵌的数据集定义
|
|
7
|
+
* 与 playground DatasetDef 同构,connectionId 替换为内嵌的完整连接对象。
|
|
8
|
+
*/
|
|
9
|
+
interface ReportDatasetDef {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
connection: DataConnection;
|
|
13
|
+
sql: string;
|
|
14
|
+
/** 参数元数据覆盖(label / 类型 / 默认值 / 选项) */
|
|
15
|
+
paramOverrides?: Record<string, Partial<Omit<QueryParamDef, 'id'>>>;
|
|
16
|
+
/** 字段中文名覆盖(name → label);查看器不消费,随模板序列化往返供设计器还原 */
|
|
17
|
+
fieldOverrides?: Record<string, Partial<Pick<DatasetField, 'label'>>>;
|
|
18
|
+
}
|
|
19
|
+
/** 当前支持的报表模板版本 */
|
|
20
|
+
declare const REPORT_TEMPLATE_VERSION = 1;
|
|
21
|
+
/** 模板版本不兼容时抛出 */
|
|
22
|
+
declare class IncompatibleTemplateVersionError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 自包含 Report Template:SheetSnapshot + 内嵌数据集定义 + 版本段。
|
|
27
|
+
* 查看器仅凭 `template` + `connector` 即可完成「参数提取 → 取数 → 展开渲染」闭环;
|
|
28
|
+
* 模板可 JSON 序列化流转。
|
|
29
|
+
*
|
|
30
|
+
* `colWidths` 继承自 `SheetSnapshot.colWidths`(模型列索引 → 像素)。
|
|
31
|
+
* 注意:`Sheet.snapshot()` 不产生 `datasets` / `version` 字段,经 `restore()`/`snapshot()` 往返会丢失,
|
|
32
|
+
* 由设计器 `getTemplate()` 在吐出快照时重新附加。
|
|
33
|
+
*/
|
|
34
|
+
interface ReportTemplate extends SheetSnapshot {
|
|
35
|
+
/** 模板结构版本;当前值为 `REPORT_TEMPLATE_VERSION` */
|
|
36
|
+
version?: number;
|
|
37
|
+
datasets?: ReportDatasetDef[];
|
|
38
|
+
}
|
|
39
|
+
/** 载入模板前校验版本:缺失或高于当前支持版本时抛可读错误 */
|
|
40
|
+
declare function assertCompatibleTemplateVersion(template: ReportTemplate): void;
|
|
41
|
+
/**
|
|
42
|
+
* 由工作簿快照构造带版本段的 Report Template。
|
|
43
|
+
* `colWidths` 随 `snapshot` 展开(已在 `SheetSnapshot.colWidths`)。
|
|
44
|
+
*/
|
|
45
|
+
declare function createReportTemplate(snapshot: SheetSnapshot, datasets?: ReportDatasetDef[]): ReportTemplate;
|
|
46
|
+
/** 读取模板内嵌的数据集定义(旧模板未内嵌时回退空数组) */
|
|
47
|
+
declare function getTemplateDatasets(template: ReportTemplate): ReportDatasetDef[];
|
|
48
|
+
/** 模板实际绑定的数据集 id(按绑定在 meta 中的出现顺序去重;绑定即真相,ADR-0002 决策 4) */
|
|
49
|
+
declare function getBoundDatasetIds(template: SheetSnapshot): string[];
|
|
50
|
+
/**
|
|
51
|
+
* Filter Bar 参数来源(ADR-0002 决策 4):模板实际绑定的数据集的查询参数并集,
|
|
52
|
+
* 同名参数合并、先见数据集为准;绑定引用了模板未定义的数据集时跳过。
|
|
53
|
+
*/
|
|
54
|
+
declare function resolveTemplateParams(template: ReportTemplate): QueryParamDef[];
|
|
55
|
+
/** 以参数定义的默认值生成 Filter Bar 初始运行时值 */
|
|
56
|
+
declare function resolveParamDefaults(params: readonly QueryParamDef[]): ParamValues;
|
|
57
|
+
/**
|
|
58
|
+
* 经数据连接器为模板实际绑定的数据集取数(并行),按数据集 id 折叠为 DatasetRecords。
|
|
59
|
+
* 任一数据集返回业务错误(`ok: false`)则整体透传该错误;绑定但未定义的数据集跳过。
|
|
60
|
+
*/
|
|
61
|
+
declare function fetchTemplateRecords(connector: DataConnector, template: ReportTemplate, values: ParamValues): Promise<Result<DatasetRecords>>;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { IncompatibleTemplateVersionError, REPORT_TEMPLATE_VERSION, ReportDatasetDef, ReportTemplate, assertCompatibleTemplateVersion, createReportTemplate, fetchTemplateRecords, getBoundDatasetIds, getTemplateDatasets, resolveParamDefaults, resolveTemplateParams };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import "./binding.js";
|
|
2
|
+
import { buildParamDefs } from "./params.js";
|
|
3
|
+
//#region src/report/template.ts
|
|
4
|
+
/** 当前支持的报表模板版本 */
|
|
5
|
+
const REPORT_TEMPLATE_VERSION = 1;
|
|
6
|
+
/** 模板版本不兼容时抛出 */
|
|
7
|
+
var IncompatibleTemplateVersionError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "IncompatibleTemplateVersionError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/** 载入模板前校验版本:缺失或高于当前支持版本时抛可读错误 */
|
|
14
|
+
function assertCompatibleTemplateVersion(template) {
|
|
15
|
+
if (template.version === void 0) throw new IncompatibleTemplateVersionError("报表模板缺少 version 字段,请在设计器中重建模板后重试");
|
|
16
|
+
if (template.version > 1) throw new IncompatibleTemplateVersionError(`报表模板版本 ${template.version} 高于当前支持的版本 1,请升级组件库后重试`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 由工作簿快照构造带版本段的 Report Template。
|
|
20
|
+
* `colWidths` 随 `snapshot` 展开(已在 `SheetSnapshot.colWidths`)。
|
|
21
|
+
*/
|
|
22
|
+
function createReportTemplate(snapshot, datasets) {
|
|
23
|
+
return {
|
|
24
|
+
...snapshot,
|
|
25
|
+
version: 1,
|
|
26
|
+
datasets
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** 读取模板内嵌的数据集定义(旧模板未内嵌时回退空数组) */
|
|
30
|
+
function getTemplateDatasets(template) {
|
|
31
|
+
return Array.isArray(template.datasets) ? template.datasets : [];
|
|
32
|
+
}
|
|
33
|
+
/** 模板实际绑定的数据集 id(按绑定在 meta 中的出现顺序去重;绑定即真相,ADR-0002 决策 4) */
|
|
34
|
+
function getBoundDatasetIds(template) {
|
|
35
|
+
const ids = [];
|
|
36
|
+
for (const item of template.meta ?? []) {
|
|
37
|
+
if (item.namespace !== "report") continue;
|
|
38
|
+
const dataset = item.payload.dataset;
|
|
39
|
+
if (typeof dataset === "string" && dataset && !ids.includes(dataset)) ids.push(dataset);
|
|
40
|
+
}
|
|
41
|
+
return ids;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Filter Bar 参数来源(ADR-0002 决策 4):模板实际绑定的数据集的查询参数并集,
|
|
45
|
+
* 同名参数合并、先见数据集为准;绑定引用了模板未定义的数据集时跳过。
|
|
46
|
+
*/
|
|
47
|
+
function resolveTemplateParams(template) {
|
|
48
|
+
const datasets = getTemplateDatasets(template);
|
|
49
|
+
const map = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const datasetId of getBoundDatasetIds(template)) {
|
|
51
|
+
const dataset = datasets.find((item) => item.id === datasetId);
|
|
52
|
+
if (!dataset) continue;
|
|
53
|
+
for (const param of buildParamDefs(dataset.sql, dataset.paramOverrides)) if (!map.has(param.id)) map.set(param.id, param);
|
|
54
|
+
}
|
|
55
|
+
return Array.from(map.values());
|
|
56
|
+
}
|
|
57
|
+
/** 以参数定义的默认值生成 Filter Bar 初始运行时值 */
|
|
58
|
+
function resolveParamDefaults(params) {
|
|
59
|
+
const values = {};
|
|
60
|
+
for (const param of params) values[param.id] = param.defaultValue;
|
|
61
|
+
return values;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 经数据连接器为模板实际绑定的数据集取数(并行),按数据集 id 折叠为 DatasetRecords。
|
|
65
|
+
* 任一数据集返回业务错误(`ok: false`)则整体透传该错误;绑定但未定义的数据集跳过。
|
|
66
|
+
*/
|
|
67
|
+
async function fetchTemplateRecords(connector, template, values) {
|
|
68
|
+
const datasets = getTemplateDatasets(template);
|
|
69
|
+
const targets = getBoundDatasetIds(template).map((datasetId) => datasets.find((item) => item.id === datasetId)).filter((dataset) => dataset !== void 0);
|
|
70
|
+
const results = await Promise.all(targets.map((dataset) => connector.query(dataset.connection, dataset.sql, values)));
|
|
71
|
+
const records = {};
|
|
72
|
+
for (let i = 0; i < targets.length; i++) {
|
|
73
|
+
const result = results[i];
|
|
74
|
+
if (!result.ok) return result;
|
|
75
|
+
records[targets[i].id] = result.data.rows;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
data: records
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { IncompatibleTemplateVersionError, REPORT_TEMPLATE_VERSION, assertCompatibleTemplateVersion, createReportTemplate, fetchTemplateRecords, getBoundDatasetIds, getTemplateDatasets, resolveParamDefaults, resolveTemplateParams };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CellAddress, CellStylePatch } from "@veltra/sheet-core";
|
|
2
|
+
//#region src/report/types.d.ts
|
|
3
|
+
/** 报表绑定扩展方向 */
|
|
4
|
+
type ReportExpand = 'down' | 'right' | 'none';
|
|
5
|
+
/** 报表绑定聚合方式 */
|
|
6
|
+
type ReportAggregate = 'list' | 'group' | 'sum' | 'avg' | 'count' | 'max' | 'min';
|
|
7
|
+
/** 报表绑定排序 */
|
|
8
|
+
type ReportSort = 'asc' | 'desc' | 'none';
|
|
9
|
+
/**
|
|
10
|
+
* 设计器预设标签:引擎不读,仅用于 UI 展示与一键切换。
|
|
11
|
+
* 缺失时 Action Pill 显示「自定义」。
|
|
12
|
+
*/
|
|
13
|
+
type ReportPreset = 'groupHeader' | 'detail' | 'subtotal' | 'grandTotal' | 'cross';
|
|
14
|
+
/** 条件样式比较运算符 */
|
|
15
|
+
type ConditionalOperator = 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'between' | 'contains';
|
|
16
|
+
/** 条件样式规则:命中后叠加 CellStyle 增量 */
|
|
17
|
+
interface ConditionalRule {
|
|
18
|
+
operator: ConditionalOperator;
|
|
19
|
+
/** 单值比较用标量;`between` 用 `[min, max]`(含端点) */
|
|
20
|
+
value: unknown;
|
|
21
|
+
style: CellStylePatch;
|
|
22
|
+
/** 求值字段;缺省取绑定格自身字段 */
|
|
23
|
+
field?: string;
|
|
24
|
+
/** 作用范围;缺省 `'cell'` */
|
|
25
|
+
scope?: 'cell' | 'row';
|
|
26
|
+
}
|
|
27
|
+
/** 设计格上的报表绑定(存于 Cell Meta namespace `report`) */
|
|
28
|
+
interface ReportBinding {
|
|
29
|
+
dataset: string;
|
|
30
|
+
field: string;
|
|
31
|
+
expand: ReportExpand;
|
|
32
|
+
aggregate: ReportAggregate;
|
|
33
|
+
/** 纵向从属父格:本格数据受该格当前行实例的值约束 */
|
|
34
|
+
rowParent?: CellAddress;
|
|
35
|
+
/** 横向从属父格:本格数据受该格当前列实例的值约束 */
|
|
36
|
+
colParent?: CellAddress;
|
|
37
|
+
/** 扩展实例是否合并为单个单元格;缺省 true */
|
|
38
|
+
mergeSpan?: boolean;
|
|
39
|
+
/** 分组/明细排序;缺省视为 none */
|
|
40
|
+
sort?: ReportSort;
|
|
41
|
+
/** 条件样式规则(按数组顺序优先级依次求值并合并样式) */
|
|
42
|
+
conditionalRules?: ConditionalRule[];
|
|
43
|
+
/** 设计器预设;引擎不读 */
|
|
44
|
+
preset?: ReportPreset;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 字段 schema(来自数据集配置,非运行时推断)。
|
|
48
|
+
* - `name`:绑定键(写入 Binding.field,英文标识)
|
|
49
|
+
* - `label`:中文显示名(字段面板与占位可读名)
|
|
50
|
+
*/
|
|
51
|
+
interface DatasetField {
|
|
52
|
+
name: string;
|
|
53
|
+
label: string;
|
|
54
|
+
type: 'string' | 'number' | 'date';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 全局数据集目录项(catalog):系统里可用的数据集定义。
|
|
58
|
+
* 与「本报表选用哪些」无关。
|
|
59
|
+
*/
|
|
60
|
+
interface DatasetCatalogItem {
|
|
61
|
+
id: string;
|
|
62
|
+
label: string;
|
|
63
|
+
fields: DatasetField[];
|
|
64
|
+
}
|
|
65
|
+
/** Dataset id → 行记录(渲染 Filled Report 用) */
|
|
66
|
+
type DatasetRecords = Record<string, Record<string, unknown>[]>;
|
|
67
|
+
/** 查询参数控件类型 */
|
|
68
|
+
type QueryParamType = 'text' | 'number' | 'date' | 'date-range' | 'select';
|
|
69
|
+
/** 下拉选项 */
|
|
70
|
+
interface QueryParamOption {
|
|
71
|
+
label: string;
|
|
72
|
+
value: string;
|
|
73
|
+
}
|
|
74
|
+
/** 查询参数定义(从 SQL `${param}` 提取,可在数据集编辑器覆盖元数据) */
|
|
75
|
+
interface QueryParamDef {
|
|
76
|
+
id: string;
|
|
77
|
+
label: string;
|
|
78
|
+
type: QueryParamType;
|
|
79
|
+
defaultValue: unknown;
|
|
80
|
+
options?: QueryParamOption[];
|
|
81
|
+
}
|
|
82
|
+
/** 运行时参数值 */
|
|
83
|
+
type ParamValues = Record<string, unknown>;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { ConditionalOperator, ConditionalRule, DatasetCatalogItem, DatasetField, DatasetRecords, ParamValues, QueryParamDef, QueryParamOption, QueryParamType, ReportAggregate, ReportBinding, ReportExpand, ReportPreset, ReportSort };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/tools/apply-style.ts
|
|
2
|
+
/**
|
|
3
|
+
* 按选区相对渲染网格的跨度判定样式写入目标(对齐 Excel):
|
|
4
|
+
* - 跨满全部列且未跨满全部行 → 行默认样式(rowStyles)
|
|
5
|
+
* - 跨满全部行且未跨满全部列 → 列默认样式(colStyles)
|
|
6
|
+
* - 其余(含全表 / 局部)→ 单元格样式
|
|
7
|
+
*/
|
|
8
|
+
function classifySelectionStyleTarget(range, renderRows, renderCols) {
|
|
9
|
+
const cols = Math.max(renderCols, 1);
|
|
10
|
+
const rows = Math.max(renderRows, 1);
|
|
11
|
+
const spansAllCols = range.start.col === 0 && range.end.col >= cols - 1;
|
|
12
|
+
const spansAllRows = range.start.row === 0 && range.end.row >= rows - 1;
|
|
13
|
+
if (spansAllCols && !spansAllRows) return "row";
|
|
14
|
+
if (spansAllRows && !spansAllCols) return "col";
|
|
15
|
+
return "cell";
|
|
16
|
+
}
|
|
17
|
+
/** 整行/整列选区 → set-axis-style 写入项(partial)或清除项(clear) */
|
|
18
|
+
function axisStyleItemsForRange(range, axis, patch) {
|
|
19
|
+
const start = axis === "row" ? range.start.row : range.start.col;
|
|
20
|
+
const end = axis === "row" ? range.end.row : range.end.col;
|
|
21
|
+
const items = [];
|
|
22
|
+
for (let index = start; index <= end; index++) items.push({
|
|
23
|
+
index,
|
|
24
|
+
...patch
|
|
25
|
+
});
|
|
26
|
+
return items;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { axisStyleItemsForRange, classifySelectionStyleTarget };
|