wui-components-v2 1.1.70 → 1.1.72

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 (82) hide show
  1. package/api/feishu.ts +20 -20
  2. package/api/index.ts +4 -4
  3. package/api/login.ts +51 -51
  4. package/api/tree.ts +28 -28
  5. package/components/action-popup/action-popup.vue +116 -116
  6. package/components/add-address-list/add-address-list.vue +187 -187
  7. package/components/add-address-page/config.ts +297 -297
  8. package/components/audio-play/audio-play.vue +220 -220
  9. package/components/batch-upload-file/batch-upload-file.vue +75 -75
  10. package/components/card-botom-buttons/card-botom-buttons.vue +279 -279
  11. package/components/demo-card/demo-card.vue +44 -44
  12. package/components/fold-card/fold-card.vue +2 -2
  13. package/components/form-control/form-control.vue +9 -1
  14. package/components/global-loading/GlobalLoading.md +366 -366
  15. package/components/global-loading/global-loading.vue +55 -55
  16. package/components/global-message/GlobalMessage.md +570 -570
  17. package/components/global-toast/GlobalToast.md +261 -261
  18. package/components/global-toast/global-toast.vue +56 -56
  19. package/components/label-value/open-value-more.vue +56 -56
  20. package/components/loading/loading.vue +5 -5
  21. package/components/login-form-box/login-form-box.vue +65 -65
  22. package/components/phone-login-form/phone-login-form.vue +204 -204
  23. package/components/privacy-popup/privacy-popup.vue +191 -191
  24. package/components/scan-input/scan-input.vue +406 -406
  25. package/components/select-col-picker/select-col-picker.vue +168 -168
  26. package/components/tab-search/tab-search.vue +73 -73
  27. package/components/tree-select/components/TreeNode.vue +200 -200
  28. package/components/tree-select/components/index.vue +471 -471
  29. package/components/tree-select/components/tree-select.vue +56 -56
  30. package/components/tree-select/index.vue +93 -93
  31. package/components/user-choose/user-choose.vue +169 -151
  32. package/components/video-play/video-play.vue +56 -56
  33. package/components/wui-auto-update-component/wui-auto-update-component.vue +132 -132
  34. package/components/wui-default/wui-default.vue +49 -49
  35. package/components/wui-details-page/wui-details-page.vue +147 -147
  36. package/components/wui-edit-page/wui-edit-page-copy.vue +757 -757
  37. package/components/wui-edit-page/wui-edit-page.vue +793 -793
  38. package/components/wui-login/wui-login.vue +43 -43
  39. package/components/wui-login1/wui-login.vue +41 -41
  40. package/components/wui-menus/wui-menus-top.vue +137 -137
  41. package/components/wui-menus/wui-menus-top1.vue +8 -8
  42. package/components/wui-menus1/components/banner-carousel.vue +320 -320
  43. package/components/wui-menus1/components/quick-panel.vue +55 -55
  44. package/components/wui-menus1/components/search-bar.vue +49 -49
  45. package/components/wui-menus1/components/section-menus.vue +208 -208
  46. package/components/wui-menus1/wui-menus-top.vue +718 -718
  47. package/components/wui-menus1/wui-menus-top1.vue +398 -398
  48. package/components/wui-notify-info/notify-handle.vue +53 -53
  49. package/components/wui-scan-binding-sensor/wui-scan-binding-sensor.vue +75 -75
  50. package/components/wui-search-history-babbar/components/SearchBar.vue +70 -70
  51. package/components/wui-select-list/wui-select-list.vue +1 -9
  52. package/components/wui-select-popup/wui-select-popup.vue +1 -0
  53. package/components/wui-tree-page/wui-tree-page.vue +297 -297
  54. package/components/wui-update-component/wui-update-component.vue +221 -221
  55. package/composables/types/theme.ts +65 -65
  56. package/composables/types/user.ts +5 -5
  57. package/composables/useColorGenerator.ts +221 -221
  58. package/composables/useCompanyFieldFilter.ts +26 -24
  59. package/composables/useEnumes.ts +43 -43
  60. package/composables/useEventBus.ts +37 -37
  61. package/composables/useGlobalLoading.ts +47 -47
  62. package/composables/useGlobalMessage.ts +53 -53
  63. package/composables/useGlobalToast.ts +63 -63
  64. package/composables/useLocale.ts +54 -54
  65. package/composables/useManualTheme.ts +179 -179
  66. package/composables/useSectionMenus.ts +120 -120
  67. package/composables/useTabbar.ts +50 -50
  68. package/composables/useTheme.ts +74 -74
  69. package/composables/useUser.ts +24 -24
  70. package/index.d.ts +90 -90
  71. package/package.json +24 -24
  72. package/store/manualThemeStore.ts +143 -143
  73. package/store/persist.ts +41 -41
  74. package/store/themeStore.ts +74 -74
  75. package/store/userStore.ts +22 -22
  76. package/styles/dark-mode.scss +498 -498
  77. package/type.ts +154 -154
  78. package/utils/control-tree.ts +353 -353
  79. package/utils/eventBus.ts +36 -36
  80. package/utils/idata-scan.ts +99 -99
  81. package/utils/index.ts +979 -979
  82. package/utils/rsaEncrypt.ts +10 -10
