@wubaoqi/guancopilot 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/NOTICE.md +17 -0
  2. package/README.md +146 -2
  3. package/assets/coreutils.wasm +0 -0
  4. package/assets/find.wasm +0 -0
  5. package/assets/guanbi.wasm +4 -0
  6. package/assets/guancli.wasm +0 -0
  7. package/assets/guanhelm.wasm +0 -0
  8. package/assets/jq.wasm +0 -0
  9. package/assets/openskills.wasm +0 -0
  10. package/assets/rg.wasm +0 -0
  11. package/assets/sed.wasm +0 -0
  12. package/bin/guancopilot +0 -0
  13. package/bin/guanvm +0 -0
  14. package/bin/platforms.cjs +6 -0
  15. package/bin/run.cjs +39 -0
  16. package/bin/runtime.cjs +44 -0
  17. package/licenses/Go-LICENSE +27 -0
  18. package/licenses/browser-_speed-highlight_core-LICENSE +121 -0
  19. package/licenses/browser-_speed-highlight_core-package.json +14 -0
  20. package/licenses/browser-dompurify-LICENSE +202 -0
  21. package/licenses/browser-dompurify-LICENSE-MPL +373 -0
  22. package/licenses/browser-dompurify-package.json +10 -0
  23. package/licenses/browser-marked-LICENSE +44 -0
  24. package/licenses/browser-marked-package.json +10 -0
  25. package/licenses/coreutils-LICENSE +18 -0
  26. package/licenses/findutils-LICENSE +18 -0
  27. package/licenses/github.com_BurntSushi_toml_v1.6.0-COPYING +21 -0
  28. package/licenses/github.com_dustin_go-humanize_v1.0.1-LICENSE +21 -0
  29. package/licenses/github.com_google_jsonschema-go_v0.4.3-LICENSE +21 -0
  30. package/licenses/github.com_google_uuid_v1.6.0-LICENSE +27 -0
  31. package/licenses/github.com_jackc_pgpassfile_v1.0.0-LICENSE +22 -0
  32. package/licenses/github.com_jackc_pgservicefile_v0.0.0-20240606120523-5a60cdf6a761-LICENSE +22 -0
  33. package/licenses/github.com_jackc_pgx_v5_v5.11.0-LICENSE +22 -0
  34. package/licenses/github.com_jackc_puddle_v2_v2.2.2-LICENSE +22 -0
  35. package/licenses/github.com_mattn_go-isatty_v0.0.20-LICENSE +9 -0
  36. package/licenses/github.com_modelcontextprotocol_go-sdk_v1.8.0-LICENSE +216 -0
  37. package/licenses/github.com_ncruces_go-strftime_v1.0.0-LICENSE +21 -0
  38. package/licenses/github.com_remyoudompheng_bigfft_v0.0.0-20230129092748-24d4a6f8daec-LICENSE +27 -0
  39. package/licenses/github.com_santhosh-tekuri_jsonschema_v6_v6.0.3-LICENSE +175 -0
  40. package/licenses/github.com_segmentio_asm_v1.1.3-LICENSE +21 -0
  41. package/licenses/github.com_segmentio_encoding_v0.5.4-LICENSE +21 -0
  42. package/licenses/github.com_tetratelabs_wazero_v1.12.0-LICENSE +201 -0
  43. package/licenses/github.com_tetratelabs_wazero_v1.12.0-NOTICE +2 -0
  44. package/licenses/github.com_yosida95_uritemplate_v3_v3.0.2-LICENSE +25 -0
  45. package/licenses/golang.org_x_image_v0.44.0-LICENSE +27 -0
  46. package/licenses/golang.org_x_net_v0.58.0-LICENSE +27 -0
  47. package/licenses/golang.org_x_oauth2_v0.36.0-LICENSE +27 -0
  48. package/licenses/golang.org_x_sync_v0.22.0-LICENSE +27 -0
  49. package/licenses/golang.org_x_sys_v0.47.0-LICENSE +27 -0
  50. package/licenses/golang.org_x_text_v0.41.0-LICENSE +27 -0
  51. package/licenses/golang.org_x_time_v0.15.0-LICENSE +27 -0
  52. package/licenses/guanbi-skills-LICENSE +133 -0
  53. package/licenses/guanbi-skills-LICENSE.README.md +28 -0
  54. package/licenses/guanbi-skills-LICENSE.zh-CN +82 -0
  55. package/licenses/jq-COPYING +176 -0
  56. package/licenses/modernc.org_libc_v1.73.4-LICENSE +27 -0
  57. package/licenses/modernc.org_libc_v1.73.4-LICENSE-3RD-PARTY.md +305 -0
  58. package/licenses/modernc.org_mathutil_v1.7.1-LICENSE +27 -0
  59. package/licenses/modernc.org_memory_v1.11.0-LICENSE +27 -0
  60. package/licenses/modernc.org_memory_v1.11.0-LICENSE-GO +27 -0
  61. package/licenses/modernc.org_memory_v1.11.0-LICENSE-LOGO +1 -0
  62. package/licenses/modernc.org_memory_v1.11.0-LICENSE-MMAP-GO +25 -0
  63. package/licenses/modernc.org_sqlite_v1.53.0-LICENSE +26 -0
  64. package/licenses/mvdan.cc_sh_v3_v3.13.1-LICENSE +27 -0
  65. package/licenses/ripgrep-COPYING +3 -0
  66. package/licenses/ripgrep-LICENSE-MIT +21 -0
  67. package/licenses/sed-LICENSE +21 -0
  68. package/manifest.json +972 -0
  69. package/package.json +30 -4
  70. package/plugin/guancopilot.js +193 -0
  71. package/skills-bundle/.guanvm-skills-bundle +0 -0
  72. package/skills-bundle/_recipes/dashboard/gates/page_check.js +16 -0
  73. package/skills-bundle/_recipes/dashboard/gates/report_check.js +4 -0
  74. package/skills-bundle/_recipes/dashboard/knowledge/01.md +1 -0
  75. package/skills-bundle/_recipes/dashboard/knowledge/02.md +1 -0
  76. package/skills-bundle/_recipes/dashboard/knowledge/03.md +1 -0
  77. package/skills-bundle/_recipes/dashboard/knowledge/04.md +1 -0
  78. package/skills-bundle/_recipes/dashboard/knowledge/05.md +1 -0
  79. package/skills-bundle/_recipes/dashboard/knowledge/06.md +1 -0
  80. package/skills-bundle/_recipes/dashboard/template.json +41 -0
  81. package/skills-bundle/_recipes/etl/gates/etl_live_check.js +10 -0
  82. package/skills-bundle/_recipes/etl/gates/etl_report_check.js +3 -0
  83. package/skills-bundle/_recipes/etl/knowledge/01.md +1 -0
  84. package/skills-bundle/_recipes/etl/knowledge/02.md +9 -0
  85. package/skills-bundle/_recipes/etl/knowledge/03.md +1 -0
  86. package/skills-bundle/_recipes/etl/knowledge/04.md +1 -0
  87. package/skills-bundle/_recipes/etl/knowledge/05.md +1 -0
  88. package/skills-bundle/_recipes/etl/template.json +39 -0
  89. package/skills-bundle/_recipes/hints.json +46 -0
  90. package/skills-bundle/guancli/.commandface.json +155 -0
  91. package/skills-bundle/guancli/SKILL.md +342 -0
  92. package/skills-bundle/guancli/evals/evals.json +156 -0
  93. package/skills-bundle/guancli/evals/fixtures/metric-candidate-empty-search.json +13 -0
  94. package/skills-bundle/guancli/evals/fixtures/metric-candidate-prior-request.json +13 -0
  95. package/skills-bundle/guancli/evals/fixtures/metric-consumption-columns.json +46 -0
  96. package/skills-bundle/guancli/evals/fixtures/metric-consumption-metadata.json +36 -0
  97. package/skills-bundle/guancli/evals/fixtures/metric-consumption-query.json +18 -0
  98. package/skills-bundle/guancli/references/analysis-primitives.md +95 -0
  99. package/skills-bundle/guancli/references/auth-and-config.md +131 -0
  100. package/skills-bundle/guancli/references/examples/metric-date-in.json +7 -0
  101. package/skills-bundle/guancli/references/examples/metric-query-rows.py +83 -0
  102. package/skills-bundle/guancli/references/examples/metric-query.sh +6 -0
  103. package/skills-bundle/guancli/references/form-chatbi.md +199 -0
  104. package/skills-bundle/guancli/references/gateway.md +148 -0
  105. package/skills-bundle/guancli/references/metric-batch-query.md +82 -0
  106. package/skills-bundle/guancli/references/metric-commands.md +562 -0
  107. package/skills-bundle/guancli/references/metric-generic-query.md +215 -0
  108. package/skills-bundle/guancli/references/metric-json-migration.md +18 -0
  109. package/skills-bundle/guancli/references/metric-query-recipes.md +358 -0
  110. package/skills-bundle/guancli/references/preview-and-output.md +192 -0
  111. package/skills-bundle/guancli/references/resource-query.md +101 -0
  112. package/skills-bundle/guands/SKILL.md +281 -0
  113. package/skills-bundle/guands/references/calc-fields-boundaries-and-pitfalls.md +160 -0
  114. package/skills-bundle/guands/references/command-reference.md +267 -0
  115. package/skills-bundle/guands/references/incremental-and-schedule.md +157 -0
  116. package/skills-bundle/guands/references/workflows.md +256 -0
  117. package/skills-bundle/guanetl/SKILL.md +391 -0
  118. package/skills-bundle/guanetl/evals/evals.json +41 -0
  119. package/skills-bundle/guanetl/references/ETL_AI_DEVELOP.md +456 -0
  120. package/skills-bundle/guanmetric/SKILL.md +144 -0
  121. package/skills-bundle/guanmetric/evals/evals.json +117 -0
  122. package/skills-bundle/guanmetric/evals/fixtures/atomic-detail-read-model.json +18 -0
  123. package/skills-bundle/guanmetric/evals/fixtures/confirmed-template-summary.json +12 -0
  124. package/skills-bundle/guanmetric/evals/fixtures/create-result-unknown-state.json +13 -0
  125. package/skills-bundle/guanmetric/evals/forward-eval-2026-09-21.json +62 -0
  126. package/skills-bundle/guanmetric/references/metric-api-contract-examples.json +690 -0
  127. package/skills-bundle/guanmetric/references/metric-api-contract.schema.json +349 -0
  128. package/skills-bundle/guanmetric/references/metric-api-contracts.md +436 -0
  129. package/skills-bundle/guanmetric/references/metric-atomic-mutation.md +238 -0
  130. package/skills-bundle/guanmetric/references/metric-batch-status.md +71 -0
  131. package/skills-bundle/guanmetric/references/metric-business-dictionary-api-contract-examples.json +53 -0
  132. package/skills-bundle/guanmetric/references/metric-business-dictionary-api-contract.schema.json +26 -0
  133. package/skills-bundle/guanmetric/references/metric-business-dictionary.md +76 -0
  134. package/skills-bundle/guanmetric/references/metric-common-fields.md +246 -0
  135. package/skills-bundle/guanmetric/references/metric-composite-mutation.md +165 -0
  136. package/skills-bundle/guanmetric/references/metric-delete-mutation.md +70 -0
  137. package/skills-bundle/guanmetric/references/metric-derived-mutation.md +453 -0
  138. package/skills-bundle/guanmetric/references/metric-excel-template.md +365 -0
  139. package/skills-bundle/guanmetric/references/metric-expression-rules.md +146 -0
  140. package/skills-bundle/guanmetric/references/metric-flow-generate-example.json +79 -0
  141. package/skills-bundle/guanmetric/references/metric-flow-generate.schema.json +93 -0
  142. package/skills-bundle/guanmetric/references/metric-flow.md +218 -0
  143. package/skills-bundle/guanmetric/references/metric-hierarchy-api-contract-examples.json +171 -0
  144. package/skills-bundle/guanmetric/references/metric-hierarchy-api-contract.schema.json +167 -0
  145. package/skills-bundle/guanmetric/references/metric-hierarchy-api-contracts.md +193 -0
  146. package/skills-bundle/guanmetric/references/metric-mutation.md +155 -0
  147. package/skills-bundle/guanmetric/references/metric-public-dimension-api-contract-examples.json +62 -0
  148. package/skills-bundle/guanmetric/references/metric-public-dimension-api-contract.schema.json +47 -0
  149. package/skills-bundle/guanmetric/references/metric-public-dimension.md +155 -0
  150. package/skills-bundle/guanmetric/references/metric-tree-api-contract-examples.json +277 -0
  151. package/skills-bundle/guanmetric/references/metric-tree-api-contract.schema.json +263 -0
  152. package/skills-bundle/guanmetric/references/metric-tree-api-contracts.md +53 -0
  153. package/skills-bundle/guanmetric/references/metric-tree-mutation.md +500 -0
  154. package/skills-bundle/guanmetric/references/metric-validation-ownership.md +74 -0
  155. package/skills-bundle/guanvis/SKILL.md +333 -0
  156. package/skills-bundle/guanvis/evals/china_map/card_01_basic_map.js +13 -0
  157. package/skills-bundle/guanvis/evals/china_map/card_02_world_map.js +10 -0
  158. package/skills-bundle/guanvis/evals/china_map/card_03_point_map.js +12 -0
  159. package/skills-bundle/guanvis/evals/china_map/page.js +10 -0
  160. package/skills-bundle/guanvis/evals/china_map/schema.js +10 -0
  161. package/skills-bundle/guanvis/evals/comparative_mvp_roundtrip/README.md +30 -0
  162. package/skills-bundle/guanvis/evals/comparative_mvp_roundtrip/card_01_comparative_mvp.js +72 -0
  163. package/skills-bundle/guanvis/evals/comparative_mvp_roundtrip/page.js +6 -0
  164. package/skills-bundle/guanvis/evals/comparative_mvp_roundtrip/schema.js +22 -0
  165. package/skills-bundle/guanvis/evals/comparative_mvp_roundtrip/verify_default_roundtrip.sh +92 -0
  166. package/skills-bundle/guanvis/evals/complex_report_pro/card_01_monthly.js +63 -0
  167. package/skills-bundle/guanvis/evals/complex_report_pro/page.js +6 -0
  168. package/skills-bundle/guanvis/evals/complex_report_pro/schema.js +6 -0
  169. package/skills-bundle/guanvis/evals/custom_chart_echarts/card_01_bar.js +13 -0
  170. package/skills-bundle/guanvis/evals/custom_chart_echarts/card_02_pie.js +13 -0
  171. package/skills-bundle/guanvis/evals/custom_chart_echarts/charts/bar.js +11 -0
  172. package/skills-bundle/guanvis/evals/custom_chart_echarts/charts/pie.js +15 -0
  173. package/skills-bundle/guanvis/evals/custom_chart_echarts/page.js +5 -0
  174. package/skills-bundle/guanvis/evals/custom_chart_echarts/schema.js +9 -0
  175. package/skills-bundle/guanvis/evals/custom_chart_sdk/card_01_level_nodes.js +19 -0
  176. package/skills-bundle/guanvis/evals/custom_chart_sdk/charts/level_nodes.css +51 -0
  177. package/skills-bundle/guanvis/evals/custom_chart_sdk/charts/level_nodes.html +1 -0
  178. package/skills-bundle/guanvis/evals/custom_chart_sdk/charts/level_nodes.js +28 -0
  179. package/skills-bundle/guanvis/evals/custom_chart_sdk/page.js +5 -0
  180. package/skills-bundle/guanvis/evals/custom_chart_sdk/schema.js +7 -0
  181. package/skills-bundle/guanvis/evals/dynamic_fields/card_01_dynamic_dataset.js +20 -0
  182. package/skills-bundle/guanvis/evals/dynamic_fields/card_02_dynamic_metric_chart.js +16 -0
  183. package/skills-bundle/guanvis/evals/dynamic_fields/metrics.js +19 -0
  184. package/skills-bundle/guanvis/evals/dynamic_fields/page.js +9 -0
  185. package/skills-bundle/guanvis/evals/dynamic_fields/schema.js +7 -0
  186. package/skills-bundle/guanvis/evals/dynamic_fields/verify_default_roundtrip.sh +137 -0
  187. package/skills-bundle/guanvis/evals/percentage_advcalc_roundtrip/README.md +26 -0
  188. package/skills-bundle/guanvis/evals/percentage_advcalc_roundtrip/card_01_percentage_advcalc.js +44 -0
  189. package/skills-bundle/guanvis/evals/percentage_advcalc_roundtrip/card_02_scroll_percentage.js +12 -0
  190. package/skills-bundle/guanvis/evals/percentage_advcalc_roundtrip/page.js +6 -0
  191. package/skills-bundle/guanvis/evals/percentage_advcalc_roundtrip/schema.js +22 -0
  192. package/skills-bundle/guanvis/evals/rank_advcalc_roundtrip/README.md +28 -0
  193. package/skills-bundle/guanvis/evals/rank_advcalc_roundtrip/card_01_rank_advcalc.js +59 -0
  194. package/skills-bundle/guanvis/evals/rank_advcalc_roundtrip/page.js +6 -0
  195. package/skills-bundle/guanvis/evals/rank_advcalc_roundtrip/schema.js +22 -0
  196. package/skills-bundle/guanvis/evals/rank_advcalc_roundtrip/verify_default_roundtrip.sh +135 -0
  197. package/skills-bundle/guanvis/evals/running_total_advcalc_roundtrip/README.md +15 -0
  198. package/skills-bundle/guanvis/evals/running_total_advcalc_roundtrip/card_01_running_total_advcalc.js +74 -0
  199. package/skills-bundle/guanvis/evals/running_total_advcalc_roundtrip/page.js +6 -0
  200. package/skills-bundle/guanvis/evals/running_total_advcalc_roundtrip/schema.js +22 -0
  201. package/skills-bundle/guanvis/evals/sales_dashboard/card_01_revenue_column.js +10 -0
  202. package/skills-bundle/guanvis/evals/sales_dashboard/card_02_trend_line.js +9 -0
  203. package/skills-bundle/guanvis/evals/sales_dashboard/card_03_kpi.js +6 -0
  204. package/skills-bundle/guanvis/evals/sales_dashboard/card_04_pie.js +9 -0
  205. package/skills-bundle/guanvis/evals/sales_dashboard/card_05_calc_field_test.js +12 -0
  206. package/skills-bundle/guanvis/evals/sales_dashboard/page.js +11 -0
  207. package/skills-bundle/guanvis/evals/sales_dashboard/schema.js +14 -0
  208. package/skills-bundle/guanvis/evals/sales_dashboard/selector_01_region.js +10 -0
  209. package/skills-bundle/guanvis/evals/split_charts/card_01_column_split.js +10 -0
  210. package/skills-bundle/guanvis/evals/split_charts/card_02_line_split.js +10 -0
  211. package/skills-bundle/guanvis/evals/split_charts/card_03_combo_split.js +11 -0
  212. package/skills-bundle/guanvis/evals/split_charts/page.js +10 -0
  213. package/skills-bundle/guanvis/evals/split_charts/schema.js +14 -0
  214. package/skills-bundle/guanvis/evals/tab_layout/card_00_total_revenue.js +6 -0
  215. package/skills-bundle/guanvis/evals/tab_layout/card_01_revenue.js +10 -0
  216. package/skills-bundle/guanvis/evals/tab_layout/card_02_trend.js +9 -0
  217. package/skills-bundle/guanvis/evals/tab_layout/card_03_story.js +11 -0
  218. package/skills-bundle/guanvis/evals/tab_layout/page.js +23 -0
  219. package/skills-bundle/guanvis/evals/tab_layout/schema.js +11 -0
  220. package/skills-bundle/guanvis/evals/tab_layout/selector_01_region.js +8 -0
  221. package/skills-bundle/guanvis/references/api-reference.md +476 -0
  222. package/skills-bundle/guanvis/references/builder-reference.md +1676 -0
  223. package/skills-bundle/guanvis/references/chart-properties.md +2101 -0
  224. package/skills-bundle/guanvis/references/checkout-editing.md +53 -0
  225. package/skills-bundle/guanvis/references/complex-report-pro-patterns.md +398 -0
  226. package/skills-bundle/guanvis/references/dir-and-page-management.md +48 -0
  227. package/skills-bundle/guanvis/references/metric-chart-reference.md +258 -0
  228. package/skills-bundle/guanvis/references/publish-and-constraints.md +60 -0
  229. package/skills-bundle/guanvis/references/selector-reference.md +431 -0
  230. package/skills-bundle/guanvis/references/tableau-migration.md +73 -0
  231. package/skills-bundle/guanvis/references/theme.md +123 -0
  232. package/skills-bundle/guanvis/references/troubleshooting.md +25 -0
  233. package/skills-bundle/guanvis/references/validation-and-chart-patterns.md +205 -0
  234. package/skills-bundle/guanwf/SKILL.md +543 -0
  235. package/skills-bundle/guanwf/references/ETL_AI_DEVELOP.md +456 -0
  236. package/skills-bundle/guanwf/references/TASK_NODE_CONTRACTS.md +64 -0
  237. package/skills-bundle/guanwf/references/WORKFLOW_DSL.md +536 -0
  238. package/skills-bundle/guanwf/references/WORKFLOW_NODES.md +166 -0
  239. package/skills-bundle/js/SKILL.md +145 -0
