@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/package.json CHANGED
@@ -1,6 +1,88 @@
1
1
  {
2
2
  "name": "@epxing/dashboard",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "5.3.0-beta.1",
4
+ "description": "看板运行态渲染",
5
+ "type": "module",
6
+ "main": "./dist/src/index.js",
7
+ "module": "./dist/src/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./types/index.d.ts",
11
+ "import": "./dist/src/index.js",
12
+ "default": "./dist/src/index.js"
13
+ },
14
+ "./src": "./dist/src/index.js",
15
+ "./tabs": {
16
+ "types": "./types/tabs.d.ts",
17
+ "import": "./dist/src/Tabs.js",
18
+ "default": "./dist/src/Tabs.js"
19
+ },
20
+ "./title": {
21
+ "types": "./types/title.d.ts",
22
+ "import": "./dist/src/Title.js",
23
+ "default": "./dist/src/Title.js"
24
+ },
25
+ "./div": {
26
+ "types": "./types/div.d.ts",
27
+ "import": "./dist/src/Div.js",
28
+ "default": "./dist/src/Div.js"
29
+ },
30
+ "./widget": {
31
+ "types": "./types/widget.d.ts",
32
+ "import": "./dist/src/Widget.js",
33
+ "default": "./dist/src/Widget.js"
34
+ },
35
+ "./src/*.css": "./src/*.css",
36
+ "./src/*.scss": "./src/*.scss",
37
+ "./src/*.png": "./src/*.png",
38
+ "./src/*.svg": "./src/*.svg",
39
+ "./src/*.jpg": "./src/*.jpg",
40
+ "./src/*.gif": "./src/*.gif",
41
+ "./src/*.woff": "./src/*.woff",
42
+ "./src/*.woff2": "./src/*.woff2",
43
+ "./src/*.ttf": "./src/*.ttf",
44
+ "./src/*.js": "./dist/src/*.js",
45
+ "./src/*.jsx": "./dist/src/*.js",
46
+ "./src/*.vue": "./dist/src/*.js",
47
+ "./src/*": "./dist/src/*.js",
48
+ "./style.css": "./dist/style.css",
49
+ "./package.json": "./package.json"
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "src",
54
+ "types",
55
+ "API.md"
56
+ ],
57
+ "dependencies": {
58
+ "gridstack": "13.3.0",
59
+ "@epxing/core": "^5.3.0-beta.1",
60
+ "@epxing/utils": "^5.3.0-beta.1",
61
+ "@epxing/components": "^5.3.0-beta.1",
62
+ "@epxing/theme": "^5.3.0-beta.1"
63
+ },
64
+ "peerDependencies": {
65
+ "vue": "3.5.39",
66
+ "vuex": "4.1.0",
67
+ "element-plus": "2.14.3"
68
+ },
69
+ "types": "./types/index.d.ts",
70
+ "sideEffects": [
71
+ "**/*.css",
72
+ "**/*.scss",
73
+ "**/*.vue",
74
+ "./dist/src/app.js",
75
+ "./dist/src/plugins/**",
76
+ "./dist/src/store/**",
77
+ "./dist/src/mixins/**",
78
+ "./dist/src/runtime-utils/util.js",
79
+ "./dist/src/runtime-services.js"
80
+ ],
81
+ "publishConfig": {
82
+ "access": "public",
83
+ "registry": "https://registry.npmjs.org/"
84
+ },
85
+ "scripts": {
86
+ "build": "cd ../.. && cross-env PKG=dashboard vite build -c build/vite.pkg.config.mjs"
87
+ }
6
88
  }
