@finebi/finebi-cli 1.6.0 → 1.6.1

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 (101) hide show
  1. package/README.md +8 -4
  2. package/package.json +1 -1
  3. package/skills/finebi-cli-agent/references/command-workflows.md +1 -1
  4. package/skills/finebi-cli-agent/references/web-app.md +9 -5
  5. package/skills/finebi-cli-agent/scripts/finebi-cli/package.json +1 -1
  6. package/skills/finebi-cli-agent/scripts/finebi-cli/src/app.js +8 -49
  7. package/skills/finebi-cli-agent/scripts/finebi-cli/src/help.js +2 -1
  8. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/AGENTS.md +8 -5
  9. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/README.md +8 -2
  10. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/acceptance.md +67 -0
  11. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/auth-and-deploy.md +2 -2
  12. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/data-contract.md +38 -21
  13. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/design.md +43 -2
  14. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/filters.md +4 -0
  15. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/examples/cockpit.config.json +22 -0
  16. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/examples/retail.config.json +185 -0
  17. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/package-lock.json +2 -2
  18. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/package.json +3 -2
  19. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/scripts/dev-proxy.mjs +2 -1
  20. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/App.tsx +45 -20
  21. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/cockpit.css +3 -0
  22. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Chart.tsx +24 -15
  23. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/DataTable.tsx +12 -5
  24. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/FilterBar.tsx +16 -10
  25. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Insights.tsx +17 -0
  26. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/MonthPicker.tsx +32 -0
  27. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/OptionFilter.tsx +7 -0
  28. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/RangeFilter.tsx +9 -0
  29. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Widget.tsx +23 -12
  30. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/calendar-locales.ts +3 -0
  31. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Bar.tsx +6 -0
  32. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Line.tsx +6 -0
  33. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/charts/Pie.tsx +6 -0
  34. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/month-picker.css +1 -0
  35. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/client.ts +15 -10
  36. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/config.d.ts +1 -0
  37. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/config.js +44 -0
  38. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/types.ts +2 -2
  39. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/retail.css +13 -0
  40. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/presentation.ts +36 -0
  41. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/table-data.ts +47 -0
  42. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/widget-queries.ts +18 -0
  43. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/fixtures.mjs +28 -0
  44. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/index.html +16 -0
  45. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/main.tsx +149 -0
  46. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/test-page.css +4 -0
  47. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/browser/vite.config.mjs +13 -0
  48. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/bundle.test.mjs +19 -0
  49. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/cockpit.test.mjs +73 -0
  50. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/proxy.test.mjs +12 -0
  51. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/retail.test.mjs +31 -0
  52. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/runtime.test.mjs +1 -1
  53. package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/vite.config.ts +2 -0
  54. package/src/app.js +8 -49
  55. package/src/help.js +2 -1
  56. package/templates/web-app/AGENTS.md +8 -5
  57. package/templates/web-app/README.md +8 -2
  58. package/templates/web-app/docs/acceptance.md +67 -0
  59. package/templates/web-app/docs/auth-and-deploy.md +2 -2
  60. package/templates/web-app/docs/data-contract.md +38 -21
  61. package/templates/web-app/docs/design.md +43 -2
  62. package/templates/web-app/docs/filters.md +4 -0
  63. package/templates/web-app/examples/cockpit.config.json +22 -0
  64. package/templates/web-app/examples/retail.config.json +185 -0
  65. package/templates/web-app/package-lock.json +2 -2
  66. package/templates/web-app/package.json +3 -2
  67. package/templates/web-app/scripts/dev-proxy.mjs +2 -1
  68. package/templates/web-app/src/App.tsx +45 -20
  69. package/templates/web-app/src/cockpit.css +3 -0
  70. package/templates/web-app/src/components/Chart.tsx +24 -15
  71. package/templates/web-app/src/components/DataTable.tsx +12 -5
  72. package/templates/web-app/src/components/FilterBar.tsx +16 -10
  73. package/templates/web-app/src/components/Insights.tsx +17 -0
  74. package/templates/web-app/src/components/MonthPicker.tsx +32 -0
  75. package/templates/web-app/src/components/OptionFilter.tsx +7 -0
  76. package/templates/web-app/src/components/RangeFilter.tsx +9 -0
  77. package/templates/web-app/src/components/Widget.tsx +23 -12
  78. package/templates/web-app/src/components/calendar-locales.ts +3 -0
  79. package/templates/web-app/src/components/charts/Bar.tsx +6 -0
  80. package/templates/web-app/src/components/charts/Line.tsx +6 -0
  81. package/templates/web-app/src/components/charts/Pie.tsx +6 -0
  82. package/templates/web-app/src/components/month-picker.css +1 -0
  83. package/templates/web-app/src/finebi/client.ts +15 -10
  84. package/templates/web-app/src/finebi/config.d.ts +1 -0
  85. package/templates/web-app/src/finebi/config.js +44 -0
  86. package/templates/web-app/src/finebi/types.ts +2 -2
  87. package/templates/web-app/src/retail.css +13 -0
  88. package/templates/web-app/src/services/presentation.ts +36 -0
  89. package/templates/web-app/src/services/table-data.ts +47 -0
  90. package/templates/web-app/src/services/widget-queries.ts +18 -0
  91. package/templates/web-app/tests/browser/fixtures.mjs +28 -0
  92. package/templates/web-app/tests/browser/index.html +16 -0
  93. package/templates/web-app/tests/browser/main.tsx +149 -0
  94. package/templates/web-app/tests/browser/test-page.css +4 -0
  95. package/templates/web-app/tests/browser/vite.config.mjs +13 -0
  96. package/templates/web-app/tests/bundle.test.mjs +19 -0
  97. package/templates/web-app/tests/cockpit.test.mjs +73 -0
  98. package/templates/web-app/tests/proxy.test.mjs +12 -0
  99. package/templates/web-app/tests/retail.test.mjs +31 -0
  100. package/templates/web-app/tests/runtime.test.mjs +1 -1
  101. package/templates/web-app/vite.config.ts +2 -0
package/README.md CHANGED
@@ -44,12 +44,16 @@ finebi-cli <resource> <command> [options]
44
44
  Create and inspect a local dynamic web app project without connecting to FineBI:
45
45
 
46
46
  ```powershell
47
- finebi-cli app create --name sales-dashboard --path D:\work
47
+ finebi-cli app create --name sales-dashboard --path D:\work --layout cockpit
48
48
  finebi-cli app inspect --path D:\work\sales-dashboard
49
49
  ```
50
50
 
51
51
  `app create` copies the bundled `web-app` template, sets the project npm name, and records the template version in `public/app-config.json`. It rejects an existing destination and does not install dependencies or run project scripts. `app inspect` checks the local configuration and reports `remoteValidated: false`; use the generated project to perform the real login and data request checks in a browser.
52
52
 
53
+ `--layout` accepts `overview` (default), `workbench`, or the green `cockpit` layout. Cockpit shares the existing Cookie query and authentication flow, with configurable KPI/chart/table bindings, independent retries, and a custom month picker. The generated README and `examples/cockpit.config.json` describe the placeholder bindings; no business data or credentials are bundled. Published sources require `directoryEntryId` and a platform HTTP endpoint that supports directory permissions; failed queries never switch sources.
54
+
55
+ Use `--layout retail` for a warm paper-style retail brief with horizontal navigation, a KPI strip, and blue/orange charts. `examples/retail.config.json` contains placeholder retail bindings; see the generated design guide for explicit KPI and month-label formats. Creation leaves the project unbound and does not query a server.
56
+
53
57
  Run `finebi-cli capability refresh` manually when backend commands changed, the cache expired, or you want to force a reload.
54
58
 
55
59
  For dashboard component queries, use repeatable inline filters or a JSON file (mutually exclusive):
@@ -65,13 +69,13 @@ For temporary field selection, first read the target instance's field directory:
65
69
 
