@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,331 @@
1
+ import PartNode from "./part-node.vue.js";
2
+ import { parseConfig, STAGE_BG, gridAutoRows, DEFAULT_THEME, isMobileStack, visualOrder, partVisible } from "../board-engine/schema.js";
3
+ import { DEFAULT_LOCALE, LOCALES, localizePart } from "../board-engine/i18n.js";
4
+ import { fetchBoardInfo, boardAssetUrl, boardAssetBaseUrl, fetchBoardData, fetchBoardParams } from "./api.js";
5
+ import { ensureThemeCss, ensureTheme } from "./theme-host.js";
6
+ import { DESIGN_BOARD_CTX } from "./context.js";
7
+ import { createBoardParams } from "../board-engine/params.js";
8
+ import { createBoardDataHub } from "../board-engine/data-hub.js";
9
+ import { createParamCatalog } from "../board-engine/param-catalog.js";
10
+ const _sfc_main = {
11
+ name: "DesignBoard",
12
+ components: { PartNode },
13
+ // 每个看板实例一套运行时上下文(参数 / 参数目录 / 取数缓存),见 ./context.ts
14
+ provide() {
15
+ return { [DESIGN_BOARD_CTX]: this.boardCtx };
16
+ },
17
+ props: {
18
+ /**
19
+ * 看板编码。迁入 design-ui 后不再读 vue-router(宿主刻意不上 router),由宿主传入:
20
+ * ① 新窗口预览/大屏:`board.html?type=board&action=view&code=xxx`(看板独立入口,无设计器外壳)
21
+ * —— 经 BoardApp 解析后传 :code;用 index.html 开同样参数也能跑,只是左侧会多出设计端页签。
22
+ * ② 语言:原 `?lang=` 由宿主解析成 :lang 传入(见 load() 里的语言优先级)。
23
+ */
24
+ code: { type: String, default: "" },
25
+ lang: { type: String, default: "" }
26
+ },
27
+ data() {
28
+ const params = createBoardParams();
29
+ const catalog = createParamCatalog({ fetchParams: fetchBoardParams });
30
+ const dataHub = createBoardDataHub({ fetchData: fetchBoardData, params });
31
+ return {
32
+ boardCtx: {
33
+ params,
34
+ catalog,
35
+ dataHub,
36
+ assetBaseUrl: boardAssetBaseUrl(),
37
+ locale: DEFAULT_LOCALE
38
+ },
39
+ /** 参数版本号:往下透传给 PartNode/PartRenderer(引擎 state 是普通对象,Vue 追踪不到) */
40
+ paramsVersion: 0,
41
+ board: {},
42
+ config: parseConfig(null),
43
+ loading: true,
44
+ errorMessage: "",
45
+ refreshKey: 0,
46
+ toolbarHidden: false,
47
+ scale: 1,
48
+ // 栅格模式下的视口宽度:用来判断是否窄屏(<768px 一律堆叠成单列)
49
+ viewportWidth: 0,
50
+ locale: DEFAULT_LOCALE,
51
+ locales: LOCALES,
52
+ resizeHandler: null
53
+ };
54
+ },
55
+ computed: {
56
+ visibleParts() {
57
+ void this.paramsVersion;
58
+ return this.config.parts.filter((p) => !p.hidden).filter((p) => partVisible(p, this.boardCtx.params.state.values)).map((p) => localizePart(p, this.locale));
59
+ },
60
+ /** 顶层部件(子级由 PartNode 递归渲染)。堆叠模式下按视觉顺序排,手机上读到的顺序才和大屏一致 */
61
+ topParts() {
62
+ const tops = this.visibleParts.filter((p) => !p.parentId);
63
+ return this.stacked ? visualOrder(tops) : tops;
64
+ },
65
+ /** 当前生效的看板参数(下钻/筛选写进去的),用于工具栏chip 与一键清空 */
66
+ activeParams() {
67
+ void this.paramsVersion;
68
+ const v = this.boardCtx.params.state.values || {};
69
+ return Object.keys(v).filter((k) => v[k] !== "" && v[k] !== null && v[k] !== void 0).map((k) => ({ code: k, value: v[k] }));
70
+ },
71
+ /**
72
+ * 看板参数版本号。
73
+ * ⚠️ 必须走 computed:paramState 是 import 进来的模块对象,
74
+ * `watch: { 'paramState.version'(){} }` 这种字符串路径是相对组件实例解析的,无效(踩过)。
75
+ */
76
+ paramVersion() {
77
+ return this.paramsVersion;
78
+ },
79
+ scaleMode() {
80
+ return this.config.page.scaleMode || "auto";
81
+ },
82
+ isGrid() {
83
+ return (this.config.page.layoutMode || "free") === "grid";
84
+ },
85
+ /** 窄屏(手机/竖屏):栅格模式下一律堆叠成单列 */
86
+ narrow() {
87
+ return this.isGrid && this.viewportWidth > 0 && this.viewportWidth < 768;
88
+ },
89
+ /** 移动端策略自动/总是堆叠('scale' = 老行为:整页等比缩放) */
90
+ stacked() {
91
+ return isMobileStack(this.config.page, this.viewportWidth);
92
+ },
93
+ /** 堆叠时整页走"单列 + 纵向流式",与页面原本是 free 还是 grid 无关 */
94
+ useGridFlow() {
95
+ return this.isGrid || this.stacked;
96
+ },
97
+ /** 手机上工具栏压到左上角并放大点击区域(原来固定右上角 + 12px 字体,手机上难点也不显眼) */
98
+ isMobile() {
99
+ return this.viewportWidth > 0 && this.viewportWidth < 768;
100
+ },
101
+ scrollStyle() {
102
+ if (this.useGridFlow) {
103
+ return { overflow: "auto", display: "block" };
104
+ }
105
+ if (this.scaleMode === "none") {
106
+ return { overflow: "auto", display: "block" };
107
+ }
108
+ return { overflow: "hidden", display: "flex", alignItems: "center", justifyContent: "center" };
109
+ },
110
+ holderStyle() {
111
+ const p = this.config.page;
112
+ if (this.useGridFlow) {
113
+ return { width: "100%", minHeight: "100%", position: "relative" };
114
+ }
115
+ return { width: p.width * this.scale + "px", height: p.height * this.scale + "px", position: "relative", flex: "0 0 auto" };
116
+ },
117
+ /**
118
+ * 当前主题代号:挂在舞台元素上(`data-board-theme`),主题 CSS 的变量就是从这里生效的。
119
+ * 视觉用 page.theme(图表用 page.chartTheme,两者在设计器里是一起切的)。
120
+ */
121
+ themeCode() {
122
+ const p = this.config.page || {};
123
+ return p.theme || p.chartTheme || DEFAULT_THEME;
124
+ },
125
+ canvasStyle() {
126
+ const p = this.config.page;
127
+ const bgImg = boardAssetUrl(p.bgImage);
128
+ const bg = bgImg ? `url(${bgImg}) center/cover no-repeat, ${p.bgColor}` : p.bgColor;
129
+ const op = (p.opacity === void 0 ? 100 : p.opacity) / 100;
130
+ if (this.useGridFlow) {
131
+ const cols = this.stacked || this.narrow ? 1 : p.gridCols || 24;
132
+ const gap = p.gridGap === void 0 ? 12 : p.gridGap;
133
+ return {
134
+ width: "100%",
135
+ // 栅格内容比容器矮时必须铺满,否则底部会露出 stage 底色(实测固定行高时露 52px)
136
+ // 用 100% 而不是 100vh:独立窗口预览时两者等价(#app 是 100%),而在宿主页签里
137
+ // 100vh 会溢出(页签不是整个视口),改成百分比才跟着容器走。
138
+ minHeight: "100%",
139
+ display: "grid",
140
+ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
141
+ // 堆叠(手机)时行高必须跟着部件走:固定 60px 行高 + 320px 高的图表
142
+ // ⇒ 部件溢出自己的行、压到下一个部件上(实测截图里饼图盖住了下面的表格)
143
+ gridAutoRows: this.stacked ? "auto" : gridAutoRows(p),
144
+ gap: gap + "px",
145
+ // 手机上工具栏浮在顶部:给它留出高度,否则第一个部件会被压在工具栏下面
146
+ padding: (this.isMobile ? 44 : gap) + "px " + gap + "px " + gap + "px",
147
+ boxSizing: "border-box",
148
+ background: bg,
149
+ // 底纹(--brd-decor)由主题 CSS 给,画在 .board-canvas::before 上:
150
+ // 直接叠进 background 会被 page.bgColor 这个"最后写入的颜色层"盖掉
151
+ opacity: op,
152
+ position: "relative"
153
+ };
154
+ }
155
+ return {
156
+ width: p.width + "px",
157
+ height: p.height + "px",
158
+ transform: `scale(${this.scale})`,
159
+ transformOrigin: "0 0",
160
+ position: "absolute",
161
+ left: 0,
162
+ top: 0,
163
+ overflow: "hidden",
164
+ background: bg,
165
+ opacity: op
166
+ };
167
+ },
168
+ stageStyle() {
169
+ const p = this.config.page;
170
+ return { background: this.scaleMode === "none" ? p.bgColor : "var(--brd-bg, " + STAGE_BG + ")" };
171
+ }
172
+ },
173
+ created() {
174
+ this.unsubscribeParams = this.boardCtx.params.subscribe(() => {
175
+ this.paramsVersion += 1;
176
+ });
177
+ this.load();
178
+ },
179
+ watch: {
180
+ // 关键:宿主只换 :code(同一实例复用时)Vue 不会再走 created,
181
+ // 不监听就会"切了看板还是显示上一个"(实测踩到)。
182
+ code(val) {
183
+ if (val && val !== this.board.boardCode) {
184
+ this.load();
185
+ }
186
+ },
187
+ /** 看板参数一变就把"当前页"写回 URL(只写配置声明过的编码) */
188
+ paramVersion() {
189
+ this.syncUrlParams();
190
+ }
191
+ },
192
+ mounted() {
193
+ this.onResize();
194
+ this.resizeHandler = () => this.onResize();
195
+ window.addEventListener("resize", this.resizeHandler);
196
+ document.addEventListener("fullscreenchange", this.onResize);
197
+ },
198
+ beforeUnmount() {
199
+ window.removeEventListener("resize", this.resizeHandler);
200
+ document.removeEventListener("fullscreenchange", this.onResize);
201
+ if (this.unsubscribeParams) this.unsubscribeParams();
202
+ },
203
+ methods: {
204
+ onResize() {
205
+ if (typeof window === "undefined") return;
206
+ this.viewportWidth = window.innerWidth || (this.$refs.stage ? this.$refs.stage.clientWidth : 0);
207
+ this.fit();
208
+ },
209
+ async load() {
210
+ const code = this.code;
211
+ this.loading = true;
212
+ try {
213
+ const info = code ? await fetchBoardInfo(code) : null;
214
+ if (info) {
215
+ this.board = info;
216
+ const cfg = parseConfig(info.configJson, {
217
+ width: info.pageWidth,
218
+ height: info.pageHeight
219
+ });
220
+ const page = cfg.page || {};
221
+ await Promise.all([ensureThemeCss(page.chartTheme), ensureThemeCss(page.theme)]);
222
+ ensureTheme(page.chartTheme);
223
+ ensureTheme(page.theme);
224
+ this.config = cfg;
225
+ this.applyUrlParams();
226
+ const q = { lang: this.lang || "" };
227
+ const fallback = this.config.page && this.config.page.locale || DEFAULT_LOCALE;
228
+ this.locale = q.lang && LOCALES.some((l) => l.code === q.lang) ? q.lang : fallback;
229
+ const ids = (this.config.parts || []).map((p) => p.data && p.data.setId).filter(Boolean);
230
+ this.boardCtx.catalog.load(ids).catch(() => {
231
+ });
232
+ this.$nextTick(this.onResize);
233
+ } else {
234
+ this.errorMessage = "看板不存在或没有访问权限";
235
+ }
236
+ } catch (e) {
237
+ this.errorMessage = e.message || String(e);
238
+ } finally {
239
+ this.loading = false;
240
+ }
241
+ },
242
+ /** 按窗口尺寸算缩放比;scaleMode=none 或栅格/堆叠时不用缩放(靠 CSS 自适应) */
243
+ fit() {
244
+ if (this.scaleMode === "none" || this.useGridFlow) {
245
+ this.scale = 1;
246
+ return;
247
+ }
248
+ const el = this.$refs.stage;
249
+ if (!el) return;
250
+ const p = this.config.page;
251
+ const s = Math.min(el.clientWidth / p.width, el.clientHeight / p.height);
252
+ this.scale = this.scaleMode === "ratio" ? Math.min(1, s) : s;
253
+ },
254
+ refresh() {
255
+ this.boardCtx.dataHub.clear();
256
+ this.refreshKey += 1;
257
+ },
258
+ onLocaleChange(code) {
259
+ this.locale = code;
260
+ },
261
+ /** 清掉一个参数(下钻后想回到全量时用) */
262
+ clearParam(code) {
263
+ this.boardCtx.params.set(code, "");
264
+ },
265
+ /**
266
+ * URL 参数 → 看板参数(深链)。
267
+ * ⚠ 只认**配置里声明过**的参数编码(导航的 paramCode、部件的 visibleWhen.param、
268
+ * 筛选方案的 paramCode、数据集声明的参数),其余一律忽略 ——
269
+ * 否则会把 ?type=/?code=/?lang= 这些**宿主参数**也灌进看板参数里。
270
+ */
271
+ navParamCodes() {
272
+ const codes = /* @__PURE__ */ new Set();
273
+ for (const p of this.config.parts || []) {
274
+ const v = p.view || {};
275
+ if (p.type === "nav" && v.paramCode) codes.add(v.paramCode);
276
+ if (p.type === "filter" && v.paramCode) codes.add(v.paramCode);
277
+ if (v.visibleWhen && v.visibleWhen.param) codes.add(v.visibleWhen.param);
278
+ if (p.data && p.data.params) Object.keys(p.data.params).forEach((k) => codes.add(k));
279
+ }
280
+ return codes;
281
+ },
282
+ applyUrlParams() {
283
+ const sp = new URLSearchParams(location.search);
284
+ let n = 0;
285
+ for (const code of this.navParamCodes()) {
286
+ const v = sp.get(code);
287
+ if (v !== null && v !== "") {
288
+ this.boardCtx.params.set(code, v);
289
+ n += 1;
290
+ }
291
+ }
292
+ if (n > 0) this.boardCtx.dataHub.clear();
293
+ },
294
+ /** 导航参数写回 URL(replaceState,不产生历史记录):这样当前页可刷新、可分享 */
295
+ syncUrlParams() {
296
+ const sp = new URLSearchParams(location.search);
297
+ let changed = false;
298
+ for (const code of this.navParamCodes()) {
299
+ const cur = this.boardCtx.params.state.values[code];
300
+ const s = cur === void 0 || cur === null ? "" : String(cur);
301
+ if (s && sp.get(code) !== s) {
302
+ sp.set(code, s);
303
+ changed = true;
304
+ } else if (!s && sp.has(code)) {
305
+ sp.delete(code);
306
+ changed = true;
307
+ }
308
+ }
309
+ if (!changed) return;
310
+ const qs = sp.toString();
311
+ history.replaceState(null, "", `${location.pathname}${qs ? "?" + qs : ""}${location.hash || ""}`);
312
+ },
313
+ clearAllParams() {
314
+ this.boardCtx.params.reset();
315
+ this.boardCtx.dataHub.clear();
316
+ this.refreshKey += 1;
317
+ },
318
+ toggleFullscreen() {
319
+ const el = this.$refs.stage;
320
+ if (!document.fullscreenElement) {
321
+ if (el.requestFullscreen) el.requestFullscreen();
322
+ } else {
323
+ document.exitFullscreen();
324
+ }
325
+ this.$nextTick(this.fit);
326
+ }
327
+ }
328
+ };
329
+ export {
330
+ _sfc_main as default
331
+ };
@@ -0,0 +1,40 @@
1
+ import { BarChart, LineChart, PieChart, FunnelChart, RadarChart, ScatterChart, GaugeChart } from "echarts/charts";
2
+ import { GridComponent, LegendComponent, RadarComponent, TitleComponent, TooltipComponent } from "echarts/components";
3
+ import * as echartsCore from "echarts/core";
4
+ import { CanvasRenderer } from "echarts/renderers";
5
+ const CHART_TYPE_MODULES = {
6
+ bar: "BarChart",
7
+ hbar: "BarChart",
8
+ line: "LineChart",
9
+ area: "LineChart",
10
+ pie: "PieChart",
11
+ donut: "PieChart",
12
+ rose: "PieChart",
13
+ funnel: "FunnelChart",
14
+ pyramid: "FunnelChart",
15
+ radar: "RadarChart",
16
+ scatter: "ScatterChart",
17
+ gauge: "GaugeChart",
18
+ ring: "GaugeChart"
19
+ };
20
+ echartsCore.use([
21
+ BarChart,
22
+ LineChart,
23
+ PieChart,
24
+ FunnelChart,
25
+ RadarChart,
26
+ ScatterChart,
27
+ GaugeChart,
28
+ GridComponent,
29
+ LegendComponent,
30
+ RadarComponent,
31
+ TitleComponent,
32
+ TooltipComponent,
33
+ CanvasRenderer
34
+ ]);
35
+ const echarts = echartsCore;
36
+ export {
37
+ CHART_TYPE_MODULES,
38
+ echartsCore as default,
39
+ echarts
40
+ };
@@ -0,0 +1,63 @@
1
+ import _sfc_main from "./part-node2.vue.js";
2
+ import { resolveComponent, openBlock, createElementBlock, withModifiers, normalizeStyle, normalizeClass, createVNode, mergeProps, withCtx, Fragment, renderList, createBlock, renderSlot } from "vue";
3
+ import _export_sfc from "../_virtual/_plugin-vue_export-helper.js";
4
+ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
5
+ const _component_part_node = resolveComponent("part-node", true);
6
+ const _component_part_renderer = resolveComponent("part-renderer");
7
+ return openBlock(), createElementBlock("div", {
8
+ class: normalizeClass(["part", [$props.extraClass, $options.stateClass]]),
9
+ style: normalizeStyle($options.nodeStyle),
10
+ onMousedown: _cache[0] || (_cache[0] = withModifiers(($event) => _ctx.$emit("node-mousedown", $event, $props.part), ["stop"]))
11
+ }, [
12
+ createVNode(_component_part_renderer, mergeProps({
13
+ part: $props.part,
14
+ "theme-code": $props.themeCode,
15
+ "refresh-key": $props.refreshKey,
16
+ "params-version": $props.paramsVersion,
17
+ "grid-options": $options.gridOptions,
18
+ "children-count": $options.childList.length,
19
+ "tab-child-counts": $options.tabChildCounts,
20
+ design: $props.design,
21
+ active: !!$props.activeId && $props.activeId === $props.part.id,
22
+ stacked: $props.stacked,
23
+ narrow: $props.narrow || $props.stacked
24
+ }, _ctx.$attrs), {
25
+ children: withCtx(({ tab }) => [
26
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.childListForTab(tab), (c) => {
27
+ return openBlock(), createBlock(_component_part_node, mergeProps({
28
+ key: c.id,
29
+ part: c,
30
+ page: $props.page,
31
+ "all-parts": $props.allParts,
32
+ "theme-code": $props.themeCode,
33
+ "refresh-key": $props.refreshKey,
34
+ "params-version": $props.paramsVersion,
35
+ design: $props.design,
36
+ narrow: $props.narrow,
37
+ stacked: $props.stacked,
38
+ depth: $props.depth + 1,
39
+ "active-id": $props.activeId,
40
+ "in-grid": $props.inGrid
41
+ }, { ref_for: true }, _ctx.$attrs), {
42
+ default: withCtx(() => [
43
+ renderSlot(_ctx.$slots, "default", {
44
+ part: c,
45
+ depth: $props.depth + 1
46
+ })
47
+ ]),
48
+ _: 2
49
+ }, 1040, ["part", "page", "all-parts", "theme-code", "refresh-key", "params-version", "design", "narrow", "stacked", "depth", "active-id", "in-grid"]);
50
+ }), 128))
51
+ ]),
52
+ _: 3
53
+ }, 16, ["part", "theme-code", "refresh-key", "params-version", "grid-options", "children-count", "tab-child-counts", "design", "active", "stacked", "narrow"]),
54
+ renderSlot(_ctx.$slots, "default", {
55
+ part: $props.part,
56
+ depth: $props.depth
57
+ })
58
+ ], 38);
59
+ }
60
+ const PartNode = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
61
+ export {
62
+ PartNode as default
63
+ };
@@ -0,0 +1,125 @@
1
+ import PartRenderer from "./part-renderer.vue.js";
2
+ import { childLayoutOf, stackPartStyle, partStyle, gridPartStyle, containerGrid, stackMinHeight, childParts, partVisible } from "../board-engine/schema.js";
3
+ import { DESIGN_BOARD_CTX } from "./context.js";
4
+ const _sfc_main = {
5
+ name: "PartNode",
6
+ components: { PartRenderer },
7
+ // 看板上下文(参数 / 参数目录 / 取数):每个 DesignBoard 实例一份
8
+ inject: { boardCtx: { from: DESIGN_BOARD_CTX, default: null } },
9
+ props: {
10
+ part: { type: Object, required: true },
11
+ /** 整页配置(取 column/rowHeight/gap 与 layoutMode) */
12
+ page: { type: Object, default: () => ({}) },
13
+ /** 完整的部件扁平数组:子级要从这里按 parentId 找(运行时传的是已本地化的副本) */
14
+ allParts: { type: Array, default: () => [] },
15
+ themeCode: { type: String, default: "tech-blue" },
16
+ refreshKey: { type: Number, default: 0 },
17
+ /** 看板参数版本号(往下透传:参数一变,子级过滤条件要重算) */
18
+ paramsVersion: { type: Number, default: 0 },
19
+ /** 额外 class(运行时用来加 .view-part,设计器用来加 active/locked 等) */
20
+ extraClass: { type: [String, Array, Object], default: "" },
21
+ /** 设计器里为 true(空容器的引导文案只在设计器显示) */
22
+ design: { type: Boolean, default: false },
23
+ /** 窄屏(<768px):栅格部件与容器内子级一律堆成单列 */
24
+ narrow: { type: Boolean, default: false },
25
+ /** 堆叠单列(移动端):自由定位的部件也从"绝对定位"切成"纵向流式" */
26
+ stacked: { type: Boolean, default: false },
27
+ /** 当前选中的部件 id(设计器用;子级也要能高亮,所以由本组件自己算 class) */
28
+ activeId: { type: String, default: "" },
29
+ /** 设计器处于栅格模式 */
30
+ inGrid: { type: Boolean, default: false },
31
+ /** 嵌套层级(顶层 0)。设计器用它把"类型徽标"按层级错开,免得被父容器的徽标盖住 */
32
+ depth: { type: Number, default: 0 }
33
+ },
34
+ computed: {
35
+ /**
36
+ * 设计器需要的状态 class。
37
+ * ⚠️ 必须在这里按"自己的 part"算:以前是把 class 从设计器的 v-for 传进来,
38
+ * 而那段 v-for 只遍历顶层 ⇒ 子部件永远拿不到 active(选中了却没有手柄/高亮)——踩过。
39
+ */
40
+ stateClass() {
41
+ return {
42
+ active: !!this.activeId && this.part.id === this.activeId,
43
+ locked: !!this.part.locked,
44
+ ghost: !!this.part.hidden,
45
+ "in-grid": !!this.inGrid
46
+ };
47
+ },
48
+ isChild() {
49
+ return !!(this.part.parentId || "");
50
+ },
51
+ gridOptions() {
52
+ return {
53
+ cols: this.page && this.page.gridCols || 24,
54
+ rowHeight: this.page && this.page.gridRowHeight || 60,
55
+ gap: this.page && this.page.gridGap !== void 0 ? this.page.gridGap : 12,
56
+ // 容器内子栅格与整页共用"行高自增"开关
57
+ autoHeight: !!(this.page && this.page.gridAutoHeight)
58
+ };
59
+ },
60
+ childList() {
61
+ const list = childParts(this.allParts, this.part.id);
62
+ void this.paramsVersion;
63
+ if (this.design) return list;
64
+ return list.filter((c) => partVisible(c, this.ctx().params.state.values));
65
+ },
66
+ /** 每个页签各有几个子级(tab容器用它决定"显示子级还是显示原来的文字") */
67
+ tabChildCounts() {
68
+ const map = {};
69
+ this.childList.forEach((c) => {
70
+ const t = c.tabIndex || 0;
71
+ map[t] = (map[t] || 0) + 1;
72
+ });
73
+ return map;
74
+ },
75
+ nodeStyle() {
76
+ if (this.stacked && this.isFreePositioned) {
77
+ return stackPartStyle(this.part, this.childList.length);
78
+ }
79
+ let st;
80
+ if (this.isChild) {
81
+ if (this.parentIsFree) {
82
+ st = partStyle(this.part);
83
+ } else {
84
+ st = gridPartStyle(this.part, containerGrid(this.page));
85
+ }
86
+ } else {
87
+ st = (this.page.layoutMode || "free") === "grid" ? gridPartStyle(this.part, this.page) : partStyle(this.part);
88
+ }
89
+ if (this.narrow && !this.isFreePositioned) {
90
+ st.gridColumn = "1 / -1";
91
+ st.gridRow = "auto";
92
+ st.minHeight = stackMinHeight(this.part.type) + "px";
93
+ }
94
+ return st;
95
+ },
96
+ /** 是否用 x/y 绝对定位 */
97
+ isFreePositioned() {
98
+ if (this.isChild) return this.parentIsFree;
99
+ return (this.page.layoutMode || "free") !== "grid";
100
+ },
101
+ parentIsFree() {
102
+ return childLayoutOf(this.parentPart) === "free";
103
+ },
104
+ parentPart() {
105
+ if (!this.isChild) return null;
106
+ return this.allParts.find((p) => p.id === this.part.parentId) || null;
107
+ }
108
+ },
109
+ methods: {
110
+ /** 上下文(缺失直接抛,静默降级只会更难查) */
111
+ ctx() {
112
+ if (!this.boardCtx) {
113
+ throw new Error("[design-board] PartNode 必须放在 <DesignBoard> 内部使用");
114
+ }
115
+ return this.boardCtx;
116
+ },
117
+ childListForTab(tab) {
118
+ if (tab === void 0 || tab === null) return this.childList;
119
+ return this.childList.filter((c) => (c.tabIndex || 0) === tab);
120
+ }
121
+ }
122
+ };
123
+ export {
124
+ _sfc_main as default
125
+ };