@guandata/guanvis 0.1.44 → 0.1.47

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## @guandata/guanvis 0.1.47 - 2026-09-11
4
+
5
+ - 交叉表新增跨视图 `filterBy` 逐格校验:横向父格缺少 `filterBy`、把 `filterBy` 写到横向展开的表头格、以及已用区域未从 A1 起步等形态会在 pack/publish 阶段明确报错并给出修正项,文档同时说明 `filterBy` 与 JOIN 虚拟视图两种交叉表形态的取舍。
6
+ - `setSheetDefaults({ showGridLines })` 可关闭 Pro 报表网格线,并可通过 `decompile` 原样回读。
7
+ - `setTableSetting` 的表格字号下限放宽到 9,便于还原密集的迁移报表区块。
8
+ - `publish --allow-overwrite` 覆盖发布后会重置被替换页面的草稿,编辑器不再打开上一版本的卡片和脚本;原生卡片 bundle 自带的运行时标记不再被误判为未替换占位符。
9
+
10
+ ## @guandata/guanvis 0.1.46 - 2026-09-09
11
+
12
+ - 正式发布按系统和架构拆分的原生程序包,安装命令不变,下载量与磁盘占用显著降低。
13
+ - 新建页面前要求指定目标目录并确认落位计划,完成后返回资源链接和存储路径。
14
+ - 页面支持卡片池配置,批量预览会汇总脚本错误并提供合法配置键和候选名称提示。
15
+ - 修复 `DATA_GRID` 错误应用视觉主题的问题。
16
+
17
+ ## @guandata/guanvis 0.1.45 - 2026-09-09
18
+
19
+ - 安装时自动选择当前系统和架构的原生程序,减少下载量与磁盘占用,原有安装命令不变;离线安装也支持自动选包。
20
+ - 新建页面前要求指定目标目录并确认落位计划,成功后输出资源链接与存储路径。
21
+ - 新增 `page.addSpareCard()`,支持将卡片放入页面卡片池,`checkout` 后回写保留卡片池配置。
22
+ - `preview` 支持批量汇总脚本错误,提供允许的配置键及候选名称提示,精简错误展示。
23
+ - 修复 `DATA_GRID` 应用视觉主题配置的问题,此类卡片不再应用视觉主题。
24
+
3
25
  ## @guandata/guanvis 0.1.44 - 2026-09-03
4
26
 
5
27
  - 页面筛选器新增条件匹配、树状、层级树状和组合条件类型,并完善全局参数筛选器的创建与原地更新。
package/README.md CHANGED
@@ -10,6 +10,8 @@ npm install -g --foreground-scripts @guandata/guanvis
10
10
 
11
11
  > 全局安装/升级时会通过 postinstall 自动执行一次 `guanvis install-skill` 刷新 AI skill。`--foreground-scripts` 用于显示明确的成功、失败或跳过结果;失败时按提示手动运行 `guanvis install-skill`。CI 等无需 skill 的环境可设 `GUAN_SKIP_INSTALL_SKILL=1` 跳过。
12
12
 
13
+ 安装包会通过 npm `optionalDependencies` 自动选择当前系统的原生二进制。不要使用 `--omit=optional` 或 `optional=false`;企业 npm 镜像也需要同步对应的 `@guandata/guanvis-<平台>-<架构>` 包。若平台包缺失,CLI 会显示对应包名和重新安装方法。
14
+
13
15
  安装后即可在终端使用:
14
16
 