66
70
  ```powershell
67
71
  finebi-cli dashboard info --id <dashboardId>
68
- finebi-cli dashboard data --id <dashboardId> --entry-id <entryId> --dimension-ids '["<dId>"]' --filter "合同付款类型=分期付款"
72
+ finebi-cli dashboard data --id <dashboardId> --entry-id <entryId> --dimension-ids '["<dimensionDId>","<measureDId>"]' --filter "合同付款类型=分期付款"
69
73
  finebi-cli dashboard data --id <dashboardId> --entry-id <entryId> --dimension-ids '[]'
70
74
  ```
71
75
 
72
- Use `entries[].queryFields[].dId` where `selectable=true`; this is a component-local key, not a dataset `fieldId` or field name. `queryFields` includes disabled fields and reports their saved `used` flag. Omit `--dimension-ids` to keep the saved selection. An explicit `[]` removes selectable dimensions while retaining summary/chart metrics; a detail table returns no columns or rows. Existing filters remain effective. Empty output columns do not imply that native pagination totals are zero.
76
+ Use `entries[].queryFields[].dId` where `selectable=true`; this is a component-local key, not a dataset `fieldId` or field name. `queryFields` includes disabled fields and reports their saved `used` flag. Omit `--dimension-ids` to keep the saved selection. The list is a complete whitelist of dimensions and measures: unlisted selectable fields are disabled. Include measure dIds explicitly to keep their output; selecting only measures disables selectable dimensions. An explicit `[]` disables all selectable dimensions and measures; a detail table returns no columns or rows. Existing filters remain effective. Empty output columns do not imply that native pagination totals are zero.
73
77
 
74
- For a published dashboard, pass the same `--directory-entry-id` to `dashboard info` and `dashboard data`, and use dIds from that source. Installing this npm package updates only the client and bundled Skill. These commands require a compatible server plugin and platform Provider; the plugin directly depends on the dimension-selection and published-source APIs. Refresh capabilities after the server update.
78
+ For a published dashboard, pass the same `--directory-entry-id` to `dashboard info` and `dashboard data`, and use dIds from that source. Installing this npm package updates only the client and bundled Skill. These commands require a server plugin and platform Provider supporting the complete dimension-and-measure whitelist; the plugin directly depends on the dimension-selection and published-source APIs. Refresh capabilities after the server update.
75
79
 
76
80
  ### Multiple environments
77
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finebi/finebi-cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "FineBI dynamic capability command line client.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -118,7 +118,7 @@
118
118
 
119
119
  ### 7.3 Dashboard 与 Widget
120
120
 
121
- 查询仪表板组件时,先从同一来源的 `dashboard info` 读取目标条目的 `queryFields`,用 `selectable=true` 的 `dId` 构造 `dashboard data --dimension-ids '["<dId>"]'`。这些 ID 是组件内部键,不是数据集 fieldId;目录包含关闭的字段,`used` 表示保存状态。省略参数沿用配置,`[]` 关闭全部可选维度;汇总组件的指标保留,明细表按列选择。可同时传 `--filter` 或 `--filters-file`;选择不清除过滤、不保存配置。插件直接依赖支持维度选择和发布来源查询的 Provider;编译构件与部署平台需具备相同接口契约。字段目录、空列表与兼容语义见 `docs/dashboard-command-design.md` 的查询契约。
121
+ 查询仪表板组件时,先从同一来源的 `dashboard info` 读取目标条目的 `queryFields`,用 `selectable=true` 的 `dId` 构造 `dashboard data --dimension-ids '["<dimensionDId>","<measureDId>"]'`。这些 ID 是组件内部键,不是数据集 fieldId;目录包含关闭的字段,`used` 表示保存状态。省略参数沿用配置;列表是维度和指标的完整白名单,未列出的可选字段全部关闭。只传维度会关闭未选指标,只传指标会关闭全部可选维度;`[]` 关闭全部可选维度和指标,明细表无列及数据行。可同时传 `--filter` 或 `--filters-file`;选择不清除过滤、不保存配置。插件直接依赖支持维度与指标完整白名单选择和发布来源查询的 Provider;编译构件与部署平台需具备相同接口契约。字段目录、空列表与兼容语义见 `docs/dashboard-command-design.md` 的查询契约。
122
122
 
123
123
  查询目录发布版时,从 `dashboard search` 的候选位置选定 `locations[].entryId`,对 `dashboard info` 和 `dashboard data` 都携带同一 `--directory-entry-id <directoryEntryId>`。先从该来源的 `info` 获取组件实例 `entries[].entryId` 和控件实际 `filterName`,再查询该组件。检查响应 `source=PUBLISHED_DIRECTORY` 及 `directoryEntryId`;普通来源为 `MY_ANALYSIS`。目录入口不能当作组件实例 ID,平台不支持或目录授权失败时不得丢弃入口参数后重试普通看板。
124
124
 
@@ -2,12 +2,16 @@
2
2
 
3
3
  用户要求生成动态页面、HTML看板或FineBI Web应用时,先选择同一CLI安装的本地 `app create`,再按生成项目AGENTS.md开发。创建模板不需要连接;读取/绑定FineBI资源才检查认证和实时命令help。
4
4
 
5
- 1. `finebi-cli app create --name <name> --path <parent>` 创建工程,不安装/运行/发布。
6
- 2. 用dashboard search/info/data确认唯一来源、板内entryId、结果字段dId、真实控件filterName;样本仅用于理解和测试。
7
- 3. 在public/app-config.json填写非敏感绑定。图表库和UI组件库按页面需求选择,默认实现及替换约定见生成项目的docs/design.md。请求、认证、解析复用模板src/finebi;业务代码按项目指南开发。
5
+ 1. `finebi-cli app create --name <name> --path <parent> [--layout retail]` 创建工程,不安装/运行/发布。米白横向经营简报可选 retail,绿色驾驶舱可选 cockpit;默认 overview,另有 workbench。
6
+ 2. 用dashboard search/info/data确认唯一来源、板内entryId、结果字段dId、真实控件filterName;按生成项目 docs/acceptance.md 记录指标口径、单位/时区、筛选范围和数据完整性。样本仅用于理解和测试。
7
+ 3. 按生成项目 docs/design.md 确定首屏、导航、图表分组和风格差异,在public/app-config.json填写非敏感绑定。先跑通最小真实查询与筛选链路,再扩展整页。图表库和UI组件库按页面需求选择;请求、认证、解析复用模板src/finebi。
8
8
  4. `finebi-cli app inspect --path <project>`只校验本地配置,不能证明字段/权限在远端存在。按项目README配置开发身份,再安装、启动、测试与构建。
9
- 5. 浏览器验收实际请求、空/错误/权限状态、图表与刷新;生产部署必须用访问者Cookie,不携带开发身份。
9
+ 5. 按生成项目 docs/acceptance.md 分别完成自动测试、构建、test:browser 合成数据验收和真实页面验收,检查整页及窄屏,记录 CLI、开发身份、生产 Cookie 各自的已验证与未验证项。生产部署必须用访问者Cookie,不携带开发身份。
10
10
 
11
- 内置CLI同样通过 `node "<skill-dir>/scripts/finebi-cli/src/index.js" app ...` 调用,不走动态业务runner。生产页面不调用CLI。禁止将开发时查到的业务数据或token写入HTML/JS/config;无筛选控件不能伪造服务端过滤。pageLimit不是分页,hasNext和warnings必须呈现。v1不提供发布上传/目录来源/服务端续页,先说明缺口,不发明命令。
11
+ 内置CLI同样通过 `node "<skill-dir>/scripts/finebi-cli/src/index.js" app ...` 调用,不走动态业务runner。生产页面不调用CLI。禁止将开发时查到的业务数据或token写入HTML/JS/config;无筛选控件不能伪造服务端过滤。pageLimit不是分页,hasNext和warnings必须呈现。不提供发布上传或服务端续页。目录来源显式配置 directoryEntryId,由原生 reportEntryId 透传;目标平台需支持 HTTP 目录权限预检与 MOUNT 查询,不能失败后回退“我的分析”。
12
+
13
+ cockpit 的 examples/cockpit.config.json 仅包含占位绑定,必须替换成真实资源和结果 fieldId;多模型显式 datasetId。布局含可配置 KPI、多系列趋势、分类对比、通栏表格及可选观察/口径。年月为自定义十二月/年份网格面板,选中只改 YYYY-MM 草稿。表格搜索、精确排序、筛选、分页、CSV 只处理已加载结果;观察不内置销售公式。widgets[].filterNames 来自真实映射,[] 表示不传覆盖,省略由源看板决定,不宣称未知映射为全局联动。请求/认证仍复用 src/finebi,不能复制个人 CLI 子进程后端。
14
+
15
+ retail 的 examples/retail.config.json 提供零售场景占位绑定,字段与口径须按同一来源核实;展示格式见生成项目 docs/design.md。不要根据原组件标题猜指标含义,也不要把记录数自动称为去重门店数。真实业务响应和分析结论不进入分发模板。
12
16
 
