@epxing/dashboard 0.0.0-stage → 5.3.0-beta.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.
- package/API.md +80 -0
- package/README.md +41 -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 +333 -0
- package/dist/src/DashboardGrid.js +183 -0
- package/dist/src/DesignGrid.js +712 -0
- package/dist/src/DesignItem.js +64 -0
- package/dist/src/Div.js +409 -0
- package/dist/src/Header.js +386 -0
- package/dist/src/Preview.js +28 -0
- package/dist/src/Tabs.js +469 -0
- package/dist/src/Title.js +102 -0
- package/dist/src/Widget.js +267 -0
- package/dist/src/chart-request-scope.js +33 -0
- package/dist/src/design-target.js +58 -0
- package/dist/src/grid/compact.js +69 -0
- package/dist/src/grid/compat/GridItem.js +36 -0
- package/dist/src/grid/compat/GridLayout.js +77 -0
- package/dist/src/grid/designState.js +22 -0
- package/dist/src/grid/engines.js +26 -0
- package/dist/src/grid/geometry.js +45 -0
- package/dist/src/grid/gridstackParams.js +70 -0
- package/dist/src/grid/mixins.js +109 -0
- package/dist/src/grid/model.js +224 -0
- package/dist/src/grid/useViewportGate.js +56 -0
- package/dist/src/index.js +27 -0
- package/dist/src/layout-util.js +109 -0
- package/dist/src/mobile-design.js +51 -0
- package/dist/src/theme-style.js +134 -0
- package/dist/style.css +1009 -0
- package/package.json +86 -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,55 @@
|
|
|
1
|
+
// 看板几何:网格坐标 → 像素矩形。公式与 vue-grid-layout 1.x 的 calcPosition 完全一致,且 padding = gap 时
|
|
2
|
+
// 连浮点运算顺序都与旧库相同(.5 边界的舍入方向才不会分歧),保证旧页面逐位一致;运行态与垫片都只依赖这里,不依赖任何布局库。
|
|
3
|
+
|
|
4
|
+
// 把网格参数解析成数值:'auto' 行高 = body 高 / 1000 * 10;'auto' 间距 = 2×行高;padding 缺省跟随间距
|
|
5
|
+
export function resolveGrid (grid, { containerWidth = 0, bodyHeight = 0 } = {}) {
|
|
6
|
+
const colNum = grid.colNum
|
|
7
|
+
// 勿化简成 bodyHeight / 100:旧实现 calculateHeight 写的是 offsetHeight / 1000 * 10,两者浮点结果不逐位相等
|
|
8
|
+
const rowHeight = grid.rowHeight === 'auto' ? Math.max(1, bodyHeight / 1000 * 10) : grid.rowHeight
|
|
9
|
+
const autoGap = [rowHeight * 2, rowHeight * 2]
|
|
10
|
+
const gap = grid.gap === 'auto' ? autoGap : grid.gap
|
|
11
|
+
const padding = grid.padding == null ? gap : (grid.padding === 'auto' ? autoGap : grid.padding)
|
|
12
|
+
const [gapX, gapY] = gap
|
|
13
|
+
const [padX, padY] = padding
|
|
14
|
+
// padding 跟随间距时用旧库 calcColWidth 的原式 (宽 − margin × (列 + 1)) / 列,保持浮点运算顺序;显式 padding 走通用式。
|
|
15
|
+
// 容器比 (列 + 1) × 间距 还窄时列宽为负(移动端 48 列 × 8px 间距在手机宽度下就是这样),与旧库一样照算:
|
|
16
|
+
// 整行项的宽 = 列宽 × 48 + 47 × 间距 仍等于容器宽 − 2 × 间距;夹成 0 会让它变成 47 × 间距、撑出容器。只有还没量到宽度(0)时才归 0
|
|
17
|
+
const rawColWidth = padX === gapX
|
|
18
|
+
? (containerWidth - gapX * (colNum + 1)) / colNum
|
|
19
|
+
: (containerWidth - padX * 2 - gapX * (colNum - 1)) / colNum
|
|
20
|
+
const colWidth = containerWidth > 0 ? rawColWidth : 0
|
|
21
|
+
return { colNum, rowHeight, gapX, gapY, padX, padY, colWidth, containerWidth }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 与旧库 calcPosition 同一运算顺序:colWidth * x + (x + 1) * margin;padding 与间距不同时末项补差值,
|
|
25
|
+
// padding = gap 时末项精确为 0,加 0 不改变浮点值,退化为旧库表达式
|
|
26
|
+
export function itemRect (item, r) {
|
|
27
|
+
const x = Number(item.x) || 0
|
|
28
|
+
const y = Number(item.y) || 0
|
|
29
|
+
const w = Math.max(1, Number(item.w) || 1)
|
|
30
|
+
const h = Math.max(1, Number(item.h) || 1)
|
|
31
|
+
return {
|
|
32
|
+
left: Math.round(r.colWidth * x + (x + 1) * r.gapX + (r.padX - r.gapX)),
|
|
33
|
+
top: Math.round(r.rowHeight * y + (y + 1) * r.gapY + (r.padY - r.gapY)),
|
|
34
|
+
width: Math.round(r.colWidth * w + Math.max(0, w - 1) * r.gapX),
|
|
35
|
+
height: Math.round(r.rowHeight * h + Math.max(0, h - 1) * r.gapY)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 整个网格的像素高度(最底一项的底边 + 下内边距);不取整(旧库 containerHeight 也不取整)
|
|
40
|
+
export function layoutHeight (items, r) {
|
|
41
|
+
let bottom = 0
|
|
42
|
+
for (const item of items || []) {
|
|
43
|
+
if (!item) continue
|
|
44
|
+
const b = (Number(item.y) || 0) + Math.max(1, Number(item.h) || 1)
|
|
45
|
+
if (b > bottom) bottom = b
|
|
46
|
+
}
|
|
47
|
+
if (!bottom) return 0
|
|
48
|
+
// padding 跟随间距时与旧库 containerHeight 同式同序:bottom × (行高 + 间距) + 间距
|
|
49
|
+
if (r.padY === r.gapY) return bottom * (r.rowHeight + r.gapY) + r.gapY
|
|
50
|
+
return r.padY * 2 + bottom * (r.rowHeight + r.gapY) - r.gapY
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function rectStyle (rect) {
|
|
54
|
+
return { left: rect.left + 'px', top: rect.top + 'px', width: rect.width + 'px', height: rect.height + 'px' }
|
|
55
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// v3 网格参数(resolveGrid 的结果)与 gridstack 13 选项之间的换算。
|
|
2
|
+
// gridstack 的 margin 是「格子内的内容 inset」,格子本身连续;vue-grid-layout / itemRect 的 gap 是「项间距」。
|
|
3
|
+
// 取 cellHeight = rowHeight + gapY、margin = gap / 2,则内容高 = h·(rowHeight + gapY) − gapY = h·rowHeight + (h − 1)·gapY,与 itemRect 同式;
|
|
4
|
+
// 宽度方向让根元素比容器宽 gapX − 2·padX,再用负 / 正外边距对齐,使 x 列内容左沿 = padX + x·(colWidth + gapX)。
|
|
5
|
+
function px (n) { return `${n}px` }
|
|
6
|
+
|
|
7
|
+
export function toGridstackOptions (resolved, { float = false, engineClass, staticGrid = false } = {}) {
|
|
8
|
+
const { colNum, rowHeight, gapX, gapY } = resolved
|
|
9
|
+
const mx = gapX / 2
|
|
10
|
+
const my = gapY / 2
|
|
11
|
+
const options = {
|
|
12
|
+
column: colNum,
|
|
13
|
+
cellHeight: rowHeight + gapY,
|
|
14
|
+
margin: `${px(my)} ${px(mx)} ${px(my)} ${px(mx)}`,
|
|
15
|
+
float,
|
|
16
|
+
staticGrid,
|
|
17
|
+
// R23:被推挤的项走库的 .grid-stack-animate 过渡(旧库也有 200ms transform 过渡,关掉会显得生硬);
|
|
18
|
+
// 库在从空 load / 跨网格 drop 时会自行临时关闭再恢复
|
|
19
|
+
animate: true,
|
|
20
|
+
resizable: { handles: 'all' },
|
|
21
|
+
// 把手常驻 DOM(库按 !alwaysShowResizeHandle 决定 autoHide、会覆盖 resizable.autoHide;autoHide 靠壳的 mouseover 摘类,
|
|
22
|
+
// 嵌套网格下 overResizeElement 被子项先占住就摘不掉);把手透明,视觉由 epx-dashboard-design-item 的圆点负责
|
|
23
|
+
alwaysShowResizeHandle: true,
|
|
24
|
+
// 整卡可拖(R22,与旧版 dragIgnoreFrom=".resize-point" 的手感一致):handle 用库默认的内容盒;
|
|
25
|
+
// cancel 排除操作条(库内置已跳过 input / textarea / button / select / option / contenteditable / .ui-resizable-handle),
|
|
26
|
+
// 以及子网格里按在锁定项上时子实例给所在层根元素打的「按下」标记(锁定的是子项、容器不因此起拖,任何嵌套层级都适用;
|
|
27
|
+
// 子网格空白处按下不打标记、容器由此起拖。不能直接写 .grid-stack——cancel 是 e.target.closest(),任何项的祖先链都含自己所在的网格,写了就全都拖不动)
|
|
28
|
+
// pause(库为嵌套网格设计的全局开关,写进 DDManager.pauseDrag):这里显式关掉、占位块实时跟手;DesignGrid 在拖拽中按「被拖的壳是否碰到
|
|
29
|
+
// 还没进入的容器」动态打开 100ms 停顿(NEAR_CONTAINER_PAUSE)——不停顿的话碰撞会先把容器推开、指针永远进不去(R27 实测),常开又手感拖沓
|
|
30
|
+
// appendTo 只对 helper: 'clone' 生效(库只在 helper 没有父节点时才 appendTo);本项目拖的是壳本体、无 clone helper,此项实际不起作用,
|
|
31
|
+
// 不要把它当成起手成本的来源(Task 9 审查 M5 注记,值不改)
|
|
32
|
+
draggable: { handle: '.grid-stack-item-content', appendTo: 'body', cancel: '.epx-dashboard-design-item__operates, .epx-dashboard-design-grid__stack--pressed', pause: false }
|
|
33
|
+
}
|
|
34
|
+
// 13.3.0 的 GridStackOptions 有 engineClass 且实例选项优先于 GridStack.registerEngine 的全局注册;
|
|
35
|
+
// 由 Task 3 决定用实例选项还是 GridStack.registerEngine
|
|
36
|
+
if (engineClass) options.engineClass = engineClass
|
|
37
|
+
return options
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function rootStyle (resolved) {
|
|
41
|
+
const { containerWidth, gapX, gapY, padX, padY } = resolved
|
|
42
|
+
return {
|
|
43
|
+
width: px(containerWidth - 2 * padX + gapX),
|
|
44
|
+
marginLeft: px(padX - gapX / 2),
|
|
45
|
+
marginRight: px(padX - gapX / 2),
|
|
46
|
+
marginTop: px(padY - gapY / 2)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// item → GridStackWidget;static 项在 gridstack 里对应 locked + noMove + noResize
|
|
51
|
+
export function toWidget (item) {
|
|
52
|
+
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 }
|
|
53
|
+
for (const k of ['minW', 'maxW', 'minH', 'maxH']) if (item[k] != null) w[k] = Number(item[k])
|
|
54
|
+
if (item.static) { w.locked = true; w.noMove = true; w.noResize = true }
|
|
55
|
+
return w
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 设计态参考网格:把组件之间的「间距槽」画成极淡的带(竖带宽 gapX、横带高 gapY),按格距(列宽 + 间距 / 行高 + 间距)重复,
|
|
59
|
+
// 带的两边正好是相邻组件的内容边沿——拖拽 / 缩放落位后组件边沿压在带边上。画 1px 细线的话线只能落在槽中间、组件永远差半个间距碰不到线;
|
|
60
|
+
// 只画列条(Figma layout grid)用户觉得不够直观。颜色要浅:默认 2.5% 黑,只作参考、不抢内容(用户已否决 5%)。
|
|
61
|
+
// 起点 = 第 0 列内容左沿 − gapX(padding 跟随间距时从 0 起、四边留白也画成槽);间距 0 没有槽可画,退化成 1px 细线贴在内容边沿
|
|
62
|
+
export function gridLineStyle (resolved, color = 'rgba(0,0,0,.025)') {
|
|
63
|
+
const { containerWidth, gapX, padX, colNum, rowHeight, gapY, padY } = resolved
|
|
64
|
+
const colPitch = (containerWidth - 2 * padX + gapX) / colNum
|
|
65
|
+
const bandX = gapX > 0 ? gapX : 1
|
|
66
|
+
const bandY = gapY > 0 ? gapY : 1
|
|
67
|
+
return {
|
|
68
|
+
backgroundImage: `linear-gradient(to right, ${color} ${px(bandX)}, transparent ${px(bandX)}), linear-gradient(to bottom, ${color} ${px(bandY)}, transparent ${px(bandY)})`,
|
|
69
|
+
backgroundSize: `${colPitch}px ${rowHeight + gapY}px`,
|
|
70
|
+
backgroundPosition: `${px(padX - bandX)} ${px(padY - bandY)}`
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// 看板页面级网格混入:服务于 Dashboard.vue(看板页面)与 Widget.vue(看板组件版)两个运行态宿主。
|
|
2
|
+
// 两者的「图表加载方式 / 页面级网格参数 / 容器网格透传 / autoHeight 换算基准」逻辑完全相同,抽到这里共用;
|
|
3
|
+
// provide 里的 getDashboardLoadWay / getDashboardLegacy 两个 getter 仍留在各组件自己的 provide() 里。
|
|
4
|
+
// 另有容器级混入 containerGridMixin:服务于 Div.vue(dashboard-div)与 Tabs.vue(dashboard-tabs)两个容器的运行态分支。
|
|
5
|
+
import { getPageGrid, getContainerGrid, isLegacyConfig, isContainerType, normalizeGridParams, MOBILE_GRID, LEGACY_CONTAINER_GRID } from './model'
|
|
6
|
+
import { stackMobileLayout } from '../layout-util'
|
|
7
|
+
|
|
8
|
+
export const pageGridMixin = {
|
|
9
|
+
data () {
|
|
10
|
+
return {
|
|
11
|
+
// rowHeight / gap 为 'auto'(旧 autoHeight)时的换算基准,随 window resize 更新
|
|
12
|
+
bodyHeight: typeof document !== 'undefined' ? document.body.offsetHeight : 0,
|
|
13
|
+
// 以下两项是兼容别名:仅供复刻旧模板的产品子类(extends: Dashboard 且复刻了 <grid-layout> 模板)使用,运行态自身不用。
|
|
14
|
+
// 旧模板写 v-if="loadPlugin"(原来等 import('grid-layout-plus') 完成后置 true)与 v-if="!load";
|
|
15
|
+
// 垫片 GridLayout / GridItem 在包安装时已同步注册,这里直接给出「已就绪」的终值
|
|
16
|
+
loadPlugin: true,
|
|
17
|
+
load: false
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
computed: {
|
|
21
|
+
mobileDashboard () {
|
|
22
|
+
return this.$configs.designTarget === 'mobile'
|
|
23
|
+
},
|
|
24
|
+
mobileRendering () {
|
|
25
|
+
return !!(this.$isMobile || this.mobileDashboard)
|
|
26
|
+
},
|
|
27
|
+
// 图表加载方式:all 全部挂载 / current 进入可视区后挂载不卸载 / viewport 进入挂载远离卸载
|
|
28
|
+
loadWay () {
|
|
29
|
+
const v = this.$configs.loadWay
|
|
30
|
+
return v === 'current' || v === 'viewport' ? v : 'all'
|
|
31
|
+
},
|
|
32
|
+
// 页面级网格参数:旧配置按 autoHeight 推导;移动端只把间距收紧成堆叠参数(原 gridMargin 的移动端分支),行高语义沿用页面
|
|
33
|
+
gridParams () {
|
|
34
|
+
const page = getPageGrid(this.$configs)
|
|
35
|
+
return this.$isMobile || this.mobileDashboard ? { ...page, gap: MOBILE_GRID.gap, padding: null } : page
|
|
36
|
+
},
|
|
37
|
+
// 容器项(dashboard-div / dashboard-tabs)内部网格参数,透传给容器组件;非容器返回 undefined
|
|
38
|
+
containerGrid () {
|
|
39
|
+
return component => (isContainerType(component.contentType)
|
|
40
|
+
? getContainerGrid(component, this.gridParams, isLegacyConfig(this.$configs))
|
|
41
|
+
: undefined)
|
|
42
|
+
},
|
|
43
|
+
// 以下三项是兼容别名:仅供复刻旧模板的产品子类(extends: Dashboard 且复刻了 <grid-layout> 模板)使用,运行态自身不用。
|
|
44
|
+
// 旧模板 :row-height="rowHeight":'auto'(旧 autoHeight)时按原 calculateHeight 的 body 高 / 1000 * 10(勿化简,保证浮点逐位一致),否则取配置数值
|
|
45
|
+
rowHeight () {
|
|
46
|
+
const { rowHeight } = this.gridParams
|
|
47
|
+
return rowHeight === 'auto' ? this.bodyHeight / 1000 * 10 : rowHeight
|
|
48
|
+
},
|
|
49
|
+
// 旧模板 :margin="gridMargin":移动端 [8, 8];间距 'auto' 时 [rowHeight × 2, rowHeight × 2](原 gridMargin 语义),否则取配置间距
|
|
50
|
+
gridMargin () {
|
|
51
|
+
if (this.mobileRendering) return [8, 8]
|
|
52
|
+
const { gap } = this.gridParams
|
|
53
|
+
return gap === 'auto' ? [this.rowHeight * 2, this.rowHeight * 2] : gap
|
|
54
|
+
},
|
|
55
|
+
// 旧模板 v-if="loadComponent(component.id)":复刻模板走垫片、没有可视区门控,恒为「挂载」(等价于 loadWay 'all')
|
|
56
|
+
loadComponent () {
|
|
57
|
+
return () => true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
methods: {
|
|
61
|
+
updateBodyHeight () {
|
|
62
|
+
this.bodyHeight = document.body.offsetHeight
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 容器级网格混入:容器的内部网格参数由父看板按配置版本算好经 comp-grid 传入(无显式 grid 时是引用稳定的
|
|
68
|
+
// 冻结常量 / 父级缓存,有显式 grid 时才是新对象;只用 computed 派生、不 watch),门控策略与配置版本标记经页面 provide 注入。
|
|
69
|
+
export const containerGridMixin = {
|
|
70
|
+
inject: {
|
|
71
|
+
// 页面级门控策略与配置版本标记;产品复刻模板 / 独立挂载时可能没有提供方,缺省按「全部挂载 + 旧配置」
|
|
72
|
+
getDashboardLoadWay: { default: () => () => 'all' },
|
|
73
|
+
getDashboardLegacy: { default: () => () => true },
|
|
74
|
+
// 移动端设计器(DashboardMobile)的手机框预览标记:它跑在 PC 端($isMobile 为 false),容器内部要和真机一样按堆叠渲染
|
|
75
|
+
epxMobilePreview: { default: false },
|
|
76
|
+
getMobileDashboard: { default: () => () => false }
|
|
77
|
+
},
|
|
78
|
+
props: {
|
|
79
|
+
// 本容器的内部网格参数(由父看板按配置版本算好传入;没传按历史写死值)
|
|
80
|
+
compGrid: { type: Object, default: null }
|
|
81
|
+
},
|
|
82
|
+
data () {
|
|
83
|
+
return {
|
|
84
|
+
// rowHeight / gap 为 'auto' 时的换算基准
|
|
85
|
+
bodyHeight: typeof document !== 'undefined' ? document.body.offsetHeight : 0
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
computed: {
|
|
89
|
+
// 移动端渲染(真机或手机框预览):容器内子项按堆叠算法重排、间距收紧成堆叠参数
|
|
90
|
+
mobileStacked () {
|
|
91
|
+
return !!(this.$isMobile || this.epxMobilePreview || this.getMobileDashboard())
|
|
92
|
+
},
|
|
93
|
+
gridParams () {
|
|
94
|
+
const base = normalizeGridParams(this.compGrid, LEGACY_CONTAINER_GRID)
|
|
95
|
+
return this.mobileStacked ? { ...base, gap: MOBILE_GRID.gap, padding: null } : base
|
|
96
|
+
},
|
|
97
|
+
loadWay () {
|
|
98
|
+
return this.getDashboardLoadWay()
|
|
99
|
+
},
|
|
100
|
+
// 嵌套容器:v3 继承本容器网格,旧配置仍用历史写死值(是否旧版由页面 provide 下发);非容器返回 undefined
|
|
101
|
+
childGrid () {
|
|
102
|
+
return item => (isContainerType(item.contentType) ? getContainerGrid(item, this.gridParams, this.getDashboardLegacy()) : undefined)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
methods: {
|
|
106
|
+
// 容器子项在移动端的渲染列表:真机 / 手机框预览时按堆叠算法重排(整块保留的容器内部同样堆叠,不再按 PC 的列布局压缩),
|
|
107
|
+
// 保留子项 style(容器内没有「移动端样式覆盖层」);PC 原样返回
|
|
108
|
+
mobileItems (items) {
|
|
109
|
+
const list = items || []
|
|
110
|
+
if (this.getMobileDashboard()) return list.filter(item => !item.mobileHidden)
|
|
111
|
+
return this.mobileStacked ? stackMobileLayout(list.filter(item => !item.mobileHidden), this.gridParams.colNum, { keepStyle: true }) : list
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// 看板布局模型:页面配置(v2 / v3)的规范化、默认值、容器网格、保存前剥离、悬浮分区、跨网格尺寸换算,
|
|
2
|
+
// 以及设计器面板用的 grid ↔ 扁平键映射、改列数换算、容器网格有效值解析。
|
|
3
|
+
// 纯函数、不依赖 Vue;结构说明见 docs/superpowers/specs/2026-09-16-dashboard-grid-refactor-design.md §7。
|
|
4
|
+
import { normalizeGridItem } from '../layout-util'
|
|
5
|
+
|
|
6
|
+
export const CONFIG_VERSION = '3.0'
|
|
7
|
+
export const CONTAINER_TYPES = ['dashboard-div', 'dashboard-tabs']
|
|
8
|
+
// 全局设置「布局」组在面板上的扁平键(面板控件只认标量),与 grid 对象经 fromGrid / toGrid 互转;只存在于面板,不落盘
|
|
9
|
+
export const GRID_FLAT_KEYS = ['gridColNum', 'gridRowHeight', 'gridGap', 'gridPadding', 'gridMode']
|
|
10
|
+
// 网格项自身的布局字段(真值在 item 上):属性面板把项字段铺给 visual 面板后会随提交漏进 itemConfigs,保存前剥掉
|
|
11
|
+
export const ITEM_LAYOUT_KEYS = ['x', 'y', 'w', 'h', 'i', 'float', 'z', 'static', 'minW', 'minH', 'maxW', 'maxH']
|
|
12
|
+
// 页面级默认:与现状一致(48 列、10px 行高、20px 间距)。padding 为 null 表示跟随 gap,
|
|
13
|
+
// 即旧版 margin 同时作用于项间与容器四边的语义,保证旧页面逐像素一致。
|
|
14
|
+
export const DEFAULT_GRID = Object.freeze({ colNum: 48, rowHeight: 10, gap: [20, 20], padding: null, mode: 'grid', canvas: 'fluid' })
|
|
15
|
+
// 历史容器(dashboard-div / dashboard-tabs)内部网格原本写死的参数,v2 配置升级时显式写入以免变形
|
|
16
|
+
export const LEGACY_CONTAINER_GRID = Object.freeze({ colNum: 48, rowHeight: 30, gap: [10, 10], padding: null, mode: 'grid', canvas: 'fluid' })
|
|
17
|
+
// 移动端堆叠布局的网格参数(原 Dashboard.gridMargin 的移动端分支)
|
|
18
|
+
export const MOBILE_GRID = Object.freeze({ colNum: 48, rowHeight: 10, gap: [8, 8], padding: null, mode: 'grid', canvas: 'fluid' })
|
|
19
|
+
|
|
20
|
+
const isPosInt = v => Number.isInteger(v) && v > 0
|
|
21
|
+
const nonNeg = v => v !== null && v !== '' && !Array.isArray(v) && Number.isFinite(Number(v)) && Number(v) >= 0
|
|
22
|
+
|
|
23
|
+
// [x, y] 数对:接受 'auto'、二元数组、单个数字(横纵同值);其余回落
|
|
24
|
+
function pair (v, fallback) {
|
|
25
|
+
if (v === 'auto') return 'auto'
|
|
26
|
+
if (Array.isArray(v) && v.length === 2 && v.every(nonNeg)) return [Number(v[0]), Number(v[1])]
|
|
27
|
+
if (nonNeg(v)) return [Number(v), Number(v)]
|
|
28
|
+
return fallback
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isContainerType (contentType) {
|
|
32
|
+
return CONTAINER_TYPES.includes(contentType)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeGridParams (grid, fallback = DEFAULT_GRID) {
|
|
36
|
+
const g = grid && typeof grid === 'object' ? grid : {}
|
|
37
|
+
const colNum = isPosInt(Number(g.colNum)) ? Number(g.colNum) : fallback.colNum
|
|
38
|
+
let rowHeight = fallback.rowHeight
|
|
39
|
+
if (g.rowHeight === 'auto') rowHeight = 'auto'
|
|
40
|
+
else if (nonNeg(g.rowHeight) && Number(g.rowHeight) > 0) rowHeight = Number(g.rowHeight)
|
|
41
|
+
const gap = pair(g.gap, fallback.gap)
|
|
42
|
+
const padding = g.padding == null ? (fallback.padding == null ? null : fallback.padding) : pair(g.padding, fallback.padding)
|
|
43
|
+
const mode = g.mode === 'float' ? 'float' : 'grid'
|
|
44
|
+
const canvas = g.canvas === 'fixed' ? 'fixed' : 'fluid'
|
|
45
|
+
const out = { colNum, rowHeight, gap, padding, mode, canvas }
|
|
46
|
+
if (canvas === 'fixed') out.size = pair(g.size, [1920, 1080])
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 旧版配置:version 缺失或主版本号 < 3('3.1' 等后续小版本仍按 v3 语义读 grid 键)
|
|
51
|
+
export function isLegacyConfig (config = {}) {
|
|
52
|
+
return !config || !(parseFloat(config.version) >= 3)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 旧版(无 grid 键)的页面级参数:autoHeight ⇒ 行高与间距都按视口高度等比(与原实现同义)
|
|
56
|
+
function legacyPageGrid (config = {}) {
|
|
57
|
+
return config.autoHeight ? { ...DEFAULT_GRID, rowHeight: 'auto', gap: 'auto' } : { ...DEFAULT_GRID }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getPageGrid (config = {}) {
|
|
61
|
+
const c = config || {}
|
|
62
|
+
return isLegacyConfig(c) ? normalizeGridParams(c.grid, legacyPageGrid(c)) : normalizeGridParams(c.grid)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 容器内部网格:容器显式 grid 优先;旧版配置无 grid 用历史写死值;v3 无 grid 继承父网格。
|
|
66
|
+
// 无显式 grid 时直接返回 base(冻结常量 / 父级 computed 缓存),引用稳定:它经 comp-grid 传给容器,
|
|
67
|
+
// 每次渲染换新对象会让父看板的每次 render 级联到容器内全部子组件重新 render
|
|
68
|
+
export function getContainerGrid (item = {}, parentGrid = DEFAULT_GRID, legacy = false) {
|
|
69
|
+
const base = legacy ? LEGACY_CONTAINER_GRID : parentGrid
|
|
70
|
+
if (item && item.grid) return normalizeGridParams(item.grid, base)
|
|
71
|
+
return base
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 深度优先遍历网格项(父先于子)。dashboard-tabs 的 items 是页签对象(无 contentType),不回调,只下钻其 items
|
|
75
|
+
export function walkItems (items, fn, parent = null) {
|
|
76
|
+
if (!Array.isArray(items)) return
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
if (!item || typeof item !== 'object' || !item.contentType) continue
|
|
79
|
+
fn(item, parent)
|
|
80
|
+
if (item.contentType === 'dashboard-tabs') {
|
|
81
|
+
for (const tab of (item.items || [])) walkItems(tab && tab.items, fn, item)
|
|
82
|
+
} else {
|
|
83
|
+
walkItems(item.items, fn, item)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 升级 / 规范化整份看板配置(深拷贝,不改入参)。运行态不调用它(运行态逐项用 normalizeGridItem +
|
|
89
|
+
// getPageGrid / getContainerGrid),设计器加载时调用并在保存时写回。
|
|
90
|
+
export function normalizeDashboardConfig (config = {}) {
|
|
91
|
+
const out = JSON.parse(JSON.stringify(config || {}))
|
|
92
|
+
out.designTarget = out.designTarget === 'mobile' ? 'mobile' : 'pc'
|
|
93
|
+
const legacy = isLegacyConfig(out)
|
|
94
|
+
out.grid = getPageGrid(out)
|
|
95
|
+
walkItems(out.items, (item, parent) => {
|
|
96
|
+
normalizeGridItem(item)
|
|
97
|
+
if (item.float !== undefined) item.float = !!item.float
|
|
98
|
+
if (item.static !== undefined) item.static = !!item.static
|
|
99
|
+
if (isContainerType(item.contentType)) {
|
|
100
|
+
const parentGrid = parent && parent.grid ? parent.grid : out.grid
|
|
101
|
+
// 展开成可变副本:getContainerGrid 可能直接返回冻结常量,不能把它挂进要被设计器改写的配置树
|
|
102
|
+
item.grid = { ...getContainerGrid(item, parentGrid, legacy) }
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
out.version = CONFIG_VERSION
|
|
106
|
+
return out
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 保存前剥离运行时字段 i(页签对象也可能被布局库写入 i),以及漏进 itemConfigs 的网格项布局键
|
|
110
|
+
// (容器条目的 grid 同样只认 item 上的那份;非容器的 grid 是组件自己的配置,不碰)。
|
|
111
|
+
// 只剥「存在对应 item」的条目:孤儿条目(画布上已没有这个 id)与自定义键原样保留,产品仓可能靠它们存别的东西
|
|
112
|
+
export function stripDashboardConfig (config = {}) {
|
|
113
|
+
const out = JSON.parse(JSON.stringify(config || {}))
|
|
114
|
+
const itemIds = new Set()
|
|
115
|
+
const containerIds = new Set()
|
|
116
|
+
walkItems(out.items, item => {
|
|
117
|
+
delete item.i
|
|
118
|
+
itemIds.add(String(item.id))
|
|
119
|
+
if (isContainerType(item.contentType)) containerIds.add(String(item.id))
|
|
120
|
+
if (item.contentType === 'dashboard-tabs') {
|
|
121
|
+
for (const tab of (item.items || [])) if (tab) delete tab.i
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
const itemConfigs = out.itemConfigs
|
|
125
|
+
if (itemConfigs && typeof itemConfigs === 'object') {
|
|
126
|
+
for (const id in itemConfigs) {
|
|
127
|
+
const cfg = itemConfigs[id]
|
|
128
|
+
if (!cfg || typeof cfg !== 'object' || !itemIds.has(id)) continue
|
|
129
|
+
for (const key of ITEM_LAYOUT_KEYS) delete cfg[key]
|
|
130
|
+
if (containerIds.has(id)) delete cfg.grid
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 行距:每行占位高度 = 一行内容高(存储的 rowHeight)+ 纵向间距;'auto' 行高为 null。
|
|
137
|
+
// 面板上的「行高」就是它:存储与几何仍按旧语义(组件高 = h·rowHeight + (h−1)·gap,与 Vue2 线上版逐像素一致),
|
|
138
|
+
// 面板改间距时按行距反推 rowHeight 落盘,于是改间距只改组件之间的空隙与四边留白、不改变组件位置和页面高度,改行高(行距)才改
|
|
139
|
+
export function gridPitch (grid) {
|
|
140
|
+
if (!grid || grid.rowHeight === 'auto') return null
|
|
141
|
+
return grid.rowHeight + (grid.gap === 'auto' ? 0 : grid.gap[1])
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 行距下间距的上限:一行内容高至少 1px
|
|
145
|
+
export function maxGapForPitch (pitch) {
|
|
146
|
+
return pitch == null ? Infinity : Math.max(0, pitch - 1)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// grid 对象 → 全局面板扁平键:gridRowHeight 是行距(见 gridPitch);'auto' 行高 / 间距在面板上是空值(由 autoHeight 开关承载),
|
|
150
|
+
// 内边距取单值(缺省 null = 跟随间距)
|
|
151
|
+
export function fromGrid (grid) {
|
|
152
|
+
const g = normalizeGridParams(grid)
|
|
153
|
+
return {
|
|
154
|
+
gridColNum: g.colNum,
|
|
155
|
+
gridRowHeight: gridPitch(g),
|
|
156
|
+
gridGap: g.gap === 'auto' ? null : g.gap[0],
|
|
157
|
+
gridPadding: g.padding == null || g.padding === 'auto' ? null : g.padding[0],
|
|
158
|
+
gridMode: g.mode
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const numOr = (v, fallback) => (v == null || v === '' || !Number.isFinite(Number(v)) ? fallback : Number(v))
|
|
163
|
+
// 只认非负数,其余(含负数)回落
|
|
164
|
+
const nonNegOr = (v, fallback) => (numOr(v, -1) < 0 ? fallback : Number(v))
|
|
165
|
+
|
|
166
|
+
// 全局面板扁平键(+ autoHeight 开关)→ grid 对象:gridRowHeight 是行距,落盘 rowHeight = 行距 − 间距(间距夹到行距 − 1);
|
|
167
|
+
// 空 / 非法值回落页面默认;autoHeight 开着时行高与间距都是 'auto';面板没有的键(canvas / size)从现有 grid 带过来
|
|
168
|
+
export function toGrid (configs = {}, grid = DEFAULT_GRID) {
|
|
169
|
+
const c = configs || {}
|
|
170
|
+
const auto = !!c.autoHeight
|
|
171
|
+
const padding = numOr(c.gridPadding, null)
|
|
172
|
+
const pitch = Math.max(1, numOr(c.gridRowHeight, gridPitch(DEFAULT_GRID)))
|
|
173
|
+
const gap = Math.min(nonNegOr(c.gridGap, DEFAULT_GRID.gap[0]), maxGapForPitch(pitch))
|
|
174
|
+
return normalizeGridParams({
|
|
175
|
+
...(grid || {}),
|
|
176
|
+
colNum: numOr(c.gridColNum, DEFAULT_GRID.colNum),
|
|
177
|
+
rowHeight: auto ? 'auto' : pitch - gap,
|
|
178
|
+
gap: auto ? 'auto' : [gap, gap],
|
|
179
|
+
padding: padding == null ? null : [padding, padding],
|
|
180
|
+
mode: c.gridMode || DEFAULT_GRID.mode
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 设计器外壳:把全局面板的补丁并入页面全局配置(不改入参)。扁平键折进 grid 后从配置里删掉;
|
|
185
|
+
// autoHeight ↔ grid.rowHeight 'auto' 双向同步(运行态 v3 只读 grid,开关只是它的面板形态);
|
|
186
|
+
// 返回列数是否变化(外壳据此询问是否按比例换算现有组件)
|
|
187
|
+
export function mergeGridConfigs (globalConfigs = {}, patch = {}) {
|
|
188
|
+
const next = { ...(globalConfigs || {}), ...(patch || {}) }
|
|
189
|
+
const current = getPageGrid(next)
|
|
190
|
+
const touched = Object.keys(patch || {}).some(key => key === 'autoHeight' || GRID_FLAT_KEYS.includes(key))
|
|
191
|
+
let grid = current
|
|
192
|
+
if (touched) {
|
|
193
|
+
const flat = { ...fromGrid(current) }
|
|
194
|
+
for (const key of GRID_FLAT_KEYS) if (key in patch) flat[key] = patch[key]
|
|
195
|
+
grid = toGrid({ ...flat, autoHeight: next.autoHeight }, current)
|
|
196
|
+
}
|
|
197
|
+
next.grid = grid
|
|
198
|
+
const auto = grid.rowHeight === 'auto'
|
|
199
|
+
if (auto !== !!next.autoHeight) next.autoHeight = auto
|
|
200
|
+
for (const key of GRID_FLAT_KEYS) delete next[key]
|
|
201
|
+
return { configs: next, colNum: current.colNum !== grid.colNum ? { from: current.colNum, to: grid.colNum } : null }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 改列数:现有项按比例换算 x / w 与宽度约束 minW / maxW(就地改;y / h / minH / maxH 不动)。有自己列数的容器(显式 grid.colNum)
|
|
205
|
+
// 本身参与换算、其子项按容器网格布局不换算;继承父网格的容器(含页签)子项跟着换算
|
|
206
|
+
export function rescaleItemsToColNum (items, from, to) {
|
|
207
|
+
if (!(from > 0) || !(to > 0) || from === to) return items
|
|
208
|
+
const ratio = to / from
|
|
209
|
+
const stopped = new Set()
|
|
210
|
+
walkItems(items, (item, parent) => {
|
|
211
|
+
if (parent && stopped.has(parent)) {
|
|
212
|
+
stopped.add(item)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
const w = Math.min(to, Math.max(1, Math.round((Number(item.w) || 1) * ratio)))
|
|
216
|
+
item.x = Math.max(0, Math.min(Math.round((Number(item.x) || 0) * ratio), to - w))
|
|
217
|
+
item.w = w
|
|
218
|
+
// 宽度约束同比换算(round(v·new/old),至少 1 列);没设的不补
|
|
219
|
+
for (const key of ['minW', 'maxW']) {
|
|
220
|
+
if (item[key] != null && item[key] !== '' && Number.isFinite(Number(item[key]))) item[key] = Math.max(1, Math.round(Number(item[key]) * ratio))
|
|
221
|
+
}
|
|
222
|
+
if (isContainerType(item.contentType) && item.grid && isPosInt(Number(item.grid.colNum))) stopped.add(item)
|
|
223
|
+
})
|
|
224
|
+
return items
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 容器项的有效内部网格(含继承链):从根开始逐层 getContainerGrid,找到 id 为止。
|
|
228
|
+
// 返回 { item, grid, parentGrid, explicit }(explicit:是否有显式 grid);不是容器 / 找不到返回 null
|
|
229
|
+
export function resolveContainerGrid (items, id, pageGrid = DEFAULT_GRID, legacy = false) {
|
|
230
|
+
let found = null
|
|
231
|
+
const grids = new Map()
|
|
232
|
+
walkItems(items, (item, parent) => {
|
|
233
|
+
if (found || !isContainerType(item.contentType)) return
|
|
234
|
+
const parentGrid = parent && grids.has(parent) ? grids.get(parent) : pageGrid
|
|
235
|
+
const grid = getContainerGrid(item, parentGrid, legacy)
|
|
236
|
+
grids.set(item, grid)
|
|
237
|
+
if (String(item.id) === String(id)) found = { item, grid, parentGrid, explicit: !!(item.grid && typeof item.grid === 'object') }
|
|
238
|
+
})
|
|
239
|
+
return found
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function partitionItems (items = []) {
|
|
243
|
+
const flow = []
|
|
244
|
+
const float = []
|
|
245
|
+
for (const item of items || []) {
|
|
246
|
+
if (!item) continue
|
|
247
|
+
(item.float ? float : flow).push(item)
|
|
248
|
+
}
|
|
249
|
+
return { flow, float }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 跨网格移动时按视觉尺寸换算 w/h:像素宽 W = w·colWidth + (w−1)·gapX,反解目标网格的 w'
|
|
253
|
+
export function convertItemBetweenGrids (item, from, to) {
|
|
254
|
+
const px = item.w * from.colWidth + (item.w - 1) * from.gapX
|
|
255
|
+
const py = item.h * from.rowHeight + (item.h - 1) * from.gapY
|
|
256
|
+
const w = Math.max(1, Math.round((px + to.gapX) / (to.colWidth + to.gapX)))
|
|
257
|
+
const h = Math.max(1, Math.round((py + to.gapY) / (to.rowHeight + to.gapY)))
|
|
258
|
+
return { ...item, w: Math.min(w, to.colNum), h }
|
|
259
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// 可视区门控:用两个 IntersectionObserver 管全部项——进入「近区」(默认 ±1 屏)激活、离开「远区」
|
|
2
|
+
// (默认 ±2 屏)停用,中间是滞后带,避免滚动边缘反复挂卸。不依赖 Vue,便于单测。
|
|
3
|
+
export function createViewportGate ({ root = null, mountMargin = '100%', unmountMargin = '200%', sticky = false, onChange } = {}) {
|
|
4
|
+
const IO = typeof window !== 'undefined' ? window.IntersectionObserver : undefined
|
|
5
|
+
if (typeof IO !== 'function') {
|
|
6
|
+
// 环境不支持:一律视为可见,调用方按「全部挂载」处理
|
|
7
|
+
return { supported: false, observe () {}, unobserve () {}, isActive: () => true, destroy () {} }
|
|
8
|
+
}
|
|
9
|
+
const idOf = new WeakMap()
|
|
10
|
+
const active = 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
|
+
// 同一元素换绑新 id(未经过 unobserve):near/far 对已在观察列表中的目标重复 observe
|
|
31
|
+
// 是 no-op,不会补发一次通知,所以要先把旧 id 的激活态按「离开」清掉,否则 active 里
|
|
32
|
+
// 会残留旧 id、新 id 又读不到激活态,直到元素下次真正跨越阈值触发回调才会纠正
|
|
33
|
+
const prevId = idOf.get(el)
|
|
34
|
+
if (prevId !== undefined && prevId !== id) emit(prevId, false)
|
|
35
|
+
idOf.set(el, id)
|
|
36
|
+
near.observe(el)
|
|
37
|
+
if (far) far.observe(el)
|
|
38
|
+
},
|
|
39
|
+
unobserve (el) {
|
|
40
|
+
near.unobserve(el)
|
|
41
|
+
if (far) far.unobserve(el)
|
|
42
|
+
const id = idOf.get(el)
|
|
43
|
+
idOf.delete(el)
|
|
44
|
+
// 直接清 active、不经过 emit:调用方主动 unobserve(如元素被移除)时自行收尾该 id 的
|
|
45
|
+
// 状态即可,门控不再补发 onChange(id, false) 通知
|
|
46
|
+
if (id != null) active.delete(id)
|
|
47
|
+
},
|
|
48
|
+
isActive (id) {
|
|
49
|
+
return active.has(id)
|
|
50
|
+
},
|
|
51
|
+
destroy () {
|
|
52
|
+
near.disconnect()
|
|
53
|
+
if (far) far.disconnect()
|
|
54
|
+
active.clear()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Dashboard from './Dashboard'
|
|
2
|
+
import DashboardTabs from './Tabs'
|
|
3
|
+
import DashboardTitle from './Title'
|
|
4
|
+
import DashboardDiv from './Div'
|
|
5
|
+
import DashboardWidget from './Widget'
|
|
6
|
+
import Preview from './Preview.vue'
|
|
7
|
+
import CompatGridLayout from './grid/compat/GridLayout.vue'
|
|
8
|
+
import CompatGridItem from './grid/compat/GridItem.vue'
|
|
9
|
+
|
|
10
|
+
/* istanbul ignore next */
|
|
11
|
+
Dashboard.install = function install (Vue) {
|
|
12
|
+
Vue.component(Dashboard.name, Dashboard)
|
|
13
|
+
Vue.component(DashboardTabs.name, DashboardTabs)
|
|
14
|
+
Vue.component(DashboardTitle.name, DashboardTitle)
|
|
15
|
+
Vue.component(DashboardDiv.name, DashboardDiv)
|
|
16
|
+
Vue.component(DashboardWidget.name, DashboardWidget)
|
|
17
|
+
Vue.component(Dashboard.name + '--preview', Preview)
|
|
18
|
+
// 垫片:原先由 Dashboard.created 动态 import grid-layout-plus 后 registerComponent;
|
|
19
|
+
// 产品子类复刻的模板里直接写 <grid-layout>/<grid-item>,改为安装时注册只读实现。
|
|
20
|
+
// 用旧的注册名挂全局(模板标签解析走注册名、不走组件 name 选项),
|
|
21
|
+
// 组件自身 name 按仓库命名规范带 epx- 前缀
|
|
22
|
+
Vue.component('GridLayout', CompatGridLayout)
|
|
23
|
+
Vue.component('GridItem', CompatGridItem)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default Dashboard
|
|
27
|
+
|
|
28
|
+
export { Dashboard, DashboardTabs, DashboardTitle, DashboardDiv, DashboardWidget }
|