uview-plus 3.4.84 → 3.4.86

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.
package/changelog.md CHANGED
@@ -1,7 +1,14 @@
1
+ ## 3.4.86(2025-08-12)
2
+ feat: 新增signature签名签字组件
3
+
4
+ ## 3.4.85(2025-08-11)
5
+ feat: 完善图片裁剪功能支持props及JS两种方式
6
+
7
+ fix: 修复H5下裁剪后图片黑屏
8
+
1
9
  ## 3.4.84(2025-08-11)
2
10
  feat: 新增cropper图片裁剪组件
3
11
 
4
-
5
12
  ## 3.4.83(2025-08-11)
6
13
  feat: 新增barcode无三方依赖条码组
7
14
 
@@ -1,12 +1,12 @@
1
- <template name="u-cropper">
2
- <view>
3
- <image :src="imgSrc.imgSrc" @click="select" :style="[ imgStyle ]" class="my-avatar"></image>
4
- <canvas canvas-id="avatar-canvas" id="avatar-canvas" class="my-canvas"
1
+ <template>
2
+ <view class="u-cropper">
3
+ <!-- <image :src="imgSrc.imgSrc" @click="select" :style="[ imgStyle ]" class="my-avatar"></image> -->
4
+ <canvas :canvas-id="'avatar-canvas-' + instanceId" :id="'avatar-canvas-' + instanceId" class="my-canvas"
5
5
  :style="{top: styleTop, height: cvsStyleHeight}" disable-scroll="false"></canvas>
6
- <canvas canvas-id="oper-canvas" id="oper-canvas" class="oper-canvas"
6
+ <canvas :canvas-id="'oper-canvas-' + instanceId" :id="'oper-canvas-' + instanceId" class="oper-canvas"
7
7
  :style="{top: styleTop, height: cvsStyleHeight}"
8
8
  disable-scroll="false" @touchstart="start" @touchmove="move" @touchend="end"></canvas>
9
- <canvas canvas-id="prv-canvas" id="prv-canvas" class="prv-canvas"disable-scroll="false"
9
+ <canvas :canvas-id="'prv-canvas-' + instanceId" :id="'prv-canvas-' + instanceId" class="prv-canvas"disable-scroll="false"
10
10
  @touchstart="hideImg":style="{ height: cvsStyleHeight, top: prvTop }"></canvas>
11
11
  <view class="oper-wrapper" :style="{display: styleDisplay}">
12
12
  <view class="oper">
@@ -24,6 +24,11 @@
24
24
  </view>
25
25
  </view>
26
26
  </view>
27
+ <view @click="chooseImage(0, {})" v-if="styleDisplay == 'none'">
28
+ <slot>
29
+
30
+ </slot>
31
+ </view>
27
32
  </view>
28
33
  </template>
29
34
 
@@ -33,6 +38,8 @@
33
38
  name: "u-cropper",
34
39
  data() {
35
40
  return {
41
+ // 添加实例ID用于区分不同实例
42
+ instanceId: Date.now() + '-' + Math.random().toString(36).substr(2, 9),
36
43
  cvsStyleHeight: '0px',
37
44
  styleDisplay: 'none',
38
45
  styleTop: '-10000px',
@@ -45,6 +52,17 @@
45
52
  },
46
53
  btnWidth: '19%',
47
54
  btnDsp: 'flex',
55
+ // 裁剪区域宽度,用于设置选择区域的宽度
56
+ arWidth: '',
57
+ // 裁剪区域高度,用于设置选择区域的高度
58
+ arHeight: '',
59
+ // 导出图片宽度,用于设置最终导出图片的宽度
60
+ expWidth: '',
61
+ // 导出图片高度,用于设置最终导出图片的高度
62
+ expHeight: '',
63
+ // 是否允许调整裁剪框大小
64
+ letChangeSize: false,
65
+
48
66
  };
49
67
  },
50
68
  watch: {
@@ -54,16 +72,6 @@
54
72
  },
55
73
  emits: ['avtinit', 'confirm'],
