@guandata/guanvis 0.1.30 → 0.1.32
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/CHANGELOG.md +14 -0
- package/README.md +17 -1
- package/bin/postinstall.js +58 -0
- package/bin/run.js +32 -1
- package/binaries/guanvis-darwin-arm64 +0 -0
- package/binaries/guanvis-darwin-x64 +0 -0
- package/binaries/guanvis-linux-arm64 +0 -0
- package/binaries/guanvis-linux-x64 +0 -0
- package/binaries/guanvis-win32-x64.exe +0 -0
- package/package.json +2 -1
- package/skills/guanvis/SKILL.md +107 -11
- package/skills/guanvis/evals/complex_report_pro/card_01_monthly.js +63 -0
- package/skills/guanvis/evals/complex_report_pro/page.js +6 -0
- package/skills/guanvis/evals/complex_report_pro/schema.js +6 -0
- package/skills/guanvis/evals/percentage_advcalc_roundtrip/README.md +1 -1
- package/skills/guanvis/references/api-reference.md +12 -2
- package/skills/guanvis/references/builder-reference.md +299 -4
- package/skills/guanvis/references/complex-report-pro-patterns.md +339 -0
- package/skills/guanvis/references/metric-chart-reference.md +2 -2
- package/skills/guanvis/references/publish-and-constraints.md +3 -1
- package/skills/guanvis/references/theme.md +1 -1
- package/skills/guanvis/references/troubleshooting.md +2 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# 复杂报表 Pro 配方与排错(AI 优先阅读)
|
|
2
|
+
|
|
3
|
+
本文件是复杂报表 Pro 的"使用手册":结构模式配方、校验红线和报错修复。API 权威定义见 `builder-reference.md` 的 ComplexReportProBuilder 章节;本文不重复 API 表,只回答"这个需求怎么写、报这个错怎么改"。
|
|
4
|
+
|
|
5
|
+
## 0. 选型:什么时候用 Pro
|
|
6
|
+
|
|
7
|
+
**先过门槛,再看形态。** 复杂报表 Pro 不是观远 BI 的默认功能(`COMPLEX_REPORT_PRO` 需要单独开通授权,导入未授权环境会被拒:`请先联系管理员开通COMPLEX_REPORT_PRO权限`)。只有满足以下任一条件才允许选 Pro:
|
|
8
|
+
|
|
9
|
+
1. 用户**明确说了**"复杂报表"或"复杂报表 Pro"(含"创建复杂报表""做一张复杂报表 Pro"等点名表述);
|
|
10
|
+
2. **修改场景**:checkout 下来的目标卡片本身已经是 Pro(保持原类型,不要改成普通卡片)。
|
|
11
|
+
|
|
12
|
+
用户只说"做个表格/报表/明细表/透视表/仪表板"时**一律用原生卡片**(`DATA_GRID`/`SCROLL_TABLE`/`PIVOT_TABLE` 等)。如果需求形态确实是原生卡片表达不了的(多级小计版式、票据套打、段落块),先用原生卡片尽量逼近,并在结果说明中**告知用户**"该版式用复杂报表 Pro 可以完整实现,但 Pro 需要单独授权,如需请明确说明"——由用户决定,不要自作主张升级。
|
|
13
|
+
|
|
14
|
+
确认走 Pro 之后,按形态选结构:
|
|
15
|
+
|
|
16
|
+
| 需求形态 | 用什么 |
|
|
17
|
+
|---|---|
|
|
18
|
+
| 平面明细列表、可交互排序/滚动 | 普通卡片 `DATA_GRID` / `SCROLL_TABLE`(即使已授权 Pro 也不要用 Pro) |
|
|
19
|
+
| 行列交叉 + 自动小计,无格子级版式要求 | 普通卡片 `PIVOT_TABLE`(同上) |
|
|
20
|
+
| 多级分组展开、显式小计/合计行、格子级样式版式(中国式报表) | Pro |
|
|
21
|
+
| 横纵双向交叉且要控制每个格子 | Pro |
|
|
22
|
+
| 每条记录一个多行卡片块(段落明细/档案/票据) | Pro(`beginBlock`) |
|
|
23
|
+
| 固定 Excel 版式套数据(发票/单据) | Pro(外部 xlsx `.setTemplate()`) |
|
|
24
|
+
| 多数据集 JOIN 后出一张表 | Pro(`setDataSourceRelations`);也可先 ETL 合表再用普通卡片 |
|
|
25
|
+
| 按组分页浏览/导出 | Pro(`setPagination` + `countPerPage`) |
|
|
26
|
+
|
|
27
|
+
从零创建优先 **Workbook DSL**(`.setWorkbook()`,可文本审阅、pack 全量校验);只有拿到现成 xlsx 模板文件时才用 `.setTemplate()`。
|
|
28
|
+
|
|
29
|
+
## 1. 模板心智模型(读懂再写)
|
|
30
|
+
|
|
31
|
+
Pro 模板 = Excel 网格上放"模板格"。每个模板格要么绑定数据(`bindDimension/bindMetric/...`),要么是随分组重复的标签(`setContextText`),要么是随扩展复制的公式(`setDynamicFormula`)。**父格(`context`)决定重复结构**:
|
|
32
|
+
|
|
33
|
+
- 纵向维度格是"锚点":每个取值向下复制一行(或一块)。
|
|
34
|
+
- 子格挂 `context: 锚点`,表示"在锚点的每个分组内展开/计算"。
|
|
35
|
+
- GcExcel 按父格树把模板行展开成最终表格;小计/总计是挂在标签格上的聚合。
|
|
36
|
+
|
|
37
|
+
**父格位置规则**(pack 本地强校验,dev29 实证语义):
|
|
38
|
+
|
|
39
|
+
| 关系 | 要求 |
|
|
40
|
+
|---|---|
|
|
41
|
+
| 左父格(纵向扩展维度) | 位于子格**左上方位**即可:同行左、同列上、左上区域都合法(覆盖组标题独占一行的"阶梯布局") |
|
|
42
|
+
| 上父格(横向扩展维度) | 必须**同列上方**(交叉表专用) |
|
|
43
|
+
| 双父格 | `context: "A4*B3"`,左父格+上父格各最多一个 |
|
|
44
|
+
| `setContextText` 标签的父格 | 必须是**维度绑定**,位于左上方位 |
|
|
45
|
+
| `bindSubtotal` 的父格 | 必须是**同行左侧的 `setContextText` 标签** |
|
|
46
|
+
| 小计行公式聚合(`setDynamicFormula` 挂标签) | 标签必须**同行左侧** |
|
|
47
|
+
| `bindGrandTotal` | **禁止**传 `context`(自动 `E=N,C=None`) |
|
|
48
|
+
|
|
49
|
+
**同一数据视图内只写 `context`,绝不写 `filterBy`**(同源 filterBy 会编译成嵌套 `LP(...)`,后端 500;pack 已本地拒绝)。`filterBy` 仅用于跨数据视图父格映射,且 cell 必须同时出现在 `context` 中。
|
|
50
|
+
|
|
51
|
+
## 2. 结构模式配方
|
|
52
|
+
|
|
53
|
+
每个配方给最小正确形态;完整可运行工程见 `evals/complex_report_pro/`(最小 Workbook DSL 工程)。
|
|
54
|
+
|
|
55
|
+
### 2.1 层级分组(区域→客户 两级)
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
sheet.bindDimension("A2", reportField("v", "区域")) // 顶层锚点,无 context
|
|
59
|
+
.bindDimension("B2", reportField("v", "客户"), { context: "A2" }) // 嵌套维度
|
|
60
|
+
.bindMetric("C2", reportField("v", "金额"), { aggregate: "SUM", context: "B2" });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
维度默认纵向扩展 + 相邻同值合并(`group` 缺省即 merge);行级不去重列表用 `group: "list"`。排序 `sort: "asc" | "desc"`。
|
|
64
|
+
|
|
65
|
+
### 2.2 横纵交叉(行=城市,列=类别)
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
sheet.bindDimension("A4", reportField("v", "城市")) // 纵向
|
|
69
|
+
.bindDimension("B3", reportField("v", "类别"), { expansion: "horizontal" }) // 横向
|
|
70
|
+
.bindMetric("B4", reportField("v", "金额"), { aggregate: "SUM", context: "A4*B3" });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2.3 分组小计 + 全表总计(三件套)
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
sheet.bindDimension("A2", reportField("v", "区域"))
|
|
77
|
+
.bindMetric("C2", reportField("v", "金额"), { aggregate: "SUM", context: "A2" })
|
|
78
|
+
.setContextText("A3", "小计", { context: "A2" }) // ① 标签挂维度
|
|
79
|
+
.bindSubtotal("C3", reportField("v", "金额"), { aggregate: "SUM", context: "A3" }) // ② 值挂标签(同行左)
|
|
80
|
+
.bindGrandTotal("C4", reportField("v", "金额"), { aggregate: "SUM" }); // ③ 总计不带 context
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
不要把老复杂报表的 `G_SUBTOTAL/G_GRANDTOTAL` 写进 Pro 模板。多级分组下给**内层维度**做小计时,标签列必须 ≤ 该维度所在列(如三级分组 B 地区/C 省份/D 城市,省份小计标签放 `C4` 挂 `C3`,不能放 A4——校验报 `must be in its column or to the left`);标签行可 `merge("C4:D4")` 横跨到城市列补齐版式。
|
|
84
|
+
|
|
85
|
+
### 2.4 小计行公式聚合(对公式列/数据列求和均可)
|
|
86
|
+
|
|
87
|
+
小计行需要聚合"动态公式列"(如费率)或再聚合数据列时,`setDynamicFormula` 挂小计标签,GcExcel 会把引用改写成整组区域(`SUM(D2)` → `SUM(D2,D4,...)` 或 `SUM(C3:C103)`):
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
sheet.setDynamicFormula("E2", "IFERROR(D2/C2,0)", { context: "B2" }) // 明细行公式列
|
|
91
|
+
.setContextText("B3", "城市小计", { context: "B2" })
|
|
92
|
+
.setDynamicFormula("D3", "SUM(D2)", { context: "B3" }) // 小计行聚合数据列
|
|
93
|
+
.setDynamicFormula("E3", "AVERAGE(E2)", { context: "B3" }); // 小计行聚合公式列
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
小计公式格的数字格式来自它**自己的 style**,不继承明细列。
|
|
97
|
+
|
|
98
|
+
### 2.5 阶梯分组(组标题独占一行)
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
sheet.bindDimension("A2", reportField("v", "国家")) // 组标题行锚点
|
|
102
|
+
.bindDimension("B3", reportField("v", "城市"), { context: "A2" }) // 明细行跨行挂锚点
|
|
103
|
+
.bindMetric("C3", reportField("v", "金额"), { aggregate: "SUM", context: "B3" })
|
|
104
|
+
.setContextText("B4", "小计", { context: "A2" }) // 小计标签也跨行挂锚点
|
|
105
|
+
.setDynamicFormula("C4", "SUM(C3)", { context: "B4" });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 2.6 段落明细块(每条记录一个多行块)
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
sheet.beginBlock("A2", reportField("orders", "订单ID")) // 锚点=块左上角,通常是记录 ID(METRIC 可作锚点)
|
|
112
|
+
.text("B2", "货主名称")
|
|
113
|
+
.dimension("C2", reportField("orders", "货主名称"))
|
|
114
|
+
.text("B3", "运货费")
|
|
115
|
+
.metric("D3", reportField("orders", "运货费"), { aggregate: "SUM", numberFormat: "#,##0.00" })
|
|
116
|
+
.spacer("A4") // 块间留白行
|
|
117
|
+
.end();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
块内禁小计/总计;其他模板格不得落入块矩形;**锚点格自动成为维度绑定,不要再对锚点格写 `.text()`/`.dimension()`**(报 `contains multiple template operations`)——锚点直接显示维度值,标签放旁边格。
|
|
121
|
+
|
|
122
|
+
### 2.7 分页(按顶层分组分页)
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
sheet.bindDimension("A3", reportField("v", "客户"), { countPerPage: 20 }); // 唯一 countPerPage,顶层无 context
|
|
126
|
+
report.setPagination(true);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
约束:单 Sheet;全模板禁 `filter/filterBy`;`countPerPage` 只能放在无 `context` 的顶层维度(嵌套子维度会截断祖先合并格,pack 拒绝)。
|
|
130
|
+
|
|
131
|
+
### 2.8 多数据源 JOIN(虚拟视图)
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
report.setDataSourceRelations([{
|
|
135
|
+
tableName: "产品供应商", // 模板里用 产品供应商.字段 引用
|
|
136
|
+
relations: [
|
|
137
|
+
{ leftId: "products", rightId: "suppliers",
|
|
138
|
+
leftColumnKey: "供应商ID", rightColumnKey: "供应商ID", joinType: "INNER" }
|
|
139
|
+
],
|
|
140
|
+
selectedCols: [
|
|
141
|
+
{ viewId: "suppliers", key: "公司名称", name: "" },
|
|
142
|
+
{ viewId: "products", key: "订购量", name: "" }
|
|
143
|
+
]
|
|
144
|
+
}]);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`leftId/rightId/viewId` 写数据视图别名,字段写显示名,pack 自动翻译成子卡 ID/zone key;同组 relations 必须连成连通图。JOIN 键是 METRIC 型 ID 时在子视图用 `AggrType.MAX` 带出。模板只能引用该组 `selectedCols` 输出的字段。
|
|
148
|
+
|
|
149
|
+
### 2.9 条件格式 / 冻结 / 图片
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
sheet.setFreeze(1, 0) // 冻结首行
|
|
153
|
+
.setConditionalFormat("C2:C10000", [ // 区域写大,GcExcel 自动裁剪到数据区
|
|
154
|
+
{ op: ">", value: "1000", backgroundColor: "#FFC7CE", fontColor: "#9C0006" },
|
|
155
|
+
{ formula: "MOD(ROW(),2)=0", backgroundColor: "#EAF3FB" } // 斑马纹
|
|
156
|
+
])
|
|
157
|
+
.bindImage("D2", reportField("v", "图片URL"), { context: "A2" });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
条件格式 op 限 `> < >= <= == !=`,颜色必须 `#RRGGBB`;`value` 是公式片段——数字直接写,文本常量带引号(`value: "\"缺货\""`);多规则可用 `stopIfTrue: true` 命中即停。超链接按位置选形态:**静态格**(标题/说明/返回目录)用 `setHyperlink(cell, url, { tooltip? })`,GcExcel 渲染保留;**扩展区行级链接**必须用 `HYPERLINK()` 动态公式(worksheet 超链条目按固定 ref 定位,不随模板行复制——给扩展区格挂 setHyperlink 只有第一行有链接)。图片导出上限 1000 格,配合 `.setColumnWidthStretch(true)` 控制版式。
|
|
161
|
+
|
|
162
|
+
### 2.9b 打印版式 / 静态资产 / 序号列
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
sheet.setImage("A1", "assets/logo.png") // 本地 logo/印章嵌入(png/jpg/gif,工程内路径)
|
|
166
|
+
.setValue("A2", "序号", { diagonalDown: true }) // 斜线表头
|
|
167
|
+
.setPrintTitleRows(2) // 打印/导出每页重复表头行
|
|
168
|
+
.setHeaderFooter({ footer: "&C第 &P 页 / 共 &N 页" }) // 页脚页码
|
|
169
|
+
.setDynamicFormula("A3", "ROW()-2", { context: "B3" }) // 序号列(父格是同行右侧维度)
|
|
170
|
+
.bindDimension("B3", reportField("v", "城市"), { context: "None" });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
序号列是唯一允许"动态公式挂同行**右侧**父格"的形态;注意此时右侧维度要显式 `context: "None"`(或其真实父格),避免它按默认推断反挂序号格形成环。
|
|
174
|
+
|
|
175
|
+
### 2.9c 排序 / 行级链接 / 涨跌着色(dev29 实证)
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
// "按销售额降序展示城市":排序放数据视图,模板展开顺序跟随数据视图返回顺序
|
|
179
|
+
var v = createCard(ChartType.DATA_GRID, "orders")
|
|
180
|
+
.bindDataset(DS)
|
|
181
|
+
.addRow(f("货主城市"))
|
|
182
|
+
.addMetric(f("应付金额", { aggrType: AggrType.SUM }))
|
|
183
|
+
.addSort(f("应付金额", { aggrType: AggrType.SUM, sortType: SortOrder.DESC }));
|
|
184
|
+
|
|
185
|
+
// 行级可点击链接:HYPERLINK 动态公式随扩展逐行复制(模板格超链接不会复制,别用)
|
|
186
|
+
sheet.setDynamicFormula("C2", 'HYPERLINK("https://bi.example.com/detail","查看")', { context: "A2" })
|
|
187
|
+
// 涨跌着色(正红↑负绿↓):条件数字格式即可,无需 setConditionalFormat
|
|
188
|
+
.bindMetric("D2", reportField("orders", "环比"), {
|
|
189
|
+
aggregate: "SUM", context: "A2",
|
|
190
|
+
numberFormat: '[Red]#,##0.00"↑";[Green]#,##0.00"↓";0.00'
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- 排序只能放数据视图(`addSort`),模板 props 里没有排序语法;聚合值排序用与 metric 相同的 `aggrType` + `sortType`。
|
|
195
|
+
- `HYPERLINK("url","文本")` 里的 URL 含点号不会被误判为数据字段(校验器已处理字符串字面量);URL 可用 `CONCATENATE("https://.../", B3)` 拼单元格值。
|
|
196
|
+
- `.setLimit(n)` 不影响 Pro 导出行数(复验),"前 N 名"需求用数据过滤或让用户在 BI 端配置,不要用 setLimit。
|
|
197
|
+
|
|
198
|
+
### 2.10 计算放哪层:BI 优先,GcExcel 只做排版级(dev29 实证)
|
|
199
|
+
|
|
200
|
+
优先级从高到低,能用上层就不要下沉:
|
|
201
|
+
|
|
202
|
+
| 计算类型 | 放哪 | 写法 |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| 行级公式(`[利润]/[销售额]`、CASE WHEN、日期函数) | 数据集普通计算字段 | `guands dataset calc-field add <dsId> --name .. --formula ..`,然后 `guanvis init --force` 刷新 schema 直接 `f("字段")` 引用 |
|
|
205
|
+
| 比率/均值/占比等**非可加**指标(`SUM(a)/COUNT(b)`) | 数据集聚合计算字段 | `--calc-type aggregation`;卡片/子视图直接 `f("字段")`(不写 aggrType),BI 按当前维度组自动重算 |
|
|
206
|
+
| 可加指标的小计/总计(SUM/COUNT/MAX...) | Pro 模板 | `bindSubtotal`/`bindGrandTotal` 三件套(§2.3) |
|
|
207
|
+
| **非可加**指标的小计/总计 | 每个展示粒度一个数据视图 | 明细=细粒度视图;小计=组粒度视图 + `context: 组维度格` + `filterBy: [{field, cell: 组维度格}]`;总计=无维度视图 + `context: "None", expansion: "none"` |
|
|
208
|
+
| 排版级算术(序号 `ROW()-n`、单格单位换算、`HYPERLINK` 拼链接) | GcExcel 动态公式 | `setDynamicFormula`,仅当 BI 查询无法逐格表达时使用 |
|
|
209
|
+
|
|
210
|
+
三个实证反模式(实验踩过,数值都是错的):
|
|
211
|
+
|
|
212
|
+
- **对聚合计算字段用 `bindSubtotal`/`bindGrandTotal`**:GcExcel 把各明细行的比率求和(各城市单均之和 26661 vs 正确组内重算 62.42)。非可加指标的小计必须走"组粒度视图"。
|
|
213
|
+
- **模板 `aggregate: "COUNT"` 当计数用**:模板聚合作用在**视图返回行**上(明细行恒为 1,小计=组内行数),不是订单数。计数放子视图 zone(`f("订单ID", { aggrType: AggrType.COUNT })`),模板格用 `aggregate: "SUM"` 透传。
|
|
214
|
+
- **派生视图(组粒度/无维度)漏掉基视图的过滤**:明细视图有 `.addFilter(...)` 时,小计/总计视图必须加**同样的过滤**,否则同一行里比率与可加列口径不一致(dev29 实测:总计占比无过滤 1.0575 vs 同行过滤后口径 1.1202)。selector childFilters 会同时作用于全部子视图,无此问题;只有写死的 addFilter 需要手动复制。
|
|
215
|
+
|
|
216
|
+
### 2.11 固定科目表 / 分列常量对比(利润表、资产负债表式)
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
// 每个对比列一个数据视图(addFilter 常量过滤),科目行全是固定行
|
|
220
|
+
var all = createCard(ChartType.DATA_GRID, "all").bindDataset(DS)
|
|
221
|
+
.addMetric(f("应付金额", { aggrType: AggrType.SUM }));
|
|
222
|
+
var north = createCard(ChartType.DATA_GRID, "north").bindDataset(DS)
|
|
223
|
+
.addMetric(f("应付金额", { aggrType: AggrType.SUM }))
|
|
224
|
+
.addFilter(filterField(DS, "货主地区", FilterType.EQ, ["华北"]));
|
|
225
|
+
|
|
226
|
+
var money = { aggregate: "SUM", context: "None", expansion: "none", numberFormat: "#,##0.00" };
|
|
227
|
+
sheet.setValue("A3", "一、营业收入", { bold: true })
|
|
228
|
+
.bindMetric("B3", reportField("all", "应付金额"), money) // 全国列
|
|
229
|
+
.bindMetric("C3", reportField("north", "应付金额"), money) // 华北列
|
|
230
|
+
.setValue("A5", "二、运营净额", { bold: true })
|
|
231
|
+
.setDynamicFormula("B5", "B3-B4", {}, { numberFormat: "#,##0.00" }) // 派生科目行
|
|
232
|
+
.setDynamicFormula("B6", "IF(B3=0,0,B4/B3)", {}, { numberFormat: "0.00%" }); // 比率行
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
要点(dev29 实证):
|
|
236
|
+
- **绑定级 `filter` option 不是常量过滤**——它是 checkout 保真通道(`F=view.field*Cell` 跨视图映射语法)。写 `filter: "字段=值"` 能过 pack,但后端导出直接 500(SearchEngine NPE)。分列对比一律"每列一个数据视图 + addFilter"。
|
|
237
|
+
- 固定单元格全用 `context: "None", expansion: "none"` 的 `bindMetric`;派生科目(净额/比率)用引用固定格的动态公式。
|
|
238
|
+
- 月份/季度桶**不要用 `granularity` 日期粒度**(zone 会生成后端查不到的 SUB_DATE 虚字段"月",pack 现已本地拒绝)——先加 `DATE_FORMAT([日期], 'yyyy-MM')` 计算字段再绑定,横向展开月份列 + `.addSort` 升序。
|
|
239
|
+
|
|
240
|
+
### 2.12 筛选器联动 Pro
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
var sel = createSelector("产品筛选")
|
|
244
|
+
.setId("...")
|
|
245
|
+
.bindField(f("产品名称"))
|
|
246
|
+
.linkTo(0, "产品名称"); // 可联动目标序列包含 Pro 子视图(按 addDataView 顺序)
|
|
247
|
+
registerSelector(sel.build());
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
运行时走 childFilters,小计/总计随筛选重算。发布后可用 `guancli card preview <proId> --filter "字段 EQ 值"` 无头验证。
|
|
251
|
+
|
|
252
|
+
## 3. 数据视图规则
|
|
253
|
+
|
|
254
|
+
- 每个 `addDataView(alias, cardBuilder)` 是一个 `DATA_GRID` 子卡:维度进 `addRow`,聚合值进 `addMetric`。
|
|
255
|
+
- **模板引用的每个字段都必须出现在对应子视图的查询区**(row/metric),否则 pack 拒绝。
|
|
256
|
+
- `bindDimension` 的字段必须是 DIM;数值列(METRIC)不能做维度绑定——需要按数值分组时先在数据集加计算字段转 STRING。
|
|
257
|
+
- 子视图 `.setLimit(n)` **不会**限制 Pro 导出行数,别用它裁数据。
|
|
258
|
+
- 聚合计算字段(schema 里 `calculationType: "aggregation"`)进 `addMetric` 时**不要**写 `aggrType`,BI 按视图维度自动聚合;配套的分层用法见 §2.10。
|
|
259
|
+
- **日期字段不要带 `granularity` 进 Pro 数据视图**(后端查不到派生的 SUB_DATE 字段,pack 已本地拒绝)——月/季分桶用 `DATE_FORMAT` 计算字段。
|
|
260
|
+
- 样式规范:主标题行高 32–36pt、表头 22–26pt、明细 18–22pt,标题 `verticalAlignment: "center"`;宽表/图片表优先 `.setColumnWidthStretch(true)`。
|
|
261
|
+
|
|
262
|
+
## 4. 校验报错 → 修复
|
|
263
|
+
|
|
264
|
+
pack/publish 阶段(本地校验,改 DSL 即可):
|
|
265
|
+
|
|
266
|
+
| 报错关键词 | 原因 | 修复 |
|
|
267
|
+
|---|---|---|
|
|
268
|
+
| `context parent ... must be in its column or to the left` | 父格在子格右侧 | 纵向父格允许**任意行**(其列或左侧)——"汇总在上"(总计/小计在明细上方、标签挂下方维度)已实证合法;同行右侧仅序号列动态公式形态放行 |
|
|
269
|
+
| `must be a vertical parent in the upper-left area or a horizontal parent above in the same column` | 上父格不是横向维度,或横向父格不在同列上方 | 交叉表列头加 `expansion: "horizontal"` 且放子格正上方;层级分组只用左父格 |
|
|
270
|
+
| `subtotal parent ... must be a setContextText cell` / `must be on the same row to the left` | `bindSubtotal` 没挂同行左侧标签 | 按 §2.3 三件套补 `setContextText`,值与标签同行 |
|
|
271
|
+
| `redundantly filters the same view ... use context only` | 同数据视图写了 `filterBy` | 删掉 filterBy,同源层级/交叉只写 `context` |
|
|
272
|
+
| `filter and filterBy cannot be combined` | 两者并用 | 只留一个 |
|
|
273
|
+
| `unknown option "context"`(出现在 bindGrandTotal 上) | `bindGrandTotal` 传了 context | 删 context;总计只支持 `aggregate/numberFormat/style` |
|
|
274
|
+
| `unsupported aggregate "COUNTA"; supported aggregates: ...` | 聚合不在白名单 | 换 `SUM/COUNT/AVERAGE/MAX/MIN/PRODUCT/STDDEV/STDDEVP/VAR/VARP`;`AVG` 会自动规范化为 `AVERAGE` |
|
|
275
|
+
| `pagination requires exactly one worksheet` / `countPerPage ... must use a top-level dimension without context` / `pagination cannot be combined with filter` | 分页约束违规 | 见 §2.7:单 Sheet、去掉 filter/filterBy、countPerPage 移到顶层维度 |
|
|
276
|
+
| `block anchor ... must be at the block's top-left` / `subtotal/grand-total bindings are not supported inside a repeated block` / `template cell ... overlaps block` | 块规则违规 | 锚点放块左上角;小计移出块;外部格移出块矩形 |
|
|
277
|
+
| `dimension binding "X" ... must reference a dimension, got metaType=METRIC` | 数值列做维度 | 见 §3:换 DIM 字段或加计算字段 |
|
|
278
|
+
| `unknown data view "X"` / `unknown column "X" in data view` / `Field 'MISSING_X' ... was not found in dataset schema` | 模板引用字段没进子视图查询区,或视图别名拼错 | 把字段加进对应 `addDataView` 子卡的 row/metric;核对别名与 `dataSourceRelations.tableName` |
|
|
279
|
+
| `relations do not connect all joined data views into one graph` | 多源 JOIN 断图 | 补 relations 使所有视图连通 |
|
|
280
|
+
| `parent graph cycle`(环路径 `A1->B2->A2->B2`) | 外部模板父格循环依赖:值格省略 `C=` 被默认推断挂左侧标签,标签又显式挂回值格 | 给块内所有值格显式写 `C=锚点`;用 `guanvis report inspect <xlsx>` 看推断父格链 |
|
|
281
|
+
| `unknown option "X"` | 绑定 options 拼错 | 只用 `expansion/fillMode/context/countPerPage/filter/filterBy/numberFormat/style`(维度另有 `group/sort`,度量另有 `aggregate`) |
|
|
282
|
+
|
|
283
|
+
发布后/线上阶段:
|
|
284
|
+
|
|
285
|
+
| 现象 | 原因 | 修复 |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| 导入成功但 `GET /api/card` 报"找不到相关卡片"或 `None.get` | Pro 卡没和引用它的 Page 同包发布;或卡 ID 不是"首字母+23 位 hex"形态 | Pro 必须与 Page 一起 pack/publish;ID 一律用 `guanvis genid` 生成 |
|
|
288
|
+
| 同包发布、ID 形态正确,Import succeeded 后 preview 仍持续"找不到相关卡片" | 该 ID 此前经历过"失败发布→成功导入"混合序列,后端残留坏状态(dev29 两次复现) | 不要调 payload:`guanvis genid` 换全新 card/page ID 重发,立即恢复 |
|
|
289
|
+
| `dynamic formula cannot reference data fields` 但公式里只有 URL/中文句号 | 旧版校验器把字符串字面量内的点号 token 误判为 `view.field`(已修复) | 升级 guanvis;`HYPERLINK("https://...","文本")` 等含点字符串现在直接可用 |
|
|
290
|
+
| 发布成功但导出 500 `SearchEngine` NPE | 绑定级 `filter` 写了自创语法(如 `"字段=值"`)——F= 是跨视图映射通道不是常量过滤 | 分列常量对比改"每列一个数据视图 + addFilter"(§2.11) |
|
|
291
|
+
| `field ... is a date-granularity zone field, which the Pro backend cannot query` | Pro 数据视图里的日期字段带了 `granularity` | 加 `DATE_FORMAT([日期],'yyyy-MM')` 计算字段并绑定它 |
|
|
292
|
+
| `unsupported option(s): filter`(bindGrandTotal 上) | `bindGrandTotal` 只接受 aggregate/numberFormat/style | 需要过滤的固定值格改 `bindMetric` + `context:"None", expansion:"none"`,过滤放数据视图 |
|
|
293
|
+
| `card preview` 结果残留 `{{...}}` | 模板表达式没被 GcExcel 识别(语法/字段/视图名错) | 用 `guanvis report inspect` 核对表达式;核对字段是否在子视图查询区 |
|
|
294
|
+
| 后端 500 `父格设置存在循环依赖` | 同 pack 表的 parent graph cycle(旧包未经过本地校验) | 重新 pack(本地环检测已覆盖),按上表修复 |
|
|
295
|
+
| 后端 500 嵌套 `LP(...)` | 旧包同源 filterBy | 删同源 filterBy 重发 |
|
|
296
|
+
| 导入报 `该应用具有 COMPLEX_REPORT_PRO,请先联系管理员开通...` | 目标环境未开 Pro license | 换有授权的环境或联系管理员,不是脚本问题 |
|
|
297
|
+
| 页面上 Pro 卡渲染空白但 preview xlsx 正常 | 前端渲染波动 | 稍候刷新或 `guanvis screenshot` 复核;以 preview xlsx 为准 |
|
|
298
|
+
|
|
299
|
+
## 5. 编辑已有 Pro(checkout 闭环)
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
guanvis checkout <pageId> -d ./work # 下载 templates/<cdId>.xlsx + 生成 createComplexReportPro 脚本(templateVersion 自增)
|
|
303
|
+
guanvis report inspect ./work/templates/<cdId>.xlsx # 看模板格/父格链/环检测(不打开 Excel)
|
|
304
|
+
guanvis report decompile ./work/templates/<cdId>.xlsx -o wb.js # 反编译为 Workbook DSL 或 patch 脚手架
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
- 创建与编辑共用一套 Workbook DSL,仅基底不同:`createReportWorkbook()` 从零构建(`addSheet`),`createReportWorkbook(templatePath)` 基底增量编辑(`editSheet`);挂载入口统一 `.setWorkbook(workbook)`。
|
|
308
|
+
- decompile 全量可表达(冻结窗格、单元格比较/公式条件格式、序号列、阶梯布局都在覆盖内)→ 输出从零构建源码,把 card JS 的 `.setTemplate(...)` 换成 `.setWorkbook(workbook)`,模板即文本化,直接改 DSL。
|
|
309
|
+
- 模板含 DSL 无法表达部件(内嵌图片/数据验证等)→ 自动输出基底编辑脚手架:只写增量操作叠加在原模板上,未触及部件(图片、条件格式、冻结、样式)原样保留。`editSheet` 内方法与 `addSheet` 完全同名,另加仅编辑模式合法的原语:`clearCell(cell)` / `unmerge(range)` / `insertRows(row, n?)` / `removeRows(row, n?)` / `insertCols(col, n?)` / `removeCols(col, n?)`(结构移动会自动重写模板表达式里的单元格引用与 `C=`/`F=` 父格,删除被引用的模板格会拒绝)/ `setCellStyle(cellOrRange, style)`(合并语义,只改给出的属性);落点已是模板表达式时须 `{ overwrite: true }` 显式声明替换。插入行列后,后续操作坐标按移位后的最终位置书写。
|
|
310
|
+
- **多轮编辑防护**:基底编辑的发布结果 = 本地 templates/ 快照 + JS 里全部 editSheet 操作,多轮修改必须在原操作序列后**累积追加**(只保留最后一轮会丢掉之前的修改)。preview/diff/pack/publish 检测到本地基底落后于线上(JS templateVersion 比 base 快照 version 大超过 1)时会打印 stale-base warning。想改写干净增量时运行 `guanvis checkout <pgId> -d <dir> --refresh-base`:只刷新 .guanvis/raw、.guanvis/base、templates/、schema.js,不动 card_*.js / page.js;刷新后**必须删除已发布过的旧操作**(它们已包含在新基底里,保留会重复应用,如 insertRows 插两次行)。
|
|
311
|
+
- 批量/样式增强(addSheet/editSheet 通用):`setRow(startCell, values, style?)` / `setColumn(...)` 一次写一行/一列(`null` 跳格);`setColumnWidth("C:E", 14)` 范围列宽;`setSheetDefaults({...})` 默认宽高;样式对象支持 `underline`(合计线)、`strikeout`、`fontFamily`、`indent`(科目缩进)、`border`/`borderTop`…(`{ style: "double", color }`,会计"下双线"= `borderBottom: { style: "double" }`)。dev29 已实证 GcExcel 全部保留。
|
|
312
|
+
- **基底编辑最小形态**:
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
var workbook = createReportWorkbook("templates/<cdId>.xlsx")
|
|
316
|
+
.editSheet("Sheet1", function (sheet) {
|
|
317
|
+
sheet.setValue("A1", "新标题", { bold: true }); // 覆盖静态格
|
|
318
|
+
sheet.setDynamicFormula("D3", "AVERAGE(B3)", { context: "A3" }); // 新增模板格
|
|
319
|
+
sheet.bindMetric("E3", reportField("v", "金额"),
|
|
320
|
+
{ aggregate: "SUM", context: "A3", overwrite: true }); // 替换已有模板格需显式 overwrite
|
|
321
|
+
});
|
|
322
|
+
// 需要新增 Sheet 时同一 workbook 上继续 .addSheet("说明", fn)
|
|
323
|
+
report.setWorkbook(workbook); // 与 .setTemplate() 二选一
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
- 改完 `guanvis diff ./work` 看影响面(基底编辑的每条指令会以 `card.template["Sheet"].op[i]` 逐条列出)→ `pack` → 发布覆盖线上 Page 前必须向用户确认后 `publish --allow-overwrite`。
|
|
327
|
+
- **templateVersion 自动管理**:publish 成功后 CLI 自动把工程 JS 的 `.setTemplateVersion(N)` 改写为 N+1(服务端按 version 缓存模板附件,同版本重发模板变更会静默不生效)。若提示未找到该调用,下轮改模板前必须手动补 `.setTemplateVersion(N+1)`。
|
|
328
|
+
|
|
329
|
+
## 6. 验证闭环(必须走完)
|
|
330
|
+
|
|
331
|
+
`pack` 通过、`preview` 有 xlsx **都不等于语义正确**——只有 GcExcel 才能验证模板展开。发布后必须:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
guancli card preview <proCardId> -o result.xlsx # 1. 导出真实渲染结果
|
|
335
|
+
# 2. 检查 result.xlsx:无残留 {{...}};行数/分组结构符合预期;抽 1-2 个小计值人工核对
|
|
336
|
+
guancli card preview <proCardId> --filter "字段 EQ 值" -o filtered.xlsx # 3.(有筛选联动时)验证 childFilters
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
需要视觉判断再用 `guanvis screenshot <pageId>`(消耗图像 token,不作默认步骤)。
|
|
@@ -165,7 +165,7 @@ registerMetricChart(card.build());
|
|
|
165
165
|
| `.addDynamicMetric(name, fields, options?)` | 添加动态指标组,候选指标写入 metric |
|
|
166
166
|
| `.addDynamicMetricAdditional(name, fields, options?)` | 添加动态指标组,候选指标写入副轴 |
|
|
167
167
|
| `.addFilter(field, type, values)` | 添加筛选 |
|
|
168
|
-
| `.addSort(field)` |
|
|
168
|
+
| `.addSort(field)` | 添加排序;字段排序方向用 `sortType: SortOrder.ASC/DESC` |
|
|
169
169
|
| `.addColorBy(metric(...))` | 添加颜色指标 |
|
|
170
170
|
| `.addSize(metric(...))` | 添加大小指标 |
|
|
171
171
|
| `.addTooltip(metric(...))` | 添加 tooltip 指标 |
|
|
@@ -227,7 +227,7 @@ var card = createMetricChart(ChartType.PIVOT_TABLE, "复杂指标透视表")
|
|
|
227
227
|
subtotalSetting: { isDisplayed: true, isAggrDsBased: true, aggrType: "SUM" },
|
|
228
228
|
fieldFormat: { numberFormat: NumberFormat.currency("¥", 0) }
|
|
229
229
|
}))
|
|
230
|
-
.addSort(metric("销售额", {
|
|
230
|
+
.addSort(metric("销售额", { sortType: SortOrder.DESC }))
|
|
231
231
|
.setTableSetting({ fixedHeaderInfo: { X: false }, isDefaultExpandedGroupedTable: true, defaultExpandCols: 0 })
|
|
232
232
|
.setConfig({ rowThreshold: [] });
|
|
233
233
|
|
|
@@ -24,6 +24,8 @@ checkout 工程表示“基于线上快照修改指定 Page”,不会新建 Pa
|
|
|
24
24
|
## 硬约束
|
|
25
25
|
|
|
26
26
|
- `schema.js` 由 `init` 命令生成;`checkout` 会尽量生成 schema,但数据集无权限、已删除或跨环境残留时只提示 warning 并继续生成可编辑工程。不允许 AI 或人工修改 `schema.js`,缺失时后续用 `guanvis init <dsId> -d <dir> --force` 补齐。
|
|
27
|
+
- `dynamic-parameters.js` 由 `guanvis parameter` 或 `checkout` 生成,不手改;`publish` 只校验项目实际引用的参数,不创建或更新线上参数。
|
|
28
|
+
- `charts/` 下的自定义图表内容文件(`<cdId>.js/.html/.css` 与 `<cdId>_assets/` 内嵌资源)是**可编辑事实源**:checkout 从线上卡片反编译生成,直接编辑即可修改图表,pack/publish 通过 `loadContent` 自动回填。重跑 checkout 会用线上版本重建这些文件(`--refresh-base` 不触碰 charts/)。
|
|
27
29
|
- card 脚本必须调用 `registerCard(card.build())`(目录模式)或返回 `card.build()`(单文件模式)。
|
|
28
30
|
- metric chart 脚本必须调用 `registerMetricChart(card.build())`。
|
|
29
31
|
- text card 脚本必须调用 `registerTextCard(textCard.build())`。
|
|
@@ -51,4 +53,4 @@ checkout 工程表示“基于线上快照修改指定 Page”,不会新建 Pa
|
|
|
51
53
|
- **布局组件 ID 例外**:Tab、Panel、AreaTitle、CardGroup、SelGroup 使用 `guanvis gen-layout-id <prefix>` 生成,形如 `tab_AbCdEf`、`panel_AbCdEf`、`areaTitle_AbCdEf`、`cardGroup_AbCdEf`、`selGroup_AbCdEf`,不适用 24 位 `genid` 规则。
|
|
52
54
|
- **线上更新默认策略**:新建工程发布新 Page;checkout 工程只修改 checkout 指定的 Page,不负责复制新版本。
|
|
53
55
|
- **覆盖前检查**:发布前可先运行 `guanvis publish <dir> --dry-run` 或 `guanvis upload <zip> --dry-run`,只构建/解析资源并列出将被覆盖的线上 Page,不提交 transfer 任务。
|
|
54
|
-
- **显式覆盖场景**:checkout 工程发布时保持 `.setId()` 不变,并在用户确认覆盖后加 `--allow-overwrite` 允许同 ID Page 覆盖;多次同 ID 发布会覆盖资源(因为 transfer API 的 `needIdMapping=false`),其中 Card/Selector ID
|
|
56
|
+
- **显式覆盖场景**:checkout 工程发布时保持 `.setId()` 不变,并在用户确认覆盖后加 `--allow-overwrite` 允许同 ID Page 覆盖;多次同 ID 发布会覆盖资源(因为 transfer API 的 `needIdMapping=false`),其中 Card/Selector ID 不做在线覆盖检查。覆盖前备份包含 Page 及其组成资源,但不会沿血缘额外导出数据集、数据账户等上游资源;备份只创建资源迁移导出记录并打印 packageId,不自动下载资源包。需要回滚时,用户应到 BI 资源迁移导出记录中下载该资源包,再手动导入覆盖回去。
|
|
@@ -14,7 +14,7 @@ AI 选择主题时不要把租户主题列表里的默认“浅色”/“深色
|
|
|
14
14
|
- `keywords` 在 `.index.json` 上没有任何命中
|
|
15
15
|
- `.applied.json` 引用的 `themeId` 本地快照已被删除(applied 步骤**只读本地、不再发起 sync**)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
执行命令时 stderr 会打印决策结果。命中线上主题时形如 `Theme: <name> [<id>] (source=preference|applied)`;落到自带兜底时形如 `Theme: 简约 (source=fallback, built-in simple.json)`,特意不打印 themeId 是因为那只是 skill 与 BI 默认值对齐的实现细节,不是租户主题列表里能查到的 ID。fallback 横幅只在 `pack`/`publish` 打印;`preview`/`diff` 是高频只读命令,落到兜底时不再重复打印(命中真实主题时仍会打印)。
|
|
18
18
|
|
|
19
19
|
普通数据集图表和指标平台 MetricChart 都会应用主题视觉配置。MetricChart 只注入 `settings` 与 `meta.chartMain.props`:透视表补表格/合计样式,柱线饼等补坐标轴、图例、数据标签和主题色;不会修改 `zoneData`、`dsInfo`、`defaultView` 等查询相关字段。
|
|
20
20
|
|
|
@@ -18,3 +18,5 @@
|
|
|
18
18
|
| publish 后 `.applied.json` 没更新 | 本次实际用的就是 skill 自带 `simple.json`(按设计 fallback 不写 applied) | 检查 stderr 的 `theme: ... falling back ...` 警告;确保对应 `themes/<id>.json` 存在或先 `theme sync` |
|
|
19
19
|
| `invalid themeId "..." (...)` | themeId 不是合法的单段文件名(见 `publish-and-constraints.md` 中的约束) | 使用 `theme list` 里出现的 id;避免 `/`、`\`、`..`;或用 `theme preference --keywords` |
|
|
20
20
|
| `theme: keywords "..." matched no theme` | 关键词与候选 themeName 没有公共子串 | `theme list` 核对候选名字;调整 keywords,或换成 `--theme-id` |
|
|
21
|
+
| `Inherited from checkout base (线上资源原有问题,不阻断发布)` + ⚠ 列表 | checkout 的线上卡片本身带有不完整数据(缺 dsInfo、字段属性残缺等),未被本次 DSL 修改触碰 | 无需处理即可发布;如果要顺手修复线上数据质量,再针对 ⚠ 项做对应 DSL 修改 |
|
|
22
|
+
| `payload validation failed with N error(s)` 且错误只出现在 ✗ 列表 | 本工程 DSL 操作引入的真实错误(继承自 base 的问题只会进 ⚠ 列表) | 按 ✗ 逐项修复 card_*.js / page.js |
|