xt-element-ui 2.2.3 → 2.2.4

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 (116) hide show
  1. package/README.md +140 -192
  2. package/docs/README.md +162 -88
  3. package/docs/components/base/theme.md +248 -0
  4. package/docs/components/base/xt-list.md +1 -1
  5. package/docs/components/base/xt-scroll-arrow.md +87 -1
  6. package/docs/components/base/xt-scroll.md +237 -0
  7. package/docs/components/utils/config.md +18 -4
  8. package/docs/components/utils/theme.md +280 -183
  9. package/lib/index.common.1.js +4 -26
  10. package/lib/index.common.2.js +1 -1
  11. package/lib/index.common.js +1871 -671
  12. package/lib/index.css +1 -1
  13. package/lib/index.umd.1.js +4 -26
  14. package/lib/index.umd.2.js +1 -1
  15. package/lib/index.umd.js +1871 -671
  16. package/lib/index.umd.min.1.js +1 -1
  17. package/lib/index.umd.min.js +1 -1
  18. package/lib/theme/element-overrides.css +607 -0
  19. package/lib/theme/element-vars.css +280 -0
  20. package/lib/xt-badge/xt-badge.common.js +1 -1
  21. package/lib/xt-badge/xt-badge.umd.js +1 -1
  22. package/lib/xt-button/xt-button.common.js +1 -1
  23. package/lib/xt-button/xt-button.umd.js +1 -1
  24. package/lib/xt-card/xt-card.common.js +1 -1
  25. package/lib/xt-card/xt-card.umd.js +1 -1
  26. package/lib/xt-card-item/xt-card-item.common.js +1 -1
  27. package/lib/xt-card-item/xt-card-item.umd.js +1 -1
  28. package/lib/xt-chart/xt-chart.common.js +5 -5
  29. package/lib/xt-chart/xt-chart.umd.js +5 -5
  30. package/lib/xt-config-provider/xt-config-provider.css +1 -1
  31. package/lib/xt-date-picker/xt-date-picker.common.1.js +4 -26
  32. package/lib/xt-date-picker/xt-date-picker.common.2.js +1 -1
  33. package/lib/xt-date-picker/xt-date-picker.common.js +1 -47
  34. package/lib/xt-date-picker/xt-date-picker.css +1 -1
  35. package/lib/xt-date-picker/xt-date-picker.umd.1.js +4 -26
  36. package/lib/xt-date-picker/xt-date-picker.umd.2.js +1 -1
  37. package/lib/xt-date-picker/xt-date-picker.umd.js +1 -47
  38. package/lib/xt-date-picker/xt-date-picker.umd.min.1.js +1 -1
  39. package/lib/xt-date-picker/xt-date-picker.umd.min.js +1 -1
  40. package/lib/xt-flex-box/xt-flex-box.common.js +1 -1
  41. package/lib/xt-flex-box/xt-flex-box.umd.js +1 -1
  42. package/lib/xt-flow/xt-flow.common.js +1 -1
  43. package/lib/xt-flow/xt-flow.umd.js +1 -1
  44. package/lib/xt-form-schema/xt-form-schema.common.js +1 -1
  45. package/lib/xt-form-schema/xt-form-schema.umd.js +1 -1
  46. package/lib/xt-grid-box/xt-grid-box.common.js +1 -1
  47. package/lib/xt-grid-box/xt-grid-box.umd.js +1 -1
  48. package/lib/xt-grid-item/xt-grid-item.common.js +1 -1
  49. package/lib/xt-grid-item/xt-grid-item.umd.js +1 -1
  50. package/lib/xt-icon/xt-icon.common.js +1 -1
  51. package/lib/xt-icon/xt-icon.umd.js +1 -1
  52. package/lib/xt-input/xt-input.common.js +1 -1
  53. package/lib/xt-input/xt-input.umd.js +1 -1
  54. package/lib/xt-list/xt-list.common.js +457 -118
  55. package/lib/xt-list/xt-list.css +1 -1
  56. package/lib/xt-list/xt-list.umd.js +457 -118
  57. package/lib/xt-list/xt-list.umd.min.js +1 -1
  58. package/lib/xt-map/xt-map.common.js +1 -1
  59. package/lib/xt-map/xt-map.umd.js +1 -1
  60. package/lib/xt-map-provider/xt-map-provider.common.js +1 -1
  61. package/lib/xt-map-provider/xt-map-provider.umd.js +1 -1
  62. package/lib/xt-page/xt-page.common.js +1 -1
  63. package/lib/xt-page/xt-page.umd.js +1 -1
  64. package/lib/xt-progress/xt-progress.common.js +1 -1
  65. package/lib/xt-progress/xt-progress.umd.js +1 -1
  66. package/lib/xt-scroll/demo.html +10 -0
  67. package/lib/xt-scroll/index.js +1 -0
  68. package/lib/xt-scroll/xt-scroll.common.js +551 -0
  69. package/lib/xt-scroll/xt-scroll.css +1 -0
  70. package/lib/xt-scroll/xt-scroll.umd.js +561 -0
  71. package/lib/xt-scroll/xt-scroll.umd.min.js +1 -0
  72. package/lib/xt-scroll-arrow/xt-scroll-arrow.common.js +591 -170
  73. package/lib/xt-scroll-arrow/xt-scroll-arrow.css +1 -1
  74. package/lib/xt-scroll-arrow/xt-scroll-arrow.umd.js +591 -170
  75. package/lib/xt-scroll-arrow/xt-scroll-arrow.umd.min.js +1 -1
  76. package/lib/xt-select-tree/xt-select-tree.common.js +1 -1
  77. package/lib/xt-select-tree/xt-select-tree.umd.js +1 -1
  78. package/lib/xt-step-price/xt-step-price.common.js +2 -2
  79. package/lib/xt-step-price/xt-step-price.umd.js +2 -2
  80. package/lib/xt-step-price-item/xt-step-price-item.common.js +1 -1
  81. package/lib/xt-step-price-item/xt-step-price-item.umd.js +1 -1
  82. package/lib/xt-tab-pane/xt-tab-pane.common.js +1 -1
  83. package/lib/xt-tab-pane/xt-tab-pane.umd.js +1 -1
  84. package/lib/xt-table/xt-table.common.js +2 -2
  85. package/lib/xt-table/xt-table.umd.js +2 -2
  86. package/lib/xt-tabs/xt-tabs.common.js +1 -1
  87. package/lib/xt-tabs/xt-tabs.umd.js +1 -1
  88. package/lib/xt-text/xt-text.common.js +1 -1
  89. package/lib/xt-text/xt-text.umd.js +1 -1
  90. package/lib/xt-time/xt-time.common.js +1 -1
  91. package/lib/xt-time/xt-time.umd.js +1 -1
  92. package/lib/xt-transfer-tree/xt-transfer-tree.common.js +1 -1
  93. package/lib/xt-transfer-tree/xt-transfer-tree.umd.js +1 -1
  94. package/lib/xt-upload/xt-upload.common.js +2 -2
  95. package/lib/xt-upload/xt-upload.umd.js +2 -2
  96. package/package.json +5 -3
  97. package/src/components/index.scss +3 -0
  98. package/src/components/theme/element-overrides.css +607 -0
  99. package/src/components/theme/element-vars.css +280 -0
  100. package/src/components/theme/iframe-injector.js +502 -0
  101. package/src/components/theme/index.js +64 -0
  102. package/src/components/xt-config-provider/style/index.scss +0 -11
  103. package/src/components/xt-date-picker/component/RangeDate.vue +1 -29
  104. package/src/components/xt-date-picker/style/index.scss +80 -53
  105. package/src/components/xt-list/index.vue +23 -12
  106. package/src/components/xt-scroll/index.js +9 -0
  107. package/src/components/xt-scroll/index.vue +363 -0
  108. package/src/components/xt-scroll/style/index.scss +107 -0
  109. package/src/components/xt-scroll-arrow/index.vue +200 -83
  110. package/src/components/xt-scroll-arrow/style/index.scss +16 -5
  111. package/src/index.js +9 -2
  112. package/src/utils/color.js +105 -0
  113. package/src/utils/index.js +456 -468
  114. package/src/utils/theme-vars.js +134 -0
  115. package/lib/css/1.87a493be.css +0 -1
  116. package/lib/xt-date-picker/css/1.87a493be.css +0 -1
