@epxing/dashboard 5.3.0-beta.2 → 5.3.0-beta.3
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 +1 -1
- package/README.md +42 -42
- package/dist/src/Dashboard.js +1 -1
- package/dist/src/Widget.js +1 -1
- package/dist/style.css +8 -8
- package/package.json +5 -5
- package/src/Dashboard.vue +459 -459
- package/src/Widget.vue +354 -354
- package/src/index.js +28 -28
- package/types/index.d.ts +7 -7
- package/types/tabs.d.ts +1 -1
- package/types/title.d.ts +1 -1
- package/types/widget.d.ts +1 -1
package/src/Widget.vue
CHANGED
|
@@ -1,354 +1,354 @@
|
|
|
1
|
-
|
|
2
|
-
<template>
|
|
3
|
-
<div :class="['dashboard', 'dashboard--'+ theme, { 'dashboard--hidden-scroll-bar': $configs.scrollBarHidden }]" :style="$component.style" ref="container">
|
|
4
|
-
<div ref="dashboard" class="dashboard-container" :style="mobileDashboard ? { width: '100%', maxWidth: (Number($configs.mobileCanvasWidth) || 375) + 'px', margin: '0 auto', flex: 'none' } : {}">
|
|
5
|
-
<epx-dashboard-grid
|
|
6
|
-
:items="components"
|
|
7
|
-
:grid="gridParams"
|
|
8
|
-
:load-way="loadWay"
|
|
9
|
-
:body-height="bodyHeight"
|
|
10
|
-
scroll-root-selector=".dashboard-container">
|
|
11
|
-
<template #item="{ item: component }">
|
|
12
|
-
<!-- 渲染组件 -->
|
|
13
|
-
<div v-if="component.contentType" class="dashboard-component">
|
|
14
|
-
<!-- epx-dashboard__component 是看板主题 CSS 的组件卡片选择器(见 theme-style.js),
|
|
15
|
-
widget 内的组件也要带上,否则外层看板的主题样式盖不到这里 -->
|
|
16
|
-
<epx-component v-bind="isPage? $forwardAttrs($attrs): {}" class="component epx-dashboard__component"
|
|
17
|
-
:name="component.contentType"
|
|
18
|
-
is-dashboard
|
|
19
|
-
:content-type="component.contentType"
|
|
20
|
-
:dashboard-id="$component.id"
|
|
21
|
-
:comp-id="getComponentId(component)"
|
|
22
|
-
:comp-title="component.title"
|
|
23
|
-
:items="component.items"
|
|
24
|
-
:itemConfigs="$component.itemConfigs"
|
|
25
|
-
:comp-style="component.style"
|
|
26
|
-
:comp-configs="component.configs"
|
|
27
|
-
:mobile-appearance="mobileRendering ? component.mobileAppearance : undefined"
|
|
28
|
-
:comp-grid="containerGrid(component)"
|
|
29
|
-
:chart-cache="$configs.chartCache"
|
|
30
|
-
:theme="theme">
|
|
31
|
-
</epx-component>
|
|
32
|
-
</div>
|
|
33
|
-
</template>
|
|
34
|
-
</epx-dashboard-grid>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</template>
|
|
38
|
-
|
|
39
|
-
<script>
|
|
40
|
-
import DashboardGrid from './DashboardGrid.vue'
|
|
41
|
-
import chartRequestScope from './chart-request-scope'
|
|
42
|
-
import { evalWith } from '@epxing/utils/src/eval-with'
|
|
43
|
-
import component from '@epxing/core/src/mixins/component'
|
|
44
|
-
import { pageGridMixin } from './grid/mixins'
|
|
45
|
-
import { stackMobileLayout } from './layout-util'
|
|
46
|
-
import { indexDashboardItems, resolveMobileItem } from './mobile-design'
|
|
47
|
-
import { getPageGrid, isLegacyConfig } from './grid/model'
|
|
48
|
-
import { mapState } from 'vuex'
|
|
49
|
-
|
|
50
|
-
// T31/T29:子树内 component 型全刷新。原用 `this.$children` 递归(Vue3 已移除)→
|
|
51
|
-
// 改走 T31 建的父子注册表 `_epxWalkChildren`(它本身就遍历整棵子树,故不需要外层递归)。
|
|
52
|
-
function refesh () {
|
|
53
|
-
this.resolveChartRequestBroker().invalidate()
|
|
54
|
-
this._epxWalkChildren(child => {
|
|
55
|
-
if (child.$options.$componentType === 'component' && child.$refesh) child.$refesh()
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
export default {
|
|
59
|
-
name: 'epx-dashboard-widget',
|
|
60
|
-
mixins: [component, pageGridMixin, chartRequestScope],
|
|
61
|
-
components: { [DashboardGrid.name]: DashboardGrid },
|
|
62
|
-
$componentType: 'component',
|
|
63
|
-
navConfigs: {
|
|
64
|
-
flag: false
|
|
65
|
-
},
|
|
66
|
-
style: {},
|
|
67
|
-
layout: 'grid',
|
|
68
|
-
// 组件列表
|
|
69
|
-
items: [
|
|
70
|
-
],
|
|
71
|
-
// 组件配置
|
|
72
|
-
itemConfigs: {
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
props: {
|
|
76
|
-
inline: Boolean,
|
|
77
|
-
routeKey: [String, Number],
|
|
78
|
-
routes: {
|
|
79
|
-
type: Array,
|
|
80
|
-
default () {
|
|
81
|
-
return []
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
data () {
|
|
86
|
-
return {
|
|
87
|
-
headerTitle: null,
|
|
88
|
-
pageParams: {},
|
|
89
|
-
model: {}, // 组件全局data
|
|
90
|
-
pageEvents: {},
|
|
91
|
-
searchScopeParams: []
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
computed: {
|
|
95
|
-
...mapState({
|
|
96
|
-
showMenu: state => state.config.showMenu,
|
|
97
|
-
showAppNav: state => state.config.showAppNav
|
|
98
|
-
}),
|
|
99
|
-
pageTitle () {
|
|
100
|
-
return this.headerTitle || (this.page && this.page.title) || (this.meta && this.meta.name)
|
|
101
|
-
},
|
|
102
|
-
components () {
|
|
103
|
-
// items itemConfigs
|
|
104
|
-
const { items = [], mItems } = this.$component
|
|
105
|
-
let list
|
|
106
|
-
if (this.mobileDashboard) {
|
|
107
|
-
list = items.filter(item => !item.mobileHidden)
|
|
108
|
-
} else if (this.$isMobile) {
|
|
109
|
-
if (mItems && mItems.length) {
|
|
110
|
-
const pcIndex = indexDashboardItems(items)
|
|
111
|
-
list = mItems.filter(c => !c.mobileHidden && pcIndex.has(String(c.id))).map(c => resolveMobileItem(c, pcIndex))
|
|
112
|
-
} else {
|
|
113
|
-
const { mobileAutoStack = true } = this.$configs
|
|
114
|
-
list = mobileAutoStack ? stackMobileLayout(items, 48) : items
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
list = items
|
|
118
|
-
}
|
|
119
|
-
// 布局项必须带 i(设计器与旧配置都以 id 为准),运行时用 id 兜底补齐
|
|
120
|
-
list.forEach(item => {
|
|
121
|
-
if (item && item.i == null && item.id != null) item.i = item.id
|
|
122
|
-
})
|
|
123
|
-
return list
|
|
124
|
-
},
|
|
125
|
-
theme () {
|
|
126
|
-
const { theme } = this.$configs
|
|
127
|
-
return theme
|
|
128
|
-
},
|
|
129
|
-
// Widget 原本没有移动端收紧间距的分支(与页面版 Dashboard 不同),按「旧配置像素一致」保留原语义,覆盖 pageGridMixin 的 gridParams
|
|
130
|
-
gridParams () {
|
|
131
|
-
const grid = getPageGrid(this.$configs)
|
|
132
|
-
return this.mobileDashboard ? { ...grid, gap: [8, 8], padding: null } : grid
|
|
133
|
-
},
|
|
134
|
-
colAttribute () {
|
|
135
|
-
return (component = {}) => {
|
|
136
|
-
const { xsspan = 24, ...other } = component
|
|
137
|
-
const colAttribute = {
|
|
138
|
-
...other,
|
|
139
|
-
xs: {
|
|
140
|
-
span: xsspan
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
delete colAttribute.id
|
|
144
|
-
delete colAttribute.style
|
|
145
|
-
delete colAttribute.title
|
|
146
|
-
return colAttribute
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
pageStyle () {
|
|
150
|
-
const { layout = 'grid', direction = 'row', justify = 'flex-start', align = 'top' } = this.$configs
|
|
151
|
-
if (layout === 'flex') {
|
|
152
|
-
return { display: 'flex', 'flex-direction': direction, 'justify-content': justify, 'align-items': align }
|
|
153
|
-
} else {
|
|
154
|
-
return { }
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
componentStyle () {
|
|
158
|
-
return component => {
|
|
159
|
-
const { layout } = this.$configs
|
|
160
|
-
const { flex } = component
|
|
161
|
-
const compoentStyle = { }
|
|
162
|
-
if (layout === 'flex' && flex) {
|
|
163
|
-
compoentStyle.flex = 1
|
|
164
|
-
}
|
|
165
|
-
return compoentStyle
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
scopeParams () {
|
|
169
|
-
const { globalScopeParams: configScopeParams = [] } = this.$configs
|
|
170
|
-
const { scopeParams: queryScopeParams = [] } = this.$configs
|
|
171
|
-
|
|
172
|
-
// 格式化scopeParams
|
|
173
|
-
const _scopeParams = this.$_.cloneDeep([...configScopeParams, ...queryScopeParams, ...this.searchScopeParams])
|
|
174
|
-
const that = this
|
|
175
|
-
for (const scopeKey in _scopeParams) {
|
|
176
|
-
const scope = _scopeParams[scopeKey]
|
|
177
|
-
if (!scope) continue
|
|
178
|
-
let values = scope.values
|
|
179
|
-
if (/\$\[(.+)\]/.test(values)) {
|
|
180
|
-
values = values.replace(/\$\[(.+)\]/, (match, key) => {
|
|
181
|
-
try {
|
|
182
|
-
return evalWith(`that.${key}`, { that }) || evalWith(`that.$query.${key}`, { that }) || evalWith(`that.$tab.${key}`, { that }) || ''
|
|
183
|
-
} catch (error) {
|
|
184
|
-
return scope.values
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
if (/#\{(.+)\}/.test(values)) {
|
|
189
|
-
values = values.replace(/#\{(.+)\}/, (match, key) => {
|
|
190
|
-
try {
|
|
191
|
-
return evalWith(`that.$query.${key}`, { that }) || evalWith(`that.$tab.${key}`, { that }) || ''
|
|
192
|
-
} catch (error) {
|
|
193
|
-
return scope.values
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
scope.values = values
|
|
198
|
-
}
|
|
199
|
-
return _scopeParams
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
watch: {
|
|
203
|
-
pageTitle: {
|
|
204
|
-
handler (v) {
|
|
205
|
-
this.$emit('change-route', this.routeKey, { title: v })
|
|
206
|
-
},
|
|
207
|
-
immediate: true
|
|
208
|
-
},
|
|
209
|
-
scopeParams: {
|
|
210
|
-
deep: true,
|
|
211
|
-
handler (v) {
|
|
212
|
-
this.pageParams.scopeParams = v
|
|
213
|
-
},
|
|
214
|
-
immediate: true
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
provide () {
|
|
218
|
-
// 容器内网格的门控策略 / 配置是否旧版都跟随本组件(provide 只跑一次,用函数延迟读)
|
|
219
|
-
const gridProvide = {
|
|
220
|
-
getMobileDashboard: () => this.mobileDashboard,
|
|
221
|
-
getDashboardLoadWay: () => this.loadWay,
|
|
222
|
-
getDashboardLegacy: () => isLegacyConfig(this.$configs)
|
|
223
|
-
}
|
|
224
|
-
if (this.isPage) {
|
|
225
|
-
return {
|
|
226
|
-
...gridProvide,
|
|
227
|
-
upPage: this,
|
|
228
|
-
pageId: this.page ? this.page.pageId : '', // 当前页面id
|
|
229
|
-
pageMetaId: this.isPage ? (this.$metaId || (this.page ? this.page.metaId : '')) : null // 当前页面对象id
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return gridProvide // T28:Vue3 provide 合并不容忍 undefined
|
|
233
|
-
},
|
|
234
|
-
methods: {
|
|
235
|
-
$refesh () {
|
|
236
|
-
refesh.apply(this)
|
|
237
|
-
// if (this.upPage) this.upPage.$refesh()
|
|
238
|
-
},
|
|
239
|
-
openFullScreen () {
|
|
240
|
-
if (this.$refs.dashboard.requestFullscreen) {
|
|
241
|
-
this.$refs.dashboard.requestFullscreen()
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
|
-
search (v) {
|
|
245
|
-
this.searchScopeParams = v
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
created () {
|
|
249
|
-
// 这里原本存了 showAppNav/showMenu 用于卸载时「还原」,已删除,理由见 beforeUnmount 的注释。
|
|
250
|
-
// 行高 / 间距按视口等比(旧 autoHeight)时才需要跟随窗口尺寸
|
|
251
|
-
const grid = this.gridParams
|
|
252
|
-
if (grid.rowHeight === 'auto' || grid.gap === 'auto') {
|
|
253
|
-
window.addEventListener('resize', this.updateBodyHeight)
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
beforeUnmount () {
|
|
257
|
-
// 这里原来无条件写两个**全局** UI 状态(SWITCH_MENU_SHOW / SWITCH_APP_NAV_SHOW),已删除。三个理由:
|
|
258
|
-
// ① **没有对应的「获取」**:整个 packages/dashboard 里没有任何地方隐藏菜单或应用导航
|
|
259
|
-
// (openFullScreen 走的是浏览器原生 requestFullscreen,与这两个状态无关)。
|
|
260
|
-
// 只还原、从不获取,等于凭空覆盖别人的状态。
|
|
261
|
-
// ② **存的值是反的**:created 里 `defaultShowMenu = this.showAppNav`、
|
|
262
|
-
// `defaultShowAppNav = this.showMenu`,两者交叉,还原时把 appNav 的旧值写进 menu、反之亦然。
|
|
263
|
-
// ③ **卸载不等于「用户离开了本页」**:标签是 v-for + v-show 全挂载,关掉一个**后台**标签、
|
|
264
|
-
// 或标签数超过 MOUNTED_TAB_MAX 被 LRU 淘汰内容,都会走到这里 ——
|
|
265
|
-
// 于是「关掉一个大屏标签,把另一个正在看的标签的菜单弹了出来」。
|
|
266
|
-
// 这段代码此前从未执行过(keep-alive 的页从不真卸载),M0 改成真卸载后才被唤醒,故一并清理。
|
|
267
|
-
window.removeEventListener('resize', this.updateBodyHeight)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
</script>
|
|
271
|
-
|
|
272
|
-
<style lang="scss" scoped>
|
|
273
|
-
.dashboard {
|
|
274
|
-
width: 100%;
|
|
275
|
-
height: 100%;
|
|
276
|
-
overflow: auto;
|
|
277
|
-
overflow-x: hidden;
|
|
278
|
-
display: flex;
|
|
279
|
-
flex-direction: column;
|
|
280
|
-
|
|
281
|
-
.base-page {
|
|
282
|
-
position: static;
|
|
283
|
-
overflow-x: hidden;
|
|
284
|
-
padding: 0;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
.dashboard--hidden-scroll-bar {
|
|
288
|
-
.dashboard-container {
|
|
289
|
-
&::-webkit-scrollbar-thumb {
|
|
290
|
-
background: none;
|
|
291
|
-
&:hover {
|
|
292
|
-
background-color: rgba(95, 95, 95, .3);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
.dashboard-container {
|
|
298
|
-
width: 100%;
|
|
299
|
-
flex: 1;
|
|
300
|
-
overflow: auto;
|
|
301
|
-
}
|
|
302
|
-
.dashboard-component {
|
|
303
|
-
width: 100%;
|
|
304
|
-
height: 100%;
|
|
305
|
-
overflow: auto;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
.dashboard--dark {
|
|
309
|
-
.dashboard-container{
|
|
310
|
-
background: #151C3D;
|
|
311
|
-
.dashboard-component {
|
|
312
|
-
>div {
|
|
313
|
-
// background: rgba(255,255,255, .1)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
.dashboard--technology {
|
|
319
|
-
.dashboard-container{
|
|
320
|
-
background: url('@/assets/images/dashboard/technology_bg.jpg') no-repeat center;
|
|
321
|
-
background-size: cover;
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
.component {
|
|
325
|
-
// position: relative;
|
|
326
|
-
// &::before {
|
|
327
|
-
// content: '';
|
|
328
|
-
// position: absolute;
|
|
329
|
-
// top: 0;
|
|
330
|
-
// display: block;
|
|
331
|
-
// width: 100%;
|
|
332
|
-
// height: 1px;
|
|
333
|
-
// background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
|
|
334
|
-
// opacity: 0.5;
|
|
335
|
-
// }
|
|
336
|
-
// &::after {
|
|
337
|
-
// content: '';
|
|
338
|
-
// position: absolute;
|
|
339
|
-
// bottom: 0;
|
|
340
|
-
// display: block;
|
|
341
|
-
// width: 100%;
|
|
342
|
-
// height: 1px;
|
|
343
|
-
// background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
|
|
344
|
-
// opacity: 0.5;
|
|
345
|
-
// }
|
|
346
|
-
// background: rgba(255,255,255, .1);
|
|
347
|
-
// backdrop-filter: blur(4px);
|
|
348
|
-
// >div {
|
|
349
|
-
// background: rgba(255,255,255, .1);
|
|
350
|
-
// backdrop-filter: blur(4px);
|
|
351
|
-
// }
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
</style>
|
|
1
|
+
|
|
2
|
+
<template>
|
|
3
|
+
<div :class="['dashboard', 'dashboard--'+ theme, { 'dashboard--hidden-scroll-bar': $configs.scrollBarHidden }]" :style="$component.style" ref="container">
|
|
4
|
+
<div ref="dashboard" class="dashboard-container" :style="mobileDashboard ? { width: '100%', maxWidth: (Number($configs.mobileCanvasWidth) || 375) + 'px', margin: '0 auto', flex: 'none' } : {}">
|
|
5
|
+
<epx-dashboard-grid
|
|
6
|
+
:items="components"
|
|
7
|
+
:grid="gridParams"
|
|
8
|
+
:load-way="loadWay"
|
|
9
|
+
:body-height="bodyHeight"
|
|
10
|
+
scroll-root-selector=".dashboard-container">
|
|
11
|
+
<template #item="{ item: component }">
|
|
12
|
+
<!-- 渲染组件 -->
|
|
13
|
+
<div v-if="component.contentType" class="dashboard-component">
|
|
14
|
+
<!-- epx-dashboard__component 是看板主题 CSS 的组件卡片选择器(见 theme-style.js),
|
|
15
|
+
widget 内的组件也要带上,否则外层看板的主题样式盖不到这里 -->
|
|
16
|
+
<epx-component v-bind="isPage? $forwardAttrs($attrs): {}" class="component epx-dashboard__component"
|
|
17
|
+
:name="component.contentType"
|
|
18
|
+
is-dashboard
|
|
19
|
+
:content-type="component.contentType"
|
|
20
|
+
:dashboard-id="$component.id"
|
|
21
|
+
:comp-id="getComponentId(component)"
|
|
22
|
+
:comp-title="component.title"
|
|
23
|
+
:items="component.items"
|
|
24
|
+
:itemConfigs="$component.itemConfigs"
|
|
25
|
+
:comp-style="component.style"
|
|
26
|
+
:comp-configs="component.configs"
|
|
27
|
+
:mobile-appearance="mobileRendering ? component.mobileAppearance : undefined"
|
|
28
|
+
:comp-grid="containerGrid(component)"
|
|
29
|
+
:chart-cache="$configs.chartCache"
|
|
30
|
+
:theme="theme">
|
|
31
|
+
</epx-component>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
</epx-dashboard-grid>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
import DashboardGrid from './DashboardGrid.vue'
|
|
41
|
+
import chartRequestScope from './chart-request-scope'
|
|
42
|
+
import { evalWith } from '@epxing/utils/src/eval-with'
|
|
43
|
+
import component from '@epxing/core/src/mixins/component'
|
|
44
|
+
import { pageGridMixin } from './grid/mixins'
|
|
45
|
+
import { stackMobileLayout } from './layout-util'
|
|
46
|
+
import { indexDashboardItems, resolveMobileItem } from './mobile-design'
|
|
47
|
+
import { getPageGrid, isLegacyConfig } from './grid/model'
|
|
48
|
+
import { mapState } from 'vuex'
|
|
49
|
+
|
|
50
|
+
// T31/T29:子树内 component 型全刷新。原用 `this.$children` 递归(Vue3 已移除)→
|
|
51
|
+
// 改走 T31 建的父子注册表 `_epxWalkChildren`(它本身就遍历整棵子树,故不需要外层递归)。
|
|
52
|
+
function refesh () {
|
|
53
|
+
this.resolveChartRequestBroker().invalidate()
|
|
54
|
+
this._epxWalkChildren(child => {
|
|
55
|
+
if (child.$options.$componentType === 'component' && child.$refesh) child.$refesh()
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
export default {
|
|
59
|
+
name: 'epx-dashboard-widget',
|
|
60
|
+
mixins: [component, pageGridMixin, chartRequestScope],
|
|
61
|
+
components: { [DashboardGrid.name]: DashboardGrid },
|
|
62
|
+
$componentType: 'component',
|
|
63
|
+
navConfigs: {
|
|
64
|
+
flag: false
|
|
65
|
+
},
|
|
66
|
+
style: {},
|
|
67
|
+
layout: 'grid',
|
|
68
|
+
// 组件列表
|
|
69
|
+
items: [
|
|
70
|
+
],
|
|
71
|
+
// 组件配置
|
|
72
|
+
itemConfigs: {
|
|
73
|
+
|
|
74
|
+
},
|
|
75
|
+
props: {
|
|
76
|
+
inline: Boolean,
|
|
77
|
+
routeKey: [String, Number],
|
|
78
|
+
routes: {
|
|
79
|
+
type: Array,
|
|
80
|
+
default () {
|
|
81
|
+
return []
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
data () {
|
|
86
|
+
return {
|
|
87
|
+
headerTitle: null,
|
|
88
|
+
pageParams: {},
|
|
89
|
+
model: {}, // 组件全局data
|
|
90
|
+
pageEvents: {},
|
|
91
|
+
searchScopeParams: []
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
computed: {
|
|
95
|
+
...mapState({
|
|
96
|
+
showMenu: state => state.config.showMenu,
|
|
97
|
+
showAppNav: state => state.config.showAppNav
|
|
98
|
+
}),
|
|
99
|
+
pageTitle () {
|
|
100
|
+
return this.headerTitle || (this.page && this.page.title) || (this.meta && this.meta.name)
|
|
101
|
+
},
|
|
102
|
+
components () {
|
|
103
|
+
// items itemConfigs
|
|
104
|
+
const { items = [], mItems } = this.$component
|
|
105
|
+
let list
|
|
106
|
+
if (this.mobileDashboard) {
|
|
107
|
+
list = items.filter(item => !item.mobileHidden)
|
|
108
|
+
} else if (this.$isMobile) {
|
|
109
|
+
if (mItems && mItems.length) {
|
|
110
|
+
const pcIndex = indexDashboardItems(items)
|
|
111
|
+
list = mItems.filter(c => !c.mobileHidden && pcIndex.has(String(c.id))).map(c => resolveMobileItem(c, pcIndex))
|
|
112
|
+
} else {
|
|
113
|
+
const { mobileAutoStack = true } = this.$configs
|
|
114
|
+
list = mobileAutoStack ? stackMobileLayout(items, 48) : items
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
list = items
|
|
118
|
+
}
|
|
119
|
+
// 布局项必须带 i(设计器与旧配置都以 id 为准),运行时用 id 兜底补齐
|
|
120
|
+
list.forEach(item => {
|
|
121
|
+
if (item && item.i == null && item.id != null) item.i = item.id
|
|
122
|
+
})
|
|
123
|
+
return list
|
|
124
|
+
},
|
|
125
|
+
theme () {
|
|
126
|
+
const { theme } = this.$configs
|
|
127
|
+
return theme
|
|
128
|
+
},
|
|
129
|
+
// Widget 原本没有移动端收紧间距的分支(与页面版 Dashboard 不同),按「旧配置像素一致」保留原语义,覆盖 pageGridMixin 的 gridParams
|
|
130
|
+
gridParams () {
|
|
131
|
+
const grid = getPageGrid(this.$configs)
|
|
132
|
+
return this.mobileDashboard ? { ...grid, gap: [8, 8], padding: null } : grid
|
|
133
|
+
},
|
|
134
|
+
colAttribute () {
|
|
135
|
+
return (component = {}) => {
|
|
136
|
+
const { xsspan = 24, ...other } = component
|
|
137
|
+
const colAttribute = {
|
|
138
|
+
...other,
|
|
139
|
+
xs: {
|
|
140
|
+
span: xsspan
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
delete colAttribute.id
|
|
144
|
+
delete colAttribute.style
|
|
145
|
+
delete colAttribute.title
|
|
146
|
+
return colAttribute
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
pageStyle () {
|
|
150
|
+
const { layout = 'grid', direction = 'row', justify = 'flex-start', align = 'top' } = this.$configs
|
|
151
|
+
if (layout === 'flex') {
|
|
152
|
+
return { display: 'flex', 'flex-direction': direction, 'justify-content': justify, 'align-items': align }
|
|
153
|
+
} else {
|
|
154
|
+
return { }
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
componentStyle () {
|
|
158
|
+
return component => {
|
|
159
|
+
const { layout } = this.$configs
|
|
160
|
+
const { flex } = component
|
|
161
|
+
const compoentStyle = { }
|
|
162
|
+
if (layout === 'flex' && flex) {
|
|
163
|
+
compoentStyle.flex = 1
|
|
164
|
+
}
|
|
165
|
+
return compoentStyle
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
scopeParams () {
|
|
169
|
+
const { globalScopeParams: configScopeParams = [] } = this.$configs
|
|
170
|
+
const { scopeParams: queryScopeParams = [] } = this.$configs
|
|
171
|
+
|
|
172
|
+
// 格式化scopeParams
|
|
173
|
+
const _scopeParams = this.$_.cloneDeep([...configScopeParams, ...queryScopeParams, ...this.searchScopeParams])
|
|
174
|
+
const that = this
|
|
175
|
+
for (const scopeKey in _scopeParams) {
|
|
176
|
+
const scope = _scopeParams[scopeKey]
|
|
177
|
+
if (!scope) continue
|
|
178
|
+
let values = scope.values
|
|
179
|
+
if (/\$\[(.+)\]/.test(values)) {
|
|
180
|
+
values = values.replace(/\$\[(.+)\]/, (match, key) => {
|
|
181
|
+
try {
|
|
182
|
+
return evalWith(`that.${key}`, { that }) || evalWith(`that.$query.${key}`, { that }) || evalWith(`that.$tab.${key}`, { that }) || ''
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return scope.values
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
if (/#\{(.+)\}/.test(values)) {
|
|
189
|
+
values = values.replace(/#\{(.+)\}/, (match, key) => {
|
|
190
|
+
try {
|
|
191
|
+
return evalWith(`that.$query.${key}`, { that }) || evalWith(`that.$tab.${key}`, { that }) || ''
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return scope.values
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
scope.values = values
|
|
198
|
+
}
|
|
199
|
+
return _scopeParams
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
watch: {
|
|
203
|
+
pageTitle: {
|
|
204
|
+
handler (v) {
|
|
205
|
+
this.$emit('change-route', this.routeKey, { title: v })
|
|
206
|
+
},
|
|
207
|
+
immediate: true
|
|
208
|
+
},
|
|
209
|
+
scopeParams: {
|
|
210
|
+
deep: true,
|
|
211
|
+
handler (v) {
|
|
212
|
+
this.pageParams.scopeParams = v
|
|
213
|
+
},
|
|
214
|
+
immediate: true
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
provide () {
|
|
218
|
+
// 容器内网格的门控策略 / 配置是否旧版都跟随本组件(provide 只跑一次,用函数延迟读)
|
|
219
|
+
const gridProvide = {
|
|
220
|
+
getMobileDashboard: () => this.mobileDashboard,
|
|
221
|
+
getDashboardLoadWay: () => this.loadWay,
|
|
222
|
+
getDashboardLegacy: () => isLegacyConfig(this.$configs)
|
|
223
|
+
}
|
|
224
|
+
if (this.isPage) {
|
|
225
|
+
return {
|
|
226
|
+
...gridProvide,
|
|
227
|
+
upPage: this,
|
|
228
|
+
pageId: this.page ? this.page.pageId : '', // 当前页面id
|
|
229
|
+
pageMetaId: this.isPage ? (this.$metaId || (this.page ? this.page.metaId : '')) : null // 当前页面对象id
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return gridProvide // T28:Vue3 provide 合并不容忍 undefined
|
|
233
|
+
},
|
|
234
|
+
methods: {
|
|
235
|
+
$refesh () {
|
|
236
|
+
refesh.apply(this)
|
|
237
|
+
// if (this.upPage) this.upPage.$refesh()
|
|
238
|
+
},
|
|
239
|
+
openFullScreen () {
|
|
240
|
+
if (this.$refs.dashboard.requestFullscreen) {
|
|
241
|
+
this.$refs.dashboard.requestFullscreen()
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
search (v) {
|
|
245
|
+
this.searchScopeParams = v
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
created () {
|
|
249
|
+
// 这里原本存了 showAppNav/showMenu 用于卸载时「还原」,已删除,理由见 beforeUnmount 的注释。
|
|
250
|
+
// 行高 / 间距按视口等比(旧 autoHeight)时才需要跟随窗口尺寸
|
|
251
|
+
const grid = this.gridParams
|
|
252
|
+
if (grid.rowHeight === 'auto' || grid.gap === 'auto') {
|
|
253
|
+
window.addEventListener('resize', this.updateBodyHeight)
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
beforeUnmount () {
|
|
257
|
+
// 这里原来无条件写两个**全局** UI 状态(SWITCH_MENU_SHOW / SWITCH_APP_NAV_SHOW),已删除。三个理由:
|
|
258
|
+
// ① **没有对应的「获取」**:整个 packages/dashboard 里没有任何地方隐藏菜单或应用导航
|
|
259
|
+
// (openFullScreen 走的是浏览器原生 requestFullscreen,与这两个状态无关)。
|
|
260
|
+
// 只还原、从不获取,等于凭空覆盖别人的状态。
|
|
261
|
+
// ② **存的值是反的**:created 里 `defaultShowMenu = this.showAppNav`、
|
|
262
|
+
// `defaultShowAppNav = this.showMenu`,两者交叉,还原时把 appNav 的旧值写进 menu、反之亦然。
|
|
263
|
+
// ③ **卸载不等于「用户离开了本页」**:标签是 v-for + v-show 全挂载,关掉一个**后台**标签、
|
|
264
|
+
// 或标签数超过 MOUNTED_TAB_MAX 被 LRU 淘汰内容,都会走到这里 ——
|
|
265
|
+
// 于是「关掉一个大屏标签,把另一个正在看的标签的菜单弹了出来」。
|
|
266
|
+
// 这段代码此前从未执行过(keep-alive 的页从不真卸载),M0 改成真卸载后才被唤醒,故一并清理。
|
|
267
|
+
window.removeEventListener('resize', this.updateBodyHeight)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
</script>
|
|
271
|
+
|
|
272
|
+
<style lang="scss" scoped>
|
|
273
|
+
.dashboard {
|
|
274
|
+
width: 100%;
|
|
275
|
+
height: 100%;
|
|
276
|
+
overflow: auto;
|
|
277
|
+
overflow-x: hidden;
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
|
|
281
|
+
.base-page {
|
|
282
|
+
position: static;
|
|
283
|
+
overflow-x: hidden;
|
|
284
|
+
padding: 0;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
.dashboard--hidden-scroll-bar {
|
|
288
|
+
.dashboard-container {
|
|
289
|
+
&::-webkit-scrollbar-thumb {
|
|
290
|
+
background: none;
|
|
291
|
+
&:hover {
|
|
292
|
+
background-color: rgba(95, 95, 95, .3);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
.dashboard-container {
|
|
298
|
+
width: 100%;
|
|
299
|
+
flex: 1;
|
|
300
|
+
overflow: auto;
|
|
301
|
+
}
|
|
302
|
+
.dashboard-component {
|
|
303
|
+
width: 100%;
|
|
304
|
+
height: 100%;
|
|
305
|
+
overflow: auto;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.dashboard--dark {
|
|
309
|
+
.dashboard-container{
|
|
310
|
+
background: #151C3D;
|
|
311
|
+
.dashboard-component {
|
|
312
|
+
>div {
|
|
313
|
+
// background: rgba(255,255,255, .1)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
.dashboard--technology {
|
|
319
|
+
.dashboard-container{
|
|
320
|
+
background: url('@/assets/images/dashboard/technology_bg.jpg') no-repeat center;
|
|
321
|
+
background-size: cover;
|
|
322
|
+
|
|
323
|
+
}
|
|
324
|
+
.component {
|
|
325
|
+
// position: relative;
|
|
326
|
+
// &::before {
|
|
327
|
+
// content: '';
|
|
328
|
+
// position: absolute;
|
|
329
|
+
// top: 0;
|
|
330
|
+
// display: block;
|
|
331
|
+
// width: 100%;
|
|
332
|
+
// height: 1px;
|
|
333
|
+
// background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
|
|
334
|
+
// opacity: 0.5;
|
|
335
|
+
// }
|
|
336
|
+
// &::after {
|
|
337
|
+
// content: '';
|
|
338
|
+
// position: absolute;
|
|
339
|
+
// bottom: 0;
|
|
340
|
+
// display: block;
|
|
341
|
+
// width: 100%;
|
|
342
|
+
// height: 1px;
|
|
343
|
+
// background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
|
|
344
|
+
// opacity: 0.5;
|
|
345
|
+
// }
|
|
346
|
+
// background: rgba(255,255,255, .1);
|
|
347
|
+
// backdrop-filter: blur(4px);
|
|
348
|
+
// >div {
|
|
349
|
+
// background: rgba(255,255,255, .1);
|
|
350
|
+
// backdrop-filter: blur(4px);
|
|
351
|
+
// }
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
</style>
|