@finebi/finebi-cli 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/package.json +5 -4
- package/skills/finebi-cli-agent/SKILL.md +8 -4
- package/skills/finebi-cli-agent/references/command-workflows.md +61 -7
- package/skills/finebi-cli-agent/references/dashboard-data-result.md +219 -0
- package/skills/finebi-cli-agent/references/dashboard-dsl-api.md +12 -1005
- package/skills/finebi-cli-agent/references/dashboard-dsl-core.md +194 -0
- package/skills/finebi-cli-agent/references/dashboard-dsl-examples.md +378 -0
- package/skills/finebi-cli-agent/references/dashboard-dsl-items.md +456 -0
- package/skills/finebi-cli-agent/references/file-to-dashboard.md +1 -1
- package/skills/finebi-cli-agent/references/handoff-contract.md +2 -0
- package/skills/finebi-cli-agent/references/mapping-rules.md +15 -15
- package/skills/finebi-cli-agent/references/scenario-workflows.md +1 -1
- package/skills/finebi-cli-agent/references/web-app.md +13 -0
- package/skills/finebi-cli-agent/references/widget-dsl-api-examples.md +21 -4
- package/skills/finebi-cli-agent/references/widget-dsl-api.md +10 -1336
- package/skills/finebi-cli-agent/references/widget-dsl-contexts.md +142 -0
- package/skills/finebi-cli-agent/references/widget-dsl-core.md +374 -0
- package/skills/finebi-cli-agent/references/widget-dsl-encodings.md +457 -0
- package/skills/finebi-cli-agent/references/widget-dsl-styles.md +413 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/package.json +1 -1
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/app.js +189 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/help.js +33 -2
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/index.js +5 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/src/schema-options.js +44 -8
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/AGENTS.md +27 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/README.md +30 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/auth-and-deploy.md +17 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/data-contract.md +21 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/design.md +13 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/docs/filters.md +89 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/index.html +2 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/package-lock.json +2191 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/package.json +15 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/public/app-config.json +11 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/scripts/dev-proxy.mjs +56 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/App.tsx +31 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Chart.tsx +31 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/DataTable.tsx +14 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/FilterBar.tsx +42 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/Widget.tsx +28 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/components/calendar.ts +14 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/client.ts +37 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/date.d.ts +1 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/date.js +5 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/filters.d.ts +4 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/filters.js +106 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/normalize.ts +77 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/finebi/types.ts +18 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/main.tsx +5 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/services/view-data.ts +18 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/src/styles.css +8 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/calendar.test.mjs +25 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/proxy.test.mjs +84 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tests/runtime.test.mjs +136 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/tsconfig.json +4 -0
- package/skills/finebi-cli-agent/scripts/finebi-cli/templates/web-app/vite.config.ts +13 -0
- package/src/app.js +189 -0
- package/src/help.js +33 -2
- package/src/index.js +5 -0
- package/src/schema-options.js +44 -8
- package/templates/web-app/AGENTS.md +27 -0
- package/templates/web-app/README.md +30 -0
- package/templates/web-app/docs/auth-and-deploy.md +17 -0
- package/templates/web-app/docs/data-contract.md +21 -0
- package/templates/web-app/docs/design.md +13 -0
- package/templates/web-app/docs/filters.md +89 -0
- package/templates/web-app/index.html +2 -0
- package/templates/web-app/package-lock.json +2191 -0
- package/templates/web-app/package.json +15 -0
- package/templates/web-app/public/app-config.json +11 -0
- package/templates/web-app/scripts/dev-proxy.mjs +56 -0
- package/templates/web-app/src/App.tsx +31 -0
- package/templates/web-app/src/components/Chart.tsx +31 -0
- package/templates/web-app/src/components/DataTable.tsx +14 -0
- package/templates/web-app/src/components/FilterBar.tsx +42 -0
- package/templates/web-app/src/components/Widget.tsx +28 -0
- package/templates/web-app/src/components/calendar.ts +14 -0
- package/templates/web-app/src/finebi/client.ts +37 -0
- package/templates/web-app/src/finebi/date.d.ts +1 -0
- package/templates/web-app/src/finebi/date.js +5 -0
- package/templates/web-app/src/finebi/filters.d.ts +4 -0
- package/templates/web-app/src/finebi/filters.js +106 -0
- package/templates/web-app/src/finebi/normalize.ts +77 -0
- package/templates/web-app/src/finebi/types.ts +18 -0
- package/templates/web-app/src/main.tsx +5 -0
- package/templates/web-app/src/services/view-data.ts +18 -0
- package/templates/web-app/src/styles.css +8 -0
- package/templates/web-app/tests/calendar.test.mjs +25 -0
- package/templates/web-app/tests/proxy.test.mjs +84 -0
- package/templates/web-app/tests/runtime.test.mjs +136 -0
- package/templates/web-app/tsconfig.json +4 -0
- package/templates/web-app/vite.config.ts +13 -0
|
@@ -99,6 +99,29 @@ function findResource(manifest, resourceName) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const BUILT_IN_RESOURCES = {
|
|
102
|
+
app: {
|
|
103
|
+
resource: "app",
|
|
104
|
+
summary: "Create and inspect local FineBI dynamic web app projects without a server connection.",
|
|
105
|
+
commands: [
|
|
106
|
+
{
|
|
107
|
+
name: "create",
|
|
108
|
+
summary: "Copy the bundled web app template into a new project directory.",
|
|
109
|
+
usage: "finebi-cli app create --name <name> --path <parent>",
|
|
110
|
+
options: [
|
|
111
|
+
{name: "--name", type: "string", description: "Simple lowercase npm project name."},
|
|
112
|
+
{name: "--path", type: "string", description: "Existing parent directory; relative paths resolve from the current directory."},
|
|
113
|
+
],
|
|
114
|
+
details: ["Creates a local project only; it does not connect to FineBI or run install/build scripts."],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "inspect",
|
|
118
|
+
summary: "Check a local web app project configuration without remote validation.",
|
|
119
|
+
usage: "finebi-cli app inspect --path <project>",
|
|
120
|
+
options: [{name: "--path", type: "string", description: "Existing project directory; relative paths resolve from the current directory."}],
|
|
121
|
+
details: ["Checks app-config.json structure and widget bindings locally. Remote dashboard and permission checks are not performed."],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
102
125
|
"install-skill": {
|
|
103
126
|
command: "install-skill",
|
|
104
127
|
resource: "install-skill",
|
|
@@ -288,7 +311,7 @@ function commandOptionsFor(command) {
|
|
|
288
311
|
|
|
289
312
|
function optionsFor(schema) {
|
|
290
313
|
const required = schema.required || [];
|
|
291
|
-
return Object.keys(schema.properties || {}).filter((fieldName) => schema.properties[fieldName].input !== "argument").
|
|
314
|
+
return Object.keys(schema.properties || {}).filter((fieldName) => schema.properties[fieldName].input !== "argument").flatMap((fieldName) => {
|
|
292
315
|
const property = schema.properties[fieldName];
|
|
293
316
|
const optionName = optionNameFor(fieldName, property);
|
|
294
317
|
const option = {
|
|
@@ -305,7 +328,15 @@ function optionsFor(schema) {
|
|
|
305
328
|
if (defaultValue !== undefined && defaultValue !== "") {
|
|
306
329
|
option.default = defaultValue;
|
|
307
330
|
}
|
|
308
|
-
|
|
331
|
+
if (property.keyValueOption) {
|
|
332
|
+
return [option, {
|
|
333
|
+
name: `--${property.keyValueOption}`,
|
|
334
|
+
required: false,
|
|
335
|
+
type: "name=value",
|
|
336
|
+
description: `Repeatable; maps each name to a one-element string array; mutually exclusive with --${optionName}. Split at the first =; empty names/values and duplicate names are rejected.`,
|
|
337
|
+
}];
|
|
338
|
+
}
|
|
339
|
+
return [option];
|
|
309
340
|
});
|
|
310
341
|
}
|
|
311
342
|
|
|
@@ -19,6 +19,7 @@ import {parseConnectionArgs, ProfileError, profileCapabilityPath, readProfile, r
|
|
|
19
19
|
import {loginWithPassword, resolveLoginInputFromEnv} from "./auth.js";
|
|
20
20
|
import {executeDynamicCommand} from "./dynamic-command.js";
|
|
21
21
|
import {handleInstallSkill} from "./install-skill.js";
|
|
22
|
+
import {handleApp} from "./app.js";
|
|
22
23
|
import {cliError} from "./errors.js";
|
|
23
24
|
import {
|
|
24
25
|
renderBuiltInCommandHelp,
|
|
@@ -48,6 +49,9 @@ export async function runCli(argv, context = {}) {
|
|
|
48
49
|
if (args[0] === "install-skill") {
|
|
49
50
|
return isHelpFlagInvocation(args) ? renderBuiltInHelp("install-skill") : handleInstallSkill(args.slice(1), context);
|
|
50
51
|
}
|
|
52
|
+
if (args[0] === "app") {
|
|
53
|
+
return isHelpFlagInvocation(args) ? renderBuiltInHelp("app") : handleApp(args.slice(1), context);
|
|
54
|
+
}
|
|
51
55
|
const management = args[0] === "auth" && ["list", "use", "remove"].includes(args[1]);
|
|
52
56
|
const builtInHelp = isHelpFlagInvocation(args) && (args.length === 1 || isBuiltInResource(args[0]));
|
|
53
57
|
const optional = management || builtInHelp || args.length === 0 || args[0] === "version";
|
|
@@ -684,6 +688,7 @@ async function handleHelp(args, context) {
|
|
|
684
688
|
|
|
685
689
|
function isBuiltInResource(resourceName) {
|
|
686
690
|
return resourceName === "install-skill"
|
|
691
|
+
|| resourceName === "app"
|
|
687
692
|
|| resourceName === "config"
|
|
688
693
|
|| resourceName === "auth"
|
|
689
694
|
|| resourceName === "capability"
|
|
@@ -4,11 +4,12 @@ import {cliError} from "./errors.js";
|
|
|
4
4
|
import {validateSchemaValue} from "./schema-validator.js";
|
|
5
5
|
|
|
6
6
|
export function buildRequestBody(schema, args) {
|
|
7
|
-
const
|
|
7
|
+
const properties = schema.properties || {};
|
|
8
|
+
const repeatable = new Set(Object.values(properties).map(property => property.keyValueOption).filter(Boolean));
|
|
9
|
+
const parsed = parseArgs(args, repeatable);
|
|
8
10
|
if (!parsed.ok) {
|
|
9
11
|
return parsed;
|
|
10
12
|
}
|
|
11
|
-
const properties = schema.properties || {};
|
|
12
13
|
const argumentFields = Object.keys(properties).filter((fieldName) => properties[fieldName].input === "argument");
|
|
13
14
|
if (parsed.positionals.length > argumentFields.length) {
|
|
14
15
|
return invalidArgument(parsed.positionals[argumentFields.length], `Unexpected argument: ${parsed.positionals[argumentFields.length]}.`);
|
|
@@ -33,10 +34,20 @@ export function buildRequestBody(schema, args) {
|
|
|
33
34
|
for (const fieldName of Object.keys(properties)) {
|
|
34
35
|
const property = properties[fieldName];
|
|
35
36
|
const optionName = optionNameFor(fieldName, property);
|
|
37
|
+
const inlineValues = property.keyValueOption && parsed.options[property.keyValueOption];
|
|
38
|
+
if (inlineValues && Object.prototype.hasOwnProperty.call(parsed.options, optionName)) {
|
|
39
|
+
return invalidArgument(fieldName, `--${property.keyValueOption} and --${optionName} are mutually exclusive.`);
|
|
40
|
+
}
|
|
36
41
|
if (property.input === "argument" && body[fieldName] !== undefined) {
|
|
37
42
|
continue;
|
|
38
43
|
}
|
|
39
|
-
if (
|
|
44
|
+
if (inlineValues) {
|
|
45
|
+
const coerced = coerceKeyValues(fieldName, inlineValues, property);
|
|
46
|
+
if (!coerced.ok) {
|
|
47
|
+
return coerced;
|
|
48
|
+
}
|
|
49
|
+
body[fieldName] = coerced.value;
|
|
50
|
+
} else if (Object.prototype.hasOwnProperty.call(parsed.options, optionName)) {
|
|
40
51
|
const coerced = coerceValue(fieldName, parsed.options[optionName], property);
|
|
41
52
|
if (!coerced.ok) {
|
|
42
53
|
return coerced;
|
|
@@ -60,7 +71,8 @@ export function buildRequestBody(schema, args) {
|
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
for (const optionName of Object.keys(parsed.options)) {
|
|
63
|
-
if (!Object.keys(properties).some((fieldName) => optionNameFor(fieldName, properties[fieldName]) === optionName
|
|
74
|
+
if (!Object.keys(properties).some((fieldName) => optionNameFor(fieldName, properties[fieldName]) === optionName
|
|
75
|
+
|| properties[fieldName].keyValueOption === optionName)) {
|
|
64
76
|
return invalidArgument(`--${optionName}`, `Unknown option: --${optionName}.`);
|
|
65
77
|
}
|
|
66
78
|
}
|
|
@@ -75,7 +87,7 @@ export function buildRequestBody(schema, args) {
|
|
|
75
87
|
};
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
function parseArgs(args) {
|
|
90
|
+
function parseArgs(args, repeatable) {
|
|
79
91
|
const options = {};
|
|
80
92
|
const positionals = [];
|
|
81
93
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -86,10 +98,13 @@ function parseArgs(args) {
|
|
|
86
98
|
}
|
|
87
99
|
const optionName = current.slice(2);
|
|
88
100
|
const next = args[index + 1];
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
const value = next === undefined || next.startsWith("--") ? true : next;
|
|
102
|
+
if (repeatable.has(optionName)) {
|
|
103
|
+
(options[optionName] ||= []).push(value);
|
|
91
104
|
} else {
|
|
92
|
-
options[optionName] =
|
|
105
|
+
options[optionName] = value;
|
|
106
|
+
}
|
|
107
|
+
if (value !== true) {
|
|
93
108
|
index += 1;
|
|
94
109
|
}
|
|
95
110
|
}
|
|
@@ -100,6 +115,27 @@ function parseArgs(args) {
|
|
|
100
115
|
};
|
|
101
116
|
}
|
|
102
117
|
|
|
118
|
+
function coerceKeyValues(fieldName, values, property) {
|
|
119
|
+
const entries = new Map();
|
|
120
|
+
for (const value of values) {
|
|
121
|
+
const separator = typeof value === "string" ? value.indexOf("=") : -1;
|
|
122
|
+
if (separator < 1 || !value.slice(0, separator).trim() || !value.slice(separator + 1).trim()) {
|
|
123
|
+
return invalidArgument(fieldName, `--${property.keyValueOption} requires name=value with a non-empty name and value.`);
|
|
124
|
+
}
|
|
125
|
+
const name = value.slice(0, separator);
|
|
126
|
+
if (entries.has(name)) {
|
|
127
|
+
return invalidArgument(fieldName, `Duplicate name in --${property.keyValueOption}: ${name}.`);
|
|
128
|
+
}
|
|
129
|
+
entries.set(name, [value.slice(separator + 1)]);
|
|
130
|
+
}
|
|
131
|
+
const value = Object.fromEntries(entries);
|
|
132
|
+
if (property.schema) {
|
|
133
|
+
const validation = validateSchemaValue(value, property.schema, fieldName);
|
|
134
|
+
if (!validation.ok) return validation;
|
|
135
|
+
}
|
|
136
|
+
return {ok: true, value};
|
|
137
|
+
}
|
|
138
|
+
|
|
103
139
|
function coerceValue(fieldName, value, property) {
|
|
104
140
|
if (property.input === "file" || property.file) {
|
|
105
141
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# FineBI 动态页面开发
|
|
2
|
+
|
|
3
|
+
## 开发顺序
|
|
4
|
+
|
|
5
|
+
1. 用 FineBI CLI 搜索并读取 dashboard info/data,确认看板实例 entryId、queryFields.dId、真实控件 filterName 和可访问来源。
|
|
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,再通过浏览器核对请求、字段、图表、刷新和权限状态。构建成功不等于认证或取数成功。
|
|
10
|
+
|
|
11
|
+
## 数据规则
|
|
12
|
+
|
|
13
|
+
- 先检查 normalization、warnings、range,再选择唯一 dataset;多模型需显式 datasetId。
|
|
14
|
+
- display 用于显示;计算仅使用 AVAILABLE 的 value,保留 DECIMAL_STRING 精度。禁止从百分比/千分位/单位文本恢复数值。受限值、未知值不能补零。
|
|
15
|
+
- 不按名称猜字段、不累加 UNKNOWN 行、不从首页估算全量 KPI。KPI 示例仅消费单行结果。
|
|
16
|
+
- 控件筛选通过真实名称生成 refer;无控件时不伪造服务端过滤。搜索和排序的范围必须在页面说明。
|
|
17
|
+
- 筛选绑定先核对源控件类型,配置/状态/请求统一通过 src/finebi/filters.js 校验。完整支持矩阵、逗号和 null 保留字、恢复默认与筛选空值语义见 docs/filters.md;不以更换 type 绕过服务端能力限制。
|
|
18
|
+
- pageLimit 是首页大小,不是分页。不得循环放大请求以绕过 maxRows;hasNext=true 必须保留警告,不能宣称全量。
|
|
19
|
+
|
|
20
|
+
## 认证与交付
|
|
21
|
+
|
|
22
|
+
- 开发凭证只存在服务端环境 FINEBI_DEV_* 或内存;禁止 VITE_* 凭证、前端 localStorage token、提交 .env 和认证日志。
|
|
23
|
+
- 生产应用使用同源当前用户 Cookie。开发代理不属于构建产物;不要把它部署为共享多用户服务。
|
|
24
|
+
- v1 不支持目录发布来源、交叉表、服务端续页、全局排序或静态托管发布;遇到需求先说明缺口。
|
|
25
|
+
- 图表按生命周期销毁;异步请求取消且旧响应不得覆盖新条件。失败状态不得展示过期数据充当最新结果。
|
|
26
|
+
|
|
27
|
+
项目创建不会安装依赖、启动服务、初始化 Git 或发布,按用户授权范围执行后续步骤。
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# FineBI 动态数据应用
|
|
2
|
+
|
|
3
|
+
这是运行时调用 FineBI 的静态应用。开发者通过 CLI 发现资源后配置绑定;生产查询使用访问者自己的登录态。
|
|
4
|
+
|
|
5
|
+
## 开始
|
|
6
|
+
|
|
7
|
+
1. 安装依赖:`npm ci`。
|
|
8
|
+
2. 填写 `public/app-config.json`。`dashboardId` 来自目标看板;`widgets[].entryId` 使用板内实例 ID;图表 `categoryFieldId/valueFieldId` 使用本次 `dashboard info` 的结果字段 dId。相同实例可以有不同展示,但 KPI 必须对应单行查询结果。
|
|
9
|
+
3. 在当前进程设置 `FINEBI_DEV_BASE_URL`(完整 decision 地址)和 `FINEBI_DEV_USERNAME/FINEBI_DEV_PASSWORD`,或 `FINEBI_DEV_TOKEN`。不要把值写入 VITE_* 或 app-config。
|
|
10
|
+
4. `npm run dev`,访问 `http://localhost:4173`。开发代理仅监听本机,自动以开发身份登录,凭证不返回浏览器。
|
|
11
|
+
5. `npm test`、`npm run build`。`npm run preview` 只展示 dist,不启用开发身份代理;真实生产取数需要同源 FineBI 路径。
|
|
12
|
+
|
|
13
|
+
## 配置
|
|
14
|
+
|
|
15
|
+
`layout` 为 `overview`(顶部概览)或 `workbench`(侧栏工作台)。`maxRows` 默认 1000,上限 10000;`pageSize` 是本地展示页大小,1–100。
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"schemaVersion":"1", "title":"销售分析", "basePath":"/webroot/decision",
|
|
20
|
+
"layout":"overview", "dashboardId":"真实看板ID", "maxRows":1000, "pageSize":8,
|
|
21
|
+
"widgets":[
|
|
22
|
+
{"entryId":"真实实例ID", "name":"销售趋势", "type":"line", "categoryFieldId":"日期结果字段ID", "valueFieldId":"销售结果字段ID"}
|
|
23
|
+
],
|
|
24
|
+
"filters":[]
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
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
|
+
|
|
30
|
+
筛选先编辑、再点击“应用筛选”;“恢复全部默认”重新查询看板保存条件。模板支持文本、单选/多选、数值选择和日期类控件,完整类型、空值语义与配置示例以[筛选契约](docs/filters.md)为准;`app inspect` 仅检查本地配置,绑定后仍需验证真实条件生效。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 认证与部署
|
|
2
|
+
|
|
3
|
+
## 本地开发
|
|
4
|
+
|
|
5
|
+
Vite开发插件读取服务端环境 `FINEBI_DEV_BASE_URL`、`FINEBI_DEV_USERNAME/FINEBI_DEV_PASSWORD`,或已有短期 `FINEBI_DEV_TOKEN`。BASE_URL必须是目标完整decision地址。支持由本地.env.local提供,禁止提交文件;不要使用VITE_前缀。
|
|
6
|
+
|
|
7
|
+
代理在内存保存开发会话,只接受本机同源、带XMLHttpRequest标记的POST;白名单限制为配置看板及组件查询,不代理登录管理或任意接口。浏览器无需持有开发账号凭证。失效返回AUTH_REQUIRED;密码方式下一次显式重试重新登录,token方式需更新token并重启开发服务。不要向他人开放开发服务端口。
|
|
8
|
+
|
|
9
|
+
筛选仅允许配置中的控件名和单值数组:下拉值必须属于 options,date 值必须是有效的 YYYY-MM-DD 日历日期,不使用固定日期白名单。空日期通过省略参数恢复保存条件,代理拒绝空字符串和多值日期数组。
|
|
10
|
+
|
|
11
|
+
## 生产
|
|
12
|
+
|
|
13
|
+
`npm run build` 输出dist,开发代理不会进入产物。将静态资源托管在与FineBI相同源的应用子路径,basePath配置为实际decision路径。用户先通过FineBI登录,查询携带当前Cookie,服务端按当前身份判断资源、数据和字段权限。
|
|
14
|
+
|
|
15
|
+
登录失效、HTTP403、查询失败分别显示状态,用户登录后点击重试。生产包不保存开发token、不调用CLI、不自动以开发者账号重登。`npm run preview`不注入身份,只用于产物展示;本版不提供部署上传/发布命令、跨域认证、嵌入SSO或目录发布来源查询。
|
|
16
|
+
|
|
17
|
+
交付应检查dist无凭证,核对真实查询网络与页面;开发身份验证通过不能证明所有生产用户有权访问,发布目录权限需另行验证。
|
|
@@ -0,0 +1,21 @@
|
|
|
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。受限/失败/不支持结果不回退静态样本。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 页面骨架
|
|
2
|
+
|
|
3
|
+
图表库和UI组件库不限定,按页面的展示、交互和设计需求选择。模板默认使用ECharts绘图,筛选栏默认使用Ant Design和date-fns;开发时可替换为D3等图表实现,或选用合适的UI组件库,也可使用原生控件。更换库需要调整对应展示组件、依赖和样式,不是app-config中的自动切换选项。
|
|
4
|
+
|
|
5
|
+
替换展示实现时继续复用src/finebi的取数与认证逻辑,遵循[数据契约](data-contract.md)和[筛选契约](filters.md),保留值状态、数据范围提示、空/错误状态及请求取消处理;按所选库的生命周期释放图表、监听器和其他资源。
|
|
6
|
+
|
|
7
|
+
默认FilterBar提供可搜索单选/多选、日期及年月区间日历、输入框、空值勾选、重置和应用按钮;其他日期类输入保留对应格式。日历与选择器采用中文界面,筛选卡片展示待应用状态,小屏幕自动换行,区间日历可滚动。主题在FilterBar的ConfigProvider中配置,布局在src/styles.css中调整。搜索仅过滤配置的候选项,不触发远端候选查询。
|
|
8
|
+
|
|
9
|
+
`layout=overview` 使用顶部标题/刷新、筛选栏、双列图表和通栏表格,适合单页分析。
|
|
10
|
+
|
|
11
|
+
`layout=workbench` 增加侧栏应用导航,适合后续扩展多页面。两者共用src/styles.css的颜色、间距和面板规则;小屏幕收为单列。通过修改应用页面与业务组件扩展,不复制请求/认证/解析代码。
|
|
12
|
+
|
|
13
|
+
默认Chart组件基于ECharts提供柱状、折线和饼图的基本映射、resize和dispose;Widget提供KPI和错误边界,DataTable提供范围明确的本地搜索/分页。Agent按业务绑定categoryFieldId/valueFieldId,不以返回形状猜图表类型。复杂图形需新增明确的适配与测试。
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 控件筛选与页面联动
|
|
2
|
+
|
|
3
|
+
## 1. 通用交互约定
|
|
4
|
+
|
|
5
|
+
模板把页面筛选状态统一转换为 FineBI `widgets/query` 的 `refer`。`src/finebi/filters.js` 是配置校验、值编码和代理校验的共同入口;UI 库可以替换,不能绕过这个入口拼装条件。
|
|
6
|
+
|
|
7
|
+
### 1.1 绑定与能力发现
|
|
8
|
+
|
|
9
|
+
开发时从同一看板的 `dashboard info` 获取真实控件 `filterName` 和实例信息,必要时读取 `dashboard bean` 确认控件类型、绑定字段、保存默认值和作用范围。`name` 必须使用解码后的真实控件名;`label` 仅用于显示。重名控件需先在源看板消除歧义,不能用 label 或组件 ID 替代请求键。没有控件时 `filters` 保持空数组,不把任意字段伪造成服务端筛选。
|
|
10
|
+
|
|
11
|
+
`app inspect` 和浏览器只检查本地配置,不访问远端,也不能证明控件类型或作用范围正确。交付前必须用真实接口证明条件生效。配置不复制源看板默认值,恢复默认后由服务端读取当前保存条件。
|
|
12
|
+
|
|
13
|
+
### 1.2 条件组合与刷新
|
|
14
|
+
|
|
15
|
+
页面编辑的是草稿,点击“应用筛选”后统一提交;未应用时明确提示页面仍显示上次查询结果。应用和刷新均重查全部绑定组件,取消旧请求,并阻止旧响应覆盖新结果。请求失败显示错误,不以过期结果冒充最新数据。
|
|
16
|
+
|
|
17
|
+
同一文本多选表示选中值集合;不同控件的组合、与组件自身过滤的叠加以及作用组件,遵循源看板配置。页面不提供任意 AND/OR 表达式,不自动继承原生点击联动,不根据返回数据猜测控件作用范围。联动开发必须先建立真实控件映射。
|
|
18
|
+
|
|
19
|
+
### 1.3 空值、默认与清除
|
|
20
|
+
|
|
21
|
+
| 页面状态 | 请求及含义 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| 未设置、`undefined`、空字符串;多选 `[]`;区间两端为空 | 省略请求键,使用源看板保存条件 |
|
|
24
|
+
| 恢复单项默认 | 将该项草稿设为空;应用后省略该项 |
|
|
25
|
+
| 恢复全部默认 | 立即提交空 `refer`,重新查询保存条件 |
|
|
26
|
+
| `null`,且配置 `allowNull:true` | 文本/数值选择发送 `["null"]`,只筛选空值 |
|
|
27
|
+
| 日期或年月区间只填写一端 | 只发送该端;服务端将另一端设为空,不继承保存端点 |
|
|
28
|
+
| 清除所有服务端筛选、不限制任何数据 | 当前协议没有统一清除操作,模板不提供这一承诺 |
|
|
29
|
+
|
|
30
|
+
“恢复默认”和“筛选空值”是不同操作。文本值 `"null"` 是原生保留字,不能作为普通文本值传递;多选不支持空值与普通值混选。日期类不支持 `allowNull`。
|
|
31
|
+
|
|
32
|
+
## 2. 模板当前内置支持
|
|
33
|
+
|
|
34
|
+
默认筛选栏使用 Ant Design:单选/多选可搜索本地配置的候选项,`date-range` 和 `month-range` 使用日历区间选择器,支持只填写一端、清空和重置。其余类型保留下面的字符串或选项状态;组件库不改变请求语义。样式与组件替换见[页面骨架](design.md)。
|
|
35
|
+
|
|
36
|
+
### 2.1 配置与状态
|
|
37
|
+
|
|
38
|
+
每个配置包含 `{name,label,type}`,`type` 省略时为 `select`。未知类型、重复名称、区间后缀与其他控件名冲突均提前报错。`options` 只用于选择类,每项为 `{label,value}`,value 是源控件接受的字符串,不能为空或重复。
|
|
39
|
+
|
|
40
|
+
| type | 页面状态(应用前) | 原生 refer 示例 | 对应源控件 |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `text` | 字符串,精确值选择 | `{"地区":["上海"]}` | 文本选择控件;不是包含/模糊匹配 |
|
|
43
|
+
| `select` | 一个 options.value | `{"地区":["上海"]}` | 文本或数值选择 |
|
|
44
|
+
| `multi-select` | options.value 字符串数组 | `{"地区":["上海,北京"]}` | 文本或数值选择 |
|
|
45
|
+
| `number` | 十进制字符串 | `{"金额":["-12.50"]}` | NumberControl 数值选择;不是数值范围/滑块 |
|
|
46
|
+
| `date` | `YYYY-MM-DD` | `{"日期":["2024-02-29"]}` | 单日期或日期面板 |
|
|
47
|
+
| `date-range` | `{start,end}`,日期字符串 | `{"日期范围_s":["2024-02-01"],"日期范围_e":["2024-02-29"]}` | 日期区间 |
|
|
48
|
+
| `year` | `YYYY` | `{"年份":["2024"]}` | 年份 |
|
|
49
|
+
| `month` | `YYYY-MM` | `{"年月":["2024-02"]}` | 年月 |
|
|
50
|
+
| `month-range` | `{start,end}`,年月字符串 | `{"年月范围_s":["2024-01"],"年月范围_e":["2024-03"]}` | 年月区间 |
|
|
51
|
+
| `quarter` | `YYYY-Q`,Q 为 1–4 | `{"季度":["2024-3"]}` | 年季度 |
|
|
52
|
+
| `time-label` | 配置中的标签名称 | `{"时间标签":["本月"]}` | 源看板已配置的时间标签 |
|
|
53
|
+
|
|
54
|
+
日期选择器可以使用原生 HTML 或 UI 库,状态始终是日历字符串,不转时间戳或时区。年份限 0001–9999,校验真实日历和起止顺序;区间端点必须显式给出字符串,空字符串表示该端留空。服务端决定日期边界的计算口径,页面不自行追加 23:59:59 或把结束日期加一天。
|
|
55
|
+
|
|
56
|
+
数值保留十进制字符串精度,不先转 JavaScript Number;接受负号和小数,不接受指数、千分位、单位、NaN 或 Infinity。多选的外层请求数组始终只有一个字符串;不能直接发送 `["上海","北京"]`,服务端只读取首项。原生逗号协议没有转义,普通文本输入及选择项中的逗号和字面值 `"null"` 被拒绝。时间标签按完整名称匹配,不使用逗号拆分规则。
|
|
57
|
+
|
|
58
|
+
### 2.2 配置示例
|
|
59
|
+
|
|
60
|
+
以下名称为示例,必须替换为同一看板中类型相符的真实 filterName。
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
"filters": [
|
|
64
|
+
{"name":"地区", "label":"销售地区", "type":"multi-select", "allowNull":true,
|
|
65
|
+
"options":[{"label":"上海", "value":"上海"},{"label":"北京", "value":"北京"}]},
|
|
66
|
+
{"name":"订单日期范围", "label":"订单日期", "type":"date-range"},
|
|
67
|
+
{"name":"金额选择", "label":"金额", "type":"number"}
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
上述区间配置用真实控件名,不手动添加 `_s/_e`;编码器统一生成后缀并检查冲突。单日期控件不能按区间协议绑定。已有两个单独 `date` 配置绑定 `_s/_e` 的项目可以继续运行,但只有 `date-range` 把两端作为一个整体校验。
|
|
72
|
+
|
|
73
|
+
### 2.3 候选项与支持边界
|
|
74
|
+
|
|
75
|
+
选择类当前使用配置中的静态 `options`,不自动查询完整候选项、不提供远端搜索、分页或级联。静态选项应来自经过核实的源控件值或业务明确允许的子集;不能把本次已加载结果中的唯一值宣称为全部候选项。`time-label.options` 必须来自源控件已有标签名称,不能自行发明动态日期表达式。
|
|
76
|
+
|
|
77
|
+
数值范围、滑块、树控件、级联控件、任意比较运算、日期时间精度输入、混选空值与普通值均不在当前适配范围。配置未知类型会报错;不要换成 `text` 绕过检查。原生 refer 没有覆盖能力的类型,需要先增加并验证服务端协议,不能只添加 UI。
|
|
78
|
+
|
|
79
|
+
本地搜索、排序、分页仅作用于已加载结果;`maxRows` 限制单次返回量,当前不提供服务端续页和全局排序。
|
|
80
|
+
|
|
81
|
+
## 3. 后续扩展方式
|
|
82
|
+
|
|
83
|
+
更换同语义 UI 组件时,沿用表中的状态类型和统一编码器。新增能力按以下链路扩展:
|
|
84
|
+
|
|
85
|
+
1. 确认源控件的类型、名称、字段、默认值、作用范围及真实请求编码,区分浏览器内部控件状态与 HTTP refer 协议;检查目标部署版本确实支持覆盖。
|
|
86
|
+
2. 扩展 `src/finebi/types.ts` 和 `filters.js` 中的配置、状态校验、编码和反向请求校验。CLI 的 `app inspect`、客户端和开发代理共用该模块,不另建一套允许列表。
|
|
87
|
+
3. 在 `FilterBar` 或业务页面接入控件。动态候选项必须有经过验证的接口、访问者权限、搜索/分页语义、加载失败状态、依赖条件和请求取消策略;级联还须定义上游变化后下游选择的保留或清除方式。不从样本推断完整候选项。
|
|
88
|
+
4. 测试合法值、非法值、保留字、名称冲突、空值、恢复默认、开放区间和组合提交;再用真实看板核对请求、数据范围与组件展示。源码或单测通过不能代替部署与浏览器验收。
|
|
89
|
+
5. 同步本契约、模板支持摘要并重新生成分发物。图表库和 UI 组件库选型见[页面骨架](design.md)。
|