xt-element-ui 1.3.3 → 1.4.0

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 (124) hide show
  1. package/CHANGELOG.md +72 -72
  2. package/LICENSE +21 -21
  3. package/README.md +240 -240
  4. package/docs/README.md +100 -100
  5. package/docs/components/base/xt-button.md +114 -114
  6. package/docs/components/base/xt-card-item.md +104 -104
  7. package/docs/components/base/xt-card.md +108 -108
  8. package/docs/components/base/xt-config-provider.md +199 -199
  9. package/docs/components/base/xt-flex-box.md +115 -115
  10. package/docs/components/base/xt-grid-box.md +302 -302
  11. package/docs/components/base/xt-input.md +150 -150
  12. package/docs/components/base/xt-map-provider.md +23 -23
  13. package/docs/components/base/xt-map.md +331 -331
  14. package/docs/components/base/xt-step-price.md +165 -165
  15. package/docs/components/base/xt-text.md +212 -212
  16. package/docs/components/base/xt-time.md +165 -165
  17. package/docs/components/extend/ex-bar.md +67 -67
  18. package/docs/components/extend/ex-button.md +62 -62
  19. package/docs/components/extend/ex-card.md +86 -86
  20. package/docs/components/extend/ex-chart.md +463 -463
  21. package/docs/components/extend/ex-icon.md +189 -189
  22. package/docs/components/extend/ex-line.md +70 -70
  23. package/docs/components/extend/ex-multi.md +155 -155
  24. package/docs/components/extend/ex-pie.md +69 -69
  25. package/docs/components/extend/ex-select-tree.md +210 -210
  26. package/docs/components/extend/ex-table.md +590 -590
  27. package/docs/components/extend/ex-upload.md +134 -134
  28. package/docs/components/utils/size.md +147 -147
  29. package/docs/components/utils/theme.md +182 -182
  30. package/lib/index.common.js +4575 -4186
  31. package/lib/index.css +1 -1
  32. package/lib/index.umd.js +4475 -4085
  33. package/lib/index.umd.min.js +5 -5
  34. package/package.json +80 -81
  35. package/src/components/ex-button/index.js +7 -7
  36. package/src/components/ex-button/index.vue +66 -66
  37. package/src/components/ex-button/style/index.scss +67 -67
  38. package/src/components/ex-card/index.js +7 -7
  39. package/src/components/ex-card/index.vue +68 -68
  40. package/src/components/ex-card/style/index.scss +73 -73
  41. package/src/components/ex-chart/index.js +7 -7
  42. package/src/components/ex-chart/index.vue +136 -136
  43. package/src/components/ex-chart/theme/dark.js +4 -1
  44. package/src/components/ex-date-picker/index.js +7 -7
  45. package/src/components/ex-icon/index.js +7 -7
  46. package/src/components/ex-icon/index.vue +168 -168
  47. package/src/components/ex-icon/style/index.scss +7 -7
  48. package/src/components/ex-page/index.js +7 -7
  49. package/src/components/ex-select-tree/index.js +7 -7
  50. package/src/components/ex-table/ExTableCell.vue +22 -22
  51. package/src/components/ex-table/index.js +7 -7
  52. package/src/components/ex-table/virtualScrollData.js +35 -35
  53. package/src/components/ex-upload/index.js +7 -7
  54. package/src/components/index.scss +49 -40
  55. package/src/components/xt-badge/index.js +7 -0
  56. package/src/components/xt-badge/index.vue +71 -0
  57. package/src/components/xt-badge/style/index.scss +88 -0
  58. package/src/components/xt-button/index.js +7 -7
  59. package/src/components/xt-button/index.vue +97 -97
  60. package/src/components/xt-card/index.js +7 -7
  61. package/src/components/xt-card/style/index.scss +48 -48
  62. package/src/components/xt-card-item/index.js +7 -7
  63. package/src/components/xt-card-item/index.vue +5 -5
  64. package/src/components/xt-card-item/style/index copy.scss +71 -71
  65. package/src/components/xt-card-item/style/index.scss +82 -82
  66. package/src/components/xt-config-provider/index.js +7 -7
  67. package/src/components/xt-config-provider/index.vue +362 -362
  68. package/src/components/xt-config-provider/style/index.scss +11 -11
  69. package/src/components/xt-flex-box/index.js +7 -7
  70. package/src/components/xt-flex-box/style/index.scss +81 -81
  71. package/src/components/xt-grid-box/index.js +7 -7
  72. package/src/components/xt-grid-box/style/index.scss +92 -92
  73. package/src/components/xt-grid-item/index.js +7 -7
  74. package/src/components/xt-grid-item/index.vue +106 -106
  75. package/src/components/xt-input/index.js +7 -7
  76. package/src/components/xt-input/index.vue +66 -66
  77. package/src/components/xt-input/style/index.scss +84 -84
  78. package/src/components/xt-map/adapters/amap.js +235 -235
  79. package/src/components/xt-map/adapters/baidu.js +254 -254
  80. package/src/components/xt-map/adapters/base.js +267 -267
  81. package/src/components/xt-map/adapters/index.js +29 -29
  82. package/src/components/xt-map/adapters/tianditu.js +242 -242
  83. package/src/components/xt-map/config/xt-map-config.js +197 -197
  84. package/src/components/xt-map/index.js +8 -8
  85. package/src/components/xt-map/index.vue +351 -351
  86. package/src/components/xt-map/loaders/script-loader.js +114 -114
  87. package/src/components/xt-map/provider.vue +200 -200
  88. package/src/components/xt-map/style/index.scss +77 -77
  89. package/src/components/xt-map-provider/index.js +8 -8
  90. package/src/components/xt-progress/index.js +7 -0
  91. package/src/components/xt-progress/index.vue +103 -0
  92. package/src/components/xt-progress/style/index.scss +119 -0
  93. package/src/components/xt-step-price/index.js +10 -10
  94. package/src/components/xt-step-price/index.vue +272 -272
  95. package/src/components/xt-step-price/style/index.scss +123 -123
  96. package/src/components/xt-step-price-item/index.js +7 -7
  97. package/src/components/xt-step-price-item/index.vue +187 -187
  98. package/src/components/xt-tabs/TabPane.vue +36 -0
  99. package/src/components/xt-tabs/index.js +10 -0
  100. package/src/components/xt-tabs/index.vue +113 -0
  101. package/src/components/xt-tabs/style/index.scss +197 -0
  102. package/src/components/xt-text/index.js +7 -7
  103. package/src/components/xt-text/index.vue +16 -2
  104. package/src/components/xt-text/style/index.scss +110 -92
  105. package/src/components/xt-time/index.js +7 -7
  106. package/src/components/xt-time/index.vue +313 -313
  107. package/src/components/xt-time/style/index.scss +23 -23
  108. package/src/config/element-registry.js +135 -135
  109. package/src/index.js +169 -178
  110. package/src/styles/css-variables.scss +257 -257
  111. package/src/styles/theme/background.scss +5 -5
  112. package/src/styles/theme/border-radius.scss +3 -3
  113. package/src/styles/theme/borders.scss +3 -3
  114. package/src/styles/theme/colors.scss +101 -101
  115. package/src/styles/theme/component-variables.scss +69 -69
  116. package/src/styles/theme/dark-variables.scss +28 -28
  117. package/src/styles/theme/font.scss +12 -12
  118. package/src/styles/theme/index.scss +11 -11
  119. package/src/styles/theme/shadows.scss +2 -2
  120. package/src/styles/theme/spacing.scss +4 -4
  121. package/src/styles/theme/transitions.scss +2 -2
  122. package/src/styles/theme/typography.scss +4 -4
  123. package/src/styles/variables-export.scss +85 -85
  124. package/src/utils/index.js +191 -202
