@jzg223/design-runtime 0.0.1 → 0.1.0-rc.2

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 (54) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +52 -0
  3. package/dist/board/api.js +57 -0
  4. package/dist/board/board-chart.vue.js +7 -0
  5. package/dist/board/board-chart2.vue.js +93 -0
  6. package/dist/board/context.js +15 -0
  7. package/dist/board/datav-map.js +66 -0
  8. package/dist/board/design-board.vue.js +172 -0
  9. package/dist/board/design-board2.vue.js +331 -0
  10. package/dist/board/echarts.js +40 -0
  11. package/dist/board/part-node.vue.js +63 -0
  12. package/dist/board/part-node2.vue.js +125 -0
  13. package/dist/board/part-renderer.vue.js +561 -0
  14. package/dist/board/part-renderer2.vue.js +884 -0
  15. package/dist/board/theme-host.js +129 -0
  16. package/dist/board-engine/asset.js +32 -0
  17. package/dist/board-engine/chart-option.js +377 -0
  18. package/dist/board-engine/data-hub.js +67 -0
  19. package/dist/board-engine/datav-meta.js +51 -0
  20. package/dist/board-engine/i18n.js +130 -0
  21. package/dist/board-engine/param-catalog.js +137 -0
  22. package/dist/board-engine/params.js +61 -0
  23. package/dist/board-engine/render-model.js +65 -0
  24. package/dist/board-engine/schema.js +856 -0
  25. package/dist/board-engine/theme-manifest.js +19 -0
  26. package/dist/board-engine/version.js +4 -0
  27. package/dist/host.js +6 -1
  28. package/dist/index.js +172 -24
  29. package/dist/style.css +1067 -0
  30. package/dist/types/board/api.d.ts +45 -0
  31. package/dist/types/board/board-chart.vue.d.ts +15 -0
  32. package/dist/types/board/context.d.ts +29 -0
  33. package/dist/types/board/datav-map.d.ts +24 -0
  34. package/dist/types/board/design-board.vue.d.ts +1409 -0
  35. package/dist/types/board/echarts.d.ts +25 -0
  36. package/dist/types/board/part-node.vue.d.ts +1237 -0
  37. package/dist/types/board/part-renderer.vue.d.ts +1205 -0
  38. package/dist/types/board/theme-host.d.ts +41 -0
  39. package/dist/types/board-engine/asset.d.ts +37 -0
  40. package/dist/types/board-engine/chart-option.d.ts +737 -0
  41. package/dist/types/board-engine/data-hub.d.ts +48 -0
  42. package/dist/types/board-engine/datav-meta.d.ts +37 -0
  43. package/dist/types/board-engine/i18n.d.ts +43 -0
  44. package/dist/types/board-engine/index.d.ts +25 -0
  45. package/dist/types/board-engine/param-catalog.d.ts +82 -0
  46. package/dist/types/board-engine/params.d.ts +37 -0
  47. package/dist/types/board-engine/render-model.d.ts +87 -0
  48. package/dist/types/board-engine/schema.d.ts +513 -0
  49. package/dist/types/board-engine/theme-manifest.d.ts +23 -0
  50. package/dist/types/board-engine/types.d.ts +147 -0
  51. package/dist/types/board-engine/version.d.ts +10 -0
  52. package/dist/types/host.d.ts +21 -0
  53. package/dist/types/index.d.ts +21 -2
  54. package/package.json +21 -4