56
74
  props:{
57
- // 裁剪区域宽度,用于设置选择区域的宽度
58
- areaWidth: '',
59
- // 裁剪区域高度,用于设置选择区域的高度
60
- areaHeight: '',
61
- // 导出图片宽度,用于设置最终导出图片的宽度
62
- exportWidth: '',
63
- // 导出图片高度,用于设置最终导出图片的高度
64
- exportHeight: '',
65
- // 是否允许调整裁剪框大小
66
- canChangeSize: false,
67
75
  minScale: '',
68
76
  maxScale: '',
69
77
  canScale: true,
@@ -76,17 +84,25 @@
76
84
  inner: false,
77
85
  quality: '',
78
86
  index: '',
87
+ canChangeSize: false,
88
+ areaWidth: '300rpx',
89
+ // 裁剪区域高度,用于设置选择区域的高度
90
+ areaHeight: '300rpx',
91
+ // 导出图片宽度,用于设置最终导出图片的宽度
92
+ exportWidth: '260rpx',
93
+ // 导出图片高度,用于设置最终导出图片的高度
94
+ exportHeight: '260rpx',
79
95
  },
80
96
  created() {
81
- this.ctxCanvas = uni.createCanvasContext('avatar-canvas', this);
82
- this.ctxCanvasOper = uni.createCanvasContext('oper-canvas', this);
83
- this.ctxCanvasPrv = uni.createCanvasContext('prv-canvas', this);
97
+ this.ctxCanvas = uni.createCanvasContext('avatar-canvas-' + this.instanceId, this);
98
+ this.ctxCanvasOper = uni.createCanvasContext('oper-canvas-' + this.instanceId, this);
99
+ this.ctxCanvasPrv = uni.createCanvasContext('prv-canvas-' + this.instanceId, this);
84
100
  this.qlty = parseInt(this.quality) || 0.9;
85
- this.imgSrc.imgSrc = this.avatarSrc;
101
+ this.imgSrc.imgSrc = this.imageSrc;
86
102
  this.letRotate = (this.canRotate === false || this.inner === true) ? 0 : 1;
87
103
  this.letScale = this.canScale === false ? 0 : 1;
88
104
  // 是否允许调整裁剪框大小,false表示不允许,其他值表示允许
89
- this.letChangeSize = this.canChangeSize === false ? 0 : 1;
105
+ this.letChangeSize = this.canChangeSize;
90
106
  this.isin = this.inner === true ? 1 : 0;
91
107
  this.indx = this.index || undefined;
92
108
  this.mnScale = this.minScale || 0.3;
@@ -162,8 +178,8 @@
162
178
  this.imgStyle = obj;
163
179
  }
164
180
 
165
- this.exportWidth && (this.exportWidth = this.exportWidth.indexOf('rpx') >= 0 ? parseInt(this.exportWidth)*this.pxRatio : parseInt(this.exportWidth));
166
- this.exportHeight && (this.exportHeight = this.exportHeight.indexOf('rpx') >= 0 ? parseInt(this.exportHeight)*this.pxRatio : parseInt(this.exportHeight));
181
+ this.expWidth && (this.expWidth = this.expWidth.indexOf('rpx') >= 0 ? parseInt(this.expWidth)*this.pxRatio : parseInt(this.expWidth));
182
+ this.expHeight && (this.expHeight = this.expHeight.indexOf('rpx') >= 0 ? parseInt(this.expHeight)*this.pxRatio : parseInt(this.expHeight));
167
183
 
