uview-ultra 4.5.53 → 4.5.74

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 (49) hide show
  1. package/changelog.md +214 -0
  2. package/components/up-button/up-button.uvue +9 -8
  3. package/components/up-button/up-button.vue +4 -4
  4. package/components/up-cell/props.js +6 -0
  5. package/components/up-cell/up-cell.uvue +15 -2
  6. package/components/up-cell/up-cell.vue +9 -2
  7. package/components/up-cell-group/up-cell-group.uvue +7 -0
  8. package/components/up-checkbox/up-checkbox.uvue +4 -3
  9. package/components/up-cropper/up-cropper.vue +1426 -1392
  10. package/components/up-form/up-form.uvue +281 -276
  11. package/components/up-form/up-form.vue +14 -6
  12. package/components/up-form-item/up-form-item.uvue +14 -1
  13. package/components/up-form-item/up-form-item.vue +14 -2
  14. package/components/up-input/up-input.uvue +4 -0
  15. package/components/up-input/up-input.vue +6 -1
  16. package/components/up-navbar/up-navbar.uvue +43 -4
  17. package/components/up-navbar/up-navbar.vue +14 -1
  18. package/components/up-number-keyboard/up-number-keyboard.uvue +7 -1
  19. package/components/up-number-keyboard/up-number-keyboard.vue +8 -1
  20. package/components/up-popup/props.js +10 -0
  21. package/components/up-popup/up-popup.uvue +89 -1
  22. package/components/up-popup/up-popup.vue +69 -1
  23. package/components/up-radio/up-radio.uvue +6 -4
  24. package/components/up-radio-group/radioGroup.js +1 -1
  25. package/components/up-radio-group/radioGroup.uts +1 -1
  26. package/components/up-root/root.js +1 -1
  27. package/components/up-select/up-select.uvue +328 -314
  28. package/components/up-select/up-select.vue +19 -2
  29. package/components/up-sticky/up-sticky.vue +4 -1
  30. package/components/up-switch/switch.uts +2 -1
  31. package/components/up-tag/up-tag.uvue +1 -1
  32. package/components/up-tag/up-tag.vue +1 -1
  33. package/components/up-transition/up-transition.uvue +10 -0
  34. package/components/up-transition/up-transition.vue +7 -0
  35. package/components/up-transition/vue.ani-style.scss +39 -10
  36. package/components/up-upload/props.js +5 -0
  37. package/components/up-upload/props.uts +5 -0
  38. package/components/up-upload/types.uts +1 -0
  39. package/components/up-upload/up-upload.uvue +9 -0
  40. package/components/up-upload/up-upload.vue +1 -0
  41. package/components/up-upload/upload.js +2 -0
  42. package/components/up-upload/upload.uts +2 -0
  43. package/components/up-upload/utils.js +23 -12
  44. package/components/up-upload/utils.uts +4 -0
  45. package/libs/theme/theme.js +12 -2
  46. package/libs/theme/theme.uts +151 -7
  47. package/libs/vite/app-icon-font.js +9 -5
  48. package/package.json +3 -2
  49. package/types/comps/upload.d.ts +5 -0