15
17
  ```bash
@@ -55,6 +57,24 @@ guanvis publish ./my_dashboard/ --allow-overwrite
55
57
 
56
58
  ## 版本更新
57
59
 
60
+ ### @guandata/guanvis 0.1.47
61
+
62
+ - 交叉表新增跨视图 `filterBy` 逐格校验,违规形态在 pack/publish 阶段给出明确修正提示,文档同时说明与 JOIN 虚拟视图的取舍。
63
+ - `setSheetDefaults({ showGridLines })` 可关闭 Pro 报表网格线,并可原样回读;表格字号下限放宽到 9。
64
+ - `publish --allow-overwrite` 覆盖发布会重置被替换页面的草稿,避免编辑器打开上一版本内容。
65
+
66
+ ### @guandata/guanvis 0.1.46
67
+
68
+ - 正式启用按系统和架构拆分的原生程序包,安装命令不变,下载量与磁盘占用显著降低。
69
+ - 新建页面要求指定目标目录并确认落位计划,页面支持卡片池配置。
70
+ - 改进批量预览错误提示,并修复 `DATA_GRID` 主题处理。
71
+
72
+ ### @guandata/guanvis 0.1.45
73
+
74
+ - 安装时自动选择当前系统和架构的原生程序,减少下载量与磁盘占用,原有安装命令不变。
75
+ - 支持 macOS arm64/x64、Linux arm64/x64 和 Windows x64;离线安装包也会自动选择匹配的平台。
76
+ - 页面支持卡片池配置,改进批量构建错误提示和页面发布目标目录检查。
77
+
58
78
  ### @guandata/guanvis 0.1.44
59
79
 
60
80
  - 新增条件匹配、树状、层级树状和组合条件筛选器,并完善全局参数筛选器支持。
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ const PACKAGE_PREFIX = "@guandata/guanvis";
4
+
5
+ const PLATFORM_TARGETS = [
6
+ { platform: "darwin", arch: "arm64", suffix: "darwin-arm64", binary: "guanvis" },
7
+ { platform: "darwin", arch: "x64", suffix: "darwin-x64", binary: "guanvis" },
8
+ { platform: "linux", arch: "x64", suffix: "linux-x64", binary: "guanvis" },
9
+ { platform: "linux", arch: "arm64", suffix: "linux-arm64", binary: "guanvis" },
10
+ { platform: "win32", arch: "x64", suffix: "win32-x64", binary: "guanvis.exe" },
11
+ ].map((target) => ({
12
+ ...target,
13
+ key: `${target.platform}-${target.arch}`,
14
+ packageName: `${PACKAGE_PREFIX}-${target.suffix}`,
15
+ }));
16
+
17
+ function targetForRuntime(platform, arch) {
18
+ return PLATFORM_TARGETS.find(
19
+ (target) => target.platform === platform && target.arch === arch
20
+ );
21
+ }
22
+
23
+ function optionalDependencies(version) {
24
+ return Object.fromEntries(
25
+ PLATFORM_TARGETS.map((target) => [target.packageName, version])
26
+ );
27
+ }
28
+
29
+ module.exports = {
30
+ PACKAGE_PREFIX,
31
+ PLATFORM_TARGETS,
32
+ optionalDependencies,
33
+ targetForRuntime,
34
+ };
package/bin/run.js CHANGED
@@ -7,34 +7,56 @@ const path = require("path");
7
7
  const fs = require("fs");
8
8
  const os = require("os");
9
9
  const { createInstallChildEnv, reexecInstallCommand } = require("./install-env");
10
-
11
- const PLATFORM_MAP = {
12
- "darwin-arm64": "guanvis-darwin-arm64",
13
- "darwin-x64": "guanvis-darwin-x64",
14
- "linux-x64": "guanvis-linux-x64",
15
- "linux-arm64": "guanvis-linux-arm64",
16
- "win32-x64": "guanvis-win32-x64.exe",
17
- };
18
-
19
- function getBinaryPath() {
20
- const key = `${process.platform}-${process.arch}`;
21
- const binaryName = PLATFORM_MAP[key];
22
- if (!binaryName) {
23
- console.error(
24
- `Unsupported platform: ${key}\nSupported: ${Object.keys(PLATFORM_MAP).join(", ")}`
10
+ const { PLATFORM_TARGETS, targetForRuntime } = require("./platforms");
11
+
12
+ function getBinaryPath(options = {}) {
13
+ const platform = options.platform || process.platform;
14
+ const arch = options.arch || process.arch;
15
+ const resolvePackage = options.resolvePackage || require.resolve;
16
+ const exists = options.exists || fs.existsSync;
17
+ const readJson = options.readJson || ((filename) => JSON.parse(fs.readFileSync(filename, "utf8")));
18
+ const key = `${platform}-${arch}`;
19
+ const target = targetForRuntime(platform, arch);
20
+ if (!target) {
21
+ throw new Error(
22
+ `Unsupported platform: ${key}\nSupported: ${PLATFORM_TARGETS.map((item) => item.key).join(", ")}`
25
23
  );
26
- process.exit(1);
27
24
  }
28
25
 
29
- const binaryPath = path.join(__dirname, "..", "binaries", binaryName);
30
- if (!fs.existsSync(binaryPath)) {
31
- console.error(
32
- `Binary not found: ${binaryPath}\nRun 'npm run build' to compile binaries.`
26
+ try {
27
+ const packageJson = resolvePackage(`${target.packageName}/package.json`);
28
+ const platformVersion = readJson(packageJson).version;
29
+ const mainVersion = readJson(path.join(__dirname, "..", "package.json")).version;
30
+ if (platformVersion !== mainVersion) {
31
+ throw new Error(
32
+ `Platform package ${target.packageName} version ${platformVersion} does not match ` +
33
+ `@guandata/guanvis ${mainVersion}. Reinstall @guandata/guanvis.`
34
+ );
35
+ }
36
+ const binaryPath = path.join(path.dirname(packageJson), "bin", target.binary);
37
+ if (!exists(binaryPath)) {
38
+ throw new Error(
39
+ `Platform package ${target.packageName} is installed but its binary is missing: ${binaryPath}. ` +
40
+ "Reinstall @guandata/guanvis."
41
+ );
42
+ }
43
+ return binaryPath;
44
+ } catch (err) {
45
+ if (err.message && err.message.startsWith("Platform package ")) throw err;
46
+
47
+ // Local development fallback. The generated directory is excluded from
48
+ // the published main package, so installed users always use the optional
49
+ // platform dependency above.
50
+ const localBinary = path.join(__dirname, "..", "platforms", target.suffix, "bin", target.binary);
51
+ if (exists(localBinary)) return localBinary;
52
+
53
+ throw new Error(
54
+ `Missing optional platform package ${target.packageName} for ${key}.\n` +
55
+ "Reinstall without disabling optional dependencies:\n" +
56
+ " npm install -g @guandata/guanvis\n" +
57
+ "If you used --omit=optional or optional=false, remove that setting first."
33
58
  );
34
- process.exit(1);
35
59
  }
36
-
37
- return binaryPath;
38
60
  }
39
61
 
40
62
  function copyDirectory(src, dest) {
@@ -81,7 +103,7 @@ function installBuddySkills(pkgRoot, skill) {
81
103
  }
82
104
  }
83
105
 
84
- if (process.argv[2] === "version" && process.argv.length === 3) {
106
+ if (require.main === module && process.argv[2] === "version" && process.argv.length === 3) {
85
107
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
86
108
  console.log(pkg.version);
87
109
  process.exit(0);
@@ -111,7 +133,7 @@ function resolveNpxInvocation() {
111
133
  return { command: "npx", argsPrefix: [] };
112
134
  }
113
135
 
114
- if (process.argv[2] === "install-skill") {
136
+ if (require.main === module && process.argv[2] === "install-skill") {
115
137
  reexecInstallCommand(__filename, process.argv.slice(2));
116
138
  const pkgRoot = path.join(__dirname, "..");
117
139
  const extraArgs = process.argv.slice(3);
@@ -143,28 +165,38 @@ if (process.argv[2] === "install-skill") {
143
165
  process.exit(status);
144
166
  }
145
167
 
146
- const binary = getBinaryPath();
168
+ if (require.main === module) {
169
+ let binary;
170
+ try {
171
+ binary = getBinaryPath();
172
+ } catch (err) {
173
+ console.error(err.message);
174
+ process.exit(1);
175
+ }
147
176
 
148
- try {
149
- if (process.platform !== "win32") {
150
- fs.chmodSync(binary, 0o755);
177
+ try {
178
+ if (process.platform !== "win32") {
179
+ fs.chmodSync(binary, 0o755);
180
+ }
181
+ } catch (_) {
182
+ // chmod may fail in read-only environments
151
183
  }
152
- } catch (_) {
153
- // chmod may fail in read-only environments
154
- }
155
184
 
156
- if (process.platform === "win32") {
157
- try { execSync("chcp 65001", { stdio: "ignore" }); } catch (_) {}
158
- }
185
+ if (process.platform === "win32") {
186
+ try { execSync("chcp 65001", { stdio: "ignore" }); } catch (_) {}
187
+ }
159
188
 
160
- try {
161
- execFileSync(binary, process.argv.slice(2), {
162
- stdio: "inherit",
163
- env: { ...process.env, GUANVIS_PROG_NAME: "guanvis" },
164
- });
165
- } catch (err) {
166
- if (err.status != null) {
167
- process.exit(err.status);
189
+ try {
190
+ execFileSync(binary, process.argv.slice(2), {
191
+ stdio: "inherit",
192
+ env: { ...process.env, GUANVIS_PROG_NAME: "guanvis" },
193
+ });
194
+ } catch (err) {
195
+ if (err.status != null) {
196
+ process.exit(err.status);
197
+ }
198
+ throw err;
168
199
  }
169
- throw err;
170
200
  }
201
+
202
+ module.exports = { getBinaryPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guandata/guanvis",
3
- "version": "0.1.44",
3
+ "version": "0.1.47",
4
4
  "description": "观远 BI Card/Page 生成工具 - 通过 JS DSL 创建图表和仪表板",
5
5
  "bin": {
6
6
  "guanvis": "bin/run.js"
@@ -8,14 +8,17 @@
8
8
  "scripts": {
9
9
  "postinstall": "node bin/postinstall.js",
10
10
  "build": "node scripts/build.js && node scripts/sync-skill.js",
11
- "test:build-script": "node scripts/build.test.js",
11
+ "pack:all": "node scripts/pack-all.js",
12
+ "test:build-script": "node scripts/build.test.js && node scripts/run.test.js",
13
+ "test:package-layout": "node scripts/package-layout.test.js && node scripts/install-smoke.test.js",
14
+ "test:auto-select": "node scripts/auto-select.test.js",
15
+ "verify:platforms": "node scripts/verify-platforms.js",
12
16
  "changelog": "node ../../scripts/generate-release-changelog.js .",
13
17
  "check-changelog": "node ../../scripts/check-release-changelog.js .",
14
- "prepublishOnly": "npm run test:build-script && npm run check-changelog && npm run build && node scripts/preflight.js"
18
+ "prepublishOnly": "npm run test:build-script && npm run check-changelog && npm run build && node scripts/preflight.js && npm run test:package-layout && npm run test:auto-select"
15
19
  },
16
20
  "files": [
17
21
  "bin/",
18
- "binaries/",
19
22
  "skills/",
20
23
  "CHANGELOG.md",
21
24
  "LICENSE",
@@ -36,11 +39,17 @@
36
39
  "linux",
37
40
  "win32"
38
41
  ],
42
+ "optionalDependencies": {
43
+ "@guandata/guanvis-darwin-arm64": "0.1.47",
44
+ "@guandata/guanvis-darwin-x64": "0.1.47",
45
+ "@guandata/guanvis-linux-x64": "0.1.47",
46
+ "@guandata/guanvis-linux-arm64": "0.1.47",
47
+ "@guandata/guanvis-win32-x64": "0.1.47"
48
+ },
39
49
  "engines": {
40
50
  "node": ">=14"
41
51
  },
42
52
  "publishConfig": {
43
- "registry": "https://registry.npmjs.org/",
44
- "access": "public"
53
+ "registry": "https://registry.npmjs.org/"
45
54
  }
46
55
  }
@@ -18,7 +18,7 @@ AI 生成 `card_*.js` 定义 Card、`page.js` 组装仪表板(`schema.js`/`met
18
18
  - `theme-colors.js` 是项目级图表主题色事实快照,不手改。准备在脚本中使用 `setThemeColor()` 前,先检查项目根目录;不存在时先运行 `guanvis theme-color sync -d <project>`。调用 `setThemeColor()` 切换主题时必须从快照选择真实 `tcId`,不得猜测或编造。已有文件直接复用,主题色列表变化或切换 BI 环境时用 `theme-color sync` 刷新;快照会记录实际选中的 profile、服务地址和 domain,通过环境变量或默认 profile 切换环境后,构建都会拒绝复用旧快照。`preview`/`diff`/`pack`/`publish` 对缺失快照的自动生成只作兜底。