168
184
  if(this.styleDisplay === 'flex') {
169
185
  this.drawInit(true);
@@ -190,9 +206,9 @@
190
206
  this.path = path;
191
207
  if( !this.hasSel ) {
192
208
  let style = this.selStyle || {};
193
- if( this.areaWidth && this.areaHeight ) {
194
- let areaWidth = this.areaWidth.indexOf('rpx') >= 0 ? parseInt(this.areaWidth) * this.pxRatio: parseInt(this.areaWidth),
195
- areaHeight = this.areaHeight.indexOf('rpx') >= 0 ? parseInt(this.areaHeight) * this.pxRatio: parseInt(this.areaHeight);
209
+ if( this.arWidth && this.arHeight ) {
210
+ let areaWidth = this.arWidth.indexOf('rpx') >= 0 ? parseInt(this.arWidth) * this.pxRatio: parseInt(this.arWidth),
211
+ areaHeight = this.arHeight.indexOf('rpx') >= 0 ? parseInt(this.arHeight) * this.pxRatio: parseInt(this.arHeight);
196
212
  style.width = areaWidth + 'px';
197
213
  style.height = areaHeight + 'px';
198
214
  style.top = (this.windowHeight - areaHeight - tabHeight)/2 + 'px';
@@ -240,12 +256,12 @@
240
256
  y = parseInt(style.top),
241
257
  width = parseInt(style.width),
242
258
  height = parseInt(style.height),
243
- expWidth = this.exportWidth || width,
244
- expHeight = this.exportHeight || height;
259
+ expWidth = this.expWidth || width,
260
+ expHeight = this.expHeight || height;
245
261
 
246
262
  // #ifdef H5
247
- x *= this.pixelRatio;
248
- y *= this.pixelRatio;
263
+ // x *= this.pixelRatio;
264
+ // y *= this.pixelRatio;
249
265
  expWidth = width;
250
266
  expHeight = height;
251
267
  // #endif
@@ -262,18 +278,17 @@
262
278
  height: height,
263
279
  destWidth: expWidth,
264
280
  destHeight: expHeight,
265
- canvasId: 'avatar-canvas',
281
+ canvasId: 'avatar-canvas-' + this.instanceId,
266
282
  fileType: 'png',
267
283
  quality: this.qlty,
268
284
  success: (r)=>{
269
285
  r = r.tempFilePath;
270
286
  // #ifdef H5
271
- alert()
272
287
  this.btop(r).then((r)=> {
273
- if(this.exportWidth && this.exportHeight) {
288
+ if(this.expWidth && this.expHeight) {
274
289
  let ctxCanvas = this.ctxCanvas;
275
- expWidth = this.exportWidth,
276
- expHeight = this.exportHeight;
290
+ expWidth = this.expWidth,
291
+ expHeight = this.expHeight;
277
292
 
278
293
  ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
279
294
  ctxCanvas.draw(false,()=>{
@@ -284,7 +299,7 @@
284
299
  height: expHeight,
285
300
  destWidth: expWidth,
286
301
  destHeight: expHeight,
287
- canvasId: 'avatar-canvas',
302
+ canvasId: 'avatar-canvas-' + this.instanceId,
288
303
  fileType: 'png',
289
304
  quality: this.qlty,
290
305
  success: (r)=>{
@@ -335,12 +350,12 @@
335
350
  prvY = this.prvY,
336
351
  prvWidth = this.prvWidth,
337
352
  prvHeight = this.prvHeight,
338
- expWidth = this.exportWidth || prvWidth,
339
- expHeight = this.exportHeight || prvHeight;
353
+ expWidth = this.expWidth || prvWidth,
354
+ expHeight = this.expHeight || prvHeight;
340
355
 
341
356
  // #ifdef H5
342
- prvX *= this.pixelRatio;
343
- prvY *= this.pixelRatio;
357
+ // prvX *= this.pixelRatio;
358
+ // prvY *= this.pixelRatio;
344
359
  expWidth = prvWidth;
345
360
  expHeight = prvHeight;
346
361
  // #endif
@@ -358,17 +373,17 @@
358
373
  height: prvHeight,
359
374
  destWidth: expWidth,
360
375
  destHeight: expHeight,
361
- canvasId: 'prv-canvas',
376
+ canvasId: 'prv-canvas-' + this.instanceId,
362
377
  fileType: 'png',
363
378
  quality: this.qlty,
364
379
  success: (r)=>{
365
380
  r = r.tempFilePath;
366
381
  // #ifdef H5
367
382
  this.btop(r).then((r)=> {
368
- if(this.exportWidth && this.exportHeight) {
383
+ if(this.expWidth && this.expHeight) {
369
384
  let ctxCanvas = this.ctxCanvas;
370
- expWidth = this.exportWidth,
371
- expHeight = this.exportHeight;
385
+ expWidth = this.expWidth,
386
+ expHeight = this.expHeight;
372
387
 
373
388
  ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
374
389
  ctxCanvas.draw(false, ()=>{
@@ -379,7 +394,7 @@
379
394
  height: expHeight,
380
395
  destWidth: expWidth,
381
396
  destHeight: expHeight,
382
- canvasId: 'avatar-canvas',
397
+ canvasId: 'avatar-canvas-' + this.instanceId,
383
398
  fileType: 'png',
384
399
  quality: this.qlty,
385
400
  success: (r)=>{
@@ -592,30 +607,24 @@
592
607
  if(this.fPreviewing) return;
593
608
  this.fPreviewing = true;
594
609
  setTimeout(()=>{ this.fPreviewing = false; }, 1000);
595
-
596
610
  let style = this.selStyle,
597
611
  x = parseInt(style.left),
598
612
  y = parseInt(style.top),
599
613
  width = parseInt(style.width),
600
614
  height = parseInt(style.height);
601
615
 
602
- // #ifdef H5
603
- x *= this.pixelRatio;
604
- y *= this.pixelRatio;
605
- // #endif
606
-
607
616
  uni.showLoading({ mask: true });
608
- console.log('size', x, y, width, height)
617
+ // console.log('size', x, y, width, height)
609
618
  uni.canvasToTempFilePath({
610
619
  x: x,
611
620
  y: y,
612
621
  width: width,
613
622
  height: height,
614
- canvasId: 'avatar-canvas',
623
+ canvasId: 'avatar-canvas-' + this.instanceId,
615
624
  fileType: 'png',
616
625
  quality: this.qlty,
617
626
  success: (r)=>{
618
- console.log(r)
627
+ // console.log(r)
619
628
  this.prvImgTmp = r = r.tempFilePath;
620
629
 
621
630
  let ctxCanvasPrv = this.ctxCanvasPrv,
@@ -635,7 +644,7 @@
635
644
  prvWidth *= radio;
636
645
  prvHeight = useHeight;
637
646
  }
638
- // ctxCanvasPrv.setFillStyle('black');
647
+ ctxCanvasPrv.setFillStyle('black');
639
648
  ctxCanvasPrv.fillRect(0, 0, prvX, prvY);
640
649
  ctxCanvasPrv.fillRect(x, y, width, height);
641
650
  this.prvX = prvX = (prvX-prvWidth)/2;
@@ -643,7 +652,7 @@
643
652
  this.prvWidth = prvWidth;
644
653
  this.prvHeight = prvHeight;
645
654
  ctxCanvasPrv.drawImage(r, prvX, prvY, prvWidth, prvHeight);
646
- ctxCanvasPrv.draw(false, () => {alert()
655
+ ctxCanvasPrv.draw(false, () => {
647
656
  // #ifdef H5
648
657
  this.btop(this.prvImgTmp).then((r)=> {
649
658
  this.showOper = false;
@@ -671,26 +680,28 @@
671
680
  },
672
681
  chooseImage(index=undefined, params=undefined, data=undefined) {
673
682
  if(params) {
674
- let areaWidth = params.areaWidth,
675
- areaHeight = params.areaHeight,
676
- expWidth = params.expWidth,
677
- expHeight = params.expHeight,
683
+ console.log(params)
684
+ let areaWidth = params.areaWidth || this.areaWidth,
685
+ areaHeight = params.areaHeight || this.areaHeight,
686
+ expWidth = params.exportWidth || this.exportWidth,
687
+ expHeight = params.exportHeight || this.exportHeight,
678
688
  quality = params.quality,
679
689
  canRotate = params.canRotate,
680
690
  canScale = params.canScale,
681
- canChangeSize = params.canChangeSize || false,
691
+ canChangeSize = params.canChangeSize,
682
692
  minScale = params.minScale,
683
693
  maxScale = params.maxScale,
684
694
  stretch = params.stretch,
685
695
  inner = params.inner,
686
696
  lock = params.lock;
697
+ console.log('areaWidth', this.areaWidth)
687
698
 
688
- expWidth && (this.exportWidth = expWidth.indexOf('rpx') >= 0 ? parseInt(expWidth)*this.pxRatio : parseInt(expWidth));
689
- expHeight && (this.exportHeight = expHeight.indexOf('rpx') >= 0 ? parseInt(expHeight)*this.pxRatio : parseInt(expHeight));
699
+ expWidth && (this.expWidth = expWidth.indexOf('rpx') >= 0 ? parseInt(expWidth)*this.pxRatio : parseInt(expWidth));
700
+ expHeight && (this.expHeight = expHeight.indexOf('rpx') >= 0 ? parseInt(expHeight)*this.pxRatio : parseInt(expHeight));
690
701
  this.letRotate = canRotate === false ? 0 : 1;
691
702
  this.letScale = canScale === false ? 0 : 1;
692
703
  // 设置是否允许调整裁剪框大小
693
- this.letChangeSize = canChangeSize === false ? 0 : 1;
704
+ this.letChangeSize = canChangeSize || false;
694
705
  this.qlty = parseInt(quality) || 0.9;
695
706
  this.mnScale = minScale || 0.3;
696
707
  this.mxScale = maxScale || 4;
@@ -712,6 +723,7 @@
712
723
  this.selStyle.height = areaHeight + 'px';
713
724
  this.selStyle.top = (this.windowHeight - areaHeight - tabHeight)/2 + 'px';
714
725
  this.selStyle.left = (this.windowWidth - areaWidth)/2 + 'px';
726
+ // console.log(this.selStyle);
715
727
  this.hasSel = true;
716
728
  }
717
729
  }
@@ -936,7 +948,7 @@
936
948
  prvHeight *= this.pixelRatio;
937
949
  // #endif
938
950
  uni.canvasGetImageData({
939
- canvasId: 'prv-canvas',
951
+ canvasId: 'prv-canvas-' + this.instanceId,
940
952
  x: prvX,
941
953
  y: prvY,
942
954
  width: prvWidth,
@@ -1070,7 +1082,7 @@
1070
1082
  prvHeight *= this.pixelRatio;
1071
1083
  // #endif
1072
1084
  uni.canvasPutImageData({
1073
- canvasId: 'prv-canvas',
1085
+ canvasId: 'prv-canvas-' + this.instanceId,
1074
1086
  x: prvX,
1075
1087
  y: prvY,
1076
1088
  width: prvWidth,
@@ -1104,7 +1116,8 @@
1104
1116
  }
1105
1117
  </script>
1106
1118
 
1107
- <style>
1119
+ <style lang="scss" scoped>
1120
+ .u-cropper {
1108
1121
  .my-canvas{
1109
1122
  display: flex;
1110
1123
  position: fixed !important;
@@ -1196,4 +1209,5 @@
1196
1209
  .my-slider {
1197
1210
  flex-grow: 1;
1198
1211
  }
1212
+ }
1199
1213
  </style>
@@ -0,0 +1,498 @@
1
+ <template>
2
+ <view class="u-signature">
3
+ <view class="u-signature__canvas-wrap">
4
+ <canvas
5
+ class="u-signature__canvas"
6
+ :canvas-id="canvasId"
7
+ :disable-scroll="true"
8
+ @touchstart="touchStart"
9
+ @touchmove="touchMove"
10
+ @touchend="touchEnd"
11
+ :style="{
12
+ width: canvasWidth + 'px',
13
+ height: canvasHeight + 'px',
14
+ background: bgColor
15
+ }"
16
+ ></canvas>
17
+ </view>
18
+
19
+ <view v-if="showToolbar" class="u-signature__toolbar">
20
+ <view class="u-signature__toolbar-icons u-flex u-flex-x">
21
+ <view class="u-signature__toolbar-icon" @click="undo">
22
+ <u-icon name="arrow-left" size="22" :color="pathStack.length === 0 ? '#ccc' : '#999'"></u-icon>
23
+ </view>
24
+ <view class="u-signature__toolbar-icon" @click="clear">
25
+ <u-icon name="trash" size="25" color="#999"></u-icon>
26
+ </view>
27
+ <view class="u-signature__toolbar-icon" @click="toggleBrushSettings">
28
+ <u-icon name="edit-pen" size="25" color="#999"></u-icon>
29
+ </view>
30
+ <view class="u-signature__toolbar-icon" @click="toggleColorSettings">
31
+ <u-icon name="grid" size="24" color="#999"></u-icon>
32
+ </view>
33
+ <view class="u-signature__toolbar-icon" @click="exportSignature">
34
+ <u-icon name="checkmark" size="25" :color="isEmpty ? '#ccc' : '#999'"></u-icon>
35
+ </view>
36
+ </view>
37
+
38
+ <!-- 笔画设置 -->
39
+ <view v-if="showBrushSettings" class="u-signature__brush-settings">
40
+ <view class="u-signature__progress">
41
+ <text class="u-signature__progress-label">笔画大小:</text>
42
+ <up-slider
43
+ v-model="lineWidth"
44
+ :min="1"
45
+ :max="20"
46
+ :step="1"
47
+ @show-value="true"
48
+ :value-show="(lineWidth)"
49
+ ></up-slider>
50
+ </view>
51
+ </view>
52
+
53
+ <!-- 颜色设置 -->
54
+ <view v-if="showColorSettings" class="u-signature__color-settings">
55
+ <view class="u-signature__color-picker">
56
+ <text class="u-signature__color-label">笔画颜色:</text>
57
+ <view class="u-signature__colors">
58
+ <view
59
+ v-for="(color, index) in presetColors"
60
+ :key="index"
61
+ class="u-signature__color-item"
62
+ :class="{'u-signature__color-item--active': lineColor === color}"
63
+ :style="{ backgroundColor: color }"
64
+ @click="selectColor(color)"
65
+ ></view>
66
+ </view>
67
+ </view>
68
+ </view>
69
+ </view>
70
+ </view>
71
+ </template>
72
+
73
+ <script>
74
+ export default {
75
+ name: 'u-signature',
76
+ props: {
77
+ // 画布宽度
78
+ width: {
79
+ type: [String, Number],
80
+ default: 300
81
+ },
82
+ // 画布高度
83
+ height: {
84
+ type: [String, Number],
85
+ default: 200
86
+ },
87
+ // 背景颜色
88
+ bgColor: {
89
+ type: String,
90
+ default: '#ffffff'
91
+ },
92
+ // 默认笔画颜色
93
+ color: {
94
+ type: String,
95
+ default: '#000000'
96
+ },
97
+ // 默认笔画粗细
98
+ thickness: {
99
+ type: [String, Number],
100
+ default: 3
101
+ },
102
+ // 是否显示工具栏
103
+ showToolbar: {
104
+ type: Boolean,
105
+ default: true
106
+ }
107
+ },
108
+ data() {
109
+ return {
110
+ canvasId: 'u-signature-' + Math.random().toString(36).substr(2, 9),
111
+ canvasWidth: 300,
112
+ canvasHeight: 200,
113
+ lineColor: '#000000',
114
+ lineWidth: 3,
115
+ isDrawing: false,
116
+ pathStack: [], // 存储绘制路径用于回退
117
+ currentPath: [], // 当前绘制路径
118
+ ctx: null,
119
+ isEmpty: true,
120
+ presetColors: [
121
+ '#000000', // 黑色
122
+ '#ff0000', // 红色
123
+ '#00ff00', // 绿色
124
+ '#0000ff', // 蓝色
125
+ '#ffff00', // 黄色
126
+ '#00ffff', // 青色
127
+ '#ff00ff', // 紫色
128
+ '#ffffff' // 白色
129
+ ],
130
+ showBrushSettings: false,
131
+ showColorSettings: false,
132
+ lastPoint: null // 保存上一个点的坐标
133
+ }
134
+ },
135
+ mounted() {
136
+ this.initCanvas()
137
+ },
138
+ watch: {
139
+ width: {
140
+ handler(newVal) {
141
+ this.canvasWidth = Number(newVal)
142
+ },
143
+ immediate: true
144
+ },
145
+ height: {
146
+ handler(newVal) {
147
+ this.canvasHeight = Number(newVal)
148
+ },
149
+ immediate: true
150
+ },
151
+ color: {
152
+ handler(newVal) {
153
+ this.lineColor = newVal
154
+ },
155
+ immediate: true
156
+ },
157
+ thickness: {
158
+ handler(newVal) {
159
+ this.lineWidth = Number(newVal)
160
+ },
161
+ immediate: true
162
+ }
163
+ },
164
+ methods: {
165
+ initCanvas() {
166
+ // #ifndef APP-NVUE
167
+ const ctx = uni.createCanvasContext(this.canvasId, this)
168
+ this.ctx = ctx
169
+ this.clearCanvas()
170
+ // #endif
171
+
172
+ // #ifdef APP-NVUE
173
+ // NVUE环境下的处理
174
+ // #endif
175
+ },
176
+
177
+ touchStart(e) {
178
+ if (!this.ctx) return
179
+
180
+ this.isDrawing = true
181
+ this.isEmpty = false
182
+ this.currentPath = []
183
+
184
+ const { x, y } = this.getCanvasPoint(e)
185
+ this.ctx.beginPath()
186
+ this.ctx.moveTo(x, y)
187
+ this.ctx.setLineCap('round')
188
+ this.ctx.setLineJoin('round')
189
+ this.ctx.setStrokeStyle(this.lineColor)
190
+ this.ctx.setLineWidth(this.lineWidth)
191
+
192
+ // 记录起始点
193
+ this.currentPath.push({
194
+ x,
195
+ y,
196
+ type: 'start',
197
+ color: this.lineColor,
198
+ width: this.lineWidth
199
+ })
200
+
201
+ // 保存上一个点
202
+ this.lastPoint = { x, y }
203
+
204
+ // 阻止默认事件以提高性能
205
+ e.preventDefault()
206
+ },
207
+
208
+ touchMove(e) {
209
+ if (!this.isDrawing || !this.ctx) return
210
+
211
+ // 阻止默认事件以提高性能
212
+ e.preventDefault()
213
+
214
+ const { x, y } = this.getCanvasPoint(e)
215
+
216
+ // 使用更密集的点采样确保线条连贯性
217
+ if (this.lastPoint) {
218
+ // 计算两点间距离
219
+ const distance = Math.sqrt(Math.pow(x - this.lastPoint.x, 2) + Math.pow(y - this.lastPoint.y, 2))
220
+ // 根据距离确定插值点数量,确保点间距不超过1像素以获得更平滑的线条
221
+ const steps = Math.max(1, Math.floor(distance / 1))
222
+
223
+ // 在两点间插入插值点
224
+ for (let i = 1; i <= steps; i++) {
225
+ const t = i / steps
226
+ const midX = this.lastPoint.x + (x - this.lastPoint.x) * t
227
+ const midY = this.lastPoint.y + (y - this.lastPoint.y) * t
228
+
229
+ this.ctx.lineTo(midX, midY)
230
+ this.ctx.stroke()
231
+ this.currentPath.push({
232
+ x: midX,
233
+ y: midY,
234
+ type: 'move'
235
+ })
236
+ }
237
+ } else {
238
+ this.ctx.lineTo(x, y)
239
+ this.ctx.stroke()
240
+ this.currentPath.push({
241
+ x,
242
+ y,
243
+ type: 'move'
244
+ })
245
+ }
246
+
247
+ this.ctx.draw(true)
248
+
249
+ // 更新上一个点
250
+ this.lastPoint = { x, y }
251
+ },
252
+
253
+ touchEnd(e) {
254
+ if (!this.isDrawing || !this.ctx) return
255
+
256
+ this.isDrawing = false
257
+ this.ctx.closePath()
258
+ this.lastPoint = null
259
+
260
+ // 将当前路径加入栈中用于回退
261
+ if (this.currentPath.length > 0) {
262
+ this.pathStack.push([...this.currentPath])
263
+ }
264
+ },
265
+
266
+ // 获取相对于canvas的坐标点
267
+ getCanvasPoint(e) {
268
+ const touch = e.touches[0]
269
+ const rect = uni.createSelectorQuery().in(this).select('.u-signature__canvas').boundingClientRect()
270
+
271
+ return new Promise((resolve) => {
272
+ rect.boundingClientRect(data => {
273
+ const x = touch.x - data.left
274
+ const y = touch.y - data.top
275
+ resolve({ x, y })
276
+ }).exec()
277
+ })
278
+ },
279
+
280
+ // 同步获取canvas坐标点(兼容处理)
281
+ getCanvasPoint(e) {
282
+ const touch = e.touches[0]
283
+ const canvas = uni.createSelectorQuery().in(this).select('.u-signature__canvas')
284
+
285
+ // 返回一个包含坐标的对象
286
+ return {
287
+ x: touch.x,
288
+ y: touch.y
289
+ }
290
+ },
291
+
292
+ // 选择颜色
293
+ selectColor(color) {
294
+ this.lineColor = color
295
+ },
296
+
297
+ // 回退操作
298
+ undo() {
299
+ if (this.pathStack.length === 0) return
300
+
301
+ // 弹出最后一个路径
302
+ this.pathStack.pop()
303
+
304
+ // 重新绘制
305
+ this.redraw()
306
+ },
307
+
308
+ // 重新绘制所有路径
309
+ redraw() {
310
+ this.clearCanvas()
311
+
312
+ if (this.pathStack.length === 0) {
313
+ this.isEmpty = true
314
+ return
315
+ }
316
+
317
+ this.isEmpty = false
318
+
319
+ // #ifndef APP-NVUE
320
+ this.pathStack.forEach(path => {
321
+ if (path.length === 0) return
322
+
323
+ this.ctx.beginPath()
324
+ this.ctx.setLineCap('round')
325
+ this.ctx.setLineJoin('round')
326
+
327
+ let lastPoint = null
328
+ path.forEach((point, index) => {
329
+ if (index === 0 && point.type === 'start') {
330
+ // 设置起始点样式
331
+ this.ctx.setStrokeStyle(point.color)
332
+ this.ctx.setLineWidth(point.width)
333
+ this.ctx.moveTo(point.x, point.y)
334
+ lastPoint = { x: point.x, y: point.y }
335
+ } else if (point.type === 'move') {
336
+ this.ctx.lineTo(point.x, point.y)
337
+ lastPoint = { x: point.x, y: point.y }
338
+ }
339
+ })
340
+
341
+ this.ctx.stroke()
342
+ this.ctx.draw(true)
343
+ })
344
+ // #endif
345
+ },
346
+
347
+ // 清空画布
348
+ clear() {
349
+ this.pathStack = []
350
+ this.currentPath = []
351
+ this.isEmpty = true
352
+ this.lastPoint = null
353
+ this.clearCanvas()
354
+ },
355
+
356
+ // 清空画布内容
357
+ clearCanvas() {
358
+ if (!this.ctx) return
359
+
360
+ // #ifndef APP-NVUE
361
+ this.ctx.setFillStyle(this.bgColor)
362
+ this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
363
+ this.ctx.draw()
364
+ // #endif
365
+ },
366
+
367
+ // 导出签名图片
368
+ exportSignature() {
369
+ if (this.isEmpty) return
370
+
371
+ // #ifndef APP-NVUE
372
+ uni.canvasToTempFilePath({
373
+ canvasId: this.canvasId,
374
+ fileType: 'png',
375
+ quality: 1,
376
+ success: (res) => {
377
+ this.$emit('confirm', res.tempFilePath)
378
+ },
379
+ fail: (err) => {
380
+ this.$emit('error', err)
381
+ }
382
+ }, this)
383
+ // #endif
384
+
385
+ // #ifdef APP-NVUE
386
+ // NVUE环境下可能需要特殊处理
387
+ // #endif
388
+ },
389
+
390
+ // 切换笔画设置显示
391
+ toggleBrushSettings() {
392
+ this.showBrushSettings = !this.showBrushSettings;
393
+ if (this.showBrushSettings) {
394
+ this.showColorSettings = false;
395
+ }
396
+ },
397
+
398
+ // 切换颜色设置显示
399
+ toggleColorSettings() {
400
+ this.showColorSettings = !this.showColorSettings;
401
+ if (this.showColorSettings) {
402
+ this.showBrushSettings = false;
403
+ }
404
+ },
405
+ }
406
+ }
407
+ </script>
408
+
409
+ <style lang="scss" scoped>
410
+ .u-signature {
411
+ display: flex;
412
+ flex-direction: column;
413
+
414
+ &__canvas-wrap {
415
+ border: 1px solid #e0e0e0;
416
+ border-radius: 4px;
417
+ overflow: hidden;
418
+ }
419
+
420
+ &__canvas {
421
+ width: 100%;
422
+ height: 100%;
423
+ }
424
+
425
+ &__toolbar {
426
+ margin-top: 5px;
427
+ background-color: #fff;
428
+ }
429
+
430
+ &__toolbar-icons {
431
+ display: flex;
432
+ justify-content: space-between;
433
+ align-items: center;
434
+ padding: 1px 0;
435
+ // border: 1px solid #e0e0e0;
436
+ border-radius: 4px;
437
+ }
438
+
439
+ &__toolbar-icon {
440
+ padding: 5px;
441
+ }
442
+
443
+ &__brush-settings,
444
+ &__color-settings {
445
+ margin-top: 15px;
446
+ padding: 1px;
447
+ // border: 1px solid #e0e0e0;
448
+ border-radius: 4px;
449
+ }
450
+
451
+ &__progress {
452
+ &-label {
453
+ display: block;
454
+ margin-bottom: 10px;
455
+ font-size: 14px;
456
+ color: #999;
457
+ }
458
+ }
459
+
460
+ &__color-picker {
461
+ margin-bottom: 10px;
462
+ }
463
+
464
+ &__color-label {
465
+ display: block;
466
+ margin-bottom: 10px;
467
+ font-size: 14px;
468
+ color: #999;
469
+ }
470
+
471
+ &__colors {
472
+ display: flex;
473
+ flex-direction: row;
474
+ flex-wrap: wrap;
475
+ gap: 10px;
476
+ }
477
+
478
+ &__color-item {
479
+ width: 30px;
480
+ height: 30px;
481
+ border-radius: 50%;
482
+ border: 2px solid #f0f0f0;
483
+ cursor: pointer;
484
+
485
+ &--active {
486
+ border-color: #2979ff;
487
+ transform: scale(1.1);
488
+ }
489
+ }
490
+
491
+ &__actions {
492
+ display: flex;
493
+ flex-direction: row;
494
+ gap: 10px;
495
+ justify-content: center;
496
+ }
497
+ }
498
+ </style>
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙跨端移动组件库,组件丰富维护更新稳定。",
5
- "version": "3.4.84",
6
- "description": "零云®uview-plus已兼容vue3,100+全面的组件和便捷的工具会让您信手拈来,如鱼得水。",
5
+ "version": "3.4.86",
6
+ "description": "零云®uview-plus已兼容vue3,100+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名等。",
7
7
  "keywords": [
8
8
  "uview",
9
9
  "uview-plus",