@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,189 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="root" :class="['epx-dashboard-grid', { 'epx-dashboard-grid--float-mode': floatMode }]" :style="containerStyle">
|
|
3
|
+
<div v-for="entry in entries" :key="entry.item.id"
|
|
4
|
+
v-gate="entry"
|
|
5
|
+
:class="['epx-dashboard-grid__item', { 'epx-dashboard-grid__item--float': entry.float, 'epx-dashboard-grid__item--static': !!entry.item.static }]"
|
|
6
|
+
:style="entry.style"
|
|
7
|
+
:data-item-id="entry.item.id"
|
|
8
|
+
:data-gated="entry.gated ? '1' : null">
|
|
9
|
+
<slot v-if="shouldRender(entry.item)" name="item" :item="entry.item" :rect="entry.rect"></slot>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
// 看板网格(运行态):不实例化任何拖拽交互组件、不 import 布局库,只按几何公式把每一项算成绝对定位盒子。
|
|
16
|
+
// 设计态(mode='design')在阶段 3 接入,本文件目前只实现 view。
|
|
17
|
+
import { normalizeGridParams, isContainerType } from './grid/model'
|
|
18
|
+
import { normalizeGridItem } from './layout-util'
|
|
19
|
+
import { resolveGrid, itemRect, layoutHeight, rectStyle } from './grid/geometry'
|
|
20
|
+
import { compact } from './grid/compact'
|
|
21
|
+
import { createViewportGate } from './grid/useViewportGate'
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
name: 'epx-dashboard-grid',
|
|
25
|
+
props: {
|
|
26
|
+
items: { type: Array, default: () => [] },
|
|
27
|
+
grid: { type: Object, default: () => ({}) },
|
|
28
|
+
mode: { type: String, default: 'view' },
|
|
29
|
+
// all:全部挂载;current:进入可视区后挂载、不再卸载;viewport:进入挂载、远离卸载
|
|
30
|
+
// 挂载后不可变;要切换请由父组件加 key 重建
|
|
31
|
+
loadWay: { type: String, default: 'all' },
|
|
32
|
+
// 显式容器宽度(测试 / 无 ResizeObserver 环境用),缺省由 ResizeObserver 测量
|
|
33
|
+
// 挂载后不可变;要切换请由父组件加 key 重建
|
|
34
|
+
width: { type: Number, default: 0 },
|
|
35
|
+
// rowHeight / gap 为 'auto' 时的换算基准(document.body 高度),由父组件传入并随 resize 更新
|
|
36
|
+
bodyHeight: { type: Number, default: 0 },
|
|
37
|
+
// 可视区门控的滚动容器:从本组件向上找最近的匹配祖先,找不到用视口
|
|
38
|
+
scrollRootSelector: { type: String, default: '.epx-dashboard__container' }
|
|
39
|
+
},
|
|
40
|
+
directives: {
|
|
41
|
+
// 用指令而不是函数 ref:指令的 mounted / unmounted 都能拿到元素本身,函数 ref 卸载时只给 null。
|
|
42
|
+
// 值是 entry:容器项(entry.gated 为 false)不交给观察器(R14:容器本身不门控,门控作用在容器内的项上)
|
|
43
|
+
gate: {
|
|
44
|
+
mounted (el, binding) { if (binding.value.gated) binding.instance.gateObserve(el, binding.value.item.id) },
|
|
45
|
+
unmounted (el, binding) { binding.instance.gateUnobserve(el) }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
data () {
|
|
49
|
+
// 门控支持性提前在这里判定,不等 mounted() 里才写入:mounted() 触发的响应式更新要等
|
|
50
|
+
// 下一次微任务才 flush,首帧渲染仍会用旧值渲染一次(0 项),与「不支持环境退化为全部
|
|
51
|
+
// 渲染」的首帧语义矛盾。判据与 useViewportGate.js 内部一致(是否存在 IntersectionObserver)。
|
|
52
|
+
const needGate = this.loadWay === 'current' || this.loadWay === 'viewport'
|
|
53
|
+
const gateUnsupported = needGate && !(typeof window !== 'undefined' && typeof window.IntersectionObserver === 'function')
|
|
54
|
+
return { measuredWidth: 0, activeIds: {}, gateUnsupported }
|
|
55
|
+
},
|
|
56
|
+
computed: {
|
|
57
|
+
gridParams () {
|
|
58
|
+
return normalizeGridParams(this.grid)
|
|
59
|
+
},
|
|
60
|
+
containerWidth () {
|
|
61
|
+
return this.width || this.measuredWidth
|
|
62
|
+
},
|
|
63
|
+
resolved () {
|
|
64
|
+
return resolveGrid(this.gridParams, { containerWidth: this.containerWidth, bodyHeight: this.bodyHeight })
|
|
65
|
+
},
|
|
66
|
+
// 整页悬浮(grid.mode === 'float',R30):全部项按悬浮处理——不压紧、按 z 叠放,与设计态只有悬浮层一致
|
|
67
|
+
floatMode () {
|
|
68
|
+
return this.gridParams.mode === 'float'
|
|
69
|
+
},
|
|
70
|
+
// 渲染用坐标:与旧布局库 mounted 后的 compact 同一算法(空隙上移、重叠推下,static 项不动、float 项原样),
|
|
71
|
+
// 算出的是副本,不写回 props / 页面配置;每项 { item: 原始项, pos: 压紧后的坐标副本 }。整页悬浮时全部项按悬浮算
|
|
72
|
+
compacted () {
|
|
73
|
+
const live = (this.items || []).filter(Boolean)
|
|
74
|
+
const positions = compact(this.floatMode ? live.map(item => ({ ...item, float: true })) : live)
|
|
75
|
+
return live.map((item, idx) => ({ item, pos: positions[idx] }))
|
|
76
|
+
},
|
|
77
|
+
// 只做几何计算、不改写 item:规范化交给下面 items 的 deep watch,在渲染前把
|
|
78
|
+
// x/y/w/h 落定成数字,这里保持 computed 该有的无副作用约定
|
|
79
|
+
entries () {
|
|
80
|
+
return this.compacted.map(({ item, pos }) => {
|
|
81
|
+
const rect = itemRect(pos, this.resolved)
|
|
82
|
+
const style = rectStyle(rect)
|
|
83
|
+
const float = this.floatMode || !!item.float
|
|
84
|
+
if (float) style.zIndex = 10 + (Number(item.z) || 0)
|
|
85
|
+
// gated:是否参与可视区门控;容器项(dashboard-div / dashboard-tabs)本身恒渲染、不观察
|
|
86
|
+
return { item, rect, style, float, gated: !isContainerType(item.contentType) }
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
containerStyle () {
|
|
90
|
+
return { height: layoutHeight(this.compacted.map(e => e.pos), this.resolved) + 'px' }
|
|
91
|
+
},
|
|
92
|
+
gated () {
|
|
93
|
+
return (this.loadWay === 'current' || this.loadWay === 'viewport') && !this.gateUnsupported
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
watch: {
|
|
97
|
+
// 规范化 x/y/w/h(原来放在 entries computed 里就地改写 item,违反 computed 无副作用
|
|
98
|
+
// 约定,items 若来自 readonly() 源还会静默失败);顺带把已经不在 items 里的项的
|
|
99
|
+
// 激活记录一并清掉,别让 activeIds 只增不减。deep: 1 只跟踪数组层与元素身份,不深度
|
|
100
|
+
// 追踪每项内部字段——这里就地写 item.x/y/w/h,用 deep: true 会和这次改写形成响应式回环。
|
|
101
|
+
// immediate 让规范化在首帧渲染前就完成,entries 求值时数字已经就绪。
|
|
102
|
+
items: {
|
|
103
|
+
deep: 1,
|
|
104
|
+
handler (v) {
|
|
105
|
+
const list = v || []
|
|
106
|
+
// activeIds 的 key 恒为字符串(对象属性名规则),这里统一转字符串比较,避免数字
|
|
107
|
+
// id 与 for...in 取到的字符串 key 类型不等导致误判「已不存在」、把整批激活记录清空
|
|
108
|
+
const alive = new Set()
|
|
109
|
+
for (const item of list) {
|
|
110
|
+
if (!item) continue
|
|
111
|
+
normalizeGridItem(item)
|
|
112
|
+
alive.add(String(item.id))
|
|
113
|
+
}
|
|
114
|
+
for (const id in this.activeIds) if (!alive.has(id)) delete this.activeIds[id]
|
|
115
|
+
},
|
|
116
|
+
immediate: true
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
methods: {
|
|
120
|
+
shouldRender (item) {
|
|
121
|
+
if (!this.gated) return true
|
|
122
|
+
// 容器项本身不门控(其内部网格各自门控);keepAlive 项豁免卸载
|
|
123
|
+
if (isContainerType(item.contentType) || item.keepAlive) return true
|
|
124
|
+
return !!this.activeIds[item.id]
|
|
125
|
+
},
|
|
126
|
+
gateObserve (el, id) {
|
|
127
|
+
if (this.gate) this.gate.observe(el, id)
|
|
128
|
+
},
|
|
129
|
+
gateUnobserve (el) {
|
|
130
|
+
if (this.gate) this.gate.unobserve(el)
|
|
131
|
+
// 元素卸载时直接清掉激活记录,不只靠 items watch 的整体 diff 兜底
|
|
132
|
+
if (el.dataset && el.dataset.itemId != null) delete this.activeIds[el.dataset.itemId]
|
|
133
|
+
},
|
|
134
|
+
measure () {
|
|
135
|
+
if (this.$refs.root) this.measuredWidth = this.$refs.root.offsetWidth
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
mounted () {
|
|
139
|
+
if (!this.width) {
|
|
140
|
+
this.measure()
|
|
141
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
142
|
+
this.resizeObserver = new ResizeObserver(() => this.measure())
|
|
143
|
+
this.resizeObserver.observe(this.$refs.root)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (this.loadWay === 'current' || this.loadWay === 'viewport') {
|
|
147
|
+
const root = (this.scrollRootSelector && this.$el.closest(this.scrollRootSelector)) || null
|
|
148
|
+
this.gate = createViewportGate({
|
|
149
|
+
root,
|
|
150
|
+
sticky: this.loadWay === 'current',
|
|
151
|
+
onChange: (id, active) => {
|
|
152
|
+
if (active) this.activeIds[id] = true
|
|
153
|
+
else delete this.activeIds[id]
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
if (!this.gate.supported) {
|
|
157
|
+
this.gateUnsupported = true
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
// 子元素指令的 mounted 先于本组件的 mounted:门控建好后把已挂载的项补观察一遍(容器项 data-gated 为空,跳过)
|
|
161
|
+
for (const el of Array.from(this.$el.children)) {
|
|
162
|
+
if (el.dataset && el.dataset.itemId != null && el.dataset.gated === '1') this.gate.observe(el, el.dataset.itemId)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
beforeUnmount () {
|
|
167
|
+
if (this.resizeObserver) this.resizeObserver.disconnect()
|
|
168
|
+
if (this.gate) this.gate.destroy()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<style lang="scss">
|
|
174
|
+
@include b(dashboard-grid) {
|
|
175
|
+
position: relative;
|
|
176
|
+
width: 100%;
|
|
177
|
+
@include e(item) {
|
|
178
|
+
position: absolute;
|
|
179
|
+
box-sizing: border-box;
|
|
180
|
+
// 图表限高:gridItem 高度由布局决定,图表不能超出;echarts 会按容器实际尺寸重绘
|
|
181
|
+
.epx-chart {
|
|
182
|
+
height: 100% !important;
|
|
183
|
+
min-height: 0 !important;
|
|
184
|
+
max-height: 100% !important;
|
|
185
|
+
overflow: hidden !important;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
</style>
|