19
19
  - 全局参数先用 `guanvis parameter` 创建或复用,再通过 `dynamic-parameters.js` 按 `dpId` 引用;`pack`/`publish` 不会隐式写入参数。创建前必须按名称查询;发现同名有效参数时立即停止,让用户明确选择"更新已有参数"或"换名创建",不得自动复用或修改。
20
20
  - `card_*.js`、`selector_*.js`、`page.js` 是可编辑源文件;`*_package.zip`、`.preview.json`(preview 落盘的全量 payload)、publish 后线上资源都是派生产物,不要手工编辑。
21
- - **Page 归属红线**:包含 Card/Selector 的资源包必须同时包含 Page,且每个 Card/Selector 都必须被至少一个 Page 引用;否则后端会产生 `pg_id` 为空、无法访问且可能阻塞后续发布的孤儿卡片。校验失败时在 `page.js` 中放置对应资源后同包发布。
21
+ - **Page 归属红线**:包含 Card/Selector 的资源包必须同时包含 Page,且每个 Card/Selector 都必须被至少一个 Page 引用;否则后端会产生 `pg_id` 为空、无法访问且可能阻塞后续发布的孤儿卡片。校验失败时在 `page.js` 中放置对应资源后同包发布。页面"卡片池"里的卡(checkout 回写为 `page.addSpareCard(cardId)`)归属本页但默认不展示,编辑时原样保留,不要删掉或改成放置。
22
22
  - 已有线上仪表板用 `guanvis checkout <pgId> -d <dir>` 拉成可编辑工程(attachCard 基线、自定义图表 `charts/` 反编译、Pro 模板 `templates/`、Page 布局脚本)。checkout 工程只用于修改指定 Page,不用于复制新 Page;只支持普通仪表板(pgType=PAGE),DSL 不能安全表达的结构会直接失败而不是清结构。**checkout 工程动手前先读 `references/checkout-editing.md`**。