@@ -0,0 +1,459 @@
1
+ <template>
2
+ <div :class="['epx-dashboard', 'epx-dashboard-'+ this.$component.id, 'epx-dashboard--'+ theme, { 'epx-dashboard--hidden-scroll-bar': $configs.scrollBarHidden, 'is-mobile-preview': mobileDashboard }]" :style="$component.style" ref="container">
3
+ <epx-dashboard-header @fullscreen="openFullScreen" :title="$component.title" :scopeFlag="$configs.scopeFlag" :globalSearchFields="$configs.globalSearchFields" :searchDefaultFlag="$configs.searchDefaultFlag" @search="search"></epx-dashboard-header>
4
+ <div ref="dashboard" class="epx-dashboard__container" :style="dashboardStyle" :class="{ 'epx-dashboard__container--mobile': mobileDashboard }">
5
+ <epx-dashboard-grid
6
+ :items="components"
7
+ :grid="gridParams"
8
+ :load-way="loadWay"
9
+ :body-height="bodyHeight">
10
+ <template #item="{ item: component }">
11
+ <!-- 渲染组件 -->
12
+ <div v-if="component.contentType" class="epx-dashboard__component-container">
13
+ <epx-component v-bind="isPage? $forwardAttrs($attrs): {}" class="epx-dashboard__component"
14
+ :name="component.contentType"
15
+ is-dashboard
16
+ :content-type="component.contentType"
17
+ :comp-id="component.id"
18
+ :comp-title="component.title"
19
+ :items="component.items"
20
+ :itemConfigs="$component.itemConfigs"
21
+ :comp-style="mobileComponentStyle(component)"
22
+ :comp-configs="component.configs"
23
+ :comp-grid="containerGrid(component)"
24
+ :chart-cache="$configs.chartCache"
25
+ :theme="themeMode"
26
+ :mobile-appearance="mobileRendering ? component.mobileAppearance : undefined"
27
+ :themeChartConfig="themeChartConfig">
28
+ </epx-component>
29
+ </div>
30
+ </template>
31
+ </epx-dashboard-grid>
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <script>
37
+ import DashboardHeader from './Header'
38
+ import DashboardGrid from './DashboardGrid.vue'
39
+ import chartRequestScope from './chart-request-scope'
40
+ import component from '@epxing/core/src/mixins/component'
41
+ import { pageGridMixin } from './grid/mixins'
42
+ import { buildThemeCss, buildThemeChartConfig } from './theme-style'
43
+ import { stackMobileLayout } from './layout-util'
44
+ import { indexDashboardItems, resolveMobileItem } from './mobile-design'
45
+ import { isLegacyConfig } from './grid/model'
46
+ import { mapState } from 'vuex'
47
+
48
+ // T31/T29:子树内 component 型全刷新。原用 `this.$children` 递归(Vue3 已移除)→
49
+ // 改走 T31 建的父子注册表 `_epxWalkChildren`(它本身就遍历整棵子树,故不需要外层递归)。
50
+ function refesh () {
51
+ this.resolveChartRequestBroker().invalidate()
52
+ this._epxWalkChildren(child => {
53
+ if (child.$options.$componentType === 'component' && child.$refesh) child.$refesh()
54
+ })
55
+ }
56
+ export default {
57
+ name: 'epx-dashboard',
58
+ mixins: [component, pageGridMixin, chartRequestScope],
59
+ components: {
60
+ [DashboardHeader.name]: DashboardHeader,
61
+ [DashboardGrid.name]: DashboardGrid
62
+ },
63
+ $componentType: 'page', // 组件类型: 页面
64
+ navConfigs: {
65
+ flag: false
66
+ },
67
+ style: {},
68
+ layout: 'grid',
69
+ // 组件列表
70
+ items: [
71
+ ],
72
+ // 组件配置
73
+ itemConfigs: {
74
+
75
+ },
76
+ props: {
77
+ inline: Boolean,
78
+ routeKey: [String, Number],
79
+ routes: {
80
+ type: Array,
81
+ default () {
82
+ return []
83
+ }
84
+ }
85
+ },
86
+ data () {
87
+ return {
88
+ headerTitle: null,
89
+ pageParams: {},
90
+ model: {}, // 组件全局data
91
+ pageEvents: {},
92
+ searchScopeParams: [],
93
+ themeMode: null,
94
+ themeStyles: {}
95
+ }
96
+ },
97
+ computed: {
98
+ ...mapState({
99
+ showMenu: state => state.config.showMenu,
100
+ showAppNav: state => state.config.showAppNav
101
+ }),
102
+ pageTitle () {
103
+ return this.headerTitle || (this.page && this.page.title) || (this.meta && this.meta.name)
104
+ },
105
+ components () {
106
+ // items itemConfigs
107
+ const { items = [], mItems } = this.$component
108
+ let list
109
+ if (this.mobileDashboard) {
110
+ list = items.filter(item => !item.mobileHidden)
111
+ } else if (this.$isMobile) {
112
+ // 移动端:优先用单独配置的 mItems(过滤掉标记 mobileHidden 的项);没配则按 PC 布局自动单列堆叠
113
+ if (mItems && mItems.length) {
114
+ const pcIndex = indexDashboardItems(items)
115
+ list = mItems.filter(c => !c.mobileHidden && pcIndex.has(String(c.id))).map(c => resolveMobileItem(c, pcIndex))
116
+ } else {
117
+ const { mobileAutoStack = true } = this.$configs
118
+ list = mobileAutoStack ? stackMobileLayout(items, 48) : items
119
+ }
120
+ } else {
121
+ list = items
122
+ }
123
+ // 布局项必须带 i(设计器与旧配置都以 id 为准),运行时用 id 兜底补齐
124
+ list.forEach(item => {
125
+ if (item && item.i == null && item.id != null) item.i = item.id
126
+ })
127
+ return list
128
+ },
129
+ // 移动端组件样式:以 PC 组件 style 为基础,叠加 mItems 项里设置的移动端覆盖(空值/未设的键沿用 PC)
130
+ mobileComponentStyle () {
131
+ return component => {
132
+ if (!this.$isMobile || this.mobileDashboard) return component.style
133
+ const pcItem = indexDashboardItems(this.$component.items || []).get(String(component.id)) || {}
134
+ const override = {}
135
+ const cur = component.style || {}
136
+ for (const k in cur) {
137
+ if (cur[k] !== undefined && cur[k] !== null && cur[k] !== '') override[k] = cur[k]
138
+ }
139
+ return { ...(pcItem.style || {}), ...override }
140
+ }
141
+ },
142
+ theme () {
143
+ const { theme } = this.$configs
144
+ return theme
145
+ },
146
+ colAttribute () {
147
+ return (component = {}) => {
148
+ const { xsspan = 24, ...other } = component
149
+ const colAttribute = {
150
+ ...other,
151
+ xs: {
152
+ span: xsspan
153
+ }
154
+ }
155
+ delete colAttribute.id
156
+ delete colAttribute.style
157
+ delete colAttribute.title
158
+ return colAttribute
159
+ }
160
+ },
161
+ pageStyle () {
162
+ const { layout = 'grid', direction = 'row', justify = 'flex-start', align = 'top' } = this.$configs
163
+ if (layout === 'flex') {
164
+ return { display: 'flex', 'flex-direction': direction, 'justify-content': justify, 'align-items': align }
165
+ } else {
166
+ return { }
167
+ }
168
+ },
169
+ dashboardStyle () {
170
+ const { backgroundColor, backgroundImage } = this.$configs
171
+ return {
172
+ ...(this.mobileDashboard ? { width: '100%', maxWidth: `${Number(this.$configs.mobileCanvasWidth) || 375}px`, margin: '0 auto', flex: 'none' } : {}),
173
+ backgroundColor,
174
+ backgroundImage: backgroundImage ? `url(${this.$util.getFileUrl(backgroundImage)})` : null
175
+ }
176
+ },
177
+ componentStyle () {
178
+ return component => {
179
+ const { layout } = this.$configs
180
+ const { flex } = component
181
+ const compoentStyle = { }
182
+ if (layout === 'flex' && flex) {
183
+ compoentStyle.flex = 1
184
+ }
185
+ return compoentStyle
186
+ }
187
+ },
188
+ scopeParams () {
189
+ const { globalScopeParams: configScopeParams = [] } = this.$configs
190
+ const { scopeParams: queryScopeParams = [] } = this.$query
191
+
192
+ // 格式化scopeParams
193
+ const _scopeParams = this.$_.cloneDeep([...configScopeParams, ...queryScopeParams, ...this.searchScopeParams])
194
+ return this.$formatScope(_scopeParams)
195
+ },
196
+ themeChartConfig () {
197
+ return buildThemeChartConfig(this.themeStyles, this.mobileRendering)
198
+ }
199
+ },
200
+ watch: {
201
+ pageTitle: {
202
+ handler (v) {
203
+ this.$emit('change-route', this.routeKey, { title: v })
204
+ },
205
+ immediate: true
206
+ },
207
+ scopeParams: {
208
+ deep: true,
209
+ handler (v) {
210
+ if (!this.$_.isEqual(this.oldParams, v)) {
211
+ this.pageParams.scopeParams = v
212
+ }
213
+ this.oldParams = v
214
+ },
215
+ immediate: true
216
+ }
217
+ },
218
+ provide () {
219
+ // 主题图表配置(系列色板等)用函数式 provide 下发,跨任意容器层(dashboard-div/tabs)注入到图表组件,且保持响应式
220
+ // 恒定返回对象(不再区分 isPage 分支返回 undefined):顺带满足 T28 的「Vue3 provide 合并不容忍 undefined」
221
+ const provideData = {
222
+ getMobileDashboard: () => this.mobileDashboard,
223
+ getThemeChartConfig: () => this.themeChartConfig,
224
+ // 容器内网格的门控策略 / 配置是否旧版都跟随页面(provide 只跑一次,用函数延迟读)
225
+ getDashboardLoadWay: () => this.loadWay,
226
+ getDashboardLegacy: () => isLegacyConfig(this.$configs)
227
+ }
228
+ if (this.isPage) {
229
+ provideData.upPage = this
230
+ provideData.pageId = this.page ? this.page.pageId : '' // 当前页面id
231
+ provideData.pageMetaId = this.isPage ? (this.$metaId || (this.page ? this.page.metaId : '')) : null // 当前页面对象id
232
+ }
233
+ return provideData
234
+ },
235
+ methods: {
236
+ getTheme () {
237
+ // 未选主题时直接跳过查询:scope.CODE 为空会命中 FM_CHART_THEME 全部记录,
238
+ // 后端按单条返回会报「存在多条结果数据」,抛出后 setThemeStyle 中断,
239
+ // 连页面自身配置的 themeStyles 都编译不出来(主题样式整体不生效)
240
+ if (!this.theme) return Promise.resolve({})
241
+ return this.$http.find({
242
+ entityId: 'FM_CHART_THEME',
243
+ 'scope.CODE': this.theme
244
+ })
245
+ .then(data => {
246
+ if (data && data.data.CONTENT) {
247
+ this.themeMode = data.data.STYLE
248
+ return JSON.parse(data?.data.CONTENT)
249
+ }
250
+ return {}
251
+ })
252
+ },
253
+ async setThemeStyle () {
254
+ // 获取主题对应的样式
255
+ const { themeStyles: configThemeStyles = {} } = this.$configs
256
+ const activeThemeStyles = await this.getTheme()
257
+ const allThemeStyles = this.$_.assignWith(configThemeStyles, activeThemeStyles, (configThemeItem, themeItem) => {
258
+ // 仅对对象做分区合并,customCss 等字符串值直接取优先级高的一方
259
+ return configThemeItem && themeItem && typeof configThemeItem === 'object' && typeof themeItem === 'object'
260
+ ? { ...themeItem, ...configThemeItem }
261
+ : configThemeItem || themeItem
262
+ })
263
+ this.themeStyles = allThemeStyles
264
+ const styles = buildThemeCss(allThemeStyles, `.epx-dashboard-${this.$component.id}`, path => this.$util.getFileUrl(path), { mobilePreview: this.mobileDashboard })
265
+ let styleElement = document.head.querySelector(`#dashboard-${this.$component.id}`)
266
+ if (!styleElement) {
267
+ styleElement = document.createElement('style')
268
+ styleElement.id = `dashboard-${this.$component.id}`
269
+ styleElement.type = 'text/css'
270
+ styleElement.textContent = styles
271
+ document.head.appendChild(styleElement)
272
+ } else {
273
+ styleElement.textContent = styles
274
+ }
275
+ },
276
+ removeThemeStyle () {
277
+ const styleElement = document.head.querySelector(`#dashboard-${this.$component.id}`)
278
+ styleElement?.remove()
279
+ },
280
+ // reloadPage 会经 RouterView 以新 key 重建整个页面实例,滚动位置无法在此恢复(旧实例的 $refs 已失效)
281
+ $refesh () {
282
+ refesh.apply(this)
283
+ if (this.upPage) this.upPage.$refesh()
284
+ this.reloadPage()
285
+ },
286
+ enableAutoReload () {
287
+ if (this.refreshInterval) {
288
+ clearInterval(this.refreshInterval)
289
+ }
290
+ this.refreshInterval = setInterval(() => {
291
+ refesh.apply(this)
292
+ }, (this.$configs.refreshInterval || 60) * 1000)
293
+ },
294
+ openFullScreen () {
295
+ if (this.$refs.dashboard.requestFullscreen) {
296
+ this.$refs.dashboard.requestFullscreen()
297
+ }
298
+ },
299
+ search (v) {
300
+ this.searchScopeParams = v
301
+ }
302
+ },
303
+ created () {
304
+ // 这里原本存了 showAppNav/showMenu 用于卸载时「还原」,已删除,理由见 beforeUnmount 的注释。
305
+ // 行高 / 间距按视口等比(旧 autoHeight)时才需要跟随窗口尺寸
306
+ const grid = this.gridParams
307
+ if (grid.rowHeight === 'auto' || grid.padding === 'auto') {
308
+ window.addEventListener('resize', this.updateBodyHeight)
309
+ }
310
+ if (this.$configs.autoRefresh) {
311
+ this.enableAutoReload()
312
+ }
313
+ this.setThemeStyle()
314
+ },
315
+
316
+ beforeUnmount () {
317
+ // 与 Widget.vue 同一处问题,已删除两行全局写入。三个理由:
318
+ // ① 整个 packages/dashboard 没有任何地方隐藏菜单/应用导航(openFullScreen 走原生 requestFullscreen),
319
+ // 这是**只还原、从不获取**的全局写入;
320
+ // ② created 里存的值是交叉的(defaultShowMenu 存的是 showAppNav,反之亦然),还原本身就是错的;
321
+ // ③ 标签是 v-for + v-show 全挂载,关**后台**标签或 LRU 淘汰内容都会走到这里 ——
322
+ // 表现为「关掉一个大屏标签,把另一个正在看的标签的菜单弹了出来」。
323
+ // 这段此前从未执行(keep-alive 的页不真卸载),M0 改成真卸载后才被唤醒。
324
+ window.removeEventListener('resize', this.updateBodyHeight)
325
+ this.removeThemeStyle()
326
+ // 清理自动刷新定时器
327
+ if (this.refreshInterval) {
328
+ clearInterval(this.refreshInterval)
329
+ this.refreshInterval = null
330
+ }
331
+ }
332
+ }
333
+ </script>
334
+ <style lang="scss">
335
+ @include b(dashboard) {
336
+ width: 100%;
337
+ height: 100%;
338
+ overflow: auto;
339
+ overflow-x: hidden;
340
+ display: flex;
341
+ flex-direction: column;
342
+ @include m(hidden-scroll-bar) {
343
+ @include e(container) {
344
+ &::-webkit-scrollbar-thumb {
345
+ background: none;
346
+ &:hover {
347
+ background-color: rgba(95, 95, 95, .3);
348
+ }
349
+ }
350
+ }
351
+ }
352
+ @include e(container) {
353
+ // background: #f0f2f6;
354
+ width: 100%;
355
+ flex: 1;
356
+ overflow: auto;
357
+ }
358
+ @include e(component-container) {
359
+ width: 100%;
360
+ height: 100%;
361
+ // 不裁剪子组件的 box-shadow:阴影渲染进栅格间距;滚动下移到 __component 自身
362
+ overflow: visible;
363
+ }
364
+ @include e(component) {
365
+ height: 100%;
366
+ // 自身内容溢出仍可滚动;元素自身的 box-shadow 不受自身 overflow 裁剪
367
+ overflow: auto;
368
+ }
369
+ .epx-card-num__title {
370
+ color: inher
371
+ }
372
+ }
373
+ // 移动端专属缩放:收紧卡片内边距、缩小标题与数字字号,让密集看板在小屏更耐看
374
+ @media only screen and (max-width: 992px) {
375
+ @include b(dashboard) {
376
+ .epx-card-num {
377
+ padding: 12px;
378
+ }
379
+ .epx-card-num__title {
380
+ font-size: 13px;
381
+ }
382
+ .epx-card-num__num > font {
383
+ font-size: 22px;
384
+ }
385
+ .epx-chart__title {
386
+ font-size: 14px;
387
+ }
388
+ .epx-chart__header {
389
+ padding: 0 8px;
390
+ }
391
+ }
392
+ }
393
+ </style>
394
+ <style lang="scss" scoped>
395
+
396
+ // .dashboard--hidden-scroll-bar {
397
+ // .dashboard-container {
398
+ // &::-webkit-scrollbar-thumb {
399
+ // background: none;
400
+ // &:hover {
401
+ // background-color: rgba(95, 95, 95, .3);
402
+ // }
403
+ // }
404
+ // }
405
+ // }
406
+ // .dashboard-container {
407
+
408
+ // }
409
+ // .dashboard-component {
410
+
411
+ // }
412
+
413
+ // .dashboard--dark {
414
+ // .dashboard-container{
415
+ // background: #151C3D;
416
+ // .dashboard-component {
417
+ // >div {
418
+ // // background: rgba(255,255,255, .1)
419
+ // }
420
+ // }
421
+ // }
422
+ // }
423
+ // .dashboard--technology {
424
+ // .dashboard-container{
425
+ // background: url('@/assets/images/dashboard/technology_bg.jpg') no-repeat center;
426
+ // background-size: cover;
427
+
428
+ // }
429
+ // .component {
430
+ // // position: relative;
431
+ // // &::before {
432
+ // // content: '';
433
+ // // position: absolute;
434
+ // // top: 0;
435
+ // // display: block;
436
+ // // width: 100%;
437
+ // // height: 1px;
438
+ // // background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
439
+ // // opacity: 0.5;
440
+ // // }
441
+ // // &::after {
442
+ // // content: '';
443
+ // // position: absolute;
444
+ // // bottom: 0;
445
+ // // display: block;
446
+ // // width: 100%;
447
+ // // height: 1px;
448
+ // // background: linear-gradient(90deg, rgba(45, 164, 247, 0) 0%, #2DA4F7 50%, rgba(45, 164, 247, 0) 100%);
449
+ // // opacity: 0.5;
450
+ // // }
451
+ // // background: rgba(255,255,255, .1);
452
+ // // backdrop-filter: blur(4px);
453
+ // // >div {
454
+ // // background: rgba(255,255,255, .1);
455
+ // // backdrop-filter: blur(4px);
456
+ // // }
457
+ // }
458
+ // }
459
+ </style>