@@ -0,0 +1,53 @@
1
+ # 编辑已有线上仪表板(checkout 工作流)
2
+
3
+ SKILL.md 正文只保留 checkout 编辑的红线速记;本文件是 `checkout → 编辑 → diff → publish` 闭环的完整参考:生成物形态、attachCard 编辑模型、DSL 操作语义、覆盖发布与备份机制。
4
+
5
+ ## 1. Checkout 生成物形态
6
+
7
+ `guanvis checkout <pgId> -d <dir>` 把线上 Page 拉成可编辑工程:
8
+
9
+ - **schema.js**:best-effort 生成——数据集无权限/已删除/跨环境残留时只 warning 并继续生成可编辑工程;后续可手动 `guanvis init <dsId> -d <dir> --force` 补齐。
10
+ - **普通 Card**:生成 `attachCard(cdId, ".guanvis/base/cards/<cdId>.json")`,接受线上 JSON 作为基线。
11
+ - **自定义图表(SDK/ECHARTS_LITE)**:脚本/HTML/CSS 反编译到 `charts/<cdId>.js|.html|.css`;超长内联资源(GeoJSON、base64 图片)抽取到 `charts/<cdId>_assets/` + `__asset_text/__asset_base64` 占位符(逐字节往返验证,未编辑时 diff 恒为空)。card JS 用 `attachCard(...).loadContent("charts/<cdId>")` 引用,直接编辑内容文件即可改图表。
12
+ - **复杂报表 Pro**:额外下载可编辑 xlsx 到 `templates/`,生成 `createComplexReportPro()` 父卡和嵌套的 attached data view,并使用下一模板版本;旧版复杂报表 checkout 直接拒绝。编辑闭环见 `complex-report-pro-patterns.md` §5。
13
+ - **Page**:生成 `createPage(...).setBasePath(...).placeCard("cdId", ...)` 脚本,尽量反编译根布局里的 Tab/CardGroup/AreaTitle/SelGroup 和快捷筛选组,并保留 parentDirId。checkout 生成的 page 布局默认用 card/selector ID 字符串。
14
+ - **适用范围**:checkout 工程只用于修改指定 Page,不用于复制新 Page;遇到嵌套布局组件等当前 DSL 不能安全表达的结构,checkout 会直接失败,不生成会清结构的工程。只支持普通仪表板(pgType=PAGE),自助取数/数据大屏/清单页等特殊页面会在 checkout 入口直接报错。
15
+ - `checkout --overwrite` 会清理输出目录下所有根级 `.js` 和旧 `.guanvis`,避免 schema/selector/metrics/other.js 残留混入运行。
16
+
17
+ ## 2. 账号与语言要求
18
+
19
+ checkout 读到的是"当前账号视角"的卡片定义,publish 会把它整体回写。必须用对页面涉及数据集拥有**完整列权限、无脱敏限制**的账号(推荐资源 owner 或管理员)执行 checkout/publish,否则读接口会按该账号权限裁剪 zone 字段/脱敏字段,回写后这些字段会从线上卡片中**永久丢失**。租户启用多语言时,操作账号语言应与卡片原始语言一致,避免把翻译后的字段显示名固化回卡片。
20
+
21
+ ## 3. attachCard 编辑模型与 DSL 操作语义
22
+
23
+ `attachCard(cardId, jsonPath)` 是"base JSON + 链式 DSL 操作 = 目标 JSON"。它不会修改 base JSON,重复执行同一组 JS 操作应产出相同 payload。`attachCard` 只能引用 manifest 记录的原始 base-card,且 `cardId` 必须与该 JSON 的资源 ID 一致。
24
+
25
+ - **通用操作**:已有卡片可继续串接常用 `createCard` 后续操作,包括标题/描述(`setName`/`setDescription`)、`setRawSettings`、图例/标签/坐标轴/表格/拆分等视觉设置。
26
+ - **Zone 操作按链式顺序真实执行**:`addRow/addMetric/...` 追加字段;`insertMetric(index, field)` 插入;`removeMetric(selector)` 删除字段并保守清理其它 zone 中同字段引用;`moveMetric(selector, index)` 调整顺序;`updateMetric(selector, patch)` / `patchMetric(...)` 修改已有字段属性并保留未设置字段配置。`setRows/setMetrics/...` 和 `clearRows/clearMetrics/...` 是整 zone 重建,不会继承被替换字段的格式,并会在验证时提示 warning——默认优先用 `update*/patch*/add*/remove*/move*`。
27
+ - **自定义图表内容编辑**:仅限 subType 为 **SDK / ECHARTS_LITE**。checkout 自动反编译出 `charts/<cdId>.js`(含内嵌资源抽取),card JS 里 `attachCard(...).loadContent("charts/<cdId>")`;也可用 `.setScript()/.setHtml()/.setCss()/.setLibs()/.addLib()` 内联修改。COMPLEX_REPORT(走 Pro 流程)、PLUGIN/PLUGIN_LITE(事实源是插件市场资源)、REPORT_FORM(填报模板配置)调用这些内容编辑 API 会直接报错,不要对它们生成此类修改;详见 `builder-reference.md` 自定义图表章节。
28
+ - **已有 selector 配置**:仅能力矩阵中标记可编辑的类型可用 `.setSelectorSetting()` 修改配置;禁止原地改变 selector 类型或重新绑定字段。已有 `PARAMETER` selector 可用 `.bindParameter()` 原地更新参数绑定;已有 `TEXT_MATCH` selector 可更新文本操作符、筛选级别、默认文本、`canClear` 和列名显示;树状筛选器、层级树状筛选器和组合条件筛选器分别使用 `.setTreeSetting()`、`.setLayerTreeSetting()` 和 `.setCombinationSetting()` 修改专属配置。自定义等未提供专用 DSL 的类型只能保留已有专属配置。完整能力边界、配置结构和适用规则见 `selector-reference.md`。
29
+ - **已有 selector 联动**:`cdType=SELECTOR` 的筛选器可用 `.addLink(cardIdOrIndex, targetFieldName?)` / `.removeLink(cardIdOrIndex)` / `.clearLinks()` 叠加修改现有 `settings.asFilter`。这些方法不支持 `TREE_SELECTOR`、`CUSTOM_SELECTOR` 等其他 selector 类型;不要把有顺序的联动增删混进 `.setSelectorSetting()`,也不要反向改 JSON。
30
+ - **Page 快捷筛选区**:按有序操作执行——`setFilterLayout` 整体设置,`clearFilterLayout` 清空,`addFilterLayoutItem` 追加去重,`insert/remove/moveFilterSelector` 局部调整;筛选栏布局、名称/控件/按钮字体、控件风格、按钮色、背景色和背景图用 `setFilterPanelLayout()`。省略字段保留 base,显式 `null` 删除对应覆盖并恢复产品/主题默认。
31
+ - **筛选器在筛选栏和画布间移动**:优先用动作级 API:`moveFilterSelectorToCanvas(selectorId, x, y, w, h)` / `moveCanvasSelectorToFilter(selectorId, index?)`;筛选器组用 `moveSelectorGroupToCanvas(group, x, y, w, h)` / `moveCanvasSelectorGroupToFilter(group, index?)`。
32
+ - **新增卡片**必须使用 `createCard()` / `createSelector()` 等工厂函数(新建资源 ID 用 `guanvis genid` 生成)。
33
+ - **影响面检查**:发布前用 `guanvis diff <dir>` 或 `preview` 输出里的 `changeSummary` 查看 base JSON 到最终 payload 的路径级影响面。
34
+ - 没有 DSL 操作覆盖的需求,先扩展 DSL,不要改 `.guanvis` JSON。
35
+
36
+ ## 4. 编辑红线的完整依据(编辑 ≠ 删除重建)
37
+
38
+ 用户要求修改/改名已发布 Page 或 Card 时,必须保留原 pgId/cdId 原地覆盖发布,先按工程来源分流:
39
+
40
+ - **本地有源工程**(该 Page 就是当前 guanvis 工程创建并发布的,`card_*.js` / `page.js` 还在,且线上没有在发布后被网页端改动过):直接修改本地 JS 源文件(改名即改 `createCard(...)` / `createPage(...)` 标题)重新 publish 同 ID 覆盖。
41
+ - **线上在发布后被网页端修改过、本地没有原始 JS 工程、或不确定线上是否已被改动**:用 `checkout` 拉取线上版本编辑——Page 改 checkout 生成的 `createPage(...)` 标题,已 checkout 的 Card 用 `attachCard(...).setName(...)`。**不得**把 `attachCard` 改写成 `createCard()`:那会绕过 base JSON,未被 DSL 表达的线上配置会在覆盖发布时丢失(`createCard()` 只用于新增卡片)。
42
+ - **禁止**用"新建一个新 Page/Card + 删除或废弃旧的"来模拟编辑——资源 ID 变化会让收藏、分享链接、订阅推送、门户菜单引用和页面权限配置全部失效,这些状态 guanvis 无法迁移。
43
+ - **全局参数同理**:只能 `parameter update <dpId>` 原地更新,禁止 delete 后重建同名参数(dpId 变化会让所有引用它的卡片/筛选器断链);`parameter delete` 只用于用户明确要求删除参数本身。
44
+ - **描述修复**:仅修复已发布 Card/Page 的描述时保留原资源 ID,用 `guanvis card/page set-description` 更新线上描述;本地有对应 JS 工程时同步更新 `.setDescription(...)`。
45
+ - **只改 Page 名称或所在目录时不必走 checkout**:`guanvis page rename <pgId> --name` 和 `guanvis page move <pgId> --parent-dir <dirId>` 直接原地改,保留 pgId 且不动页面内容;目标目录不存在时先 `guanvis dir create`。需要连内容一起改才按上面的分流走 checkout。删除页面本身用 `guanvis page delete <pgId> --yes`(页面上还有卡片时再加 `--force`,`--force` 是"连卡片一起删"的开关、不是二级确认,单给 `--force` 仍会被 `--yes` 拦住)。页面删除是软删,页面连同卡片与权限进回收站,但**CLI 没有恢复命令**,要找回只能人工登录 BI 网页端在回收站里还原;页面目录的删除(`guanvis dir delete`)则是物理删,不进回收站、无从恢复。
46
+
47
+ ## 5. 发布:覆盖确认、备份与"另存新版本"
48
+
49
+ checkout 工程发布的是对 checkout 指定 Page 的**覆盖式修改**,不承担"复制新版本"职责。
50
+
51
+ - **覆盖确认(Agent 红线)**:对 checkout 工程同 ID 发布时,CLI 检测到同 ID Page 必须显式加 `--allow-overwrite` 才会覆盖。**Agent 禁止在未确认的情况下自行加 `--allow-overwrite`**:CLI 提示将覆盖线上 Page 时,必须先停止发布,向用户说明将覆盖的 Page ID、名称和覆盖后可能替换原页面布局,等用户明确确认"覆盖"后才可以重跑并加该参数。发布前可用 `--dry-run` 查看会覆盖哪些线上 Page。Card/Selector ID 不做在线覆盖检查。
52
+ - **覆盖备份机制**:使用 `--allow-overwrite` 时,CLI 会先为冲突 Page 发起资源包导出备份并等待导出成功;备份包含 Page 及其组成资源,但不会沿血缘额外导出数据集、数据账户等上游资源(避免普通用户因缺少上游资源所有者权限而无法备份)。备份未成功则中止覆盖。CLI 只记录备份导出记录和 packageId,不自动下载资源包;需要回滚时,到 BI 资源迁移导出记录中手动下载该资源包后再导入覆盖回去。
53
+ - **保留原页面、出一个新版本**:不要把 checkout JSON 复制后改 ID 做"新版本",也不要在 guanvis CLI 里手写复杂 ID map 或用浏览器手工复制页面。用 `guanvis page save-as <pgId> --suffix _guanvis`(或 `--name <新名>`,可加 `--parent-dir <dirId>`)做 BI 原生整页另存为:服务端事务复制全部卡片并重映射联动/下钻/父子/指标关系等 ID,命令自动 release 发布副本(7.2.0 之前无草稿态会自动跳过),然后基于副本页面 checkout/edit。命令会回读副本核对卡片数(无指标平台 license 时指标卡会被服务端过滤)并扫描是否残留源页卡片 ID 引用,出现 ⚠ 警告时先排查再继续。
@@ -0,0 +1,398 @@
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
+ 只支持 `COMPLEX_REPORT_PRO`:旧版复杂报表(COMPLEX_REPORT)不支持创建或编辑,一律拒绝并建议外部迁移后新建 Pro;Pro 父卡也不能用泛化 `attachCard()` 修改——checkout 会生成 `createComplexReportPro()` 工程,编辑走 §5。
30
+
31
+ ## 1. 模板心智模型(读懂再写)
32
+
33
+ Pro 模板 = Excel 网格上放"模板格"。每个模板格要么绑定数据(`bindDimension/bindMetric/...`),要么是随分组重复的标签(`setContextText`),要么是随扩展复制的公式(`setDynamicFormula`)。**父格(`context`)决定重复结构**:
34
+
35
+ - 纵向维度格是"锚点":每个取值向下复制一行(或一块)。
36
+ - 子格挂 `context: 锚点`,表示"在锚点的每个分组内展开/计算"。
37
+ - GcExcel 按父格树把模板行展开成最终表格;小计/总计是挂在标签格上的聚合。
38
+
39
+ **父格位置规则**(pack 本地强校验,dev29 实证语义):
40
+
41
+ | 关系 | 要求 |
42
+ |---|---|
43
+ | 左父格(纵向扩展维度) | 位于子格**左上方位**即可:同行左、同列上、左上区域都合法(覆盖组标题独占一行的"阶梯布局") |
44
+ | 上父格(横向扩展维度) | 必须**同列上方**(交叉表专用) |
45
+ | 双父格 | `context: "A4*B3"`,左父格+上父格各最多一个 |
46
+ | `setContextText` 标签的父格 | 必须是**维度绑定**,位于左上方位 |
47
+ | `bindSubtotal` 的父格 | 必须是**同行左侧的 `setContextText` 标签** |
48
+ | 小计行公式聚合(`setDynamicFormula` 挂标签) | 标签必须**同行左侧** |
49
+ | `bindGrandTotal` | **禁止**传 `context`(自动 `E=N,C=None`) |
50
+
51
+ **同一数据视图内只写 `context`,绝不写 `filterBy`**(同源 filterBy 会编译成嵌套 `LP(...)`,后端 500;pack 已本地拒绝)。`filterBy` 仅用于跨数据视图父格映射,且 cell 必须同时出现在 `context` 中。
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
+
64
+ ## 2. 结构模式配方
65
+
66
+ 每个配方给最小正确形态;完整可运行工程见 `evals/complex_report_pro/`(最小 Workbook DSL 工程)。
67
+
68
+ ### 2.0 最小工程骨架(从零创建)
69
+
70
+ ```bash
71
+ guanvis init <dsId> -d ./pro_report/ # 单数据集
72
+ guanvis init <ds1> <ds2> -d ./x/ --alias ORDERS,SALES # 多数据集必须用 --alias 命名全局变量
73
+ cd ./pro_report && guanvis genid 3 # 父卡 + 每个数据视图 + page 各一个 ID
74
+ ```
75
+
76
+ 多数据集时**不要**手写 `var X = defineDataset(...)`(schema.js 的顶层 var 不会进入其他脚本的作用域),一律用 `--alias`;单数据集裸引用可用 `f("字段")`,多数据集用 `field(SALES, "字段")` 指明归属(`f()` 永远取第一个数据集)。
77
+
78
+ `card_01_xxx.js` 三段式:数据视图 + workbook + 注册(下例为分组小计形态,其他结构套本节配方):
79
+
80
+ ```javascript
81
+ var orders = createCard(ChartType.DATA_GRID, "orders") // 数据视图:模板引用的字段都必须进 row/metric
82
+ .setId("<genid>")
83
+ .bindDataset(DS)
84
+ .addRow(f("区域"))
85
+ .addMetric(f("金额", { aggrType: AggrType.SUM }));
86
+
87
+ var workbook = createReportWorkbook().addSheet("月报", function (sheet) {
88
+ sheet.merge("A1:B1")
89
+ .setValue("A1", "销售月报", { fontSize: 16, bold: true, horizontalAlignment: "center", verticalAlignment: "center" })
90
+ .setRowHeight(1, 34)
91
+ .setValue("A2", "区域", { bold: true }).setValue("B2", "金额", { bold: true })
92
+ .bindDimension("A3", reportField("orders", "区域"))
93
+ .bindMetric("B3", reportField("orders", "金额"), { aggregate: "SUM", context: "A3", numberFormat: "#,##0.00" })
94
+ .setContextText("A4", "小计", { context: "A3" })
95
+ .bindSubtotal("B4", reportField("orders", "金额"), { aggregate: "SUM", context: "A4" })
96
+ .bindGrandTotal("B5", reportField("orders", "金额"), { aggregate: "SUM" });
97
+ });
98
+
99
+ registerComplexReportPro(createComplexReportPro("销售月报")
100
+ .setId("<genid>")
101
+ .setWorkbook(workbook)
102
+ .addDataView("orders", orders)
103
+ .setColumnWidthStretch(true)
104
+ .build());
105
+ ```
106
+
107
+ `page.js` 正常 `registerPage`(Pro 卡按注册顺序参与 card index,或用 ID 字符串);**Pro 卡必须与引用它的 Page 同包 pack/publish**。之后与普通流程一致:`preview` → `pack`/`publish`。
108
+
109
+ ### 2.1 层级分组(区域→客户 两级)
110
+
111
+ ```javascript
112
+ sheet.bindDimension("A2", reportField("v", "区域")) // 顶层锚点,无 context
113
+ .bindDimension("B2", reportField("v", "客户"), { context: "A2" }) // 嵌套维度
114
+ .bindMetric("C2", reportField("v", "金额"), { aggregate: "SUM", context: "B2" });
115
+ ```
116
+
117
+ 维度默认纵向扩展 + 相邻同值合并(`group` 缺省即 merge);行级不去重列表用 `group: "list"`。排序 `sort: "asc" | "desc"`。
118
+
119
+ ### 2.2 横纵交叉(行=城市,列=类别)
120
+
121
+ ```javascript
122
+ sheet.bindDimension("A4", reportField("v", "城市")) // 纵向
123
+ .bindDimension("B3", reportField("v", "类别"), { expansion: "horizontal" }) // 横向
124
+ .bindMetric("B4", reportField("v", "金额"), { aggregate: "SUM", context: "A4*B3" });
125
+ ```
126
+
127
+ ### 2.3 分组小计 + 全表总计(三件套)
128
+
129
+ ```javascript
130
+ sheet.bindDimension("A2", reportField("v", "区域"))
131
+ .bindMetric("C2", reportField("v", "金额"), { aggregate: "SUM", context: "A2" })
132
+ .setContextText("A3", "小计", { context: "A2" }) // ① 标签挂维度
133
+ .bindSubtotal("C3", reportField("v", "金额"), { aggregate: "SUM", context: "A3" }) // ② 值挂标签(同行左)
134
+ .bindGrandTotal("C4", reportField("v", "金额"), { aggregate: "SUM" }); // ③ 总计不带 context
135
+ ```
136
+
137
+ 不要把老复杂报表的 `G_SUBTOTAL/G_GRANDTOTAL` 写进 Pro 模板。多级分组下给**内层维度**做小计时,标签列必须 ≤ 该维度所在列(如三级分组 B 地区/C 省份/D 城市,省份小计标签放 `C4` 挂 `C3`,不能放 A4——校验报 `must be in its column or to the left`);标签行可 `merge("C4:D4")` 横跨到城市列补齐版式。
138
+
139
+ ### 2.4 小计行公式聚合(对公式列/数据列求和均可)
140
+
141
+ 小计行需要聚合"动态公式列"(如费率)或再聚合数据列时,`setDynamicFormula` 挂小计标签,GcExcel 会把引用改写成整组区域(`SUM(D2)` → `SUM(D2,D4,...)` 或 `SUM(C3:C103)`):
142
+
143
+ ```javascript
144
+ sheet.setDynamicFormula("E2", "IFERROR(D2/C2,0)", { context: "B2" }) // 明细行公式列
145
+ .setContextText("B3", "城市小计", { context: "B2" })
146
+ .setDynamicFormula("D3", "SUM(D2)", { context: "B3" }) // 小计行聚合数据列
147
+ .setDynamicFormula("E3", "AVERAGE(E2)", { context: "B3" }); // 小计行聚合公式列
148
+ ```
149
+
150
+ 小计公式格的数字格式来自它**自己的 style**,不继承明细列。
151
+
152
+ ### 2.5 阶梯分组(组标题独占一行)
153
+
154
+ ```javascript
155
+ sheet.bindDimension("A2", reportField("v", "国家")) // 组标题行锚点
156
+ .bindDimension("B3", reportField("v", "城市"), { context: "A2" }) // 明细行跨行挂锚点
157
+ .bindMetric("C3", reportField("v", "金额"), { aggregate: "SUM", context: "B3" })
158
+ .setContextText("B4", "小计", { context: "A2" }) // 小计标签也跨行挂锚点
159
+ .setDynamicFormula("C4", "SUM(C3)", { context: "B4" });
160
+ ```
161
+
162
+ ### 2.6 段落明细块(每条记录一个多行块)
163
+
164
+ ```javascript
165
+ sheet.beginBlock("A2", reportField("orders", "订单ID")) // 锚点=块左上角,通常是记录 ID(METRIC 可作锚点)
166
+ .text("B2", "货主名称")
167
+ .dimension("C2", reportField("orders", "货主名称"))
168
+ .text("B3", "运货费")
169
+ .metric("D3", reportField("orders", "运货费"), { aggregate: "SUM", numberFormat: "#,##0.00" })
170
+ .spacer("A4") // 块间留白行
171
+ .end();
172
+ ```
173
+
174
+ 块内禁小计/总计;其他模板格不得落入块矩形;**锚点格自动成为维度绑定,不要再对锚点格写 `.text()`/`.dimension()`**(报 `contains multiple template operations`)——锚点直接显示维度值,标签放旁边格。
175
+
176
+ ### 2.7 分页(按顶层分组分页)
177
+
178
+ ```javascript
179
+ sheet.bindDimension("A3", reportField("v", "客户"), { countPerPage: 20 }); // 唯一 countPerPage,顶层无 context
180
+ report.setPagination(true);
181
+ ```
182
+
183
+ 约束:单 Sheet;全模板禁 `filter/filterBy`;`countPerPage` 只能放在无 `context` 的顶层维度(嵌套子维度会截断祖先合并格,pack 拒绝)。
184
+
185
+ ### 2.8 多数据源 JOIN(虚拟视图)
186
+
187
+ ```javascript
188
+ report.setDataSourceRelations([{
189
+ tableName: "产品供应商", // 模板里用 产品供应商.字段 引用
190
+ relations: [
191
+ { leftId: "products", rightId: "suppliers",
192
+ leftColumnKey: "供应商ID", rightColumnKey: "供应商ID", joinType: "INNER" }
193
+ ],
194
+ selectedCols: [
195
+ { viewId: "suppliers", key: "公司名称", name: "" },
196
+ { viewId: "products", key: "订购量", name: "" }
197
+ ]
198
+ }]);
199
+ ```
200
+
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 关联不到的成员不出现、空交点为空白、展开顺序以关联结果为准。
202
+
203
+ ### 2.9 条件格式 / 冻结 / 图片
204
+
205
+ ```javascript
206
+ sheet.setFreeze(1, 0) // 冻结首行
207
+ .setConditionalFormat("C2:C10000", [ // 区域写大,GcExcel 自动裁剪到数据区
208
+ { op: ">", value: "1000", backgroundColor: "#FFC7CE", fontColor: "#9C0006" },
209
+ { formula: "MOD(ROW(),2)=0", backgroundColor: "#EAF3FB" } // 斑马纹
210
+ ])
211
+ .bindImage("D2", reportField("v", "图片URL"), { context: "A2" });
212
+ ```
213
+
214
+ 条件格式 op 限 `> < >= <= == !=`,颜色必须 `#RRGGBB`;`value` 是公式片段——数字直接写,文本常量带引号(`value: "\"缺货\""`);多规则可用 `stopIfTrue: true` 命中即停。超链接按位置选形态:**静态格**(标题/说明/返回目录)用 `setHyperlink(cell, url, { tooltip? })`,GcExcel 渲染保留;**扩展区行级链接**必须用 `HYPERLINK()` 动态公式(worksheet 超链条目按固定 ref 定位,不随模板行复制——给扩展区格挂 setHyperlink 只有第一行有链接)。图片导出上限 1000 格,配合 `.setColumnWidthStretch(true)` 控制版式。
215
+
216
+ ### 2.9b 打印版式 / 静态资产 / 序号列
217
+
218
+ ```javascript
219
+ sheet.setImage("A1", "assets/logo.png") // 本地 logo/印章嵌入(png/jpg/gif,工程内路径)
220
+ .setValue("A2", "序号", { diagonalDown: true }) // 斜线表头
221
+ .setPrintTitleRows(2) // 打印/导出每页重复表头行
222
+ .setHeaderFooter({ footer: "&C第 &P 页 / 共 &N 页" }) // 页脚页码
223
+ .setDynamicFormula("A3", "ROW()-2", { context: "B3" }) // 序号列(父格是同行右侧维度)
224
+ .bindDimension("B3", reportField("v", "城市"), { context: "None" });
225
+ ```
226
+
227
+ 序号列是唯一允许"动态公式挂同行**右侧**父格"的形态;注意此时右侧维度要显式 `context: "None"`(或其真实父格),避免它按默认推断反挂序号格形成环。
228
+
229
+ ### 2.9c 排序 / 行级链接 / 涨跌着色(dev29 实证)
230
+
231
+ ```javascript
232
+ // "按销售额降序展示城市":排序放数据视图,模板展开顺序跟随数据视图返回顺序
233
+ var v = createCard(ChartType.DATA_GRID, "orders")
234
+ .bindDataset(DS)
235
+ .addRow(f("货主城市"))
236
+ .addMetric(f("应付金额", { aggrType: AggrType.SUM }))
237
+ .addSort(f("应付金额", { aggrType: AggrType.SUM, sortType: SortOrder.DESC }));
238
+
239
+ // 行级可点击链接:HYPERLINK 动态公式随扩展逐行复制(模板格超链接不会复制,别用)
240
+ sheet.setDynamicFormula("C2", 'HYPERLINK("https://bi.example.com/detail","查看")', { context: "A2" })
241
+ // 涨跌着色(正红↑负绿↓):条件数字格式即可,无需 setConditionalFormat
242
+ .bindMetric("D2", reportField("orders", "环比"), {
243
+ aggregate: "SUM", context: "A2",
244
+ numberFormat: '[Red]#,##0.00"↑";[Green]#,##0.00"↓";0.00'
245
+ });
246
+ ```
247
+
248
+ - 排序只能放数据视图(`addSort`),模板 props 里没有排序语法;聚合值排序用与 metric 相同的 `aggrType` + `sortType`。
249
+ - `HYPERLINK("url","文本")` 里的 URL 含点号不会被误判为数据字段(校验器已处理字符串字面量);URL 可用 `CONCATENATE("https://.../", B3)` 拼单元格值。
250
+ - `.setLimit(n)` 不影响 Pro 导出行数(复验),"前 N 名"需求用数据过滤或让用户在 BI 端配置,不要用 setLimit。
251
+
252
+ ### 2.10 计算放哪层:BI 优先,GcExcel 只做排版级(dev29 实证)
253
+
254
+ 优先级从高到低,能用上层就不要下沉:
255
+
256
+ | 计算类型 | 放哪 | 写法 |
257
+ |---|---|---|
258
+ | 行级公式(`[利润]/[销售额]`、CASE WHEN、日期函数) | 数据集普通计算字段 | `guands dataset calc-field add <dsId> --name .. --formula ..`,然后 `guanvis init --force` 刷新 schema 直接 `f("字段")` 引用 |
259
+ | 比率/均值/占比等**非可加**指标(`SUM(a)/COUNT(b)`) | 数据集聚合计算字段 | `--calc-type aggregation`;卡片/子视图直接 `f("字段")`(不写 aggrType),BI 按当前维度组自动重算 |
260
+ | 可加指标的小计/总计(SUM/COUNT/MAX...) | Pro 模板 | `bindSubtotal`/`bindGrandTotal` 三件套(§2.3) |
261
+ | **非可加**指标的小计/总计 | 每个展示粒度一个数据视图 | 明细=细粒度视图;小计=组粒度视图 + `context: 组维度格` + `filterBy: [{field, cell: 组维度格}]`;总计=无维度视图 + `context: "None", expansion: "none"` |
262
+ | 排版级算术(序号 `ROW()-n`、单格单位换算、`HYPERLINK` 拼链接) | GcExcel 动态公式 | `setDynamicFormula`,仅当 BI 查询无法逐格表达时使用 |
263
+
264
+ 三个实证反模式(实验踩过,数值都是错的):
265
+
266
+ - **对聚合计算字段用 `bindSubtotal`/`bindGrandTotal`**:GcExcel 把各明细行的比率求和(各城市单均之和 26661 vs 正确组内重算 62.42)。非可加指标的小计必须走"组粒度视图"。
267
+ - **模板 `aggregate: "COUNT"` 当计数用**:模板聚合作用在**视图返回行**上(明细行恒为 1,小计=组内行数),不是订单数。计数放子视图 zone(`f("订单ID", { aggrType: AggrType.COUNT })`),模板格用 `aggregate: "SUM"` 透传。
268
+ - **派生视图(组粒度/无维度)漏掉基视图的过滤**:明细视图有 `.addFilter(...)` 时,小计/总计视图必须加**同样的过滤**,否则同一行里比率与可加列口径不一致(dev29 实测:总计占比无过滤 1.0575 vs 同行过滤后口径 1.1202)。selector childFilters 会同时作用于全部子视图,无此问题;只有写死的 addFilter 需要手动复制。
269
+
270
+ ### 2.11 固定科目表 / 分列常量对比(利润表、资产负债表式)
271
+
272
+ ```javascript
273
+ // 每个对比列一个数据视图(addFilter 常量过滤),科目行全是固定行
274
+ var all = createCard(ChartType.DATA_GRID, "all").bindDataset(DS)
275
+ .addMetric(f("应付金额", { aggrType: AggrType.SUM }));
276
+ var north = createCard(ChartType.DATA_GRID, "north").bindDataset(DS)
277
+ .addMetric(f("应付金额", { aggrType: AggrType.SUM }))
278
+ .addFilter(filterField(DS, "货主地区", FilterType.EQ, ["华北"]));
279
+
280
+ var money = { aggregate: "SUM", context: "None", expansion: "none", numberFormat: "#,##0.00" };
281
+ sheet.setValue("A3", "一、营业收入", { bold: true })
282
+ .bindMetric("B3", reportField("all", "应付金额"), money) // 全国列
283
+ .bindMetric("C3", reportField("north", "应付金额"), money) // 华北列
284
+ .setValue("A5", "二、运营净额", { bold: true })
285
+ .setDynamicFormula("B5", "B3-B4", {}, { numberFormat: "#,##0.00" }) // 派生科目行
286
+ .setDynamicFormula("B6", "IF(B3=0,0,B4/B3)", {}, { numberFormat: "0.00%" }); // 比率行
287
+ ```
288
+
289
+ 要点(dev29 实证):
290
+ - **绑定级 `filter` option 不是常量过滤**——它是 checkout 保真通道(`F=view.field*Cell` 跨视图映射语法)。写 `filter: "字段=值"` 能过 pack,但后端导出直接 500(SearchEngine NPE)。分列对比一律"每列一个数据视图 + addFilter"。
291
+ - 固定单元格全用 `context: "None", expansion: "none"` 的 `bindMetric`;派生科目(净额/比率)用引用固定格的动态公式。
292
+ - 月份/季度桶**不要用 `granularity` 日期粒度**(zone 会生成后端查不到的 SUB_DATE 虚字段"月",pack 现已本地拒绝)——先加 `DATE_FORMAT([日期], 'yyyy-MM')` 计算字段再绑定,横向展开月份列 + `.addSort` 升序。
293
+
294
+ ### 2.12 筛选器联动 Pro
295
+
296
+ ```javascript
297
+ var sel = createSelector("产品筛选")
298
+ .setId("...")
299
+ .bindField(f("产品名称"))
300
+ .linkTo(0, "产品名称"); // 可联动目标序列包含 Pro 子视图(按 addDataView 顺序)
301
+ registerSelector(sel.build());
302
+ ```
303
+
304
+ 运行时走 childFilters,小计/总计随筛选重算。发布后可用 `guancli card preview <proId> --filter "字段 EQ 值"` 无头验证。
305
+
306
+ ## 3. 数据视图规则
307
+
308
+ - 每个 `addDataView(alias, cardBuilder)` 是一个 `DATA_GRID` 子卡:维度进 `addRow`,聚合值进 `addMetric`。
309
+ - **模板引用的每个字段都必须出现在对应子视图的查询区**(row/metric),否则 pack 拒绝。
310
+ - `bindDimension` 的字段必须是 DIM;数值列(METRIC)不能做维度绑定——需要按数值分组时先在数据集加计算字段转 STRING。
311
+ - 子视图 `.setLimit(n)` **不会**限制 Pro 导出行数,别用它裁数据。
312
+ - 聚合计算字段(schema 里 `calculationType: "aggregation"`)进 `addMetric` 时**不要**写 `aggrType`,BI 按视图维度自动聚合;配套的分层用法见 §2.10。
313
+ - **日期字段不要带 `granularity` 进 Pro 数据视图**(后端查不到派生的 SUB_DATE 字段,pack 已本地拒绝)——月/季分桶用 `DATE_FORMAT` 计算字段。
314
+ - 样式规范:主标题行高 32–36pt、表头 22–26pt、明细 18–22pt,标题 `verticalAlignment: "center"`;宽表/图片表优先 `.setColumnWidthStretch(true)`。
315
+
316
+ ## 4. 校验报错 → 修复
317
+
318
+ pack/publish 阶段(本地校验,改 DSL 即可):
319
+
320
+ | 报错关键词 | 原因 | 修复 |
321
+ |---|---|---|
322
+ | `context parent ... must be in its column or to the left` | 父格在子格右侧 | 纵向父格允许**任意行**(其列或左侧)——"汇总在上"(总计/小计在明细上方、标签挂下方维度)已实证合法;同行右侧仅序号列动态公式形态放行 |
323
+ | `must be a vertical parent in the upper-left area or a horizontal parent above in the same column` | 上父格不是横向维度,或横向父格不在同列上方 | 交叉表列头加 `expansion: "horizontal"` 且放子格正上方;层级分组只用左父格 |
324
+ | `subtotal parent ... must be a setContextText cell` / `must be on the same row to the left` | `bindSubtotal` 没挂同行左侧标签 | 按 §2.3 三件套补 `setContextText`,值与标签同行 |
325
+ | `redundantly filters the same view ... use context only` | 同数据视图写了 `filterBy` | 删掉 filterBy,同源层级/交叉只写 `context` |
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`),其它格不能当过滤键 |
332
+ | `unknown option "context"`(出现在 bindGrandTotal 上) | `bindGrandTotal` 传了 context | 删 context;总计只支持 `aggregate/numberFormat/style` |
333
+ | `unsupported aggregate "COUNTA"; supported aggregates: ...` | 聚合不在白名单 | 换 `SUM/COUNT/AVERAGE/MAX/MIN/PRODUCT/STDDEV/STDDEVP/VAR/VARP`;`AVG` 会自动规范化为 `AVERAGE` |
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 移到顶层维度 |
335
+ | `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` | 块规则违规 | 锚点放块左上角;小计移出块;外部格移出块矩形 |
336
+ | `dimension binding "X" ... must reference a dimension, got metaType=METRIC` | 数值列做维度 | 见 §3:换 DIM 字段或加计算字段 |
337
+ | `unknown data view "X"` / `unknown column "X" in data view` / `Field 'MISSING_X' ... was not found in dataset schema` | 模板引用字段没进子视图查询区,或视图别名拼错 | 把字段加进对应 `addDataView` 子卡的 row/metric;核对别名与 `dataSourceRelations.tableName` |
338
+ | `relations do not connect all joined data views into one graph` | 多源 JOIN 断图 | 补 relations 使所有视图连通 |
339
+ | `parent graph cycle`(环路径 `A1->B2->A2->B2`) | 外部模板父格循环依赖:值格省略 `C=` 被默认推断挂左侧标签,标签又显式挂回值格 | 给块内所有值格显式写 `C=锚点`;用 `guanvis report inspect <xlsx>` 看推断父格链 |
340
+ | `unknown option "X"` | 绑定 options 拼错 | 只用 `expansion/fillMode/context/countPerPage/filter/filterBy/numberFormat/style`(维度另有 `group/sort`,度量另有 `aggregate`) |
341
+
342
+ 发布后/线上阶段:
343
+
344
+ | 现象 | 原因 | 修复 |
345
+ |---|---|---|
346
+ | 导入成功但 `GET /api/card` 报"找不到相关卡片"或 `None.get` | Pro 卡没和引用它的 Page 同包发布;或卡 ID 不是"首字母+23 位 hex"形态 | Pro 必须与 Page 一起 pack/publish;ID 一律用 `guanvis genid` 生成 |
347
+ | 同包发布、ID 形态正确,Import succeeded 后 preview 仍持续"找不到相关卡片" | 该 ID 此前经历过"失败发布→成功导入"混合序列,后端残留坏状态(dev29 两次复现) | 不要调 payload:`guanvis genid` 换全新 card/page ID 重发,立即恢复 |
348
+ | `dynamic formula cannot reference data fields` 但公式里只有 URL/中文句号 | 旧版校验器把字符串字面量内的点号 token 误判为 `view.field`(已修复) | 升级 guanvis;`HYPERLINK("https://...","文本")` 等含点字符串现在直接可用 |
349
+ | 发布成功但导出 500 `SearchEngine` NPE | 绑定级 `filter` 写了自创语法(如 `"字段=值"`)——F= 是跨视图映射通道不是常量过滤 | 分列常量对比改"每列一个数据视图 + addFilter"(§2.11) |
350
+ | `field ... is a date-granularity zone field, which the Pro backend cannot query` | Pro 数据视图里的日期字段带了 `granularity` | 加 `DATE_FORMAT([日期],'yyyy-MM')` 计算字段并绑定它 |
351
+ | `unsupported option(s): filter`(bindGrandTotal 上) | `bindGrandTotal` 只接受 aggregate/numberFormat/style | 需要过滤的固定值格改 `bindMetric` + `context:"None", expansion:"none"`,过滤放数据视图 |
352
+ | `card preview` 结果残留 `{{...}}` | 模板表达式没被 GcExcel 识别(语法/字段/视图名错) | 用 `guanvis report inspect` 核对表达式;核对字段是否在子视图查询区 |
353
+ | 后端 500 `父格设置存在循环依赖` | 同 pack 表的 parent graph cycle(旧包未经过本地校验) | 重新 pack(本地环检测已覆盖),按上表修复 |
354
+ | 后端 500 嵌套 `LP(...)` | 旧包同源 filterBy | 删同源 filterBy 重发 |
355
+ | 导入报 `该应用具有 COMPLEX_REPORT_PRO,请先联系管理员开通...` | 目标环境未开 Pro license | 换有授权的环境或联系管理员,不是脚本问题 |
356
+ | 页面上 Pro 卡渲染空白但 preview xlsx 正常 | 前端渲染波动 | 稍候刷新或 `guanvis screenshot` 复核;以 preview xlsx 为准 |
357
+
358
+ ## 5. 编辑已有 Pro(checkout 闭环)
359
+
360
+ ```bash
361
+ guanvis checkout <pageId> -d ./work # 下载 templates/<cdId>.xlsx + 生成 createComplexReportPro 脚本(templateVersion 自增)
362
+ guanvis report inspect ./work/templates/<cdId>.xlsx # 看模板格/父格链/环检测(不打开 Excel)
363
+ guanvis report decompile ./work/templates/<cdId>.xlsx -o wb.js # 反编译为 Workbook DSL 或 patch 脚手架
364
+ ```
365
+
366
+ - 创建与编辑共用一套 Workbook DSL,仅基底不同:`createReportWorkbook()` 从零构建(`addSheet`),`createReportWorkbook(templatePath)` 基底增量编辑(`editSheet`);挂载入口统一 `.setWorkbook(workbook)`。
367
+ - decompile 全量可表达(冻结窗格、单元格比较/公式条件格式、序号列、阶梯布局都在覆盖内)→ 输出从零构建源码,把 card JS 的 `.setTemplate(...)` 换成 `.setWorkbook(workbook)`,模板即文本化,直接改 DSL。
368
+ - 模板含 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 }` 显式声明替换。插入行列后,后续操作坐标按移位后的最终位置书写。
369
+ - **多轮编辑防护**:基底编辑的发布结果 = 本地 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 插两次行)。
370
+ - 批量/样式增强(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 全部保留。
371
+ - **基底编辑最小形态**:
372
+
373
+ ```javascript
374
+ var workbook = createReportWorkbook("templates/<cdId>.xlsx")
375
+ .editSheet("Sheet1", function (sheet) {
376
+ sheet.setValue("A1", "新标题", { bold: true }); // 覆盖静态格
377
+ sheet.setDynamicFormula("D3", "AVERAGE(B3)", { context: "A3" }); // 新增模板格
378
+ sheet.bindMetric("E3", reportField("v", "金额"),
379
+ { aggregate: "SUM", context: "A3", overwrite: true }); // 替换已有模板格需显式 overwrite
380
+ });
381
+ // 需要新增 Sheet 时同一 workbook 上继续 .addSheet("说明", fn)
382
+ report.setWorkbook(workbook); // 与 .setTemplate() 二选一
383
+ ```
384
+
385
+ - 改完 `guanvis diff ./work` 看影响面(基底编辑的每条指令会以 `card.template["Sheet"].op[i]` 逐条列出)→ `pack` → 发布覆盖线上 Page 前必须向用户确认后 `publish --allow-overwrite`。
386
+ - **templateVersion 自动管理**:publish 成功后 CLI 自动把工程 JS 的 `.setTemplateVersion(N)` 改写为 N+1(服务端按 version 缓存模板附件,同版本重发模板变更会静默不生效)。若提示未找到该调用,下轮改模板前必须手动补 `.setTemplateVersion(N+1)`。
387
+
388
+ ## 6. 验证闭环(必须走完)
389
+
390
+ `pack` 通过、`preview` 有 xlsx **都不等于语义正确**——只有 GcExcel 才能验证模板展开。发布后必须:
391
+
392
+ ```bash
393
+ guancli card preview <proCardId> -o result.xlsx # 1. 导出真实渲染结果
394
+ # 2. 检查 result.xlsx:无残留 {{...}};行数/分组结构符合预期;抽 1-2 个小计值人工核对
395
+ guancli card preview <proCardId> --filter "字段 EQ 值" -o filtered.xlsx # 3.(有筛选联动时)验证 childFilters
396
+ ```
397
+
398
+ 需要视觉判断再用 `guanvis screenshot <pageId>`(消耗图像 token,不作默认步骤)。
@@ -0,0 +1,48 @@
1
+ # 页面目录与页面壳管理(guanvis dir / guanvis page)
2
+
3
+ 本文是 `guanvis dir`(页面目录树)与 `guanvis page rename/move/delete`(页面壳)的完整硬约束。改**页面内容**(卡片、布局、筛选器)不在本文范围,仍走 checkout + publish(见 `checkout-editing.md`)。
4
+
5
+ ## 1. 页面目录管理(guanvis dir)
6
+
7
+ `guanvis dir` 管的是**页面(PAGE)目录树**——发布时 `--page-parent-dir` / `.setParentDir()` 要传的那个 `dirId`。
8
+
9
+ ```bash
10
+ guanvis dir create --name "季度经营" --parent <父目录 dirId> # 输出的 dirId 可直接用于 publish --page-parent-dir
11
+ guanvis dir rename <dirId> --name "新名称" # 保留 dirId 与目录内容
12
+ guanvis dir move <dirId> --parent <新父目录 dirId> # 保留 dirId 与目录名
13
+ guanvis dir delete <dirId> # 不加 --yes 时只打印目标信息并取消
14
+ guanvis dir delete <dirId> --yes # 真正删除
15
+ ```
16
+
17
+ 硬约束:
18
+
19
+ - **`dir delete` 必须显式加 `--yes`**:不加时只打印目标目录(dirId、路径、父目录、是否为空)与警告,然后以「操作已取消」返回,不发删除请求。
20
+ - **删除目标必须是空目录**:只要还有子目录或页面就一律拒绝并列出内容,本命令**不提供任何级联删除开关**;先手动清空或把内容移走(页面用 `guanvis page move`,子目录用 `guanvis dir move`)。
21
+ - **目录删除是物理删除,不进回收站、不可恢复**——与页面删除(软删,进回收站)语义相反,不要把页面那边"删了还能捞回来"的经验套到目录上。删错只能重建目录再把内容搬回去,而目录权限配置搬不回来。
22
+ - **`--parent` 必填且不能用空串表示根目录**:根目录也有真实 `dirId`,用 `guancli page tree --type dir` 查。
23
+ - **根目录不可改名、移动或删除**,命令在写前直接拦截。
24
+ - **不能把目录移进它自己的子树**(成环):命令在写前检测并说清是成环,不依赖服务端那句看不出成因的 500。
25
+ - 四个子命令都做写前预检(目标存在、是目录不是页面)+ 写后回读目录树比对(名称/父目录/存在性),回读不一致一律判为失败。`rename` 与 `move` 走同一个服务端接口且 `name` 与 `parentDirId` 必须同时提交,CLI 会自动回填未改动的那个并确认它没被顺带改掉。
26
+ - 只读查看目录树用 `guancli page tree --type dir`,`guanvis dir` 不提供 `tree` 子命令。
27
+
28
+ ## 2. 页面本身的改名/移动/删除(guanvis page)
29
+
30
+ 下面三个命令只动页面这个"壳",保留 pgId。
31
+
32
+ ```bash
33
+ guanvis page rename <pgId> --name "月度销售看板" # 保留 pgId 与页面内容
34
+ guanvis page move <pgId> --parent-dir <dirId> # 换目录,保留 pgId
35
+ guanvis page delete <pgId> # 不加 --yes 时只打印目标信息并取消
36
+ guanvis page delete <pgId> --yes # 删除(页面上不能有卡片)
37
+ guanvis page delete <pgId> --yes --force # 连同页面上的卡片一起删除
38
+ ```
39
+
40
+ 硬约束:
41
+
42
+ - **`page delete` 必须显式加 `--yes`**:不加时只打印目标(pgId、名称、页面类型、目录路径、卡片数)与警告,然后以「操作已取消」返回,不发删除请求。
43
+ - **`--force` 不是二级确认**,它是后端 force 参数的直通,语义只有"连页面上的卡片一起删";单独给 `--force` 而不给 `--yes` 仍会被 `--yes` 闸门拦住。页面上有卡片却不加 `--force` 时,服务端会以 1004 拒绝,CLI 把它翻译成"还有 N 张卡片,请追加 --force"。
44
+ - **页面删除是软删**:页面连同卡片与权限进回收站——但 **CLI 不提供任何恢复命令**,要找回只能人工登录 BI 网页端,在回收站里找到该页面并还原。不要向用户承诺一条能撤销的命令。
45
+ - **被 SuperApp/门户引用的页面、概览页之类的特殊页面由服务端硬阻断**,`--force` 也绕不过;这类报错原样带出,需要先在 BI 网页端解除引用或改用对应入口删除。
46
+ - **`--parent-dir` 必填且不能用空串**:后端收到空值只回一句"目录不存在",CLI 在写前就拦下并提示根目录也有真实 `dirId`。
47
+ - **rename / move 是可逆操作,不需要 `--yes`**;改名超出服务端长度上限(实测 50 个字符)时后端会截断后仍返回成功,写后回读会把它判为失败并点明截断后的实际名称。
48
+ - 三个子命令都做写前预检(目标在页面树里存在、是页面不是目录——传目录 `dirId` 会被拦下并指回 `guanvis dir`)+ 写后回读比对(delete 查页面树里已消失,rename 查名称、move 查 `parentDirId`,并确认没顺带改掉另一个字段),回读不一致一律判为失败。