@@ -0,0 +1,129 @@
1
+ import { getHostBoard } from "../host.js";
2
+ import { themeOf, setThemeTokens } from "../board-engine/schema.js";
3
+ import { parseManifest } from "../board-engine/theme-manifest.js";
4
+ const CHART_VAR_PREFIX = "--brd-chart-";
5
+ const SCHEME_VAR = "--brd-scheme";
6
+ const TOOLTIP_BG_VAR = "--brd-tooltip-bg";
7
+ const TOOLTIP_BORDER_VAR = "--brd-tooltip-border";
8
+ const FRAME_VAR_1 = "--brd-frame-1";
9
+ const FRAME_VAR_2 = "--brd-frame-2";
10
+ const DECO_VAR_1 = "--brd-deco-1";
11
+ const DECO_VAR_2 = "--brd-deco-2";
12
+ const DEFAULT_THEME_BASE = "/board/theme/";
13
+ function themeBaseUrl() {
14
+ const injected = String(getHostBoard().themeBaseUrl ?? "").trim();
15
+ let raw = injected;
16
+ if (!raw && typeof window !== "undefined") {
17
+ const cfg = window._CONFIG ?? {};
18
+ raw = String(cfg.VUE_APP_THEME_BASE_URL ?? "").trim();
19
+ }
20
+ const base = raw || DEFAULT_THEME_BASE;
21
+ try {
22
+ const docBase = typeof document !== "undefined" ? document.baseURI : void 0;
23
+ return new URL(base.replace(/\/+$/, "") + "/", docBase).href;
24
+ } catch {
25
+ return base.replace(/\/+$/, "") + "/";
26
+ }
27
+ }
28
+ const registered = {};
29
+ const cssLoading = /* @__PURE__ */ new Map();
30
+ let manifestPromise = null;
31
+ function readThemeTokens(code) {
32
+ if (typeof document === "undefined" || !code) return null;
33
+ const probe = document.createElement("div");
34
+ probe.setAttribute("data-board-theme", code);
35
+ probe.style.cssText = "position:absolute;left:-9999px;top:-9999px;width:0;height:0;";
36
+ document.body.appendChild(probe);
37
+ const cs = getComputedStyle(probe);
38
+ const pick = (name) => String(cs.getPropertyValue(name) || "").trim();
39
+ const colors = [];
40
+ for (let i = 1; i <= 8; i++) {
41
+ const v = pick(CHART_VAR_PREFIX + i);
42
+ if (v) colors.push(v);
43
+ }
44
+ const scheme = pick(SCHEME_VAR);
45
+ const frame1 = pick(FRAME_VAR_1);
46
+ const frame2 = pick(FRAME_VAR_2);
47
+ const deco1 = pick(DECO_VAR_1);
48
+ const deco2 = pick(DECO_VAR_2);
49
+ const tokens = {
50
+ // 少于 3 个颜色当"没读到"(宁可回落内置主题调色板,也不要半个调色板)
51
+ colors: colors.length >= 3 ? colors : null,
52
+ dark: scheme ? scheme !== "light" : null,
53
+ bg: pick("--brd-bg") || null,
54
+ tooltipBg: pick(TOOLTIP_BG_VAR) || null,
55
+ tooltipBorder: pick(TOOLTIP_BORDER_VAR) || null,
56
+ frameColors: frame1 ? [frame1, frame2 || "transparent"] : null,
57
+ decoColors: deco1 ? [deco1, deco2 || deco1] : null
58
+ };
59
+ document.body.removeChild(probe);
60
+ return tokens;
61
+ }
62
+ function ensureTheme(code) {
63
+ const key = String(code ?? "");
64
+ if (registered[key]) return themeOf(key);
65
+ registered[key] = true;
66
+ const tokens = readThemeTokens(key);
67
+ if (tokens) setThemeTokens(key, tokens);
68
+ return themeOf(key);
69
+ }
70
+ function ensureThemeCss(code) {
71
+ const key = String(code ?? "").trim();
72
+ if (!key || typeof document === "undefined") return Promise.resolve(false);
73
+ const url = new URL(`${key}.css`, themeBaseUrl()).href;
74
+ const exist = document.querySelector(
75
+ `link[data-board-theme-css="${key}"], link[href$="/${key}.css"]`
76
+ );
77
+ if (exist) return Promise.resolve(true);
78
+ const pending = cssLoading.get(key);
79
+ if (pending) return pending;
80
+ const task = new Promise((resolve) => {
81
+ const link = document.createElement("link");
82
+ link.rel = "stylesheet";
83
+ link.href = url;
84
+ link.setAttribute("data-board-theme-css", key);
85
+ let timer;
86
+ const done = (ok) => {
87
+ clearTimeout(timer);
88
+ resolve(ok);
89
+ };
90
+ timer = setTimeout(() => done(false), 3e3);
91
+ link.onload = () => done(true);
92
+ link.onerror = () => done(false);
93
+ document.head.appendChild(link);
94
+ }).then((ok) => {
95
+ registered[key] = false;
96
+ ensureTheme(key);
97
+ return ok;
98
+ });
99
+ cssLoading.set(key, task);
100
+ return task;
101
+ }
102
+ function loadManifest() {
103
+ if (manifestPromise) return manifestPromise;
104
+ manifestPromise = (async () => {
105
+ try {
106
+ const res = await fetch(themeBaseUrl() + "index.json", { cache: "no-store" });
107
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
108
+ return parseManifest(await res.json());
109
+ } catch (error) {
110
+ console.warn("[design-board] 读不到主题清单 index.json(只列内置主题):", (error == null ? void 0 : error.message) ?? error);
111
+ return [];
112
+ }
113
+ })();
114
+ return manifestPromise;
115
+ }
116
+ function resetThemeFileCache() {
117
+ manifestPromise = null;
118
+ cssLoading.clear();
119
+ Object.keys(registered).forEach((k) => delete registered[k]);
120
+ }
121
+ export {
122
+ DEFAULT_THEME_BASE,
123
+ ensureTheme,
124
+ ensureThemeCss,
125
+ loadManifest,
126
+ readThemeTokens,
127
+ resetThemeFileCache,
128
+ themeBaseUrl
129
+ };
@@ -0,0 +1,32 @@
1
+ const ASSET_PREFIX = "asset:";
2
+ function isAssetRef(v) {
3
+ return typeof v === "string" && v.slice(0, ASSET_PREFIX.length) === ASSET_PREFIX;
4
+ }
5
+ function assetPath(v) {
6
+ return isAssetRef(v) ? String(v).slice(ASSET_PREFIX.length) : "";
7
+ }
8
+ function toAssetRef(path) {
9
+ const p = String(path ?? "").replace(/^\/+/, "");
10
+ return p ? ASSET_PREFIX + p : "";
11
+ }
12
+ function assetName(v) {
13
+ if (!isAssetRef(v)) return "";
14
+ const p = assetPath(v);
15
+ const i = p.lastIndexOf("/");
16
+ return i >= 0 ? p.slice(i + 1) : p;
17
+ }
18
+ function assetUrl(v, base) {
19
+ const s = typeof v === "string" ? v.trim() : "";
20
+ if (!s) return "";
21
+ if (!isAssetRef(s)) return s;
22
+ const prefix = String(base ?? "").replace(/\/+$/, "");
23
+ return `${prefix}/design/board/asset/file?path=${encodeURIComponent(assetPath(s))}`;
24
+ }
25
+ export {
26
+ ASSET_PREFIX,
27
+ assetName,
28
+ assetPath,
29
+ assetUrl,
30
+ isAssetRef,
31
+ toAssetRef
32
+ };
@@ -0,0 +1,377 @@
1
+ import { themeOf, measureLabel } from "./schema.js";
2
+ function fmtNum(v, numFormat) {
3
+ if (v === null || v === void 0 || v === "") return v;
4
+ const n = Number(v);
5
+ if (Number.isNaN(n)) return v;
6
+ const f = String(numFormat || "");
7
+ const m = /\.(\d+)/.exec(f);
8
+ const dec = m ? m[1].length : null;
9
+ if (f.indexOf("%") >= 0) return (n * 100).toFixed(dec === null ? 0 : dec) + "%";
10
+ const digits = dec === null ? Number.isInteger(n) ? 0 : 2 : dec;
11
+ const s = n.toFixed(digits);
12
+ return f.indexOf(",") >= 0 ? s.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : s;
13
+ }
14
+ function withAlpha(color, a) {
15
+ const m = /^#([0-9a-fA-F]{6})$/.exec(String(color || ""));
16
+ if (!m) return color;
17
+ const n = parseInt(m[1], 16);
18
+ return `rgba(${n >> 16 & 255}, ${n >> 8 & 255}, ${n & 255}, ${a})`;
19
+ }
20
+ function barFill(color, horizontal) {
21
+ return {
22
+ type: "linear",
23
+ x: 0,
24
+ y: 0,
25
+ x2: horizontal ? 1 : 0,
26
+ y2: horizontal ? 0 : 1,
27
+ colorStops: [
28
+ { offset: 0, color: withAlpha(color, 0.98) },
29
+ { offset: 1, color: withAlpha(color, 0.45) }
30
+ ]
31
+ };
32
+ }
33
+ function buildChartOption(rows, columns, part, themeCode, opts) {
34
+ var _a;
35
+ const theme = themeOf(themeCode);
36
+ const data = part.data || {};
37
+ const view = part.view || {};
38
+ const chart = view.chart || "bar";
39
+ const narrow = !!(opts && opts.narrow);
40
+ const dims = (data.dims || []).slice();
41
+ const measures = (data.measures || []).slice();
42
+ const dim = dims[0];
43
+ const measure = measures[0];
44
+ const aliasOf = (name) => {
45
+ const c = (columns || []).find((x) => x.name === name);
46
+ return c && c.alias || name;
47
+ };
48
+ const labelOfMeasure = (m) => measureLabel(m, aliasOf(m && (m.alias || m.field)));
49
+ const dimName = dim ? aliasOf(dim) : "";
50
+ const measureName = measure ? aliasOf(measure.alias || measure.field) : "";
51
+ const valueOf = (row, key) => {
52
+ if (!key) return null;
53
+ if (row[key] !== void 0) return row[key];
54
+ const hit = Object.keys(row).find((k) => k.toLowerCase() === String(key).toLowerCase());
55
+ return hit ? row[hit] : null;
56
+ };
57
+ const categories = rows.map((r) => String(valueOf(r, dim) === null ? "" : valueOf(r, dim)));
58
+ const values = rows.map((r) => {
59
+ const v = valueOf(r, measure ? measure.alias || measure.field : "");
60
+ const n = Number(v);
61
+ return Number.isNaN(n) ? 0 : n;
62
+ });
63
+ (_a = (columns || []).find((c) => c.name === (measure && (measure.alias || measure.field)))) == null ? void 0 : _a.numFormat;
64
+ const cartesian = ["bar", "hbar", "line", "area"].indexOf(chart) >= 0;
65
+ const msList = cartesian ? measures.filter((m) => m && m.field) : [];
66
+ const MIX_TYPES = ["bar", "line", "area"];
67
+ const seriesTypeOf = (m, base) => m && MIX_TYPES.indexOf(m.type) >= 0 ? m.type : base;
68
+ const seriesOfMeasure = (type, extra, styleOf) => (msList.length ? msList : [measure || {}]).map((m, i) => {
69
+ var _a2;
70
+ const key = m.alias || m.field;
71
+ const fmt = (_a2 = (columns || []).find((c) => c.name === key)) == null ? void 0 : _a2.numFormat;
72
+ const data2 = rows.map((r) => {
73
+ const n = Number(valueOf(r, key));
74
+ return Number.isNaN(n) ? 0 : n;
75
+ });
76
+ const ex = Object.assign({}, extra);
77
+ const exLabel = ex.label;
78
+ delete ex.label;
79
+ const color = theme.colors[i % theme.colors.length];
80
+ const styled = typeof styleOf === "function" ? styleOf(color) : {};
81
+ return Object.assign({
82
+ name: msList.length ? labelOfMeasure(m) : measureName,
83
+ type: seriesTypeOf(m, type),
84
+ // 双 Y 轴:第二个度量挂到右轴(更多度量仍放左轴)
85
+ yAxisIndex: view.dualAxis && msList.length > 1 && i === 1 ? 1 : 0,
86
+ // 堆叠:同一个 stack 名才会叠起来
87
+ stack: view.stack ? "total" : void 0,
88
+ label: Object.assign({ show: !!view.showLabel, color: theme.text, formatter: (p) => fmtNum(p.value, fmt) }, exLabel || {}),
89
+ data: data2
90
+ }, ex, styled);
91
+ });
92
+ const yAxisOf = (extra) => {
93
+ if (view.dualAxis && msList.length > 1) {
94
+ return [
95
+ Object.assign({ type: "value" }, valueAxis, {}),
96
+ Object.assign({ type: "value" }, valueAxis, { splitLine: { show: false } }, {})
97
+ ];
98
+ }
99
+ return Object.assign({ type: "value" }, valueAxis, {});
100
+ };
101
+ ({
102
+ color: theme.text
103
+ });
104
+ const axisCommon = {
105
+ axisLine: { show: false },
106
+ axisTick: { show: false },
107
+ axisLabel: { color: theme.subText, fontSize: 12, fontFamily: "微软雅黑" },
108
+ splitLine: { show: false }
109
+ };
110
+ const valueAxis = {
111
+ ...axisCommon,
112
+ splitLine: { lineStyle: { color: theme.splitLine, type: "dashed" } }
113
+ };
114
+ const tooltip = {
115
+ trigger: ["pie", "donut", "rose", "funnel", "pyramid"].indexOf(chart) >= 0 ? "item" : "axis",
116
+ // 深色半透明提示框:ECharts 默认白底方框在深色大屏上非常跳(用户截图里那个就是)
117
+ backgroundColor: theme.tooltipBg || "rgba(9,17,31,0.94)",
118
+ borderColor: theme.tooltipBorder || theme.splitLine,
119
+ borderWidth: 1,
120
+ padding: [8, 12],
121
+ textStyle: { color: theme.text, fontSize: 12, fontFamily: "微软雅黑" },
122
+ extraCssText: "border-radius:10px;box-shadow:0 12px 30px rgba(2,8,20,0.45);backdrop-filter:blur(8px);",
123
+ axisPointer: {
124
+ type: "line",
125
+ lineStyle: { color: theme.splitLine, width: 1, type: "dashed" },
126
+ label: { backgroundColor: theme.colors[0], color: "#fff", borderRadius: 4, padding: [3, 6] }
127
+ }
128
+ };
129
+ const legend = {
130
+ show: view.showLegend !== false,
131
+ textStyle: { color: theme.subText, fontSize: 12, fontFamily: "微软雅黑" },
132
+ icon: "circle",
133
+ itemWidth: 8,
134
+ itemHeight: 8,
135
+ itemGap: 16,
136
+ // 窄屏图例挪到底部横排:右侧竖排图例在 366px 宽里会吃掉 1/3 宽度
137
+ top: narrow ? void 0 : 2,
138
+ bottom: narrow ? 0 : void 0,
139
+ type: "scroll"
140
+ };
141
+ switch (chart) {
142
+ case "pie":
143
+ case "donut":
144
+ case "rose": {
145
+ const isDonut = chart === "donut";
146
+ const tooManyLabels = categories.length > 8;
147
+ const bottomLegend = narrow || tooManyLabels;
148
+ return {
149
+ color: theme.colors,
150
+ tooltip,
151
+ legend: bottomLegend ? { ...legend, orient: "horizontal", left: "center" } : { ...legend, orient: "vertical", right: 8, top: "middle" },
152
+ series: [
153
+ {
154
+ type: "pie",
155
+ radius: isDonut ? ["42%", "68%"] : chart === "rose" ? ["18%", "70%"] : "68%",
156
+ center: bottomLegend ? ["50%", "42%"] : ["40%", "52%"],
157
+ roseType: chart === "rose" ? "radius" : void 0,
158
+ avoidLabelOverlap: true,
159
+ // 扇形之间留 2px 底色缝 + 圆角:比"紧贴 + 1px 描边"现代得多;再加同色辉光(大屏观感)
160
+ itemStyle: {
161
+ borderColor: theme.bg || "#070c18",
162
+ borderWidth: 2,
163
+ borderRadius: 6,
164
+ shadowBlur: 16,
165
+ shadowColor: withAlpha(theme.colors[0], 0.55)
166
+ },
167
+ // 类目太多 → 干脆不画;窄屏 → 放进扇形里(外侧会被容器裁掉)
168
+ label: tooManyLabels ? { show: false } : narrow ? { show: view.showLabel !== false, position: "inside", color: "#fff", fontSize: 12, formatter: "{b}\n{d}%" } : { show: view.showLabel !== false, color: theme.text, fontSize: 12, formatter: "{b}\n{d}%" },
169
+ labelLine: { lineStyle: { color: theme.splitLine } },
170
+ data: rows.map((r, i) => ({
171
+ name: categories[i],
172
+ value: values[i]
173
+ }))
174
+ }
175
+ ]
176
+ };
177
+ }
178
+ case "funnel":
179
+ case "pyramid": {
180
+ const isPyramid = chart === "pyramid";
181
+ return {
182
+ color: theme.colors,
183
+ tooltip,
184
+ legend,
185
+ series: [
186
+ {
187
+ type: "funnel",
188
+ left: "12%",
189
+ width: "76%",
190
+ top: 40,
191
+ bottom: 16,
192
+ sort: isPyramid ? "ascending" : "descending",
193
+ gap: isPyramid ? 0 : 2,
194
+ label: Object.assign(
195
+ { color: theme.text, fontSize: 12, formatter: "{b}: {c}" },
196
+ // 金字塔的段是上窄下宽,标签放外面会被裁 ⇒ 默认放段内居中
197
+ isPyramid ? { position: "inside", color: "#04121f", fontWeight: 600 } : {}
198
+ ),
199
+ itemStyle: isPyramid ? { borderColor: "rgba(7,12,24,0.6)", borderWidth: 1 } : { borderColor: theme.bg || "#070c18", borderWidth: 2, borderRadius: 4 },
200
+ data: rows.map((r, i) => ({ name: categories[i], value: values[i] }))
201
+ }
202
+ ]
203
+ };
204
+ }
205
+ case "radar": {
206
+ const max = Math.max(1, ...values);
207
+ return {
208
+ color: theme.colors,
209
+ tooltip,
210
+ legend,
211
+ radar: {
212
+ indicator: rows.map((r, i) => ({ name: categories[i], max: Math.ceil(max * 1.2) })),
213
+ axisName: { color: theme.subText },
214
+ splitLine: { lineStyle: { color: theme.splitLine } },
215
+ splitArea: { areaStyle: { color: ["transparent"] } },
216
+ axisLine: { lineStyle: { color: theme.splitLine } }
217
+ },
218
+ series: [
219
+ {
220
+ type: "radar",
221
+ areaStyle: { opacity: 0.25 },
222
+ data: [{ value: values, name: measureName }]
223
+ }
224
+ ]
225
+ };
226
+ }
227
+ case "scatter": {
228
+ return {
229
+ color: theme.colors,
230
+ tooltip,
231
+ grid: { left: 50, right: 20, top: 40, bottom: 40 },
232
+ xAxis: { type: "category", data: categories, ...axisCommon, name: dimName, nameTextStyle: { color: theme.subText } },
233
+ yAxis: { type: "value", ...valueAxis, name: measureName, nameTextStyle: { color: theme.subText } },
234
+ series: [{ type: "scatter", symbolSize: 11, itemStyle: { color: withAlpha(theme.colors[0], 0.85), borderColor: theme.colors[0], borderWidth: 1 }, data: values }]
235
+ };
236
+ }
237
+ case "gauge": {
238
+ const last = values.length ? values[values.length - 1] : 0;
239
+ const max = Math.max(1, ...values);
240
+ return {
241
+ color: theme.colors,
242
+ series: [
243
+ {
244
+ type: "gauge",
245
+ min: 0,
246
+ max: Math.ceil(max * 1.2),
247
+ progress: {
248
+ show: true,
249
+ width: 14,
250
+ roundCap: true,
251
+ itemStyle: { color: barFill(theme.colors[0], false) }
252
+ },
253
+ axisLine: { roundCap: true, lineStyle: { width: 14, color: [[1, theme.splitLine]] } },
254
+ axisLabel: { color: theme.subText, fontSize: 10, distance: 16 },
255
+ axisTick: { show: false },
256
+ splitLine: { length: 10, lineStyle: { color: theme.splitLine } },
257
+ pointer: { width: 4, length: "58%", itemStyle: { color: theme.colors[0] } },
258
+ anchor: { show: true, size: 10, itemStyle: { color: theme.colors[0] } },
259
+ detail: { valueAnimation: true, color: theme.text, fontSize: 26, fontWeight: 600, offsetCenter: [0, "68%"] },
260
+ title: { color: theme.subText, fontSize: 12, offsetCenter: [0, "90%"] },
261
+ // 中心的名称优先用配置里的显示名(measure.label,如「平均年龄」):
262
+ // 度量别名(alias)是 SQL 列别名、只能是字母数字下划线,拿它当标题会显示成 avg_age / cnt。
263
+ data: [{ value: last, name: measure && measure.label || measureName }]
264
+ }
265
+ ]
266
+ };
267
+ }
268
+ case "ring": {
269
+ const pct = Math.max(0, Math.min(100, values.length ? Number(values[values.length - 1]) : 0));
270
+ return {
271
+ color: theme.colors,
272
+ series: [
273
+ {
274
+ type: "gauge",
275
+ startAngle: 90,
276
+ endAngle: -270,
277
+ min: 0,
278
+ max: 100,
279
+ radius: "82%",
280
+ pointer: { show: false },
281
+ progress: { show: true, overlap: false, roundCap: true, clip: false, width: 10, itemStyle: { color: barFill(theme.colors[0], false) } },
282
+ axisLine: { lineStyle: { width: 10, color: [[1, theme.splitLine]] } },
283
+ splitLine: { show: false },
284
+ axisTick: { show: false },
285
+ axisLabel: { show: false },
286
+ anchor: { show: false },
287
+ detail: {
288
+ valueAnimation: true,
289
+ offsetCenter: [0, 0],
290
+ color: theme.text,
291
+ fontSize: 26,
292
+ fontWeight: 600,
293
+ formatter: (v) => Number(v).toFixed(1) + "%"
294
+ },
295
+ title: { show: false },
296
+ data: [{ value: pct, name: measure && measure.label || measureName }]
297
+ }
298
+ ]
299
+ };
300
+ }
301
+ case "line":
302
+ case "area": {
303
+ return {
304
+ color: theme.colors,
305
+ tooltip,
306
+ legend,
307
+ grid: { left: 56, right: view.dualAxis && msList.length > 1 ? 48 : 24, top: 40, bottom: 36 },
308
+ xAxis: { type: "category", data: categories, boundaryGap: false, ...axisCommon },
309
+ yAxis: yAxisOf(),
310
+ series: seriesOfMeasure("line", {
311
+ smooth: !!view.smooth,
312
+ showSymbol: rows.length <= 40,
313
+ symbolSize: 6
314
+ }, (c) => ({
315
+ itemStyle: { color: c, shadowBlur: 14, shadowColor: withAlpha(c, 0.65) },
316
+ // 发光线:shadowBlur 让折线在大屏上"亮"起来(纯色细线在深色底上太安静)
317
+ lineStyle: { width: 2.5, color: c, shadowBlur: 12, shadowColor: withAlpha(c, 0.75) },
318
+ // 面积图:从主题色渐到透明(比原来整块 0.22 透明度干净很多)
319
+ areaStyle: chart === "area" ? {
320
+ color: {
321
+ type: "linear",
322
+ x: 0,
323
+ y: 0,
324
+ x2: 0,
325
+ y2: 1,
326
+ colorStops: [{ offset: 0, color: withAlpha(c, 0.42) }, { offset: 1, color: withAlpha(c, 0.02) }]
327
+ }
328
+ } : void 0
329
+ }))
330
+ };
331
+ }
332
+ case "hbar": {
333
+ return {
334
+ color: theme.colors,
335
+ tooltip,
336
+ legend,
337
+ grid: { left: 90, right: 30, top: 40, bottom: 30 },
338
+ xAxis: yAxisOf(),
339
+ yAxis: { type: "category", data: categories, ...axisCommon },
340
+ series: seriesOfMeasure("bar", {
341
+ barMaxWidth: 22,
342
+ label: { show: !!view.showLabel, position: "right", color: theme.text }
343
+ }, (c) => ({ itemStyle: { borderRadius: [0, 6, 6, 0], color: barFill(c, true), shadowBlur: 12, shadowColor: withAlpha(c, 0.45), shadowOffsetX: 2 } }))
344
+ };
345
+ }
346
+ case "bar":
347
+ default: {
348
+ return {
349
+ color: theme.colors,
350
+ tooltip,
351
+ legend,
352
+ // 类目多的时候不能让每个标签都画(原来写死 interval:0):28 个系统名列在几百像素宽的
353
+ // 部件里会糊成一团黑块。>10 个类目就交给 ECharts 自动抽稀,并斜排。
354
+ grid: { left: 56, right: view.dualAxis && msList.length > 1 ? 48 : 24, top: 40, bottom: 36 },
355
+ xAxis: {
356
+ type: "category",
357
+ data: categories,
358
+ ...axisCommon,
359
+ axisLabel: {
360
+ color: theme.subText,
361
+ interval: categories.length > 10 ? "auto" : 0,
362
+ rotate: categories.length > 8 ? 30 : 0
363
+ }
364
+ },
365
+ yAxis: yAxisOf(),
366
+ series: seriesOfMeasure("bar", {
367
+ barMaxWidth: 34,
368
+ label: { show: !!view.showLabel, position: "top", color: theme.text }
369
+ }, (c) => ({ itemStyle: { borderRadius: [6, 6, 0, 0], color: barFill(c, false), shadowBlur: 12, shadowColor: withAlpha(c, 0.45), shadowOffsetY: 2 } }))
370
+ };
371
+ }
372
+ }
373
+ }
374
+ export {
375
+ buildChartOption,
376
+ fmtNum
377
+ };
@@ -0,0 +1,67 @@
1
+ function buildPartQuery(part, params = {}) {
2
+ const d = part == null ? void 0 : part.data;
3
+ if (!d || !d.setId) return null;
4
+ const measures = (d.measures ?? []).filter((m) => m && m.field);
5
+ const first = measures[0];
6
+ return {
7
+ setId: d.setId,
8
+ dims: (d.dims ?? []).filter(Boolean),
9
+ measures,
10
+ filters: (d.filters ?? []).filter((f) => f && f.field),
11
+ // 部件自身参数优先,其次看板级「筛选方案」参数
12
+ params: { ...params, ...d.params ?? {} },
13
+ orderField: d.orderField || (first ? first.alias || first.field : ""),
14
+ orderDesc: !!d.orderDesc,
15
+ limit: d.limit || 1e3
16
+ };
17
+ }
18
+ function createBoardDataHub(options) {
19
+ const ttl = options.ttl ?? 1500;
20
+ const now = options.now ?? (() => Date.now());
21
+ const inflight = /* @__PURE__ */ new Map();
22
+ const cache = /* @__PURE__ */ new Map();
23
+ const currentParams = () => {
24
+ var _a;
25
+ return ((_a = options.params) == null ? void 0 : _a.all()) ?? {};
26
+ };
27
+ return {
28
+ queryOf(part) {
29
+ return buildPartQuery(part, currentParams());
30
+ },
31
+ async fetch(part, force) {
32
+ const body = buildPartQuery(part, currentParams());
33
+ if (!body) return null;
34
+ const key = JSON.stringify(body);
35
+ if (!force) {
36
+ const hit = cache.get(key);
37
+ if (hit && now() - hit.t < ttl) return hit.v;
38
+ }
39
+ const running = inflight.get(key);
40
+ if (running) return running;
41
+ const task = options.fetchData(body).then((res) => {
42
+ const v = res ?? null;
43
+ cache.set(key, { t: now(), v });
44
+ return v;
45
+ }).catch((error) => {
46
+ console.warn("[design-board] 部件取数失败:", error);
47
+ return null;
48
+ }).then((v) => {
49
+ inflight.delete(key);
50
+ return v;
51
+ });
52
+ inflight.set(key, task);
53
+ return task;
54
+ },
55
+ clear() {
56
+ cache.clear();
57
+ inflight.clear();
58
+ },
59
+ stats() {
60
+ return { cacheSize: cache.size, inflightSize: inflight.size };
61
+ }
62
+ };
63
+ }
64
+ export {
65
+ buildPartQuery,
66
+ createBoardDataHub
67
+ };
@@ -0,0 +1,51 @@
1
+ const DATAV_BORDER_META = {
2
+ 1: "圆角切角",
3
+ 2: "内衬双层直角",
4
+ 3: "双层叠框",
5
+ 4: "左上折角",
6
+ 5: "上下亮线",
7
+ 6: "细描边+顶光",
8
+ 7: "渐变描边",
9
+ 8: "上下短亮线",
10
+ 9: "四角括号",
11
+ 10: "圆角发光",
12
+ 11: "箭头面板(重)",
13
+ 12: "圆角发光(细)",
14
+ 13: "切角面板"
15
+ };
16
+ const DATAV_DECORATION_META = {
17
+ 1: { label: "飞线点阵", size: [260, 60] },
18
+ 2: { label: "中分横线", size: [280, 40] },
19
+ 3: { label: "频谱条", size: [300, 35] },
20
+ 4: { label: "竖向分隔线", size: [16, 140] },
21
+ 5: { label: "阶梯折线", size: [280, 60] },
22
+ 6: { label: "声波条", size: [300, 35] },
23
+ 7: { label: "角标箭头(固定 21×20)", size: [60, 60] },
24
+ 8: { label: "流线", size: [280, 70] },
25
+ 9: { label: "科技环(正方形)", size: [150, 150] },
26
+ 10: { label: "横轴双线", size: [280, 60] },
27
+ 11: { label: "箭头框", size: [280, 80] },
28
+ 12: { label: "雷达扫描(正方形)", size: [170, 170] }
29
+ };
30
+ const DATAV_BORDER_DEFAULT = 8;
31
+ const DATAV_DECORATION_DEFAULT = 5;
32
+ function datavBorderLabel(n) {
33
+ return DATAV_BORDER_META[Number(n)] ?? `边框 ${Number(n) || DATAV_BORDER_DEFAULT}`;
34
+ }
35
+ function datavDecorationLabel(n) {
36
+ const m = DATAV_DECORATION_META[Number(n)] ?? DATAV_DECORATION_META[DATAV_DECORATION_DEFAULT];
37
+ return m.label;
38
+ }
39
+ function datavDecorationSizeOf(n) {
40
+ const m = DATAV_DECORATION_META[Number(n)] ?? DATAV_DECORATION_META[DATAV_DECORATION_DEFAULT];
41
+ return [m.size[0], m.size[1]];
42
+ }
43
+ export {
44
+ DATAV_BORDER_DEFAULT,
45
+ DATAV_BORDER_META,
46
+ DATAV_DECORATION_DEFAULT,
47
+ DATAV_DECORATION_META,
48
+ datavBorderLabel,
49
+ datavDecorationLabel,
50
+ datavDecorationSizeOf
51
+ };