13
17
  模板内置文本、单选/多选、数值选择、日期及日期区间、年月季度、年月区间和已配置时间标签;草稿统一应用,恢复默认与筛选空值分开。配置、状态、请求编码和代理校验共用 src/finebi/filters.js。数值范围、滑块、树/级联和动态候选项暂不支持,不得换成其他类型绕过检查。绑定前核实源控件类型和作用范围,候选项不得冒充完整数据。通用约定、完整支持矩阵、值格式及扩展步骤见生成项目 docs/filters.md。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finebi/finebi-cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "private": true,
5
5
  "description": "FineBI dynamic capability command line client.",
6
6
  "license": "UNLICENSED",
@@ -26,6 +26,7 @@ export async function createApp(args, context = {}) {
26
26
  if (!parentPath) {
27
27
  return invalid("app.create", "path must be an existing parent directory.");
28
28
  }
29
+ if (options.value.layout && !['overview','workbench','cockpit','retail'].includes(options.value.layout)) return invalid('app.create','layout must be overview, workbench, cockpit or retail.');
29
30
  const parent = resolve(parentPath);
30
31
  const destination = resolve(parent, name);
31
32
  if (dirname(destination) !== parent) {
@@ -52,6 +53,8 @@ export async function createApp(args, context = {}) {
52
53
  return invalid("app.create", error.code === "EEXIST" ? `Destination already exists: ${destination}` : `Cannot create destination: ${error.message}`);
53
54
  }
54
55
  await copyTemplateContents(templateRoot, destination);
56
+ if(options.value.layout) config.layout=options.value.layout;
57
+ if(config.layout==='cockpit') config.insights=true;
55
58
  await mkdir(resolve(destination, "public"), {recursive: true});
56
59
  await writeFile(resolve(destination, "package.json"), `${JSON.stringify({...templatePackage, name}, null, 2)}\n`, "utf8");
57
60
  await writeFile(resolve(destination, "public", "app-config.json"), `${JSON.stringify({...config, templateVersion: templatePackage.version}, null, 2)}\n`, "utf8");
@@ -108,11 +111,14 @@ async function inspectApp(args) {
108
111
  if (!(await stat(project)).isDirectory()) return invalid("app.inspect", "path must point to a project directory.");
109
112
  const configPath = resolve(project, "public", "app-config.json");
110
113
  const config = JSON.parse(await readFile(configPath, "utf8"));
111
- const errors = await validateConfig(config);
114
+ const {configErrors}=await import('../templates/web-app/src/finebi/config.js');
115
+ const errors = configErrors(config);
112
116
  if (errors.length) return failure("app.inspect", cliError("INVALID_ARGUMENT", {path: "public/app-config.json", message: errors.join(" ")}));
113
117
  return success("app.inspect", {
114
118
  path: project,
115
119
  dashboardId: config.dashboardId,
120
+ layout: config.layout,
121
+ directoryEntryId: config.directoryEntryId || null,
116
122
  widgets: config.widgets.length,
117
123
  templateVersion: config.templateVersion || null,
118
124
  remoteValidated: false,
@@ -122,59 +128,12 @@ async function inspectApp(args) {
122
128
  }
123
129
  }
124
130
 
125
- async function validateConfig(config) {
126
- const errors = [];
127
- if (!config || typeof config !== "object" || Array.isArray(config)) return ["configuration must be a JSON object."];
128
- const allowedConfigFields = new Set(["schemaVersion", "title", "basePath", "layout", "dashboardId", "maxRows", "pageSize", "widgets", "filters", "templateVersion"]);
129
- for (const field of Object.keys(config)) if (!allowedConfigFields.has(field)) errors.push(`unsupported config field: ${field}.`);
130
- if (config.schemaVersion !== "1") errors.push("schemaVersion must be \"1\".");
131
- if (!nonEmptyString(config.title)) errors.push("title is required.");
132
- if (!nonEmptyString(config.basePath) || !/^\/(?!\/)[\w/-]+$/.test(config.basePath)) errors.push("basePath must be a same-origin path.");
133
- if (!["overview", "workbench"].includes(config.layout)) errors.push("layout must be overview or workbench.");
134
- if (!Number.isInteger(config.maxRows) || config.maxRows < 1 || config.maxRows > 10000) errors.push("maxRows must be an integer from 1 to 10000.");
135
- if (!Number.isInteger(config.pageSize) || config.pageSize < 1 || config.pageSize > 100) errors.push("pageSize must be an integer from 1 to 100.");
136
- if (typeof config.dashboardId !== "string" || !config.dashboardId.trim()) {
137
- if (Array.isArray(config.widgets) && config.widgets.length === 0) return ["project is not bound: dashboardId and widgets must be configured."];
138
- errors.push("dashboardId is required.");
139
- }
140
- if (!Array.isArray(config.widgets) || config.widgets.length === 0) {
141
- errors.push("widgets must be a non-empty array.");
142
- return errors;
143
- }
144
- for (const [index, widget] of config.widgets.entries()) {
145
- if (!widget || typeof widget !== "object") {
146
- errors.push(`widgets[${index}] must be an object.`);
147
- continue;
148
- }
149
- const allowedWidgetFields = new Set(["entryId", "name", "type", "datasetId", "categoryFieldId", "valueFieldId"]);
150
- for (const field of Object.keys(widget)) if (!allowedWidgetFields.has(field)) errors.push(`widgets[${index}].${field} is unsupported.`);
151
- for (const field of ["entryId", "name", "type"]) {
152
- if (typeof widget[field] !== "string" || !widget[field].trim()) errors.push(`widgets[${index}].${field} is required.`);
153
- }
154
- if (!["table", "bar", "line", "pie", "kpi"].includes(widget.type)) errors.push(`widgets[${index}].type is unsupported.`);
155
- if (widget.type && widget.type !== "table" && !nonEmptyString(widget.valueFieldId)) errors.push(`widgets[${index}].valueFieldId is required for ${widget.type}.`);
156
- if (widget.type && !["table", "kpi"].includes(widget.type) && !nonEmptyString(widget.categoryFieldId)) errors.push(`widgets[${index}].categoryFieldId is required for ${widget.type}.`);
157
- for (const field of ["datasetId", "categoryFieldId", "valueFieldId"]) {
158
- if (widget[field] !== undefined && !nonEmptyString(widget[field])) errors.push(`widgets[${index}].${field} must be a non-empty string.`);
159
- }
160
- }
161
- try {
162
- const {validateFilterBindings} = await import("../templates/web-app/src/finebi/filters.js");
163
- validateFilterBindings(config.filters);
164
- } catch (error) {errors.push(error.message);}
165
- return errors;
166
- }
167
-
168
- function nonEmptyString(value) {
169
- return typeof value === "string" && value.trim().length > 0;
170
- }
171
-
172
131
  function parseOptions(args, command) {
173
132
  const value = {};
174
133
  for (let index = 0; index < args.length; index++) {
175
134
  const option = args[index];
176
135
  if (option === "--json") continue;
177
- if (option !== "--name" && option !== "--path") return {ok: false, error: invalid(command, `Unsupported option: ${option}`)};
136
+ if (!["--name","--path",...(command==='app.create'?['--layout']:[])].includes(option)) return {ok: false, error: invalid(command, `Unsupported option: ${option}`)};
178
137
  const key = option.slice(2);
179
138
  const next = args[++index];
180
139
  if (!next || next.startsWith("--")) return {ok: false, error: invalid(command, `${option} requires a value.`)};
@@ -106,9 +106,10 @@ const BUILT_IN_RESOURCES = {
106
106
  {
107
107
  name: "create",
108
108
  summary: "Copy the bundled web app template into a new project directory.",
109
- usage: "finebi-cli app create --name <name> --path <parent>",
109
+ usage: "finebi-cli app create --name <name> --path <parent> [--layout overview|workbench|cockpit|retail]",
110
110
  options: [
111
111
  {name: "--name", type: "string", description: "Simple lowercase npm project name."},
112
+ {name: "--layout", type: "string", description: "Page layout: overview (default), workbench, cockpit or retail."},
112
113
  {name: "--path", type: "string", description: "Existing parent directory; relative paths resolve from the current directory."},
113
114
  ],
114
115
  details: ["Creates a local project only; it does not connect to FineBI or run install/build scripts."],
@@ -2,14 +2,16 @@
2
2
 
3
3
  ## 开发顺序
4
4
 
5
- 1. 用 FineBI CLI 搜索并读取 dashboard info/data,确认看板实例 entryId、queryFields.dId、真实控件 filterName 和可访问来源。
5
+ 1. 用 FineBI CLI 搜索并读取 dashboard info/data,确认唯一来源、看板实例 entryId、queryFields.dId 和真实控件 filterName。按[验收文档](docs/acceptance.md#1-绑定与设计准备)记录指标公式/聚合、单位、时间粒度与时区、筛选作用范围及数据完整性;必要时读取 bean,未知项明确标记,不按组件标题猜语义。
6
6
  2. 阅读 [数据契约](docs/data-contract.md)、[筛选](docs/filters.md)、[认证部署](docs/auth-and-deploy.md),再修改 public/app-config.json。
7
- 3. 从 overview 或 workbench 布局开始,按页面需求选择图表库和UI组件库;默认ECharts和Ant Design筛选控件均可替换,约定见[页面骨架](docs/design.md)。业务转换放 src/services,页面放 src/pages 或 App.tsx。通用请求与原生解析集中在 src/finebi,避免页面散落 fetch 或重复协议实现。
8
- 4. 使用真实接口开发;样本仅用于 tests,禁止将开发阶段查询数据写入正式页面、配置或回退数据。
9
- 5. npm test、npm run build,再通过浏览器核对请求、字段、图表、刷新和权限状态。构建成功不等于认证或取数成功。
7
+ 3. 按[设计选择](docs/design.md#设计选择与信息组织)写出页面面向谁、首屏重点、导航、图表分组和信息密度,遵循用户已确认的方向继续开发。新风格说明与已有布局在结构、层级和视觉上的区别。从 overview、workbench、cockpit 或 retail 起步,示例配置仅含占位绑定。
8
+ 4. 先跑通一个指标、一张图和一个真实控件(源看板有控件时)的查询、展示、应用与恢复默认,再扩展整页。使用真实接口开发;合成样本仅用于 tests,禁止将开发查询数据写入正式页面、配置或回退数据。
9
+ 5. 业务转换放 src/services,页面放 src/pages 或 App.tsx,通用请求与原生解析复用 src/finebi。改动共享组件时检查其他布局,不为换风格复制查询、认证或筛选编码。
10
+ 6. 按[分层验收](docs/acceptance.md)执行 npm test、npm run build、浏览器冒烟及真实绑定验收。检查整页和关键状态,分别记录 CLI、开发身份、生产 Cookie 的验证结论;构建成功不等于认证、取数或视觉验收通过。
10
11
 
11
12
  ## 数据规则
12
13
 
14
+ - 用 CLI 指定 `--dimension-ids` 时,必须同时列出需要的维度和指标;只传维度会关闭未选指标。模板目前不提供字段选择配置,浏览器查询沿用保存配置;完整语义见[数据契约](docs/data-contract.md#字段选择)。
13
15
  - 先检查 normalization、warnings、range,再选择唯一 dataset;多模型需显式 datasetId。
14
16
  - display 用于显示;计算仅使用 AVAILABLE 的 value,保留 DECIMAL_STRING 精度。禁止从百分比/千分位/单位文本恢复数值。受限值、未知值不能补零。
15
17
  - 不按名称猜字段、不累加 UNKNOWN 行、不从首页估算全量 KPI。KPI 示例仅消费单行结果。
@@ -21,7 +23,8 @@
21
23
 
22
24
  - 开发凭证只存在服务端环境 FINEBI_DEV_* 或内存;禁止 VITE_* 凭证、前端 localStorage token、提交 .env 和认证日志。
23
25
  - 生产应用使用同源当前用户 Cookie。开发代理不属于构建产物;不要把它部署为共享多用户服务。
24
- - v1 不支持目录发布来源、交叉表、服务端续页、全局排序或静态托管发布;遇到需求先说明缺口。
26
+ - 目录来源配置 directoryEntryId,原生请求保留 reportEntryId;需目标平台 HTTP 接口支持目录权限预检与 MOUNT 查询,不得失败后回退到分析主题。不支持交叉表、服务端续页、全局排序或托管发布命令。
25
27
  - 图表按生命周期销毁;异步请求取消且旧响应不得覆盖新条件。失败状态不得展示过期数据充当最新结果。
28
+ - 验收证据、截图和临时脚本放项目 temp/,不随包发布。交付记录已验证/失败/未验证项、运行方式、模板版本和业务绑定范围;模板维护流程由 CLI 源仓库负责,本项目无需其脚本才能开发和验收。
26
29
 
27
30
  项目创建不会安装依赖、启动服务、初始化 Git 或发布,按用户授权范围执行后续步骤。
@@ -8,11 +8,11 @@
8
8
  2. 填写 `public/app-config.json`。`dashboardId` 来自目标看板;`widgets[].entryId` 使用板内实例 ID;图表 `categoryFieldId/valueFieldId` 使用本次 `dashboard info` 的结果字段 dId。相同实例可以有不同展示,但 KPI 必须对应单行查询结果。
9
9
  3. 在当前进程设置 `FINEBI_DEV_BASE_URL`(完整 decision 地址)和 `FINEBI_DEV_USERNAME/FINEBI_DEV_PASSWORD`,或 `FINEBI_DEV_TOKEN`。不要把值写入 VITE_* 或 app-config。
10
10
  4. `npm run dev`,访问 `http://localhost:4173`。开发代理仅监听本机,自动以开发身份登录,凭证不返回浏览器。
11
- 5. `npm test`、`npm run build`。`npm run preview` 只展示 dist,不启用开发身份代理;真实生产取数需要同源 FineBI 路径。
11
+ 5. 按[开发与验收](docs/acceptance.md)执行 `npm test`、`npm run build`、`npm run test:browser`,再验证真实绑定。浏览器验收页使用合成数据,不连接 FineBI。`npm run preview` 只展示 dist,不启用开发身份代理;真实生产取数需要同源 FineBI 路径。
12
12
 
13
13
  ## 配置
14
14
 
15
- `layout` 为 `overview`(顶部概览)或 `workbench`(侧栏工作台)。`maxRows` 默认 1000,上限 10000;`pageSize` 是本地展示页大小,1–100。
15
+ `layout` 为 `overview`(顶部概览)、`workbench`(侧栏工作台)、`cockpit`(绿色经营驾驶舱)或 `retail`(米白经营简报)。可用 `finebi-cli app create --name my-retail --path <parent> --layout retail` 创建;默认配置仍为空绑定,不会请求示例资源。`maxRows` 默认 1000,上限 10000;`pageSize` 是本地展示页大小,1–100。
16
16
 
17
17
  ```json
18
18
  {
@@ -27,4 +27,10 @@
27
27
 
28
28
  默认组件 type 支持 table、kpi、bar、line、pie;多模型时指定 datasetId。配置不包含数据或任何凭证。协议和限制见 [数据契约](docs/data-contract.md)、[筛选与联动](docs/filters.md)、[认证与部署](docs/auth-and-deploy.md)。布局、图表库和UI组件库的选型与替换约定见 [页面骨架](docs/design.md)。
29
29
 
30
+ cockpit 的配置骨架在 [examples/cockpit.config.json](examples/cockpit.config.json),仅包含占位绑定。替换所有 `REPLACE_*`,再复制到 `public/app-config.json`;可删除不需要的组件,KPI 数量也可调整。`insights:true` 开启当前查询观察,`dataNotes` 补充口径。多系列 `series`、表格字段/本地筛选和控件作用映射见 [页面骨架](docs/design.md)。示例中的 `filterNames:[]` 表示不覆盖控件,必须按真实映射配置,不能直接当成业务默认。
31
+
32
+ retail 的 [examples/retail.config.json](examples/retail.config.json) 提供零售分析绑定骨架:四项 KPI、趋势、门店 TOP10、区域/品类、门店类型和明细。采用横向导航、米白背景和蓝橙图表,不依赖侧栏。示例不含业务数据,`REPLACE_*`、模型选择、时区及筛选作用范围必须核实替换;普通分析来源需删除目录入口占位字段。金额与百分比、毫秒月份的显式显示配置见[页面骨架](docs/design.md)。
33
+
34
+ 目录发布看板在配置中增加 `directoryEntryId`,使用与该入口匹配的 dashboardId/实例。模板将其原样作为 `reportEntryId` 传给 Cookie 查询接口及开发代理;目标平台需同时支持目录权限预检和 MOUNT 查询,不能只支持 CLI 目录查询。错误独立显示,不回退“我的分析”。
35
+
30
36
  筛选先编辑、再点击“应用筛选”;“恢复全部默认”重新查询看板保存条件。模板支持文本、单选/多选、数值选择和日期类控件,完整类型、空值语义与配置示例以[筛选契约](docs/filters.md)为准;`app inspect` 仅检查本地配置,绑定后仍需验证真实条件生效。
@@ -0,0 +1,67 @@
1
+ # 开发与验收
2
+
3
+ 本文件用于生成项目的独立验收,无需 CLI 源仓库。测试夹具、真实开发身份和生产访问者身份分别记录,不能互相代替。协议以[数据契约](data-contract.md)、[筛选](filters.md)和[认证部署](auth-and-deploy.md)为准。
4
+
5
+ ## 1. 绑定与设计准备
6
+
7
+ 先记录以下最小清单,证据与临时文件放项目 `temp/`;不记录密码、token 或 Cookie。
8
+
9
+ | 项目 | 核实内容 |
10
+ |---|---|
11
+ | 来源 | CLI profile、目标地址、dashboardId、目录入口或分析主题;板内实例 entryId |
12
+ | 字段与指标 | queryFields.dId、选定 datasetId、维度/指标、公式或聚合、单位;记录数不能自动解释为去重对象数 |
13
+ | 时间 | 时间粒度、原值编码、显示时区;日期条件与时间戳轴标签分开核实 |
14
+ | 筛选 | 真实控件名、类型、默认行为、候选项来源、组件作用范围;无源控件时不添加服务端筛选 |
15
+ | 范围 | 返回行数、hasNext、normalization、warnings;不根据首页推算全量 |
16
+ | 设计 | 使用者和主要决策、首屏重点、导航/分组/密度,与既有布局的区别 |
17
+
18
+ 先完成一个指标、一张图,以及存在时的一个真实筛选:源结果 → 页面值 → 应用条件 → 恢复保存默认。证据不足时说明未知项,继续不依赖该项的工作。
19
+
20
+ ## 2. 本地自动检查
21
+
22
+ 1. 首次安装使用 `npm ci`。
23
+ 2. `npm test` 验证已覆盖的数据、配置、认证、交互逻辑和分包行为。
24
+ 3. `npm run build` 完成类型检查与生产构建。
25
+ 4. `npm run test:browser` 启动独立浏览器验收页,打开终端给出的本机地址。选择布局后运行检查;按第 3 节调整浏览器视口并重复运行。页面必须显示 PASS,FAIL 或未完成不能视为通过。
26
+
27
+ 浏览器验收页只使用 `tests/browser/` 中的合成响应,复用实际 App、查询编码、适配器和图表组件;不读取 `.env.local`、不使用开发代理、不访问 FineBI。它检查图形实际绘制、运行错误、筛选草稿/应用/恢复、目录入口与组件作用范围、空/部分/失败状态、单项重试和组件边界。截图判断、真实权限、生产部署和未覆盖控件仍需人工或对应专项验证。
28
+
29
+ 测试页和夹具不得复制到 public/ 或生产配置。`npm run build` 只构建正式入口;浏览器测试服务仅在回环地址使用,完成后停止。
30
+
31
+ ## 3. 浏览器视觉与交互
32
+
33
+ 检查实际应用整页,包括首屏、图表中段、最后一行和明细。推荐 1440、1024、390px 视口;记录实际宽度,不把桌面缩放当成小屏验证。共享组件改变时至少覆盖 overview、workbench、cockpit、retail;只改某布局样式时重点验证该布局及相邻断点。
34
+
35
+ - 正常/空结果、部分适配、截断、受限值、失败与重试有清楚状态,失败不保留旧值充当最新结果。
36
+ - 图表画布有实际内容,控制台没有新增运行错误或图表模块缺失告警;图例、轴标签、大数值和长标题不裁切。
37
+ - 检查实际组件数量,同时用奇数/偶数组合观察排列;无异常空洞、重叠和页面横向溢出。表格内部滚动允许存在。
38
+ - 逐个点击页内导航并滚动到页面底部,确认导航仍可操作、目标标题不被遮挡;retail 同时检查桌面单行与窄屏两行导航,以及返回经营概览。
39
+ - 草稿不触发查询,应用后核对请求及页面值;恢复默认按源保存条件重新查询。检查键盘焦点、窄屏控件、单组件失败与重试;有年月/区间等控件时验证其实际操作。
40
+ - 展开范围和适配说明,确认技术详情可读,影响业务判断的限制没有被隐藏。
41
+
42
+ 自动边界检查不能判断所有视觉问题。保存有上下文的截图,并人工检查留白、密度、文字和绘图质量。
43
+
44
+ ## 4. 真实取数与身份
45
+
46
+ | 验证层 | 必须记录的证据 | 不证明什么 |
47
+ |---|---|---|
48
+ | CLI | 同一 profile 的资源、字段与查询结果 | 浏览器身份或原生查询接口可用 |
49
+ | 开发页面 | 脱敏请求参数、真实返回、页面值;应用/恢复条件的前后对照 | 所有生产用户均有权限 |
50
+ | 生产 Cookie | 部署后的同源静态页面,目标访问者身份及目录入口下的取数、失效和无权限表现 | 其他账号、SSO 环境或部署拓扑也可用 |
51
+
52
+ 目录来源检查每次查询保留 reportEntryId。存在过滤作用范围差异时同时检查受影响和不受影响组件。不要仅用 HTTP 200、组件数量或 CLI 返回证明页面值正确。生产环境未提供时明确写“未验证”,不为完成验收新增部署或扩大权限。
53
+
54
+ ## 5. 交付记录
55
+
56
+ 记录应用路径/启动命令、CLI 与模板版本、绑定来源和口径、改动范围,以及下表。结果使用“通过 / 失败 / 未验证 / 不适用”,附原因和证据路径。
57
+
58
+ | 项目 | 结果 | 证据或限制 |
59
+ |---|---|---|
60
+ | 本地测试 / 类型检查 / 构建 | 待记录 | 命令、退出码与日志 |
61
+ | 浏览器夹具 | 待记录 | 布局、实际视口、PASS/FAIL 与截图 |
62
+ | 真实开发取数 / 筛选 / 恢复 | 待记录 | 请求和显示值对照,未覆盖控件 |
63
+ | 生产 Cookie / 权限 | 待记录 | 环境、身份范围;未部署注明未验证 |
64
+ | 整页视觉 / 键盘 | 待记录 | 桌面与小屏截图、人工检查结果 |
65
+ | 交付包 | 待记录 | 包含同一次构建的 dist;排除 .env*、temp、node_modules、凭证和抓取样本 |
66
+
67
+ 修复后重新执行受影响检查。既有项目不会随模板自动升级;记录采用的模板版本和本次应用的修复,保留业务绑定与本地凭证。
@@ -6,12 +6,12 @@ Vite开发插件读取服务端环境 `FINEBI_DEV_BASE_URL`、`FINEBI_DEV_USERNA
6
6
 
7
7
  代理在内存保存开发会话,只接受本机同源、带XMLHttpRequest标记的POST;白名单限制为配置看板及组件查询,不代理登录管理或任意接口。浏览器无需持有开发账号凭证。失效返回AUTH_REQUIRED;密码方式下一次显式重试重新登录,token方式需更新token并重启开发服务。不要向他人开放开发服务端口。
8
8
 
9
- 筛选仅允许配置中的控件名和单值数组:下拉值必须属于 options,date 值必须是有效的 YYYY-MM-DD 日历日期,不使用固定日期白名单。空日期通过省略参数恢复保存条件,代理拒绝空字符串和多值日期数组。
9
+ 代理使用共享筛选校验器校验配置与 refer,类型、编码、候选值和默认语义统一见[筛选契约](filters.md),不维护独立编码规则。
10
10
 
11
11
  ## 生产
12
12
 
13
13
  `npm run build` 输出dist,开发代理不会进入产物。将静态资源托管在与FineBI相同源的应用子路径,basePath配置为实际decision路径。用户先通过FineBI登录,查询携带当前Cookie,服务端按当前身份判断资源、数据和字段权限。
14
14
 
15
- 登录失效、HTTP403、查询失败分别显示状态,用户登录后点击重试。生产包不保存开发token、不调用CLI、不自动以开发者账号重登。`npm run preview`不注入身份,只用于产物展示;本版不提供部署上传/发布命令、跨域认证、嵌入SSO或目录发布来源查询。
15
+ 登录失效、HTTP403、查询失败分别显示状态,用户登录后点击重试。生产包不保存开发token、不调用CLI、不自动以开发者账号重登。`npm run preview`不注入身份,只用于产物展示;本版不提供部署上传/发布命令、跨域认证或生产嵌入式SSO接入。目录发布来源配置 directoryEntryId,需目标平台原生 HTTP 接口支持对应目录权限;身份仍是访问者 Cookie,详情见[数据契约](data-contract.md)。
16
16
 
17
17
  交付应检查dist无凭证,核对真实查询网络与页面;开发身份验证通过不能证明所有生产用户有权访问,发布目录权限需另行验证。
@@ -1,21 +1,38 @@
1
- # 数据契约与适配范围
2
-
3
- ## 输入与版本
4
-
5
- 浏览器调用 `/v5/api/dashboard/report/{dashboardId}/widgets/query`,传板内实例 widgetIds、includeWidgetBean=true、pageLimit、refer。原生结果仅在 src/finebi/normalize.ts 解析。输出 schemaVersion=1,与 CLI 标准化结果采用相同的字段、值状态与范围语义;v1 浏览器只覆盖 DETAIL 和 CHART,不等于 Java 适配器的全部覆盖。
6
-
7
- ## 消费顺序
8
-
9
- 检查 normalization(COMPLETE/PARTIAL/UNSUPPORTED)→ warnings → 选唯一 dataset → 按 columns 解释 rows[].cells → 按 range 说明范围。COMPLETE 只表示本次结构转换完整,不表示全集。多模型需指定 datasetId;UNKNOWN 行不得自动求和。
10
-
11
- columns 含 key、fieldId、name、type、encoding。key 仅在本次dataset内唯一;fieldId 是结果字段ID,不能当底层数据集字段ID。绑定使用fieldId,不使用可能重复的name。
12
-
13
- cells 含 value、display、state、reason:AVAILABLE 可用,NULL 确认空值,UNAVAILABLE 无法可靠解释,RESTRICTED 权限受限。表格优先显示 display,受限单元格统一显示“受限”。计算不能从 display 反解析数字;DECIMAL_STRING 保留上游值,JS Number 仅用于图形坐标且不能用于高精度金额计算。DATE 的 EPOCH_MILLIS_STRING 保留毫秒字符串,不擅自改变时区。日期元数据支持原生类型3、13、15、20;未知类型保守返回不可用。
14
-
15
- 浏览器适配器不能获知服务端 cellCharLimit,字符串不能据此承诺未经截断的数据库原文。图表可见数值不代表底层明细;数值型字符串不会被猜成图表指标。交叉表/分组表及缺少内联模型的图形明确为不支持;多模型、computedState 和缺列都会保留警告。
16
-
17
- ## 范围
18
-
19
- 明细表 range.total 采用有效原生row;PAGE是本次首页,pageLimit不提供续页。hasNext=true 原样保留并产生 NEXT_PAGE_UNSUPPORTED;即使返回数等于总数,也不覆盖这一状态。当前仅提供已加载结果的搜索、排序和分页,页面显示该范围。图表UNPAGED也不等于底层明细全量,可能存在Top N或聚合。
20
-
21
- 服务端返回空数组与缺失组件不同:前者可显示空数据,后者是 INVALID_RESPONSE。受限/失败/不支持结果不回退静态样本。
1
+ # 数据契约与适配范围
2
+
3
+ ## 输入与版本
4
+
5
+ 浏览器调用 `/v5/api/dashboard/report/{dashboardId}/widgets/query`,传板内实例 widgetIds、includeWidgetBean=true、pageLimit、refer。原生结果仅在 src/finebi/normalize.ts 解析。输出 schemaVersion=1,与 CLI 标准化结果采用相同的字段、值状态与范围语义;v1 浏览器只覆盖 DETAIL 和 CHART,不等于 Java 适配器的全部覆盖。
6
+
7
+ 配置 directoryEntryId 时请求增加 reportEntryId,开发代理校验来源与配置严格相等后透传。目标平台 HTTP 查询服务需同时支持目录权限预检与 MOUNT 模式;仅升级 CLI Provider 不代表该 HTTP 入口已支持。权限/版本错误直接呈现,不移除入口后重试。各展示组件独立请求,批量加载限制三路并发,单个组件失败不遮蔽其他结果;同一原生实例可有不同展示和筛选绑定。
8
+
9
+ ### 字段选择
10
+
11
+ 当前模板没有字段选择配置,浏览器请求不传 `dimensionSelections`,因此沿用组件保存的维度和指标配置。页面展示字段的绑定不等于服务端字段选择;用 CLI 验证自定义选择得到的结果,也不代表模板会自动使用同一选择。
12
+
13
+ 支持完整白名单语义的平台接口通过 `dimensionSelections` 按组件指定维度和指标的 `dId` 列表;CLI 对应参数为 `dashboard data --dimension-ids`。ID 从同一来源的 `dashboard info` 中目标实例的 `queryFields` 取得,只选 `selectable=true` 的字段,包括已关闭但仍可选择的字段;不能用字段名或底层数据集字段 ID 代替。
14
+
15
+ - 不传选择项时沿用保存配置。
16
+ - 显式列表是维度和指标的完整白名单,未列出的可选字段全部关闭;需要维度和指标同时输出时必须一起列出。
17
+ - 只传维度会关闭未选指标,只传指标会关闭全部可选维度;`[]` 关闭全部可选维度和指标,不保证各组件都有有效输出,明细表不返回列及数据行。
18
+ - 选择只影响本次查询,不保存配置、不清除过滤、不按列表顺序重排字段,也不能添加组件未配置的字段。
19
+
20
+ 这套语义要求目标平台 Provider/HTTP 查询接口支持维度和指标完整白名单;只升级 CLI 不会为模板增加字段选择能力。
21
+
22
+ ## 消费顺序
23
+
24
+ 检查 normalization(COMPLETE/PARTIAL/UNSUPPORTED)→ warnings → 选唯一 dataset → 按 columns 解释 rows[].cells → 按 range 说明范围。COMPLETE 只表示本次结构转换完整,不表示全集。多模型需指定 datasetId;UNKNOWN 行不得自动求和。
25
+
26
+ columns 含 key、fieldId、name、type、encoding。key 仅在本次dataset内唯一;fieldId 是结果字段ID,不能当底层数据集字段ID。绑定使用fieldId,不使用可能重复的name。
27
+
28
+ cells 含 value、display、state、reason:AVAILABLE 可用,NULL 确认空值,UNAVAILABLE 无法可靠解释,RESTRICTED 权限受限。表格优先显示 display,受限单元格统一显示“受限”。计算不能从 display 反解析数字;DECIMAL_STRING 保留上游值,JS Number 仅用于图形坐标且不能用于高精度金额计算。DATE 的 EPOCH_MILLIS_STRING 保留毫秒字符串,不擅自改变时区。日期元数据支持原生类型3、13、15、20;未知类型保守返回不可用。
29
+
30
+ 浏览器适配器不能获知服务端 cellCharLimit,字符串不能据此承诺未经截断的数据库原文。图表可见数值不代表底层明细;数值型字符串不会被猜成图表指标。交叉表/分组表及缺少内联模型的图形明确为不支持;多模型、computedState 和缺列都会保留警告。
31
+
32
+ ## 范围
33
+
34
+ 明细表 range.total 采用有效原生row;PAGE是本次首页,pageLimit不提供续页。hasNext=true 原样保留并产生 NEXT_PAGE_UNSUPPORTED;即使返回数等于总数,也不覆盖这一状态。当前仅提供已加载结果的搜索、排序和分页,页面显示该范围。图表UNPAGED也不等于底层明细全量,可能存在Top N或聚合。
35
+
36
+ 服务端返回空数组与缺失组件不同:前者可显示空数据,后者是 INVALID_RESPONSE。受限/失败/不支持结果不回退静态样本。
37
+
38
+ 表格排序使用 AVAILABLE 的 value,DECIMAL_STRING 按十进制字符串比较,支持合法指数而不转换为浮点金额;缺值排在可用值之后。搜索、展示筛选和 CSV 使用 displayCell 的统一值状态呈现;CSV 导出当前匹配集合而非仅当前页,含 UNKNOWN 行标识并转义公式前缀。图表坐标允许 Number 近似,展开数据表保留原始显示值。经营观察仅消费当前结果的状态、行数和单值 KPI,不自动累加 UNKNOWN 行或推断业务比率。
@@ -1,13 +1,54 @@
1
1
  # 页面骨架
2
2
 
3
+ ## 设计选择与信息组织
4
+
5
+ 开始改页面前,用几句话记录使用者、主要决策、首屏指标、分析顺序、导航和信息密度;已有用户偏好直接沿用。需要新风格时,说明与已有页面在导航、指标组织、图表布局、字体层级和配色上的具体差异。
6
+
7
+ | 布局 | 适用起点 | 信息组织 |
8
+ |---|---|---|
9
+ | overview | 聚焦一个主题的单页分析 | 顶部标题与筛选,组件顺序呈现,通栏明细 |
10
+ | workbench | 需要常驻导航的分析工作台 | 侧栏与内容区,组件结构沿用基础版 |
11
+ | cockpit | 高频经营监测、多指标对比 | 深绿侧栏、独立 KPI 区、经营观察和分析图 |
12
+ | retail | 零售经营简报 | 横向导航、米白底色、连续 KPI 带和编号分析区 |
13
+
14
+ 布局名称是开发起点。图表按分析问题分组,例如趋势、对比、构成、明细;根据真实组件数量决定双列、通栏或分组,不能假设固定数量、首图类型或标题长度。检查奇数和偶数组件、长名称、大数值、多系列、警告与空状态;留白应服务阅读,避免固定跨列造成孤立卡片或大块空位。
15
+
16
+ 首屏给出当前指标与筛选条件,查询范围和影响理解的限制保持可见,适配代码等技术细节可展开。图表轴单位、时间标签、颜色与图例应一致;仅在已核实的口径上调整显示。小屏按阅读顺序收为单列,表格在自身容器滚动,控件与提示不能遮挡内容。浏览器检查范围见[验收文档](acceptance.md#3-浏览器视觉与交互)。
17
+
18
+ ## 展示实现与扩展
19
+
3
20
  图表库和UI组件库不限定,按页面的展示、交互和设计需求选择。模板默认使用ECharts绘图,筛选栏默认使用Ant Design和date-fns;开发时可替换为D3等图表实现,或选用合适的UI组件库,也可使用原生控件。更换库需要调整对应展示组件、依赖和样式,不是app-config中的自动切换选项。
4
21
 
5
22
  替换展示实现时继续复用src/finebi的取数与认证逻辑,遵循[数据契约](data-contract.md)和[筛选契约](filters.md),保留值状态、数据范围提示、空/错误状态及请求取消处理;按所选库的生命周期释放图表、监听器和其他资源。
6
23
 
7
- 默认FilterBar提供可搜索单选/多选、日期及年月区间日历、输入框、空值勾选、重置和应用按钮;其他日期类输入保留对应格式。日历与选择器采用中文界面,筛选卡片展示待应用状态,小屏幕自动换行,区间日历可滚动。主题在FilterBar的ConfigProvider中配置,布局在src/styles.css中调整。搜索仅过滤配置的候选项,不触发远端候选查询。
24
+ 默认FilterBar提供可搜索单选/多选、日期及年月区间日历、年月面板、输入框、空值勾选、重置和应用按钮;其他日期类输入保留对应格式。年月面板提供十二个月、年份网格、前后翻年、本月、默认条件与单项清除,方向键/Enter/Esc 操作且关闭回到触发按钮。选择只改草稿,完整状态为 YYYY-MM;清除沿用源默认。日历与选择器采用中文界面,小屏幕自动换行,区间日历可滚动。主题在FilterBar的ConfigProvider中配置,布局在src/styles.css中调整。搜索仅过滤配置的候选项,不触发远端候选查询。
8
25
 
9
26
  `layout=overview` 使用顶部标题/刷新、筛选栏、双列图表和通栏表格,适合单页分析。
10
27
 
11
28
  `layout=workbench` 增加侧栏应用导航,适合后续扩展多页面。两者共用src/styles.css的颜色、间距和面板规则;小屏幕收为单列。通过修改应用页面与业务组件扩展,不复制请求/认证/解析代码。
12
29
 
13
- 默认Chart组件基于ECharts提供柱状、折线和饼图的基本映射、resize和dispose;Widget提供KPI和错误边界,DataTable提供范围明确的本地搜索/分页。Agent按业务绑定categoryFieldId/valueFieldId,不以返回形状猜图表类型。复杂图形需新增明确的适配与测试。
30
+ `layout=cockpit` 使用深绿侧栏、标题动作区、筛选卡片、四列 KPI、可选经营观察、双列图表、通栏明细和口径折叠区;少于四个或更多 KPI 按配置自然排列,窄屏收为两列 KPI 和单列图表。绿色样式集中在 src/cockpit.css。组件独立加载/失败/重试,批量加载最多三路并发;筛选或刷新取消旧请求,单项重试另发当前组件的请求。所有布局复用同一协议。
31
+
32
+ `layout=retail` 使用米白背景、墨色标题、横向导航、蓝橙图表和编号分析区,样式集中在 `src/retail.css`。导航在滚动时固定于顶部;页内链接定位到对应区域,销售分析定位到“销售表现”标题。锚点预留导航高度,窄屏两行导航也不能遮住目标。KPI 使用连续指标带;分析图按双列连续排列,明细表通栏,窄屏为两列 KPI 和单列图表。筛选作用与查询范围可以展开,数据缺失和部分适配警告保持可见。绑定骨架为 [examples/retail.config.json](../examples/retail.config.json),不包含真实资源或数据;可按业务删减组件,沿用同一查询、认证与取消机制。
33
+
34
+ 布局与绑定配置:
35
+
36
+ | 配置 | 用途 |
37
+ |---|---|
38
+ | `description` / `dataNotes` | 页头说明 / 已核实的数据口径文本 |
39
+ | `insights:true` | 根据当前结果显示单行 KPI 原始显示值、返回行数、失败和适配状态;不包含销售业务公式,不推断全量经营结论 |
40
+ | `widgets[].description` | 组件说明、单位或已核实的口径 |
41
+ | `widgets[].series:[{fieldId,label}]` | line 多系列开关、bar 指标切换;与 categoryFieldId 同属选定 dataset,不跨模型拼接;单指标继续用 valueFieldId |
42
+ | `widgets[].tableFieldIds` | table 按 fieldId 指定展示列及顺序;不配置时显示全部结果列 |
43
+ | `widgets[].localFilterFieldIds` | table 对指定结果字段提供已加载值筛选;不触发远端查询 |
44
+ | `widgets[].filterNames` | 已核实的真实控件作用映射;省略时由源看板决定,空数组表示该组件不传控件覆盖,详见[筛选契约](filters.md) |
45
+ | `widgets[].valueFormat` | KPI 可选 `number`(精确十进制分组并省略尾零)或 `percent`(比例乘 100,四舍五入保留两位);只处理 AVAILABLE 的 value,不从 display 解析数值。原值保留在指标 title;未配置沿用源显示值 |
46
+ | `widgets[].categoryFormat` / `categoryTimeZone` | line/bar 可选 `epoch-month`,并显式指定 IANA 时区(如 Asia/Shanghai),把已核实为毫秒时间戳的分类值显示为 YYYY-MM。仅改变轴标签,展开表保留原值;不能按数值外观猜日期 |
47
+
48
+ 示例骨架为 [examples/cockpit.config.json](../examples/cockpit.config.json)。默认 public/app-config.json 不绑定业务资源,不含静态数据;示例也只包含资源/字段占位符。组件和观察都可按需删减,不复制参考应用的 CLI 子进程后端。
49
+
50
+ 默认Chart组件基于ECharts提供柱状、折线和饼图的字段映射、空值断线、resize和dispose;可展开原始显示值表。Widget提供KPI和错误边界,DataTable提供范围明确的本地搜索、精确十进制排序、已加载值筛选、分页和 CSV 匹配结果导出。CSV 保留值状态/UNKNOWN 行标识并转义公式前缀。Agent按业务绑定categoryFieldId/valueFieldId,不以返回形状猜图表类型。复杂图形需新增明确的适配与测试。
51
+
52
+ 图表按配置类型从`src/components/charts/`加载,只有实际渲染非空图表时才加载对应ECharts模块;表格和KPI不依赖图表运行库。筛选栏按需加载,选择框和日期区间控件各有独立加载入口。加载期间显示状态,加载失败由错误边界展示;图表继续在卸载时dispose。新增图表时添加对应注册入口并复用Chart,避免在页面入口引入整套图表库。
53
+
54
+ 日期适配器使用`calendar-locales.ts`中的中文和默认英文语言。Vite将`date-fns/locale`精确映射到该文件,避免动态语言查找引入所有语言包;扩展界面语言时同步添加对应导出和验证,不修改日期解析规则。构建回归测试检查入口依赖、图表独立入口和语言包范围。部署时完整上传同一次构建的`dist`目录,保留相对路径与所有按需加载资源。
@@ -16,6 +16,8 @@
16
16
 
17
17
  同一文本多选表示选中值集合;不同控件的组合、与组件自身过滤的叠加以及作用组件,遵循源看板配置。页面不提供任意 AND/OR 表达式,不自动继承原生点击联动,不根据返回数据猜测控件作用范围。联动开发必须先建立真实控件映射。
18
18
 
19
+ `widgets[].filterNames` 可记录已核实的作用控件名,查询该展示组件时仅传这些控件的覆盖;`[]` 不传覆盖,省略则继续由源看板决定。只能引用 filters 中的真实名称,不得根据本次结果猜测。筛选栏展示已配置的作用组件和未映射情况,不能把未知作用范围显示成全局联动。同一实例不同展示的映射分别生效。
20
+
19
21
  ### 1.3 空值、默认与清除
20
22
 
21
23
  | 页面状态 | 请求及含义 |
@@ -33,6 +35,8 @@
33
35
 
34
36
  默认筛选栏使用 Ant Design:单选/多选可搜索本地配置的候选项,`date-range` 和 `month-range` 使用日历区间选择器,支持只填写一端、清空和重置。其余类型保留下面的字符串或选项状态;组件库不改变请求语义。样式与组件替换见[页面骨架](design.md)。
35
37
 
38
+ 单 `month` 使用自定义年月面板,默认显示“源看板默认”。支持十二个月点选、点击年份进入年份网格、前后翻年、本月、默认条件和清除;方向键、Home/End、PageUp/PageDown、Enter 和 Esc 可操作。关闭后回到触发按钮,切换网格保留焦点,窄屏限制面板宽度。选择只修改 YYYY-MM 草稿;默认/清除省略请求覆盖,点击应用才取数,不承诺清除源看板保存条件。
39
+
36
40
  ### 2.1 配置与状态
37
41
 
38
42
  每个配置包含 `{name,label,type}`,`type` 省略时为 `select`。未知类型、重复名称、区间后缀与其他控件名冲突均提前报错。`options` 只用于选择类,每项为 `{label,value}`,value 是源控件接受的字符串,不能为空或重复。
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": "1",
3
+ "title": "经营概览",
4
+ "description": "资源绑定示例:替换所有 REPLACE_*,不包含业务数据",
5
+ "basePath": "/webroot/decision",
6
+ "layout": "cockpit",
7
+ "dashboardId": "REPLACE_DASHBOARD_ID",
8
+ "maxRows": 1000,
9
+ "pageSize": 8,
10
+ "insights": true,
11
+ "dataNotes": ["请填写经过核实的数据单位、组件口径与筛选作用范围。"],
12
+ "widgets": [
13
+ {"entryId":"REPLACE_KPI_1", "name":"指标一", "type":"kpi", "valueFieldId":"REPLACE_VALUE_1", "filterNames":["REPLACE_MONTH_CONTROL"]},
14
+ {"entryId":"REPLACE_KPI_2", "name":"指标二", "type":"kpi", "valueFieldId":"REPLACE_VALUE_2", "filterNames":[]},
15
+ {"entryId":"REPLACE_KPI_3", "name":"指标三", "type":"kpi", "valueFieldId":"REPLACE_VALUE_3", "filterNames":[]},
16
+ {"entryId":"REPLACE_KPI_4", "name":"指标四", "type":"kpi", "valueFieldId":"REPLACE_VALUE_4", "filterNames":[]},
17
+ {"entryId":"REPLACE_TREND", "name":"年度趋势", "type":"line", "categoryFieldId":"REPLACE_TIME_FIELD", "series":[{"fieldId":"REPLACE_MEASURE_A", "label":"指标 A"},{"fieldId":"REPLACE_MEASURE_B", "label":"指标 B"}], "filterNames":[]},
18
+ {"entryId":"REPLACE_COMPARISON", "name":"分类对比", "type":"bar", "categoryFieldId":"REPLACE_CATEGORY_FIELD", "valueFieldId":"REPLACE_MEASURE", "filterNames":[]},
19
+ {"entryId":"REPLACE_TABLE", "name":"分组明细", "type":"table", "tableFieldIds":["REPLACE_GROUP_FIELD","REPLACE_MEASURE"], "localFilterFieldIds":["REPLACE_GROUP_FIELD"], "filterNames":["REPLACE_MONTH_CONTROL"]}
20
+ ],
21
+ "filters": [{"name":"REPLACE_MONTH_CONTROL", "label":"年月", "type":"month"}]
22
+ }