package/utils/index.ts CHANGED
@@ -1,979 +1,979 @@
1
- import type { Comparator } from '../type'
2
-
3
- // const filefxd = ['image', 'video', 'audio', 'file', 'wav']
4
- const baseUrl = uni.getStorageSync('BASE_URL')
5
- const token = uni.getStorageSync('TOKEN')
6
- const hydrocarbonProgramToken = uni.getStorageSync('HYDROCARBON_PROGRAM_TOKEN')
7
-
8
- /**
9
- * 获取当前页面路径
10
- * @returns 当前页面路径
11
- */
12
- export function getCurrentPath() {
13
- const pages = getCurrentPages()
14
- const currentPage = pages[pages.length - 1]
15
- return currentPage.route || ''
16
- }
17
-
18
- /**
19
- * 格式化列表项展示数据
20
- * @param data 数据值
21
- * @param type 数据类型
22
- * @returns 格式化后的数据
23
- */
24
- export function formatItemData(data: any, type: string) {
25
- // console.log('[Mock] formatItemData', data, type)
26
- if (type === 'relselect' || type === 'tree-entity-select' || type === 'table-entity-select') {
27
- // 判断data是否为数组
28
- if (Array.isArray(data)) {
29
- const dataArr = data.map(item => item.split('@R@')[1]).join(',')
30
- return dataArr
31
- }
32
- // 字符串中是否包含@,@
33
- if (typeof data === 'string' && data.includes('@,@')) {
34
- return data.split('@,@').map((item: string) => item.split('@R@')[1]).join(',')
35
- }
36
-
37
- // 字符串中是否包含@R@
38
- if (typeof data === 'string' && data.includes('@R@')) {
39
- return data.split('@R@')[1]
40
- }
41
-
42
- return data || ''
43
- }
44
-
45
- if (type === 'datetime') {
46
- return data ? data.split('.')[0] : ''
47
- }
48
-
49
- if (type === 'file' || type === 'relfile' || type === 'video') {
50
- if (data) {
51
- // 判断是否为JSON文件
52
- if (typeof data === 'string') {
53
- const a = JSON.parse(data)
54
- return [{
55
- url: `${baseUrl}/v3/files${a.base.path}?@token=${token}&@programToken=${hydrocarbonProgramToken}&name=${a.base.fileName || ''}`,
56
- name: a.base.fileName,
57
- type: a.base.type,
58
- }]
59
- }
60
- else if (Array.isArray(data)) {
61
- return data.map((item) => {
62
- if (item.includes('@R@')) {
63
- const a = JSON.parse(item.split('@R@')[1])
64
- return {
65
- url: `${baseUrl}/v3/files${a.base.path}?@token=${token}&@programToken=${hydrocarbonProgramToken}&name=${a.base.fileName || ''}`,
66
- name: a.base.fileName,
67
- type: a.base.type,
68
- }
69
- }
70
- else {
71
- const a = JSON.parse(item)
72
- return {
73
- url: `${baseUrl}/v3/files${a.base.path}?@token=${token}&@programToken=${hydrocarbonProgramToken}&name=${a.base.fileName || ''}`,
74
- name: a.base.fileName,
75
- type: a.base.type,
76
- }
77
- }
78
- })
79
- }
80
- else {
81
- return [{
82
- url: data.url,
83
- name: data.name,
84
- type: getAfterLastMatch(data.name, '.') || getAfterLastMatch(data.url, '.'),
85
- }]
86
- }
87
- }
88
- return [{
89
- url: 'https://',
90
- name: '',
91
- type: 'img',
92
- }]
93
- }
94
-
95
- return data
96
- }
97
-
98
- /**
99
- * 高并发环境下也能保证唯一的ID生成器
100
- * @returns 25字符的唯一字符串
101
- */
102
- let counter = 0
103
- export function generateHighResolutionID(): string {
104
- const time = Date.now().toString(36)
105
- const random = Math.random().toString(36).substring(2, 10)
106
- counter = (counter + 1) % Number.MAX_SAFE_INTEGER
107
- return `${time}-${random}-${counter.toString(36).padStart(6, '0')}`
108
- }
109
-
110
- // 下载文件
111
- export function downloadFile(url: string) {
112
- console.log('下载', url)
113
- uni.downloadFile({
114
- url, // 文件的网络地址
115
- success: (res) => {
116
- if (res.statusCode === 200) {
117
- console.log('下载成功', res.tempFilePath)
118
- // 打开文件(如PDF、Word等)
119
- uni.openDocument({
120
- filePath: res.tempFilePath,
121
- showMenu: true,
122
- success: () => console.log('打开文件成功'),
123
- })
124
- }
125
- else {
126
- console.error('下载失败', res)
127
- uni.showToast({
128
- title: '下载失败',
129
- icon: 'none',
130
- })
131
- }
132
- },
133
- fail: (err) => {
134
- console.error('下载失败', err)
135
- uni.showToast({
136
- title: '下载失败',
137
- icon: 'none',
138
- })
139
- },
140
- })
141
- }
142
-
143
- /**
144
- * 字符串截取匹配最后一项(不包含匹配字符)
145
- *
146
- *
147
- */
148
- function getAfterLastMatch(str: string, pattern: string) {
149
- const lastIndex = str.lastIndexOf(pattern)
150
- if (lastIndex === -1)
151
- return null
152
- return str.substring(lastIndex + pattern.length)
153
- }
154
-
155
- /****
156
- *
157
- * 根据comparator、defaultValue、val
158
- * 判断按钮是否满足显示
159
- *
160
- *
161
- */
162
- export function comparator(
163
- comparator: Comparator,
164
- val: string,
165
- defaultValue: string,
166
- ): boolean {
167
- let dvs: string[]
168
- let value = val
169
- if (val && val.indexOf && val.indexOf('@R@') > 0) {
170
- const vs = val.split('@R@')
171
- value = vs[1]
172
- }
173
-
174
- if (!comparator) {
175
- comparator = 'equals'
176
- }
177
-
178
- if (comparator === 'none1') {
179
- return !value || value === ''
180
- }
181
- else if (comparator === 'none1n') {
182
- return !!value && value !== ''
183
- }
184
-
185
- if (!defaultValue) {
186
- return true
187
- }
188
-
189
- if (!value && value !== '0') {
190
- return false
191
- }
192
- let vals
193
- switch (comparator) {
194
- case 'contain':
195
- return value.includes(defaultValue)
196
- case 'equal':
197
- case 'equals':
198
- return isEqual(value, defaultValue)
199
- case 'exclude':
200
- dvs = toArray(defaultValue)
201
- vals = toArray(value)
202
- for (let i = 0; i < dvs.length; i++) {
203
- for (let j = 0; j < vals.length; j++) {
204
- if (dvs[i] === vals[j]) {
205
- return false
206
- }
207
- }
208
- }
209
- return true
210
- case 'includeOne':
211
- case 'include_anyone':
212
- dvs = toArray(defaultValue)
213
- vals = toArray(value)
214
- for (let i = 0; i < dvs.length; i++) {
215
- for (let j = 0; j < vals.length; j++) {
216
- if (dvs[i] === vals[j]) {
217
- return true
218
- }
219
- }
220
- }
221
- return false
222
-
223
- case 'include_all':
224
- {
225
- dvs = toArray(defaultValue)
226
- vals = toArray(value)
227
- let count = 0
228
- const targetCount = dvs.length
229
- for (let i = 0; i < dvs.length; i++) {
230
- for (let j = 0; j < vals.length; j++) {
231
- if (dvs[i] === vals[j]) {
232
- count++
233
- break
234
- }
235
- }
236
- }
237
- return targetCount <= count
238
- }
239
- case 'Greater':
240
- case 'greater':
241
- if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
242
- return Number(value) > Number(defaultValue)
243
- }
244
- else {
245
- return value > defaultValue
246
- }
247
- case 'Less':
248
- case 'less':
249
- if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
250
- return Number(value) < Number(defaultValue)
251
- }
252
- else {
253
- return value < defaultValue
254
- }
255
- case 'noGreater':
256
- case 'nogreater':
257
- if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
258
- return Number(value) <= Number(defaultValue)
259
- }
260
- else {
261
- return value <= defaultValue
262
- }
263
- case 'noLess':
264
- case 'noless':
265
- if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
266
- return Number(value) >= Number(defaultValue)
267
- }
268
- else {
269
- return value >= defaultValue
270
- }
271
- case 'unequal':
272
- case 'unequals':
273
- return !isEqual(value, defaultValue)
274
- default:
275
- console.log(`暂不支持的比较关系:${comparator};请联系管理员添加。`)
276
- return false // 兜底返回值
277
- }
278
- }
279
-
280
- function isEqual(value: string, defaultValue: string) {
281
- if (defaultValue === '是' || defaultValue === '否' || defaultValue === 'true' || defaultValue === 'false') {
282
- if (value === 'true' || value === '是') {
283
- if (defaultValue === '是' || defaultValue === 'true') {
284
- return true
285
- }
286
- else {
287
- return false
288
- }
289
- }
290
- else if (value === 'false' || value === '否') {
291
- if (defaultValue === '否' || defaultValue === 'false') {
292
- return true
293
- }
294
- else {
295
- return false
296
- }
297
- }
298
- else {
299
- return false
300
- }
301
- }
302
- else {
303
- if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
304
- return Number(value) === Number(defaultValue)
305
- }
306
- else {
307
- return value === defaultValue
308
- }
309
- }
310
- }
311
-
312
- function toArray(value: string) {
313
- let val
314
- if (typeof value == 'string') {
315
- val = value.split(',')
316
- }
317
- else if (Array.isArray(value)) {
318
- val = value
319
- }
320
- else {
321
- val = [value]
322
- }
323
- return val
324
- }
325
-
326
- /**
327
- * 根据目标颜色生成渐变色字符串
328
- * @param color - 目标颜色,可以是十六进制颜色或RGB颜色
329
- * @param angle - 渐变角度,默认为180deg
330
- * @param startOpacity - 起始透明度,默认为1(不透明)
331
- * @param endOpacity - 结束透明度,默认为0.3
332
- * @returns 渐变色字符串
333
- */
334
- export function generateGradientColor(
335
- color: string,
336
- angle: number = 180,
337
- startOpacity: number = 1,
338
- endOpacity: number = 0.3,
339
- ): string {
340
- // 处理十六进制颜色转换为RGB
341
- const hexToRgb = (hex: string): { r: number, g: number, b: number } | null => {
342
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
343
- return result
344
- ? {
345
- r: Number.parseInt(result[1], 16),
346
- g: Number.parseInt(result[2], 16),
347
- b: Number.parseInt(result[3], 16),
348
- }
349
- : null
350
- }
351
-
352
- // 将RGB颜色转换为rgba格式
353
- const toRgba = (color: string, opacity: number): string => {
354
- if (color.startsWith('#')) {
355
- const rgb = hexToRgb(color)
356
- if (rgb) {
357
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`
358
- }
359
- }
360
- else if (color.startsWith('rgb(')) {
361
- // 处理rgb()格式
362
- const rgbValues = color.match(/\d+/g)
363
- if (rgbValues && rgbValues.length === 3) {
364
- return `rgba(${rgbValues[0]}, ${rgbValues[1]}, ${rgbValues[2]}, ${opacity})`
365
- }
366
- }
367
- // 如果已经是rgba或其他格式,直接返回带透明度的版本
368
- return color.replace(/rgb\(/, 'rgba(').replace(/\)/, `, ${opacity})`)
369
- }
370
-
371
- // 生成渐变色字符串
372
- const startColor = color.startsWith('rgba') ? color : toRgba(color, startOpacity)
373
- const endColor = toRgba(color, endOpacity)
374
-
375
- return `linear-gradient(${angle}deg, ${startColor} 0%, ${endColor} 100%)`
376
- }
377
-
378
- // 使用示例
379
- // console.log(generateGradientColor('#2094FF'));
380
- // 输出: linear-gradient(180deg, rgba(32,148,255,1) 0%, rgba(32,148,255,0.3) 100%)
381
-
382
- // console.log(generateGradientColor('#2094FF', 90, 0.8, 0.1));
383
- // 输出: linear-gradient(90deg, rgba(32,148,255,0.8) 0%, rgba(32,148,255,0.1) 100%)
384
-
385
- /**
386
- * 格式化日期显示
387
- * @param date - 要格式化的日期,默认为当前日期
388
- * @returns 格式化后的日期字符串,如"2025年9月25日 周四 下午"
389
- */
390
- export function formatChineseDate(date: Date = new Date()): string {
391
- // 年份
392
- const year = date.getFullYear()
393
-
394
- // 月份(月份从0开始,需要+1)
395
- const month = date.getMonth() + 1
396
-
397
- // 日期
398
- const day = date.getDate()
399
-
400
- // 星期几
401
- const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
402
- const weekday = weekdays[date.getDay()]
403
-
404
- // 上午/下午判断
405
- const hour = date.getHours()
406
- const period = hour < 12 ? '上午' : '下午'
407
-
408
- return `${year}年${month}月${day}日 ${weekday} ${period}`
409
- }
410
-
411
- // 使用示例
412
- // console.log(formatChineseDate(new Date(2025, 8, 25)));
413
- // 输出: 2025年9月25日 周四 下午
414
-
415
- // console.log(formatChineseDate(new Date(2025, 8, 25, 10, 30)));
416
- // 输出: 2025年9月25日 周四 上午
417
-
418
- // console.log(formatChineseDate());
419
- // 输出当前日期的格式化结果
420
-
421
- /**
422
- * 淡化颜色
423
- * @param color - 目标颜色,可以是十六进制颜色或RGB颜色
424
- * @returns 淡化后的颜色字符串
425
- */
426
- export function desaturateColor(color: string, opacity: number = 0.04): string {
427
- // 处理十六进制颜色转换为RGB
428
- const hexToRgb = (hex: string): { r: number, g: number, b: number } | null => {
429
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
430
- return result
431
- ? {
432
- r: Number.parseInt(result[1], 16),
433
- g: Number.parseInt(result[2], 16),
434
- b: Number.parseInt(result[3], 16),
435
- }
436
- : null
437
- }
438
- // 将RGB颜色转换为rgba格式
439
- const toRgba = (color: string, opacity: number): string => {
440
- if (color.startsWith('#')) {
441
- const rgb = hexToRgb(color)
442
- if (rgb) {
443
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`
444
- }
445
- }
446
- else if (color.startsWith('rgb(')) {
447
- // 处理rgb()格式
448
- const rgbValues = color.match(/\d+/g)
449
- if (rgbValues && rgbValues.length === 3) {
450
- return `rgba(${rgbValues[0]}, ${rgbValues[1]}, ${rgbValues[2]}, ${opacity})`
451
- }
452
- }
453
- // 如果已经是rgba或其他格式,直接返回带透明度的版本
454
- return color.replace(/rgb\(/, 'rgba(').replace(/\)/, `, ${opacity})`)
455
- }
456
-
457
- return toRgba(color, opacity)
458
- }
459
-
460
- /**
461
- * 扩展版本:判断字符串中是否包含多个字符中的任意一个
462
- */
463
- export function containsAnyCharacter(str: string, chars: string[]): boolean {
464
- if (typeof str !== 'string' || !Array.isArray(chars)) {
465
- return false
466
- }
467
-
468
- // 验证数组中的每个元素都是单个字符
469
- for (const char of chars) {
470
- if (typeof char !== 'string' || char.length !== 1) {
471
- throw new Error('数组中的每个元素必须是单个字符')
472
- }
473
- }
474
-
475
- return chars.some(char => str.includes(char))
476
- }
477
- // 测试用例
478
- // console.log(containsAnyCharacter("hello world", ["a", "e", "i", "o", "u"])); // true (包含o)
479
-
480
- /**
481
- * 颜色转换和生成工具类 (针对十六进制颜色优化)
482
- */
483
-
484
- /**
485
- * 十六进制颜色转换为RGB对象
486
- * @param {string} hex - 十六进制颜色值,如 #2164F3 或 2164F3
487
- * @returns {object} RGB对象 {r, g, b}
488
- */
489
- export function hexToRgb(hex: string) {
490
- // 移除 # 号
491
- hex = hex.replace(/^#/, '')
492
-
493
- // 处理简写形式如 #FFF
494
- if (hex.length === 3) {
495
- hex = hex.split('').map(c => c + c).join('')
496
- }
497
-
498
- // 解析十六进制值
499
- const bigint = Number.parseInt(hex, 16)
500
-
501
- return {
502
- r: (bigint >> 16) & 255,
503
- g: (bigint >> 8) & 255,
504
- b: bigint & 255,
505
- }
506
- }
507
-
508
- /**
509
- * RGB对象转换为十六进制颜色
510
- * @param {object} rgb - RGB对象 {r, g, b}
511
- * @returns {string} 十六进制颜色值
512
- */
513
- export function rgbToHex(rgb: { r: any, g: any, b: any }) {
514
- const toHex = (c: { toString: (arg0: number) => any }) => {
515
- const hex = c.toString(16)
516
- return hex.length === 1 ? `0${hex}` : hex
517
- }
518
- return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`.toUpperCase()
519
- }
520
-
521
- /**
522
- * 将 RGB 颜色转换为 HSL 颜色模型
523
- * @param {number} r - 红色值 (0-255)
524
- * @param {number} g - 绿色值 (0-255)
525
- * @param {number} b - 蓝色值 (0-255)
526
- * @returns {object} HSL对象 {h, s, l}
527
- */
528
- export function rgbToHsl(r: number, g: number, b: number) {
529
- r /= 255
530
- g /= 255
531
- b /= 255
532
-
533
- const max = Math.max(r, g, b)
534
- const min = Math.min(r, g, b)
535
- let h
536
- let s
537
- const l = (max + min) / 2
538
-
539
- if (max === min) {
540
- h = s = 0
541
- }
542
- else {
543
- const d = max - min
544
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
545
-
546
- switch (max) {
547
- case r: h = (g - b) / d + (g < b ? 6 : 0)
548
- break
549
- case g: h = (b - r) / d + 2
550
- break
551
- case b: h = (r - g) / d + 4
552
- break
553
- default: h = 0 // 添加默认情况,确保h被初始化
554
- }
555
-
556
- h /= 6
557
- }
558
-
559
- return {
560
- h: Math.round(h * 360),
561
- s: Math.round(s * 100),
562
- l: Math.round(l * 100),
563
- }
564
- }
565
-
566
- /**
567
- * 将 HSL 颜色转换为 RGB 颜色
568
- * @param {number} h - 色相 (0-360)
569
- * @param {number} s - 饱和度 (0-100)
570
- * @param {number} l - 亮度 (0-100)
571
- * @returns {object} RGB对象 {r, g, b}
572
- */
573
- export function hslToRgb(h: number, s: number = 50, l: number = 50): { r: number, g: number, b: number } {
574
- h = h !== undefined ? h / 360 : 0
575
- s = s !== undefined ? s / 100 : 0.5
576
- l = l !== undefined ? l / 100 : 0.5
577
-
578
- let r, g, b
579
-
580
- if (s === 0) {
581
- r = g = b = l
582
- }
583
- else {
584
- const hue2rgb = (p: number, q: number, t: number) => {
585
- if (t < 0)
586
- t += 1
587
- if (t > 1)
588
- t -= 1
589
- if (t < 1 / 6)
590
- return p + (q - p) * 6 * t
591
- if (t < 1 / 2)
592
- return q
593
- if (t < 2 / 3)
594
- return p + (q - p) * (2 / 3 - t) * 6
595
- return p
596
- }
597
-
598
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s
599
- const p = 2 * l - q
600
-
601
- r = hue2rgb(p, q, h + 1 / 3)
602
- g = hue2rgb(p, q, h)
603
- b = hue2rgb(p, q, h - 1 / 3)
604
- }
605
-
606
- return {
607
- r: Math.round(r * 255),
608
- g: Math.round(g * 255),
609
- b: Math.round(b * 255),
610
- }
611
- }
612
-
613
- /**
614
- * 基于主色调生成随机颜色
615
- * @param {string} baseHex - 基础十六进制颜色,如 #2164F3
616
- * @param {object} options - 配置选项
617
- * @returns {object} 颜色对象
618
- */
619
- export function generateRandomColorFromHex(baseHex: string, options = { hueRange: 15, saturationRange: 20, lightnessRange: 20 }) {
620
- const {
621
- hueRange = 15,
622
- saturationRange = 20,
623
- lightnessRange = 20,
624
- } = options
625
-
626
- // 转换为RGB
627
- const baseRgb = hexToRgb(baseHex)
628
-
629
- // 转换为HSL
630
- const baseHsl = rgbToHsl(baseRgb.r, baseRgb.g, baseRgb.b)
631
-
632
- // 在指定范围内随机调整HSL值
633
- const randomH = baseHsl.h + (Math.random() * 2 - 1) * hueRange
634
- const randomS = baseHsl.s + (Math.random() * 2 - 1) * saturationRange
635
- const randomL = baseHsl.l + (Math.random() * 2 - 1) * lightnessRange
636
-
637
- // 确保值在有效范围内
638
- const h = Math.max(0, Math.min(360, randomH))
639
- const s = Math.max(0, Math.min(100, randomS))
640
- const l = Math.max(0, Math.min(100, randomL))
641
-
642
- // 转换回RGB
643
- const rgb = hslToRgb(h, s, l)
644
- const hex = rgbToHex(rgb)
645
-
646
- return {
647
- hex,
648
- rgb,
649
- hsl: { h: Math.round(h), s: Math.round(s), l: Math.round(l) },
650
- }
651
- }
652
-
653
- /**
654
- * 批量生成协调的颜色
655
- * @param {string} baseHex - 基础十六进制颜色
656
- * @param {number} count - 需要生成的颜色数量
657
- * @param {object} options - 配置选项
658
- * @returns {Array} 颜色数组
659
- */
660
- export function generateHarmoniousColors(baseHex: string, count: number, options = { hueRange: 15, saturationRange: 20, lightnessRange: 20, colorType: 'all' }) {
661
- const colors = []
662
- const baseRgb = hexToRgb(baseHex)
663
- const baseHsl = rgbToHsl(baseRgb.r, baseRgb.g, baseRgb.b)
664
-
665
- const {
666
- hueRange = 15,
667
- saturationRange = 20,
668
- lightnessRange = 20,
669
- // 新增:颜色类型,可选值:'all', 'light', 'dark', 'muted'
670
- colorType = 'all',
671
- } = options
672
-
673
- // 使用let声明,允许重新赋值
674
- let adjustedSaturationRange = saturationRange
675
-
676
- // 根据颜色类型调整基础亮度
677
- let baseLightness = baseHsl.l
678
- if (colorType === 'light') {
679
- baseLightness = Math.min(80, baseHsl.l + 20)
680
- }
681
- else if (colorType === 'dark') {
682
- baseLightness = Math.max(20, baseHsl.l - 20)
683
- }
684
- else if (colorType === 'muted') {
685
- adjustedSaturationRange = Math.min(adjustedSaturationRange, 15)
686
- }
687
-
688
- for (let i = 0; i < count; i++) {
689
- // 在指定范围内随机调整HSL值
690
- const randomH = baseHsl.h + (Math.random() * 2 - 1) * hueRange
691
- const randomS = baseHsl.s + (Math.random() * 2 - 1) * adjustedSaturationRange
692
- const randomL = baseLightness + (Math.random() * 2 - 1) * lightnessRange
693
-
694
- // 确保值在有效范围内
695
- const h = Math.max(0, Math.min(360, randomH))
696
- const s = Math.max(0, Math.min(100, randomS))
697
- const l = Math.max(0, Math.min(100, randomL))
698
-
699
- // 转换回RGB
700
- const rgb = hslToRgb(h, s, l)
701
- const hex = rgbToHex(rgb)
702
-
703
- colors.push({
704
- hex,
705
- rgb,
706
- hsl: { h: Math.round(h), s: Math.round(s), l: Math.round(l) },
707
- rgbString: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
708
- })
709
- }
710
-
711
- return colors
712
- }
713
-
714
- /**
715
- * 生成一组完美的图标颜色(智能算法)
716
- * @param {string} baseHex - 基础颜色
717
- * @param {number} count - 图标数量
718
- * @returns {Array} 优化后的颜色数组
719
- */
720
- export function generateIconColors(baseHex: string, count: number) {
721
- const baseRgb = hexToRgb(baseHex)
722
- const baseHsl = rgbToHsl(baseRgb.r, baseRgb.g, baseRgb.b)
723
- const colors = []
724
-
725
- // 根据数量调整色相分布
726
- const hueStep = 360 / (count + 1)
727
-
728
- for (let i = 0; i < count; i++) {
729
- // 创建更均匀的色相分布
730
- let hue = (baseHsl.h + (i * hueStep)) % 360
731
-
732
- // 保持主色调附近有一些颜色
733
- if (i < 3) {
734
- hue = baseHsl.h + (i - 1) * 10
735
- }
736
-
737
- // 智能调整饱和度和亮度
738
- // 重要图标更鲜艳,次要图标更柔和
739
- const saturation = Math.max(60, Math.min(90, baseHsl.s + (Math.random() * 20 - 10),
740
- ))
741
-
742
- // 创建对比度:一些更亮,一些更暗
743
- const lightness = i % 2 === 0
744
- ? Math.min(70, baseHsl.l + 15 + Math.random() * 10)
745
- : Math.max(30, baseHsl.l - 10 + Math.random() * 10)
746
-
747
- const rgb = hslToRgb(hue, saturation, lightness)
748
- const hex = rgbToHex(rgb)
749
-
750
- colors.push({
751
- hex,
752
- rgb,
753
- hsl: { h: Math.round(hue), s: Math.round(saturation), l: Math.round(lightness) },
754
- rgbString: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
755
- })
756
- }
757
-
758
- return colors
759
- }
760
-
761
- /**
762
- * 检查颜色对比度是否足够(用于文本可读性)
763
- * @param {string} hexColor - 十六进制颜色
764
- * @param {string} backgroundColor - 背景十六进制颜色,默认白色
765
- * @returns {number} 对比度比率
766
- */
767
- export function getContrastRatio(hexColor: string, backgroundColor = '#FFFFFF') {
768
- const rgb1 = hexToRgb(hexColor)
769
- const rgb2 = hexToRgb(backgroundColor)
770
-
771
- const luminance1 = getLuminance(rgb1)
772
- const luminance2 = getLuminance(rgb2)
773
-
774
- const lighter = Math.max(luminance1, luminance2)
775
- const darker = Math.min(luminance1, luminance2)
776
-
777
- return (lighter + 0.05) / (darker + 0.05)
778
- }
779
-
780
- /**
781
- * 计算颜色亮度
782
- * @param {object} rgb - RGB对象
783
- * @returns {number} 亮度值
784
- */
785
- function getLuminance(rgb: { r: number, g: number, b: number }) {
786
- const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((v) => {
787
- v /= 255
788
- return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4
789
- })
790
-
791
- return 0.2126 * r + 0.7152 * g + 0.0722 * b
792
- }
793
-
794
- /**
795
- * 生成颜色变体(浅色、深色等)
796
- * @param {string} hex - 基础颜色
797
- * @param {string} variant - 变体类型:'lighter', 'light', 'dark', 'darker', 'muted'
798
- * @returns {string} 变体颜色的十六进制值
799
- */
800
- export function getColorVariant(hex: string, variant = 'light') {
801
- const rgb = hexToRgb(hex)
802
- const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
803
-
804
- switch (variant) {
805
- case 'lighter':
806
- hsl.l = Math.min(95, hsl.l + 40)
807
- hsl.s = Math.max(30, hsl.s - 10)
808
- break
809
- case 'light':
810
- hsl.l = Math.min(85, hsl.l + 30)
811
- hsl.s = Math.max(40, hsl.s - 15)
812
- break
813
- case 'dark':
814
- hsl.l = Math.max(20, hsl.l - 20)
815
- hsl.s = Math.min(80, hsl.s + 10)
816
- break
817
- case 'darker':
818
- hsl.l = Math.max(10, hsl.l - 30)
819
- hsl.s = Math.min(70, hsl.s + 20)
820
- break
821
- case 'muted':
822
- hsl.s = Math.max(20, hsl.s - 30)
823
- break
824
- }
825
-
826
- const variantRgb = hslToRgb(hsl.h, hsl.s, hsl.l)
827
- return rgbToHex(variantRgb)
828
- }
829
-
830
- /**
831
- *
832
- *
833
- * 获取字符串的第一个字符
834
- * @param {string} str - 输入的字符串
835
- * @returns {string} 字符串的第一个字符
836
- */
837
- export function getFirstChar(str: string) {
838
- return str.charAt(0)
839
- }
840
-
841
- // 生成七彩虹范围内的任意颜色
842
- /**
843
- * 七彩虹颜色范围(HSL表示)
844
- * 每个颜色范围:色相(H)范围,饱和度(S)固定,亮度(L)固定
845
- * 选择较高的饱和度(70-80%)和中等亮度(45-55%)以确保白色文字可见
846
- */
847
- const COLOR_RANGES = [
848
- { name: '红', hueRange: [0, 10], saturation: 75, lightness: 50 },
849
- { name: '橙', hueRange: [20, 40], saturation: 75, lightness: 50 },
850
- { name: '黄', hueRange: [45, 60], saturation: 75, lightness: 45 }, // 黄色稍微暗一点以保证白色文字可见
851
- { name: '绿', hueRange: [90, 140], saturation: 70, lightness: 45 },
852
- { name: '青', hueRange: [160, 190], saturation: 70, lightness: 50 },
853
- { name: '蓝', hueRange: [200, 240], saturation: 75, lightness: 45 },
854
- { name: '紫', hueRange: [260, 300], saturation: 70, lightness: 50 },
855
- { name: '粉', hueRange: [310, 340], saturation: 70, lightness: 55 },
856
- ] as const
857
-
858
- type ColorRange = typeof COLOR_RANGES[number]
859
-
860
- /**
861
- * 将HSL转换为十六进制颜色
862
- */
863
- function hslToHex(h: number, s: number, l: number): string {
864
- l /= 100
865
- const a = s * Math.min(l, 1 - l) / 100
866
- const f = (n: number) => {
867
- const k = (n + h / 30) % 12
868
- const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
869
- return Math.round(255 * color).toString(16).padStart(2, '0')
870
- }
871
- return `#${f(0)}${f(8)}${f(4)}`
872
- }
873
-
874
- /**
875
- * 检查颜色是否为暗色系
876
- * 通过HSL的亮度判断,亮度低于30%的被认为是暗色
877
- */
878
- function isDarkColor(h: number, s: number, l: number): boolean {
879
- return l < 30
880
- }
881
-
882
- /**
883
- * 七彩虹颜色生成器类
884
- */
885
- export class RainbowColorGenerator {
886
- private lastColorRange: ColorRange | null = null
887
- private lastColorHex: string | null = null
888
-
889
- /**
890
- * 生成随机颜色值
891
- */
892
- private generateRandomColorFromRange(range: ColorRange): string {
893
- const { hueRange, saturation, lightness } = range
894
- const hue = Math.floor(Math.random() * (hueRange[1] - hueRange[0] + 1)) + hueRange[0]
895
-
896
- // 确保颜色不是暗色系
897
- if (isDarkColor(hue, saturation, lightness)) {
898
- // 如果亮度太低,增加亮度到安全范围
899
- return hslToHex(hue, saturation, Math.max(lightness, 40))
900
- }
901
-
902
- return hslToHex(hue, saturation, lightness)
903
- }
904
-
905
- /**
906
- * 检查两个颜色是否属于同一个色系
907
- */
908
- private isSameColorRange(range1: ColorRange, range2: ColorRange): boolean {
909
- return range1.name === range2.name
910
- }
911
-
912
- /**
913
- * 获取可用的颜色范围(排除上一个使用的颜色范围)
914
- */
915
- private getAvailableRanges(): ColorRange[] {
916
- if (!this.lastColorRange) {
917
- return [...COLOR_RANGES]
918
- }
919
- return COLOR_RANGES.filter(range => !this.isSameColorRange(range, this.lastColorRange!))
920
- }
921
-
922
- /**
923
- * 获取下一个随机颜色
924
- * @returns 十六进制颜色字符串
925
- */
926
- getNextColor(): string {
927
- const availableRanges = this.getAvailableRanges()
928
-
929
- // 随机选择一个颜色范围
930
- const randomIndex = Math.floor(Math.random() * availableRanges.length)
931
- const selectedRange = availableRanges[randomIndex]
932
-
933
- // 生成颜色
934
- const color = this.generateRandomColorFromRange(selectedRange)
935
-
936
- // 更新上一个颜色
937
- this.lastColorRange = selectedRange
938
- this.lastColorHex = color
939
-
940
- return color
941
- }
942
-
943
- /**
944
- * 重置生成器状态
945
- */
946
- reset(): void {
947
- this.lastColorRange = null
948
- this.lastColorHex = null
949
- }
950
-
951
- /**
952
- * 获取上一个生成的颜色
953
- */
954
- getLastColor(): string | null {
955
- return this.lastColorHex
956
- }
957
- }
958
- /**
959
- * 格式化手机号
960
- * @param phone 手机号字符串
961
- * @param mask 隐藏码,默认为****
962
- * @returns 格式化后的手机号字符串
963
- */
964
- export function formatPhone(phone: string, mask = '****') {
965
- if (!phone) return '';
966
-
967
- const cleanPhone = phone.replace(/\D/g, '');
968
- const len = cleanPhone.length;
969
-
970
- // 根据号码长度决定显示方式
971
- if (len === 11) {
972
- return cleanPhone.slice(0, 3) + mask + cleanPhone.slice(-4);
973
- } else if (len === 8) {
974
- // 座机号码等短号码
975
- return cleanPhone.slice(0, 2) + mask + cleanPhone.slice(-2);
976
- }
977
-
978
- return phone;
979
- }
1
+ import type { Comparator } from '../type'
2
+
3
+ // const filefxd = ['image', 'video', 'audio', 'file', 'wav']
4
+ const baseUrl = uni.getStorageSync('BASE_URL')
5
+ const token = uni.getStorageSync('TOKEN')
6
+ const hydrocarbonProgramToken = uni.getStorageSync('HYDROCARBON_PROGRAM_TOKEN')
7
+
8
+ /**
9
+ * 获取当前页面路径
10
+ * @returns 当前页面路径
11
+ */
12
+ export function getCurrentPath() {
13
+ const pages = getCurrentPages()
14
+ const currentPage = pages[pages.length - 1]
15
+ return currentPage.route || ''
16
+ }
17
+
18
+ /**
19
+ * 格式化列表项展示数据
20
+ * @param data 数据值
21
+ * @param type 数据类型
22
+ * @returns 格式化后的数据
23
+ */
24
+ export function formatItemData(data: any, type: string) {
25
+ // console.log('[Mock] formatItemData', data, type)
26
+ if (type === 'relselect' || type === 'tree-entity-select' || type === 'table-entity-select') {
27
+ // 判断data是否为数组
28
+ if (Array.isArray(data)) {
29
+ const dataArr = data.map(item => item.split('@R@')[1]).join(',')
30
+ return dataArr
31
+ }
32
+ // 字符串中是否包含@,@
33
+ if (typeof data === 'string' && data.includes('@,@')) {
34
+ return data.split('@,@').map((item: string) => item.split('@R@')[1]).join(',')
35
+ }
36
+
37
+ // 字符串中是否包含@R@
38
+ if (typeof data === 'string' && data.includes('@R@')) {
39
+ return data.split('@R@')[1]
40
+ }
41
+
42
+ return data || ''
43
+ }
44
+
45
+ if (type === 'datetime') {
46
+ return data ? data.split('.')[0] : ''
47
+ }
48
+
49
+ if (type === 'file' || type === 'relfile' || type === 'video') {
50
+ if (data) {
51
+ // 判断是否为JSON文件
52
+ if (typeof data === 'string') {
53
+ const a = JSON.parse(data)
54
+ return [{
55
+ url: `${baseUrl}/v3/files${a.base.path}?@token=${token}&@programToken=${hydrocarbonProgramToken}&name=${a.base.fileName || ''}`,
56
+ name: a.base.fileName,
57
+ type: a.base.type,
58
+ }]
59
+ }
60
+ else if (Array.isArray(data)) {
61
+ return data.map((item) => {
62
+ if (item.includes('@R@')) {
63
+ const a = JSON.parse(item.split('@R@')[1])
64
+ return {
65
+ url: `${baseUrl}/v3/files${a.base.path}?@token=${token}&@programToken=${hydrocarbonProgramToken}&name=${a.base.fileName || ''}`,
66
+ name: a.base.fileName,
67
+ type: a.base.type,
68
+ }
69
+ }
70
+ else {
71
+ const a = JSON.parse(item)
72
+ return {
73
+ url: `${baseUrl}/v3/files${a.base.path}?@token=${token}&@programToken=${hydrocarbonProgramToken}&name=${a.base.fileName || ''}`,
74
+ name: a.base.fileName,
75
+ type: a.base.type,
76
+ }
77
+ }
78
+ })
79
+ }
80
+ else {
81
+ return [{
82
+ url: data.url,
83
+ name: data.name,
84
+ type: getAfterLastMatch(data.name, '.') || getAfterLastMatch(data.url, '.'),
85
+ }]
86
+ }
87
+ }
88
+ return [{
89
+ url: 'https://',
90
+ name: '',
91
+ type: 'img',
92
+ }]
93
+ }
94
+
95
+ return data
96
+ }
97
+
98
+ /**
99
+ * 高并发环境下也能保证唯一的ID生成器
100
+ * @returns 25字符的唯一字符串
101
+ */
102
+ let counter = 0
103
+ export function generateHighResolutionID(): string {
104
+ const time = Date.now().toString(36)
105
+ const random = Math.random().toString(36).substring(2, 10)
106
+ counter = (counter + 1) % Number.MAX_SAFE_INTEGER
107
+ return `${time}-${random}-${counter.toString(36).padStart(6, '0')}`
108
+ }
109
+
110
+ // 下载文件
111
+ export function downloadFile(url: string) {
112
+ console.log('下载', url)
113
+ uni.downloadFile({
114
+ url, // 文件的网络地址
115
+ success: (res) => {
116
+ if (res.statusCode === 200) {
117
+ console.log('下载成功', res.tempFilePath)
118
+ // 打开文件(如PDF、Word等)
119
+ uni.openDocument({
120
+ filePath: res.tempFilePath,
121
+ showMenu: true,
122
+ success: () => console.log('打开文件成功'),
123
+ })
124
+ }
125
+ else {
126
+ console.error('下载失败', res)
127
+ uni.showToast({
128
+ title: '下载失败',
129
+ icon: 'none',
130
+ })
131
+ }
132
+ },
133
+ fail: (err) => {
134
+ console.error('下载失败', err)
135
+ uni.showToast({
136
+ title: '下载失败',
137
+ icon: 'none',
138
+ })
139
+ },
140
+ })
141
+ }
142
+
143
+ /**
144
+ * 字符串截取匹配最后一项(不包含匹配字符)
145
+ *
146
+ *
147
+ */
148
+ function getAfterLastMatch(str: string, pattern: string) {
149
+ const lastIndex = str.lastIndexOf(pattern)
150
+ if (lastIndex === -1)
151
+ return null
152
+ return str.substring(lastIndex + pattern.length)
153
+ }
154
+
155
+ /****
156
+ *
157
+ * 根据comparator、defaultValue、val
158
+ * 判断按钮是否满足显示
159
+ *
160
+ *
161
+ */
162
+ export function comparator(
163
+ comparator: Comparator,
164
+ val: string,
165
+ defaultValue: string,
166
+ ): boolean {
167
+ let dvs: string[]
168
+ let value = val
169
+ if (val && val.indexOf && val.indexOf('@R@') > 0) {
170
+ const vs = val.split('@R@')
171
+ value = vs[1]
172
+ }
173
+
174
+ if (!comparator) {
175
+ comparator = 'equals'
176
+ }
177
+
178
+ if (comparator === 'none1') {
179
+ return !value || value === ''
180
+ }
181
+ else if (comparator === 'none1n') {
182
+ return !!value && value !== ''
183
+ }
184
+
185
+ if (!defaultValue) {
186
+ return true
187
+ }
188
+
189
+ if (!value && value !== '0') {
190
+ return false
191
+ }
192
+ let vals
193
+ switch (comparator) {
194
+ case 'contain':
195
+ return value.includes(defaultValue)
196
+ case 'equal':
197
+ case 'equals':
198
+ return isEqual(value, defaultValue)
199
+ case 'exclude':
200
+ dvs = toArray(defaultValue)
201
+ vals = toArray(value)
202
+ for (let i = 0; i < dvs.length; i++) {
203
+ for (let j = 0; j < vals.length; j++) {
204
+ if (dvs[i] === vals[j]) {
205
+ return false
206
+ }
207
+ }
208
+ }
209
+ return true
210
+ case 'includeOne':
211
+ case 'include_anyone':
212
+ dvs = toArray(defaultValue)
213
+ vals = toArray(value)
214
+ for (let i = 0; i < dvs.length; i++) {
215
+ for (let j = 0; j < vals.length; j++) {
216
+ if (dvs[i] === vals[j]) {
217
+ return true
218
+ }
219
+ }
220
+ }
221
+ return false
222
+
223
+ case 'include_all':
224
+ {
225
+ dvs = toArray(defaultValue)
226
+ vals = toArray(value)
227
+ let count = 0
228
+ const targetCount = dvs.length
229
+ for (let i = 0; i < dvs.length; i++) {
230
+ for (let j = 0; j < vals.length; j++) {
231
+ if (dvs[i] === vals[j]) {
232
+ count++
233
+ break
234
+ }
235
+ }
236
+ }
237
+ return targetCount <= count
238
+ }
239
+ case 'Greater':
240
+ case 'greater':
241
+ if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
242
+ return Number(value) > Number(defaultValue)
243
+ }
244
+ else {
245
+ return value > defaultValue
246
+ }
247
+ case 'Less':
248
+ case 'less':
249
+ if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
250
+ return Number(value) < Number(defaultValue)
251
+ }
252
+ else {
253
+ return value < defaultValue
254
+ }
255
+ case 'noGreater':
256
+ case 'nogreater':
257
+ if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
258
+ return Number(value) <= Number(defaultValue)
259
+ }
260
+ else {
261
+ return value <= defaultValue
262
+ }
263
+ case 'noLess':
264
+ case 'noless':
265
+ if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
266
+ return Number(value) >= Number(defaultValue)
267
+ }
268
+ else {
269
+ return value >= defaultValue
270
+ }
271
+ case 'unequal':
272
+ case 'unequals':
273
+ return !isEqual(value, defaultValue)
274
+ default:
275
+ console.log(`暂不支持的比较关系:${comparator};请联系管理员添加。`)
276
+ return false // 兜底返回值
277
+ }
278
+ }
279
+
280
+ function isEqual(value: string, defaultValue: string) {
281
+ if (defaultValue === '是' || defaultValue === '否' || defaultValue === 'true' || defaultValue === 'false') {
282
+ if (value === 'true' || value === '是') {
283
+ if (defaultValue === '是' || defaultValue === 'true') {
284
+ return true
285
+ }
286
+ else {
287
+ return false
288
+ }
289
+ }
290
+ else if (value === 'false' || value === '否') {
291
+ if (defaultValue === '否' || defaultValue === 'false') {
292
+ return true
293
+ }
294
+ else {
295
+ return false
296
+ }
297
+ }
298
+ else {
299
+ return false
300
+ }
301
+ }
302
+ else {
303
+ if (!Number.isNaN(Number(value)) && !Number.isNaN(Number(defaultValue))) {
304
+ return Number(value) === Number(defaultValue)
305
+ }
306
+ else {
307
+ return value === defaultValue
308
+ }
309
+ }
310
+ }
311
+
312
+ function toArray(value: string) {
313
+ let val
314
+ if (typeof value == 'string') {
315
+ val = value.split(',')
316
+ }
317
+ else if (Array.isArray(value)) {
318
+ val = value
319
+ }
320
+ else {
321
+ val = [value]
322
+ }
323
+ return val
324
+ }
325
+
326
+ /**
327
+ * 根据目标颜色生成渐变色字符串
328
+ * @param color - 目标颜色,可以是十六进制颜色或RGB颜色
329
+ * @param angle - 渐变角度,默认为180deg
330
+ * @param startOpacity - 起始透明度,默认为1(不透明)
331
+ * @param endOpacity - 结束透明度,默认为0.3
332
+ * @returns 渐变色字符串
333
+ */
334
+ export function generateGradientColor(
335
+ color: string,
336
+ angle: number = 180,
337
+ startOpacity: number = 1,
338
+ endOpacity: number = 0.3,
339
+ ): string {
340
+ // 处理十六进制颜色转换为RGB
341
+ const hexToRgb = (hex: string): { r: number, g: number, b: number } | null => {
342
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
343
+ return result
344
+ ? {
345
+ r: Number.parseInt(result[1], 16),
346
+ g: Number.parseInt(result[2], 16),
347
+ b: Number.parseInt(result[3], 16),
348
+ }
349
+ : null
350
+ }
351
+
352
+ // 将RGB颜色转换为rgba格式
353
+ const toRgba = (color: string, opacity: number): string => {
354
+ if (color.startsWith('#')) {
355
+ const rgb = hexToRgb(color)
356
+ if (rgb) {
357
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`
358
+ }
359
+ }
360
+ else if (color.startsWith('rgb(')) {
361
+ // 处理rgb()格式
362
+ const rgbValues = color.match(/\d+/g)
363
+ if (rgbValues && rgbValues.length === 3) {
364
+ return `rgba(${rgbValues[0]}, ${rgbValues[1]}, ${rgbValues[2]}, ${opacity})`
365
+ }
366
+ }
367
+ // 如果已经是rgba或其他格式,直接返回带透明度的版本
368
+ return color.replace(/rgb\(/, 'rgba(').replace(/\)/, `, ${opacity})`)
369
+ }
370
+
371
+ // 生成渐变色字符串
372
+ const startColor = color.startsWith('rgba') ? color : toRgba(color, startOpacity)
373
+ const endColor = toRgba(color, endOpacity)
374
+
375
+ return `linear-gradient(${angle}deg, ${startColor} 0%, ${endColor} 100%)`
376
+ }
377
+
378
+ // 使用示例
379
+ // console.log(generateGradientColor('#2094FF'));
380
+ // 输出: linear-gradient(180deg, rgba(32,148,255,1) 0%, rgba(32,148,255,0.3) 100%)
381
+
382
+ // console.log(generateGradientColor('#2094FF', 90, 0.8, 0.1));
383
+ // 输出: linear-gradient(90deg, rgba(32,148,255,0.8) 0%, rgba(32,148,255,0.1) 100%)
384
+
385
+ /**
386
+ * 格式化日期显示
387
+ * @param date - 要格式化的日期,默认为当前日期
388
+ * @returns 格式化后的日期字符串,如"2025年9月25日 周四 下午"
389
+ */
390
+ export function formatChineseDate(date: Date = new Date()): string {
391
+ // 年份
392
+ const year = date.getFullYear()
393
+
394
+ // 月份(月份从0开始,需要+1)
395
+ const month = date.getMonth() + 1
396
+
397
+ // 日期
398
+ const day = date.getDate()
399
+
400
+ // 星期几
401
+ const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
402
+ const weekday = weekdays[date.getDay()]
403
+
404
+ // 上午/下午判断
405
+ const hour = date.getHours()
406
+ const period = hour < 12 ? '上午' : '下午'
407
+
408
+ return `${year}年${month}月${day}日 ${weekday} ${period}`
409
+ }
410
+
411
+ // 使用示例
412
+ // console.log(formatChineseDate(new Date(2025, 8, 25)));
413
+ // 输出: 2025年9月25日 周四 下午
414
+
415
+ // console.log(formatChineseDate(new Date(2025, 8, 25, 10, 30)));
416
+ // 输出: 2025年9月25日 周四 上午
417
+
418
+ // console.log(formatChineseDate());
419
+ // 输出当前日期的格式化结果
420
+
421
+ /**
422
+ * 淡化颜色
423
+ * @param color - 目标颜色,可以是十六进制颜色或RGB颜色
424
+ * @returns 淡化后的颜色字符串
425
+ */
426
+ export function desaturateColor(color: string, opacity: number = 0.04): string {
427
+ // 处理十六进制颜色转换为RGB
428
+ const hexToRgb = (hex: string): { r: number, g: number, b: number } | null => {
429
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
430
+ return result
431
+ ? {
432
+ r: Number.parseInt(result[1], 16),
433
+ g: Number.parseInt(result[2], 16),
434
+ b: Number.parseInt(result[3], 16),
435
+ }
436
+ : null
437
+ }
438
+ // 将RGB颜色转换为rgba格式
439
+ const toRgba = (color: string, opacity: number): string => {
440
+ if (color.startsWith('#')) {
441
+ const rgb = hexToRgb(color)
442
+ if (rgb) {
443
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`
444
+ }
445
+ }
446
+ else if (color.startsWith('rgb(')) {
447
+ // 处理rgb()格式
448
+ const rgbValues = color.match(/\d+/g)
449
+ if (rgbValues && rgbValues.length === 3) {
450
+ return `rgba(${rgbValues[0]}, ${rgbValues[1]}, ${rgbValues[2]}, ${opacity})`
451
+ }
452
+ }
453
+ // 如果已经是rgba或其他格式,直接返回带透明度的版本
454
+ return color.replace(/rgb\(/, 'rgba(').replace(/\)/, `, ${opacity})`)
455
+ }
456
+
457
+ return toRgba(color, opacity)
458
+ }
459
+
460
+ /**
461
+ * 扩展版本:判断字符串中是否包含多个字符中的任意一个
462
+ */
463
+ export function containsAnyCharacter(str: string, chars: string[]): boolean {
464
+ if (typeof str !== 'string' || !Array.isArray(chars)) {
465
+ return false
466
+ }
467
+
468
+ // 验证数组中的每个元素都是单个字符
469
+ for (const char of chars) {
470
+ if (typeof char !== 'string' || char.length !== 1) {
471
+ throw new Error('数组中的每个元素必须是单个字符')
472
+ }
473
+ }
474
+
475
+ return chars.some(char => str.includes(char))
476
+ }
477
+ // 测试用例
478
+ // console.log(containsAnyCharacter("hello world", ["a", "e", "i", "o", "u"])); // true (包含o)
479
+
480
+ /**
481
+ * 颜色转换和生成工具类 (针对十六进制颜色优化)
482
+ */
483
+
484
+ /**
485
+ * 十六进制颜色转换为RGB对象
486
+ * @param {string} hex - 十六进制颜色值,如 #2164F3 或 2164F3
487
+ * @returns {object} RGB对象 {r, g, b}
488
+ */
489
+ export function hexToRgb(hex: string) {
490
+ // 移除 # 号
491
+ hex = hex.replace(/^#/, '')
492
+
493
+ // 处理简写形式如 #FFF
494
+ if (hex.length === 3) {
495
+ hex = hex.split('').map(c => c + c).join('')
496
+ }
497
+
498
+ // 解析十六进制值
499
+ const bigint = Number.parseInt(hex, 16)
500
+
501
+ return {
502
+ r: (bigint >> 16) & 255,
503
+ g: (bigint >> 8) & 255,
504
+ b: bigint & 255,
505
+ }
506
+ }
507
+
508
+ /**
509
+ * RGB对象转换为十六进制颜色
510
+ * @param {object} rgb - RGB对象 {r, g, b}
511
+ * @returns {string} 十六进制颜色值
512
+ */
513
+ export function rgbToHex(rgb: { r: any, g: any, b: any }) {
514
+ const toHex = (c: { toString: (arg0: number) => any }) => {
515
+ const hex = c.toString(16)
516
+ return hex.length === 1 ? `0${hex}` : hex
517
+ }
518
+ return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`.toUpperCase()
519
+ }
520
+
521
+ /**
522
+ * 将 RGB 颜色转换为 HSL 颜色模型
523
+ * @param {number} r - 红色值 (0-255)
524
+ * @param {number} g - 绿色值 (0-255)
525
+ * @param {number} b - 蓝色值 (0-255)
526
+ * @returns {object} HSL对象 {h, s, l}
527
+ */
528
+ export function rgbToHsl(r: number, g: number, b: number) {
529
+ r /= 255
530
+ g /= 255
531
+ b /= 255
532
+
533
+ const max = Math.max(r, g, b)
534
+ const min = Math.min(r, g, b)
535
+ let h
536
+ let s
537
+ const l = (max + min) / 2
538
+
539
+ if (max === min) {
540
+ h = s = 0
541
+ }
542
+ else {
543
+ const d = max - min
544
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
545
+
546
+ switch (max) {
547
+ case r: h = (g - b) / d + (g < b ? 6 : 0)
548
+ break
549
+ case g: h = (b - r) / d + 2
550
+ break
551
+ case b: h = (r - g) / d + 4
552
+ break
553
+ default: h = 0 // 添加默认情况,确保h被初始化
554
+ }
555
+
556
+ h /= 6
557
+ }
558
+
559
+ return {
560
+ h: Math.round(h * 360),
561
+ s: Math.round(s * 100),
562
+ l: Math.round(l * 100),
563
+ }
564
+ }
565
+
566
+ /**
567
+ * 将 HSL 颜色转换为 RGB 颜色
568
+ * @param {number} h - 色相 (0-360)
569
+ * @param {number} s - 饱和度 (0-100)
570
+ * @param {number} l - 亮度 (0-100)
571
+ * @returns {object} RGB对象 {r, g, b}
572
+ */
573
+ export function hslToRgb(h: number, s: number = 50, l: number = 50): { r: number, g: number, b: number } {
574
+ h = h !== undefined ? h / 360 : 0
575
+ s = s !== undefined ? s / 100 : 0.5
576
+ l = l !== undefined ? l / 100 : 0.5
577
+
578
+ let r, g, b
579
+
580
+ if (s === 0) {
581
+ r = g = b = l
582
+ }
583
+ else {
584
+ const hue2rgb = (p: number, q: number, t: number) => {
585
+ if (t < 0)
586
+ t += 1
587
+ if (t > 1)
588
+ t -= 1
589
+ if (t < 1 / 6)
590
+ return p + (q - p) * 6 * t
591
+ if (t < 1 / 2)
592
+ return q
593
+ if (t < 2 / 3)
594
+ return p + (q - p) * (2 / 3 - t) * 6
595
+ return p
596
+ }
597
+
598
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s
599
+ const p = 2 * l - q
600
+
601
+ r = hue2rgb(p, q, h + 1 / 3)
602
+ g = hue2rgb(p, q, h)
603
+ b = hue2rgb(p, q, h - 1 / 3)
604
+ }
605
+
606
+ return {
607
+ r: Math.round(r * 255),
608
+ g: Math.round(g * 255),
609
+ b: Math.round(b * 255),
610
+ }
611
+ }
612
+
613
+ /**
614
+ * 基于主色调生成随机颜色
615
+ * @param {string} baseHex - 基础十六进制颜色,如 #2164F3
616
+ * @param {object} options - 配置选项
617
+ * @returns {object} 颜色对象
618
+ */
619
+ export function generateRandomColorFromHex(baseHex: string, options = { hueRange: 15, saturationRange: 20, lightnessRange: 20 }) {
620
+ const {
621
+ hueRange = 15,
622
+ saturationRange = 20,
623
+ lightnessRange = 20,
624
+ } = options
625
+
626
+ // 转换为RGB
627
+ const baseRgb = hexToRgb(baseHex)
628
+
629
+ // 转换为HSL
630
+ const baseHsl = rgbToHsl(baseRgb.r, baseRgb.g, baseRgb.b)
631
+
632
+ // 在指定范围内随机调整HSL值
633
+ const randomH = baseHsl.h + (Math.random() * 2 - 1) * hueRange
634
+ const randomS = baseHsl.s + (Math.random() * 2 - 1) * saturationRange
635
+ const randomL = baseHsl.l + (Math.random() * 2 - 1) * lightnessRange
636
+
637
+ // 确保值在有效范围内
638
+ const h = Math.max(0, Math.min(360, randomH))
639
+ const s = Math.max(0, Math.min(100, randomS))
640
+ const l = Math.max(0, Math.min(100, randomL))
641
+
642
+ // 转换回RGB
643
+ const rgb = hslToRgb(h, s, l)
644
+ const hex = rgbToHex(rgb)
645
+
646
+ return {
647
+ hex,
648
+ rgb,
649
+ hsl: { h: Math.round(h), s: Math.round(s), l: Math.round(l) },
650
+ }
651
+ }
652
+
653
+ /**
654
+ * 批量生成协调的颜色
655
+ * @param {string} baseHex - 基础十六进制颜色
656
+ * @param {number} count - 需要生成的颜色数量
657
+ * @param {object} options - 配置选项
658
+ * @returns {Array} 颜色数组
659
+ */
660
+ export function generateHarmoniousColors(baseHex: string, count: number, options = { hueRange: 15, saturationRange: 20, lightnessRange: 20, colorType: 'all' }) {
661
+ const colors = []
662
+ const baseRgb = hexToRgb(baseHex)
663
+ const baseHsl = rgbToHsl(baseRgb.r, baseRgb.g, baseRgb.b)
664
+
665
+ const {
666
+ hueRange = 15,
667
+ saturationRange = 20,
668
+ lightnessRange = 20,
669
+ // 新增:颜色类型,可选值:'all', 'light', 'dark', 'muted'
670
+ colorType = 'all',
671
+ } = options
672
+
673
+ // 使用let声明,允许重新赋值
674
+ let adjustedSaturationRange = saturationRange
675
+
676
+ // 根据颜色类型调整基础亮度
677
+ let baseLightness = baseHsl.l
678
+ if (colorType === 'light') {
679
+ baseLightness = Math.min(80, baseHsl.l + 20)
680
+ }
681
+ else if (colorType === 'dark') {
682
+ baseLightness = Math.max(20, baseHsl.l - 20)
683
+ }
684
+ else if (colorType === 'muted') {
685
+ adjustedSaturationRange = Math.min(adjustedSaturationRange, 15)
686
+ }
687
+
688
+ for (let i = 0; i < count; i++) {
689
+ // 在指定范围内随机调整HSL值
690
+ const randomH = baseHsl.h + (Math.random() * 2 - 1) * hueRange
691
+ const randomS = baseHsl.s + (Math.random() * 2 - 1) * adjustedSaturationRange
692
+ const randomL = baseLightness + (Math.random() * 2 - 1) * lightnessRange
693
+
694
+ // 确保值在有效范围内
695
+ const h = Math.max(0, Math.min(360, randomH))
696
+ const s = Math.max(0, Math.min(100, randomS))
697
+ const l = Math.max(0, Math.min(100, randomL))
698
+
699
+ // 转换回RGB
700
+ const rgb = hslToRgb(h, s, l)
701
+ const hex = rgbToHex(rgb)
702
+
703
+ colors.push({
704
+ hex,
705
+ rgb,
706
+ hsl: { h: Math.round(h), s: Math.round(s), l: Math.round(l) },
707
+ rgbString: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
708
+ })
709
+ }
710
+
711
+ return colors
712
+ }
713
+
714
+ /**
715
+ * 生成一组完美的图标颜色(智能算法)
716
+ * @param {string} baseHex - 基础颜色
717
+ * @param {number} count - 图标数量
718
+ * @returns {Array} 优化后的颜色数组
719
+ */
720
+ export function generateIconColors(baseHex: string, count: number) {
721
+ const baseRgb = hexToRgb(baseHex)
722
+ const baseHsl = rgbToHsl(baseRgb.r, baseRgb.g, baseRgb.b)
723
+ const colors = []
724
+
725
+ // 根据数量调整色相分布
726
+ const hueStep = 360 / (count + 1)
727
+
728
+ for (let i = 0; i < count; i++) {
729
+ // 创建更均匀的色相分布
730
+ let hue = (baseHsl.h + (i * hueStep)) % 360
731
+
732
+ // 保持主色调附近有一些颜色
733
+ if (i < 3) {
734
+ hue = baseHsl.h + (i - 1) * 10
735
+ }
736
+
737
+ // 智能调整饱和度和亮度
738
+ // 重要图标更鲜艳,次要图标更柔和
739
+ const saturation = Math.max(60, Math.min(90, baseHsl.s + (Math.random() * 20 - 10),
740
+ ))
741
+
742
+ // 创建对比度:一些更亮,一些更暗
743
+ const lightness = i % 2 === 0
744
+ ? Math.min(70, baseHsl.l + 15 + Math.random() * 10)
745
+ : Math.max(30, baseHsl.l - 10 + Math.random() * 10)
746
+
747
+ const rgb = hslToRgb(hue, saturation, lightness)
748
+ const hex = rgbToHex(rgb)
749
+
750
+ colors.push({
751
+ hex,
752
+ rgb,
753
+ hsl: { h: Math.round(hue), s: Math.round(saturation), l: Math.round(lightness) },
754
+ rgbString: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
755
+ })
756
+ }
757
+
758
+ return colors
759
+ }
760
+
761
+ /**
762
+ * 检查颜色对比度是否足够(用于文本可读性)
763
+ * @param {string} hexColor - 十六进制颜色
764
+ * @param {string} backgroundColor - 背景十六进制颜色,默认白色
765
+ * @returns {number} 对比度比率
766
+ */
767
+ export function getContrastRatio(hexColor: string, backgroundColor = '#FFFFFF') {
768
+ const rgb1 = hexToRgb(hexColor)
769
+ const rgb2 = hexToRgb(backgroundColor)
770
+
771
+ const luminance1 = getLuminance(rgb1)
772
+ const luminance2 = getLuminance(rgb2)
773
+
774
+ const lighter = Math.max(luminance1, luminance2)
775
+ const darker = Math.min(luminance1, luminance2)
776
+
777
+ return (lighter + 0.05) / (darker + 0.05)
778
+ }
779
+
780
+ /**
781
+ * 计算颜色亮度
782
+ * @param {object} rgb - RGB对象
783
+ * @returns {number} 亮度值
784
+ */
785
+ function getLuminance(rgb: { r: number, g: number, b: number }) {
786
+ const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((v) => {
787
+ v /= 255
788
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4
789
+ })
790
+
791
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b
792
+ }
793
+
794
+ /**
795
+ * 生成颜色变体(浅色、深色等)
796
+ * @param {string} hex - 基础颜色
797
+ * @param {string} variant - 变体类型:'lighter', 'light', 'dark', 'darker', 'muted'
798
+ * @returns {string} 变体颜色的十六进制值
799
+ */
800
+ export function getColorVariant(hex: string, variant = 'light') {
801
+ const rgb = hexToRgb(hex)
802
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
803
+
804
+ switch (variant) {
805
+ case 'lighter':
806
+ hsl.l = Math.min(95, hsl.l + 40)
807
+ hsl.s = Math.max(30, hsl.s - 10)
808
+ break
809
+ case 'light':
810
+ hsl.l = Math.min(85, hsl.l + 30)
811
+ hsl.s = Math.max(40, hsl.s - 15)
812
+ break
813
+ case 'dark':
814
+ hsl.l = Math.max(20, hsl.l - 20)
815
+ hsl.s = Math.min(80, hsl.s + 10)
816
+ break
817
+ case 'darker':
818
+ hsl.l = Math.max(10, hsl.l - 30)
819
+ hsl.s = Math.min(70, hsl.s + 20)
820
+ break
821
+ case 'muted':
822
+ hsl.s = Math.max(20, hsl.s - 30)
823
+ break
824
+ }
825
+
826
+ const variantRgb = hslToRgb(hsl.h, hsl.s, hsl.l)
827
+ return rgbToHex(variantRgb)
828
+ }
829
+
830
+ /**
831
+ *
832
+ *
833
+ * 获取字符串的第一个字符
834
+ * @param {string} str - 输入的字符串
835
+ * @returns {string} 字符串的第一个字符
836
+ */
837
+ export function getFirstChar(str: string) {
838
+ return str.charAt(0)
839
+ }
840
+
841
+ // 生成七彩虹范围内的任意颜色
842
+ /**
843
+ * 七彩虹颜色范围(HSL表示)
844
+ * 每个颜色范围:色相(H)范围,饱和度(S)固定,亮度(L)固定
845
+ * 选择较高的饱和度(70-80%)和中等亮度(45-55%)以确保白色文字可见
846
+ */
847
+ const COLOR_RANGES = [
848
+ { name: '红', hueRange: [0, 10], saturation: 75, lightness: 50 },
849
+ { name: '橙', hueRange: [20, 40], saturation: 75, lightness: 50 },
850
+ { name: '黄', hueRange: [45, 60], saturation: 75, lightness: 45 }, // 黄色稍微暗一点以保证白色文字可见
851
+ { name: '绿', hueRange: [90, 140], saturation: 70, lightness: 45 },
852
+ { name: '青', hueRange: [160, 190], saturation: 70, lightness: 50 },
853
+ { name: '蓝', hueRange: [200, 240], saturation: 75, lightness: 45 },
854
+ { name: '紫', hueRange: [260, 300], saturation: 70, lightness: 50 },
855
+ { name: '粉', hueRange: [310, 340], saturation: 70, lightness: 55 },
856
+ ] as const
857
+
858
+ type ColorRange = typeof COLOR_RANGES[number]
859
+
860
+ /**
861
+ * 将HSL转换为十六进制颜色
862
+ */
863
+ function hslToHex(h: number, s: number, l: number): string {
864
+ l /= 100
865
+ const a = s * Math.min(l, 1 - l) / 100
866
+ const f = (n: number) => {
867
+ const k = (n + h / 30) % 12
868
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
869
+ return Math.round(255 * color).toString(16).padStart(2, '0')
870
+ }
871
+ return `#${f(0)}${f(8)}${f(4)}`
872
+ }
873
+
874
+ /**
875
+ * 检查颜色是否为暗色系
876
+ * 通过HSL的亮度判断,亮度低于30%的被认为是暗色
877
+ */
878
+ function isDarkColor(h: number, s: number, l: number): boolean {
879
+ return l < 30
880
+ }
881
+
882
+ /**
883
+ * 七彩虹颜色生成器类
884
+ */
885
+ export class RainbowColorGenerator {
886
+ private lastColorRange: ColorRange | null = null
887
+ private lastColorHex: string | null = null
888
+
889
+ /**
890
+ * 生成随机颜色值
891
+ */
892
+ private generateRandomColorFromRange(range: ColorRange): string {
893
+ const { hueRange, saturation, lightness } = range
894
+ const hue = Math.floor(Math.random() * (hueRange[1] - hueRange[0] + 1)) + hueRange[0]
895
+
896
+ // 确保颜色不是暗色系
897
+ if (isDarkColor(hue, saturation, lightness)) {
898
+ // 如果亮度太低,增加亮度到安全范围
899
+ return hslToHex(hue, saturation, Math.max(lightness, 40))
900
+ }
901
+
902
+ return hslToHex(hue, saturation, lightness)
903
+ }
904
+
905
+ /**
906
+ * 检查两个颜色是否属于同一个色系
907
+ */
908
+ private isSameColorRange(range1: ColorRange, range2: ColorRange): boolean {
909
+ return range1.name === range2.name
910
+ }
911
+
912
+ /**
913
+ * 获取可用的颜色范围(排除上一个使用的颜色范围)
914
+ */
915
+ private getAvailableRanges(): ColorRange[] {
916
+ if (!this.lastColorRange) {
917
+ return [...COLOR_RANGES]
918
+ }
919
+ return COLOR_RANGES.filter(range => !this.isSameColorRange(range, this.lastColorRange!))
920
+ }
921
+
922
+ /**
923
+ * 获取下一个随机颜色
924
+ * @returns 十六进制颜色字符串
925
+ */
926
+ getNextColor(): string {
927
+ const availableRanges = this.getAvailableRanges()
928
+
929
+ // 随机选择一个颜色范围
930
+ const randomIndex = Math.floor(Math.random() * availableRanges.length)
931
+ const selectedRange = availableRanges[randomIndex]
932
+
933
+ // 生成颜色
934
+ const color = this.generateRandomColorFromRange(selectedRange)
935
+
936
+ // 更新上一个颜色
937
+ this.lastColorRange = selectedRange
938
+ this.lastColorHex = color
939
+
940
+ return color
941
+ }
942
+
943
+ /**
944
+ * 重置生成器状态
945
+ */
946
+ reset(): void {
947
+ this.lastColorRange = null
948
+ this.lastColorHex = null
949
+ }
950
+
951
+ /**
952
+ * 获取上一个生成的颜色
953
+ */
954
+ getLastColor(): string | null {
955
+ return this.lastColorHex
956
+ }
957
+ }
958
+ /**
959
+ * 格式化手机号
960
+ * @param phone 手机号字符串
961
+ * @param mask 隐藏码,默认为****
962
+ * @returns 格式化后的手机号字符串
963
+ */
964
+ export function formatPhone(phone: string, mask = '****') {
965
+ if (!phone) return '';
966
+
967
+ const cleanPhone = phone.replace(/\D/g, '');
968
+ const len = cleanPhone.length;
969
+
970
+ // 根据号码长度决定显示方式
971
+ if (len === 11) {
972
+ return cleanPhone.slice(0, 3) + mask + cleanPhone.slice(-4);
973
+ } else if (len === 8) {
974
+ // 座机号码等短号码
975
+ return cleanPhone.slice(0, 2) + mask + cleanPhone.slice(-2);
976
+ }
977
+
978
+ return phone;
979
+ }