@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
@@ -0,0 +1,157 @@
1
+ // 看板移动端布局工具
2
+ // 把 PC 端组件布局(vue-grid-layout 的 x/y/w/h)转成移动端堆叠布局:
3
+ // 数字统计类组件半宽(一行 2 个),其它组件整行,按视觉顺序(上→下、左→右)纵向排列。
4
+ // 运行时无 mItems 时的自动兜底、移动端设计器「按 PC 布局生成」均复用本函数。
5
+
6
+ // 数字类组件在移动端固定半宽(一行 2 个):数字统计 / 数字卡 / 流程数字 / 任务数字卡 / 数字子项等
7
+ const DEFAULT_HALF_TYPES = [
8
+ 'num-statistic', 'card-num', 'num',
9
+ 'flow-num-statistic', 'flow-num-card', 'flow-num-item',
10
+ 'task-num-card',
11
+ 'num-item', 'num-sub-item'
12
+ ]
13
+ // 容器类:移动端可展开成子项平铺(默认)或整块保留(item.mobileExpand === false)
14
+ const CONTAINER_TYPES = ['dashboard-div', 'dashboard-tabs']
15
+
16
+ // 移动端默认列宽:图表整行、数字类半宽,其它组件按 PC 宽度占比定档
17
+ function mobileSpan (item, colNum, halfTypes) {
18
+ if (item.contentType === 'chart' || item.contentType === 'report-chart') return colNum
19
+ const half = Math.floor(colNum / 2)
20
+ if (halfTypes.includes(item.contentType)) return half
21
+ const w = Math.max(1, Number(item.w) || 1)
22
+ const ratio = w / colNum
23
+ if (ratio <= 1 / 3) return Math.floor(colNum / 3)
24
+ if (ratio <= 1 / 2) return half
25
+ return colNum
26
+ }
27
+
28
+ // 展开容器:mobileExpand !== false(默认拆开)时把子项提出来一起堆叠——标签页取所有页签的子项、容器取 items;
29
+ // mobileExpand === false 则容器整块保留(当一个组件整行)。只展开一层,嵌套容器按整块处理
30
+ function expandForMobile (items) {
31
+ const out = []
32
+ for (const item of items || []) {
33
+ if (!item) continue
34
+ if (CONTAINER_TYPES.includes(item.contentType) && item.mobileExpand !== false) {
35
+ // 子项 x/y 是容器内相对坐标,与外层项的绝对坐标不同坐标系:
36
+ // 展开时叠加容器偏移生成副本(不改原引用),排序才与 PC 视觉顺序一致,
37
+ // 否则容器内组件(相对 y 普遍偏小)会被整体排到页面最前、与 PC 对不上
38
+ const baseX = Number(item.x) || 0
39
+ const baseY = Number(item.y) || 0
40
+ const children = item.contentType === 'dashboard-tabs'
41
+ ? (item.items || []).reduce((acc, tab) => acc.concat((tab && tab.items) || []), [])
42
+ : (item.items || [])
43
+ for (const c of children) {
44
+ if (!c) continue
45
+ out.push({ ...c, x: (Number(c.x) || 0) + baseX, y: (Number(c.y) || 0) + baseY })
46
+ }
47
+ } else {
48
+ out.push(item)
49
+ }
50
+ }
51
+ return out
52
+ }
53
+
54
+ // 按 PC 视觉顺序(上→下、左→右)排序
55
+ const byYX = (a, b) => ((Number(a.y) || 0) - (Number(b.y) || 0)) || ((Number(a.x) || 0) - (Number(b.x) || 0))
56
+
57
+ // 贪心堆叠:spanOf 定每项列宽(夹在 [1, colNum]),整行组件独占一行、其余并排放满即换行;每项高度不足 minH 兜底。
58
+ // 返回新对象(带算好的 x/y/w/h),供「按 PC 生成」按占比定宽、「排序快捷键」按各自当前宽重排两处复用
59
+ function packStack (list, colNum, spanOf, minH) {
60
+ const result = []
61
+ let y = 0
62
+ let curX = 0 // 当前行已用宽度
63
+ let rowH = 0 // 当前行最大高度
64
+ // 收尾当前行:把 y 推到行底、重置行状态
65
+ const closeRow = () => {
66
+ if (curX > 0) { y += rowH; curX = 0; rowH = 0 }
67
+ }
68
+ for (const item of list) {
69
+ const h = Math.max(Number(item.h) || minH, minH)
70
+ const span = Math.max(1, Math.min(colNum, spanOf(item)))
71
+ if (span >= colNum) {
72
+ // 整行组件:先收尾当前多列行,再独占一整行
73
+ closeRow()
74
+ result.push({ ...item, x: 0, y, w: colNum, h })
75
+ y += h
76
+ } else {
77
+ // 半宽 / 三分之一:当前行放不下就换行,再并排放入
78
+ if (curX + span > colNum) closeRow()
79
+ result.push({ ...item, x: curX, y, w: span, h })
80
+ curX += span
81
+ rowH = Math.max(rowH, h)
82
+ }
83
+ }
84
+ closeRow()
85
+ return result
86
+ }
87
+
88
+ /**
89
+ * PC 布局 → 移动端单列堆叠:容器按 mobileExpand 展开/保留 → 按 y、x 排序(保留 PC 上的相邻关系)→
90
+ * 图表整行、数字类半宽,其它组件按 PC 宽度占比定档 → 贪心填充成多列,放不下换行。
91
+ * 不复制 PC 的 style:mItems 项作为移动端样式覆盖层,未覆盖的键运行时沿用 PC(见 Dashboard.compStyle)。
92
+ * @param {Array} items 顶层组件数组,每项含 contentType 及 x/y/w/h(容器含 items)
93
+ * @param {number} colNum 移动端列数(与运行态移动端网格一致,默认 48)
94
+ * @param {Object} options { minH 最小行高, halfTypes 固定半宽的类型, keepStyle 保留各项 style(容器内部堆叠用:容器里没有「移动端样式覆盖层」,子项样式直接沿用) }
95
+ * @returns {Array} 移动端布局项(x/y/w/h 已按堆叠算好;默认不含 style)
96
+ */
97
+ export function stackMobileLayout (items = [], colNum = 48, options = {}) {
98
+ const { minH = 3, halfTypes = DEFAULT_HALF_TYPES, keepStyle = false } = options
99
+ const stripStyle = ({ style, ...rest }) => {
100
+ // 保留容器的子项也不复制 PC 样式,否则以后 PC 改色会被旧快照覆盖。
101
+ if (Array.isArray(rest.items)) rest.items = rest.items.map(stripStyle)
102
+ return rest
103
+ }
104
+ const flat = expandForMobile(items)
105
+ .map(keepStyle ? item => item : stripStyle)
106
+ .sort(byYX)
107
+ // 经 expandForMobile 后仍在列表里的容器都是「整块保留」的:无条件独占整行,不按 PC 宽度占比定档
108
+ // (PC 上一个三分之一宽的小容器,保留后若仍按占比给三分之一档,手机上就挤成一条)
109
+ const spanOf = item => (CONTAINER_TYPES.includes(item.contentType) ? colNum : mobileSpan(item, colNum, halfTypes))
110
+ return packStack(flat, colNum, spanOf, minH)
111
+ }
112
+
113
+ /**
114
+ * 移动端设计器「排序快捷键」:把某项在阅读序(y、x)里前移 / 后移 / 置顶 / 置底,再按各项当前列宽重新堆叠。
115
+ * 保留每项已设的列宽 w 与高度 h,只重算 x/y;返回按新序堆叠好的新数组(调用方据 id 把 x/y 写回原项即可,保持引用)。
116
+ * @param {Array} items 参与排序的移动端项(通常是可见项,隐藏项不参与)
117
+ * @param {string} id 要移动的项 id
118
+ * @param {string} dir 'top' | 'up' | 'down' | 'bottom'
119
+ * @param {number} colNum 移动端列数
120
+ * @param {Object} options { minH 最小行高 }
121
+ * @returns {Array} 重排后的项(新对象,x/y 已重算)
122
+ */
123
+ export function reorderStack (items = [], id, dir, colNum = 48, options = {}) {
124
+ const { minH = 3 } = options
125
+ const order = (items || []).filter(Boolean).slice().sort(byYX)
126
+ const i = order.findIndex(it => String(it.id) === String(id))
127
+ if (i < 0) return packStack(order, colNum, item => Math.max(1, Number(item.w) || 1), minH)
128
+ const [moved] = order.splice(i, 1)
129
+ if (dir === 'top') order.unshift(moved)
130
+ else if (dir === 'bottom') order.push(moved)
131
+ else if (dir === 'down') order.splice(Math.min(order.length, i + 1), 0, moved)
132
+ else order.splice(Math.max(0, i - 1), 0, moved) // 默认按 up 处理:与上一项换位
133
+ return packStack(order, colNum, item => Math.max(1, Number(item.w) || 1), minH)
134
+ }
135
+
136
+ /**
137
+ * 网格项字段兜底:grid-layout-plus 校验 layout 每项的 x/y/w/h 必须是 number,
138
+ * 缺一个就整块抛 `VueGridLayout: Layout[0].x must be a number!`,看板整页不渲染。
139
+ * 历史数据(早期看板、手工改过的配置)里这些字段可能缺失或存成了字符串,这里就地补齐。
140
+ * 就地改而不是返回新对象:调用方拿到的是 props 里那份数组的元素,与既有 `item.i = item.id` 写法一致。
141
+ * @param {Object} item 网格项
142
+ * @param {Object} defaults 缺省值,按需覆盖 { x, y, w, h }
143
+ * @returns {Object} 传入的同一个对象
144
+ */
145
+ export function normalizeGridItem (item, defaults = {}) {
146
+ if (!item || typeof item !== 'object') return item
147
+ // i 与 id 重复已不再持久化,运行时用 id 兜底供 grid-layout 使用
148
+ if (item.i == null && item.id != null) item.i = item.id
149
+ const fallback = { x: 0, y: 0, w: 1, h: 1, ...defaults }
150
+ for (const key of ['x', 'y', 'w', 'h']) {
151
+ const raw = item[key]
152
+ // 字符串数字('3')也要转成数字:grid-layout 校验的是 typeof === 'number',不是能不能转
153
+ const num = (raw == null || raw === '') ? NaN : Number(raw)
154
+ item[key] = Number.isFinite(num) ? num : fallback[key]
155
+ }
156
+ return item
157
+ }
@@ -0,0 +1,50 @@
1
+ import { stackMobileLayout } from './layout-util'
2
+ import { normalizeMobileAppearance } from '@epxing/utils/src/mobile-chart-appearance'
3
+
4
+ export function indexDashboardItems (items = [], result = new Map()) {
5
+ for (const item of items) {
6
+ if (!item) continue
7
+ if (item.id != null) result.set(String(item.id), item)
8
+ if (Array.isArray(item.items)) indexDashboardItems(item.items, result)
9
+ }
10
+ return result
11
+ }
12
+
13
+ // 移动端项仅决定几何、隐藏及外观;报表引用和业务配置始终取当前 PC 项。
14
+ export function resolveMobileItem (item, pcIndex) {
15
+ const pc = pcIndex.get(String(item.id)) || item
16
+ const styleOverride = Object.fromEntries(Object.entries(item.style || {}).filter(([, value]) => value !== '' && value !== undefined && value !== null))
17
+ const resolved = {
18
+ ...pc,
19
+ x: item.x,
20
+ y: item.y,
21
+ w: item.w,
22
+ h: item.h,
23
+ i: item.i || item.id,
24
+ mobileHidden: item.mobileHidden,
25
+ mobileAppearance: normalizeMobileAppearance(item.mobileAppearance),
26
+ style: { ...(pc.style || {}), ...styleOverride }
27
+ }
28
+ if (Array.isArray(pc.items)) {
29
+ const saved = indexDashboardItems(item.items || [])
30
+ resolved.items = pc.items.map(child => resolveMobileItem(saved.get(String(child.id)) || child, pcIndex))
31
+ }
32
+ return resolved
33
+ }
34
+
35
+ // 重置几何时按组件身份保留移动端样式、隐藏及图表覆盖,也支持容器拆开/合并。
36
+ export function regenerateMobileDesign (pcItems, mobileItems, colNum = 48) {
37
+ const saved = indexDashboardItems(mobileItems)
38
+ const preserve = item => {
39
+ const old = saved.get(String(item.id))
40
+ const next = { ...item }
41
+ if (old) {
42
+ for (const key of ['style', 'mobileHidden', 'mobileAppearance']) {
43
+ if (old[key] !== undefined) next[key] = key === 'mobileAppearance' ? normalizeMobileAppearance(old[key]) : old[key]
44
+ }
45
+ }
46
+ if (Array.isArray(item.items)) next.items = item.items.map(preserve)
47
+ return next
48
+ }
49
+ return stackMobileLayout(pcItems, colNum).map(preserve)
50
+ }
@@ -0,0 +1,185 @@
1
+ // 仪表盘主题样式构建工具
2
+ // 将 FM_CHART_THEME.CONTENT / $configs.themeStyles 的 JSON 配置编译为注入页面的 CSS 文本,
3
+ // 同时生成图表组件的 ECharts 公共配置。
4
+ // 配置结构与字段说明见 docs/dashboard-theme-config.md
5
+
6
+ const kebabCase = key => key.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
7
+ const camelCase = key => key.replace(/-(\w)/g, (s, c) => c.toUpperCase())
8
+
9
+ // 装饰条(伪元素)默认定位,topAccent 配置里的同名键可覆盖
10
+ const ACCENT_BASE = {
11
+ content: "''",
12
+ position: 'absolute',
13
+ top: '0',
14
+ left: '0',
15
+ right: '0',
16
+ height: '3px',
17
+ 'pointer-events': 'none'
18
+ }
19
+
20
+ /**
21
+ * 将样式组对象序列化为 CSS 声明文本
22
+ * - 键自动转 kebab-case(borderRadius 与 border-radius 均可)
23
+ * - 空值(''/null/undefined)跳过,避免产生 url(undefined) 等垃圾声明
24
+ * - background-image 视为文件路径,经 getFileUrl 包装为 url(...);渐变请写在 background 键
25
+ */
26
+ export function formatStyleToCss (style, getFileUrl) {
27
+ if (!style) return ''
28
+ return Object.keys(style)
29
+ .filter(key => style[key] !== '' && style[key] !== null && style[key] !== undefined)
30
+ .map(key => {
31
+ const cssKey = kebabCase(key)
32
+ if (cssKey === 'background-image') {
33
+ return `${cssKey}: url(${getFileUrl(style[key])});`
34
+ }
35
+ return `${cssKey}: ${style[key]};`
36
+ })
37
+ .join('\n')
38
+ }
39
+
40
+ /**
41
+ * 将 hoverStyle 拍平为单层 CSS 声明对象,兼容两种写法:
42
+ * - 样式组结构(面板"悬浮"态产出):{ background:{...}, border:{...}, fontStyle:{...} }
43
+ * - 扁平 CSS(手写):{ transform: '...', 'box-shadow': '...' }
44
+ */
45
+ function flattenStyleGroups (style) {
46
+ if (!style || typeof style !== 'object') return null
47
+ const flat = {}
48
+ for (const key in style) {
49
+ const value = style[key]
50
+ if (value && typeof value === 'object') {
51
+ Object.assign(flat, value) // 样式组:并入其内部 CSS 声明
52
+ } else if (value !== '' && value !== null && value !== undefined) {
53
+ flat[key] = value // 扁平 CSS 声明
54
+ }
55
+ }
56
+ return flat
57
+ }
58
+
59
+ /**
60
+ * 根据主题配置生成完整 CSS 文本
61
+ * @param {Object} themeStyles 主题配置(page/component/num/chart/custom 分区)
62
+ * @param {String} scope 作用域选择器,如 `.epx-dashboard-xxx`
63
+ * @param {Function} getFileUrl 文件路径转 URL
64
+ * @param {Object} options containerSelector - page.background 的目标选择器(运行时为容器,设计器为根节点);
65
+ * hoverAncestor - 悬浮触发祖先(设计器组件被 pointer-events:none,自身 :hover 不触发,传 '.drag-component' 改用祖先触发)
66
+ */
67
+ // 生成 page/component/num 三个样式分区的 CSS 规则数组(base 与 mobile 覆盖层都复用)
68
+ function collectStyleRules (parts, scope, getFileUrl, options = {}) {
69
+ const { containerSelector = '.epx-dashboard__container', hoverAncestor = '' } = options
70
+ const { page = {}, component = {}, num = {} } = parts
71
+ const rules = []
72
+ const addRule = (selector, ...styleGroups) => {
73
+ const css = styleGroups.map(group => formatStyleToCss(group, getFileUrl)).filter(Boolean).join('\n')
74
+ if (css) rules.push(`${selector} {\n${css}\n}`)
75
+ }
76
+ // 顶部装饰条:用 ::before 伪元素绘制,宿主自动加 relative + hidden
77
+ const addAccent = (selector, accent) => {
78
+ if (!accent || !Object.keys(accent).length) return
79
+ addRule(selector, { position: 'relative', overflow: 'hidden' })
80
+ addRule(`${selector}::before`, { ...ACCENT_BASE, ...accent })
81
+ }
82
+ // 悬浮触发选择器:运行时用元素自身 :hover;设计器组件被 pointer-events:none,改用祖先 :hover 触发
83
+ const hoverSel = (elementSel) => hoverAncestor
84
+ ? `${scope} ${hoverAncestor}:hover ${elementSel}`
85
+ : `${scope} ${elementSel}:hover`
86
+ // 悬浮文字:标题/数字带行内样式(:style),普通 CSS 选择器盖不过,用 !important 覆盖。
87
+ // 注意:图表正文为 ECharts canvas 渲染,CSS 无法改色,hover 改不动图表文字。
88
+ const addHoverFont = (elementSel, hoverStyle, selectors) => {
89
+ const font = hoverStyle && hoverStyle.fontStyle
90
+ if (!font || !Object.keys(font).length) return
91
+ const css = Object.keys(font)
92
+ .filter(key => font[key] !== '' && font[key] !== null && font[key] !== undefined)
93
+ .map(key => `${kebabCase(key)}: ${font[key]} !important;`)
94
+ .join('\n')
95
+ const base = hoverSel(elementSel)
96
+ if (css) rules.push(`${selectors.map(s => `${base} ${s}`).join(', ')} {\n${css}\n}`)
97
+ }
98
+
99
+ // 页面
100
+ addRule(`${scope} ${containerSelector}`.trim(), page.background)
101
+ // 图表组件卡片
102
+ addRule(`${scope} .epx-dashboard__component`, component.background, component.border, component.fontStyle, component.shadow)
103
+ addRule(hoverSel('.epx-dashboard__component'), flattenStyleGroups(component.hoverStyle))
104
+ addHoverFont('.epx-dashboard__component', component.hoverStyle, ['.epx-chart__title', '.epx-card-num__title', '.epx-card-num__num font'])
105
+ addAccent(`${scope} .epx-dashboard__component`, component.topAccent)
106
+ addRule(`${scope} .epx-chart__header`, component.titleBackground)
107
+ addRule(`${scope} .epx-chart__title, ${scope} .epx-card-num__title`, component.titleFontStyle)
108
+ // 数字统计卡片
109
+ addRule(`${scope} .epx-card-num`, num.background, num.border, num.shadow)
110
+ addRule(hoverSel('.epx-card-num'), flattenStyleGroups(num.hoverStyle))
111
+ addHoverFont('.epx-card-num', num.hoverStyle, ['.epx-card-num__title', '.epx-card-num__num font'])
112
+ addAccent(`${scope} .epx-card-num`, num.topAccent)
113
+ addRule(`${scope} .epx-card-num__title`, num.titleFontStyle)
114
+ addRule(`${scope} .epx-card-num__num font`, num.fontStyle)
115
+ return rules
116
+ }
117
+
118
+ export function buildThemeCss (themeStyles = {}, scope, getFileUrl, options = {}) {
119
+ const rules = collectStyleRules(themeStyles, scope, getFileUrl, options)
120
+
121
+ // 自定义 CSS 逃生舱:& 占位符替换为当前仪表盘作用域,可写伪元素/hover/nth-child 等任意规则
122
+ const customCss = themeStyles.customCss || (themeStyles.custom && themeStyles.custom.css)
123
+ if (typeof customCss === 'string' && customCss.trim()) {
124
+ rules.push(customCss.replace(/&/g, scope))
125
+ }
126
+
127
+ // 移动端覆盖层:mobile 分区只写要覆盖的键,包进 @media;未覆盖的键靠 CSS 级联继承 PC(断点与 $isMobile 的 992 对齐)
128
+ if (themeStyles.mobile && typeof themeStyles.mobile === 'object') {
129
+ const mobileRules = collectStyleRules(themeStyles.mobile, scope, getFileUrl, options)
130
+ if (mobileRules.length) {
131
+ rules.push(`@media only screen and (max-width: 992px) {\n${mobileRules.join('\n')}\n}`)
132
+ }
133
+ // 设计器预览:同一套移动端规则再以 .is-mobile-preview 类作用域输出,桌面宽画布也能预览(根节点加该类即生效)
134
+ if (options.mobilePreview) {
135
+ rules.push(...collectStyleRules(themeStyles.mobile, `${scope}.is-mobile-preview`, getFileUrl, options))
136
+ }
137
+ }
138
+ return rules.join('\n')
139
+ }
140
+
141
+ /**
142
+ * 由主题配置生成 ECharts 公共配置(图表组件的 themeChartConfig)
143
+ * - component.fontStyle -> 全局文字 / 图例 / 坐标轴标签
144
+ * - chart.colors -> 系列色板(数组或逗号分隔字符串)
145
+ * - chart.splitLineColor / chart.axisLineColor -> 网格分割线 / 轴线颜色
146
+ * @param {Boolean} isMobile 为 true 时按键合并 themeStyles.mobile 的 component.fontStyle / chart.*(图表走 JS,不走 @media)
147
+ */
148
+ export function buildThemeChartConfig (themeStyles = {}, isMobile = false) {
149
+ const baseComponent = themeStyles.component || {}
150
+ const baseChart = themeStyles.chart || {}
151
+ const mobile = (isMobile && themeStyles.mobile) || {}
152
+ // 移动端覆盖:fontStyle 深合并(移动端只写的键覆盖),chart 各项浅合并
153
+ const fontStyle = { ...(baseComponent.fontStyle || {}), ...((mobile.component || {}).fontStyle || {}) }
154
+ const chart = { ...baseChart, ...(mobile.chart || {}) }
155
+ const config = {}
156
+ if (Object.keys(fontStyle).length) {
157
+ const textStyle = {}
158
+ for (const key in fontStyle) {
159
+ textStyle[camelCase(key)] = fontStyle[key]
160
+ }
161
+ Object.assign(config, {
162
+ textStyle,
163
+ legend: { textStyle },
164
+ xAxis: { axisLabel: { ...textStyle } },
165
+ yAxis: { axisLabel: { ...textStyle } }
166
+ })
167
+ }
168
+ const colors = typeof chart.colors === 'string'
169
+ ? chart.colors.split(/[,,\s]+/).filter(Boolean)
170
+ : chart.colors
171
+ if (Array.isArray(colors) && colors.length) {
172
+ config.color = colors
173
+ }
174
+ if (chart.splitLineColor) {
175
+ const splitLine = { lineStyle: { color: chart.splitLineColor } }
176
+ config.xAxis = { ...config.xAxis, splitLine: { ...splitLine } }
177
+ config.yAxis = { ...config.yAxis, splitLine: { ...splitLine } }
178
+ }
179
+ if (chart.axisLineColor) {
180
+ const axisLine = { lineStyle: { color: chart.axisLineColor } }
181
+ config.xAxis = { ...config.xAxis, axisLine: { ...axisLine } }
182
+ config.yAxis = { ...config.yAxis, axisLine: { ...axisLine } }
183
+ }
184
+ return config
185
+ }
package/types/div.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { DashboardDiv as default } from './index'
@@ -0,0 +1,7 @@
1
+ import type { App, DefineComponent, Plugin } from 'vue'
2
+ export declare const Dashboard: DefineComponent<any, any, any> & Plugin
3
+ export declare const DashboardTabs: DefineComponent<any, any, any>
4
+ export declare const DashboardTitle: DefineComponent<any, any, any>
5
+ export declare const DashboardDiv: DefineComponent<any, any, any>
6
+ export declare const DashboardWidget: DefineComponent<any, any, any>
7
+ export default Dashboard
@@ -0,0 +1 @@
1
+ export { DashboardTabs as default } from './index'
@@ -0,0 +1 @@
1
+ export { DashboardTitle as default } from './index'
@@ -0,0 +1 @@
1
+ export { DashboardWidget as default } from './index'