@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.
Files changed (64) hide show
  1. package/API.md +79 -0
  2. package/README.md +40 -2
  3. package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
  4. package/dist/_virtual/preload-helper.js +88 -0
  5. package/dist/assets/technology_bg-oWIS2awO.jpg +0 -0
  6. package/dist/src/Dashboard.js +332 -0
  7. package/dist/src/DashboardGrid.js +182 -0
  8. package/dist/src/DesignGrid.js +711 -0
  9. package/dist/src/DesignItem.js +63 -0
  10. package/dist/src/Div.js +408 -0
  11. package/dist/src/Header.js +385 -0
  12. package/dist/src/Preview.js +27 -0
  13. package/dist/src/Tabs.js +468 -0
  14. package/dist/src/Title.js +101 -0
  15. package/dist/src/Widget.js +266 -0
  16. package/dist/src/chart-request-scope.js +32 -0
  17. package/dist/src/design-target.js +57 -0
  18. package/dist/src/grid/compact.js +68 -0
  19. package/dist/src/grid/compat/GridItem.js +35 -0
  20. package/dist/src/grid/compat/GridLayout.js +76 -0
  21. package/dist/src/grid/designState.js +21 -0
  22. package/dist/src/grid/engines.js +25 -0
  23. package/dist/src/grid/geometry.js +44 -0
  24. package/dist/src/grid/gridstackParams.js +69 -0
  25. package/dist/src/grid/mixins.js +108 -0
  26. package/dist/src/grid/model.js +223 -0
  27. package/dist/src/grid/useViewportGate.js +55 -0
  28. package/dist/src/index.js +26 -0
  29. package/dist/src/layout-util.js +108 -0
  30. package/dist/src/mobile-design.js +50 -0
  31. package/dist/src/theme-style.js +133 -0
  32. package/dist/style.css +1009 -0
  33. package/package.json +85 -3
  34. package/src/Dashboard.vue +459 -0
  35. package/src/DashboardGrid.vue +189 -0
  36. package/src/DesignGrid.vue +851 -0
  37. package/src/DesignItem.vue +124 -0
  38. package/src/Div.vue +419 -0
  39. package/src/Header.vue +501 -0
  40. package/src/Preview.vue +30 -0
  41. package/src/Tabs.vue +484 -0
  42. package/src/Title.vue +107 -0
  43. package/src/Widget.vue +354 -0
  44. package/src/chart-request-scope.js +22 -0
  45. package/src/design-target.js +55 -0
  46. package/src/grid/compact.js +81 -0
  47. package/src/grid/compat/GridItem.vue +30 -0
  48. package/src/grid/compat/GridLayout.vue +76 -0
  49. package/src/grid/designState.js +17 -0
  50. package/src/grid/engines.js +22 -0
  51. package/src/grid/geometry.js +55 -0
  52. package/src/grid/gridstackParams.js +72 -0
  53. package/src/grid/mixins.js +114 -0
  54. package/src/grid/model.js +259 -0
  55. package/src/grid/useViewportGate.js +57 -0
  56. package/src/index.js +28 -0
  57. package/src/layout-util.js +157 -0
  58. package/src/mobile-design.js +50 -0
  59. package/src/theme-style.js +185 -0
  60. package/types/div.d.ts +1 -0
  61. package/types/index.d.ts +7 -0
  62. package/types/tabs.d.ts +1 -0
  63. package/types/title.d.ts +1 -0
  64. package/types/widget.d.ts +1 -0
