uview-plus 3.5.5 → 3.5.15

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.
@@ -0,0 +1,1095 @@
1
+ <template>
2
+ <view class="up-color-picker">
3
+ <view clas="up-color-picker__trigger" @click="show = true"
4
+ :style="{backgroundColor: value}" >
5
+ <slot></slot>
6
+ </view>
7
+ <up-popup :show="show" mode="bottom" round="10" @close="close" :closeOnClickOverlay="true">
8
+ <view class="up-color-picker__content">
9
+ <view class="up-color-picker__header">
10
+ <text class="up-color-picker__title">选择颜色</text>
11
+ </view>
12
+
13
+ <!-- 纯色/渐变色切换 -->
14
+ <view class="up-color-picker__switch">
15
+ <up-subsection
16
+ :list="[{ name: '纯色' }, { name: '渐变' }]"
17
+ :current="colorTypeIndex"
18
+ @change="changeColorType"
19
+ fontSize="14"
20
+ ></up-subsection>
21
+ </view>
22
+
23
+ <!-- 渐变色选择器 -->
24
+ <view v-if="colorTypeIndex == 1" class="up-color-picker__gradient">
25
+ <!-- 渐变色控制条 -->
26
+ <view
27
+ class="up-color-picker__gradient-track"
28
+ :style="{ background: gradientStyle }"
29
+ >
30
+ <view
31
+ class="up-color-picker__gradient-pointer"
32
+ v-for="(item, index) in gradientColors"
33
+ :key="index"
34
+ :style="{ left: getGradientPointerPosition(index) + 'px' }"
35
+ @click="openColorPickerForGradient(index)"
36
+ @touchstart="onPointerTouchStart($event, index)"
37
+ @touchmove.stop="onPointerTouchMove"
38
+ @touchend.stop="onPointerTouchEnd"
39
+ >
40
+ <view class="up-color-picker__gradient-pointer-inner" :style="{ backgroundColor: item.color }"></view>
41
+ </view>
42
+ </view>
43
+
44
+ <view class="up-color-picker__gradient-controls">
45
+ <up-button
46
+ type="primary"
47
+ size="mini"
48
+ plain
49
+ @click="addGradientColor"
50
+ class="up-color-picker__add-btn"
51
+ >
52
+ 添加颜色
53
+ </up-button>
54
+ </view>
55
+
56
+ <!-- 圆形方向选择器 -->
57
+ <view class="up-color-picker__gradient-direction">
58
+ <text>方向:</text>
59
+ <view class="up-color-picker__gradient__direction-circle"
60
+ @touchstart="onDirectionTouchStart"
61
+ @touchmove="onDirectionTouchMove"
62
+ @touchend="onDirectionTouchEnd">
63
+ <view
64
+ class="up-color-picker__direction-pointer"
65
+ :style="{
66
+ left: directionPointer.x + 'px',
67
+ top: directionPointer.y + 'px'
68
+ }"
69
+ ></view>
70
+ </view>
71
+ </view>
72
+ </view>
73
+
74
+ <!-- 纯色选择器 -->
75
+ <view class="up-color-picker__solid">
76
+ <!-- 饱和度和明度选择区域 -->
77
+ <view
78
+ class="up-color-picker__saturation"
79
+ :style="{ backgroundColor: `hsl(${hue}, 100%, 50%)` }"
80
+ @touchstart="onSaturationTouchStart"
81
+ @touchmove="onSaturationTouchMove"
82
+ @touchend="onSaturationTouchEnd"
83
+ >
84
+ <view
85
+ class="up-color-picker__saturation-pointer"
86
+ :style="{
87
+ left: saturationPosition.x + 'px',
88
+ top: saturationPosition.y + 'px'
89
+ }"
90
+ ></view>
91
+ </view>
92
+
93
+ <!-- 色相选择 -->
94
+ <view
95
+ class="up-color-picker__hue"
96
+ @touchstart="onHueTouchStart"
97
+ @touchmove="onHueTouchMove"
98
+ @touchend="onHueTouchEnd"
99
+ >
100
+ <view
101
+ class="up-color-picker__hue-pointer"
102
+ :style="{ left: huePosition + 'px' }"
103
+ ></view>
104
+ </view>
105
+
106
+ <!-- 透明度选择 -->
107
+ <view v-if="colorTypeIndex == 0"
108
+ class="up-color-picker__alpha"
109
+ @touchstart="onAlphaTouchStart"
110
+ @touchmove="onAlphaTouchMove"
111
+ @touchend="onAlphaTouchEnd"
112
+ >
113
+ <view class="up-color-picker__alpha-bg"></view>
114
+ <view
115
+ class="up-color-picker__alpha-pointer"
116
+ :style="{ left: alphaPosition + 'px' }"
117
+ ></view>
118
+ </view>
119
+ </view>
120
+
121
+ <!-- 常用颜色 -->
122
+ <view v-if="commonColors && commonColors.length" class="up-color-picker__common">
123
+ <text class="up-color-picker__common-title">常用颜色</text>
124
+ <view class="up-color-picker__common-list">
125
+ <view
126
+ v-for="(color, index) in commonColors"
127
+ :key="index"
128
+ class="up-color-picker__common-item"
129
+ :style="{ backgroundColor: color }"
130
+ @click="selectCommonColor(color)"
131
+ ></view>
132
+ </view>
133
+ </view>
134
+
135
+ <!-- 颜色预览和操作按钮 -->
136
+ <view class="up-color-picker__footer">
137
+ <view class="up-color-picker__preview">
138
+ <view
139
+ class="up-color-picker__preview-color"
140
+ :style="{ backgroundColor: displayColor }"
141
+ ></view>
142
+ <text class="up-color-picker__preview-text">{{ displayColor }}</text>
143
+ </view>
144
+ <view class="up-color-picker__actions">
145
+ <up-button
146
+ type="primary"
147
+ size="small"
148
+ @click="confirm"
149
+ class="up-color-picker__btn"
150
+ >
151
+ 确定
152
+ </up-button>
153
+ <up-button
154
+ type="info"
155
+ size="small"
156
+ @click="close"
157
+ class="up-color-picker__btn"
158
+ >
159
+ 取消
160
+ </up-button>
161
+ </view>
162
+ </view>
163
+ </view>
164
+ </up-popup>
165
+ </view>
166
+ </template>
167
+
168
+ <script>
169
+ export default {
170
+ name: 'up-color-picker',
171
+ props: {
172
+ // 初始颜色值
173
+ modelValue: {
174
+ type: String,
175
+ default: '#ff0000'
176
+ },
177
+ // 常用颜色列表
178
+ commonColors: {
179
+ type: Array,
180
+ default: () => [
181
+ ]
182
+ }
183
+ },
184
+ data() {
185
+ return {
186
+ show: false,
187
+ // 颜色类型索引:0-纯色,1-渐变色
188
+ colorTypeIndex: 0,
189
+ // 纯色相关数据
190
+ hue: 0,
191
+ saturation: 100,
192
+ lightness: 50,
193
+ alpha: 1,
194
+ saturationPosition: { x: 150, y: 0 },
195
+ huePosition: 0,
196
+ alphaPosition: 0, // 将在initColor中设置为最右侧
197
+ // 渐变色相关数据
198
+ gradientColors: [
199
+ { color: '#ff0000', percent: 0 },
200
+ { color: '#0000ff', percent: 1 }
201
+ ],
202
+ gradientDirections: [
203
+ { label: '从左到右', value: 'to right' },
204
+ { label: '从上到下', value: 'to bottom' },
205
+ { label: '从左上到右下', value: 'to bottom right' },
206
+ { label: '从右上到左下', value: 'to bottom left' }
207
+ ],
208
+ currentDirection: { label: '从左到右', value: 'to right' },
209
+ showDirectionPicker: false,
210
+ // 当前选中颜色
211
+ currentColor: '#ff0000',
212
+ // 渐变色控制相关
213
+ draggingPointerIndex: -1,
214
+ directionPointer: { x: 20, y: 20 }, // 默认向右(在圆的右侧)
215
+ // 渐变节点颜色修改相关
216
+ editingGradientIndex: -1,
217
+ // 保存纯色和渐变色的各自状态
218
+ solidColorState: {
219
+ hue: 0,
220
+ saturation: 100,
221
+ lightness: 50,
222
+ alpha: 1,
223
+ saturationPosition: { x: 150, y: 0 },
224
+ huePosition: 0,
225
+ alphaPosition: 0, // 将在initColor中设置为最右侧
226
+ currentColor: '#ff0000'
227
+ },
228
+ gradientColorState: {
229
+ gradientColors: [
230
+ { color: '#ff0000', percent: 0 },
231
+ { color: '#0000ff', percent: 1 }
232
+ ],
233
+ currentDirection: { label: '从左到右', value: 'to right' },
234
+ directionPointer: { x: 100, y: 0 }
235
+ },
236
+ // 区分预览类型:solid-纯色预览,gradient-整体渐变预览,gradient-point-渐变点预览
237
+ previewType: 'solid'
238
+ }
239
+ },
240
+ computed: {
241
+ // 渐变色样式
242
+ gradientStyle() {
243
+ const colors = this.gradientColors.map(item => `${item.color} ${Math.round(item.percent * 100)}%`).join(', ')
244
+ return `linear-gradient(${this.currentDirection.value}, ${colors})`
245
+ },
246
+ // 显示的颜色预览值
247
+ displayColor() {
248
+ if (this.previewType === 'gradient-point' && this.editingGradientIndex >= 0) {
249
+ return this.gradientColors[this.editingGradientIndex].color;
250
+ }
251
+ console.log(this.editingGradientIndex)
252
+ return this.currentColor;
253
+ }
254
+ },
255
+ watch: {
256
+ show(newVal) {
257
+ if (newVal) {
258
+ this.initColor()
259
+ }
260
+ },
261
+ colorTypeIndex(newVal) {
262
+ if (newVal == 0) {
263
+ this.editingGradientIndex = -1
264
+ }
265
+ },
266
+ },
267
+ mounted() {
268
+ this.initColor()
269
+ },
270
+ emits: ['update:modelValue', 'confirm', 'close'],
271
+ methods: {
272
+ // 初始化颜色
273
+ initColor() {
274
+ if (this.modelValue) {
275
+ this.currentColor = this.modelValue
276
+ if (this.modelValue.includes('linear-gradient')) {
277
+ // 解析渐变色
278
+ this.colorTypeIndex = 1
279
+ this.parseGradientColor(this.modelValue)
280
+ this.previewType = 'gradient';
281
+ } else {
282
+ // 解析纯色
283
+ this.colorTypeIndex = 0
284
+ this.parseSolidColor(this.modelValue)
285
+ this.previewType = 'solid';
286
+ }
287
+ }
288
+ // 初始化方向指针位置
289
+ this.initDirectionPointer();
290
+ // 初始化alphaPosition为最右侧
291
+ this.initAlphaPosition();
292
+ },
293
+
294
+ // 初始化alpha位置为最右侧
295
+ async initAlphaPosition() {
296
+ const query = uni.createSelectorQuery().in(this);
297
+ query.select('.up-color-picker__alpha').boundingClientRect();
298
+ await this.$nextTick();
299
+ query.exec(res => {
300
+ const rect = res[0];
301
+ if (rect) {
302
+ this.alphaPosition = rect.width || 150; // 默认150像素
303
+ } else {
304
+ this.alphaPosition = 150; // 默认值
305
+ }
306
+
307
+ // 同步solidColorState中的alphaPosition
308
+ this.solidColorState.alphaPosition = this.alphaPosition;
309
+ this.updateSolidColor();
310
+ });
311
+ },
312
+
313
+ // 初始化方向指针位置
314
+ initDirectionPointer() {
315
+ const angle = this.getDirectionAngle(this.currentDirection.value);
316
+ this.setDirectionPointerByAngle(angle);
317
+ },
318
+
319
+ // 根据方向值获取角度
320
+ getDirectionAngle(direction) {
321
+ switch(direction) {
322
+ case 'to right': return 0;
323
+ case 'to bottom': return 90;
324
+ case 'to left': return 180;
325
+ case 'to top': return 270;
326
+ case 'to bottom right': return 45;
327
+ case 'to bottom left': return 135;
328
+ case 'to top left': return 225;
329
+ case 'to top right': return 315;
330
+ default: return 0;
331
+ }
332
+ },
333
+
334
+ // 根据角度设置方向指针位置
335
+ setDirectionPointerByAngle(angle) {
336
+ const radian = angle * Math.PI / 180;
337
+ const radius = 20; // 圆半径
338
+ this.directionPointer = {
339
+ x: radius * Math.cos(radian) + 20, // 20是圆心位置
340
+ y: radius * Math.sin(radian) + 20
341
+ };
342
+ },
343
+
344
+ // 打开颜色选择器以修改渐变节点颜色
345
+ openColorPickerForGradient(index) {
346
+ this.editingGradientIndex = index;
347
+ // 设置当前颜色为选中节点的颜色
348
+ const color = this.gradientColors[index].color;
349
+ this.currentColor = color;
350
+ // 设置预览类型为渐变点预览
351
+ this.previewType = 'gradient-point';
352
+
353
+ // 解析颜色并设置选择器位置
354
+ if (!color.includes('linear-gradient')) {
355
+ this.colorTypeIndex = 0;
356
+ this.parseSolidColor(color);
357
+ // 保存当前渐变状态
358
+ this.gradientColorState = {
359
+ gradientColors: [...this.gradientColors],
360
+ currentDirection: {...this.currentDirection},
361
+ directionPointer: {...this.directionPointer}
362
+ };
363
+ }
364
+ },
365
+
366
+ // 解析纯色
367
+ parseSolidColor(color) {
368
+ // 简化处理,实际项目中可以使用更复杂的颜色解析
369
+ this.currentColor = color
370
+ // 如果正在编辑渐变节点,则更新该节点颜色
371
+ if (this.editingGradientIndex >= 0) {
372
+ this.gradientColors[this.editingGradientIndex].color = color;
373
+ // 切换回渐变模式并恢复渐变状态
374
+ this.colorTypeIndex = 1;
375
+ Object.assign(this, this.solidColorState);
376
+ this.previewType = 'gradient-point';
377
+ } else {
378
+ this.previewType = 'solid';
379
+ }
380
+ },
381
+
382
+ // 解析渐变色
383
+ parseGradientColor(gradient) {
384
+ // 简化处理,实际项目中可以使用更复杂的渐变解析
385
+ this.currentColor = gradient
386
+ // this.previewType = 'gradient';
387
+ },
388
+
389
+ // 切换颜色类型
390
+ changeColorType(index) {
391
+ // 保存当前状态
392
+ if (this.colorTypeIndex === 0) {
393
+ // 保存纯色状态
394
+ this.solidColorState = {
395
+ hue: this.hue,
396
+ saturation: this.saturation,
397
+ lightness: this.lightness,
398
+ alpha: this.alpha,
399
+ saturationPosition: {...this.saturationPosition},
400
+ huePosition: this.huePosition,
401
+ alphaPosition: this.alphaPosition,
402
+ currentColor: this.currentColor
403
+ };
404
+ } else {
405
+ // 保存渐变状态
406
+ this.gradientColorState = {
407
+ gradientColors: [...this.gradientColors],
408
+ currentDirection: {...this.currentDirection},
409
+ directionPointer: {...this.directionPointer}
410
+ };
411
+ }
412
+
413
+ this.colorTypeIndex = index;
414
+
415
+ // 恢复目标状态
416
+ if (index === 0) {
417
+ Object.assign(this, this.solidColorState);
418
+ this.previewType = 'solid';
419
+ } else {
420
+ Object.assign(this, this.gradientColorState);
421
+ // 确保gradientColors是响应式的
422
+ this.gradientColors = [...this.gradientColorState.gradientColors];
423
+ this.previewType = 'gradient';
424
+ }
425
+ },
426
+
427
+ // 饱和度和明度触摸开始
428
+ onSaturationTouchStart(e) {
429
+ this.updateSaturationPosition(e)
430
+ },
431
+
432
+ // 饱和度和明度触摸移动
433
+ onSaturationTouchMove(e) {
434
+ this.updateSaturationPosition(e)
435
+ },
436
+
437
+ // 饱和度和明度触摸结束
438
+ onSaturationTouchEnd(e) {
439
+ this.updateSaturationPosition(e)
440
+ },
441
+
442
+ // 更新饱和度和明度位置
443
+ updateSaturationPosition(e) {
444
+ const touch = e.touches[0] || e.changedTouches[0]
445
+ const target = e.currentTarget
446
+ const query = uni.createSelectorQuery().in(this);
447
+ query.select('.up-color-picker__saturation').boundingClientRect()
448
+
449
+ query.exec(res => {
450
+ const rect = res[0];
451
+ if (rect) {
452
+ let x = touch.clientX - rect.left
453
+ let y = touch.clientY - rect.top
454
+
455
+ // 边界处理
456
+ x = Math.max(0, Math.min(x, rect.width))
457
+ y = Math.max(0, Math.min(y, rect.height))
458
+
459
+ this.saturationPosition = { x, y }
460
+ this.updateSolidColor()
461
+ }
462
+ })
463
+ },
464
+
465
+ // 色相触摸开始
466
+ onHueTouchStart(e) {
467
+ this.updateHuePosition(e)
468
+ },
469
+
470
+ // 色相触摸移动
471
+ onHueTouchMove(e) {
472
+ this.updateHuePosition(e)
473
+ },
474
+
475
+ // 色相触摸结束
476
+ onHueTouchEnd(e) {
477
+ this.updateHuePosition(e)
478
+ },
479
+
480
+ // 更新色相位置
481
+ updateHuePosition(e) {
482
+ const touch = e.touches[0] || e.changedTouches[0]
483
+ const target = e.currentTarget
484
+ const query = uni.createSelectorQuery().in(this);
485
+ query.select('.up-color-picker__hue').boundingClientRect()
486
+
487
+ query.exec(res => {
488
+ const rect = res[0];
489
+ if (rect) {
490
+ let x = touch.clientX - rect.left
491
+ x = Math.max(0, Math.min(x, rect.width))
492
+ this.huePosition = x
493
+ this.hue = Math.round((x / rect.width) * 360)
494
+ this.updateSolidColor()
495
+ }
496
+ })
497
+ },
498
+
499
+ // 透明度触摸开始
500
+ onAlphaTouchStart(e) {
501
+ this.updateAlphaPosition(e)
502
+ },
503
+
504
+ // 透明度触摸移动
505
+ onAlphaTouchMove(e) {
506
+ this.updateAlphaPosition(e)
507
+ },
508
+
509
+ // 透明度触摸结束
510
+ onAlphaTouchEnd(e) {
511
+ this.updateAlphaPosition(e)
512
+ },
513
+
514
+ // 更新透明度位置
515
+ updateAlphaPosition(e) {
516
+ const touch = e.touches[0] || e.changedTouches[0]
517
+ const target = e.currentTarget
518
+ const query = uni.createSelectorQuery().in(this);
519
+ query.select('.up-color-picker__alpha').boundingClientRect()
520
+
521
+ query.exec(res => {
522
+ const rect = res[0];
523
+ if (rect) {
524
+ let x = touch.clientX - rect.left
525
+ x = Math.max(0, Math.min(x, rect.width))
526
+ this.alphaPosition = x
527
+ this.alpha = x / rect.width
528
+ this.updateSolidColor()
529
+ }
530
+ })
531
+ },
532
+
533
+ // 更新纯色
534
+ updateSolidColor() {
535
+ // 使用实际元素尺寸替代硬编码的150
536
+ const query = uni.createSelectorQuery().in(this);
537
+ query.select('.up-color-picker__saturation').boundingClientRect();
538
+
539
+ query.exec(res => {
540
+ const rect = res[0];
541
+ const size = rect ? Math.min(rect.width, rect.height) : 150; // 默认150作为后备值
542
+
543
+ const s = (this.saturationPosition.x / size) * 100
544
+ const l = 100 - (this.saturationPosition.y / size) * 100
545
+ this.saturation = s
546
+ this.lightness = l
547
+
548
+ // 使用正确的HSL转RGB算法
549
+ if (this.colorTypeIndex == 0) {
550
+ this.currentColor = this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha)
551
+ } else if (this.colorTypeIndex == 1) {
552
+ this.gradientColors[this.editingGradientIndex].color
553
+ = this.hslToRgb(this.hue, this.saturation, this.lightness, this.alpha)
554
+ }
555
+ });
556
+ },
557
+
558
+ // 添加渐变色
559
+ addGradientColor() {
560
+ if (this.gradientColors.length < 5) {
561
+ this.gradientColors.push({
562
+ color: '#ffffff',
563
+ percent: 1
564
+ })
565
+ }
566
+ },
567
+
568
+ // 删除渐变色
569
+ removeGradientColor(index) {
570
+ if (this.gradientColors.length > 2) {
571
+ this.gradientColors.splice(index, 1)
572
+ }
573
+ },
574
+
575
+ // 获取渐变控制点位置
576
+ getGradientPointerPosition(index) {
577
+ const trackWidth = 280; // 需要与样式中的宽度一致
578
+ return this.gradientColors[index].percent * trackWidth;
579
+ },
580
+
581
+ // 更新渐变色
582
+ updateGradientColor(e) {
583
+ const touch = e.touches[0] || e.changedTouches[0];
584
+ const query = uni.createSelectorQuery().in(this);
585
+ query.select('.up-color-picker__gradient-track').boundingClientRect();
586
+
587
+ query.exec(res => {
588
+ const rect = res[0];
589
+ if (rect) {
590
+ let x = touch.clientX - rect.left;
591
+ // 边界处理
592
+ x = Math.max(0, Math.min(x, rect.width));
593
+ const percent = x / rect.width;
594
+
595
+ // 如果正在拖动控制点,则更新该控制点位置
596
+ if (this.draggingPointerIndex >= 0) {
597
+ this.gradientColors[this.draggingPointerIndex].percent = percent;
598
+ // 保持控制点顺序
599
+ this.gradientColors.sort((a, b) => a.percent - b.percent);
600
+ } else {
601
+ // 否则添加新的控制点(此处可选功能)
602
+ }
603
+ }
604
+ });
605
+ },
606
+
607
+ // 控制点触摸开始
608
+ onPointerTouchStart(e, index) {
609
+ this.draggingPointerIndex = index;
610
+ // 点击控制点时更新预览颜色
611
+ this.currentColor = this.gradientColors[index].color;
612
+ this.previewType = 'gradient-point';
613
+ this.editingGradientIndex = index; // 添加这行以正确设置编辑索引
614
+ // 阻止事件冒泡
615
+ e.stopPropagation();
616
+ },
617
+
618
+ // 控制点触摸移动
619
+ onPointerTouchMove(e) {
620
+ if (this.draggingPointerIndex === -1) return;
621
+
622
+ const touch = e.touches[0] || e.changedTouches[0];
623
+ const query = uni.createSelectorQuery().in(this);
624
+ query.select('.up-color-picker__gradient-track').boundingClientRect();
625
+
626
+ query.exec(res => {
627
+ const rect = res[0];
628
+ if (rect) {
629
+ let x = touch.clientX - rect.left;
630
+ // 边界处理
631
+ x = Math.max(0, Math.min(x, rect.width));
632
+ let percent = x / rect.width;
633
+
634
+ // 处理边界情况,确保能精确到达两端
635
+ if (x === 0) percent = 0;
636
+ if (x === rect.width) percent = 1;
637
+
638
+ // 更新控制点位置
639
+ this.gradientColors[this.draggingPointerIndex].percent = percent;
640
+ // 保持控制点顺序
641
+ this.gradientColors.sort((a, b) => a.percent - b.percent);
642
+ // 更新拖拽索引以匹配排序后的索引
643
+ this.draggingPointerIndex = this.gradientColors.findIndex((item, index) => {
644
+ // 使用更精确的比较方法,处理浮点数精度问题
645
+ return Math.abs(item.percent - percent) < 0.0001;
646
+ });
647
+ }
648
+ });
649
+ },
650
+
651
+ // 控制点触摸结束
652
+ onPointerTouchEnd() {
653
+ this.draggingPointerIndex = -1;
654
+ },
655
+
656
+ // 方向选择器触摸开始
657
+ onDirectionTouchStart(e) {
658
+ this.updateDirection(e);
659
+ },
660
+
661
+ // 方向选择器触摸移动
662
+ onDirectionTouchMove(e) {
663
+ this.updateDirection(e);
664
+ },
665
+
666
+ // 方向选择器触摸结束
667
+ onDirectionTouchEnd(e) {
668
+ this.updateDirection(e);
669
+ },
670
+
671
+ // 更新方向
672
+ updateDirection(e) {
673
+ const touch = e.touches[0] || e.changedTouches[0];
674
+ const query = uni.createSelectorQuery().in(this);
675
+ query.select('.up-color-picker__gradient__direction-circle').boundingClientRect();
676
+
677
+ query.exec(res => {
678
+ const rect = res[0];
679
+ if (rect) {
680
+ const centerX = rect.left + rect.width / 2;
681
+ const centerY = rect.top + rect.height / 2;
682
+ const x = touch.clientX - centerX;
683
+ const y = touch.clientY - centerY;
684
+ const distance = Math.sqrt(x * x + y * y);
685
+ const maxDistance = rect.width / 2;
686
+
687
+ // 限制在圆内
688
+ if (distance <= maxDistance) {
689
+ this.directionPointer = {
690
+ x: x + rect.width / 2,
691
+ y: y + rect.height / 2
692
+ };
693
+ } else {
694
+ // 限制在圆周上
695
+ const ratio = maxDistance / distance;
696
+ this.directionPointer = {
697
+ x: x * ratio + rect.width / 2,
698
+ y: y * ratio + rect.height / 2
699
+ };
700
+ }
701
+
702
+ // 计算角度并更新方向
703
+ const angle = Math.atan2(y, x) * 180 / Math.PI;
704
+ console.log(angle)
705
+ this.updateGradientDirection(angle);
706
+ }
707
+ });
708
+ },
709
+
710
+ // 根据角度更新渐变方向
711
+ updateGradientDirection(angle) {
712
+ // 角度转换为标准方向
713
+ if (angle < 0) angle += 360;
714
+
715
+ if (angle >= 315 || angle < 45) {
716
+ this.currentDirection = { label: '从左到右', value: 'to right' };
717
+ } else if (angle >= 45 && angle < 135) {
718
+ this.currentDirection = { label: '从上到下', value: 'to bottom' };
719
+ } else if (angle >= 135 && angle < 225) {
720
+ this.currentDirection = { label: '从右到左', value: 'to left' };
721
+ } else {
722
+ this.currentDirection = { label: '从下到上', value: 'to top' };
723
+ }
724
+ },
725
+
726
+ // 确认方向选择
727
+ confirmDirection(e) {
728
+ this.currentDirection = this.gradientDirections[e.index]
729
+ this.showDirectionPicker = false
730
+ },
731
+
732
+ // 选择常用颜色
733
+ selectCommonColor(color) {
734
+ this.currentColor = color
735
+ if (this.colorTypeIndex === 0) {
736
+ this.parseSolidColor(color)
737
+ } else {
738
+ // 如果是渐变模式,将常用颜色作为渐变的一个节点
739
+ this.gradientColors[this.editingGradientIndex].color = color
740
+ }
741
+ },
742
+
743
+ // 确认选择
744
+ confirm() {
745
+ let color = this.currentColor
746
+ if (this.colorTypeIndex === 1) {
747
+ color = this.gradientStyle
748
+ }
749
+ this.$emit('update:modelValue', color)
750
+ this.show = false;
751
+ this.$emit('confirm', color)
752
+ // 重置编辑状态
753
+ this.editingGradientIndex = -1;
754
+ this.previewType = this.colorTypeIndex === 0 ? 'solid' : 'gradient';
755
+ this.close()
756
+ },
757
+
758
+ // 关闭选择器
759
+ close() {
760
+ this.show = false;
761
+ this.$emit('close')
762
+ },
763
+
764
+ // HSL转RGB辅助函数
765
+ hslToRgb(h, s, l, a = 1) {
766
+ h = h / 360;
767
+ s = s / 100;
768
+ l = l / 100;
769
+
770
+ let r, g, b;
771
+
772
+ if (s === 0) {
773
+ r = g = b = l; // achromatic
774
+ } else {
775
+ const hue2rgb = (p, q, t) => {
776
+ if (t < 0) t += 1;
777
+ if (t > 1) t -= 1;
778
+ if (t < 1/6) return p + (q - p) * 6 * t;
779
+ if (t < 1/2) return q;
780
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
781
+ return p;
782
+ };
783
+
784
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
785
+ const p = 2 * l - q;
786
+ r = hue2rgb(p, q, h + 1/3);
787
+ g = hue2rgb(p, q, h);
788
+ b = hue2rgb(p, q, h - 1/3);
789
+ }
790
+
791
+ const round = (value) => Math.round(value * 255);
792
+ return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${a.toFixed(2)})`;
793
+ }
794
+ }
795
+ }
796
+ </script>
797
+
798
+ <style lang="scss" scoped>
799
+ .up-color-picker {
800
+ &__content {
801
+ width: 100%;
802
+ padding: 20px;
803
+ background-color: #fff;
804
+ }
805
+
806
+ &__header {
807
+ text-align: center;
808
+ margin-bottom: 20px;
809
+ }
810
+
811
+ &__title {
812
+ font-size: 18px;
813
+ font-weight: bold;
814
+ color: #333;
815
+ }
816
+
817
+ &__switch {
818
+ margin-bottom: 20px;
819
+ }
820
+
821
+ &__saturation {
822
+ position: relative;
823
+ width: 100%;
824
+ height: 150px;
825
+ border-radius: 4px;
826
+ margin-bottom: 15px;
827
+ overflow: hidden;
828
+
829
+ &:after {
830
+ content: '';
831
+ position: absolute;
832
+ top: 0;
833
+ left: 0;
834
+ right: 0;
835
+ bottom: 0;
836
+ background: linear-gradient(to right, #fff, rgba(255,255,255,0));
837
+ }
838
+
839
+ &:before {
840
+ content: '';
841
+ position: absolute;
842
+ top: 0;
843
+ left: 0;
844
+ right: 0;
845
+ bottom: 0;
846
+ background: linear-gradient(to top, #000, rgba(0,0,0,0));
847
+ }
848
+ }
849
+
850
+ &__saturation-pointer {
851
+ position: absolute;
852
+ width: 12px;
853
+ height: 12px;
854
+ border: 2px solid #fff;
855
+ border-radius: 50%;
856
+ transform: translate(-50%, -50%);
857
+ box-shadow: 0 0 2px rgba(0,0,0,0.5);
858
+ pointer-events: none;
859
+ }
860
+
861
+ &__hue,
862
+ &__alpha {
863
+ position: relative;
864
+ width: 100%;
865
+ height: 12px;
866
+ border-radius: 6px;
867
+ margin-bottom: 15px;
868
+ cursor: pointer;
869
+ }
870
+
871
+ &__hue {
872
+ background: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%);
873
+ }
874
+
875
+ &__alpha {
876
+ position: relative;
877
+ overflow: hidden;
878
+
879
+ &-bg {
880
+ position: absolute;
881
+ top: 0;
882
+ left: 0;
883
+ right: 0;
884
+ bottom: 0;
885
+ background: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%),
886
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%);
887
+ background-size: 10px 10px;
888
+ background-position: 0 0, 5px 5px;
889
+ }
890
+
891
+ &:after {
892
+ content: '';
893
+ position: absolute;
894
+ top: 0;
895
+ left: 0;
896
+ right: 0;
897
+ bottom: 0;
898
+ background: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,1));
899
+ }
900
+ }
901
+
902
+ &__hue-pointer,
903
+ &__alpha-pointer {
904
+ position: absolute;
905
+ width: 4px;
906
+ height: 16px;
907
+ background: #fff;
908
+ border: 1px solid #ccc;
909
+ border-radius: 2px;
910
+ transform: translateX(-50%);
911
+ top: -2px;
912
+ box-shadow: 0 0 2px rgba(0,0,0,0.5);
913
+ pointer-events: none;
914
+ }
915
+
916
+ &__gradient {
917
+ &-bar {
918
+ width: 100%;
919
+ height: 40px;
920
+ border-radius: 4px;
921
+ }
922
+
923
+ &-track {
924
+ position: relative;
925
+ margin-top: 20px;
926
+ width: 100%;
927
+ height: 32px;
928
+ border-radius: 4px;
929
+ margin-bottom: 15px;
930
+ cursor: pointer;
931
+ }
932
+
933
+ &-pointer {
934
+ position: absolute;
935
+ top: -10px;
936
+ transform: translateX(-50%);
937
+ width: 0;
938
+ height: 0;
939
+ border-left: 8px solid transparent;
940
+ border-right: 8px solid transparent;
941
+ border-top: 10px solid #333;
942
+ z-index: 10;
943
+ }
944
+
945
+ &-pointer-inner {
946
+ position: absolute;
947
+ top: -25px;
948
+ left: -10px;
949
+ width: 20px;
950
+ height: 20px;
951
+ border-radius: 50%;
952
+ border: 2px solid #fff;
953
+ box-shadow: 0 0 2px rgba(0,0,0,0.5);
954
+ }
955
+
956
+ &-controls {
957
+ display: flex;
958
+ flex-direction: row;
959
+ flex-wrap: wrap;
960
+ align-items: center;
961
+ margin-bottom: 15px;
962
+ }
963
+
964
+ &-item {
965
+ display: flex;
966
+ flex-direction: row;
967
+ align-items: center;
968
+ margin-right: 10px;
969
+ margin-bottom: 10px;
970
+ }
971
+
972
+ &-color-preview {
973
+ width: 20px;
974
+ height: 20px;
975
+ border-radius: 50%;
976
+ margin-right: 5px;
977
+ border: 1px solid #eee;
978
+ }
979
+
980
+ &-percent {
981
+ font-size: 12px;
982
+ color: #666;
983
+ margin-right: 5px;
984
+ }
985
+
986
+ &-direction {
987
+ display: flex;
988
+ flex-direction: row;
989
+ align-items: center;
990
+ margin: 10px 0;
991
+ }
992
+
993
+ &__direction-circle {
994
+ width: 40px;
995
+ height: 40px;
996
+ border-radius: 50%;
997
+ background: conic-gradient(
998
+ #ff0000 0deg,
999
+ #ffff00 60deg,
1000
+ #00ff00 120deg,
1001
+ #00ffff 180deg,
1002
+ #0000ff 240deg,
1003
+ #ff00ff 300deg,
1004
+ #ff0000 360deg
1005
+ );
1006
+ position: relative;
1007
+ margin: 10px auto;
1008
+ cursor: pointer;
1009
+ border: 2px solid #eee;
1010
+ }
1011
+
1012
+ &__direction-pointer {
1013
+ position: absolute;
1014
+ width: 6px;
1015
+ height: 6px;
1016
+ background: #fff;
1017
+ border: 2px solid #333;
1018
+ border-radius: 50%;
1019
+ transform: translate(-50%, -50%);
1020
+ box-shadow: 0 0 2px rgba(0,0,0,0.5);
1021
+ z-index: 10;
1022
+ pointer-events: none;
1023
+ }
1024
+ }
1025
+
1026
+ &__add-btn {
1027
+ margin-top: 10px;
1028
+ }
1029
+
1030
+ &__common {
1031
+ margin-top: 20px;
1032
+
1033
+ &-title {
1034
+ display: block;
1035
+ margin-bottom: 10px;
1036
+ font-size: 14px;
1037
+ color: #666;
1038
+ }
1039
+
1040
+ &-list {
1041
+ display: flex;
1042
+ flex-direction: row;
1043
+ flex-wrap: wrap;
1044
+ }
1045
+
1046
+ &-item {
1047
+ width: 24px;
1048
+ height: 24px;
1049
+ border-radius: 50%;
1050
+ margin-right: 10px;
1051
+ margin-bottom: 10px;
1052
+ border: 1px solid #eee;
1053
+ cursor: pointer;
1054
+ }
1055
+ }
1056
+
1057
+ &__footer {
1058
+ margin-top: 20px;
1059
+ }
1060
+
1061
+ &__preview {
1062
+ display: flex;
1063
+ flex-direction: row;
1064
+ align-items: center;
1065
+ margin-bottom: 15px;
1066
+ }
1067
+
1068
+ &__preview-color {
1069
+ width: 40px;
1070
+ height: 40px;
1071
+ border-radius: 4px;
1072
+ border: 1px solid #eee;
1073
+ margin-right: 10px;
1074
+ }
1075
+
1076
+ &__preview-text {
1077
+ font-size: 14px;
1078
+ color: #333;
1079
+ flex: 1;
1080
+ overflow: hidden;
1081
+ text-overflow: ellipsis;
1082
+ white-space: nowrap;
1083
+ }
1084
+
1085
+ &__actions {
1086
+ display: flex;
1087
+ flex-direction: row;
1088
+ justify-content: flex-end;
1089
+
1090
+ & .up-color-picker__btn {
1091
+ margin-left: 10px;
1092
+ }
1093
+ }
1094
+ }
1095
+ </style>