@@ -1,468 +1,456 @@
1
- import EchartsUtil from '../components/xt-chart/utils'
2
-
3
- const defaultConfig = {
4
- theme: 'white',
5
- size: 'medium',
6
- primaryColor: '#1890ff'
7
- }
8
-
9
- let currentConfig = { ...defaultConfig }
10
-
11
- const configChangeListeners = []
12
-
13
- const emitConfigChange = function(key, value) {
14
- configChangeListeners.forEach(listener => {
15
- listener(key, value)
16
- })
17
- }
18
-
19
- export const getConfig = function() {
20
- return { ...currentConfig }
21
- }
22
-
23
- export const setConfig = function(config) {
24
- if (typeof config !== 'object' || config === null) {
25
- console.warn('[XtElementUI] setConfig 必须传入对象参数')
26
- return
27
- }
28
-
29
- if (config.theme !== undefined) {
30
- setTheme(config.theme)
31
- }
32
- if (config.size !== undefined) {
33
- setSize(config.size)
34
- }
35
- if (config.primaryColor !== undefined) {
36
- setPrimaryColor(config.primaryColor)
37
- }
38
- }
39
-
40
- function isBrowser() {
41
- return typeof window !== 'undefined' && typeof document !== 'undefined'
42
- }
43
-
44
- export const setTheme = function(theme) {
45
- const validThemes = ['white', 'dark']
46
- if (!validThemes.includes(theme)) {
47
- console.warn(`[XtElementUI] 无效的主题值: ${theme},可选值: ${validThemes.join(', ')}`)
48
- return
49
- }
50
-
51
- currentConfig.theme = theme
52
-
53
- if (isBrowser()) {
54
- const root = document.documentElement
55
-
56
- if (theme === 'dark') {
57
- root.setAttribute('data-theme', 'dark')
58
- } else {
59
- root.setAttribute('data-theme', theme)
60
- }
61
-
62
- EchartsUtil.changeAllTheme(currentConfig.theme, currentConfig.size)
63
- }
64
-
65
- emitConfigChange('theme', theme)
66
- }
67
-
68
- export const setSize = function(size) {
69
- const validSizes = ['small', 'medium', 'large']
70
- if (!validSizes.includes(size)) {
71
- console.warn(`[XtElementUI] 无效的大小值: ${size},可选值: ${validSizes.join(', ')}`)
72
- return
73
- }
74
-
75
- currentConfig.size = size
76
-
77
- if (isBrowser()) {
78
- document.documentElement.setAttribute('data-size', size)
79
- EchartsUtil.changeAllTheme(currentConfig.theme, currentConfig.size)
80
- }
81
-
82
- emitConfigChange('size', size)
83
- }
84
-
85
- // 将十六进制颜色转换为 RGB 对象
86
- function hexToRgb(hex) {
87
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
88
- return result ? {
89
- r: parseInt(result[1], 16),
90
- g: parseInt(result[2], 16),
91
- b: parseInt(result[3], 16)
92
- } : null
93
- }
94
-
95
- // 将 RGB 对象转换为十六进制颜色
96
- function rgbToHex(r, g, b) {
97
- return '#' + [r, g, b].map(x => {
98
- const hex = x.toString(16)
99
- return hex.length === 1 ? '0' + hex : hex
100
- }).join('')
101
- }
102
-
103
- // 计算浅色系列(混合白色)
104
- function lightenColor(hex, percent) {
105
- const rgb = hexToRgb(hex)
106
- if (!rgb) return hex
107
-
108
- const amount = Math.round(2.55 * percent)
109
- const r = Math.min(255, rgb.r + amount)
110
- const g = Math.min(255, rgb.g + amount)
111
- const b = Math.min(255, rgb.b + amount)
112
-
113
- return rgbToHex(r, g, b)
114
- }
115
-
116
- export const setPrimaryColor = function(color) {
117
- const colorRegex = /^#[0-9A-Fa-f]{3}$|^#[0-9A-Fa-f]{6}$|^#[0-9A-Fa-f]{8}$/
118
- if (!colorRegex.test(color)) {
119
- console.warn(`[XtElementUI] 无效的颜色值: ${color},请使用十六进制颜色格式,如 #1890ff`)
120
- return
121
- }
122
-
123
- let validColor = color
124
- if (color.length === 4) {
125
- validColor = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]
126
- } else if (color.length === 9) {
127
- validColor = color.substring(0, 7)
128
- }
129
-
130
- currentConfig.primaryColor = validColor
131
-
132
- if (isBrowser()) {
133
- document.documentElement.style.setProperty('--xt-color-primary', validColor)
134
-
135
- document.documentElement.style.setProperty('--xt-color-primary-light-3', lightenColor(validColor, 30))
136
- document.documentElement.style.setProperty('--xt-color-primary-light-5', lightenColor(validColor, 50))
137
- document.documentElement.style.setProperty('--xt-color-primary-light-7', lightenColor(validColor, 70))
138
- document.documentElement.style.setProperty('--xt-color-primary-light-8', lightenColor(validColor, 80))
139
- document.documentElement.style.setProperty('--xt-color-primary-light-9', lightenColor(validColor, 90))
140
- }
141
-
142
- emitConfigChange('primaryColor', validColor)
143
- }
144
-
145
- // 获取当前主题
146
- export const getTheme = function() {
147
- return currentConfig.theme
148
- }
149
-
150
- // 获取当前字体大小
151
- export const getSize = function() {
152
- return currentConfig.size
153
- }
154
-
155
- // 获取当前主色调
156
- export const getPrimaryColor = function() {
157
- return currentConfig.primaryColor
158
- }
159
-
160
- // 重置为默认配置
161
- export const resetConfig = function() {
162
- setConfig(defaultConfig)
163
- }
164
-
165
- // 监听配置变更
166
- export const onConfigChange = function(listener) {
167
- if (typeof listener === 'function') {
168
- configChangeListeners.push(listener)
169
- return function() {
170
- const index = configChangeListeners.indexOf(listener)
171
- if (index > -1) {
172
- configChangeListeners.splice(index, 1)
173
- }
174
- }
175
- } else {
176
- console.warn('[XtElementUI] onConfigChange 必须传入函数')
177
- }
178
- }
179
-
180
- // ==================== 数字格式化 ====================
181
-
182
- export const formatNumber = function(value, options) {
183
- if (value === null || value === undefined || value === '') {
184
- return ''
185
- }
186
-
187
- const num = typeof value === 'string' ? parseFloat(value) : value
188
- if (isNaN(num)) {
189
- return String(value)
190
- }
191
-
192
- const opts = Object.assign({
193
- decimals: 2,
194
- thousand: true,
195
- prefix: '',
196
- suffix: '',
197
- showSign: false
198
- }, options || {})
199
-
200
- let result = num.toFixed(opts.decimals)
201
-
202
- if (opts.thousand) {
203
- const parts = result.split('.')
204
- parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
205
- result = parts.join('.')
206
- }
207
-
208
- if (opts.showSign && num > 0) {
209
- result = '+' + result
210
- }
211
-
212
- return opts.prefix + result + opts.suffix
213
- }
214
-
215
- export const formatThousand = function(value, decimals) {
216
- return formatNumber(value, {
217
- decimals: decimals != null ? decimals : 2,
218
- thousand: true
219
- })
220
- }
221
-
222
- export const formatPercent = function(value, decimals) {
223
- if (value === null || value === undefined || value === '') {
224
- return ''
225
- }
226
- const num = typeof value === 'string' ? parseFloat(value) : value
227
- if (isNaN(num)) {
228
- return String(value)
229
- }
230
- return formatNumber(num * 100, {
231
- decimals: decimals != null ? decimals : 2,
232
- thousand: true,
233
- suffix: '%'
234
- })
235
- }
236
-
237
- // ==================== 日期格式化 ====================
238
-
239
- function padZero(num, len) {
240
- len = len || 2
241
- return String(num).padStart(len, '0')
242
- }
243
-
244
- export const formatDate = function(date, format) {
245
- if (!date) {
246
- return ''
247
- }
248
-
249
- let d
250
- if (date instanceof Date) {
251
- d = date
252
- } else if (typeof date === 'number') {
253
- d = new Date(date.toString().length === 10 ? date * 1000 : date)
254
- } else if (typeof date === 'string') {
255
- date = date.trim()
256
- if (/^\d+$/.test(date)) {
257
- d = new Date(date.length === 10 ? parseInt(date) * 1000 : parseInt(date))
258
- } else {
259
- d = new Date(date.replace(/-/g, '/'))
260
- }
261
- } else {
262
- return ''
263
- }
264
-
265
- if (isNaN(d.getTime())) {
266
- return ''
267
- }
268
-
269
- const fmt = format || 'yyyy-MM-dd'
270
- const year = d.getFullYear()
271
- const month = d.getMonth() + 1
272
- const day = d.getDate()
273
- const hour = d.getHours()
274
- const minute = d.getMinutes()
275
- const second = d.getSeconds()
276
- const week = d.getDay()
277
-
278
- const weekMap = ['', '一', '二', '三', '四', '五', '六']
279
-
280
- return fmt
281
- .replace('yyyy', year)
282
- .replace('MM', padZero(month))
283
- .replace('M', month)
284
- .replace('dd', padZero(day))
285
- .replace('d', day)
286
- .replace('HH', padZero(hour))
287
- .replace('H', hour)
288
- .replace('hh', padZero(hour % 12 || 12))
289
- .replace('h', hour % 12 || 12)
290
- .replace('mm', padZero(minute))
291
- .replace('m', minute)
292
- .replace('ss', padZero(second))
293
- .replace('s', second)
294
- .replace('w', weekMap[week])
295
- .replace('W', '星期' + weekMap[week])
296
- }
297
-
298
- export const formatDateTime = function(date, format) {
299
- return formatDate(date, format || 'yyyy-MM-dd HH:mm:ss')
300
- }
301
-
302
- export const formatTime = function(date, format) {
303
- return formatDate(date, format || 'HH:mm:ss')
304
- }
305
-
306
- export const formatRelativeTime = function(date) {
307
- if (!date) {
308
- return ''
309
- }
310
-
311
- let d
312
- if (date instanceof Date) {
313
- d = date
314
- } else if (typeof date === 'number') {
315
- d = new Date(date.toString().length === 10 ? date * 1000 : date)
316
- } else if (typeof date === 'string') {
317
- date = date.trim()
318
- if (/^\d+$/.test(date)) {
319
- d = new Date(date.length === 10 ? parseInt(date) * 1000 : parseInt(date))
320
- } else {
321
- d = new Date(date.replace(/-/g, '/'))
322
- }
323
- } else {
324
- return ''
325
- }
326
-
327
- if (isNaN(d.getTime())) {
328
- return ''
329
- }
330
-
331
- const now = new Date()
332
- const diff = now.getTime() - d.getTime()
333
- const minute = 60 * 1000
334
- const hour = 60 * minute
335
- const day = 24 * hour
336
- const week = 7 * day
337
- const month = 30 * day
338
- const year = 365 * day
339
-
340
- if (diff < minute) {
341
- return '刚刚'
342
- } else if (diff < hour) {
343
- return Math.floor(diff / minute) + '分钟前'
344
- } else if (diff < day) {
345
- return Math.floor(diff / hour) + '小时前'
346
- } else if (diff < week) {
347
- return Math.floor(diff / day) + '天前'
348
- } else if (diff < month) {
349
- return Math.floor(diff / week) + '周前'
350
- } else if (diff < year) {
351
- return Math.floor(diff / month) + '个月前'
352
- } else {
353
- return Math.floor(diff / year) + '年前'
354
- }
355
- }
356
-
357
- // ==================== 金额格式化 ====================
358
-
359
- export const formatMoney = function(value, options) {
360
- if (value === null || value === undefined || value === '') {
361
- return ''
362
- }
363
-
364
- const num = typeof value === 'string' ? parseFloat(value) : value
365
- if (isNaN(num)) {
366
- return String(value)
367
- }
368
-
369
- const opts = Object.assign({
370
- currency: 'CNY',
371
- decimals: 2,
372
- prefix: '',
373
- suffix: '',
374
- showSign: false
375
- }, options || {})
376
-
377
- const currencySymbols = {
378
- CNY: '¥',
379
- USD: '$',
380
- EUR: '',
381
- JPY: '¥',
382
- GBP: '£',
383
- AUD: 'A$',
384
- CAD: 'C$'
385
- }
386
-
387
- let symbol = currencySymbols[opts.currency] || ''
388
- let formatted = num.toFixed(opts.decimals)
389
-
390
- const parts = formatted.split('.')
391
- parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
392
- formatted = parts.join('.')
393
-
394
- if (opts.showSign && num > 0) {
395
- formatted = '+' + formatted
396
- }
397
-
398
- if (opts.prefix) {
399
- formatted = opts.prefix + formatted
400
- }
401
-
402
- if (opts.suffix) {
403
- formatted = formatted + opts.suffix
404
- }
405
-
406
- return symbol + formatted
407
- }
408
-
409
- export const formatCNY = function(value, decimals) {
410
- return formatMoney(value, {
411
- currency: 'CNY',
412
- decimals: decimals != null ? decimals : 2
413
- })
414
- }
415
-
416
- export const formatUSD = function(value, decimals) {
417
- return formatMoney(value, {
418
- currency: 'USD',
419
- decimals: decimals != null ? decimals : 2
420
- })
421
- }
422
-
423
- // ==================== 文件大小格式化 ====================
424
-
425
- export const formatFileSize = function(bytes) {
426
- if (bytes === null || bytes === undefined || bytes === '') {
427
- return ''
428
- }
429
-
430
- const num = typeof bytes === 'string' ? parseFloat(bytes) : bytes
431
- if (isNaN(num)) {
432
- return String(bytes)
433
- }
434
-
435
- if (num === 0) {
436
- return '0 B'
437
- }
438
-
439
- const k = 1024
440
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
441
- const i = Math.floor(Math.log(num) / Math.log(k))
442
-
443
- return parseFloat((num / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
444
- }
445
-
446
- export default {
447
- setTheme,
448
- setSize,
449
- setPrimaryColor,
450
- getConfig,
451
- setConfig,
452
- getTheme,
453
- getSize,
454
- getPrimaryColor,
455
- resetConfig,
456
- onConfigChange,
457
- formatNumber,
458
- formatThousand,
459
- formatPercent,
460
- formatDate,
461
- formatDateTime,
462
- formatTime,
463
- formatRelativeTime,
464
- formatMoney,
465
- formatCNY,
466
- formatUSD,
467
- formatFileSize
468
- }
1
+ import EchartsUtil from '../components/xt-chart/utils'
2
+ import { generateXtPrimaryColorVars } from './color'
3
+ import { applyThemeVars } from './theme-vars'
4
+
5
+ const defaultConfig = {
6
+ theme: 'white',
7
+ size: 'medium',
8
+ primaryColor: '#1890ff'
9
+ }
10
+
11
+ let currentConfig = { ...defaultConfig }
12
+
13
+ const configChangeListeners = []
14
+
15
+ const emitConfigChange = function(key, value) {
16
+ configChangeListeners.forEach(listener => {
17
+ listener(key, value)
18
+ })
19
+ }
20
+
21
+ export const getConfig = function() {
22
+ return { ...currentConfig }
23
+ }
24
+
25
+ export const setConfig = function(config) {
26
+ if (typeof config !== 'object' || config === null) {
27
+ console.warn('[XtElementUI] setConfig 必须传入对象参数')
28
+ return
29
+ }
30
+
31
+ if (config.theme !== undefined) {
32
+ setTheme(config.theme)
33
+ }
34
+ if (config.size !== undefined) {
35
+ setSize(config.size)
36
+ }
37
+ if (config.primaryColor !== undefined) {
38
+ setPrimaryColor(config.primaryColor)
39
+ }
40
+ }
41
+
42
+ function isBrowser() {
43
+ return typeof window !== 'undefined' && typeof document !== 'undefined'
44
+ }
45
+
46
+ /**
47
+ * 设置主题(亮/暗)
48
+ * 通过 applyThemeVars 统一设置 --el-* CSS 变量,确保组件库和 Element UI 同步响应
49
+ * 同时设置 --xt-* CSS 变量供 xt 组件使用
50
+ */
51
+ export const setTheme = function(theme) {
52
+ const validThemes = ['white', 'dark']
53
+ if (!validThemes.includes(theme)) {
54
+ console.warn(`[XtElementUI] 无效的主题值: ${theme},可选值: ${validThemes.join(', ')}`)
55
+ return
56
+ }
57
+
58
+ currentConfig.theme = theme
59
+
60
+ if (isBrowser()) {
61
+ const root = document.documentElement
62
+
63
+ // 统一通过 applyThemeVars 设置 --el-* 变量
64
+ applyThemeVars(root, {
65
+ theme,
66
+ primaryColor: currentConfig.primaryColor
67
+ })
68
+
69
+ // 同步设置 xt 组件主色变量(--xt-*)
70
+ const xtVars = generateXtPrimaryColorVars(currentConfig.primaryColor)
71
+ for (const key in xtVars) {
72
+ root.style.setProperty(key, xtVars[key])
73
+ }
74
+
75
+ EchartsUtil.changeAllTheme(currentConfig.theme, currentConfig.size)
76
+ }
77
+
78
+ emitConfigChange('theme', theme)
79
+ }
80
+
81
+ export const setSize = function(size) {
82
+ const validSizes = ['small', 'medium', 'large']
83
+ if (!validSizes.includes(size)) {
84
+ console.warn(`[XtElementUI] 无效的大小值: ${size},可选值: ${validSizes.join(', ')}`)
85
+ return
86
+ }
87
+
88
+ currentConfig.size = size
89
+
90
+ if (isBrowser()) {
91
+ document.documentElement.setAttribute('data-size', size)
92
+ EchartsUtil.changeAllTheme(currentConfig.theme, currentConfig.size)
93
+ }
94
+
95
+ emitConfigChange('size', size)
96
+ }
97
+
98
+ export const setPrimaryColor = function(color) {
99
+ const colorRegex = /^#[0-9A-Fa-f]{3}$|^#[0-9A-Fa-f]{6}$|^#[0-9A-Fa-f]{8}$/
100
+ if (!colorRegex.test(color)) {
101
+ console.warn(`[XtElementUI] 无效的颜色值: ${color},请使用十六进制颜色格式,如 #1890ff`)
102
+ return
103
+ }
104
+
105
+ let validColor = color
106
+ if (color.length === 4) {
107
+ validColor = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]
108
+ } else if (color.length === 9) {
109
+ validColor = color.substring(0, 7)
110
+ }
111
+
112
+ currentConfig.primaryColor = validColor
113
+
114
+ if (isBrowser()) {
115
+ const root = document.documentElement
116
+
117
+ // 统一通过 applyThemeVars 设置 --el-* 主色变量
118
+ applyThemeVars(root, {
119
+ theme: currentConfig.theme,
120
+ primaryColor: validColor
121
+ })
122
+
123
+ // 同步设置 xt 组件主色变量(--xt-*)
124
+ const xtVars = generateXtPrimaryColorVars(validColor)
125
+ for (const key in xtVars) {
126
+ root.style.setProperty(key, xtVars[key])
127
+ }
128
+ }
129
+
130
+ emitConfigChange('primaryColor', validColor)
131
+ }
132
+
133
+ // 获取当前主题
134
+ export const getTheme = function() {
135
+ return currentConfig.theme
136
+ }
137
+
138
+ // 获取当前字体大小
139
+ export const getSize = function() {
140
+ return currentConfig.size
141
+ }
142
+
143
+ // 获取当前主色调
144
+ export const getPrimaryColor = function() {
145
+ return currentConfig.primaryColor
146
+ }
147
+
148
+ // 重置为默认配置
149
+ export const resetConfig = function() {
150
+ setConfig(defaultConfig)
151
+ }
152
+
153
+ // 监听配置变更
154
+ export const onConfigChange = function(listener) {
155
+ if (typeof listener === 'function') {
156
+ configChangeListeners.push(listener)
157
+ return function() {
158
+ const index = configChangeListeners.indexOf(listener)
159
+ if (index > -1) {
160
+ configChangeListeners.splice(index, 1)
161
+ }
162
+ }
163
+ } else {
164
+ console.warn('[XtElementUI] onConfigChange 必须传入函数')
165
+ }
166
+ }
167
+
168
+ // ==================== 数字格式化 ====================
169
+
170
+ export const formatNumber = function(value, options) {
171
+ if (value === null || value === undefined || value === '') {
172
+ return ''
173
+ }
174
+
175
+ const num = typeof value === 'string' ? parseFloat(value) : value
176
+ if (isNaN(num)) {
177
+ return String(value)
178
+ }
179
+
180
+ const opts = Object.assign({
181
+ decimals: 2,
182
+ thousand: true,
183
+ prefix: '',
184
+ suffix: '',
185
+ showSign: false
186
+ }, options || {})
187
+
188
+ let result = num.toFixed(opts.decimals)
189
+
190
+ if (opts.thousand) {
191
+ const parts = result.split('.')
192
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
193
+ result = parts.join('.')
194
+ }
195
+
196
+ if (opts.showSign && num > 0) {
197
+ result = '+' + result
198
+ }
199
+
200
+ return opts.prefix + result + opts.suffix
201
+ }
202
+
203
+ export const formatThousand = function(value, decimals) {
204
+ return formatNumber(value, {
205
+ decimals: decimals != null ? decimals : 2,
206
+ thousand: true
207
+ })
208
+ }
209
+
210
+ export const formatPercent = function(value, decimals) {
211
+ if (value === null || value === undefined || value === '') {
212
+ return ''
213
+ }
214
+ const num = typeof value === 'string' ? parseFloat(value) : value
215
+ if (isNaN(num)) {
216
+ return String(value)
217
+ }
218
+ return formatNumber(num * 100, {
219
+ decimals: decimals != null ? decimals : 2,
220
+ thousand: true,
221
+ suffix: '%'
222
+ })
223
+ }
224
+
225
+ // ==================== 日期格式化 ====================
226
+
227
+ function padZero(num, len) {
228
+ len = len || 2
229
+ return String(num).padStart(len, '0')
230
+ }
231
+
232
+ export const formatDate = function(date, format) {
233
+ if (!date) {
234
+ return ''
235
+ }
236
+
237
+ let d
238
+ if (date instanceof Date) {
239
+ d = date
240
+ } else if (typeof date === 'number') {
241
+ d = new Date(date.toString().length === 10 ? date * 1000 : date)
242
+ } else if (typeof date === 'string') {
243
+ date = date.trim()
244
+ if (/^\d+$/.test(date)) {
245
+ d = new Date(date.length === 10 ? parseInt(date) * 1000 : parseInt(date))
246
+ } else {
247
+ d = new Date(date.replace(/-/g, '/'))
248
+ }
249
+ } else {
250
+ return ''
251
+ }
252
+
253
+ if (isNaN(d.getTime())) {
254
+ return ''
255
+ }
256
+
257
+ const fmt = format || 'yyyy-MM-dd'
258
+ const year = d.getFullYear()
259
+ const month = d.getMonth() + 1
260
+ const day = d.getDate()
261
+ const hour = d.getHours()
262
+ const minute = d.getMinutes()
263
+ const second = d.getSeconds()
264
+ const week = d.getDay()
265
+
266
+ const weekMap = ['', '一', '二', '三', '四', '五', '六']
267
+
268
+ return fmt
269
+ .replace('yyyy', year)
270
+ .replace('MM', padZero(month))
271
+ .replace('M', month)
272
+ .replace('dd', padZero(day))
273
+ .replace('d', day)
274
+ .replace('HH', padZero(hour))
275
+ .replace('H', hour)
276
+ .replace('hh', padZero(hour % 12 || 12))
277
+ .replace('h', hour % 12 || 12)
278
+ .replace('mm', padZero(minute))
279
+ .replace('m', minute)
280
+ .replace('ss', padZero(second))
281
+ .replace('s', second)
282
+ .replace('w', weekMap[week])
283
+ .replace('W', '星期' + weekMap[week])
284
+ }
285
+
286
+ export const formatDateTime = function(date, format) {
287
+ return formatDate(date, format || 'yyyy-MM-dd HH:mm:ss')
288
+ }
289
+
290
+ export const formatTime = function(date, format) {
291
+ return formatDate(date, format || 'HH:mm:ss')
292
+ }
293
+
294
+ export const formatRelativeTime = function(date) {
295
+ if (!date) {
296
+ return ''
297
+ }
298
+
299
+ let d
300
+ if (date instanceof Date) {
301
+ d = date
302
+ } else if (typeof date === 'number') {
303
+ d = new Date(date.toString().length === 10 ? date * 1000 : date)
304
+ } else if (typeof date === 'string') {
305
+ date = date.trim()
306
+ if (/^\d+$/.test(date)) {
307
+ d = new Date(date.length === 10 ? parseInt(date) * 1000 : parseInt(date))
308
+ } else {
309
+ d = new Date(date.replace(/-/g, '/'))
310
+ }
311
+ } else {
312
+ return ''
313
+ }
314
+
315
+ if (isNaN(d.getTime())) {
316
+ return ''
317
+ }
318
+
319
+ const now = new Date()
320
+ const diff = now.getTime() - d.getTime()
321
+ const minute = 60 * 1000
322
+ const hour = 60 * minute
323
+ const day = 24 * hour
324
+ const week = 7 * day
325
+ const month = 30 * day
326
+ const year = 365 * day
327
+
328
+ if (diff < minute) {
329
+ return '刚刚'
330
+ } else if (diff < hour) {
331
+ return Math.floor(diff / minute) + '分钟前'
332
+ } else if (diff < day) {
333
+ return Math.floor(diff / hour) + '小时前'
334
+ } else if (diff < week) {
335
+ return Math.floor(diff / day) + '天前'
336
+ } else if (diff < month) {
337
+ return Math.floor(diff / week) + '周前'
338
+ } else if (diff < year) {
339
+ return Math.floor(diff / month) + '个月前'
340
+ } else {
341
+ return Math.floor(diff / year) + '年前'
342
+ }
343
+ }
344
+
345
+ // ==================== 金额格式化 ====================
346
+
347
+ export const formatMoney = function(value, options) {
348
+ if (value === null || value === undefined || value === '') {
349
+ return ''
350
+ }
351
+
352
+ const num = typeof value === 'string' ? parseFloat(value) : value
353
+ if (isNaN(num)) {
354
+ return String(value)
355
+ }
356
+
357
+ const opts = Object.assign({
358
+ currency: 'CNY',
359
+ decimals: 2,
360
+ prefix: '',
361
+ suffix: '',
362
+ showSign: false
363
+ }, options || {})
364
+
365
+ const currencySymbols = {
366
+ CNY: '¥',
367
+ USD: '$',
368
+ EUR: '€',
369
+ JPY: '¥',
370
+ GBP: '£',
371
+ AUD: 'A$',
372
+ CAD: 'C$'
373
+ }
374
+
375
+ let symbol = currencySymbols[opts.currency] || ''
376
+ let formatted = num.toFixed(opts.decimals)
377
+
378
+ const parts = formatted.split('.')
379
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
380
+ formatted = parts.join('.')
381
+
382
+ if (opts.showSign && num > 0) {
383
+ formatted = '+' + formatted
384
+ }
385
+
386
+ if (opts.prefix) {
387
+ formatted = opts.prefix + formatted
388
+ }
389
+
390
+ if (opts.suffix) {
391
+ formatted = formatted + opts.suffix
392
+ }
393
+
394
+ return symbol + formatted
395
+ }
396
+
397
+ export const formatCNY = function(value, decimals) {
398
+ return formatMoney(value, {
399
+ currency: 'CNY',
400
+ decimals: decimals != null ? decimals : 2
401
+ })
402
+ }
403
+
404
+ export const formatUSD = function(value, decimals) {
405
+ return formatMoney(value, {
406
+ currency: 'USD',
407
+ decimals: decimals != null ? decimals : 2
408
+ })
409
+ }
410
+
411
+ // ==================== 文件大小格式化 ====================
412
+
413
+ export const formatFileSize = function(bytes) {
414
+ if (bytes === null || bytes === undefined || bytes === '') {
415
+ return ''
416
+ }
417
+
418
+ const num = typeof bytes === 'string' ? parseFloat(bytes) : bytes
419
+ if (isNaN(num)) {
420
+ return String(bytes)
421
+ }
422
+
423
+ if (num === 0) {
424
+ return '0 B'
425
+ }
426
+
427
+ const k = 1024
428
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
429
+ const i = Math.floor(Math.log(num) / Math.log(k))
430
+
431
+ return parseFloat((num / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
432
+ }
433
+
434
+ export default {
435
+ setTheme,
436
+ setSize,
437
+ setPrimaryColor,
438
+ getConfig,
439
+ setConfig,
440
+ getTheme,
441
+ getSize,
442
+ getPrimaryColor,
443
+ resetConfig,
444
+ onConfigChange,
445
+ formatNumber,
446
+ formatThousand,
447
+ formatPercent,
448
+ formatDate,
449
+ formatDateTime,
450
+ formatTime,
451
+ formatRelativeTime,
452
+ formatMoney,
453
+ formatCNY,
454
+ formatUSD,
455
+ formatFileSize
456
+ }