package/src/Tabs.vue ADDED
@@ -0,0 +1,484 @@
1
+ <template>
2
+ <div ref="container" :class="['container', 'container--'+theme, { 'drag-element': isDragElement }, { 'is-design': mode==='design'}]" @click="updateTabName">
3
+ <div class="tabs el-tabs el-tabs--card el-tabs--top">
4
+ <div class="el-tabs__header is-top">
5
+ <span tabindex="0" class="el-tabs__new-tab" v-if="mode==='design'">
6
+ <i class="el-icon-plus" @click="addTab"></i>
7
+ </span>
8
+ <div class="el-tabs__nav-wrap is-top">
9
+ <div class="el-tabs__nav-scroll">
10
+ <div role="tablist" class="el-tabs__nav is-top">
11
+ <div :class="['el-tabs__item', 'is-top', {'is-closable': mode==='design' }, { 'is-active': activeTab===tab.id }]" v-for="tab in items" :key="tab.id" @dblclick="editTab(tab)" @click.stop="activeTab=tab.id">
12
+ <input
13
+ class="edit-tab__input"
14
+ v-if="editTabId===tab.id"
15
+ ref="editTabInput"
16
+ type="text"
17
+ placeholder=""
18
+ v-model="editTabName"
19
+ maxlength="10"
20
+ />
21
+ <template v-else>
22
+ <span>{{tab.name}}</span>
23
+ <span class="el-icon-close" @click.stop="removeTab(tab.id)" v-if="mode==='design'"></span>
24
+ </template>
25
+
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ <div class="el-tabs__content" ref="tabsContent">
32
+ <div class="el-tab-pane" v-for="tab in items" :key="tab.id" :style="{display: activeTab===tab.id? 'block': 'none'}" >
33
+ <!-- 运行态:headless 网格,不依赖布局库;移动端(真机 / 手机框预览)页签内子项按堆叠重排(containerGridMixin.mobileItems) -->
34
+ <epx-dashboard-grid
35
+ v-if="mode !== 'design'"
36
+ class="epx-dashboard-tabs__grid"
37
+ :items="mobileItems(tab.items || [])"
38
+ :grid="gridParams"
39
+ :load-way="loadWay"
40
+ :body-height="bodyHeight"
41
+ scroll-root-selector=".epx-dashboard__container, .dashboard-container">
42
+ <template #item="{ item }">
43
+ <div class="drag-component" >
44
+ <component :is="'epx-'+ item.contentType" :ref="`component_${item.i}`"
45
+ class="epx-dashboard__component"
46
+ is-dashboard
47
+ :key="item.id"
48
+ :comp-id="item.id"
49
+ :comp-title="item.title"
50
+ :comp-style="item.style"
51
+ :comp-configs="getCompConfigs(item, item.id)"
52
+ :comp-grid="childGrid(item)"
53
+ :chart-cache="chartCache"
54
+ :mobile-appearance="mobileStacked ? item.mobileAppearance : undefined"
55
+ :items="item.items"
56
+ :item-configs="itemConfigs"
57
+ :theme="theme" ></component>
58
+ </div>
59
+ </template>
60
+ </epx-dashboard-grid>
61
+ <!-- 设计态:子网格(gridstack 封装)挂在页面级根实例下,跨网格拖入 / 拖出由 transfer 事件搬数据。
62
+ 只给激活页签建子网格:gridstack 一个壳只能有一个子网格(makeSubGrid 对已有 subGrid 的壳直接返回旧实例),
63
+ 非激活页签 display:none 也量不到宽度;切页签时旧子网格随内容卸载、新页签重建(页签内容组件随之重建) -->
64
+ <epx-dashboard-design-grid
65
+ v-else-if="activeTab === tab.id"
66
+ class="epx-dashboard-tabs__design-grid"
67
+ :sub-grid-of="epxDashboardDesignGrid"
68
+ :items="tab.items || []"
69
+ :grid="gridParams"
70
+ :body-height="bodyHeight"
71
+ :min-height="designMinHeight"
72
+ @change="onDesignChange"
73
+ @transfer-out="item => onTransferOut(tab, item)"
74
+ @transfer-in="item => onTransferIn(tab, item)">
75
+ <template #item="{ item }">
76
+ <epx-dashboard-design-item :item="item" :active="!!design && design.isActive(item)" :floatable="gridParams.mode !== 'float'" @select="openConfigPanel">
77
+ <!-- 渲染组件:嵌套容器同样进设计态(items / changeItems / open-config 的传法与页面级画布对容器项一致) -->
78
+ <div v-if="item.contentType && !reloadComponent[item.id]" class="drag-component">
79
+ <epx-component :name="item.contentType" :ref="`component_${item.i}`"
80
+ class="epx-dashboard__component"
81
+ is-dashboard
82
+ :key="item.id"
83
+ :comp-id="item.id"
84
+ :comp-title="item.title"
85
+ :comp-style="item.style"
86
+ :comp-configs="getCompConfigs(item, item.id)"
87
+ :comp-grid="childGrid(item)"
88
+ :comp-item="item"
89
+ :items="item.items"
90
+ :item-configs="itemConfigs"
91
+ :dashboard-id="dashboardId"
92
+ :all-component-list="allComponentList"
93
+ :theme="theme"
94
+ mode="design"
95
+ :mobile-appearance="mobileStacked ? item.mobileAppearance : undefined"
96
+ @changeItems="v => changeSubItems(tab, item, v)"
97
+ @open-config="(...args) => $emit('open-config', ...args)"></epx-component>
98
+ </div>
99
+ <span v-else>{{item.i}}</span>
100
+ <template #operates>
101
+ <i class="component-operate__item el-icon-edit-outline" v-if="item.contentType==='report-chart'" @click="designReport(item)"></i>
102
+ <i class="component-operate__item el-icon-setting" @click="editComponent(item)"></i>
103
+ <!-- <i class="component-operate__item el-icon-copy-document" v-if="item.contentType==='report-chart'" @click="$emit('copy-report', item)"></i> -->
104
+ <el-dropdown class="componnet-dropdown" @command="v=>deleteComponent(item, v)" v-if="item.contentType==='report-chart'">
105
+ <i class="component-operate__item el-icon-delete"></i>
106
+ <template v-slot:dropdown><el-dropdown-menu>
107
+ <el-dropdown-item command="only">仅从仪表盘中删除</el-dropdown-item>
108
+ <el-dropdown-item command="all">从仪表盘和报表库中删除</el-dropdown-item>
109
+ </el-dropdown-menu></template>
110
+ </el-dropdown>
111
+ <i class="component-operate__item el-icon-delete" v-else @click="deleteComponent(item)"></i>
112
+ </template>
113
+ </epx-dashboard-design-item>
114
+ </template>
115
+ </epx-dashboard-design-grid>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </template>
121
+
122
+ <script>
123
+ import { defineAsyncComponent } from 'vue'
124
+ import { normalizeGridItem } from './layout-util'
125
+ import DashboardGrid from './DashboardGrid.vue'
126
+ import { containerGridMixin } from './grid/mixins'
127
+ export default {
128
+ name: 'epx-dashboard-tabs',
129
+ // 运行态网格参数 / 门控策略 / 嵌套容器网格透传(compGrid prop、gridParams / loadWay / childGrid)来自 containerGridMixin
130
+ mixins: [containerGridMixin],
131
+ components: {
132
+ [DashboardGrid.name]: DashboardGrid,
133
+ // 设计态画布(gridstack 封装)与项 chrome 按需加载,运行态不拉 gridstack
134
+ 'epx-dashboard-design-grid': defineAsyncComponent(() => import('./DesignGrid.vue')),
135
+ 'epx-dashboard-design-item': defineAsyncComponent(() => import('./DesignItem.vue'))
136
+ },
137
+ inject: {
138
+ // 父网格 api(页面级画布或上级容器的 DesignGrid 提供):激活页签的子网格挂到它下面;独立挂载时为空,子网格退化为独立网格
139
+ epxDashboardDesignGrid: { default: null },
140
+ // 设计态共享选中态(页面级画布提供):容器内与页面级只有一个选中项
141
+ epxDashboardDesign: { default: null }
142
+ },
143
+ props: {
144
+ items: {
145
+ type: Array,
146
+ default () {
147
+ return []
148
+ }
149
+ },
150
+ itemConfigs: {
151
+ type: Object,
152
+ default () {
153
+ return {}
154
+ }
155
+ },
156
+ theme: String,
157
+ // 本容器项的 id:设计态选中项的 owner 标记(与 Div 对齐)
158
+ compId: String,
159
+ mode: String,
160
+ allComponentList: Array,
161
+ chartCache: Boolean,
162
+ dashboardId: String
163
+ },
164
+ data () {
165
+ return {
166
+ activeTab: '',
167
+ reloadComponent: {},
168
+ isDragElement: false,
169
+ editTabId: '',
170
+ editTabName: '',
171
+ // 设计态页签内容区可视高度,传给激活页签的子网格做 min-height
172
+ designMinHeight: 0
173
+ }
174
+ },
175
+ computed: {
176
+ tab () {
177
+ return this.items.find(item => item.id === this.activeTab)
178
+ },
179
+ tabIndex () {
180
+ return this.items.findIndex(item => item.id === this.activeTab)
181
+ },
182
+ getCompConfigs () {
183
+ return (component) => {
184
+ // 如果是页面 查找页面默认配置
185
+ const { configs = {}, id, title, contentType, ...otherConfigs } = component
186
+ const compId = this.getDashboardLegacy() && this.dashboardId ? this.dashboardId + '-' + id : id
187
+ const itemConfig = this.itemConfigs[compId] || {}
188
+ const _configs = { ...otherConfigs, ...configs, ...itemConfig }
189
+ if (title) {
190
+ _configs.componentName = title
191
+ }
192
+ return _configs
193
+ }
194
+ },
195
+ design () {
196
+ return this.epxDashboardDesign
197
+ },
198
+ activeComponent () {
199
+ return this.design ? this.design.state.activeItem : null
200
+ }
201
+ },
202
+ watch: {
203
+ items: {
204
+ handler (tabs = []) {
205
+ // i 与 id 兜底(i 不再持久化)+ x/y/w/h 必须是数字(网格引擎按数值布局)
206
+ for (const tab of tabs) {
207
+ for (const item of (tab.items || [])) {
208
+ normalizeGridItem(item)
209
+ }
210
+ }
211
+ },
212
+ deep: true,
213
+ immediate: true
214
+ }
215
+ },
216
+ methods: {
217
+ addTab () {
218
+ const newTabName = this.items.length + ''
219
+ const tabs = this.items
220
+ tabs.push({
221
+ name: '标签页' + (this.items.length + 1),
222
+ id: newTabName,
223
+ items: []
224
+ })
225
+ this.$emit('changeItems', tabs)
226
+ },
227
+ removeTab (targetName) {
228
+ const tabs = this.items
229
+ if (tabs.length <= 1) {
230
+ this.$message.error('最少有一个标签')
231
+ return
232
+ }
233
+ let activeName = this.activeTab
234
+ let activeIndex = -1
235
+ if (activeName === targetName) {
236
+ tabs.forEach((tab, index) => {
237
+ if (tab.id === targetName) {
238
+ activeIndex = index
239
+ const nextTab = tabs[index + 1] || tabs[index - 1]
240
+ if (nextTab) {
241
+ activeName = nextTab.id
242
+ }
243
+ }
244
+ })
245
+ }
246
+ this.activeTab = activeName
247
+ tabs.splice(activeIndex, 1)
248
+ this.$emit('changeItems', tabs)
249
+ },
250
+ editTab (tab) {
251
+ if (this.mode !== 'design') return
252
+ this.editTabId = tab.id
253
+ this.editTabName = tab.name
254
+ this.$nextTick(() => {
255
+ this.$refs.editTabInput[0].focus()
256
+ })
257
+ },
258
+ updateTabName () {
259
+ if (this.editTabId && this.editTabName) {
260
+ const index = this.items.findIndex(item => item.id === this.editTabId)
261
+ this.items.splice(index, 1, { ...this.items[index], name: this.editTabName })
262
+ this.$emit('changeItems', this.items)
263
+ this.editTabId = null
264
+ this.editTabName = ''
265
+ }
266
+ },
267
+ addGridItem (v) {
268
+ const tab = this.tabs.find(item => item.name === this.activeTab)
269
+ if (tab) {
270
+ tab.items.push(v)
271
+ }
272
+ },
273
+ // 库改了子项坐标(拖拽 / 缩放 / 推挤 / 首次对账):画布已就地写回,只需通知上层保存
274
+ onDesignChange () {
275
+ this.$emit('changeItems', this.items)
276
+ },
277
+ // 跨网格搬出(页签内项被拖到页面或别的容器):源侧必须同步移出,否则子网格下一次同步会把壳重新加回来
278
+ onTransferOut (tab, item) {
279
+ const index = (tab.items || []).findIndex(i => i.id === item.id)
280
+ if (index !== -1) tab.items.splice(index, 1)
281
+ this.$emit('changeItems', this.items)
282
+ },
283
+ // 跨网格搬入当前页签:画布已按本网格换算过 w/h
284
+ onTransferIn (tab, item) {
285
+ if (!tab.items) tab.items = []
286
+ tab.items.push(item)
287
+ this.$emit('changeItems', this.items)
288
+ },
289
+ // 页签里嵌套容器的子项变化:替换该容器项对象后上报(与页面级画布 changeSubItems 同手法)
290
+ changeSubItems (tab, item, v) {
291
+ const index = (tab.items || []).findIndex(i => i.id === item.id)
292
+ if (index === -1) return
293
+ tab.items.splice(index, 1, { ...tab.items[index], items: v })
294
+ this.$emit('changeItems', this.items)
295
+ },
296
+ openConfigPanel (component) {
297
+ if (this.design) this.design.select(component, this.compId)
298
+ },
299
+ // 设计态页签内容区可视高度(所有页签共用同一个内容区)
300
+ measureDesignHeight () {
301
+ if (this.$refs.tabsContent) this.designMinHeight = this.$refs.tabsContent.clientHeight
302
+ },
303
+ designReport (component) {
304
+ const configs = this.getCompConfigs(component, component.id)
305
+ this.goPage({
306
+ component: 'report-design',
307
+ params: {
308
+ id: configs.id,
309
+ callback: () => {
310
+ this.reloadComponent[component.id] = true
311
+ this.$nextTick(() => {
312
+ this.reloadComponent[component.id] = false
313
+ })
314
+ }
315
+ }
316
+
317
+ })
318
+ },
319
+ editComponent (activeComponent) {
320
+ const componentInfo = this.allComponentList.find(item => item.CODE === activeComponent.contentType) || { CODE: activeComponent.contentType }
321
+ const configs = this.getCompConfigs(activeComponent, activeComponent.id)
322
+ this.$emit('open-config', activeComponent.id, componentInfo, configs)
323
+ },
324
+ deleteComponent (component, command) {
325
+ const activeComponent = component || this.activeComponent
326
+ const index = this.tab.items.findIndex(item => item.id === activeComponent.id)
327
+ if (activeComponent.contentType === 'report-chat' && command === 'all') {
328
+ const configs = this.getCompConfigs(activeComponent, activeComponent.id)
329
+ this.$http.removeMulti({
330
+ entityId: 'FM_REPORT_BASE',
331
+ ids: configs.id
332
+ })
333
+ }
334
+ if (index !== -1) {
335
+ this.tab.items.splice(index, 1)
336
+ this.$emit('changeItems', this.items)
337
+ // 被删的正是选中项:清掉共享选中态,操作条 / 属性面板不再指向已删除的项
338
+ if (this.design && this.design.isActive(activeComponent)) this.design.clear()
339
+ }
340
+ }
341
+ },
342
+ mounted () {
343
+ if (!this.items || this.items.length === 0) {
344
+ const items = [{
345
+ id: 'tab_' + new Date().getTime(),
346
+ name: '标签页1',
347
+ items: []
348
+ }]
349
+ this.$emit('changeItems', items)
350
+ this.activeTab = items[0].id
351
+ } else {
352
+ this.activeTab = this.items[0].id
353
+ }
354
+ if (this.mode !== 'design') return
355
+ this.measureDesignHeight()
356
+ if (typeof ResizeObserver !== 'undefined' && this.$refs.tabsContent) {
357
+ this.designResizeObserver = new ResizeObserver(() => this.measureDesignHeight())
358
+ this.designResizeObserver.observe(this.$refs.tabsContent)
359
+ }
360
+ },
361
+ beforeUnmount () {
362
+ if (this.designResizeObserver) this.designResizeObserver.disconnect()
363
+ }
364
+ }
365
+ </script>
366
+
367
+ <style lang="scss" scoped>
368
+ div.container {
369
+ height: 100%;
370
+ pointer-events: all!important;
371
+ }
372
+ // div.container--technology {
373
+ // background-color: rgba(255,255,255, .1);
374
+ // &::before {
375
+ // content: '';
376
+ // position: absolute;
377
+ // top: 0;
378
+ // display: block;
379
+ // width: 100%;
380
+ // height: 1px;
381
+ // background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
382
+ // opacity: 0.5;
383
+ // }
384
+ // &::after {
385
+ // content: '';
386
+ // position: absolute;
387
+ // bottom: 0;
388
+ // display: block;
389
+ // width: 100%;
390
+ // height: 1px;
391
+ // background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
392
+ // opacity: 0.5;
393
+ // }
394
+ // .el-tabs--card > .el-tabs__header {
395
+ // border-color: rgba(255,255,255, .3);
396
+ // .el-tabs__nav {
397
+ // border-color: rgba(255,255,255, .3);
398
+ // }
399
+ // .el-tabs__item {
400
+ // border-left-color: rgba(255,255,255, .3);
401
+ // color: rgba(255,255,255, .6);
402
+ // &.is-active, &:hover, &:focus {
403
+ // color: #fff;
404
+ // background: rgba(0,0,0,.2)
405
+ // }
406
+ // }
407
+ // }
408
+ // }
409
+ // div.container--dark {
410
+ // background-color: rgba(255,255,255, .1);
411
+ // .el-tabs--card > .el-tabs__header {
412
+ // border-color: rgba(255,255,255, .3);
413
+ // .el-tabs__nav {
414
+ // border-color: rgba(255,255,255, .3);
415
+ // }
416
+ // .el-tabs__item {
417
+ // border-left-color: rgba(255,255,255, .3);
418
+ // color: rgba(255,255,255, .6);
419
+ // &.is-active, &:hover, &:focus {
420
+ // color: #fff;
421
+ // background: rgba(0,0,0,.2)
422
+ // }
423
+ // }
424
+ // }
425
+ // }
426
+ .tabs {
427
+ height: 100%;
428
+
429
+ :deep(.el-tabs__content) {
430
+ height: calc(100% - 58px);
431
+ }
432
+ :deep(.el-tab-pane) {
433
+ height: 100%;
434
+ overflow: auto;
435
+ }
436
+ :deep(.el-tabs__item) {
437
+ color: inherit;
438
+ }
439
+
440
+ }
441
+ .drag-element {
442
+
443
+ :deep(.el-tab-pane) {
444
+ border: 1px dashed;
445
+ @include border_color_primary;
446
+ }
447
+
448
+ }
449
+ .epx-dashboard-tabs__grid {
450
+ min-height: 100%;
451
+ }
452
+ .drag-component {
453
+ width: 100%;
454
+ height: 100%;
455
+ overflow: auto;
456
+ }
457
+ .is-design {
458
+ .drag-component {
459
+ >* {
460
+ pointer-events: none;
461
+ }
462
+ }
463
+ }
464
+ // 操作条按钮:横排在 epx-dashboard-design-item__operates 里
465
+ .component-operate__item {
466
+ display: block;
467
+ font-size: var(--epx-font-size-base);
468
+ color: #fff;
469
+ line-height: 20px;
470
+ text-align: center;
471
+ cursor: pointer;
472
+ margin: 2px 0;
473
+ }
474
+ .componnet-dropdown {
475
+ .component-operate__item {
476
+ width: 20px;
477
+ margin-top: 0;
478
+ }
479
+ }
480
+ .edit-tab__input {
481
+ border: none;
482
+ outline: none;
483
+ }
484
+ </style>
package/src/Title.vue ADDED
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <div :class="['epx-dashboard-title', 'epx-dashboard-title--'+ theme]" :style="[layoutStyle, $component.style]">
3
+ <h3 v-if="$configs.level==='h3'" :style="titleStyle"><i v-if="$configs.icon" :class="$configs.icon" :style="{color: $configs.iconColor}"></i>{{ $component.title }}</h3>
4
+ <h2 v-else-if="$configs.level==='h2'" :style="titleStyle"><i v-if="$configs.icon" :class="$configs.icon" :style="{color: $configs.iconColor}"></i>{{ $component.title }}</h2>
5
+ <h1 v-else :style="titleStyle"><i v-if="$configs.icon" :class="$configs.icon" :style="{color: $configs.iconColor}"></i>{{ $component.title }}</h1>
6
+ <p class="epx-dashboard-title__sub-title" v-if="subTitle" :style="subTitleStyle">{{ subTitle }}</p>
7
+ </div>
8
+ </template>
9
+
10
+ <script>
11
+ import component from '@epxing/core/src/mixins/component'
12
+ export default {
13
+ name: 'epx-dashboard-title',
14
+ mixins: [component],
15
+
16
+ // ↓↓↓ 配置点默认值(页面配置可覆盖)
17
+ subTitle: '', // 副标题文案,配置后展示在标题下方
18
+ subTitleFontStyle: {}, // 副标题字体样式(PC 端)
19
+ subTitleFontStyleMobile: {}, // 副标题字体样式(移动端,在 PC 样式基础上覆盖,只需配置差异项)
20
+ titleFontStyle: {}, // 主标题字体样式(PC 端)
21
+ titleFontStyleMobile: {}, // 主标题字体样式(移动端,在 PC 样式基础上覆盖,只需配置差异项)
22
+ horizontal: 'center', // 水平对齐:flex-start 居左 / center 居中 / flex-end 居右
23
+ vertical: 'center', // 垂直对齐:flex-start 顶部 / center 居中 / flex-end 底部
24
+
25
+ props: {
26
+ theme: String
27
+ },
28
+ computed: {
29
+ subTitle () {
30
+ const { subTitle = '' } = this.$configs
31
+ return subTitle
32
+ },
33
+ titleStyle () {
34
+ const { titleFontStyle = {}, titleFontStyleMobile = {} } = this.$configs
35
+ const style = { ...titleFontStyle }
36
+ // 移动端在 PC 样式基础上叠加覆盖,仅需配置与 PC 不同的属性
37
+ if (this.$isMobile) Object.assign(style, titleFontStyleMobile)
38
+ return style
39
+ },
40
+ subTitleStyle () {
41
+ const { subTitleFontStyle = {}, subTitleFontStyleMobile = {} } = this.$configs
42
+ const style = { ...subTitleFontStyle }
43
+ // 移动端在 PC 样式基础上叠加覆盖,仅需配置与 PC 不同的属性
44
+ if (this.$isMobile) Object.assign(style, subTitleFontStyleMobile)
45
+ return style
46
+ },
47
+ layoutStyle () {
48
+ const { horizontal = 'center', vertical = 'center' } = this.$configs
49
+ // 组件样式里配置了 textAlign 时,水平对齐以它为准
50
+ const { textAlign } = this.$component.style || {}
51
+ const align = { left: 'flex-start', center: 'center', right: 'flex-end' }[textAlign] || horizontal
52
+ return {
53
+ alignItems: align,
54
+ justifyContent: vertical,
55
+ textAlign: { 'flex-start': 'left', center: 'center', 'flex-end': 'right' }[align]
56
+ }
57
+ }
58
+ }
59
+ }
60
+ </script>
61
+
62
+ <style lang="scss">
63
+ @include b(dashboard-title) {
64
+ height: 100%;
65
+ vertical-align: middle;
66
+ display: flex;
67
+ flex-direction: column;
68
+ align-items: center;
69
+ justify-content: space-around;
70
+ text-align: center;
71
+ i {
72
+ margin-right: 4px;
73
+ }
74
+ @include e(sub-title) {
75
+ margin: 4px 0 0;
76
+ font-size: 12px;
77
+ color: var(--color-text-assistant);
78
+ }
79
+ @include m(dark) {
80
+ color: rgba(255, 255, 255, .8);
81
+ @include e(sub-title) {
82
+ color: rgba(255, 255, 255, .5);
83
+ }
84
+ }
85
+ @include m(technology) {
86
+ color: rgba(255, 255, 255, .8);
87
+ @include e(sub-title) {
88
+ color: rgba(255, 255, 255, .5);
89
+ }
90
+ }
91
+ }
92
+
93
+ // 移动端默认缩小主标题;用 em 保持相对根节点字号缩放,组件样式/配置点显式设置的 fontSize(行内)仍优先
94
+ @media only screen and (max-width: 992px) {
95
+ @include b(dashboard-title) {
96
+ h1 {
97
+ font-size: 1.5em;
98
+ }
99
+ h2 {
100
+ font-size: 1.25em;
101
+ }
102
+ h3 {
103
+ font-size: 1em;
104
+ }
105
+ }
106
+ }
107
+ </style>