@epxing/dashboard 0.0.0-stage → 5.3.0-beta.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.
- package/API.md +79 -0
- package/README.md +40 -2
- package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
- package/dist/_virtual/preload-helper.js +88 -0
- package/dist/assets/technology_bg-oWIS2awO.jpg +0 -0
- package/dist/src/Dashboard.js +332 -0
- package/dist/src/DashboardGrid.js +182 -0
- package/dist/src/DesignGrid.js +711 -0
- package/dist/src/DesignItem.js +63 -0
- package/dist/src/Div.js +408 -0
- package/dist/src/Header.js +385 -0
- package/dist/src/Preview.js +27 -0
- package/dist/src/Tabs.js +468 -0
- package/dist/src/Title.js +101 -0
- package/dist/src/Widget.js +266 -0
- package/dist/src/chart-request-scope.js +32 -0
- package/dist/src/design-target.js +57 -0
- package/dist/src/grid/compact.js +68 -0
- package/dist/src/grid/compat/GridItem.js +35 -0
- package/dist/src/grid/compat/GridLayout.js +76 -0
- package/dist/src/grid/designState.js +21 -0
- package/dist/src/grid/engines.js +25 -0
- package/dist/src/grid/geometry.js +44 -0
- package/dist/src/grid/gridstackParams.js +69 -0
- package/dist/src/grid/mixins.js +108 -0
- package/dist/src/grid/model.js +223 -0
- package/dist/src/grid/useViewportGate.js +55 -0
- package/dist/src/index.js +26 -0
- package/dist/src/layout-util.js +108 -0
- package/dist/src/mobile-design.js +50 -0
- package/dist/src/theme-style.js +133 -0
- package/dist/style.css +1009 -0
- package/package.json +85 -3
- package/src/Dashboard.vue +459 -0
- package/src/DashboardGrid.vue +189 -0
- package/src/DesignGrid.vue +851 -0
- package/src/DesignItem.vue +124 -0
- package/src/Div.vue +419 -0
- package/src/Header.vue +501 -0
- package/src/Preview.vue +30 -0
- package/src/Tabs.vue +484 -0
- package/src/Title.vue +107 -0
- package/src/Widget.vue +354 -0
- package/src/chart-request-scope.js +22 -0
- package/src/design-target.js +55 -0
- package/src/grid/compact.js +81 -0
- package/src/grid/compat/GridItem.vue +30 -0
- package/src/grid/compat/GridLayout.vue +76 -0
- package/src/grid/designState.js +17 -0
- package/src/grid/engines.js +22 -0
- package/src/grid/geometry.js +55 -0
- package/src/grid/gridstackParams.js +72 -0
- package/src/grid/mixins.js +114 -0
- package/src/grid/model.js +259 -0
- package/src/grid/useViewportGate.js +57 -0
- package/src/index.js +28 -0
- package/src/layout-util.js +157 -0
- package/src/mobile-design.js +50 -0
- package/src/theme-style.js +185 -0
- package/types/div.d.ts +1 -0
- package/types/index.d.ts +7 -0
- package/types/tabs.d.ts +1 -0
- package/types/title.d.ts +1 -0
- package/types/widget.d.ts +1 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function px(n) {
|
|
2
|
+
return `${n}px`;
|
|
3
|
+
}
|
|
4
|
+
function toGridstackOptions(resolved, { float = false, engineClass, staticGrid = false } = {}) {
|
|
5
|
+
const { colNum, rowHeight, gapX, gapY } = resolved;
|
|
6
|
+
const mx = gapX / 2;
|
|
7
|
+
const my = gapY / 2;
|
|
8
|
+
const options = {
|
|
9
|
+
column: colNum,
|
|
10
|
+
cellHeight: rowHeight + gapY,
|
|
11
|
+
margin: `${px(my)} ${px(mx)} ${px(my)} ${px(mx)}`,
|
|
12
|
+
float,
|
|
13
|
+
staticGrid,
|
|
14
|
+
// R23:被推挤的项走库的 .grid-stack-animate 过渡(旧库也有 200ms transform 过渡,关掉会显得生硬);
|
|
15
|
+
// 库在从空 load / 跨网格 drop 时会自行临时关闭再恢复
|
|
16
|
+
animate: true,
|
|
17
|
+
resizable: { handles: "all" },
|
|
18
|
+
// 把手常驻 DOM(库按 !alwaysShowResizeHandle 决定 autoHide、会覆盖 resizable.autoHide;autoHide 靠壳的 mouseover 摘类,
|
|
19
|
+
// 嵌套网格下 overResizeElement 被子项先占住就摘不掉);把手透明,视觉由 epx-dashboard-design-item 的圆点负责
|
|
20
|
+
alwaysShowResizeHandle: true,
|
|
21
|
+
// 整卡可拖(R22,与旧版 dragIgnoreFrom=".resize-point" 的手感一致):handle 用库默认的内容盒;
|
|
22
|
+
// cancel 排除操作条(库内置已跳过 input / textarea / button / select / option / contenteditable / .ui-resizable-handle),
|
|
23
|
+
// 以及子网格里按在锁定项上时子实例给所在层根元素打的「按下」标记(锁定的是子项、容器不因此起拖,任何嵌套层级都适用;
|
|
24
|
+
// 子网格空白处按下不打标记、容器由此起拖。不能直接写 .grid-stack——cancel 是 e.target.closest(),任何项的祖先链都含自己所在的网格,写了就全都拖不动)
|
|
25
|
+
// pause(库为嵌套网格设计的全局开关,写进 DDManager.pauseDrag):这里显式关掉、占位块实时跟手;DesignGrid 在拖拽中按「被拖的壳是否碰到
|
|
26
|
+
// 还没进入的容器」动态打开 100ms 停顿(NEAR_CONTAINER_PAUSE)——不停顿的话碰撞会先把容器推开、指针永远进不去(R27 实测),常开又手感拖沓
|
|
27
|
+
// appendTo 只对 helper: 'clone' 生效(库只在 helper 没有父节点时才 appendTo);本项目拖的是壳本体、无 clone helper,此项实际不起作用,
|
|
28
|
+
// 不要把它当成起手成本的来源(Task 9 审查 M5 注记,值不改)
|
|
29
|
+
draggable: { handle: ".grid-stack-item-content", appendTo: "body", cancel: ".epx-dashboard-design-item__operates, .epx-dashboard-design-grid__stack--pressed", pause: false }
|
|
30
|
+
};
|
|
31
|
+
if (engineClass) options.engineClass = engineClass;
|
|
32
|
+
return options;
|
|
33
|
+
}
|
|
34
|
+
function rootStyle(resolved) {
|
|
35
|
+
const { containerWidth, gapX, gapY, padX, padY } = resolved;
|
|
36
|
+
return {
|
|
37
|
+
width: px(containerWidth - 2 * padX + gapX),
|
|
38
|
+
marginLeft: px(padX - gapX / 2),
|
|
39
|
+
marginRight: px(padX - gapX / 2),
|
|
40
|
+
marginTop: px(padY - gapY / 2)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function toWidget(item) {
|
|
44
|
+
const w = { id: String(item.id), x: Number(item.x) || 0, y: Number(item.y) || 0, w: Number(item.w) || 1, h: Number(item.h) || 1 };
|
|
45
|
+
for (const k of ["minW", "maxW", "minH", "maxH"]) if (item[k] != null) w[k] = Number(item[k]);
|
|
46
|
+
if (item.static) {
|
|
47
|
+
w.locked = true;
|
|
48
|
+
w.noMove = true;
|
|
49
|
+
w.noResize = true;
|
|
50
|
+
}
|
|
51
|
+
return w;
|
|
52
|
+
}
|
|
53
|
+
function gridLineStyle(resolved, color = "rgba(0,0,0,.025)") {
|
|
54
|
+
const { containerWidth, gapX, padX, colNum, rowHeight, gapY, padY } = resolved;
|
|
55
|
+
const colPitch = (containerWidth - 2 * padX + gapX) / colNum;
|
|
56
|
+
const bandX = gapX > 0 ? gapX : 1;
|
|
57
|
+
const bandY = gapY > 0 ? gapY : 1;
|
|
58
|
+
return {
|
|
59
|
+
backgroundImage: `linear-gradient(to right, ${color} ${px(bandX)}, transparent ${px(bandX)}), linear-gradient(to bottom, ${color} ${px(bandY)}, transparent ${px(bandY)})`,
|
|
60
|
+
backgroundSize: `${colPitch}px ${rowHeight + gapY}px`,
|
|
61
|
+
backgroundPosition: `${px(padX - bandX)} ${px(padY - bandY)}`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
gridLineStyle,
|
|
66
|
+
rootStyle,
|
|
67
|
+
toGridstackOptions,
|
|
68
|
+
toWidget
|
|
69
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { isContainerType, getContainerGrid, isLegacyConfig, getPageGrid, MOBILE_GRID, normalizeGridParams, LEGACY_CONTAINER_GRID } from "./model.js";
|
|
2
|
+
import { stackMobileLayout } from "../layout-util.js";
|
|
3
|
+
const pageGridMixin = {
|
|
4
|
+
data() {
|
|
5
|
+
return {
|
|
6
|
+
// rowHeight / gap 为 'auto'(旧 autoHeight)时的换算基准,随 window resize 更新
|
|
7
|
+
bodyHeight: typeof document !== "undefined" ? document.body.offsetHeight : 0,
|
|
8
|
+
// 以下两项是兼容别名:仅供复刻旧模板的产品子类(extends: Dashboard 且复刻了 <grid-layout> 模板)使用,运行态自身不用。
|
|
9
|
+
// 旧模板写 v-if="loadPlugin"(原来等 import('grid-layout-plus') 完成后置 true)与 v-if="!load";
|
|
10
|
+
// 垫片 GridLayout / GridItem 在包安装时已同步注册,这里直接给出「已就绪」的终值
|
|
11
|
+
loadPlugin: true,
|
|
12
|
+
load: false
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
computed: {
|
|
16
|
+
mobileDashboard() {
|
|
17
|
+
return this.$configs.designTarget === "mobile";
|
|
18
|
+
},
|
|
19
|
+
mobileRendering() {
|
|
20
|
+
return !!(this.$isMobile || this.mobileDashboard);
|
|
21
|
+
},
|
|
22
|
+
// 图表加载方式:all 全部挂载 / current 进入可视区后挂载不卸载 / viewport 进入挂载远离卸载
|
|
23
|
+
loadWay() {
|
|
24
|
+
const v = this.$configs.loadWay;
|
|
25
|
+
return v === "current" || v === "viewport" ? v : "all";
|
|
26
|
+
},
|
|
27
|
+
// 页面级网格参数:旧配置按 autoHeight 推导;移动端只把间距收紧成堆叠参数(原 gridMargin 的移动端分支),行高语义沿用页面
|
|
28
|
+
gridParams() {
|
|
29
|
+
const page = getPageGrid(this.$configs);
|
|
30
|
+
return this.$isMobile || this.mobileDashboard ? { ...page, gap: MOBILE_GRID.gap, padding: null } : page;
|
|
31
|
+
},
|
|
32
|
+
// 容器项(dashboard-div / dashboard-tabs)内部网格参数,透传给容器组件;非容器返回 undefined
|
|
33
|
+
containerGrid() {
|
|
34
|
+
return (component) => isContainerType(component.contentType) ? getContainerGrid(component, this.gridParams, isLegacyConfig(this.$configs)) : void 0;
|
|
35
|
+
},
|
|
36
|
+
// 以下三项是兼容别名:仅供复刻旧模板的产品子类(extends: Dashboard 且复刻了 <grid-layout> 模板)使用,运行态自身不用。
|
|
37
|
+
// 旧模板 :row-height="rowHeight":'auto'(旧 autoHeight)时按原 calculateHeight 的 body 高 / 1000 * 10(勿化简,保证浮点逐位一致),否则取配置数值
|
|
38
|
+
rowHeight() {
|
|
39
|
+
const { rowHeight } = this.gridParams;
|
|
40
|
+
return rowHeight === "auto" ? this.bodyHeight / 1e3 * 10 : rowHeight;
|
|
41
|
+
},
|
|
42
|
+
// 旧模板 :margin="gridMargin":移动端 [8, 8];间距 'auto' 时 [rowHeight × 2, rowHeight × 2](原 gridMargin 语义),否则取配置间距
|
|
43
|
+
gridMargin() {
|
|
44
|
+
if (this.mobileRendering) return [8, 8];
|
|
45
|
+
const { gap } = this.gridParams;
|
|
46
|
+
return gap === "auto" ? [this.rowHeight * 2, this.rowHeight * 2] : gap;
|
|
47
|
+
},
|
|
48
|
+
// 旧模板 v-if="loadComponent(component.id)":复刻模板走垫片、没有可视区门控,恒为「挂载」(等价于 loadWay 'all')
|
|
49
|
+
loadComponent() {
|
|
50
|
+
return () => true;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
methods: {
|
|
54
|
+
updateBodyHeight() {
|
|
55
|
+
this.bodyHeight = document.body.offsetHeight;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const containerGridMixin = {
|
|
60
|
+
inject: {
|
|
61
|
+
// 页面级门控策略与配置版本标记;产品复刻模板 / 独立挂载时可能没有提供方,缺省按「全部挂载 + 旧配置」
|
|
62
|
+
getDashboardLoadWay: { default: () => () => "all" },
|
|
63
|
+
getDashboardLegacy: { default: () => () => true },
|
|
64
|
+
// 移动端设计器(DashboardMobile)的手机框预览标记:它跑在 PC 端($isMobile 为 false),容器内部要和真机一样按堆叠渲染
|
|
65
|
+
epxMobilePreview: { default: false },
|
|
66
|
+
getMobileDashboard: { default: () => () => false }
|
|
67
|
+
},
|
|
68
|
+
props: {
|
|
69
|
+
// 本容器的内部网格参数(由父看板按配置版本算好传入;没传按历史写死值)
|
|
70
|
+
compGrid: { type: Object, default: null }
|
|
71
|
+
},
|
|
72
|
+
data() {
|
|
73
|
+
return {
|
|
74
|
+
// rowHeight / gap 为 'auto' 时的换算基准
|
|
75
|
+
bodyHeight: typeof document !== "undefined" ? document.body.offsetHeight : 0
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
computed: {
|
|
79
|
+
// 移动端渲染(真机或手机框预览):容器内子项按堆叠算法重排、间距收紧成堆叠参数
|
|
80
|
+
mobileStacked() {
|
|
81
|
+
return !!(this.$isMobile || this.epxMobilePreview || this.getMobileDashboard());
|
|
82
|
+
},
|
|
83
|
+
gridParams() {
|
|
84
|
+
const base = normalizeGridParams(this.compGrid, LEGACY_CONTAINER_GRID);
|
|
85
|
+
return this.mobileStacked ? { ...base, gap: MOBILE_GRID.gap, padding: null } : base;
|
|
86
|
+
},
|
|
87
|
+
loadWay() {
|
|
88
|
+
return this.getDashboardLoadWay();
|
|
89
|
+
},
|
|
90
|
+
// 嵌套容器:v3 继承本容器网格,旧配置仍用历史写死值(是否旧版由页面 provide 下发);非容器返回 undefined
|
|
91
|
+
childGrid() {
|
|
92
|
+
return (item) => isContainerType(item.contentType) ? getContainerGrid(item, this.gridParams, this.getDashboardLegacy()) : void 0;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
methods: {
|
|
96
|
+
// 容器子项在移动端的渲染列表:真机 / 手机框预览时按堆叠算法重排(整块保留的容器内部同样堆叠,不再按 PC 的列布局压缩),
|
|
97
|
+
// 保留子项 style(容器内没有「移动端样式覆盖层」);PC 原样返回
|
|
98
|
+
mobileItems(items) {
|
|
99
|
+
const list = items || [];
|
|
100
|
+
if (this.getMobileDashboard()) return list.filter((item) => !item.mobileHidden);
|
|
101
|
+
return this.mobileStacked ? stackMobileLayout(list.filter((item) => !item.mobileHidden), this.gridParams.colNum, { keepStyle: true }) : list;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
export {
|
|
106
|
+
containerGridMixin,
|
|
107
|
+
pageGridMixin
|
|
108
|
+
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { normalizeGridItem } from "../layout-util.js";
|
|
2
|
+
const CONFIG_VERSION = "3.0";
|
|
3
|
+
const CONTAINER_TYPES = ["dashboard-div", "dashboard-tabs"];
|
|
4
|
+
const GRID_FLAT_KEYS = ["gridColNum", "gridRowHeight", "gridGap", "gridPadding", "gridMode"];
|
|
5
|
+
const ITEM_LAYOUT_KEYS = ["x", "y", "w", "h", "i", "float", "z", "static", "minW", "minH", "maxW", "maxH"];
|
|
6
|
+
const DEFAULT_GRID = Object.freeze({ colNum: 48, rowHeight: 10, gap: [20, 20], padding: null, mode: "grid", canvas: "fluid" });
|
|
7
|
+
const LEGACY_CONTAINER_GRID = Object.freeze({ colNum: 48, rowHeight: 30, gap: [10, 10], padding: null, mode: "grid", canvas: "fluid" });
|
|
8
|
+
const MOBILE_GRID = Object.freeze({ colNum: 48, rowHeight: 10, gap: [8, 8], padding: null, mode: "grid", canvas: "fluid" });
|
|
9
|
+
const isPosInt = (v) => Number.isInteger(v) && v > 0;
|
|
10
|
+
const nonNeg = (v) => v !== null && v !== "" && !Array.isArray(v) && Number.isFinite(Number(v)) && Number(v) >= 0;
|
|
11
|
+
function pair(v, fallback) {
|
|
12
|
+
if (v === "auto") return "auto";
|
|
13
|
+
if (Array.isArray(v) && v.length === 2 && v.every(nonNeg)) return [Number(v[0]), Number(v[1])];
|
|
14
|
+
if (nonNeg(v)) return [Number(v), Number(v)];
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
function isContainerType(contentType) {
|
|
18
|
+
return CONTAINER_TYPES.includes(contentType);
|
|
19
|
+
}
|
|
20
|
+
function normalizeGridParams(grid, fallback = DEFAULT_GRID) {
|
|
21
|
+
const g = grid && typeof grid === "object" ? grid : {};
|
|
22
|
+
const colNum = isPosInt(Number(g.colNum)) ? Number(g.colNum) : fallback.colNum;
|
|
23
|
+
let rowHeight = fallback.rowHeight;
|
|
24
|
+
if (g.rowHeight === "auto") rowHeight = "auto";
|
|
25
|
+
else if (nonNeg(g.rowHeight) && Number(g.rowHeight) > 0) rowHeight = Number(g.rowHeight);
|
|
26
|
+
const gap = pair(g.gap, fallback.gap);
|
|
27
|
+
const padding = g.padding == null ? fallback.padding == null ? null : fallback.padding : pair(g.padding, fallback.padding);
|
|
28
|
+
const mode = g.mode === "float" ? "float" : "grid";
|
|
29
|
+
const canvas = g.canvas === "fixed" ? "fixed" : "fluid";
|
|
30
|
+
const out = { colNum, rowHeight, gap, padding, mode, canvas };
|
|
31
|
+
if (canvas === "fixed") out.size = pair(g.size, [1920, 1080]);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
function isLegacyConfig(config = {}) {
|
|
35
|
+
return !config || !(parseFloat(config.version) >= 3);
|
|
36
|
+
}
|
|
37
|
+
function legacyPageGrid(config = {}) {
|
|
38
|
+
return config.autoHeight ? { ...DEFAULT_GRID, rowHeight: "auto", gap: "auto" } : { ...DEFAULT_GRID };
|
|
39
|
+
}
|
|
40
|
+
function getPageGrid(config = {}) {
|
|
41
|
+
const c = config || {};
|
|
42
|
+
return isLegacyConfig(c) ? normalizeGridParams(c.grid, legacyPageGrid(c)) : normalizeGridParams(c.grid);
|
|
43
|
+
}
|
|
44
|
+
function getContainerGrid(item = {}, parentGrid = DEFAULT_GRID, legacy = false) {
|
|
45
|
+
const base = legacy ? LEGACY_CONTAINER_GRID : parentGrid;
|
|
46
|
+
if (item && item.grid) return normalizeGridParams(item.grid, base);
|
|
47
|
+
return base;
|
|
48
|
+
}
|
|
49
|
+
function walkItems(items, fn, parent = null) {
|
|
50
|
+
if (!Array.isArray(items)) return;
|
|
51
|
+
for (const item of items) {
|
|
52
|
+
if (!item || typeof item !== "object" || !item.contentType) continue;
|
|
53
|
+
fn(item, parent);
|
|
54
|
+
if (item.contentType === "dashboard-tabs") {
|
|
55
|
+
for (const tab of item.items || []) walkItems(tab && tab.items, fn, item);
|
|
56
|
+
} else {
|
|
57
|
+
walkItems(item.items, fn, item);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function normalizeDashboardConfig(config = {}) {
|
|
62
|
+
const out = JSON.parse(JSON.stringify(config || {}));
|
|
63
|
+
out.designTarget = out.designTarget === "mobile" ? "mobile" : "pc";
|
|
64
|
+
const legacy = isLegacyConfig(out);
|
|
65
|
+
out.grid = getPageGrid(out);
|
|
66
|
+
walkItems(out.items, (item, parent) => {
|
|
67
|
+
normalizeGridItem(item);
|
|
68
|
+
if (item.float !== void 0) item.float = !!item.float;
|
|
69
|
+
if (item.static !== void 0) item.static = !!item.static;
|
|
70
|
+
if (isContainerType(item.contentType)) {
|
|
71
|
+
const parentGrid = parent && parent.grid ? parent.grid : out.grid;
|
|
72
|
+
item.grid = { ...getContainerGrid(item, parentGrid, legacy) };
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
out.version = CONFIG_VERSION;
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function stripDashboardConfig(config = {}) {
|
|
79
|
+
const out = JSON.parse(JSON.stringify(config || {}));
|
|
80
|
+
const itemIds = /* @__PURE__ */ new Set();
|
|
81
|
+
const containerIds = /* @__PURE__ */ new Set();
|
|
82
|
+
walkItems(out.items, (item) => {
|
|
83
|
+
delete item.i;
|
|
84
|
+
itemIds.add(String(item.id));
|
|
85
|
+
if (isContainerType(item.contentType)) containerIds.add(String(item.id));
|
|
86
|
+
if (item.contentType === "dashboard-tabs") {
|
|
87
|
+
for (const tab of item.items || []) if (tab) delete tab.i;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const itemConfigs = out.itemConfigs;
|
|
91
|
+
if (itemConfigs && typeof itemConfigs === "object") {
|
|
92
|
+
for (const id in itemConfigs) {
|
|
93
|
+
const cfg = itemConfigs[id];
|
|
94
|
+
if (!cfg || typeof cfg !== "object" || !itemIds.has(id)) continue;
|
|
95
|
+
for (const key of ITEM_LAYOUT_KEYS) delete cfg[key];
|
|
96
|
+
if (containerIds.has(id)) delete cfg.grid;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function gridPitch(grid) {
|
|
102
|
+
if (!grid || grid.rowHeight === "auto") return null;
|
|
103
|
+
return grid.rowHeight + (grid.gap === "auto" ? 0 : grid.gap[1]);
|
|
104
|
+
}
|
|
105
|
+
function maxGapForPitch(pitch) {
|
|
106
|
+
return pitch == null ? Infinity : Math.max(0, pitch - 1);
|
|
107
|
+
}
|
|
108
|
+
function fromGrid(grid) {
|
|
109
|
+
const g = normalizeGridParams(grid);
|
|
110
|
+
return {
|
|
111
|
+
gridColNum: g.colNum,
|
|
112
|
+
gridRowHeight: gridPitch(g),
|
|
113
|
+
gridGap: g.gap === "auto" ? null : g.gap[0],
|
|
114
|
+
gridPadding: g.padding == null || g.padding === "auto" ? null : g.padding[0],
|
|
115
|
+
gridMode: g.mode
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const numOr = (v, fallback) => v == null || v === "" || !Number.isFinite(Number(v)) ? fallback : Number(v);
|
|
119
|
+
const nonNegOr = (v, fallback) => numOr(v, -1) < 0 ? fallback : Number(v);
|
|
120
|
+
function toGrid(configs = {}, grid = DEFAULT_GRID) {
|
|
121
|
+
const c = configs || {};
|
|
122
|
+
const auto = !!c.autoHeight;
|
|
123
|
+
const padding = numOr(c.gridPadding, null);
|
|
124
|
+
const pitch = Math.max(1, numOr(c.gridRowHeight, gridPitch(DEFAULT_GRID)));
|
|
125
|
+
const gap = Math.min(nonNegOr(c.gridGap, DEFAULT_GRID.gap[0]), maxGapForPitch(pitch));
|
|
126
|
+
return normalizeGridParams({
|
|
127
|
+
...grid || {},
|
|
128
|
+
colNum: numOr(c.gridColNum, DEFAULT_GRID.colNum),
|
|
129
|
+
rowHeight: auto ? "auto" : pitch - gap,
|
|
130
|
+
gap: auto ? "auto" : [gap, gap],
|
|
131
|
+
padding: padding == null ? null : [padding, padding],
|
|
132
|
+
mode: c.gridMode || DEFAULT_GRID.mode
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function mergeGridConfigs(globalConfigs = {}, patch = {}) {
|
|
136
|
+
const next = { ...globalConfigs || {}, ...patch || {} };
|
|
137
|
+
const current = getPageGrid(next);
|
|
138
|
+
const touched = Object.keys(patch || {}).some((key) => key === "autoHeight" || GRID_FLAT_KEYS.includes(key));
|
|
139
|
+
let grid = current;
|
|
140
|
+
if (touched) {
|
|
141
|
+
const flat = { ...fromGrid(current) };
|
|
142
|
+
for (const key of GRID_FLAT_KEYS) if (key in patch) flat[key] = patch[key];
|
|
143
|
+
grid = toGrid({ ...flat, autoHeight: next.autoHeight }, current);
|
|
144
|
+
}
|
|
145
|
+
next.grid = grid;
|
|
146
|
+
const auto = grid.rowHeight === "auto";
|
|
147
|
+
if (auto !== !!next.autoHeight) next.autoHeight = auto;
|
|
148
|
+
for (const key of GRID_FLAT_KEYS) delete next[key];
|
|
149
|
+
return { configs: next, colNum: current.colNum !== grid.colNum ? { from: current.colNum, to: grid.colNum } : null };
|
|
150
|
+
}
|
|
151
|
+
function rescaleItemsToColNum(items, from, to) {
|
|
152
|
+
if (!(from > 0) || !(to > 0) || from === to) return items;
|
|
153
|
+
const ratio = to / from;
|
|
154
|
+
const stopped = /* @__PURE__ */ new Set();
|
|
155
|
+
walkItems(items, (item, parent) => {
|
|
156
|
+
if (parent && stopped.has(parent)) {
|
|
157
|
+
stopped.add(item);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const w = Math.min(to, Math.max(1, Math.round((Number(item.w) || 1) * ratio)));
|
|
161
|
+
item.x = Math.max(0, Math.min(Math.round((Number(item.x) || 0) * ratio), to - w));
|
|
162
|
+
item.w = w;
|
|
163
|
+
for (const key of ["minW", "maxW"]) {
|
|
164
|
+
if (item[key] != null && item[key] !== "" && Number.isFinite(Number(item[key]))) item[key] = Math.max(1, Math.round(Number(item[key]) * ratio));
|
|
165
|
+
}
|
|
166
|
+
if (isContainerType(item.contentType) && item.grid && isPosInt(Number(item.grid.colNum))) stopped.add(item);
|
|
167
|
+
});
|
|
168
|
+
return items;
|
|
169
|
+
}
|
|
170
|
+
function resolveContainerGrid(items, id, pageGrid = DEFAULT_GRID, legacy = false) {
|
|
171
|
+
let found = null;
|
|
172
|
+
const grids = /* @__PURE__ */ new Map();
|
|
173
|
+
walkItems(items, (item, parent) => {
|
|
174
|
+
if (found || !isContainerType(item.contentType)) return;
|
|
175
|
+
const parentGrid = parent && grids.has(parent) ? grids.get(parent) : pageGrid;
|
|
176
|
+
const grid = getContainerGrid(item, parentGrid, legacy);
|
|
177
|
+
grids.set(item, grid);
|
|
178
|
+
if (String(item.id) === String(id)) found = { item, grid, parentGrid, explicit: !!(item.grid && typeof item.grid === "object") };
|
|
179
|
+
});
|
|
180
|
+
return found;
|
|
181
|
+
}
|
|
182
|
+
function partitionItems(items = []) {
|
|
183
|
+
const flow = [];
|
|
184
|
+
const float = [];
|
|
185
|
+
for (const item of items || []) {
|
|
186
|
+
if (!item) continue;
|
|
187
|
+
(item.float ? float : flow).push(item);
|
|
188
|
+
}
|
|
189
|
+
return { flow, float };
|
|
190
|
+
}
|
|
191
|
+
function convertItemBetweenGrids(item, from, to) {
|
|
192
|
+
const px = item.w * from.colWidth + (item.w - 1) * from.gapX;
|
|
193
|
+
const py = item.h * from.rowHeight + (item.h - 1) * from.gapY;
|
|
194
|
+
const w = Math.max(1, Math.round((px + to.gapX) / (to.colWidth + to.gapX)));
|
|
195
|
+
const h = Math.max(1, Math.round((py + to.gapY) / (to.rowHeight + to.gapY)));
|
|
196
|
+
return { ...item, w: Math.min(w, to.colNum), h };
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
CONFIG_VERSION,
|
|
200
|
+
CONTAINER_TYPES,
|
|
201
|
+
DEFAULT_GRID,
|
|
202
|
+
GRID_FLAT_KEYS,
|
|
203
|
+
ITEM_LAYOUT_KEYS,
|
|
204
|
+
LEGACY_CONTAINER_GRID,
|
|
205
|
+
MOBILE_GRID,
|
|
206
|
+
convertItemBetweenGrids,
|
|
207
|
+
fromGrid,
|
|
208
|
+
getContainerGrid,
|
|
209
|
+
getPageGrid,
|
|
210
|
+
gridPitch,
|
|
211
|
+
isContainerType,
|
|
212
|
+
isLegacyConfig,
|
|
213
|
+
maxGapForPitch,
|
|
214
|
+
mergeGridConfigs,
|
|
215
|
+
normalizeDashboardConfig,
|
|
216
|
+
normalizeGridParams,
|
|
217
|
+
partitionItems,
|
|
218
|
+
rescaleItemsToColNum,
|
|
219
|
+
resolveContainerGrid,
|
|
220
|
+
stripDashboardConfig,
|
|
221
|
+
toGrid,
|
|
222
|
+
walkItems
|
|
223
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function createViewportGate({ root = null, mountMargin = "100%", unmountMargin = "200%", sticky = false, onChange } = {}) {
|
|
2
|
+
const IO = typeof window !== "undefined" ? window.IntersectionObserver : void 0;
|
|
3
|
+
if (typeof IO !== "function") {
|
|
4
|
+
return { supported: false, observe() {
|
|
5
|
+
}, unobserve() {
|
|
6
|
+
}, isActive: () => true, destroy() {
|
|
7
|
+
} };
|
|
8
|
+
}
|
|
9
|
+
const idOf = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
const active = /* @__PURE__ */ new Set();
|
|
11
|
+
const emit = (id, on) => {
|
|
12
|
+
if (id == null) return;
|
|
13
|
+
if (on && !active.has(id)) {
|
|
14
|
+
active.add(id);
|
|
15
|
+
if (onChange) onChange(id, true);
|
|
16
|
+
} else if (!on && active.has(id)) {
|
|
17
|
+
active.delete(id);
|
|
18
|
+
if (onChange) onChange(id, false);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const near = new IO((entries) => {
|
|
22
|
+
for (const e of entries) if (e.isIntersecting) emit(idOf.get(e.target), true);
|
|
23
|
+
}, { root, rootMargin: `${mountMargin} 0px` });
|
|
24
|
+
const far = sticky ? null : new IO((entries) => {
|
|
25
|
+
for (const e of entries) if (!e.isIntersecting) emit(idOf.get(e.target), false);
|
|
26
|
+
}, { root, rootMargin: `${unmountMargin} 0px` });
|
|
27
|
+
return {
|
|
28
|
+
supported: true,
|
|
29
|
+
observe(el, id) {
|
|
30
|
+
const prevId = idOf.get(el);
|
|
31
|
+
if (prevId !== void 0 && prevId !== id) emit(prevId, false);
|
|
32
|
+
idOf.set(el, id);
|
|
33
|
+
near.observe(el);
|
|
34
|
+
if (far) far.observe(el);
|
|
35
|
+
},
|
|
36
|
+
unobserve(el) {
|
|
37
|
+
near.unobserve(el);
|
|
38
|
+
if (far) far.unobserve(el);
|
|
39
|
+
const id = idOf.get(el);
|
|
40
|
+
idOf.delete(el);
|
|
41
|
+
if (id != null) active.delete(id);
|
|
42
|
+
},
|
|
43
|
+
isActive(id) {
|
|
44
|
+
return active.has(id);
|
|
45
|
+
},
|
|
46
|
+
destroy() {
|
|
47
|
+
near.disconnect();
|
|
48
|
+
if (far) far.disconnect();
|
|
49
|
+
active.clear();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
createViewportGate
|
|
55
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Dashboard from "./Dashboard.js";
|
|
2
|
+
import DashboardTabs from "./Tabs.js";
|
|
3
|
+
import DashboardTitle from "./Title.js";
|
|
4
|
+
import DashboardDiv from "./Div.js";
|
|
5
|
+
import DashboardWidget from "./Widget.js";
|
|
6
|
+
import Preview from "./Preview.js";
|
|
7
|
+
import CompatGridLayout from "./grid/compat/GridLayout.js";
|
|
8
|
+
import CompatGridItem from "./grid/compat/GridItem.js";
|
|
9
|
+
Dashboard.install = function install(Vue) {
|
|
10
|
+
Vue.component(Dashboard.name, Dashboard);
|
|
11
|
+
Vue.component(DashboardTabs.name, DashboardTabs);
|
|
12
|
+
Vue.component(DashboardTitle.name, DashboardTitle);
|
|
13
|
+
Vue.component(DashboardDiv.name, DashboardDiv);
|
|
14
|
+
Vue.component(DashboardWidget.name, DashboardWidget);
|
|
15
|
+
Vue.component(Dashboard.name + "--preview", Preview);
|
|
16
|
+
Vue.component("GridLayout", CompatGridLayout);
|
|
17
|
+
Vue.component("GridItem", CompatGridItem);
|
|
18
|
+
};
|
|
19
|
+
export {
|
|
20
|
+
Dashboard,
|
|
21
|
+
DashboardDiv,
|
|
22
|
+
DashboardTabs,
|
|
23
|
+
DashboardTitle,
|
|
24
|
+
DashboardWidget,
|
|
25
|
+
Dashboard as default
|
|
26
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const DEFAULT_HALF_TYPES = [
|
|
2
|
+
"num-statistic",
|
|
3
|
+
"card-num",
|
|
4
|
+
"num",
|
|
5
|
+
"flow-num-statistic",
|
|
6
|
+
"flow-num-card",
|
|
7
|
+
"flow-num-item",
|
|
8
|
+
"task-num-card",
|
|
9
|
+
"num-item",
|
|
10
|
+
"num-sub-item"
|
|
11
|
+
];
|
|
12
|
+
const CONTAINER_TYPES = ["dashboard-div", "dashboard-tabs"];
|
|
13
|
+
function mobileSpan(item, colNum, halfTypes) {
|
|
14
|
+
if (item.contentType === "chart" || item.contentType === "report-chart") return colNum;
|
|
15
|
+
const half = Math.floor(colNum / 2);
|
|
16
|
+
if (halfTypes.includes(item.contentType)) return half;
|
|
17
|
+
const w = Math.max(1, Number(item.w) || 1);
|
|
18
|
+
const ratio = w / colNum;
|
|
19
|
+
if (ratio <= 1 / 3) return Math.floor(colNum / 3);
|
|
20
|
+
if (ratio <= 1 / 2) return half;
|
|
21
|
+
return colNum;
|
|
22
|
+
}
|
|
23
|
+
function expandForMobile(items) {
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const item of items || []) {
|
|
26
|
+
if (!item) continue;
|
|
27
|
+
if (CONTAINER_TYPES.includes(item.contentType) && item.mobileExpand !== false) {
|
|
28
|
+
const baseX = Number(item.x) || 0;
|
|
29
|
+
const baseY = Number(item.y) || 0;
|
|
30
|
+
const children = item.contentType === "dashboard-tabs" ? (item.items || []).reduce((acc, tab) => acc.concat(tab && tab.items || []), []) : item.items || [];
|
|
31
|
+
for (const c of children) {
|
|
32
|
+
if (!c) continue;
|
|
33
|
+
out.push({ ...c, x: (Number(c.x) || 0) + baseX, y: (Number(c.y) || 0) + baseY });
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
out.push(item);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
const byYX = (a, b) => (Number(a.y) || 0) - (Number(b.y) || 0) || (Number(a.x) || 0) - (Number(b.x) || 0);
|
|
42
|
+
function packStack(list, colNum, spanOf, minH) {
|
|
43
|
+
const result = [];
|
|
44
|
+
let y = 0;
|
|
45
|
+
let curX = 0;
|
|
46
|
+
let rowH = 0;
|
|
47
|
+
const closeRow = () => {
|
|
48
|
+
if (curX > 0) {
|
|
49
|
+
y += rowH;
|
|
50
|
+
curX = 0;
|
|
51
|
+
rowH = 0;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
for (const item of list) {
|
|
55
|
+
const h = Math.max(Number(item.h) || minH, minH);
|
|
56
|
+
const span = Math.max(1, Math.min(colNum, spanOf(item)));
|
|
57
|
+
if (span >= colNum) {
|
|
58
|
+
closeRow();
|
|
59
|
+
result.push({ ...item, x: 0, y, w: colNum, h });
|
|
60
|
+
y += h;
|
|
61
|
+
} else {
|
|
62
|
+
if (curX + span > colNum) closeRow();
|
|
63
|
+
result.push({ ...item, x: curX, y, w: span, h });
|
|
64
|
+
curX += span;
|
|
65
|
+
rowH = Math.max(rowH, h);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
closeRow();
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function stackMobileLayout(items = [], colNum = 48, options = {}) {
|
|
72
|
+
const { minH = 3, halfTypes = DEFAULT_HALF_TYPES, keepStyle = false } = options;
|
|
73
|
+
const stripStyle = ({ style, ...rest }) => {
|
|
74
|
+
if (Array.isArray(rest.items)) rest.items = rest.items.map(stripStyle);
|
|
75
|
+
return rest;
|
|
76
|
+
};
|
|
77
|
+
const flat = expandForMobile(items).map(keepStyle ? (item) => item : stripStyle).sort(byYX);
|
|
78
|
+
const spanOf = (item) => CONTAINER_TYPES.includes(item.contentType) ? colNum : mobileSpan(item, colNum, halfTypes);
|
|
79
|
+
return packStack(flat, colNum, spanOf, minH);
|
|
80
|
+
}
|
|
81
|
+
function reorderStack(items = [], id, dir, colNum = 48, options = {}) {
|
|
82
|
+
const { minH = 3 } = options;
|
|
83
|
+
const order = (items || []).filter(Boolean).slice().sort(byYX);
|
|
84
|
+
const i = order.findIndex((it) => String(it.id) === String(id));
|
|
85
|
+
if (i < 0) return packStack(order, colNum, (item) => Math.max(1, Number(item.w) || 1), minH);
|
|
86
|
+
const [moved] = order.splice(i, 1);
|
|
87
|
+
if (dir === "top") order.unshift(moved);
|
|
88
|
+
else if (dir === "bottom") order.push(moved);
|
|
89
|
+
else if (dir === "down") order.splice(Math.min(order.length, i + 1), 0, moved);
|
|
90
|
+
else order.splice(Math.max(0, i - 1), 0, moved);
|
|
91
|
+
return packStack(order, colNum, (item) => Math.max(1, Number(item.w) || 1), minH);
|
|
92
|
+
}
|
|
93
|
+
function normalizeGridItem(item, defaults = {}) {
|
|
94
|
+
if (!item || typeof item !== "object") return item;
|
|
95
|
+
if (item.i == null && item.id != null) item.i = item.id;
|
|
96
|
+
const fallback = { x: 0, y: 0, w: 1, h: 1, ...defaults };
|
|
97
|
+
for (const key of ["x", "y", "w", "h"]) {
|
|
98
|
+
const raw = item[key];
|
|
99
|
+
const num = raw == null || raw === "" ? NaN : Number(raw);
|
|
100
|
+
item[key] = Number.isFinite(num) ? num : fallback[key];
|
|
101
|
+
}
|
|
102
|
+
return item;
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
normalizeGridItem,
|
|
106
|
+
reorderStack,
|
|
107
|
+
stackMobileLayout
|
|
108
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { stackMobileLayout } from "./layout-util.js";
|
|
2
|
+
import { normalizeMobileAppearance } from "@epxing/utils/src/mobile-chart-appearance";
|
|
3
|
+
function indexDashboardItems(items = [], result = /* @__PURE__ */ new Map()) {
|
|
4
|
+
for (const item of items) {
|
|
5
|
+
if (!item) continue;
|
|
6
|
+
if (item.id != null) result.set(String(item.id), item);
|
|
7
|
+
if (Array.isArray(item.items)) indexDashboardItems(item.items, result);
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
function resolveMobileItem(item, pcIndex) {
|
|
12
|
+
const pc = pcIndex.get(String(item.id)) || item;
|
|
13
|
+
const styleOverride = Object.fromEntries(Object.entries(item.style || {}).filter(([, value]) => value !== "" && value !== void 0 && value !== null));
|
|
14
|
+
const resolved = {
|
|
15
|
+
...pc,
|
|
16
|
+
x: item.x,
|
|
17
|
+
y: item.y,
|
|
18
|
+
w: item.w,
|
|
19
|
+
h: item.h,
|
|
20
|
+
i: item.i || item.id,
|
|
21
|
+
mobileHidden: item.mobileHidden,
|
|
22
|
+
mobileAppearance: normalizeMobileAppearance(item.mobileAppearance),
|
|
23
|
+
style: { ...pc.style || {}, ...styleOverride }
|
|
24
|
+
};
|
|
25
|
+
if (Array.isArray(pc.items)) {
|
|
26
|
+
const saved = indexDashboardItems(item.items || []);
|
|
27
|
+
resolved.items = pc.items.map((child) => resolveMobileItem(saved.get(String(child.id)) || child, pcIndex));
|
|
28
|
+
}
|
|
29
|
+
return resolved;
|
|
30
|
+
}
|
|
31
|
+
function regenerateMobileDesign(pcItems, mobileItems, colNum = 48) {
|
|
32
|
+
const saved = indexDashboardItems(mobileItems);
|
|
33
|
+
const preserve = (item) => {
|
|
34
|
+
const old = saved.get(String(item.id));
|
|
35
|
+
const next = { ...item };
|
|
36
|
+
if (old) {
|
|
37
|
+
for (const key of ["style", "mobileHidden", "mobileAppearance"]) {
|
|
38
|
+
if (old[key] !== void 0) next[key] = key === "mobileAppearance" ? normalizeMobileAppearance(old[key]) : old[key];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(item.items)) next.items = item.items.map(preserve);
|
|
42
|
+
return next;
|
|
43
|
+
};
|
|
44
|
+
return stackMobileLayout(pcItems, colNum).map(preserve);
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
indexDashboardItems,
|
|
48
|
+
regenerateMobileDesign,
|
|
49
|
+
resolveMobileItem
|
|
50
|
+
};
|