23
23
  - **Checkout 账号红线**:checkout 读到的是"当前账号视角",publish 整体回写。必须用对涉及数据集有**完整列权限、无脱敏限制**的账号(推荐 owner 或管理员)执行 checkout/publish,否则被裁剪的字段会在回写后从线上卡片永久丢失;多语言租户操作账号语言须与卡片原始语言一致。
24
24
  - **Checkout JSON 红线**:`.guanvis/raw/**`、`.guanvis/base/**`、`.guanvis/manifest.json` 是只读快照。Agent 不得修改这些 JSON,也不得复制 checkout JSON 改副本创建新资源;现有卡片修改必须通过 `attachCard(...)` 的 DSL 操作表达(优先 `update*/patch*/add*/remove*/move*`,整 zone 重建的 `set*/clear*` 会触发 warning),新增卡片必须用 `createCard()` / `createSelector()` 工厂函数;操作清单见 `references/checkout-editing.md` §3。
@@ -71,7 +71,7 @@ AI 生成 `card_*.js` 定义 Card、`page.js` 组装仪表板(`schema.js`/`met
71
71
  21. **布局组件**:AreaTitle/CardGroup/SelGroup/Tab 只支持放画布根布局、不支持嵌套组合;SelGroup 内只能放 selector;详见 `references/builder-reference.md`
72
72
  22. **资源包/checkout JSON 禁止手改**:只改 DSL 源文件,不改 ZIP 内部文件与 `.guanvis/**` JSON;批量重绑、迁移页面等需求先讨论方案,必要时扩展 DSL,不手工改生成物。
73
73
  23. **动态字段**:用户明确需要字段切换时用 `.addDynamicRow()` / `.addDynamicMetric()` 等(普通卡动态维度/数值,指标卡动态维度/指标);细节见 `references/builder-reference.md`。
74
- 24. **自定义图表(HTML/CSS/JS 自绘)**:内置图表满足不了的可视化才走 `createCustomChart()`。子类型选型:纯 ECharts 配置能画 → `CustomChartSubType.ECHARTS_LITE`(脚本给 `option` 赋值,禁用 `GDPlugin`);需要自定义 DOM/CSS 布局或第三方库(节点图、进度轴、Vega 等)→ `CustomChartSubType.SDK`(`charts/<name>.{html,css,js}` 三件套 + `loadContent("charts/<name>")`,js 里实现 `renderChart(data, clickFunc, config)` 并 `new GDPlugin().init(renderChart)`)。数据用 `.addDataView(createCard(ChartType.DATA_GRID, ...))` 传入。最小可运行工程直接抄 `evals/custom_chart_sdk/`(SDK)或 `evals/custom_chart_echarts/`(ECHARTS_LITE)。**布局写法**:对齐/等比类要求用结构性保证而非数值手调——固定尺寸容器 + flex 居中让几何天然成立,关键坐标(如贯穿线端点)在渲染 JS 里 `getBoundingClientRect()` 实测反写并挂 resize 重算;手调 top/margin 意味着每发布一次才能验一次。**视觉验收**:preview 只校验结构不渲染 HTML/CSS,本地 file:// 打开也不可靠——正确闭环是发布(可先发到测试目录)后 `guanvis screenshot <pgId>` 读图确认(这是总则"screenshot 不作默认闭环"的例外:自定义图表视觉由手写 HTML/CSS 决定,结构回读覆盖不到);读图判断即可,不要解析 PNG 像素做几何量化,也不要为验收安装图像处理依赖;模型不支持图像理解时,改为把 publish 成功后输出的 `Page URL` 交给用户人工确认。不要浪费时间做本地渲染验证。完整 API 见 `references/builder-reference.md` 的 CustomChartBuilder 章节。
74
+ 24. **自定义图表(HTML/CSS/JS 自绘)**:内置图表满足不了的可视化才走 `createCustomChart()`。子类型选型:纯 ECharts 配置能画 → `CustomChartSubType.ECHARTS_LITE`(脚本给 `option` 赋值,禁用 `GDPlugin`);需要自定义 DOM/CSS 布局或第三方库(节点图、进度轴、Vega 等)→ `CustomChartSubType.SDK`(`charts/<name>.{html,css,js}` 三件套 + `loadContent("charts/<name>")`,js 里实现 `renderChart(data, clickFunc, config)` 并 `new GDPlugin().init(renderChart)`)。数据用 `.addDataView(createCard(ChartType.DATA_GRID, ...))` 传入;`DATA_GRID` 只承载数据,不应用页面主题,pack/publish 会删除其 `settings` 中的 `bgSettings`、`backgroundColor`、`showTitle`、`style`、`titleSetting`,checkout 工程回写时也会清理既有值。最小可运行工程直接抄 `evals/custom_chart_sdk/`(SDK)或 `evals/custom_chart_echarts/`(ECHARTS_LITE)。**布局写法**:对齐/等比类要求用结构性保证而非数值手调——固定尺寸容器 + flex 居中让几何天然成立,关键坐标(如贯穿线端点)在渲染 JS 里 `getBoundingClientRect()` 实测反写并挂 resize 重算;手调 top/margin 意味着每发布一次才能验一次。**视觉验收**:preview 只校验结构不渲染 HTML/CSS,本地 file:// 打开也不可靠——正确闭环是发布(可先发到测试目录)后 `guanvis screenshot <pgId>` 读图确认(这是总则"screenshot 不作默认闭环"的例外:自定义图表视觉由手写 HTML/CSS 决定,结构回读覆盖不到);读图判断即可,不要解析 PNG 像素做几何量化,也不要为验收安装图像处理依赖;模型不支持图像理解时,改为把 publish 成功后输出的 `Page URL` 交给用户人工确认。不要浪费时间做本地渲染验证。完整 API 见 `references/builder-reference.md` 的 CustomChartBuilder 章节。
75
75
  25. **复杂报表 Pro**:非默认功能,需单独授权——只有用户明确点名"复杂报表 / 复杂报表 Pro"或被修改卡片已是 Pro 才选 Pro;用户只说"表格/报表/透视表"一律用原生卡片(DATA_GRID/PIVOT_TABLE 等),版式做不到时告知"Pro 可实现但需授权"由用户决定。动手前必读 `references/complex-report-pro-patterns.md`(§0 选型、§2 配方、§3 数据视图规则、§4 报错修复、§5 编辑闭环、§6 验证闭环),全部结构红线与修复表以该文件为准;工作流程见下文「复杂报表 Pro 工作方式」
76
76
 
77
77
  ## 何时使用
@@ -197,10 +197,13 @@ guanvis preview ./my_dashboard/
197
197
  guanvis diff ./existing_dashboard/ # checkout/attach 工程的路径级变更摘要
198
198
 
199
199
  # 发布(publish 自带构建打包上传;pack 只用于生成离线 ZIP 走 upload)
200
+ guanvis publish ./my_dashboard/ --dry-run # 先看"页面落位计划"(目录路径 + dirId),交给用户确认
200
201
  guanvis publish ./my_dashboard/ [--page-parent-dir <dir_id>]
201
- # --page-parent-dir 要求目录已存在(也可在 page.js 里 .setParentDir());
202
+ # 每个 Page 都必须有存储目录:page.js 里 .setParentDir("<dirId>") 或 --page-parent-dir;
203
+ # 两者皆无且线上没有同 pgId 页面可沿用时 publish/pack 直接拒绝(不会落根目录,也不会自动建目录)。
202
204
  # 目标目录还不存在时先 guanvis dir create --name <名称> --parent <父目录 dirId> 建出来,
203
205
  # 用它输出的 dirId 再 publish;查已有目录 ID 用 guancli page tree --type dir
206
+ # 拉不到页面目录树时 publish 会阻断(路径来自目录树,读不到就无法确认落位),先检查账号的页面目录权限
204
207
  guanvis pack [-o output.zip] ./my_dashboard/
205
208
  guanvis upload output.zip # 只允许上传 guanvis pack 原样生成的 ZIP
206
209
 
@@ -301,3 +304,8 @@ guanvis icon list --group default_line -f json
301
304
  - 是否有 Page,布局方式如何。
302
305
  - 是否有验证错误及修复建议。
303
306
  - `pack`/`publish`/`upload` 哪些步骤已成功。
307
+ - 每个已发布 Page 的访问链接和存储路径,直接取 `publish` 成功后"页面落位(发布后回读)"段里的 `链接:`(`<BI 地址>/page/<pgId>`)和 `路径:`(页面目录名称链)。回读失败或提示"实际目录与计划目录不一致"时如实转述,不要编造路径。
308
+
309
+ ## 发布前必须确认页面存储目录
310
+
311
+ 创建/发布仪表板前先明确页面放在哪个文件夹并由用户确认(招行等客户的目录管理规范):先用 `guancli page tree --type dir` 找到目录的路径和 dirId,把"页面 <名称> 将保存到 <路径>"交给用户;目录不存在时用 `guanvis dir create` 显式创建,**不要**让发布流程自动建目录。确认后在 `page.js` 写 `.setParentDir("<dirId>")`(或发布时 `--page-parent-dir`),`publish --dry-run` 会打印"页面落位计划"(目录路径 + dirId + 新建/覆盖),确认无误再真实发布。修改已发布页面(checkout 工程或线上已有同 pgId)时不指定目录会沿用当前目录,落位计划里会标注"沿用"。任何 Page 都不允许没有目录:`publish`/`pack` 会拒绝,不会把页面放到根目录。
@@ -402,13 +402,14 @@ overview.linkTo("bbbbbbbbbbbbbbbbbbbbbbbb", {
402
402
  |------|------|
403
403
  | `createPage(name)` | 创建 Page |
404
404
  | `.setId(pgId)` | 设置 Page ID |
405
- | `.setParentDir(dirId)` | 设置页面所在目录 ID(不设置则在根目录),目录 ID 可通过 `guancli page tree` 获取 |
405
+ | `.setParentDir(dirId)` | 设置页面所在目录 ID(**必填**:新页面没有目录会被 `publish`/`pack` 拒绝,不会落根目录;已发布页面不设置时沿用线上当前目录),目录 ID 可通过 `guancli page tree --type dir` 获取,不存在时用 `guanvis dir create` 创建 |
406
406
  | `.addRow(specs, height?)` | **推荐**:灵活行布局,specs = `[{ card: cardRef, w: colSpan }, ...]`;不传 w 时自动等分当前栅格 |
407
407
  | `.addFullWidthCard(cardRef, height?)` | 全宽行,等价于 `addRow([{ card }], height)` |
408
408
  | `.addHalfWidthCards(a, b, height?)` | 左右各半 |
409
409
  | `.addThirdWidthCards(a, b, c, height?)` | 三等分 |
410
410
  | `.addQuarterWidthCards(a, b, c, d, height?)` | 四等分 |
411
411
  | `.placeCard(cardRef, x, y, w, h)` | 精确放置 Card,`w/h` 必须大于 0, `x/y/w/h` 必须显式填写 |
412
+ | `.addSpareCard(cardId)` | 把已注册的顶层 Card 放进页面的**卡片池**:仍归属本页、可查询,但不上画布、默认不展示。checkout 回写出的 `addSpareCard(...)` 必须保留,不要删掉或改成 `placeCard` |
412
413
  | `.addTab(tab, height?)` | 添加一个满宽 tab 容器;不传 height 时按第一个 panel 内容自动推导 |
413
414
  | `.addAreaTitle(areaTitle, height?)` | 添加一个满宽区域小标题|
414
415
  | `.addCardGroup(group, height?)` | 添加一个满宽卡片组;不传 height 时按标题和组内布局自动推导 |
@@ -1225,7 +1226,7 @@ Workbook API:
1225
1226
 
1226
1227
  绑定 `options` 支持 `expansion`、`fillMode`、`context`、`countPerPage`、`filter`、`filterBy`、`numberFormat` 和 `style`;维度额外支持 `group`、`sort`,度量额外支持 `aggregate`。未知 option 会直接校验失败,不再静默忽略。
1227
1228
 
1228
- `context` 可写单父格 `A3`,也可写横纵双父格 `A4*B3`。父格必须是维度绑定:左父格与当前格同一行且纵向扩展,上父格与当前格同一列且横向扩展,最多各一个。相同数据视图的层级和交叉表只用 `context`;同时生成同源 `filterBy` 会形成嵌套 `LP(...)`,现在会在 pack 阶段拒绝。`filterBy` 仅用于跨数据视图映射,字段的 view 必须是当前绑定 view,引用的 cell 必须同时列在 `context` 中。`filter` 与 `filterBy` 不能并用。
1229
+ `context` 可写单父格 `A3`,也可写横纵双父格 `A4*B3`。父格必须是维度绑定:左父格与当前格同一行且纵向扩展,上父格与当前格同一列且横向扩展,最多各一个。相同数据视图的层级和交叉表只用 `context`;同时生成同源 `filterBy` 会形成嵌套 `LP(...)`,现在会在 pack 阶段拒绝。`filterBy` 仅用于跨数据视图映射,字段的 view 必须是当前绑定 view,引用的 cell 必须是 `context` 里的父格或父格的 `context` 祖先(多级表头可同时给年、月两个列键)。`filter` 与 `filterBy` 不能并用。**同一 Sheet 里 `filterBy` 与 `expansion: "horizontal"` 并存时按格校验**(pack 拒绝,规则与实测见 complex-report-pro-patterns.md §1):带 `filterBy` 的格若挂在横向父格下,`filterBy` 必须包含该横向父格;同 Sheet 其它挂在横向父格下、自身不横向扩展的绑定/动态公式必须也带 `filterBy`,否则改用 `setDataSourceRelations` 虚拟视图 + `context`;`filterBy` 不能写在横向扩展格自身上;第 1 行与 A 列都要有格(标题放 A1),否则渲染器还原横向表头时错位。同一列里下方第二段带的顶层维度要显式 `context: "None"`,否则默认父格推断会把它挂到上一段的同列维度。
1229
1230
 
1230
1231
  聚合可用 `SUM/COUNT/AVERAGE/MAX/MIN/PRODUCT/STDDEV/STDDEVP/VAR/VARP`。`AVG` 是输入别名,会编译为 `AVERAGE`;`COUNTA` 不是 Pro 聚合函数,会在本地拒绝。`countPerPage` 只接受正整数或 `*`。分页模式最多设置一个 `countPerPage`,且只能放在没有 `context` 的顶层 `bindDimension` 上;放在嵌套子维度上会导致 Pro 分页器只合并部分祖先单元格,因此 `pack` 会直接拒绝。
1231
1232
 
@@ -1297,7 +1298,7 @@ checkout 得到的 `templates/<cdId>.xlsx` 可用 `guanvis report decompile <xls
1297
1298
  | `sheet.setRow(startCell, values, style?)` | 从 startCell 起横向批量写标量值,数组元素 `null` 跳过该格;style 应用到每个写入格。表头行一行写完 |
1298
1299
  | `sheet.setColumn(startCell, values, style?)` | 纵向批量写(固定科目列一次写完) |
1299
1300
  | `sheet.setColumnWidth(col, width)` | col 支持 `"B"` 或范围 `"B:D"`(整段同宽) |
1300
- | `sheet.setSheetDefaults({ defaultColumnWidth?, defaultRowHeight?, defaultRowHidden? })` | 未显式设置宽高的行列默认值(编译时自动先于其他操作应用,脚本内位置不影响结果);`defaultRowHidden: true` 使未显式声明的行默认隐藏(OOXML zeroHeight |
1301
+ | `sheet.setSheetDefaults({ defaultColumnWidth?, defaultRowHeight?, defaultRowHidden?, showGridLines? })` | 未显式设置宽高的行列默认值(编译时自动先于其他操作应用,脚本内位置不影响结果);`defaultRowHidden: true` 使未显式声明的行默认隐藏(OOXML zeroHeight);`showGridLines: false` 隐藏工作表网格线(OOXML sheetView showGridLines="0",无边框单元格之间不再画浅灰线,报表类版式常用;decompile 原样还原) |
1301
1302
  | `sheet.setHyperlink(cell, url, { tooltip?, display? })` | **静态格**超链接(标题/返回目录等固定格;dev29 实证 GcExcel 渲染保留);`display` 为链接可见文本(格无自身值时用户看到的就是它)。超链不随模板扩展复制——扩展区行级链接用 `setDynamicFormula` + `HYPERLINK("url","文本")`(dev29 实证展开为 shared formula 每行可点) |
1302
1303
 
1303
1304
  样式对象在原有 `bold/italic/fontSize/fontColor/backgroundColor/horizontalAlignment/verticalAlignment/wrapText/numberFormat/borderColor/diagonalDown` 基础上新增(compile/decompile/setCellStyle 合并三端一致,roundtrip 有测试锁定):
@@ -1168,7 +1168,7 @@ Tooltip 展示哪些数据字段由 `.addTooltip(field)` 控制;`setTooltip()`
1168
1168
  | `selectedColor` | string | 选中高亮色,可使用带透明度的 `rgba(...)` |
1169
1169
  | `colWidth` | integer,15–1000 | 表格列宽 |
1170
1170
  | `fontFamily` | 非空 string | 全表字体 |
1171
- | `fontSize` | number,12–72 | 全表字号 |
1171
+ | `fontSize` | number,9–72 | 全表字号(BI 面板只提供 12 起,表格本身按给定值渲染;紧凑的迁移报表块常用 10) |
1172
1172
  | `cellPadding` | `"SMALL"` / `"MIDDLE"` / `"LARGE"` | 单元格内间距 |
1173
1173
 
1174
1174
  滚动表的 `appearance` 仅支持 `colorType`、`banding`、`divider`、`backgroundColor`、`colWidth` 和 `cellPadding`。其中 `colorType` 可选 `"grey"` / `"light-blue"` / `"purple"` / `"lake-blue"` / `"green"` / `"yellow"`,`divider` 使用 `{ row: boolean, column: boolean, color: string }`。
@@ -50,6 +50,17 @@ Pro 模板 = Excel 网格上放"模板格"。每个模板格要么绑定数据
50
50
 
51
51
  **同一数据视图内只写 `context`,绝不写 `filterBy`**(同源 filterBy 会编译成嵌套 `LP(...)`,后端 500;pack 已本地拒绝)。`filterBy` 仅用于跨数据视图父格映射,且 cell 必须同时出现在 `context` 中。
52
52
 
53
+ **`filterBy` 与横向扩展同在一个 Sheet 时按格三条规则**(pack 已本地拒绝,报错前缀 `cross-view filterBy on a sheet with a horizontal expansion`)。原因:Sheet 上只要有一个 `filterBy`,Pro 后端就把整张 Sheet 切成"两遍渲染"——第一遍只做纵向扩展(横向扩展格先占位、带 filterBy 的格改成 lookup 且只保留左父格),第二遍才还原横向扩展并给 lookup 格补上父格;lookup 按列取值的键来自 `filterBy`,不来自 `context`。这些错误后端都不报,只出错数,所以本地必须硬拒绝:
54
+
55
+ 1. 带 `filterBy` 的格若 `context` 里有横向父格,`filterBy` 必须同时包含该横向父格(只给纵向键 → 每列重复同一个值)。
56
+ 2. 同一 Sheet 上,凡 `context`(含默认推断)挂在横向父格下、自身不是 `expansion: "horizontal"`、又没有 `filterBy` 的绑定或动态公式,一律拒绝(第一遍就被消费,不随列扩展,输出一个总量)。要么给它补含横向父格的 `filterBy`,要么整表改 §2.8 JOIN 虚拟视图 + 只写 `context`。
57
+ 3. `filterBy` 不能写在横向扩展格自身上(lookup 改写会丢掉它的 E=H),横向列头单独放一格,过滤放在值格。
58
+ 4. 第 1 行和 A 列都必须有格(值、模板或带样式的格),让工作表已用区域从 A1 开始:渲染器还原横向表头时按已用区域相对坐标定位,模板从 B 列或第 2 行起步时表头整体错位一格、占位符残留、查值全空。把标题放在 A1 即可;A 列纯留白时放一个空格值。
59
+
60
+ 满足规则的形态实测按列正确:三视图交叉表(行轴视图 / 列轴视图 / 度量视图)度量格 `context: "A3*B2"` + `filterBy: [{行键, A3}, {列键, B2}]`;两级列头(国家→是否已付)时 `filterBy` 可以额外带 `context` 祖先格 `B2`(`context` 每个方向只写最近父格,`filterBy` 允许写祖先),合并列头与空值列都正确。与 §2.8 JOIN 形态的取舍:filterBy 形态行/列成员各来自自己的视图、无交点为空,JOIN 形态是 INNER JOIN、关联不到的成员直接消失。
61
+
62
+ **多段带的顶层锚点写 `context: "None"`**:同一列里第二段带的顶层维度(如第 7 行的 `A7`)会被默认父格推断挂到上一段的同列维度(`guanvis report inspect` 显示 `A3 (inherited)`),第二段就嵌进第一段的分组里;显式 `None` 才是独立的一段。
63
+
53
64
  ## 2. 结构模式配方
54
65
 
55
66
  每个配方给最小正确形态;完整可运行工程见 `evals/complex_report_pro/`(最小 Workbook DSL 工程)。
@@ -187,7 +198,7 @@ report.setDataSourceRelations([{
187
198
  }]);
188
199
  ```
189
200
 
190
- `leftId/rightId/viewId` 写数据视图别名,字段写显示名,pack 自动翻译成子卡 ID/zone key;同组 relations 必须连成连通图。JOIN 键是 METRIC 型 ID 时在子视图用 `AggrType.MAX` 带出。模板只能引用该组 `selectedCols` 输出的字段。
201
+ `leftId/rightId/viewId` 写数据视图别名,字段写显示名,pack 自动翻译成子卡 ID/zone key;同组 relations 必须连成连通图。JOIN 键是 METRIC 型 ID 时在子视图用 `AggrType.MAX` 带出。模板只能引用该组 `selectedCols` 输出的字段;`name` 非空即输出列别名(不同视图的同名字段用 `订单_雇员ID` 这类别名区分,模板按别名引用)。虚拟视图是行级明细:只有度量的视图要在 `addRow` 里放关联键(METRIC 键先加 STRING 计算字段),否则 DATA_GRID 聚成一行、JOIN 只剩一条。"行轴、列轴、度量来自不同数据视图的交叉表"有两种形态:度量格 `filterBy` 同时带行键和列键(§1 三条规则,行/列成员各来自自己的视图、无交点为空),或本节 JOIN 虚拟视图;当 Sheet 上还有其它挂在横向父格下、无法加 `filterBy` 的格(例如列头下方按列的计数行、动态公式)时只能走 JOIN。JOIN 的代价是 INNER JOIN 关联不到的成员不出现、空交点为空白、展开顺序以关联结果为准。
191
202
 
192
203
  ### 2.9 条件格式 / 冻结 / 图片
193
204
 
@@ -313,6 +324,11 @@ pack/publish 阶段(本地校验,改 DSL 即可):
313
324
  | `subtotal parent ... must be a setContextText cell` / `must be on the same row to the left` | `bindSubtotal` 没挂同行左侧标签 | 按 §2.3 三件套补 `setContextText`,值与标签同行 |
314
325
  | `redundantly filters the same view ... use context only` | 同数据视图写了 `filterBy` | 删掉 filterBy,同源层级/交叉只写 `context` |
315
326
  | `filter and filterBy cannot be combined` | 两者并用 | 只留一个 |
327
+ | `cross-view filterBy on a sheet with a horizontal expansion: filterBy on cell X must include its horizontal parent Y` | 带 `filterBy` 的格挂在横向父格下,但 `filterBy` 只给了纵向键 | 在 `filterBy` 里补 `{ field: 列键字段, cell: Y }`(§1 规则 1) |
328
+ | `cross-view filterBy on a sheet with a horizontal expansion: cell X (...) hangs off horizontal parent Y without a filterBy` | Sheet 上有 `filterBy`,而 X 只用 `context`(或默认推断)挂在横向父格下 | 给 X 补含 Y 的 `filterBy`;无法补(同视图计数行、动态公式)时整表改 §2.8 JOIN 虚拟视图 + 只写 `context` |
329
+ | `cross-view filterBy on a sheet with a horizontal expansion: filterBy on cell X cannot be combined with expansion "horizontal" on the same cell` | 把 `filterBy` 写在了横向扩展的列头格上 | 列头格只保留 `expansion: "horizontal"`,过滤放到值格 |
330
+ | `cross-view filterBy on a sheet with a horizontal expansion: row 1 and column A must both hold a cell` | 模板从 B 列或第 2 行起步,已用区域不从 A1 开始 | 标题放 A1,或 `setValue("A1", " ")` 放一个空格值(§1 规则 4) |
331
+ | `filterBy[i] cell X must also be listed in context (or be a context ancestor of a listed parent)` | `filterBy` 引用的格既不是 `context` 父格,也不是父格的 `context` 祖先 | 多级表头按祖先链写(`context: "A4*B2"` 可带 `B2` 的父格 `B1`),其它格不能当过滤键 |
316
332
  | `unknown option "context"`(出现在 bindGrandTotal 上) | `bindGrandTotal` 传了 context | 删 context;总计只支持 `aggregate/numberFormat/style` |
317
333
  | `unsupported aggregate "COUNTA"; supported aggregates: ...` | 聚合不在白名单 | 换 `SUM/COUNT/AVERAGE/MAX/MIN/PRODUCT/STDDEV/STDDEVP/VAR/VARP`;`AVG` 会自动规范化为 `AVERAGE` |
318
334
  | `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 移到顶层维度 |
Binary file
Binary file
Binary file
Binary file
Binary file