@@ -1,362 +1,362 @@
1
- <script>
2
- import EchartsUtil from '../ex-chart/utils.js'
3
- export default {
4
- name: 'XtConfigProvider',
5
- inheritAttrs: false,
6
- provide() {
7
- return {
8
- xtConfig: {
9
- theme: this.theme,
10
- size: this.size,
11
- primaryColor: this.primaryColor
12
- }
13
- }
14
- },
15
- watch: {
16
- theme(newVal) {
17
- this.xtConfig ={
18
- theme: newVal,
19
- size: this.size,
20
- primaryColor: this.primaryColor
21
- }
22
- },
23
- size(newVal) {
24
- this.xtConfig ={
25
- theme: newVal,
26
- size: this.size,
27
- primaryColor: this.primaryColor
28
- }
29
- },
30
- primaryColor(newVal) {
31
- this.xtConfig ={
32
- theme: newVal,
33
- size: this.size,
34
- primaryColor: this.primaryColor
35
- }
36
- }
37
- },
38
- render(h) {
39
- // Vue 2 不支持 Fragment,当 tag="template" 时需要特殊处理
40
- // 如果设置了 proxyElement,则不渲染包裹元素,只渲染 slot 内容
41
- if (this.tag === 'template' || this.proxyElement) {
42
- // 渲染 slot 内容,如果只有一个元素则直接返回,否则包裹一个 div
43
- const slotContent = this.$slots.default
44
-
45
- if (!slotContent || slotContent.length === 0) {
46
- return h('div')
47
- }
48
-
49
- // 如果 slot 只有一个元素,直接返回该元素
50
- if (slotContent.length === 1) {
51
- return slotContent[0]
52
- }
53
-
54
- // Vue 2 不支持多根节点,需要包裹一个 div
55
- // 使用普通的 div 包裹,避免 display: contents 的兼容性问题
56
- return h('div', {
57
- class: 'xt-config-provider-wrapper',
58
- attrs: {
59
- 'data-theme': this.theme
60
- }
61
- }, slotContent)
62
- }
63
-
64
- // 正常渲染包裹元素
65
- return h(this.tag, {
66
- style: this.mergedStyle,
67
- class: this.computedClass,
68
- attrs: {
69
- ...this.customAttrs,
70
- 'data-theme': this.theme
71
- }
72
- }, this.$slots.default)
73
- },
74
- props: {
75
- theme: {
76
- type: String,
77
- default: 'white',
78
- validator: (value) => {
79
- return ['white', 'dark', 'auto'].includes(value)
80
- }
81
- },
82
- size: {
83
- type: String,
84
- default: 'medium'
85
- },
86
- primaryColor: {
87
- type: String,
88
- default: '#1890ff'
89
- },
90
- vars: {
91
- type: Object,
92
- default: () => ({})
93
- },
94
- tag: {
95
- type: String,
96
- default: 'div',
97
- validator: (value) => {
98
- return ['div', 'span', 'section', 'main', 'template', 'article', 'aside'].includes(value)
99
- }
100
- },
101
- injectBackground: {
102
- type: Boolean,
103
- default: false
104
- },
105
- injectColor: {
106
- type: Boolean,
107
- default: false
108
- },
109
- proxyElement: {
110
- type: [HTMLElement, String, Object],
111
- default: null,
112
- description: '代理元素,将样式应用到该元素上。支持 HTMLElement、CSS选择器字符串或 ref 对象'
113
- },
114
- onThemeChange: {
115
- type: Function,
116
- default: null,
117
- description: '主题改变时的钩子函数,接收参数: { theme, size, primaryColor }'
118
- },
119
- onStyleApplied: {
120
- type: Function,
121
- default: null,
122
- description: '样式应用完成时的钩子函数,接收参数: { element, style, theme }'
123
- }
124
- },
125
- computed: {
126
- mergedStyle() {
127
- const result = { ...this.vars }
128
-
129
- if (this.primaryColor) {
130
- const color = this.normalizeColor(this.primaryColor)
131
- result['--xt-color-primary'] = color
132
- // 浅色系列(与 css-variables.scss 保持一致)
133
- result['--xt-color-primary-light-3'] = this.lightenColor(color, 30)
134
- result['--xt-color-primary-light-5'] = this.lightenColor(color, 50)
135
- result['--xt-color-primary-light-7'] = this.lightenColor(color, 70)
136
- result['--xt-color-primary-light-8'] = this.lightenColor(color, 80)
137
- result['--xt-color-primary-light-9'] = this.lightenColor(color, 90)
138
- // 深色系列
139
- result['--xt-color-primary-dark-2'] = this.darkenColor(color, 20)
140
- }
141
-
142
- const sizeMap = {
143
- small: '14px',
144
- medium: '16px',
145
- large: '18px'
146
- }
147
- if (sizeMap[this.size]) {
148
- result['--xt-font-size-base'] = sizeMap[this.size]
149
- }
150
-
151
- if (this.theme === 'dark') {
152
- if (this.injectBackground) {
153
- result.backgroundColor = result['--xt-color-bg-primary'] || '#141414'
154
- }
155
- if (this.injectColor) {
156
- result.color = result['--xt-color-text-primary'] || '#E5EAF3'
157
- }
158
- } else {
159
- // 恢复默认主题颜色(light 主题)
160
- if (this.injectBackground) {
161
- result.backgroundColor = result['--xt-color-bg-primary'] || '#ffffff'
162
- }
163
- if (this.injectColor) {
164
- result.color = result['--xt-color-text-primary'] || '#2c3e50'
165
- }
166
- }
167
-
168
- return result
169
- },
170
- computedClass() {
171
- const classes = []
172
-
173
- if (this.tag !== 'template') {
174
- classes.push('xt-config-provider')
175
- }
176
-
177
- return classes
178
- },
179
- customAttrs() {
180
- const props = ['theme', 'size', 'primaryColor', 'vars', 'tag', 'injectBackground', 'injectColor', 'proxyElement']
181
- const attrs = {}
182
-
183
- for (const key in this.$attrs) {
184
- if (!props.includes(key)) {
185
- attrs[key] = this.$attrs[key]
186
- }
187
- }
188
-
189
- return attrs
190
- }
191
- },
192
- mounted() {
193
- this.applyProxyElementStyle()
194
- },
195
- updated() {
196
- this.applyProxyElementStyle()
197
- },
198
- beforeUnmount() {
199
- this.clearProxyElementStyle()
200
- },
201
- methods: {
202
- normalizeColor(color) {
203
- if (!color) return '#1890ff'
204
-
205
- if (/^#[0-9A-Fa-f]{6}$/.test(color)) {
206
- return color
207
- }
208
-
209
- if (/^#[0-9A-Fa-f]{3}$/.test(color)) {
210
- return '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]
211
- }
212
-
213
- if (/^#[0-9A-Fa-f]{8}$/.test(color)) {
214
- return color.substring(0, 7)
215
- }
216
-
217
- const rgbaMatch = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i)
218
- if (rgbaMatch) {
219
- const r = parseInt(rgbaMatch[1])
220
- const g = parseInt(rgbaMatch[2])
221
- const b = parseInt(rgbaMatch[3])
222
- return this.rgbToHex(r, g, b)
223
- }
224
-
225
- console.warn('[XtConfigProvider] 无法识别的颜色格式:', color)
226
- return '#1890ff'
227
- },
228
-
229
- hexToRgb(hex) {
230
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
231
- return result ? {
232
- r: parseInt(result[1], 16),
233
- g: parseInt(result[2], 16),
234
- b: parseInt(result[3], 16)
235
- } : null
236
- },
237
-
238
- rgbToHex(r, g, b) {
239
- return '#' + [r, g, b].map(x => {
240
- const hex = x.toString(16)
241
- return hex.length === 1 ? '0' + hex : hex
242
- }).join('')
243
- },
244
-
245
- lightenColor(hex, percent) {
246
- const rgb = this.hexToRgb(hex)
247
- if (!rgb) return hex
248
-
249
- // 使用与 Element Plus 一致的算法:将颜色与白色按比例混合
250
- // percent 表示混合白色的比例(0-100),即 (1 - percent/100) 是原色比例
251
- const ratio = percent / 100
252
- const r = Math.round(rgb.r * (1 - ratio) + 255 * ratio)
253
- const g = Math.round(rgb.g * (1 - ratio) + 255 * ratio)
254
- const b = Math.round(rgb.b * (1 - ratio) + 255 * ratio)
255
-
256
- return this.rgbToHex(r, g, b)
257
- },
258
-
259
- darkenColor(hex, percent) {
260
- const rgb = this.hexToRgb(hex)
261
- if (!rgb) return hex
262
-
263
- // 使用按比例混合黑色的方式变暗
264
- // percent 表示混合黑色的比例(0-100)
265
- const ratio = percent / 100
266
- const r = Math.max(0, Math.round(rgb.r * (1 - ratio)))
267
- const g = Math.max(0, Math.round(rgb.g * (1 - ratio)))
268
- const b = Math.max(0, Math.round(rgb.b * (1 - ratio)))
269
-
270
- return this.rgbToHex(r, g, b)
271
- },
272
-
273
- getProxyElement() {
274
- const { proxyElement } = this
275
-
276
- if (!proxyElement) return null
277
-
278
- if (proxyElement instanceof HTMLElement) {
279
- return proxyElement
280
- }
281
-
282
- if (typeof proxyElement === 'string') {
283
- return document.querySelector(proxyElement)
284
- }
285
-
286
- if (proxyElement.$el) {
287
- return proxyElement.$el
288
- }
289
-
290
- if (proxyElement.value && proxyElement.value instanceof HTMLElement) {
291
- return proxyElement.value
292
- }
293
-
294
- console.warn('[XtConfigProvider] 无法解析 proxyElement:', proxyElement)
295
- return null
296
- },
297
-
298
- applyProxyElementStyle() {
299
- const element = this.getProxyElement()
300
- if (!element) return
301
-
302
- const style = this.mergedStyle
303
- const prevTheme = element.getAttribute('data-theme')
304
-
305
- // 应用样式到代理元素
306
- for (const key in style) {
307
- element.style.setProperty(key, style[key])
308
- }
309
-
310
- // 设置 data-theme 属性
311
- element.setAttribute('data-theme', this.theme)
312
-
313
- // 更新图表颜色
314
- EchartsUtil.changeAllTheme(this.theme, this.size, this.primaryColor)
315
-
316
- // 触发主题改变钩子(当主题实际发生变化时)
317
- if (prevTheme !== this.theme && typeof this.onThemeChange === 'function') {
318
- this.onThemeChange({
319
- theme: this.theme,
320
- size: this.size,
321
- primaryColor: this.primaryColor,
322
- prevTheme: prevTheme
323
- })
324
- }
325
-
326
- // 触发样式应用完成钩子
327
- if (typeof this.onStyleApplied === 'function') {
328
- this.onStyleApplied({
329
- element: element,
330
- style: style,
331
- theme: this.theme,
332
- size: this.size,
333
- primaryColor: this.primaryColor
334
- })
335
- }
336
-
337
- },
338
-
339
- clearProxyElementStyle() {
340
- const element = this.getProxyElement()
341
- if (!element) return
342
-
343
- const style = this.mergedStyle
344
-
345
- for (const key in style) {
346
- element.style.removeProperty(key)
347
- }
348
-
349
- // 移除 data-theme 属性
350
- element.removeAttribute('data-theme')
351
- }
352
- }
353
- }
354
- </script>
355
-
356
- <style scoped>
357
- /* 包裹容器样式 - 最小化影响布局 */
358
- .xt-config-provider-wrapper {
359
- width: 100%;
360
- height: 100%;
361
- }
362
- </style>
1
+ <script>
2
+ import EchartsUtil from '../ex-chart/utils.js'
3
+ export default {
4
+ name: 'XtConfigProvider',
5
+ inheritAttrs: false,
6
+ provide() {
7
+ return {
8
+ xtConfig: {
9
+ theme: this.theme,
10
+ size: this.size,
11
+ primaryColor: this.primaryColor
12
+ }
13
+ }
14
+ },
15
+ watch: {
16
+ theme(newVal) {
17
+ this.xtConfig ={
18
+ theme: newVal,
19
+ size: this.size,
20
+ primaryColor: this.primaryColor
21
+ }
22
+ },
23
+ size(newVal) {
24
+ this.xtConfig ={
25
+ theme: newVal,
26
+ size: this.size,
27
+ primaryColor: this.primaryColor
28
+ }
29
+ },
30
+ primaryColor(newVal) {
31
+ this.xtConfig ={
32
+ theme: newVal,
33
+ size: this.size,
34
+ primaryColor: this.primaryColor
35
+ }
36
+ }
37
+ },
38
+ render(h) {
39
+ // Vue 2 不支持 Fragment,当 tag="template" 时需要特殊处理
40
+ // 如果设置了 proxyElement,则不渲染包裹元素,只渲染 slot 内容
41
+ if (this.tag === 'template' || this.proxyElement) {
42
+ // 渲染 slot 内容,如果只有一个元素则直接返回,否则包裹一个 div
43
+ const slotContent = this.$slots.default
44
+
45
+ if (!slotContent || slotContent.length === 0) {
46
+ return h('div')
47
+ }
48
+
49
+ // 如果 slot 只有一个元素,直接返回该元素
50
+ if (slotContent.length === 1) {
51
+ return slotContent[0]
52
+ }
53
+
54
+ // Vue 2 不支持多根节点,需要包裹一个 div
55
+ // 使用普通的 div 包裹,避免 display: contents 的兼容性问题
56
+ return h('div', {
57
+ class: 'xt-config-provider-wrapper',
58
+ attrs: {
59
+ 'data-theme': this.theme
60
+ }
61
+ }, slotContent)
62
+ }
63
+
64
+ // 正常渲染包裹元素
65
+ return h(this.tag, {
66
+ style: this.mergedStyle,
67
+ class: this.computedClass,
68
+ attrs: {
69
+ ...this.customAttrs,
70
+ 'data-theme': this.theme
71
+ }
72
+ }, this.$slots.default)
73
+ },
74
+ props: {
75
+ theme: {
76
+ type: String,
77
+ default: 'white',
78
+ validator: (value) => {
79
+ return ['white', 'dark', 'auto'].includes(value)
80
+ }
81
+ },
82
+ size: {
83
+ type: String,
84
+ default: 'medium'
85
+ },
86
+ primaryColor: {
87
+ type: String,
88
+ default: '#1890ff'
89
+ },
90
+ vars: {
91
+ type: Object,
92
+ default: () => ({})
93
+ },
94
+ tag: {
95
+ type: String,
96
+ default: 'div',
97
+ validator: (value) => {
98
+ return ['div', 'span', 'section', 'main', 'template', 'article', 'aside'].includes(value)
99
+ }
100
+ },
101
+ injectBackground: {
102
+ type: Boolean,
103
+ default: false
104
+ },
105
+ injectColor: {
106
+ type: Boolean,
107
+ default: false
108
+ },
109
+ proxyElement: {
110
+ type: [HTMLElement, String, Object],
111
+ default: null,
112
+ description: '代理元素,将样式应用到该元素上。支持 HTMLElement、CSS选择器字符串或 ref 对象'
113
+ },
114
+ onThemeChange: {
115
+ type: Function,
116
+ default: null,
117
+ description: '主题改变时的钩子函数,接收参数: { theme, size, primaryColor }'
118
+ },
119
+ onStyleApplied: {
120
+ type: Function,
121
+ default: null,
122
+ description: '样式应用完成时的钩子函数,接收参数: { element, style, theme }'
123
+ }
124
+ },
125
+ computed: {
126
+ mergedStyle() {
127
+ const result = { ...this.vars }
128
+
129
+ if (this.primaryColor) {
130
+ const color = this.normalizeColor(this.primaryColor)
131
+ result['--xt-color-primary'] = color
132
+ // 浅色系列(与 css-variables.scss 保持一致)
133
+ result['--xt-color-primary-light-3'] = this.lightenColor(color, 30)
134
+ result['--xt-color-primary-light-5'] = this.lightenColor(color, 50)
135
+ result['--xt-color-primary-light-7'] = this.lightenColor(color, 70)
136
+ result['--xt-color-primary-light-8'] = this.lightenColor(color, 80)
137
+ result['--xt-color-primary-light-9'] = this.lightenColor(color, 90)
138
+ // 深色系列
139
+ result['--xt-color-primary-dark-2'] = this.darkenColor(color, 20)
140
+ }
141
+
142
+ const sizeMap = {
143
+ small: '14px',
144
+ medium: '16px',
145
+ large: '18px'
146
+ }
147
+ if (sizeMap[this.size]) {
148
+ result['--xt-font-size-base'] = sizeMap[this.size]
149
+ }
150
+
151
+ if (this.theme === 'dark') {
152
+ if (this.injectBackground) {
153
+ result.backgroundColor = result['--xt-color-bg-primary'] || '#141414'
154
+ }
155
+ if (this.injectColor) {
156
+ result.color = result['--xt-color-text-primary'] || '#E5EAF3'
157
+ }
158
+ } else {
159
+ // 恢复默认主题颜色(light 主题)
160
+ if (this.injectBackground) {
161
+ result.backgroundColor = result['--xt-color-bg-primary'] || '#ffffff'
162
+ }
163
+ if (this.injectColor) {
164
+ result.color = result['--xt-color-text-primary'] || '#2c3e50'
165
+ }
166
+ }
167
+
168
+ return result
169
+ },
170
+ computedClass() {
171
+ const classes = []
172
+
173
+ if (this.tag !== 'template') {
174
+ classes.push('xt-config-provider')
175
+ }
176
+
177
+ return classes
178
+ },
179
+ customAttrs() {
180
+ const props = ['theme', 'size', 'primaryColor', 'vars', 'tag', 'injectBackground', 'injectColor', 'proxyElement']
181
+ const attrs = {}
182
+
183
+ for (const key in this.$attrs) {
184
+ if (!props.includes(key)) {
185
+ attrs[key] = this.$attrs[key]
186
+ }
187
+ }
188
+
189
+ return attrs
190
+ }
191
+ },
192
+ mounted() {
193
+ this.applyProxyElementStyle()
194
+ },
195
+ updated() {
196
+ this.applyProxyElementStyle()
197
+ },
198
+ beforeUnmount() {
199
+ this.clearProxyElementStyle()
200
+ },
201
+ methods: {
202
+ normalizeColor(color) {
203
+ if (!color) return '#1890ff'
204
+
205
+ if (/^#[0-9A-Fa-f]{6}$/.test(color)) {
206
+ return color
207
+ }
208
+
209
+ if (/^#[0-9A-Fa-f]{3}$/.test(color)) {
210
+ return '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]
211
+ }
212
+
213
+ if (/^#[0-9A-Fa-f]{8}$/.test(color)) {
214
+ return color.substring(0, 7)
215
+ }
216
+
217
+ const rgbaMatch = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i)
218
+ if (rgbaMatch) {
219
+ const r = parseInt(rgbaMatch[1])
220
+ const g = parseInt(rgbaMatch[2])
221
+ const b = parseInt(rgbaMatch[3])
222
+ return this.rgbToHex(r, g, b)
223
+ }
224
+
225
+ console.warn('[XtConfigProvider] 无法识别的颜色格式:', color)
226
+ return '#1890ff'
227
+ },
228
+
229
+ hexToRgb(hex) {
230
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
231
+ return result ? {
232
+ r: parseInt(result[1], 16),
233
+ g: parseInt(result[2], 16),
234
+ b: parseInt(result[3], 16)
235
+ } : null
236
+ },
237
+
238
+ rgbToHex(r, g, b) {
239
+ return '#' + [r, g, b].map(x => {
240
+ const hex = x.toString(16)
241
+ return hex.length === 1 ? '0' + hex : hex
242
+ }).join('')
243
+ },
244
+
245
+ lightenColor(hex, percent) {
246
+ const rgb = this.hexToRgb(hex)
247
+ if (!rgb) return hex
248
+
249
+ // 使用与 Element Plus 一致的算法:将颜色与白色按比例混合
250
+ // percent 表示混合白色的比例(0-100),即 (1 - percent/100) 是原色比例
251
+ const ratio = percent / 100
252
+ const r = Math.round(rgb.r * (1 - ratio) + 255 * ratio)
253
+ const g = Math.round(rgb.g * (1 - ratio) + 255 * ratio)
254
+ const b = Math.round(rgb.b * (1 - ratio) + 255 * ratio)
255
+
256
+ return this.rgbToHex(r, g, b)
257
+ },
258
+
259
+ darkenColor(hex, percent) {
260
+ const rgb = this.hexToRgb(hex)
261
+ if (!rgb) return hex
262
+
263
+ // 使用按比例混合黑色的方式变暗
264
+ // percent 表示混合黑色的比例(0-100)
265
+ const ratio = percent / 100
266
+ const r = Math.max(0, Math.round(rgb.r * (1 - ratio)))
267
+ const g = Math.max(0, Math.round(rgb.g * (1 - ratio)))
268
+ const b = Math.max(0, Math.round(rgb.b * (1 - ratio)))
269
+
270
+ return this.rgbToHex(r, g, b)
271
+ },
272
+
273
+ getProxyElement() {
274
+ const { proxyElement } = this
275
+
276
+ if (!proxyElement) return null
277
+
278
+ if (proxyElement instanceof HTMLElement) {
279
+ return proxyElement
280
+ }
281
+
282
+ if (typeof proxyElement === 'string') {
283
+ return document.querySelector(proxyElement)
284
+ }
285
+
286
+ if (proxyElement.$el) {
287
+ return proxyElement.$el
288
+ }
289
+
290
+ if (proxyElement.value && proxyElement.value instanceof HTMLElement) {
291
+ return proxyElement.value
292
+ }
293
+
294
+ console.warn('[XtConfigProvider] 无法解析 proxyElement:', proxyElement)
295
+ return null
296
+ },
297
+
298
+ applyProxyElementStyle() {
299
+ const element = this.getProxyElement()
300
+ if (!element) return
301
+
302
+ const style = this.mergedStyle
303
+ const prevTheme = element.getAttribute('data-theme')
304
+
305
+ // 应用样式到代理元素
306
+ for (const key in style) {
307
+ element.style.setProperty(key, style[key])
308
+ }
309
+
310
+ // 设置 data-theme 属性
311
+ element.setAttribute('data-theme', this.theme)
312
+
313
+ // 更新图表颜色
314
+ EchartsUtil.changeAllTheme(this.theme, this.size, this.primaryColor)
315
+
316
+ // 触发主题改变钩子(当主题实际发生变化时)
317
+ if (prevTheme !== this.theme && typeof this.onThemeChange === 'function') {
318
+ this.onThemeChange({
319
+ theme: this.theme,
320
+ size: this.size,
321
+ primaryColor: this.primaryColor,
322
+ prevTheme: prevTheme
323
+ })
324
+ }
325
+
326
+ // 触发样式应用完成钩子
327
+ if (typeof this.onStyleApplied === 'function') {
328
+ this.onStyleApplied({
329
+ element: element,
330
+ style: style,
331
+ theme: this.theme,
332
+ size: this.size,
333
+ primaryColor: this.primaryColor
334
+ })
335
+ }
336
+
337
+ },
338
+
339
+ clearProxyElementStyle() {
340
+ const element = this.getProxyElement()
341
+ if (!element) return
342
+
343
+ const style = this.mergedStyle
344
+
345
+ for (const key in style) {
346
+ element.style.removeProperty(key)
347
+ }
348
+
349
+ // 移除 data-theme 属性
350
+ element.removeAttribute('data-theme')
351
+ }
352
+ }
353
+ }
354
+ </script>
355
+
356
+ <style scoped>
357
+ /* 包裹容器样式 - 最小化影响布局 */
358
+ .xt-config-provider-wrapper {
359
+ width: 100%;
360
+ height: 100%;
361
+ }
362
+ </style>