@@ -1,1392 +1,1426 @@
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" :style="{ top: styleTop, height: cvsStyleHeight }" disable-scroll="false"></canvas>
5
- <canvas :canvas-id="'oper-canvas-' + instanceId" :id="'oper-canvas-' + instanceId" class="oper-canvas" :style="{ top: styleTop, height: cvsStyleHeight }" disable-scroll="false" @touchstart="start" @touchmove="move" @touchend="end"></canvas>
6
- <canvas :canvas-id="'prv-canvas-' + instanceId" :id="'prv-canvas-' + instanceId" class="prv-canvas" disable-scroll="false" @touchstart="hideImg" :style="{ height: cvsStyleHeight, top: prvTop }"></canvas>
7
- <view class="oper-wrapper" :style="{ display: styleDisplay }">
8
- <view class="oper">
9
- <view class="btn-wrapper" v-if="showOper">
10
- <view @click="select" hover-class="hover" :style="{ width: btnWidth }">
11
- <text>{{ t("up.common.re-select") }}</text>
12
- </view>
13
- <view @click="close" hover-class="hover" :style="{ width: btnWidth }">
14
- <text>{{ t("up.common.close") }}</text>
15
- </view>
16
- <view @click="rotate" hover-class="hover" :style="{ width: btnWidth, display: btnDsp }">
17
- <text>{{ t("up.common.rotate") }}</text>
18
- </view>
19
- <view @click="preview" hover-class="hover" :style="{ width: btnWidth }">
20
- <text>{{ t("up.common.preview") }}</text>
21
- </view>
22
- <view @click="confirm" hover-class="hover" :style="{ width: btnWidth }">
23
- <text>{{ t("up.common.confirm") }}</text>
24
- </view>
25
- </view>
26
- <view class="clr-wrapper" v-else>
27
- <slider class="my-slider" @change="colorChange" block-size="25" value="0" min="-100" max="100" activeColor="red" backgroundColor="green" block-color="grey" show-value></slider>
28
- <view @click="prvUpload" hover-class="hover" :style="{ width: btnWidth }">
29
- <text>{{ t("up.common.confirm") }}</text>
30
- </view>
31
- </view>
32
- </view>
33
- </view>
34
- <view @click="chooseImage(0, {})" v-if="styleDisplay == 'none'">
35
- <slot>
36
-
37
- </slot>
38
- </view>
39
- </view>
40
- </template>
41
-
42
- <script setup>
43
- import { getCurrentInstance, reactive, toRefs, watch } from 'vue'
44
- import { commonProps } from '../../libs/composable/useUltraUI.js'
45
- import { t } from '../../libs/i18n/index.js'
46
-
47
- const tabHeight = 50
48
-
49
- defineOptions({
50
- name: 'up-cropper',
51
- // #ifdef MP-WEIXIN
52
- options: {
53
- virtualHost: true
54
- }
55
- // #endif
56
- })
57
-
58
- const props = defineProps({
59
- ...commonProps,
60
- minScale: { type: [String, Number], default: '' },
61
- maxScale: { type: [String, Number], default: '' },
62
- canScale: { type: Boolean, default: true },
63
- canRotate: { type: Boolean, default: true },
64
- lockWidth: { type: [String, Number, Boolean], default: '' },
65
- lockHeight: { type: [String, Number, Boolean], default: '' },
66
- stretch: { type: String, default: '' },
67
- lock: { type: String, default: '' },
68
- noTab: { type: Boolean, default: true },
69
- inner: { type: Boolean, default: false },
70
- quality: { type: [String, Number], default: '' },
71
- index: { type: [String, Number], default: '' },
72
- canChangeSize: { type: Boolean, default: false },
73
- areaWidth: { type: String, default: '300rpx' },
74
- areaHeight: { type: String, default: '300rpx' },
75
- exportWidth: { type: String, default: '260rpx' },
76
- exportHeight: { type: String, default: '260rpx' },
77
- fillColor: {
78
- type: String,
79
- default: 'transparent'
80
- },
81
- avatarSrc: {
82
- type: String,
83
- default: ''
84
- },
85
- imageSrc: {
86
- type: String,
87
- default: ''
88
- },
89
- avatarStyle: {
90
- type: [String, Boolean],
91
- default: ''
92
- }
93
- })
94
-
95
- const emit = defineEmits(['avtinit', 'confirm', 'cancel'])
96
- const instance = getCurrentInstance()
97
- const proxy = instance?.proxy
98
-
99
- const state = reactive({
100
- instanceId: Date.now() + '-' + Math.random().toString(36).substr(2, 9),
101
- cvsStyleHeight: '0px',
102
- styleDisplay: 'none',
103
- styleTop: '-10000px',
104
- prvTop: '-10000px',
105
- imgStyle: {},
106
- selStyle: {},
107
- showOper: true,
108
- imgSrc: {
109
- imgSrc: ''
110
- },
111
- btnWidth: '19%',
112
- btnDsp: 'flex',
113
- arWidth: '',
114
- arHeight: '',
115
- expWidth: '',
116
- expHeight: '',
117
- letChangeSize: false,
118
- safeAreaInsetsBottom: 0,
119
- // runtime fields (originally assigned on this outside data())
120
- platform: '',
121
- pixelRatio: 1,
122
- windowWidth: 0,
123
- windowHeight: 0,
124
- drawTop: 0,
125
- moreHeight: 0,
126
- pxRatio: 1,
127
- ctxCanvas: null,
128
- ctxCanvasOper: null,
129
- ctxCanvasPrv: null,
130
- canvas: null,
131
- canvasOper: null,
132
- qlty: 0.9,
133
- letRotate: 1,
134
- letScale: 1,
135
- isin: 0,
136
- indx: undefined,
137
- mnScale: 0.3,
138
- mxScale: 4,
139
- noBar: 1,
140
- stc: '',
141
- lck: '',
142
- imageSrc: '',
143
- avatarStyle: '',
144
- imgPath: '',
145
- imgWidth: 0,
146
- imgHeight: 0,
147
- path: '',
148
- hasSel: false,
149
- fSelecting: false,
150
- fUploading: false,
151
- fPrvUploading: false,
152
- fPreviewing: false,
153
- fRotateing: false,
154
- fixWidth: 0,
155
- fixHeight: 0,
156
- lckWidth: 0,
157
- lckHeight: 0,
158
- scaleWidth: 0,
159
- scaleHeight: 0,
160
- scaleSize: 1,
161
- rotateDeg: 0,
162
- posWidth: 0,
163
- posHeight: 0,
164
- useWidth: 0,
165
- useHeight: 0,
166
- drawTm: 0,
167
- prvTm: 0,
168
- prvImg: '',
169
- prvImgTmp: '',
170
- prvImgData: null,
171
- target: null,
172
- prvX: 0,
173
- prvY: 0,
174
- prvWidth: 0,
175
- prvHeight: 0,
176
- touch0: null,
177
- touch1: null,
178
- fgDistance: 0,
179
- resizeHandle: null,
180
- rtn: undefined
181
- })
182
-
183
- // expose reactive fields to template
184
- const {
185
- instanceId,
186
- cvsStyleHeight,
187
- styleDisplay,
188
- styleTop,
189
- prvTop,
190
- imgStyle,
191
- selStyle,
192
- showOper,
193
- imgSrc,
194
- btnWidth,
195
- btnDsp
196
- } = toRefs(state)
197
-
198
- function windowResize() {
199
- let sysInfo = uni.getSystemInfoSync();
200
- state.platform = sysInfo.platform;
201
- state.pixelRatio = sysInfo.pixelRatio;
202
- state.windowWidth = sysInfo.windowWidth;
203
- state.safeAreaInsetsBottom = sysInfo.safeAreaInsets.bottom;
204
- // #ifdef H5
205
- state.drawTop = sysInfo.windowTop;
206
- state.windowHeight = sysInfo.windowHeight + sysInfo.windowBottom;
207
- state.cvsStyleHeight = state.windowHeight - tabHeight + 'px';
208
- // #endif
209
- // #ifdef APP-PLUS
210
- if (state.platform === 'android') {
211
- state.windowHeight = sysInfo.screenHeight + sysInfo.statusBarHeight;
212
- state.cvsStyleHeight = state.windowHeight - tabHeight + 'px';
213
- } else {
214
- state.windowHeight = sysInfo.windowHeight + state.moreHeight;
215
- state.cvsStyleHeight = state.windowHeight - tabHeight - state.safeAreaInsetsBottom + 6 + 'px';
216
- }
217
- // #endif
218
- // #ifdef MP
219
- state.windowHeight = sysInfo.windowHeight + state.moreHeight;
220
- state.cvsStyleHeight = state.windowHeight - tabHeight - state.safeAreaInsetsBottom - 2 + 'px';
221
- // #endif
222
- state.pxRatio = state.windowWidth / 750;
223
-
224
- let style = props.avatarStyle;
225
- if (style && style !== true && (style = style.trim())) {
226
- style = style.split(';');
227
- let obj = {};
228
- for (let v of style) {
229
- if (!v) continue;
230
- v = v.trim().split(':');
231
- if (v[1].indexOf('rpx') >= 0) {
232
- let arr = v[1].trim().split(' ');
233
- for (let k in arr) {
234
- if (!arr[k]) continue;
235
- if (arr[k].indexOf('rpx') >= 0) {
236
- arr[k] = parseFloat(arr[k]) * state.pxRatio + 'px';
237
- }
238
- }
239
- v[1] = arr.join(' ');
240
- }
241
- obj[v[0].trim()] = v[1].trim();
242
- }
243
- state.imgStyle = obj;
244
- }
245
-
246
- state.expWidth && (state.expWidth = state.expWidth.indexOf('rpx') >= 0 ? parseInt(state.expWidth) * state.pxRatio : parseInt(state.expWidth));
247
- state.expHeight && (state.expHeight = state.expHeight.indexOf('rpx') >= 0 ? parseInt(state.expHeight) * state.pxRatio : parseInt(state.expHeight));
248
-
249
- if (state.styleDisplay === 'flex') {
250
- drawInit(true);
251
- }
252
- hideImg();
253
- }
254
- function loadImage(path) {
255
- uni.showLoading({ mask: true });
256
- state.imgPath = path;
257
- uni.getImageInfo({
258
- src: path,
259
- success: r => {
260
- state.imgWidth = r.width;
261
- state.imgHeight = r.height;
262
- state.path = path;
263
- if (!state.hasSel) {
264
- let style = state.selStyle || {};
265
- if (state.arWidth && state.arHeight) {
266
- let areaWidth = state.arWidth.indexOf('rpx') >= 0 ? parseInt(state.arWidth) * state.pxRatio : parseInt(state.arWidth),
267
- areaHeight = state.arHeight.indexOf('rpx') >= 0 ? parseInt(state.arHeight) * state.pxRatio : parseInt(state.arHeight);
268
- style.width = areaWidth + 'px';
269
- style.height = areaHeight + 'px';
270
- style.top = (state.windowHeight - areaHeight - tabHeight) / 2 + 'px';
271
- style.left = (state.windowWidth - areaWidth) / 2 + 'px';
272
- } else {
273
- uni.showModal({
274
- title: t("up.cropper.emptyWidhtOrHeight"),
275
- showCancel: false
276
- })
277
- return;
278
- }
279
- state.selStyle = style;
280
- }
281
-
282
- if (state.noBar) {
283
- drawInit(true);
284
- } else {
285
- uni.hideTabBar({
286
- complete: () => {
287
- drawInit(true);
288
- }
289
- });
290
- }
291
- },
292
- fail: () => {
293
- uni.showToast({
294
- title: "error3",
295
- duration: 2000,
296
- })
297
- },
298
- complete() {
299
- uni.hideLoading();
300
- }
301
- });
302
- }
303
- function select() {
304
- if (state.fSelecting) return;
305
- state.fSelecting = true;
306
- setTimeout(() => { state.fSelecting = false; }, 500);
307
- uni.chooseImage({
308
- count: 1,
309
- sizeType: ['original', 'compressed'],
310
- sourceType: ['album', 'camera'],
311
- success: (r) => {
312
- loadImage(r.tempFilePaths[0]);
313
- }, fail(err) {
314
- emit('cancel')
315
- }
316
- })
317
- }
318
- function confirm() {
319
- if (state.fUploading) return;
320
- state.fUploading = true;
321
- setTimeout(() => { state.fUploading = false; }, 1000)
322
-
323
- let style = state.selStyle,
324
- x = parseInt(style.left),
325
- y = parseInt(style.top),
326
- width = parseInt(style.width),
327
- height = parseInt(style.height),
328
- expWidth = state.expWidth || width,
329
- expHeight = state.expHeight || height;
330
-
331
- // #ifdef H5
332
- // x *= state.pixelRatio;
333
- // y *= state.pixelRatio;
334
- expWidth = width;
335
- expHeight = height;
336
- // #endif
337
-
338
- uni.showLoading({ mask: true });
339
- state.styleDisplay = 'none';
340
- state.styleTop = '-10000px';
341
- state.hasSel = false;
342
- hideImg();
343
- uni.canvasToTempFilePath({
344
- x: x,
345
- y: y,
346
- width: width,
347
- height: height,
348
- destWidth: expWidth,
349
- destHeight: expHeight,
350
- canvasId: 'avatar-canvas-' + state.instanceId,
351
- fileType: 'png',
352
- quality: state.qlty,
353
- success: (r) => {
354
- r = r.tempFilePath;
355
- // #ifdef H5
356
- btop(r).then((r) => {
357
- if (state.expWidth && state.expHeight) {
358
- let ctxCanvas = state.ctxCanvas;
359
- expWidth = state.expWidth,
360
- expHeight = state.expHeight;
361
-
362
- ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
363
- ctxCanvas.draw(false, () => {
364
- uni.canvasToTempFilePath({
365
- x: 0,
366
- y: 0,
367
- width: expWidth,
368
- height: expHeight,
369
- destWidth: expWidth,
370
- destHeight: expHeight,
371
- canvasId: 'avatar-canvas-' + state.instanceId,
372
- fileType: 'png',
373
- quality: state.qlty,
374
- success: (r) => {
375
- r = r.tempFilePath;
376
- btop(r).then((r) => {
377
- emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
378
- });
379
- },
380
- fail: () => {
381
- uni.showToast({
382
- title: "error0",
383
- duration: 2000,
384
- })
385
- }
386
- });
387
- });
388
- } else {
389
- emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
390
- }
391
- })
392
- // #endif
393
- // #ifndef H5
394
- emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
395
- // #endif
396
- },
397
- fail: (res) => {
398
- uni.showToast({
399
- title: "error1",
400
- duration: 2000,
401
- })
402
- },
403
- complete: () => {
404
- uni.hideLoading();
405
- state.noBar || uni.showTabBar();
406
- }
407
- }, proxy);
408
- }
409
- // 用户点击"预览"模式下的"确认"按钮时被调用,用于将预览的裁剪结果上传
410
- function prvUpload() {
411
- if (state.fPrvUploading) return;
412
- state.fPrvUploading = true;
413
- setTimeout(() => { state.fPrvUploading = false; }, 1000)
414
-
415
- let style = state.selStyle,
416
- destWidth = parseInt(style.width),
417
- destHeight = parseInt(style.height),
418
- prvX = state.prvX,
419
- prvY = state.prvY,
420
- prvWidth = state.prvWidth,
421
- prvHeight = state.prvHeight,
422
- expWidth = state.expWidth || prvWidth,
423
- expHeight = state.expHeight || prvHeight;
424
-
425
- // #ifdef H5
426
- // prvX *= state.pixelRatio;
427
- // prvY *= state.pixelRatio;
428
- expWidth = prvWidth;
429
- expHeight = prvHeight;
430
- // #endif
431
-
432
- uni.showLoading({ mask: true });
433
-
434
- state.styleDisplay = 'none';
435
- state.styleTop = '-10000px';
436
- state.hasSel = false;
437
- hideImg();
438
- uni.canvasToTempFilePath({
439
- x: prvX,
440
- y: prvY,
441
- width: prvWidth,
442
- height: prvHeight,
443
- destWidth: expWidth,
444
- destHeight: expHeight,
445
- canvasId: 'prv-canvas-' + state.instanceId,
446
- fileType: 'png',
447
- quality: state.qlty,
448
- success: (r) => {
449
- r = r.tempFilePath;
450
- // #ifdef H5
451
- btop(r).then((r) => {
452
- if (state.expWidth && state.expHeight) {
453
- let ctxCanvas = state.ctxCanvas;
454
- expWidth = state.expWidth,
455
- expHeight = state.expHeight;
456
-
457
- ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
458
- ctxCanvas.draw(false, () => {
459
- uni.canvasToTempFilePath({
460
- x: 0,
461
- y: 0,
462
- width: expWidth,
463
- height: expHeight,
464
- destWidth: expWidth,
465
- destHeight: expHeight,
466
- canvasId: 'avatar-canvas-' + state.instanceId,
467
- fileType: 'png',
468
- quality: state.qlty,
469
- success: (r) => {
470
- r = r.tempFilePath;
471
- btop(r).then((r) => {
472
- emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
473
- });
474
- },
475
- fail: () => {
476
- uni.showToast({
477
- title: "error0",
478
- duration: 2000,
479
- })
480
- }
481
- });
482
- });
483
- } else {
484
- emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
485
- }
486
- })
487
- // #endif
488
- // #ifndef H5
489
- emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
490
- // #endif
491
- },
492
- fail: () => {
493
- uni.showToast({
494
- title: "error_prv",
495
- duration: 2000,
496
- })
497
- },
498
- complete: () => {
499
- uni.hideLoading();
500
- state.noBar || uni.showTabBar();
501
- }
502
- }, proxy);
503
- }
504
- function drawInit(ini = false) {
505
- let allWidth = state.windowWidth,
506
- allHeight = state.windowHeight,
507
- imgWidth = state.imgWidth,
508
- imgHeight = state.imgHeight,
509
- imgRadio = imgWidth / imgHeight,
510
- useWidth = allWidth - 40,
511
- useHeight = allHeight - tabHeight - 80,
512
- pixelRatio = state.pixelRatio,
513
- selWidth = parseInt(state.selStyle.width),
514
- selHeight = parseInt(state.selStyle.height);
515
-
516
- state.fixWidth = 0;
517
- state.fixHeight = 0;
518
- state.lckWidth = 0;
519
- state.lckHeight = 0;
520
- switch (state.stc) {
521
- case 'x': state.fixWidth = 1; break;
522
- case 'y': state.fixHeight = 1; break;
523
- case 'long': if (imgRadio > 1) state.fixWidth = 1; else state.fixHeight = 1; break;
524
- case 'short': if (imgRadio > 1) state.fixHeight = 1; else state.fixWidth = 1; break;
525
- case 'longSel': if (selWidth > selHeight) state.fixWidth = 1; else state.fixHeight = 1; break;
526
- case 'shortSel': if (selWidth > selHeight) state.fixHeight = 1; else state.fixWidth = 1; break;
527
- }
528
- // lck 用于控制裁剪框的宽度和高度锁定行为
529
- // 'x': 锁定宽度,不允许水平方向调整
530
- // 'y': 锁定高度,不允许垂直方向调整
531
- // 'long': 根据图片长边锁定,如果图片横向较长则锁定宽度,否则锁定高度
532
- // 'short': 根据图片短边锁定,如果图片横向较长则锁定高度,否则锁定宽度
533
- // 'longSel': 根据选择框的长边锁定,如果选择框宽度大于高度则锁定宽度,否则锁定高度
534
- // 'shortSel': 根据选择框的短边锁定,如果选择框宽度大于高度则锁定高度,否则锁定宽度
535
- switch (state.lck) {
536
- case 'x': state.lckWidth = 1; break;
537
- case 'y': state.lckHeight = 1; break;
538
- case 'long': if (imgRadio > 1) state.lckWidth = 1; else state.lckHeight = 1; break;
539
- case 'short': if (imgRadio > 1) state.lckHeight = 1; else state.lckWidth = 1; break;
540
- case 'longSel': if (selWidth > selHeight) state.lckWidth = 1; else state.lckHeight = 1; break;
541
- case 'shortSel': if (selWidth > selHeight) state.lckHeight = 1; else state.lckWidth = 1; break;
542
- }
543
- if (state.fixWidth) {
544
- useWidth = selWidth;
545
- useHeight = useWidth / imgRadio;
546
- } else if (state.fixHeight) {
547
- useHeight = selHeight;
548
- useWidth = useHeight * imgRadio;
549
- } else if (imgRadio < 1) {
550
- if (imgHeight < useHeight) {
551
- useWidth = imgWidth;
552
- useHeight = imgHeight;
553
- } else {
554
- useHeight = useHeight;
555
- useWidth = useHeight * imgRadio;
556
- }
557
- } else {
558
- if (imgWidth < useWidth) {
559
- useWidth = imgWidth;
560
- useHeight = imgHeight;
561
- } else {
562
- useWidth = useWidth;
563
- useHeight = useWidth / imgRadio;
564
- }
565
- }
566
- if (state.isin) {
567
- state.scaleWidth = 0;
568
- state.scaleHeight = 0;
569
- if (useWidth < selWidth) {
570
- useWidth = selWidth;
571
- useHeight = useWidth / imgRadio;
572
- state.lckHeight = 0;
573
- }
574
- if (useHeight < selHeight) {
575
- useHeight = selHeight;
576
- useWidth = useHeight * imgRadio;
577
- state.lckWidth = 0;
578
- }
579
- }
580
-
581
- state.scaleSize = 1;
582
- state.rotateDeg = 0;
583
- state.posWidth = (allWidth - useWidth) / 2;
584
- state.posHeight = (allHeight - useHeight - tabHeight) / 2;
585
- state.useWidth = useWidth;
586
- state.useHeight = useHeight;
587
-
588
- let style = state.selStyle,
589
- left = parseInt(style.left),
590
- top = parseInt(style.top),
591
- width = parseInt(style.width),
592
- height = parseInt(style.height),
593
- canvas = state.canvas,
594
- canvasOper = state.canvasOper,
595
- ctxCanvas = state.ctxCanvas,
596
- ctxCanvasOper = state.ctxCanvasOper;
597
-
598
- ctxCanvasOper.setLineWidth(3);
599
- ctxCanvasOper.setStrokeStyle('grey');
600
- ctxCanvasOper.setGlobalAlpha(0.4);
601
- ctxCanvasOper.setFillStyle('black');
602
- ctxCanvasOper.strokeRect(left, top, width, height);
603
- ctxCanvasOper.fillRect(0, 0, state.windowWidth, top);
604
- ctxCanvasOper.fillRect(0, top, left, height);
605
- ctxCanvasOper.fillRect(0, top + height, state.windowWidth, state.windowHeight - height - top - tabHeight);
606
- ctxCanvasOper.fillRect(left + width, top, state.windowWidth - width - left, height);
607
- ctxCanvasOper.setStrokeStyle('red');
608
- ctxCanvasOper.moveTo(left + 20, top); ctxCanvasOper.lineTo(left, top); ctxCanvasOper.lineTo(left, top + 20);
609
- ctxCanvasOper.moveTo(left + width - 20, top); ctxCanvasOper.lineTo(left + width, top); ctxCanvasOper.lineTo(left + width, top + 20);
610
- ctxCanvasOper.moveTo(left + 20, top + height); ctxCanvasOper.lineTo(left, top + height); ctxCanvasOper.lineTo(left, top + height - 20);
611
- ctxCanvasOper.moveTo(left + width - 20, top + height); ctxCanvasOper.lineTo(left + width, top + height); ctxCanvasOper.lineTo(left + width, top + height - 20);
612
-
613
- // 绘制控制点(四个角)
614
- const controlPointSize = 10;
615
- ctxCanvasOper.setFillStyle('white');
616
- ctxCanvasOper.setStrokeStyle('grey');
617
- ctxCanvasOper.setLineWidth(1);
618
- // 左上角
619
- ctxCanvasOper.fillRect(left - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
620
- ctxCanvasOper.strokeRect(left - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
621
- // 右上角
622
- ctxCanvasOper.fillRect(left + width - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
623
- ctxCanvasOper.strokeRect(left + width - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
624
- // 左下角
625
- ctxCanvasOper.fillRect(left - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
626
- ctxCanvasOper.strokeRect(left - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
627
- // 右下角
628
- ctxCanvasOper.fillRect(left + width - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
629
- ctxCanvasOper.strokeRect(left + width - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
630
-
631
- ctxCanvasOper.stroke();
632
-
633
- ctxCanvasOper.draw(false, () => {
634
- if (ini) {
635
- state.styleDisplay = 'flex';
636
- // #ifdef H5
637
- state.styleTop = state.drawTop + 'px';
638
- // #endif
639
- // #ifndef H5
640
- state.styleTop = '0';
641
- // #endif
642
- if (props.fillColor && props.fillColor !== 'transparent') {
643
- ctxCanvas.setFillStyle(props.fillColor);
644
- }
645
- drawImage();
646
- }
647
- });
648
-
649
- emit("avtinit");
650
- }
651
- function drawImage() {
652
- let tm_now = Date.now();
653
- if (tm_now - state.drawTm < 20) return;
654
- state.drawTm = tm_now;
655
- let ctxCanvas = state.ctxCanvas;
656
- if (props.fillColor && props.fillColor !== 'transparent') {
657
- ctxCanvas.fillRect(0, 0, state.windowWidth, state.windowHeight - tabHeight);
658
- }
659
- ctxCanvas.translate(state.posWidth + state.useWidth / 2, state.posHeight + state.useHeight / 2);
660
- ctxCanvas.scale(state.scaleSize, state.scaleSize);
661
- ctxCanvas.rotate(state.rotateDeg * Math.PI / 180);
662
- ctxCanvas.drawImage(state.imgPath, -state.useWidth / 2, -state.useHeight / 2, state.useWidth, state.useHeight);
663
- ctxCanvas.draw(false);
664
- }
665
- function hideImg() {
666
- state.prvImg = '';
667
- state.prvTop = '-10000px';
668
- state.showOper = true;
669
- state.prvImgData = null;
670
- state.target = null;
671
- }
672
- function close() {
673
- console.log('up-cropper close');
674
-
675
- state.styleDisplay = 'none';
676
- state.styleTop = '-10000px';
677
- state.hasSel = false;
678
- hideImg();
679
- state.noBar || uni.showTabBar();
680
- emit('cancel');
681
- }
682
- function preview() {
683
- if (state.fPreviewing) return;
684
- state.fPreviewing = true;
685
- setTimeout(() => { state.fPreviewing = false; }, 1000);
686
- let style = state.selStyle,
687
- x = parseInt(style.left),
688
- y = parseInt(style.top),
689
- width = parseInt(style.width),
690
- height = parseInt(style.height);
691
-
692
- uni.showLoading({ mask: true });
693
- // console.log('size', x, y, width, height)
694
- uni.canvasToTempFilePath({
695
- x: x,
696
- y: y,
697
- width: width,
698
- height: height,
699
- canvasId: 'avatar-canvas-' + state.instanceId,
700
- fileType: 'png',
701
- quality: state.qlty,
702
- success: (r) => {
703
- // console.log(r)
704
- state.prvImgTmp = r = r.tempFilePath;
705
-
706
- let ctxCanvasPrv = state.ctxCanvasPrv,
707
- prvX = state.windowWidth,
708
- prvY = parseInt(state.cvsStyleHeight),
709
- prvWidth = parseInt(state.selStyle.width),
710
- prvHeight = parseInt(state.selStyle.height),
711
- useWidth = prvX - 40,
712
- useHeight = prvY - 80,
713
- radio = useWidth / prvWidth,
714
- rHeight = prvHeight * radio;
715
- if (rHeight < useHeight) {
716
- prvWidth = useWidth;
717
- prvHeight = rHeight;
718
- } else {
719
- radio = useHeight / prvHeight;
720
- prvWidth *= radio;
721
- prvHeight = useHeight;
722
- }
723
- if (props.fillColor && props.fillColor !== 'transparent') {
724
- ctxCanvasPrv.setFillStyle(props.fillColor);
725
- ctxCanvasPrv.fillRect(0, 0, prvX, prvY);
726
- ctxCanvasPrv.fillRect(x, y, width, height);
727
- }
728
- state.prvX = prvX = (prvX - prvWidth) / 2;
729
- state.prvY = prvY = (prvY - prvHeight) / 2;
730
- state.prvWidth = prvWidth;
731
- state.prvHeight = prvHeight;
732
- ctxCanvasPrv.drawImage(r, prvX, prvY, prvWidth, prvHeight);
733
- ctxCanvasPrv.draw(false, () => {
734
- // #ifdef H5
735
- btop(state.prvImgTmp).then((r) => {
736
- state.showOper = false;
737
- state.prvTop = state.drawTop + 'px';
738
- })
739
- // #endif
740
- // #ifndef H5
741
- if (state.platform != 'android') {
742
- state.showOper = false;
743
- }
744
- state.prvTop = '0';
745
- // #endif
746
- });
747
- },
748
- fail: () => {
749
- uni.showToast({
750
- title: "error2",
751
- duration: 2000,
752
- })
753
- },
754
- complete: () => {
755
- uni.hideLoading();
756
- }
757
- }, proxy);
758
- }
759
- function chooseImage(index = undefined, params = undefined, data = undefined) {
760
- const imageSrc = typeof params?.imageSrc === 'string' ? params.imageSrc.trim() : '';
761
- if (params) {
762
- console.log(params)
763
- let areaWidth = params.areaWidth || props.areaWidth,
764
- areaHeight = params.areaHeight || props.areaHeight,
765
- expWidth = params.exportWidth || props.exportWidth,
766
- expHeight = params.exportHeight || props.exportHeight,
767
- quality = params.quality,
768
- canRotate = params.canRotate,
769
- canScale = params.canScale,
770
- canChangeSize = params.canChangeSize,
771
- minScale = params.minScale,
772
- maxScale = params.maxScale,
773
- stretch = params.stretch,
774
- inner = params.inner,
775
- lock = params.lock;
776
- console.log('areaWidth', props.areaWidth)
777
-
778
- expWidth && (state.expWidth = expWidth.indexOf('rpx') >= 0 ? parseInt(expWidth) * state.pxRatio : parseInt(expWidth));
779
- expHeight && (state.expHeight = expHeight.indexOf('rpx') >= 0 ? parseInt(expHeight) * state.pxRatio : parseInt(expHeight));
780
- state.isin = inner === true ? 1 : 0;
781
- state.letRotate = (canRotate === false || state.isin) ? 0 : 1;
782
- state.letScale = canScale === false ? 0 : 1;
783
- // 设置是否允许调整裁剪框大小
784
- state.letChangeSize = canChangeSize || false;
785
- state.qlty = parseInt(quality) || 0.9;
786
- state.mnScale = minScale || 0.3;
787
- state.mxScale = maxScale || 4;
788
- state.stc = stretch;
789
- state.lck = lock;
790
- if (state.isin) {
791
- state.btnWidth = '24%';
792
- state.btnDsp = 'none';
793
- } else {
794
- state.btnWidth = '19%';
795
- state.btnDsp = 'flex';
796
- }
797
-
798
- if (areaWidth && areaHeight) {
799
- areaWidth = areaWidth.indexOf('rpx') >= 0 ? parseInt(areaWidth) * state.pxRatio : parseInt(areaWidth);
800
- areaHeight = areaHeight.indexOf('rpx') >= 0 ? parseInt(areaHeight) * state.pxRatio : parseInt(areaHeight);
801
- state.selStyle.width = areaWidth + 'px';
802
- state.selStyle.height = areaHeight + 'px';
803
- state.selStyle.top = (state.windowHeight - areaHeight - tabHeight) / 2 + 'px';
804
- state.selStyle.left = (state.windowWidth - areaWidth) / 2 + 'px';
805
- // console.log(state.selStyle);
806
- state.hasSel = true;
807
- }
808
- }
809
- state.rtn = data;
810
- state.indx = index;
811
- // 业务方已自行拍照/选图时,直接裁剪传入的路径,不再打开系统选图
812
- if (imageSrc) {
813
- loadImage(imageSrc);
814
- return;
815
- }
816
- select();
817
- }
818
- function rotate() {
819
- // #ifdef APP-PLUS
820
- if (state.platform === 'android') {
821
- if (state.fRotateing) return;
822
- state.fRotateing = true;
823
- setTimeout(() => { state.fRotateing = false; }, 500);
824
- }
825
- // #endif
826
-
827
- // if(state.letRotate) {
828
- state.rotateDeg += 90 - state.rotateDeg % 90;
829
- drawImage();
830
- // }
831
- }
832
- function start(e) {
833
- let touches = e.touches,
834
- touch0 = touches[0],
835
- touch1 = touches[1];
836
-
837
- state.touch0 = touch0;
838
- state.touch1 = touch1;
839
-
840
- if (touch1) {
841
- let x = touch1.x - touch0.x,
842
- y = touch1.y - touch0.y;
843
- state.fgDistance = Math.sqrt(x * x + y * y);
844
- } else {
845
- // 只有在允许调整大小时才检查控制点
846
- if (state.letChangeSize) {
847
- // 检查是否点击在控制点上
848
- const controlPointSize = 20;
849
- const x = touch0.x;
850
- const y = touch0.y;
851
- const style = state.selStyle;
852
- const left = parseInt(style.left);
853
- const top = parseInt(style.top);
854
- const width = parseInt(style.width);
855
- const height = parseInt(style.height);
856
-
857
- // 检查四个控制点
858
- if (Math.abs(x - left) < controlPointSize && Math.abs(y - top) < controlPointSize) {
859
- state.resizeHandle = 'top-left';
860
- } else if (Math.abs(x - (left + width)) < controlPointSize && Math.abs(y - top) < controlPointSize) {
861
- state.resizeHandle = 'top-right';
862
- } else if (Math.abs(x - left) < controlPointSize && Math.abs(y - (top + height)) < controlPointSize) {
863
- state.resizeHandle = 'bottom-left';
864
- } else if (Math.abs(x - (left + width)) < controlPointSize && Math.abs(y - (top + height)) < controlPointSize) {
865
- state.resizeHandle = 'bottom-right';
866
- } else {
867
- state.resizeHandle = null;
868
- }
869
- } else {
870
- state.resizeHandle = null;
871
- }
872
- }
873
- }
874
- function move(e) {
875
- let touches = e.touches,
876
- touch0 = touches[0],
877
- touch1 = touches[1];
878
-
879
- if (touch1) {
880
- let x = touch1.x - touch0.x,
881
- y = touch1.y - touch0.y,
882
- fgDistance = Math.sqrt(x * x + y * y),
883
- scaleSize = 0.005 * (fgDistance - state.fgDistance),
884
- beScaleSize = state.scaleSize + scaleSize;
885
-
886
- do {
887
- if (!state.letScale) break;
888
- if (beScaleSize < state.mnScale) break;
889
- if (beScaleSize > state.mxScale) break;
890
- if (state.isin) {
891
- let imgWidth = state.useWidth * beScaleSize,
892
- imgHeight = state.useHeight * beScaleSize,
893
- rx0 = state.posWidth + state.useWidth / 2,
894
- ry0 = state.posHeight + state.useHeight / 2,
895
- l = rx0 - imgWidth / 2, t = ry0 - imgHeight / 2,
896
- r = l + imgWidth, b = t + imgHeight,
897
- left = parseInt(state.selStyle.left),
898
- top = parseInt(state.selStyle.top),
899
- width = parseInt(state.selStyle.width),
900
- height = parseInt(state.selStyle.height);
901
- if (left < l || left + width > r || top < t || top + height > b) break;
902
- state.scaleWidth = (state.useWidth - imgWidth) / 2;
903
- state.scaleHeight = (state.useHeight - imgHeight) / 2;
904
- }
905
-
906
- state.scaleSize = beScaleSize;
907
- } while (0);
908
- state.fgDistance = fgDistance;
909
-
910
- if (touch1.x !== touch0.x && state.letRotate) {
911
- x = (state.touch1.y - state.touch0.y) / (state.touch1.x - state.touch0.x);
912
- y = (touch1.y - touch0.y) / (touch1.x - touch0.x);
913
- state.rotateDeg += Math.atan((y - x) / (1 + x * y)) * 180 / Math.PI;
914
- state.touch0 = touch0;
915
- state.touch1 = touch1;
916
- }
917
-
918
- drawImage();
919
- } else if (state.touch0) {
920
- // 只有在允许调整大小时才处理裁剪框大小调整
921
- if (state.resizeHandle && state.letChangeSize) {
922
- const style = { ...state.selStyle };
923
- const left = parseInt(style.left);
924
- const top = parseInt(style.top);
925
- const width = parseInt(style.width);
926
- const height = parseInt(style.height);
927
- const minWidth = 50;
928
- const minHeight = 50;
929
-
930
- switch (state.resizeHandle) {
931
- case 'top-left':
932
- style.left = touch0.x + 'px';
933
- style.top = touch0.y + 'px';
934
- style.width = (left + width - touch0.x) + 'px';
935
- style.height = (top + height - touch0.y) + 'px';
936
- break;
937
- case 'top-right':
938
- style.top = touch0.y + 'px';
939
- style.width = (touch0.x - left) + 'px';
940
- style.height = (top + height - touch0.y) + 'px';
941
- break;
942
- case 'bottom-left':
943
- style.left = touch0.x + 'px';
944
- style.width = (left + width - touch0.x) + 'px';
945
- style.height = (touch0.y - top) + 'px';
946
- break;
947
- case 'bottom-right':
948
- style.width = (touch0.x - left) + 'px';
949
- style.height = (touch0.y - top) + 'px';
950
- break;
951
- }
952
-
953
- // 约束裁剪框:最小尺寸、屏幕边界,inner 模式下再限制在图片内
954
- let nl = parseInt(style.left);
955
- let nt = parseInt(style.top);
956
- let nw = parseInt(style.width);
957
- let nh = parseInt(style.height);
958
-
959
- // 屏幕边界
960
- if (nl < 0) nl = 0;
961
- if (nt < 0) nt = 0;
962
- if (nl + nw > state.windowWidth) nw = state.windowWidth - nl;
963
- if (nt + nh > state.windowHeight - tabHeight) nh = state.windowHeight - tabHeight - nt;
964
-
965
- if (state.isin) {
966
- const imgWidth = state.useWidth * state.scaleSize;
967
- const imgHeight = state.useHeight * state.scaleSize;
968
- const rx0 = state.posWidth + state.useWidth / 2;
969
- const ry0 = state.posHeight + state.useHeight / 2;
970
- const imgL = rx0 - imgWidth / 2;
971
- const imgT = ry0 - imgHeight / 2;
972
- const imgR = imgL + imgWidth;
973
- const imgB = imgT + imgHeight;
974
- if (nl < imgL) nl = imgL;
975
- if (nt < imgT) nt = imgT;
976
- if (nl + nw > imgR) nw = imgR - nl;
977
- if (nt + nh > imgB) nh = imgB - nt;
978
- }
979
-
980
- if (nw >= minWidth && nh >= minHeight) {
981
- style.left = nl + 'px';
982
- style.top = nt + 'px';
983
- style.width = nw + 'px';
984
- style.height = nh + 'px';
985
- state.selStyle = style;
986
- // 重新绘制操作层
987
- drawInit();
988
- }
989
- } else {
990
- // 原有的移动图片逻辑
991
- let x = touch0.x - state.touch0.x,
992
- y = touch0.y - state.touch0.y,
993
- beX = state.posWidth + x,
994
- beY = state.posHeight + y;
995
- if (state.isin) {
996
- let imgWidth = state.useWidth * state.scaleSize,
997
- imgHeight = state.useHeight * state.scaleSize,
998
- rx0 = beX + state.useWidth / 2,
999
- ry0 = beY + state.useHeight / 2,
1000
- l = rx0 - imgWidth / 2, t = ry0 - imgHeight / 2,
1001
- r = l + imgWidth, b = t + imgHeight,
1002
- left = parseInt(state.selStyle.left),
1003
- top = parseInt(state.selStyle.top),
1004
- width = parseInt(state.selStyle.width),
1005
- height = parseInt(state.selStyle.height);
1006
- if (!state.lckWidth && Math.abs(x) < 100) {
1007
- if (left >= l && left + width <= r) {
1008
- state.posWidth = beX;
1009
- } else if (left < l) {
1010
- state.posWidth = left - state.scaleWidth;
1011
- } else if (left + width > r) {
1012
- state.posWidth = left - (imgWidth - width) - state.scaleWidth;
1013
- }
1014
- }
1015
- if (!state.lckHeight && Math.abs(y) < 100) {
1016
- if (top >= t && top + height <= b) {
1017
- state.posHeight = beY;
1018
- } else if (top < t) {
1019
- state.posHeight = top - state.scaleHeight;
1020
- } else if (top + height > b) {
1021
- state.posHeight = top - (imgHeight - height) - state.scaleHeight;
1022
- }
1023
- }
1024
- } else {
1025
- if (Math.abs(x) < 100 && !state.lckWidth) state.posWidth = beX;
1026
- if (Math.abs(y) < 100 && !state.lckHeight) state.posHeight = beY;
1027
- }
1028
-
1029
- state.touch0 = touch0;
1030
- drawImage();
1031
- }
1032
- }
1033
- }
1034
- function end(e) {
1035
- let touches = e.touches,
1036
- touch0 = touches && touches[0],
1037
- touch1 = touches && touches[1];
1038
- if (touch0) {
1039
- state.touch0 = touch0;
1040
- } else {
1041
- state.touch0 = null;
1042
- state.touch1 = null;
1043
- state.resizeHandle = null; // 重置调整手柄
1044
- }
1045
- }
1046
- function getImgData() {
1047
- return new Promise((resolve, reject) => {
1048
- let prvX = state.prvX,
1049
- prvY = state.prvY,
1050
- prvWidth = state.prvWidth,
1051
- prvHeight = state.prvHeight;
1052
- // #ifdef APP-PLUS||H5
1053
- prvX *= state.pixelRatio;
1054
- prvY *= state.pixelRatio;
1055
- prvWidth *= state.pixelRatio;
1056
- prvHeight *= state.pixelRatio;
1057
- // #endif
1058
- uni.canvasGetImageData({
1059
- canvasId: 'prv-canvas-' + state.instanceId,
1060
- x: prvX,
1061
- y: prvY,
1062
- width: prvWidth,
1063
- height: prvHeight,
1064
- success(res) {
1065
- resolve(res.data);
1066
- },
1067
- fail(err) {
1068
- reject(err);
1069
- }
1070
- }, proxy);
1071
- });
1072
- }
1073
- async function colorChange(e) {
1074
- let tm_now = Date.now();
1075
- if (tm_now - state.prvTm < 100) return;
1076
- state.prvTm = tm_now;
1077
-
1078
- uni.showLoading({ mask: true });
1079
-
1080
- if (!state.prvImgData) {
1081
- if (!(state.prvImgData = await getImgData().catch((res) => {
1082
- uni.showToast({
1083
- title: "error_read",
1084
- duration: 2000,
1085
- })
1086
- }))) return;
1087
- state.target = new Uint8ClampedArray(state.prvImgData.length);
1088
- }
1089
-
1090
- let data = state.prvImgData,
1091
- target = state.target,
1092
- i = e.detail.value,
1093
- r, g, b, a, h, s, l, d, p, q, t, min, max, hK, tR, tG, tB;
1094
-
1095
- if (i === 0) {
1096
- target = data;
1097
- } else {
1098
- i = (i + 100) / 200;
1099
- if (i < 0.005) i = 0;
1100
- if (i > 0.995) i = 1;
1101
- for (let n = data.length - 1; n >= 0; n -= 4) {
1102
- r = data[n - 3] / 255;
1103
- g = data[n - 2] / 255;
1104
- b = data[n - 1] / 255;
1105
- max = Math.max(r, g, b);
1106
- min = Math.min(r, g, b);
1107
- d = max - min;
1108
- if (max === min) {
1109
- h = 0;
1110
- } else if (max === r && g >= b) {
1111
- h = 60 * ((g - b) / d);
1112
- } else if (max === r && g < b) {
1113
- h = 60 * ((g - b) / d) + 360;
1114
- } else if (max === g) {
1115
- h = 60 * ((b - r) / d) + 120;
1116
- } else if (max === b) {
1117
- h = 60 * ((r - g) / d) + 240;
1118
- }
1119
- l = (max + min) / 2;
1120
- if (l === 0 || max === min) {
1121
- s = 0;
1122
- } else if (0 < l && l <= 0.5) {
1123
- s = d / (2 * l);
1124
- } else if (l > 0.5) {
1125
- s = d / (2 - 2 * l);
1126
- }
1127
- data[n] && (a = data[n]);
1128
-
1129
- if (i < 0.5) {
1130
- s = s * i / 0.5;
1131
- } else if (i > 0.5) {
1132
- s = 2 * s + 2 * i - (s * i / 0.5) - 1;
1133
- }
1134
-
1135
- if (s === 0) {
1136
- r = g = b = Math.round(l * 255);
1137
- } else {
1138
- if (l < 0.5) {
1139
- q = l * (1 + s);
1140
- } else if (l >= 0.5) {
1141
- q = l + s - (l * s);
1142
- }
1143
- p = 2 * l - q;
1144
- hK = h / 360;
1145
- tR = hK + 1 / 3;
1146
- tG = hK;
1147
- tB = hK - 1 / 3;
1148
- let correctRGB = (t) => {
1149
- if (t < 0) {
1150
- return t + 1.0;
1151
- }
1152
- if (t > 1) {
1153
- return t - 1.0;
1154
- }
1155
- return t;
1156
- };
1157
- let createRGB = (t) => {
1158
- if (t < (1 / 6)) {
1159
- return p + ((q - p) * 6 * t);
1160
- } else if (t >= (1 / 6) && t < (1 / 2)) {
1161
- return q;
1162
- } else if (t >= (1 / 2) && t < (2 / 3)) {
1163
- return p + ((q - p) * 6 * ((2 / 3) - t));
1164
- }
1165
- return p;
1166
- };
1167
- r = tR = Math.round(createRGB(correctRGB(tR)) * 255);
1168
- g = tG = Math.round(createRGB(correctRGB(tG)) * 255);
1169
- b = tB = Math.round(createRGB(correctRGB(tB)) * 255);
1170
- }
1171
- a && (target[n] = a);
1172
- target[n - 3] = r;
1173
- target[n - 2] = g;
1174
- target[n - 1] = b;
1175
- }
1176
- }
1177
- let prvX = state.prvX,
1178
- prvY = state.prvY,
1179
- prvWidth = state.prvWidth,
1180
- prvHeight = state.prvHeight;
1181
-
1182
- if (props.fillColor && props.fillColor !== 'transparent') {
1183
- state.ctxCanvasPrv.setFillStyle(props.fillColor);
1184
- state.ctxCanvasPrv.fillRect(prvX, prvY, prvWidth, prvHeight);
1185
- }
1186
- state.ctxCanvasPrv.draw(true);
1187
-
1188
- // #ifdef APP-PLUS||H5
1189
- prvX *= state.pixelRatio;
1190
- prvY *= state.pixelRatio;
1191
- prvWidth *= state.pixelRatio;
1192
- prvHeight *= state.pixelRatio;
1193
- // #endif
1194
- uni.canvasPutImageData({
1195
- canvasId: 'prv-canvas-' + state.instanceId,
1196
- x: prvX,
1197
- y: prvY,
1198
- width: prvWidth,
1199
- height: prvHeight,
1200
- data: target,
1201
- fail() {
1202
- uni.showToast({
1203
- title: 'error_put',
1204
- duration: 2000
1205
- })
1206
- },
1207
- complete() {
1208
- uni.hideLoading();
1209
- }
1210
- }, proxy);
1211
- }
1212
- function btop(base64) {
1213
- return new Promise(function (resolve, reject) {
1214
- var arr = base64.split(','),
1215
- mime = arr[0].match(/:(.*?);/)[1],
1216
- bstr = atob(arr[1]),
1217
- n = bstr.length,
1218
- u8arr = new Uint8Array(n);
1219
- while (n--) {
1220
- u8arr[n] = bstr.charCodeAt(n);
1221
- }
1222
- return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([u8arr], { type: mime })));
1223
- });
1224
- }
1225
-
1226
-
1227
-
1228
- // setup init (was created)
1229
- state.imageSrc = props.imageSrc || props.avatarSrc || ''
1230
- state.avatarStyle = props.avatarStyle || ''
1231
- state.ctxCanvas = uni.createCanvasContext('avatar-canvas-' + state.instanceId, proxy)
1232
- state.ctxCanvasOper = uni.createCanvasContext('oper-canvas-' + state.instanceId, proxy)
1233
- state.ctxCanvasPrv = uni.createCanvasContext('prv-canvas-' + state.instanceId, proxy)
1234
- state.qlty = parseInt(props.quality) || 0.9
1235
- state.imgSrc.imgSrc = state.imageSrc
1236
- state.letRotate = (props.canRotate === false || props.inner === true) ? 0 : 1
1237
- state.letScale = props.canScale === false ? 0 : 1
1238
- state.letChangeSize = props.canChangeSize
1239
- state.isin = props.inner === true ? 1 : 0
1240
- state.indx = props.index || undefined
1241
- state.mnScale = props.minScale || 0.3
1242
- state.mxScale = props.maxScale || 4
1243
- state.noBar = props.noTab === true ? 1 : 0
1244
- state.stc = props.stretch
1245
- state.lck = props.lock
1246
- if (state.isin) {
1247
- state.btnWidth = '24%'
1248
- state.btnDsp = 'none'
1249
- } else {
1250
- state.btnWidth = '19%'
1251
- state.btnDsp = 'flex'
1252
- }
1253
- if (state.noBar) {
1254
- state.moreHeight = 0
1255
- windowResize()
1256
- } else {
1257
- uni.showTabBar({
1258
- complete: (res) => {
1259
- state.moreHeight = (res.errMsg === 'showTabBar:ok') ? 50 : 0
1260
- windowResize()
1261
- }
1262
- })
1263
- }
1264
-
1265
- watch(() => props.avatarSrc, (val) => {
1266
- state.imgSrc.imgSrc = val
1267
- })
1268
- watch(() => props.imageSrc, (val) => {
1269
- if (val) {
1270
- state.imageSrc = val
1271
- state.imgSrc.imgSrc = val
1272
- }
1273
- })
1274
-
1275
- defineExpose({
1276
- chooseImage,
1277
- select,
1278
- loadImage,
1279
- confirm,
1280
- preview,
1281
- close,
1282
- rotate
1283
- })
1284
- </script>
1285
-
1286
- <style lang="scss" scoped>
1287
- .u-cropper {
1288
- .my-canvas {
1289
- display: flex;
1290
- position: fixed !important;
1291
- background: #000000;
1292
- left: 0;
1293
- z-index: 100000;
1294
- width: 100%;
1295
- }
1296
-
1297
- .my-avatar {
1298
- width: 150rpx;
1299
- height: 150rpx;
1300
- border-radius: 100%;
1301
- }
1302
-
1303
- .oper-canvas {
1304
- display: flex;
1305
- position: fixed !important;
1306
- left: 0;
1307
- z-index: 100001;
1308
- width: 100%;
1309
- }
1310
-
1311
- .prv-canvas {
1312
- display: flex;
1313
- position: fixed !important;
1314
- background: #000000;
1315
- left: 0;
1316
- z-index: 200000;
1317
- width: 100%;
1318
- }
1319
-
1320
- .oper-wrapper {
1321
- height: 50px;
1322
- position: fixed !important;
1323
- box-sizing: border-box;
1324
- border: 1px solid #F1F1F1;
1325
- background: #ffffff;
1326
- width: 100%;
1327
- left: 0;
1328
- bottom: 0;
1329
- z-index: 100009;
1330
- flex-direction: row;
1331
- }
1332
-
1333
- .oper {
1334
- display: flex;
1335
- flex-direction: column;
1336
- justify-content: center;
1337
- padding: 10rpx 20rpx;
1338
- width: 100%;
1339
- height: 100%;
1340
- box-sizing: border-box;
1341
- align-self: center;
1342
- }
1343
-
1344
- .btn-wrapper {
1345
- display: flex;
1346
- flex-direction: row;
1347
- /* #ifndef H5 */
1348
- flex-grow: 1;
1349
- /* #endif */
1350
- /* #ifdef H5 */
1351
- height: 50px;
1352
- /* #endif */
1353
- justify-content: space-between;
1354
- }
1355
-
1356
- .btn-wrapper view {
1357
- display: flex;
1358
- align-items: center;
1359
- justify-content: center;
1360
- font-size: 16px;
1361
- color: #333;
1362
- border: 1px solid #f1f1f1;
1363
- border-radius: 6%;
1364
- }
1365
-
1366
- .hover {
1367
- background: #f1f1f1;
1368
- border-radius: 6%;
1369
- }
1370
-
1371
- .clr-wrapper {
1372
- display: flex;
1373
- flex-direction: row;
1374
- flex-grow: 1;
1375
- }
1376
-
1377
- .clr-wrapper view {
1378
- display: flex;
1379
- align-items: center;
1380
- justify-content: center;
1381
- font-size: 16px;
1382
- color: #333;
1383
- border: 1px solid #f1f1f1;
1384
- border-radius: 6%;
1385
- }
1386
-
1387
- .my-slider {
1388
- flex-grow: 1;
1389
- }
1390
- }
1391
- </style>
1392
-
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" :style="{ top: styleTop, height: cvsStyleHeight }" disable-scroll="false"></canvas>
5
+ <canvas :canvas-id="'oper-canvas-' + instanceId" :id="'oper-canvas-' + instanceId" class="oper-canvas" :style="{ top: styleTop, height: cvsStyleHeight }" disable-scroll="false" @touchstart="start" @touchmove="move" @touchend="end"></canvas>
6
+ <canvas :canvas-id="'prv-canvas-' + instanceId" :id="'prv-canvas-' + instanceId" class="prv-canvas" disable-scroll="false" @touchstart="hideImg" :style="{ height: cvsStyleHeight, top: prvTop }"></canvas>
7
+ <view class="oper-wrapper" :style="operStyle">
8
+ <view class="oper">
9
+ <view class="btn-wrapper" v-if="showOper">
10
+ <view @click="select" hover-class="hover" :style="{ width: btnWidth }">
11
+ <text>{{ t("up.common.re-select") }}</text>
12
+ </view>
13
+ <view @click="close" hover-class="hover" :style="{ width: btnWidth }">
14
+ <text>{{ t("up.common.close") }}</text>
15
+ </view>
16
+ <view @click="rotate" hover-class="hover" :style="{ width: btnWidth, display: btnDsp }">
17
+ <text>{{ t("up.common.rotate") }}</text>
18
+ </view>
19
+ <view @click="preview" hover-class="hover" :style="{ width: btnWidth }">
20
+ <text>{{ t("up.common.preview") }}</text>
21
+ </view>
22
+ <view @click="confirm" hover-class="hover" :style="{ width: btnWidth }">
23
+ <text>{{ t("up.common.confirm") }}</text>
24
+ </view>
25
+ </view>
26
+ <view class="clr-wrapper" v-else>
27
+ <slider class="my-slider" @change="colorChange" block-size="25" value="0" min="-100" max="100" activeColor="red" backgroundColor="green" block-color="grey" show-value></slider>
28
+ <view @click="prvUpload" hover-class="hover" :style="{ width: btnWidth }">
29
+ <text>{{ t("up.common.confirm") }}</text>
30
+ </view>
31
+ </view>
32
+ </view>
33
+ </view>
34
+ <view @click="chooseImage(0, {})" v-if="styleDisplay == 'none'">
35
+ <slot>
36
+
37
+ </slot>
38
+ </view>
39
+ </view>
40
+ </template>
41
+
42
+ <script setup>
43
+ import { computed, getCurrentInstance, reactive, toRefs, watch } from 'vue'
44
+ import { commonProps } from '../../libs/composable/useUltraUI.js'
45
+ import { t } from '../../libs/i18n/index.js'
46
+
47
+ const tabHeight = 50
48
+
49
+ defineOptions({
50
+ name: 'up-cropper',
51
+ // #ifdef MP-WEIXIN
52
+ options: {
53
+ virtualHost: true
54
+ }
55
+ // #endif
56
+ })
57
+
58
+ const props = defineProps({
59
+ ...commonProps,
60
+ minScale: { type: [String, Number], default: '' },
61
+ maxScale: { type: [String, Number], default: '' },
62
+ canScale: { type: Boolean, default: true },
63
+ canRotate: { type: Boolean, default: true },
64
+ lockWidth: { type: [String, Number, Boolean], default: '' },
65
+ lockHeight: { type: [String, Number, Boolean], default: '' },
66
+ stretch: { type: String, default: '' },
67
+ lock: { type: String, default: '' },
68
+ noTab: { type: Boolean, default: true },
69
+ inner: { type: Boolean, default: false },
70
+ quality: { type: [String, Number], default: '' },
71
+ // 导出图片格式:png 为无损压缩(quality 不生效),jpg 才会按 quality 压缩
72
+ fileType: { type: String, default: 'png' },
73
+ index: { type: [String, Number], default: '' },
74
+ canChangeSize: { type: Boolean, default: false },
75
+ areaWidth: { type: String, default: '300rpx' },
76
+ areaHeight: { type: String, default: '300rpx' },
77
+ exportWidth: { type: String, default: '260rpx' },
78
+ exportHeight: { type: String, default: '260rpx' },
79
+ fillColor: {
80
+ type: String,
81
+ default: 'transparent'
82
+ },
83
+ avatarSrc: {
84
+ type: String,
85
+ default: ''
86
+ },
87
+ imageSrc: {
88
+ type: String,
89
+ default: ''
90
+ },
91
+ avatarStyle: {
92
+ type: [String, Boolean],
93
+ default: ''
94
+ }
95
+ })
96
+
97
+ const emit = defineEmits(['avtinit', 'confirm', 'cancel'])
98
+ const instance = getCurrentInstance()
99
+ const proxy = instance?.proxy
100
+
101
+ const state = reactive({
102
+ instanceId: Date.now() + '-' + Math.random().toString(36).substr(2, 9),
103
+ // 导出图片格式,可被 chooseImage 的 params 覆盖
104
+ fileType: props.fileType,
105
+ cvsStyleHeight: '0px',
106
+ styleDisplay: 'none',
107
+ styleTop: '-10000px',
108
+ prvTop: '-10000px',
109
+ imgStyle: {},
110
+ selStyle: {},
111
+ showOper: true,
112
+ imgSrc: {
113
+ imgSrc: ''
114
+ },
115
+ btnWidth: '19%',
116
+ btnDsp: 'flex',
117
+ arWidth: '',
118
+ arHeight: '',
119
+ expWidth: '',
120
+ expHeight: '',
121
+ letChangeSize: false,
122
+ safeAreaInsetsBottom: 0,
123
+ // runtime fields (originally assigned on this outside data())
124
+ platform: '',
125
+ pixelRatio: 1,
126
+ windowWidth: 0,
127
+ windowHeight: 0,
128
+ drawTop: 0,
129
+ moreHeight: 0,
130
+ pxRatio: 1,
131
+ ctxCanvas: null,
132
+ ctxCanvasOper: null,
133
+ ctxCanvasPrv: null,
134
+ canvas: null,
135
+ canvasOper: null,
136
+ qlty: 0.9,
137
+ letRotate: 1,
138
+ letScale: 1,
139
+ isin: 0,
140
+ indx: undefined,
141
+ mnScale: 0.3,
142
+ mxScale: 4,
143
+ noBar: 1,
144
+ stc: '',
145
+ lck: '',
146
+ imageSrc: '',
147
+ avatarStyle: '',
148
+ imgPath: '',
149
+ imgWidth: 0,
150
+ imgHeight: 0,
151
+ path: '',
152
+ hasSel: false,
153
+ fSelecting: false,
154
+ fUploading: false,
155
+ fPrvUploading: false,
156
+ fPreviewing: false,
157
+ fRotateing: false,
158
+ fixWidth: 0,
159
+ fixHeight: 0,
160
+ lckWidth: 0,
161
+ lckHeight: 0,
162
+ scaleWidth: 0,
163
+ scaleHeight: 0,
164
+ scaleSize: 1,
165
+ rotateDeg: 0,
166
+ posWidth: 0,
167
+ posHeight: 0,
168
+ useWidth: 0,
169
+ useHeight: 0,
170
+ drawTm: 0,
171
+ prvTm: 0,
172
+ prvImg: '',
173
+ prvImgTmp: '',
174
+ prvImgData: null,
175
+ target: null,
176
+ prvX: 0,
177
+ prvY: 0,
178
+ prvWidth: 0,
179
+ prvHeight: 0,
180
+ touch0: null,
181
+ touch1: null,
182
+ fgDistance: 0,
183
+ resizeHandle: null,
184
+ rtn: undefined
185
+ })
186
+
187
+ // expose reactive fields to template
188
+ const {
189
+ instanceId,
190
+ cvsStyleHeight,
191
+ styleDisplay,
192
+ styleTop,
193
+ prvTop,
194
+ imgStyle,
195
+ selStyle,
196
+ showOper,
197
+ imgSrc,
198
+ btnWidth,
199
+ btnDsp
200
+ } = toRefs(state)
201
+
202
+ // 导出图片格式:png 无损,quality 不生效;只有 jpg 才会按 quality 压缩
203
+ const expFileType = computed(() => {
204
+ const type = String(state.fileType || '').toLowerCase()
205
+ return type === 'jpg' || type === 'jpeg' ? 'jpg' : 'png'
206
+ })
207
+
208
+ // 图片质量归一化:接受 number 与数字字符串,越界夹到 0-1,无效值回落 0.9。
209
+ // 原来用 parseInt,parseInt(0.5) 是 0,`|| 0.9` 会把所有小数质量悄悄改回 0.9
210
+ function parseQuality(value) {
211
+ const num = parseFloat(value)
212
+ if (isNaN(num)) return 0.9
213
+ return Math.min(1, Math.max(0, num))
214
+ }
215
+
216
+ // 底部操作栏样式:把底部安全区算进高度并用 padding-bottom 留出。
217
+ // 画布高度已减掉安全区,而操作栏若仍是 50px 贴屏幕最底,两者之间会露出一条
218
+ // 页面背景,页面上浮动的按钮/标签会从这条缝里透出来,和裁剪底部按钮重叠
219
+ const operStyle = computed(() => {
220
+ const safe = Math.max(0, parseInt(String(state.safeAreaInsetsBottom)) || 0)
221
+ return {
222
+ display: state.styleDisplay,
223
+ height: (tabHeight + safe) + 'px',
224
+ paddingBottom: safe + 'px'
225
+ }
226
+ })
227
+
228
+ function windowResize() {
229
+ let sysInfo = uni.getSystemInfoSync();
230
+ state.platform = sysInfo.platform;
231
+ state.pixelRatio = sysInfo.pixelRatio;
232
+ state.windowWidth = sysInfo.windowWidth;
233
+ // 个别端取不到 safeAreaInsets,兜底为 0,避免算出 NaN 把布局顶飞
234
+ state.safeAreaInsetsBottom = (sysInfo.safeAreaInsets && sysInfo.safeAreaInsets.bottom) || 0;
235
+ // #ifdef H5
236
+ state.drawTop = sysInfo.windowTop;
237
+ state.windowHeight = sysInfo.windowHeight + sysInfo.windowBottom;
238
+ state.cvsStyleHeight = state.windowHeight - tabHeight + 'px';
239
+ // #endif
240
+ // #ifdef APP-PLUS
241
+ if (state.platform === 'android') {
242
+ state.windowHeight = sysInfo.screenHeight + sysInfo.statusBarHeight;
243
+ state.cvsStyleHeight = state.windowHeight - tabHeight + 'px';
244
+ } else {
245
+ state.windowHeight = sysInfo.windowHeight + state.moreHeight;
246
+ state.cvsStyleHeight = state.windowHeight - tabHeight - state.safeAreaInsetsBottom + 6 + 'px';
247
+ }
248
+ // #endif
249
+ // #ifdef MP
250
+ state.windowHeight = sysInfo.windowHeight + state.moreHeight;
251
+ state.cvsStyleHeight = state.windowHeight - tabHeight - state.safeAreaInsetsBottom - 2 + 'px';
252
+ // #endif
253
+ state.pxRatio = state.windowWidth / 750;
254
+
255
+ let style = props.avatarStyle;
256
+ if (style && style !== true && (style = style.trim())) {
257
+ style = style.split(';');
258
+ let obj = {};
259
+ for (let v of style) {
260
+ if (!v) continue;
261
+ v = v.trim().split(':');
262
+ if (v[1].indexOf('rpx') >= 0) {
263
+ let arr = v[1].trim().split(' ');
264
+ for (let k in arr) {
265
+ if (!arr[k]) continue;
266
+ if (arr[k].indexOf('rpx') >= 0) {
267
+ arr[k] = parseFloat(arr[k]) * state.pxRatio + 'px';
268
+ }
269
+ }
270
+ v[1] = arr.join(' ');
271
+ }
272
+ obj[v[0].trim()] = v[1].trim();
273
+ }
274
+ state.imgStyle = obj;
275
+ }
276
+
277
+ state.expWidth && (state.expWidth = state.expWidth.indexOf('rpx') >= 0 ? parseInt(state.expWidth) * state.pxRatio : parseInt(state.expWidth));
278
+ state.expHeight && (state.expHeight = state.expHeight.indexOf('rpx') >= 0 ? parseInt(state.expHeight) * state.pxRatio : parseInt(state.expHeight));
279
+
280
+ if (state.styleDisplay === 'flex') {
281
+ drawInit(true);
282
+ }
283
+ hideImg();
284
+ }
285
+ function loadImage(path) {
286
+ uni.showLoading({ mask: true });
287
+ state.imgPath = path;
288
+ uni.getImageInfo({
289
+ src: path,
290
+ success: r => {
291
+ state.imgWidth = r.width;
292
+ state.imgHeight = r.height;
293
+ state.path = path;
294
+ if (!state.hasSel) {
295
+ let style = state.selStyle || {};
296
+ if (state.arWidth && state.arHeight) {
297
+ let areaWidth = state.arWidth.indexOf('rpx') >= 0 ? parseInt(state.arWidth) * state.pxRatio : parseInt(state.arWidth),
298
+ areaHeight = state.arHeight.indexOf('rpx') >= 0 ? parseInt(state.arHeight) * state.pxRatio : parseInt(state.arHeight);
299
+ style.width = areaWidth + 'px';
300
+ style.height = areaHeight + 'px';
301
+ style.top = (state.windowHeight - areaHeight - tabHeight) / 2 + 'px';
302
+ style.left = (state.windowWidth - areaWidth) / 2 + 'px';
303
+ } else {
304
+ uni.showModal({
305
+ title: t("up.cropper.emptyWidhtOrHeight"),
306
+ showCancel: false
307
+ })
308
+ return;
309
+ }
310
+ state.selStyle = style;
311
+ }
312
+
313
+ if (state.noBar) {
314
+ drawInit(true);
315
+ } else {
316
+ uni.hideTabBar({
317
+ complete: () => {
318
+ drawInit(true);
319
+ }
320
+ });
321
+ }
322
+ },
323
+ fail: () => {
324
+ uni.showToast({
325
+ title: "error3",
326
+ duration: 2000,
327
+ })
328
+ },
329
+ complete() {
330
+ uni.hideLoading();
331
+ }
332
+ });
333
+ }
334
+ function select() {
335
+ if (state.fSelecting) return;
336
+ state.fSelecting = true;
337
+ setTimeout(() => { state.fSelecting = false; }, 500);
338
+ uni.chooseImage({
339
+ count: 1,
340
+ sizeType: ['original', 'compressed'],
341
+ sourceType: ['album', 'camera'],
342
+ success: (r) => {
343
+ loadImage(r.tempFilePaths[0]);
344
+ }, fail(err) {
345
+ emit('cancel')
346
+ }
347
+ })
348
+ }
349
+ function confirm() {
350
+ if (state.fUploading) return;
351
+ state.fUploading = true;
352
+ setTimeout(() => { state.fUploading = false; }, 1000)
353
+
354
+ let style = state.selStyle,
355
+ x = parseInt(style.left),
356
+ y = parseInt(style.top),
357
+ width = parseInt(style.width),
358
+ height = parseInt(style.height),
359
+ expWidth = state.expWidth || width,
360
+ expHeight = state.expHeight || height;
361
+
362
+ // #ifdef H5
363
+ // x *= state.pixelRatio;
364
+ // y *= state.pixelRatio;
365
+ expWidth = width;
366
+ expHeight = height;
367
+ // #endif
368
+
369
+ uni.showLoading({ mask: true });
370
+ state.styleDisplay = 'none';
371
+ state.styleTop = '-10000px';
372
+ state.hasSel = false;
373
+ hideImg();
374
+ uni.canvasToTempFilePath({
375
+ x: x,
376
+ y: y,
377
+ width: width,
378
+ height: height,
379
+ destWidth: expWidth,
380
+ destHeight: expHeight,
381
+ canvasId: 'avatar-canvas-' + state.instanceId,
382
+ fileType: expFileType.value,
383
+ quality: state.qlty,
384
+ success: (r) => {
385
+ r = r.tempFilePath;
386
+ // #ifdef H5
387
+ btop(r).then((r) => {
388
+ if (state.expWidth && state.expHeight) {
389
+ let ctxCanvas = state.ctxCanvas;
390
+ expWidth = state.expWidth,
391
+ expHeight = state.expHeight;
392
+
393
+ ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
394
+ ctxCanvas.draw(false, () => {
395
+ uni.canvasToTempFilePath({
396
+ x: 0,
397
+ y: 0,
398
+ width: expWidth,
399
+ height: expHeight,
400
+ destWidth: expWidth,
401
+ destHeight: expHeight,
402
+ canvasId: 'avatar-canvas-' + state.instanceId,
403
+ fileType: expFileType.value,
404
+ quality: state.qlty,
405
+ success: (r) => {
406
+ r = r.tempFilePath;
407
+ btop(r).then((r) => {
408
+ emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
409
+ });
410
+ },
411
+ fail: () => {
412
+ uni.showToast({
413
+ title: "error0",
414
+ duration: 2000,
415
+ })
416
+ }
417
+ });
418
+ });
419
+ } else {
420
+ emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
421
+ }
422
+ })
423
+ // #endif
424
+ // #ifndef H5
425
+ emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
426
+ // #endif
427
+ },
428
+ fail: (res) => {
429
+ uni.showToast({
430
+ title: "error1",
431
+ duration: 2000,
432
+ })
433
+ },
434
+ complete: () => {
435
+ uni.hideLoading();
436
+ state.noBar || uni.showTabBar();
437
+ }
438
+ }, proxy);
439
+ }
440
+ // 用户点击"预览"模式下的"确认"按钮时被调用,用于将预览的裁剪结果上传
441
+ function prvUpload() {
442
+ if (state.fPrvUploading) return;
443
+ state.fPrvUploading = true;
444
+ setTimeout(() => { state.fPrvUploading = false; }, 1000)
445
+
446
+ let style = state.selStyle,
447
+ destWidth = parseInt(style.width),
448
+ destHeight = parseInt(style.height),
449
+ prvX = state.prvX,
450
+ prvY = state.prvY,
451
+ prvWidth = state.prvWidth,
452
+ prvHeight = state.prvHeight,
453
+ expWidth = state.expWidth || prvWidth,
454
+ expHeight = state.expHeight || prvHeight;
455
+
456
+ // #ifdef H5
457
+ // prvX *= state.pixelRatio;
458
+ // prvY *= state.pixelRatio;
459
+ expWidth = prvWidth;
460
+ expHeight = prvHeight;
461
+ // #endif
462
+
463
+ uni.showLoading({ mask: true });
464
+
465
+ state.styleDisplay = 'none';
466
+ state.styleTop = '-10000px';
467
+ state.hasSel = false;
468
+ hideImg();
469
+ uni.canvasToTempFilePath({
470
+ x: prvX,
471
+ y: prvY,
472
+ width: prvWidth,
473
+ height: prvHeight,
474
+ destWidth: expWidth,
475
+ destHeight: expHeight,
476
+ canvasId: 'prv-canvas-' + state.instanceId,
477
+ fileType: expFileType.value,
478
+ quality: state.qlty,
479
+ success: (r) => {
480
+ r = r.tempFilePath;
481
+ // #ifdef H5
482
+ btop(r).then((r) => {
483
+ if (state.expWidth && state.expHeight) {
484
+ let ctxCanvas = state.ctxCanvas;
485
+ expWidth = state.expWidth,
486
+ expHeight = state.expHeight;
487
+
488
+ ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
489
+ ctxCanvas.draw(false, () => {
490
+ uni.canvasToTempFilePath({
491
+ x: 0,
492
+ y: 0,
493
+ width: expWidth,
494
+ height: expHeight,
495
+ destWidth: expWidth,
496
+ destHeight: expHeight,
497
+ canvasId: 'avatar-canvas-' + state.instanceId,
498
+ fileType: expFileType.value,
499
+ quality: state.qlty,
500
+ success: (r) => {
501
+ r = r.tempFilePath;
502
+ btop(r).then((r) => {
503
+ emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
504
+ });
505
+ },
506
+ fail: () => {
507
+ uni.showToast({
508
+ title: "error0",
509
+ duration: 2000,
510
+ })
511
+ }
512
+ });
513
+ });
514
+ } else {
515
+ emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
516
+ }
517
+ })
518
+ // #endif
519
+ // #ifndef H5
520
+ emit("confirm", { avatar: state.imgSrc, path: r, index: state.indx, data: state.rtn });
521
+ // #endif
522
+ },
523
+ fail: () => {
524
+ uni.showToast({
525
+ title: "error_prv",
526
+ duration: 2000,
527
+ })
528
+ },
529
+ complete: () => {
530
+ uni.hideLoading();
531
+ state.noBar || uni.showTabBar();
532
+ }
533
+ }, proxy);
534
+ }
535
+ function drawInit(ini = false) {
536
+ let allWidth = state.windowWidth,
537
+ allHeight = state.windowHeight,
538
+ imgWidth = state.imgWidth,
539
+ imgHeight = state.imgHeight,
540
+ imgRadio = imgWidth / imgHeight,
541
+ useWidth = allWidth - 40,
542
+ useHeight = allHeight - tabHeight - 80,
543
+ pixelRatio = state.pixelRatio,
544
+ selWidth = parseInt(state.selStyle.width),
545
+ selHeight = parseInt(state.selStyle.height);
546
+
547
+ state.fixWidth = 0;
548
+ state.fixHeight = 0;
549
+ state.lckWidth = 0;
550
+ state.lckHeight = 0;
551
+ switch (state.stc) {
552
+ case 'x': state.fixWidth = 1; break;
553
+ case 'y': state.fixHeight = 1; break;
554
+ case 'long': if (imgRadio > 1) state.fixWidth = 1; else state.fixHeight = 1; break;
555
+ case 'short': if (imgRadio > 1) state.fixHeight = 1; else state.fixWidth = 1; break;
556
+ case 'longSel': if (selWidth > selHeight) state.fixWidth = 1; else state.fixHeight = 1; break;
557
+ case 'shortSel': if (selWidth > selHeight) state.fixHeight = 1; else state.fixWidth = 1; break;
558
+ }
559
+ // lck 用于控制裁剪框的宽度和高度锁定行为
560
+ // 'x': 锁定宽度,不允许水平方向调整
561
+ // 'y': 锁定高度,不允许垂直方向调整
562
+ // 'long': 根据图片长边锁定,如果图片横向较长则锁定宽度,否则锁定高度
563
+ // 'short': 根据图片短边锁定,如果图片横向较长则锁定高度,否则锁定宽度
564
+ // 'longSel': 根据选择框的长边锁定,如果选择框宽度大于高度则锁定宽度,否则锁定高度
565
+ // 'shortSel': 根据选择框的短边锁定,如果选择框宽度大于高度则锁定高度,否则锁定宽度
566
+ switch (state.lck) {
567
+ case 'x': state.lckWidth = 1; break;
568
+ case 'y': state.lckHeight = 1; break;
569
+ case 'long': if (imgRadio > 1) state.lckWidth = 1; else state.lckHeight = 1; break;
570
+ case 'short': if (imgRadio > 1) state.lckHeight = 1; else state.lckWidth = 1; break;
571
+ case 'longSel': if (selWidth > selHeight) state.lckWidth = 1; else state.lckHeight = 1; break;
572
+ case 'shortSel': if (selWidth > selHeight) state.lckHeight = 1; else state.lckWidth = 1; break;
573
+ }
574
+ if (state.fixWidth) {
575
+ useWidth = selWidth;
576
+ useHeight = useWidth / imgRadio;
577
+ } else if (state.fixHeight) {
578
+ useHeight = selHeight;
579
+ useWidth = useHeight * imgRadio;
580
+ } else if (imgRadio < 1) {
581
+ if (imgHeight < useHeight) {
582
+ useWidth = imgWidth;
583
+ useHeight = imgHeight;
584
+ } else {
585
+ useHeight = useHeight;
586
+ useWidth = useHeight * imgRadio;
587
+ }
588
+ } else {
589
+ if (imgWidth < useWidth) {
590
+ useWidth = imgWidth;
591
+ useHeight = imgHeight;
592
+ } else {
593
+ useWidth = useWidth;
594
+ useHeight = useWidth / imgRadio;
595
+ }
596
+ }
597
+ if (state.isin) {
598
+ state.scaleWidth = 0;
599
+ state.scaleHeight = 0;
600
+ if (useWidth < selWidth) {
601
+ useWidth = selWidth;
602
+ useHeight = useWidth / imgRadio;
603
+ state.lckHeight = 0;
604
+ }
605
+ if (useHeight < selHeight) {
606
+ useHeight = selHeight;
607
+ useWidth = useHeight * imgRadio;
608
+ state.lckWidth = 0;
609
+ }
610
+ }
611
+
612
+ state.scaleSize = 1;
613
+ state.rotateDeg = 0;
614
+ state.posWidth = (allWidth - useWidth) / 2;
615
+ state.posHeight = (allHeight - useHeight - tabHeight) / 2;
616
+ state.useWidth = useWidth;
617
+ state.useHeight = useHeight;
618
+
619
+ let style = state.selStyle,
620
+ left = parseInt(style.left),
621
+ top = parseInt(style.top),
622
+ width = parseInt(style.width),
623
+ height = parseInt(style.height),
624
+ canvas = state.canvas,
625
+ canvasOper = state.canvasOper,
626
+ ctxCanvas = state.ctxCanvas,
627
+ ctxCanvasOper = state.ctxCanvasOper;
628
+
629
+ ctxCanvasOper.setLineWidth(3);
630
+ ctxCanvasOper.setStrokeStyle('grey');
631
+ ctxCanvasOper.setGlobalAlpha(0.4);
632
+ ctxCanvasOper.setFillStyle('black');
633
+ ctxCanvasOper.strokeRect(left, top, width, height);
634
+ ctxCanvasOper.fillRect(0, 0, state.windowWidth, top);
635
+ ctxCanvasOper.fillRect(0, top, left, height);
636
+ ctxCanvasOper.fillRect(0, top + height, state.windowWidth, state.windowHeight - height - top - tabHeight);
637
+ ctxCanvasOper.fillRect(left + width, top, state.windowWidth - width - left, height);
638
+ ctxCanvasOper.setStrokeStyle('red');
639
+ ctxCanvasOper.moveTo(left + 20, top); ctxCanvasOper.lineTo(left, top); ctxCanvasOper.lineTo(left, top + 20);
640
+ ctxCanvasOper.moveTo(left + width - 20, top); ctxCanvasOper.lineTo(left + width, top); ctxCanvasOper.lineTo(left + width, top + 20);
641
+ ctxCanvasOper.moveTo(left + 20, top + height); ctxCanvasOper.lineTo(left, top + height); ctxCanvasOper.lineTo(left, top + height - 20);
642
+ ctxCanvasOper.moveTo(left + width - 20, top + height); ctxCanvasOper.lineTo(left + width, top + height); ctxCanvasOper.lineTo(left + width, top + height - 20);
643
+
644
+ // 绘制控制点(四个角)
645
+ const controlPointSize = 10;
646
+ ctxCanvasOper.setFillStyle('white');
647
+ ctxCanvasOper.setStrokeStyle('grey');
648
+ ctxCanvasOper.setLineWidth(1);
649
+ // 左上角
650
+ ctxCanvasOper.fillRect(left - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
651
+ ctxCanvasOper.strokeRect(left - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
652
+ // 右上角
653
+ ctxCanvasOper.fillRect(left + width - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
654
+ ctxCanvasOper.strokeRect(left + width - controlPointSize / 2, top - controlPointSize / 2, controlPointSize, controlPointSize);
655
+ // 左下角
656
+ ctxCanvasOper.fillRect(left - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
657
+ ctxCanvasOper.strokeRect(left - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
658
+ // 右下角
659
+ ctxCanvasOper.fillRect(left + width - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
660
+ ctxCanvasOper.strokeRect(left + width - controlPointSize / 2, top + height - controlPointSize / 2, controlPointSize, controlPointSize);
661
+
662
+ ctxCanvasOper.stroke();
663
+
664
+ ctxCanvasOper.draw(false, () => {
665
+ if (ini) {
666
+ state.styleDisplay = 'flex';
667
+ // #ifdef H5
668
+ state.styleTop = state.drawTop + 'px';
669
+ // #endif
670
+ // #ifndef H5
671
+ state.styleTop = '0';
672
+ // #endif
673
+ if (props.fillColor && props.fillColor !== 'transparent') {
674
+ ctxCanvas.setFillStyle(props.fillColor);
675
+ }
676
+ drawImage();
677
+ }
678
+ });
679
+
680
+ emit("avtinit");
681
+ }
682
+ function drawImage() {
683
+ let tm_now = Date.now();
684
+ if (tm_now - state.drawTm < 20) return;
685
+ state.drawTm = tm_now;
686
+ let ctxCanvas = state.ctxCanvas;
687
+ if (props.fillColor && props.fillColor !== 'transparent') {
688
+ ctxCanvas.fillRect(0, 0, state.windowWidth, state.windowHeight - tabHeight);
689
+ }
690
+ ctxCanvas.translate(state.posWidth + state.useWidth / 2, state.posHeight + state.useHeight / 2);
691
+ ctxCanvas.scale(state.scaleSize, state.scaleSize);
692
+ ctxCanvas.rotate(state.rotateDeg * Math.PI / 180);
693
+ ctxCanvas.drawImage(state.imgPath, -state.useWidth / 2, -state.useHeight / 2, state.useWidth, state.useHeight);
694
+ ctxCanvas.draw(false);
695
+ }
696
+ function hideImg() {
697
+ state.prvImg = '';
698
+ state.prvTop = '-10000px';
699
+ state.showOper = true;
700
+ state.prvImgData = null;
701
+ state.target = null;
702
+ }
703
+ function close() {
704
+ console.log('up-cropper close');
705
+
706
+ state.styleDisplay = 'none';
707
+ state.styleTop = '-10000px';
708
+ state.hasSel = false;
709
+ hideImg();
710
+ state.noBar || uni.showTabBar();
711
+ emit('cancel');
712
+ }
713
+ function preview() {
714
+ if (state.fPreviewing) return;
715
+ state.fPreviewing = true;
716
+ setTimeout(() => { state.fPreviewing = false; }, 1000);
717
+ let style = state.selStyle,
718
+ x = parseInt(style.left),
719
+ y = parseInt(style.top),
720
+ width = parseInt(style.width),
721
+ height = parseInt(style.height);
722
+
723
+ uni.showLoading({ mask: true });
724
+ // console.log('size', x, y, width, height)
725
+ uni.canvasToTempFilePath({
726
+ x: x,
727
+ y: y,
728
+ width: width,
729
+ height: height,
730
+ canvasId: 'avatar-canvas-' + state.instanceId,
731
+ fileType: expFileType.value,
732
+ quality: state.qlty,
733
+ success: (r) => {
734
+ // console.log(r)
735
+ state.prvImgTmp = r = r.tempFilePath;
736
+
737
+ let ctxCanvasPrv = state.ctxCanvasPrv,
738
+ prvX = state.windowWidth,
739
+ prvY = parseInt(state.cvsStyleHeight),
740
+ prvWidth = parseInt(state.selStyle.width),
741
+ prvHeight = parseInt(state.selStyle.height),
742
+ useWidth = prvX - 40,
743
+ useHeight = prvY - 80,
744
+ radio = useWidth / prvWidth,
745
+ rHeight = prvHeight * radio;
746
+ if (rHeight < useHeight) {
747
+ prvWidth = useWidth;
748
+ prvHeight = rHeight;
749
+ } else {
750
+ radio = useHeight / prvHeight;
751
+ prvWidth *= radio;
752
+ prvHeight = useHeight;
753
+ }
754
+ if (props.fillColor && props.fillColor !== 'transparent') {
755
+ ctxCanvasPrv.setFillStyle(props.fillColor);
756
+ ctxCanvasPrv.fillRect(0, 0, prvX, prvY);
757
+ ctxCanvasPrv.fillRect(x, y, width, height);
758
+ }
759
+ state.prvX = prvX = (prvX - prvWidth) / 2;
760
+ state.prvY = prvY = (prvY - prvHeight) / 2;
761
+ state.prvWidth = prvWidth;
762
+ state.prvHeight = prvHeight;
763
+ ctxCanvasPrv.drawImage(r, prvX, prvY, prvWidth, prvHeight);
764
+ ctxCanvasPrv.draw(false, () => {
765
+ // #ifdef H5
766
+ btop(state.prvImgTmp).then((r) => {
767
+ state.showOper = false;
768
+ state.prvTop = state.drawTop + 'px';
769
+ })
770
+ // #endif
771
+ // #ifndef H5
772
+ if (state.platform != 'android') {
773
+ state.showOper = false;
774
+ }
775
+ state.prvTop = '0';
776
+ // #endif
777
+ });
778
+ },
779
+ fail: () => {
780
+ uni.showToast({
781
+ title: "error2",
782
+ duration: 2000,
783
+ })
784
+ },
785
+ complete: () => {
786
+ uni.hideLoading();
787
+ }
788
+ }, proxy);
789
+ }
790
+ function chooseImage(index = undefined, params = undefined, data = undefined) {
791
+ const imageSrc = typeof params?.imageSrc === 'string' ? params.imageSrc.trim() : '';
792
+ if (params) {
793
+ console.log(params)
794
+ let areaWidth = params.areaWidth || props.areaWidth,
795
+ areaHeight = params.areaHeight || props.areaHeight,
796
+ expWidth = params.exportWidth || props.exportWidth,
797
+ expHeight = params.exportHeight || props.exportHeight,
798
+ quality = params.quality,
799
+ fileType = params.fileType,
800
+ canRotate = params.canRotate,
801
+ canScale = params.canScale,
802
+ canChangeSize = params.canChangeSize,
803
+ minScale = params.minScale,
804
+ maxScale = params.maxScale,
805
+ stretch = params.stretch,
806
+ inner = params.inner,
807
+ lock = params.lock;
808
+ console.log('areaWidth', props.areaWidth)
809
+
810
+ expWidth && (state.expWidth = expWidth.indexOf('rpx') >= 0 ? parseInt(expWidth) * state.pxRatio : parseInt(expWidth));
811
+ expHeight && (state.expHeight = expHeight.indexOf('rpx') >= 0 ? parseInt(expHeight) * state.pxRatio : parseInt(expHeight));
812
+ state.isin = inner === true ? 1 : 0;
813
+ state.letRotate = (canRotate === false || state.isin) ? 0 : 1;
814
+ state.letScale = canScale === false ? 0 : 1;
815
+ // 设置是否允许调整裁剪框大小
816
+ state.letChangeSize = canChangeSize || false;
817
+ state.qlty = parseQuality(quality)
818
+ // 单次调用可通过 params.fileType 覆盖组件的 fileType
819
+ if (fileType) state.fileType = fileType;
820
+ state.mnScale = minScale || 0.3;
821
+ state.mxScale = maxScale || 4;
822
+ state.stc = stretch;
823
+ state.lck = lock;
824
+ if (state.isin) {
825
+ state.btnWidth = '24%';
826
+ state.btnDsp = 'none';
827
+ } else {
828
+ state.btnWidth = '19%';
829
+ state.btnDsp = 'flex';
830
+ }
831
+
832
+ if (areaWidth && areaHeight) {
833
+ areaWidth = areaWidth.indexOf('rpx') >= 0 ? parseInt(areaWidth) * state.pxRatio : parseInt(areaWidth);
834
+ areaHeight = areaHeight.indexOf('rpx') >= 0 ? parseInt(areaHeight) * state.pxRatio : parseInt(areaHeight);
835
+ state.selStyle.width = areaWidth + 'px';
836
+ state.selStyle.height = areaHeight + 'px';
837
+ state.selStyle.top = (state.windowHeight - areaHeight - tabHeight) / 2 + 'px';
838
+ state.selStyle.left = (state.windowWidth - areaWidth) / 2 + 'px';
839
+ // console.log(state.selStyle);
840
+ state.hasSel = true;
841
+ }
842
+ }
843
+ state.rtn = data;
844
+ state.indx = index;
845
+ // 业务方已自行拍照/选图时,直接裁剪传入的路径,不再打开系统选图
846
+ if (imageSrc) {
847
+ loadImage(imageSrc);
848
+ return;
849
+ }
850
+ select();
851
+ }
852
+ function rotate() {
853
+ // #ifdef APP-PLUS
854
+ if (state.platform === 'android') {
855
+ if (state.fRotateing) return;
856
+ state.fRotateing = true;
857
+ setTimeout(() => { state.fRotateing = false; }, 500);
858
+ }
859
+ // #endif
860
+
861
+ // if(state.letRotate) {
862
+ state.rotateDeg += 90 - state.rotateDeg % 90;
863
+ drawImage();
864
+ // }
865
+ }
866
+ function start(e) {
867
+ let touches = e.touches,
868
+ touch0 = touches[0],
869
+ touch1 = touches[1];
870
+
871
+ state.touch0 = touch0;
872
+ state.touch1 = touch1;
873
+
874
+ if (touch1) {
875
+ let x = touch1.x - touch0.x,
876
+ y = touch1.y - touch0.y;
877
+ state.fgDistance = Math.sqrt(x * x + y * y);
878
+ } else {
879
+ // 只有在允许调整大小时才检查控制点
880
+ if (state.letChangeSize) {
881
+ // 检查是否点击在控制点上
882
+ const controlPointSize = 20;
883
+ const x = touch0.x;
884
+ const y = touch0.y;
885
+ const style = state.selStyle;
886
+ const left = parseInt(style.left);
887
+ const top = parseInt(style.top);
888
+ const width = parseInt(style.width);
889
+ const height = parseInt(style.height);
890
+
891
+ // 检查四个控制点
892
+ if (Math.abs(x - left) < controlPointSize && Math.abs(y - top) < controlPointSize) {
893
+ state.resizeHandle = 'top-left';
894
+ } else if (Math.abs(x - (left + width)) < controlPointSize && Math.abs(y - top) < controlPointSize) {
895
+ state.resizeHandle = 'top-right';
896
+ } else if (Math.abs(x - left) < controlPointSize && Math.abs(y - (top + height)) < controlPointSize) {
897
+ state.resizeHandle = 'bottom-left';
898
+ } else if (Math.abs(x - (left + width)) < controlPointSize && Math.abs(y - (top + height)) < controlPointSize) {
899
+ state.resizeHandle = 'bottom-right';
900
+ } else {
901
+ state.resizeHandle = null;
902
+ }
903
+ } else {
904
+ state.resizeHandle = null;
905
+ }
906
+ }
907
+ }
908
+ function move(e) {
909
+ let touches = e.touches,
910
+ touch0 = touches[0],
911
+ touch1 = touches[1];
912
+
913
+ if (touch1) {
914
+ let x = touch1.x - touch0.x,
915
+ y = touch1.y - touch0.y,
916
+ fgDistance = Math.sqrt(x * x + y * y),
917
+ scaleSize = 0.005 * (fgDistance - state.fgDistance),
918
+ beScaleSize = state.scaleSize + scaleSize;
919
+
920
+ do {
921
+ if (!state.letScale) break;
922
+ if (beScaleSize < state.mnScale) break;
923
+ if (beScaleSize > state.mxScale) break;
924
+ if (state.isin) {
925
+ let imgWidth = state.useWidth * beScaleSize,
926
+ imgHeight = state.useHeight * beScaleSize,
927
+ rx0 = state.posWidth + state.useWidth / 2,
928
+ ry0 = state.posHeight + state.useHeight / 2,
929
+ l = rx0 - imgWidth / 2, t = ry0 - imgHeight / 2,
930
+ r = l + imgWidth, b = t + imgHeight,
931
+ left = parseInt(state.selStyle.left),
932
+ top = parseInt(state.selStyle.top),
933
+ width = parseInt(state.selStyle.width),
934
+ height = parseInt(state.selStyle.height);
935
+ if (left < l || left + width > r || top < t || top + height > b) break;
936
+ state.scaleWidth = (state.useWidth - imgWidth) / 2;
937
+ state.scaleHeight = (state.useHeight - imgHeight) / 2;
938
+ }
939
+
940
+ state.scaleSize = beScaleSize;
941
+ } while (0);
942
+ state.fgDistance = fgDistance;
943
+
944
+ if (touch1.x !== touch0.x && state.letRotate) {
945
+ x = (state.touch1.y - state.touch0.y) / (state.touch1.x - state.touch0.x);
946
+ y = (touch1.y - touch0.y) / (touch1.x - touch0.x);
947
+ state.rotateDeg += Math.atan((y - x) / (1 + x * y)) * 180 / Math.PI;
948
+ state.touch0 = touch0;
949
+ state.touch1 = touch1;
950
+ }
951
+
952
+ drawImage();
953
+ } else if (state.touch0) {
954
+ // 只有在允许调整大小时才处理裁剪框大小调整
955
+ if (state.resizeHandle && state.letChangeSize) {
956
+ const style = { ...state.selStyle };
957
+ const left = parseInt(style.left);
958
+ const top = parseInt(style.top);
959
+ const width = parseInt(style.width);
960
+ const height = parseInt(style.height);
961
+ const minWidth = 50;
962
+ const minHeight = 50;
963
+
964
+ switch (state.resizeHandle) {
965
+ case 'top-left':
966
+ style.left = touch0.x + 'px';
967
+ style.top = touch0.y + 'px';
968
+ style.width = (left + width - touch0.x) + 'px';
969
+ style.height = (top + height - touch0.y) + 'px';
970
+ break;
971
+ case 'top-right':
972
+ style.top = touch0.y + 'px';
973
+ style.width = (touch0.x - left) + 'px';
974
+ style.height = (top + height - touch0.y) + 'px';
975
+ break;
976
+ case 'bottom-left':
977
+ style.left = touch0.x + 'px';
978
+ style.width = (left + width - touch0.x) + 'px';
979
+ style.height = (touch0.y - top) + 'px';
980
+ break;
981
+ case 'bottom-right':
982
+ style.width = (touch0.x - left) + 'px';
983
+ style.height = (touch0.y - top) + 'px';
984
+ break;
985
+ }
986
+
987
+ // 约束裁剪框:最小尺寸、屏幕边界,inner 模式下再限制在图片内
988
+ let nl = parseInt(style.left);
989
+ let nt = parseInt(style.top);
990
+ let nw = parseInt(style.width);
991
+ let nh = parseInt(style.height);
992
+
993
+ // 屏幕边界
994
+ if (nl < 0) nl = 0;
995
+ if (nt < 0) nt = 0;
996
+ if (nl + nw > state.windowWidth) nw = state.windowWidth - nl;
997
+ if (nt + nh > state.windowHeight - tabHeight) nh = state.windowHeight - tabHeight - nt;
998
+
999
+ if (state.isin) {
1000
+ const imgWidth = state.useWidth * state.scaleSize;
1001
+ const imgHeight = state.useHeight * state.scaleSize;
1002
+ const rx0 = state.posWidth + state.useWidth / 2;
1003
+ const ry0 = state.posHeight + state.useHeight / 2;
1004
+ const imgL = rx0 - imgWidth / 2;
1005
+ const imgT = ry0 - imgHeight / 2;
1006
+ const imgR = imgL + imgWidth;
1007
+ const imgB = imgT + imgHeight;
1008
+ if (nl < imgL) nl = imgL;
1009
+ if (nt < imgT) nt = imgT;
1010
+ if (nl + nw > imgR) nw = imgR - nl;
1011
+ if (nt + nh > imgB) nh = imgB - nt;
1012
+ }
1013
+
1014
+ if (nw >= minWidth && nh >= minHeight) {
1015
+ style.left = nl + 'px';
1016
+ style.top = nt + 'px';
1017
+ style.width = nw + 'px';
1018
+ style.height = nh + 'px';
1019
+ state.selStyle = style;
1020
+ // 重新绘制操作层
1021
+ drawInit();
1022
+ }
1023
+ } else {
1024
+ // 原有的移动图片逻辑
1025
+ let x = touch0.x - state.touch0.x,
1026
+ y = touch0.y - state.touch0.y,
1027
+ beX = state.posWidth + x,
1028
+ beY = state.posHeight + y;
1029
+ if (state.isin) {
1030
+ let imgWidth = state.useWidth * state.scaleSize,
1031
+ imgHeight = state.useHeight * state.scaleSize,
1032
+ rx0 = beX + state.useWidth / 2,
1033
+ ry0 = beY + state.useHeight / 2,
1034
+ l = rx0 - imgWidth / 2, t = ry0 - imgHeight / 2,
1035
+ r = l + imgWidth, b = t + imgHeight,
1036
+ left = parseInt(state.selStyle.left),
1037
+ top = parseInt(state.selStyle.top),
1038
+ width = parseInt(state.selStyle.width),
1039
+ height = parseInt(state.selStyle.height);
1040
+ if (!state.lckWidth && Math.abs(x) < 100) {
1041
+ if (left >= l && left + width <= r) {
1042
+ state.posWidth = beX;
1043
+ } else if (left < l) {
1044
+ state.posWidth = left - state.scaleWidth;
1045
+ } else if (left + width > r) {
1046
+ state.posWidth = left - (imgWidth - width) - state.scaleWidth;
1047
+ }
1048
+ }
1049
+ if (!state.lckHeight && Math.abs(y) < 100) {
1050
+ if (top >= t && top + height <= b) {
1051
+ state.posHeight = beY;
1052
+ } else if (top < t) {
1053
+ state.posHeight = top - state.scaleHeight;
1054
+ } else if (top + height > b) {
1055
+ state.posHeight = top - (imgHeight - height) - state.scaleHeight;
1056
+ }
1057
+ }
1058
+ } else {
1059
+ if (Math.abs(x) < 100 && !state.lckWidth) state.posWidth = beX;
1060
+ if (Math.abs(y) < 100 && !state.lckHeight) state.posHeight = beY;
1061
+ }
1062
+
1063
+ state.touch0 = touch0;
1064
+ drawImage();
1065
+ }
1066
+ }
1067
+ }
1068
+ function end(e) {
1069
+ let touches = e.touches,
1070
+ touch0 = touches && touches[0],
1071
+ touch1 = touches && touches[1];
1072
+ if (touch0) {
1073
+ state.touch0 = touch0;
1074
+ } else {
1075
+ state.touch0 = null;
1076
+ state.touch1 = null;
1077
+ state.resizeHandle = null; // 重置调整手柄
1078
+ }
1079
+ }
1080
+ function getImgData() {
1081
+ return new Promise((resolve, reject) => {
1082
+ let prvX = state.prvX,
1083
+ prvY = state.prvY,
1084
+ prvWidth = state.prvWidth,
1085
+ prvHeight = state.prvHeight;
1086
+ // #ifdef APP-PLUS||H5
1087
+ prvX *= state.pixelRatio;
1088
+ prvY *= state.pixelRatio;
1089
+ prvWidth *= state.pixelRatio;
1090
+ prvHeight *= state.pixelRatio;
1091
+ // #endif
1092
+ uni.canvasGetImageData({
1093
+ canvasId: 'prv-canvas-' + state.instanceId,
1094
+ x: prvX,
1095
+ y: prvY,
1096
+ width: prvWidth,
1097
+ height: prvHeight,
1098
+ success(res) {
1099
+ resolve(res.data);
1100
+ },
1101
+ fail(err) {
1102
+ reject(err);
1103
+ }
1104
+ }, proxy);
1105
+ });
1106
+ }
1107
+ async function colorChange(e) {
1108
+ let tm_now = Date.now();
1109
+ if (tm_now - state.prvTm < 100) return;
1110
+ state.prvTm = tm_now;
1111
+
1112
+ uni.showLoading({ mask: true });
1113
+
1114
+ if (!state.prvImgData) {
1115
+ if (!(state.prvImgData = await getImgData().catch((res) => {
1116
+ uni.showToast({
1117
+ title: "error_read",
1118
+ duration: 2000,
1119
+ })
1120
+ }))) return;
1121
+ state.target = new Uint8ClampedArray(state.prvImgData.length);
1122
+ }
1123
+
1124
+ let data = state.prvImgData,
1125
+ target = state.target,
1126
+ i = e.detail.value,
1127
+ r, g, b, a, h, s, l, d, p, q, t, min, max, hK, tR, tG, tB;
1128
+
1129
+ if (i === 0) {
1130
+ target = data;
1131
+ } else {
1132
+ i = (i + 100) / 200;
1133
+ if (i < 0.005) i = 0;
1134
+ if (i > 0.995) i = 1;
1135
+ for (let n = data.length - 1; n >= 0; n -= 4) {
1136
+ r = data[n - 3] / 255;
1137
+ g = data[n - 2] / 255;
1138
+ b = data[n - 1] / 255;
1139
+ max = Math.max(r, g, b);
1140
+ min = Math.min(r, g, b);
1141
+ d = max - min;
1142
+ if (max === min) {
1143
+ h = 0;
1144
+ } else if (max === r && g >= b) {
1145
+ h = 60 * ((g - b) / d);
1146
+ } else if (max === r && g < b) {
1147
+ h = 60 * ((g - b) / d) + 360;
1148
+ } else if (max === g) {
1149
+ h = 60 * ((b - r) / d) + 120;
1150
+ } else if (max === b) {
1151
+ h = 60 * ((r - g) / d) + 240;
1152
+ }
1153
+ l = (max + min) / 2;
1154
+ if (l === 0 || max === min) {
1155
+ s = 0;
1156
+ } else if (0 < l && l <= 0.5) {
1157
+ s = d / (2 * l);
1158
+ } else if (l > 0.5) {
1159
+ s = d / (2 - 2 * l);
1160
+ }
1161
+ data[n] && (a = data[n]);
1162
+
1163
+ if (i < 0.5) {
1164
+ s = s * i / 0.5;
1165
+ } else if (i > 0.5) {
1166
+ s = 2 * s + 2 * i - (s * i / 0.5) - 1;
1167
+ }
1168
+
1169
+ if (s === 0) {
1170
+ r = g = b = Math.round(l * 255);
1171
+ } else {
1172
+ if (l < 0.5) {
1173
+ q = l * (1 + s);
1174
+ } else if (l >= 0.5) {
1175
+ q = l + s - (l * s);
1176
+ }
1177
+ p = 2 * l - q;
1178
+ hK = h / 360;
1179
+ tR = hK + 1 / 3;
1180
+ tG = hK;
1181
+ tB = hK - 1 / 3;
1182
+ let correctRGB = (t) => {
1183
+ if (t < 0) {
1184
+ return t + 1.0;
1185
+ }
1186
+ if (t > 1) {
1187
+ return t - 1.0;
1188
+ }
1189
+ return t;
1190
+ };
1191
+ let createRGB = (t) => {
1192
+ if (t < (1 / 6)) {
1193
+ return p + ((q - p) * 6 * t);
1194
+ } else if (t >= (1 / 6) && t < (1 / 2)) {
1195
+ return q;
1196
+ } else if (t >= (1 / 2) && t < (2 / 3)) {
1197
+ return p + ((q - p) * 6 * ((2 / 3) - t));
1198
+ }
1199
+ return p;
1200
+ };
1201
+ r = tR = Math.round(createRGB(correctRGB(tR)) * 255);
1202
+ g = tG = Math.round(createRGB(correctRGB(tG)) * 255);
1203
+ b = tB = Math.round(createRGB(correctRGB(tB)) * 255);
1204
+ }
1205
+ a && (target[n] = a);
1206
+ target[n - 3] = r;
1207
+ target[n - 2] = g;
1208
+ target[n - 1] = b;
1209
+ }
1210
+ }
1211
+ let prvX = state.prvX,
1212
+ prvY = state.prvY,
1213
+ prvWidth = state.prvWidth,
1214
+ prvHeight = state.prvHeight;
1215
+
1216
+ if (props.fillColor && props.fillColor !== 'transparent') {
1217
+ state.ctxCanvasPrv.setFillStyle(props.fillColor);
1218
+ state.ctxCanvasPrv.fillRect(prvX, prvY, prvWidth, prvHeight);
1219
+ }
1220
+ state.ctxCanvasPrv.draw(true);
1221
+
1222
+ // #ifdef APP-PLUS||H5
1223
+ prvX *= state.pixelRatio;
1224
+ prvY *= state.pixelRatio;
1225
+ prvWidth *= state.pixelRatio;
1226
+ prvHeight *= state.pixelRatio;
1227
+ // #endif
1228
+ uni.canvasPutImageData({
1229
+ canvasId: 'prv-canvas-' + state.instanceId,
1230
+ x: prvX,
1231
+ y: prvY,
1232
+ width: prvWidth,
1233
+ height: prvHeight,
1234
+ data: target,
1235
+ fail() {
1236
+ uni.showToast({
1237
+ title: 'error_put',
1238
+ duration: 2000
1239
+ })
1240
+ },
1241
+ complete() {
1242
+ uni.hideLoading();
1243
+ }
1244
+ }, proxy);
1245
+ }
1246
+ function btop(base64) {
1247
+ return new Promise(function (resolve, reject) {
1248
+ var arr = base64.split(','),
1249
+ mime = arr[0].match(/:(.*?);/)[1],
1250
+ bstr = atob(arr[1]),
1251
+ n = bstr.length,
1252
+ u8arr = new Uint8Array(n);
1253
+ while (n--) {
1254
+ u8arr[n] = bstr.charCodeAt(n);
1255
+ }
1256
+ return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([u8arr], { type: mime })));
1257
+ });
1258
+ }
1259
+
1260
+
1261
+
1262
+ // setup init (was created)
1263
+ state.imageSrc = props.imageSrc || props.avatarSrc || ''
1264
+ state.avatarStyle = props.avatarStyle || ''
1265
+ state.ctxCanvas = uni.createCanvasContext('avatar-canvas-' + state.instanceId, proxy)
1266
+ state.ctxCanvasOper = uni.createCanvasContext('oper-canvas-' + state.instanceId, proxy)
1267
+ state.ctxCanvasPrv = uni.createCanvasContext('prv-canvas-' + state.instanceId, proxy)
1268
+ state.qlty = parseQuality(props.quality)
1269
+ state.imgSrc.imgSrc = state.imageSrc
1270
+ state.letRotate = (props.canRotate === false || props.inner === true) ? 0 : 1
1271
+ state.letScale = props.canScale === false ? 0 : 1
1272
+ state.letChangeSize = props.canChangeSize
1273
+ state.isin = props.inner === true ? 1 : 0
1274
+ state.indx = props.index || undefined
1275
+ state.mnScale = props.minScale || 0.3
1276
+ state.mxScale = props.maxScale || 4
1277
+ state.noBar = props.noTab === true ? 1 : 0
1278
+ state.stc = props.stretch
1279
+ state.lck = props.lock
1280
+ if (state.isin) {
1281
+ state.btnWidth = '24%'
1282
+ state.btnDsp = 'none'
1283
+ } else {
1284
+ state.btnWidth = '19%'
1285
+ state.btnDsp = 'flex'
1286
+ }
1287
+ if (state.noBar) {
1288
+ state.moreHeight = 0
1289
+ windowResize()
1290
+ } else {
1291
+ uni.showTabBar({
1292
+ complete: (res) => {
1293
+ state.moreHeight = (res.errMsg === 'showTabBar:ok') ? 50 : 0
1294
+ windowResize()
1295
+ }
1296
+ })
1297
+ }
1298
+
1299
+ watch(() => props.avatarSrc, (val) => {
1300
+ state.imgSrc.imgSrc = val
1301
+ })
1302
+ watch(() => props.imageSrc, (val) => {
1303
+ if (val) {
1304
+ state.imageSrc = val
1305
+ state.imgSrc.imgSrc = val
1306
+ }
1307
+ })
1308
+
1309
+ defineExpose({
1310
+ chooseImage,
1311
+ select,
1312
+ loadImage,
1313
+ confirm,
1314
+ preview,
1315
+ close,
1316
+ rotate
1317
+ })
1318
+ </script>
1319
+
1320
+ <style lang="scss" scoped>
1321
+ .u-cropper {
1322
+ .my-canvas {
1323
+ display: flex;
1324
+ position: fixed !important;
1325
+ background: #000000;
1326
+ left: 0;
1327
+ z-index: 100000;
1328
+ width: 100%;
1329
+ }
1330
+
1331
+ .my-avatar {
1332
+ width: 150rpx;
1333
+ height: 150rpx;
1334
+ border-radius: 100%;
1335
+ }
1336
+
1337
+ .oper-canvas {
1338
+ display: flex;
1339
+ position: fixed !important;
1340
+ left: 0;
1341
+ z-index: 100001;
1342
+ width: 100%;
1343
+ }
1344
+
1345
+ .prv-canvas {
1346
+ display: flex;
1347
+ position: fixed !important;
1348
+ background: #000000;
1349
+ left: 0;
1350
+ z-index: 200000;
1351
+ width: 100%;
1352
+ }
1353
+
1354
+ .oper-wrapper {
1355
+ height: 50px;
1356
+ position: fixed !important;
1357
+ box-sizing: border-box;
1358
+ border: 1px solid #F1F1F1;
1359
+ background: #ffffff;
1360
+ width: 100%;
1361
+ left: 0;
1362
+ bottom: 0;
1363
+ z-index: 100009;
1364
+ flex-direction: row;
1365
+ }
1366
+
1367
+ .oper {
1368
+ display: flex;
1369
+ flex-direction: column;
1370
+ justify-content: center;
1371
+ padding: 10rpx 20rpx;
1372
+ width: 100%;
1373
+ height: 100%;
1374
+ box-sizing: border-box;
1375
+ align-self: center;
1376
+ }
1377
+
1378
+ .btn-wrapper {
1379
+ display: flex;
1380
+ flex-direction: row;
1381
+ /* #ifndef H5 */
1382
+ flex-grow: 1;
1383
+ /* #endif */
1384
+ /* #ifdef H5 */
1385
+ height: 50px;
1386
+ /* #endif */
1387
+ justify-content: space-between;
1388
+ }
1389
+
1390
+ .btn-wrapper view {
1391
+ display: flex;
1392
+ align-items: center;
1393
+ justify-content: center;
1394
+ font-size: 16px;
1395
+ color: #333;
1396
+ border: 1px solid #f1f1f1;
1397
+ border-radius: 6%;
1398
+ }
1399
+
1400
+ .hover {
1401
+ background: #f1f1f1;
1402
+ border-radius: 6%;
1403
+ }
1404
+
1405
+ .clr-wrapper {
1406
+ display: flex;
1407
+ flex-direction: row;
1408
+ flex-grow: 1;
1409
+ }
1410
+
1411
+ .clr-wrapper view {
1412
+ display: flex;
1413
+ align-items: center;
1414
+ justify-content: center;
1415
+ font-size: 16px;
1416
+ color: #333;
1417
+ border: 1px solid #f1f1f1;
1418
+ border-radius: 6%;
1419
+ }
1420
+
1421
+ .my-slider {
1422
+ flex-grow: 1;
1423
+ }
1424
+ }
1425
+ </style>
1426
+