@uxda/appkit 4.2.82 → 4.2.84

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 (127) hide show
  1. package/.eslintrc.mjs +7 -7
  2. package/README.md +187 -187
  3. package/babel.config.js +12 -12
  4. package/dist/appkit.css +41 -3
  5. package/dist/index.js +994 -535
  6. package/package.json +81 -81
  7. package/project.config.json +15 -15
  8. package/project.tt.json +13 -13
  9. package/rollup.config.mjs +77 -77
  10. package/src/Appkit.ts +67 -67
  11. package/src/balance/api/endpoints.ts +133 -133
  12. package/src/balance/api/index.ts +112 -106
  13. package/src/balance/components/AccountView.vue +773 -773
  14. package/src/balance/components/BalanceCard.vue +210 -210
  15. package/src/balance/components/BalanceReminder.vue +84 -84
  16. package/src/balance/components/ConsumptionFilter.vue +218 -218
  17. package/src/balance/components/ConsumptionRules.vue +68 -68
  18. package/src/balance/components/DateFilter.vue +259 -259
  19. package/src/balance/components/DateRange.vue +111 -111
  20. package/src/balance/components/ListFilter.vue +62 -62
  21. package/src/balance/components/ListFilterPicker.vue +191 -191
  22. package/src/balance/components/PromoterCard.vue +310 -310
  23. package/src/balance/components/SecondBalance.vue +77 -77
  24. package/src/balance/components/Tip.vue +45 -45
  25. package/src/balance/components/index.ts +8 -8
  26. package/src/balance/types.ts +99 -99
  27. package/src/components/bt-cropper/index.vue +730 -730
  28. package/src/components/bt-cropper/utils/calcCropper.js +42 -42
  29. package/src/components/bt-cropper/utils/calcImagePosition.js +23 -23
  30. package/src/components/bt-cropper/utils/calcImageSize.js +37 -37
  31. package/src/components/bt-cropper/utils/calcPointDistance.js +12 -12
  32. package/src/components/bt-cropper/utils/calcRightAndBottom.js +7 -7
  33. package/src/components/bt-cropper/utils/ratio.js +3 -3
  34. package/src/components/bt-cropper/utils/tools.js +25 -25
  35. package/src/components/dd-area/index.vue +225 -225
  36. package/src/components/dd-icon/doc.md +21 -21
  37. package/src/components/dd-icon/index.vue +23 -23
  38. package/src/components/dd-notice-bar/index.vue +78 -78
  39. package/src/components/dd-search/doc.md +34 -34
  40. package/src/components/dd-search/index.vue +168 -168
  41. package/src/components/dd-selector/index.vue +124 -124
  42. package/src/components/dd-skeleton/doc.md +19 -19
  43. package/src/components/dd-skeleton/index.vue +36 -36
  44. package/src/global.ts +6 -6
  45. package/src/index.ts +93 -93
  46. package/src/main.scss +1 -1
  47. package/src/notice/api/endpoints.ts +54 -17
  48. package/src/notice/api/index.ts +115 -106
  49. package/src/notice/components/NoticeBanner.vue +247 -247
  50. package/src/notice/components/NoticeEntry.vue +99 -99
  51. package/src/notice/components/NoticeList.vue +311 -311
  52. package/src/notice/components/NoticeList2.vue +243 -0
  53. package/src/notice/components/NoticePopup.vue +163 -163
  54. package/src/notice/components/index.ts +6 -5
  55. package/src/notice/components/useCommonList.ts +86 -86
  56. package/src/notice/components/useNotice.ts +35 -35
  57. package/src/notice/index.ts +1 -1
  58. package/src/notice/types.ts +25 -25
  59. package/src/payment/api/config.ts +7 -7
  60. package/src/payment/api/endpoints.ts +95 -95
  61. package/src/payment/api/index.ts +101 -101
  62. package/src/payment/components/AmountPicker.vue +90 -90
  63. package/src/payment/components/RechargeResult.vue +69 -69
  64. package/src/payment/components/RechargeView.vue +190 -190
  65. package/src/payment/components/RightsPicker.vue +105 -105
  66. package/src/payment/components/TradeView.vue +363 -363
  67. package/src/payment/components/UserAgreement.vue +234 -234
  68. package/src/payment/components/index.ts +22 -22
  69. package/src/payment/index.ts +5 -5
  70. package/src/payment/services/index.ts +16 -16
  71. package/src/payment/services/invoke-recharge.ts +25 -25
  72. package/src/payment/services/request-payment.ts +130 -130
  73. package/src/payment/types.ts +32 -32
  74. package/src/register/components/SelfRegistration.vue +233 -233
  75. package/src/register/components/index.ts +2 -2
  76. package/src/scenarios/components/SharePoster.vue +364 -364
  77. package/src/scenarios/components/index.ts +2 -2
  78. package/src/scenarios/components/poster-paste.vue +93 -93
  79. package/src/scenarios/components/share-poster.md +273 -273
  80. package/src/shared/components/AppDrawer.vue +53 -53
  81. package/src/shared/components/AppVerify.vue +128 -128
  82. package/src/shared/components/DeviceVersion.vue +78 -78
  83. package/src/shared/components/EmptyView.vue +33 -33
  84. package/src/shared/components/OcrBusinessLicense.vue +137 -137
  85. package/src/shared/components/OcrIcon.vue +229 -229
  86. package/src/shared/components/PageHeader.vue +84 -84
  87. package/src/shared/components/index.ts +8 -8
  88. package/src/shared/composables/index.ts +9 -9
  89. package/src/shared/composables/useAmount.ts +46 -46
  90. package/src/shared/composables/useCountdown.ts +46 -46
  91. package/src/shared/composables/useCrypto.ts +76 -76
  92. package/src/shared/composables/useDeviceEnv.ts +11 -11
  93. package/src/shared/composables/useDragBox.ts +97 -97
  94. package/src/shared/composables/useEncode.ts +43 -43
  95. package/src/shared/composables/useLogger.ts +131 -131
  96. package/src/shared/composables/useSafeArea.ts +46 -46
  97. package/src/shared/composables/useTabbar.ts +24 -24
  98. package/src/shared/composables/useUpload.ts +61 -61
  99. package/src/shared/composables/useValidator.ts +32 -32
  100. package/src/shared/composables/useWxAuth.ts +48 -48
  101. package/src/shared/http/Http.ts +148 -146
  102. package/src/shared/http/index.ts +1 -1
  103. package/src/shared/http/types.ts +163 -160
  104. package/src/shared/index.ts +8 -8
  105. package/src/shared/weixin/index.ts +9 -9
  106. package/src/shared/weixin/jssdk.ts +103 -103
  107. package/src/shared/weixin/payment.ts +38 -38
  108. package/src/styles/vars.scss +3 -3
  109. package/src/user/api/endpoints.ts +17 -17
  110. package/src/user/api/index.ts +117 -111
  111. package/src/user/components/LoginSetting.vue +114 -114
  112. package/src/user/components/UserAuth.vue +220 -220
  113. package/src/user/components/UserBinding.vue +277 -277
  114. package/src/user/components/UserBindingSuccess.vue +80 -80
  115. package/src/user/components/UserEntry.vue +139 -139
  116. package/src/user/components/UserFeedback.vue +427 -427
  117. package/src/user/components/UserFeedbackEntry.vue +175 -175
  118. package/src/user/components/UserHeadCrop.vue +65 -65
  119. package/src/user/components/UserInfo.vue +708 -708
  120. package/src/user/components/UserResourceEmpty.vue +75 -75
  121. package/src/user/components/index.ts +23 -23
  122. package/src/user/index.ts +1 -1
  123. package/src/utils/utils.ts +28 -28
  124. package/tsconfig.json +30 -30
  125. package/types/global.d.ts +22 -22
  126. package/types/vue.d.ts +10 -10
  127. /package/dist/assets/{asset-DcH8Kg-2 → asset-3B_CoPto} +0 -0
@@ -1,730 +1,730 @@
1
- <template>
2
- <view class="bt-container">
3
- <view class="iconfont icon-replay" @click.stop="resetImage"></view>
4
- <view @touchend="onTouchEnd" @touchstart="onTouchStart" class="mainContent">
5
- <image :src="imageSrc" mode="aspectFit" data-type="image" @touchmove.stop.prevent="onImageMove"
6
- :style="[imageStyle]" class="image"></image>
7
- <view class="cropper" v-if="imageSrc && imageInfo" :style="{
8
- width: cropperPosition.width + 'px',
9
- height: cropperPosition.height + 'px',
10
- left: cropperPosition.left - 1 + 'px',
11
- top: cropperPosition.top - 1 + 'px',
12
- transition,
13
- }">
14
- <template v-if="showGrid">
15
- <view class="line row row1"></view>
16
- <view class="line row row2"></view>
17
- <view class="line col col1"></view>
18
- <view class="line col col2"></view>
19
- </template>
20
- </view>
21
- <view @touchmove.stop.prevent="onHandleResize(-1, 0, $event)" class="controller vertical"
22
- :style="[controllerPosition.left]" />
23
- <view @touchmove.stop.prevent="onHandleResize(1, 0, $event)" class="controller vertical"
24
- :style="[controllerPosition.right]" />
25
- <view @touchmove.stop.prevent="onHandleResize(0, -1, $event)" class="controller horizon"
26
- :style="[controllerPosition.top]" />
27
- <view @touchmove.stop.prevent="onHandleResize(0, 1, $event)" class="controller horizon"
28
- :style="[controllerPosition.bottom]" />
29
- <template v-if="ratio == 0">
30
- <view @touchmove.stop.prevent="onHandleResize(-1, -1, $event)" class="controller controller_dot"
31
- :style="[controllerPosition.leftTop]" />
32
- <view @touchmove.stop.prevent="onHandleResize(1, -1, $event)" class="controller controller_dot"
33
- :style="[controllerPosition.rightTop]" />
34
- <view @touchmove.stop.prevent="onHandleResize(-1, 1, $event)" class="controller controller_dot"
35
- :style="[controllerPosition.leftBottom]" />
36
- <view @touchmove.stop.prevent="onHandleResize(1, 1, $event)" class="controller controller_dot"
37
- :style="[controllerPosition.rightBottom]" />
38
- </template>
39
- </view>
40
- <view class="slot">
41
- <slot />
42
- </view>
43
- <canvas v-if="isWeapp" type="2d" class="bt-canvas" :style="{
44
- width: dSize.width + 'px',
45
- height: dSize.height + 'px',
46
- }"></canvas>
47
- <canvas v-if="!isWeapp && showCanvas" canvas-id="bt-canvas" class="bt-canvas" :style="{
48
- width: dSize.width + 'px',
49
- height: dSize.height + 'px',
50
- }"></canvas>
51
- </view>
52
- </template>
53
-
54
- <script>
55
- /**
56
- * better-cropper 图片裁切插件
57
- */
58
- import Taro, { getSystemInfoSync, showToast } from '@tarojs/taro'
59
- import calcImageSize from './utils/calcImageSize.js'
60
- import calcImagePosition from './utils/calcImagePosition.js'
61
- import calcCropper from './utils/calcCropper.js'
62
- import calcRightAndBottom from './utils/calcRightAndBottom.js'
63
- import calcPointDistance from './utils/calcPointDistance.js'
64
- import { getTouchPoints, sleep } from './utils/tools.js'
65
-
66
- var startOffsetX = 0,
67
- startOffsetY = 0,
68
- startTouchsDistance = 0,
69
- startChangeLeft = 0,
70
- startChangeTop = 0,
71
- startChangeWidth = 0,
72
- startChangeHeight = 0,
73
- initScale = 1,
74
- startTouches = [],
75
- timer = null
76
- export default {
77
- name: 'bt-cropper',
78
- props: {
79
- // 图片路径,支持网络路径和本地路径
80
- imageSrc: {
81
- type: String,
82
- default: '',
83
- },
84
- // 输出图片的格式,默认jpg
85
- fileType: {
86
- type: String,
87
- default: 'jpg',
88
- },
89
- // 生成的图片的宽度
90
- dWidth: {
91
- type: Number,
92
- default: 1000,
93
- },
94
- // 裁切比例,0表示自由
95
- ratio: {
96
- type: Number,
97
- default: 0,
98
- },
99
- // 是否展示网格
100
- showGrid: {
101
- type: Boolean,
102
- default: false,
103
- },
104
- // 图片质量,0-1 越大质量越好
105
- quality: {
106
- type: Number,
107
- default: 1,
108
- },
109
- },
110
- data() {
111
- return {
112
- imageInfo: '',
113
- containerRect: '',
114
- offsetX: 0,
115
- offsetY: 0,
116
- changeWidth: 0,
117
- changeHeight: 0,
118
- windowWidth: 375,
119
- dpr: 2,
120
- forceChangeWidth: 0,
121
- forceChangeHeight: 0,
122
- animate: false,
123
- imgScale: 1,
124
- imgTranslateX: 0,
125
- imgTranslateY: 0,
126
- // 这个变量不要删掉,不然会造成性能严重下降,这是uni-app框架的Bug
127
- showCanvas: false,
128
- cropperPosition: {
129
- left: 0,
130
- top: 0,
131
- width: 0,
132
- height: 0,
133
- },
134
- imageBoundingRect: {
135
- left: 0,
136
- top: 0,
137
- width: 0,
138
- height: 0,
139
- },
140
- }
141
- },
142
- watch: {
143
- imageSrc: {
144
- handler(src) {
145
- this.imageInfo = ''
146
- this.resetImage()
147
- setTimeout(() => {
148
- this.getImageInfo()
149
- }, 150)
150
- },
151
- immediate: true,
152
- },
153
- ratio: {
154
- handler(ratio) {
155
- this.resetRatio()
156
- this.applyAnim()
157
- },
158
- immediate: false,
159
- },
160
- },
161
- computed: {
162
- isWeapp() {
163
- return Taro.getEnv() === 'WEAPP'
164
- },
165
- // 生成图片的大小
166
- dSize() {
167
- return {
168
- width: this.dWidth,
169
- height: this.dWidth * (this.cropperPosition.height / this.cropperPosition.width),
170
- }
171
- },
172
- imageStyle() {
173
- let style = {
174
- left: this.imageBoundingRect.left + 'px',
175
- top: this.imageBoundingRect.top + 'px',
176
- width: this.imageBoundingRect.width + 'px',
177
- height: this.imageBoundingRect.height + 'px',
178
- transition: this.transition,
179
- transform: `matrix(${this.imgScale}, 0, 0, ${this.imgScale}, ${this.imgTranslateX}, ${this.imgTranslateY})`,
180
- }
181
- return style
182
- },
183
- transition() {
184
- return this.animate ? '0.2s' : 'none'
185
- },
186
- // 四个控制点的位置
187
- controllerPosition() {
188
- const up40 = 20,
189
- up30 = 15,
190
- up20 = 10
191
- const transition = this.transition
192
- return {
193
- left: {
194
- left: this.cropperPosition.left - up30 + 'px',
195
- top: this.cropperPosition.top + this.cropperPosition.height / 2 - up40 + 'px',
196
- transition,
197
- },
198
- right: {
199
- left: this.cropperPosition.left + this.cropperPosition.width - up20 + 'px',
200
- top: this.cropperPosition.top + this.cropperPosition.height / 2 - up40 + 'px',
201
- transition,
202
- },
203
- top: {
204
- left: this.cropperPosition.left + this.cropperPosition.width / 2 - up40 + 'px',
205
- top: this.cropperPosition.top - up30 + 'px',
206
- transition,
207
- },
208
- bottom: {
209
- left: this.cropperPosition.left + this.cropperPosition.width / 2 - up40 + 'px',
210
- top: this.cropperPosition.top + this.cropperPosition.height - up20 + 'px',
211
- transition,
212
- },
213
- leftTop: {
214
- left: this.cropperPosition.left - up40 + 'px',
215
- top: this.cropperPosition.top - up40 + 'px',
216
- transition,
217
- },
218
- rightTop: {
219
- left: this.cropperPosition.left + this.cropperPosition.width - up40 + 'px',
220
- top: this.cropperPosition.top - up40 + 'px',
221
- transition,
222
- },
223
- leftBottom: {
224
- left: this.cropperPosition.left - up40 + 'px',
225
- top: this.cropperPosition.top + this.cropperPosition.height - up40 + 'px',
226
- transition,
227
- },
228
- rightBottom: {
229
- left: this.cropperPosition.left + this.cropperPosition.width - up40 + 'px',
230
- top: this.cropperPosition.top + this.cropperPosition.height - up40 + 'px',
231
- transition,
232
- },
233
- }
234
- },
235
- },
236
- methods: {
237
- applyAnim() {
238
- this.animate = true
239
- clearTimeout(timer)
240
- timer = setTimeout(() => {
241
- this.animate = false
242
- }, 200)
243
- },
244
- async getContainer() {
245
- const systemInfo = getSystemInfoSync()
246
- this.windowWidth = systemInfo.windowWidth
247
- this.dpr = systemInfo.pixelRatio
248
- return new Promise((resolve) => {
249
- Taro.createSelectorQuery()
250
- .select('.mainContent')
251
- .boundingClientRect((rect) => {
252
- this.containerRect = rect
253
- resolve(rect)
254
- })
255
- .exec()
256
- })
257
- },
258
- async getImageInfo() {
259
- await this.getContainer()
260
- Taro.getImageInfo({
261
- src: this.imageSrc,
262
- success: (res) => {
263
- this.imageInfo = res
264
- calcImageSize(this.imageInfo, this.containerRect).then((res) => {
265
- this.imageBoundingRect = res
266
- this.resetRatio()
267
- })
268
- },
269
- fail: (err) => {
270
- console.error(err)
271
- this.imageInfo = ''
272
- showToast({
273
- title: '下载图片失败',
274
- icon: 'none',
275
- })
276
- },
277
- })
278
- },
279
- resetImage() {
280
- this.imgTranslateX = 0
281
- this.imgTranslateY = 0
282
- this.imgScale = 1
283
- this.resetRatio()
284
- },
285
- // 重置裁剪框
286
- resetRatio() {
287
- this.$nextTick(() => {
288
- if (this.imageInfo) {
289
- this.cropperPosition = calcCropper(this.imageBoundingRect, {
290
- width: this.ratio,
291
- height: 1,
292
- })
293
- this.checkImagePosition()
294
- }
295
- })
296
- },
297
- onTouchStart(ev) {
298
- this.animate = false
299
- if (timer) {
300
- clearTimeout(timer)
301
- }
302
- startTouches = Array.from(ev.touches)
303
- if (ev.target.dataset.type === 'image') {
304
- startOffsetX = this.imgTranslateX
305
- startOffsetY = this.imgTranslateY
306
- if (ev.touches.length == 2) {
307
- initScale = this.imgScale
308
- startTouchsDistance = calcPointDistance(...getTouchPoints(startTouches))
309
- }
310
- } else {
311
- startChangeLeft = this.cropperPosition.left
312
- startChangeTop = this.cropperPosition.top
313
- startChangeWidth = this.cropperPosition.width
314
- startChangeHeight = this.cropperPosition.height
315
- }
316
- },
317
- onTouchEnd() {
318
- startTouches = []
319
- this.checkImagePosition()
320
- if (timer) {
321
- clearTimeout(timer)
322
- }
323
- timer = setTimeout(this.zoom, 1000)
324
- },
325
- getImagePosition() {
326
- return calcImagePosition(this.imageBoundingRect, {
327
- imgTranslateX: this.imgTranslateX,
328
- imgTranslateY: this.imgTranslateY,
329
- imgScale: this.imgScale,
330
- })
331
- },
332
- getCropperPosition() {
333
- return calcRightAndBottom(this.cropperPosition)
334
- },
335
- // 检查图片位置
336
- checkImagePosition() {
337
- const imagePosition = this.getImagePosition()
338
- const cropperPosition = this.getCropperPosition()
339
- // 如果裁剪框大于图像大小,就放大到裁剪框大小
340
- const widthScale = cropperPosition.width / imagePosition.width
341
- const heightScale = cropperPosition.height / imagePosition.height
342
- const scale = Math.max(widthScale, heightScale)
343
- if (scale > 1) {
344
- this.imageZoom(
345
- {
346
- left: cropperPosition.left + cropperPosition.width / 2,
347
- top: cropperPosition.top + cropperPosition.height / 2,
348
- },
349
- scale
350
- )
351
- this.applyAnim()
352
- }
353
- // 判断是否超出边界
354
- if (imagePosition.left > cropperPosition.left) {
355
- this.imgTranslateX = this.imgTranslateX - (imagePosition.left - cropperPosition.left)
356
- } else if (imagePosition.right < cropperPosition.right) {
357
- this.imgTranslateX = this.imgTranslateX + (cropperPosition.right - imagePosition.right)
358
- }
359
- if (imagePosition.top > cropperPosition.top) {
360
- this.imgTranslateY = this.imgTranslateY - (imagePosition.top - cropperPosition.top)
361
- } else if (imagePosition.bottom < cropperPosition.bottom) {
362
- this.imgTranslateY = this.imgTranslateY + (cropperPosition.bottom - imagePosition.bottom)
363
- }
364
- },
365
- zoom() {
366
- // 容器比例
367
- const containerRatio = this.containerRect.width / this.containerRect.height
368
- // 移动后的裁剪框比例
369
- const cropperRatio = this.cropperPosition.width / this.cropperPosition.height
370
- // 放大比例
371
- let scale = 1
372
- if (cropperRatio > containerRatio) {
373
- scale = this.containerRect.width / this.cropperPosition.width
374
- } else {
375
- scale = this.containerRect.height / this.cropperPosition.height
376
- }
377
- // 放大裁剪框
378
- this.cropperPosition.width *= scale
379
- this.cropperPosition.height *= scale
380
- // // 移动图像
381
- this.imageZoom(
382
- {
383
- left: this.cropperPosition.left,
384
- top: this.cropperPosition.top,
385
- },
386
- scale
387
- )
388
- // 将裁剪框上下居中
389
- const cropperTop = (this.containerRect.height - this.cropperPosition.height) / 2
390
- // 需要上下移动的距离
391
- const moveTop = cropperTop - this.cropperPosition.top
392
- // 将裁剪框左右居中
393
- const cropperLeft = (this.containerRect.width - this.cropperPosition.width) / 2
394
- // 需要左右移动的距离
395
- const moveLeft = cropperLeft - this.cropperPosition.left
396
- this.cropperPosition.left = cropperLeft
397
- this.cropperPosition.top = cropperTop
398
-
399
- // 移动图像使之与裁剪框对齐
400
- this.imgTranslateX += moveLeft
401
- this.imgTranslateY += moveTop
402
- this.checkImagePosition()
403
- this.applyAnim()
404
- },
405
- imageZoom(
406
- center = {
407
- left: 0,
408
- top: 0,
409
- },
410
- scale = 1
411
- ) {
412
- const imagePosition = this.getImagePosition()
413
- this.imgScale = this.imgScale * scale
414
- const offsetLeftPercent = (center.left - imagePosition.left) / imagePosition.width
415
- this.imgTranslateX =
416
- this.imgTranslateX + ((imagePosition.width * (scale - 1)) / 2) * (1 - offsetLeftPercent * 2)
417
- const offsetTopPercent = (center.top - imagePosition.top) / imagePosition.height
418
- this.imgTranslateY =
419
- this.imgTranslateY + ((imagePosition.height * (scale - 1)) / 2) * (1 - offsetTopPercent * 2)
420
- },
421
- onImageMove(ev) {
422
- if (ev.touches.length == 2 && startTouches.length == 2) {
423
- const points = getTouchPoints(ev.touches)
424
- const imgScale = (initScale * calcPointDistance(...points)) / startTouchsDistance
425
- this.imageZoom(
426
- {
427
- left: this.cropperPosition.left + this.cropperPosition.width / 2,
428
- top: this.cropperPosition.top + this.cropperPosition.height / 2,
429
- },
430
- imgScale / this.imgScale
431
- )
432
- } else if (ev.touches.length == 1 && startTouches.length == 1) {
433
- const [startClientX, startClientY] = getTouchPoints(startTouches)[0]
434
- const [clientX, clientY] = getTouchPoints(ev.touches)[0]
435
- this.imgTranslateX = startOffsetX + clientX - startClientX
436
- this.imgTranslateY = startOffsetY + clientY - startClientY
437
- }
438
- },
439
- // 调整裁剪框大小
440
- onHandleResize(pX, pY, ev) {
441
- const [startClientX, startClientY] = getTouchPoints(startTouches)[0]
442
- const [clientX, clientY] = getTouchPoints(ev.touches)[0]
443
- const cropperBoundingRect = this.getCropperPosition()
444
- const imageBoundingRect = this.getImagePosition()
445
- const changeX = clientX - startClientX
446
- const changeY = clientY - startClientY
447
- const minSize = {
448
- width: 50,
449
- height: 50,
450
- }
451
- const imageRemainHeight = imageBoundingRect.bottom - cropperBoundingRect.top
452
- const cropperRemainHeight = this.containerRect.bottom - cropperBoundingRect.top
453
- const maxHeight = Math.min(imageRemainHeight, cropperRemainHeight)
454
- const imageRemainWidth = imageBoundingRect.right - cropperBoundingRect.left
455
- const cropperRemainWidth = this.containerRect.right - cropperBoundingRect.left
456
- const maxWidth = Math.min(imageRemainWidth, cropperRemainWidth)
457
- let width = 0
458
- switch (pX) {
459
- case 1:
460
- width = startChangeWidth + changeX
461
- if (width < maxWidth) {
462
- if (width > minSize.width) {
463
- this.cropperPosition.width = width
464
- }
465
- }
466
- break
467
- case -1:
468
- const left = startChangeLeft + changeX
469
- const minLeft = Math.min(imageBoundingRect.left, cropperBoundingRect.left)
470
- width = startChangeWidth - changeX
471
- if (left > minLeft) {
472
- if (width > minSize.width) {
473
- this.cropperPosition.left = left
474
- this.cropperPosition.width = width
475
- }
476
- }
477
- break
478
- case 0:
479
- if (this.ratio != 0) this.cropperPosition.width = this.cropperPosition.height * this.ratio
480
- break
481
- }
482
- switch (pY) {
483
- case 1:
484
- const height = startChangeHeight + changeY
485
- if (height < maxHeight && height > minSize.height) {
486
- this.cropperPosition.height = height
487
- }
488
- break
489
- case -1:
490
- const top = startChangeTop + changeY
491
- const minTop = Math.min(imageBoundingRect.top, cropperBoundingRect.top)
492
- if (top > minTop) {
493
- const height = startChangeHeight - changeY
494
- if (height > minSize.height) {
495
- this.cropperPosition.top = top
496
- this.cropperPosition.height = height
497
- }
498
- }
499
- break
500
- case 0:
501
- if (this.ratio != 0) this.cropperPosition.height = this.cropperPosition.width / this.ratio
502
- break
503
- }
504
- },
505
- // 开始裁剪
506
- async crop() {
507
- this.showCanvas = true
508
- return new Promise((resolve) => {
509
- this.$nextTick(() => {
510
- this.onCrop().then((res) => {
511
- resolve(res)
512
- })
513
- })
514
- })
515
- },
516
- // 开始裁切
517
- async onCrop() {
518
- let canvas, image, ctx, err, res
519
- if (Taro.getEnv() === 'WEAPP') {
520
- canvas = await new Promise((resolve) => {
521
- Taro.createSelectorQuery()
522
- .select('.bt-canvas')
523
- .node((res) => {
524
- resolve(res.node)
525
- })
526
- .exec()
527
- })
528
- console.warn('在小程序模拟器上可能会裁剪失败,真机无此问题,放心使用')
529
- image = canvas.createImage()
530
- image.src = this.imageInfo.path
531
- await new Promise((resolve) => (image.onload = resolve))
532
- canvas.width = this.dSize.width
533
- canvas.height = this.dSize.height
534
- ctx = canvas.getContext('2d')
535
- } else {
536
- image = this.imageInfo.path
537
- ctx = Taro.createCanvasContext('bt-canvas', this)
538
- }
539
-
540
- const imagePosition = this.getImagePosition()
541
- const cropperPosition = this.getCropperPosition()
542
- const scale = imagePosition.width / this.imageInfo.width
543
- const offsetLeft = (cropperPosition.left - imagePosition.left) / scale
544
- const offsetTop = (cropperPosition.top - imagePosition.top) / scale
545
- const cropperWidth = cropperPosition.width / scale
546
- const cropperHeight = cropperPosition.height / scale
547
- ctx.drawImage(
548
- image,
549
- offsetLeft,
550
- offsetTop,
551
- cropperWidth,
552
- cropperHeight,
553
- 0,
554
- 0,
555
- this.dSize.width,
556
- this.dSize.height
557
- )
558
- ctx.fillStyle = '#000'
559
-
560
- // if (Taro.getEnv() === 'WEAPP') {
561
- // await new Promise((resolve) => ctx.draw(true, resolve))
562
- // }
563
- // 等待一段时间,不然ios会裁剪失败
564
- await sleep(200)
565
- // 在vue3里面,只能写成这种回调形式,否则报错
566
- ;[err, res] = await new Promise((resolve) => {
567
- Taro.canvasToTempFilePath({
568
- // #ifdef MP-WEIXIN
569
- canvas,
570
- // #endif
571
- // #ifndef MP-WEIXIN
572
- canvasId: 'bt-canvas',
573
- // #endif
574
- fileType: this.fileType,
575
- destWidth: this.dSize.width,
576
- destHeight: this.dSize.height,
577
- quality: this.quality,
578
- success(res) {
579
- console.log('裁剪成功')
580
- resolve([null, res])
581
- },
582
- fail(err) {
583
- console.log('裁剪失败', err)
584
- resolve([err, null])
585
- },
586
- complete: () => {
587
- this.showCanvas = false
588
- },
589
- })
590
- })
591
- return [err, res]
592
- },
593
- },
594
- }
595
- </script>
596
-
597
- <style lang="scss">
598
- .bt-container {
599
- display: flex;
600
- flex-direction: column;
601
- justify-content: space-between;
602
- height: 100%;
603
- box-sizing: border-box;
604
- background-color: #0e1319;
605
- position: relative;
606
- overflow: hidden;
607
-
608
- .iconfont {
609
- position: absolute;
610
- z-index: 999;
611
- top: 20px;
612
- font-size: 15px;
613
- padding: 5px;
614
- background-color: rgba(255, 255, 255, 0.2);
615
- border-radius: 50%;
616
- color: #ffffff;
617
-
618
- &.active {
619
- color: #007aff;
620
- }
621
- }
622
-
623
- .icon-replay {
624
- right: 20px;
625
- }
626
-
627
- .bt-canvas {
628
- position: absolute;
629
- left: 100%;
630
- top: 0;
631
- width: 300px;
632
- height: 300px;
633
- }
634
-
635
- .mainContent {
636
- flex: 1;
637
- margin: 30px;
638
- position: relative;
639
-
640
- .image {
641
- position: absolute;
642
- transform-origin: center center;
643
- }
644
-
645
- .controller {
646
- position: absolute;
647
- z-index: 99;
648
- padding: 10px;
649
-
650
- &::after {
651
- display: block;
652
- content: '';
653
- box-shadow: 0 0 5px #333;
654
- background-color: #e4e7ed;
655
- }
656
-
657
- &.controller_dot {
658
- &::after {
659
- width: 20px;
660
- height: 20px;
661
- border-radius: 99px;
662
- }
663
- }
664
-
665
- &.vertical {
666
- &::after {
667
- width: 5px;
668
- height: 20px;
669
- }
670
- }
671
-
672
- &.horizon {
673
- &::after {
674
- width: 20px;
675
- height: 5px;
676
- }
677
- }
678
- }
679
-
680
- .cropper {
681
- position: absolute;
682
- border: 1px solid #eee;
683
- box-sizing: content-box;
684
- transform-origin: center center;
685
- outline: 999px solid rgba(0, 0, 0, 0.5);
686
- will-change: transform;
687
- display: contain;
688
- pointer-events: none;
689
-
690
- .line {
691
- position: absolute;
692
- }
693
-
694
- .row {
695
- width: 100%;
696
- height: 0px;
697
- left: 0;
698
- border-top: 1px dashed #007aff;
699
- }
700
-
701
- .col {
702
- height: 100%;
703
- width: 0px;
704
- border-left: 1px dashed #007aff;
705
- }
706
-
707
- .row1 {
708
- top: 33%;
709
- }
710
-
711
- .row2 {
712
- top: 66%;
713
- }
714
-
715
- .col1 {
716
- left: 33%;
717
- }
718
-
719
- .col2 {
720
- left: 66%;
721
- }
722
- }
723
- }
724
-
725
- .slot {
726
- position: relative;
727
- padding-top: 10px;
728
- }
729
- }
730
- </style>
1
+ <template>
2
+ <view class="bt-container">
3
+ <view class="iconfont icon-replay" @click.stop="resetImage"></view>
4
+ <view @touchend="onTouchEnd" @touchstart="onTouchStart" class="mainContent">
5
+ <image :src="imageSrc" mode="aspectFit" data-type="image" @touchmove.stop.prevent="onImageMove"
6
+ :style="[imageStyle]" class="image"></image>
7
+ <view class="cropper" v-if="imageSrc && imageInfo" :style="{
8
+ width: cropperPosition.width + 'px',
9
+ height: cropperPosition.height + 'px',
10
+ left: cropperPosition.left - 1 + 'px',
11
+ top: cropperPosition.top - 1 + 'px',
12
+ transition,
13
+ }">
14
+ <template v-if="showGrid">
15
+ <view class="line row row1"></view>
16
+ <view class="line row row2"></view>
17
+ <view class="line col col1"></view>
18
+ <view class="line col col2"></view>
19
+ </template>
20
+ </view>
21
+ <view @touchmove.stop.prevent="onHandleResize(-1, 0, $event)" class="controller vertical"
22
+ :style="[controllerPosition.left]" />
23
+ <view @touchmove.stop.prevent="onHandleResize(1, 0, $event)" class="controller vertical"
24
+ :style="[controllerPosition.right]" />
25
+ <view @touchmove.stop.prevent="onHandleResize(0, -1, $event)" class="controller horizon"
26
+ :style="[controllerPosition.top]" />
27
+ <view @touchmove.stop.prevent="onHandleResize(0, 1, $event)" class="controller horizon"
28
+ :style="[controllerPosition.bottom]" />
29
+ <template v-if="ratio == 0">
30
+ <view @touchmove.stop.prevent="onHandleResize(-1, -1, $event)" class="controller controller_dot"
31
+ :style="[controllerPosition.leftTop]" />
32
+ <view @touchmove.stop.prevent="onHandleResize(1, -1, $event)" class="controller controller_dot"
33
+ :style="[controllerPosition.rightTop]" />
34
+ <view @touchmove.stop.prevent="onHandleResize(-1, 1, $event)" class="controller controller_dot"
35
+ :style="[controllerPosition.leftBottom]" />
36
+ <view @touchmove.stop.prevent="onHandleResize(1, 1, $event)" class="controller controller_dot"
37
+ :style="[controllerPosition.rightBottom]" />
38
+ </template>
39
+ </view>
40
+ <view class="slot">
41
+ <slot />
42
+ </view>
43
+ <canvas v-if="isWeapp" type="2d" class="bt-canvas" :style="{
44
+ width: dSize.width + 'px',
45
+ height: dSize.height + 'px',
46
+ }"></canvas>
47
+ <canvas v-if="!isWeapp && showCanvas" canvas-id="bt-canvas" class="bt-canvas" :style="{
48
+ width: dSize.width + 'px',
49
+ height: dSize.height + 'px',
50
+ }"></canvas>
51
+ </view>
52
+ </template>
53
+
54
+ <script>
55
+ /**
56
+ * better-cropper 图片裁切插件
57
+ */
58
+ import Taro, { getSystemInfoSync, showToast } from '@tarojs/taro'
59
+ import calcImageSize from './utils/calcImageSize.js'
60
+ import calcImagePosition from './utils/calcImagePosition.js'
61
+ import calcCropper from './utils/calcCropper.js'
62
+ import calcRightAndBottom from './utils/calcRightAndBottom.js'
63
+ import calcPointDistance from './utils/calcPointDistance.js'
64
+ import { getTouchPoints, sleep } from './utils/tools.js'
65
+
66
+ var startOffsetX = 0,
67
+ startOffsetY = 0,
68
+ startTouchsDistance = 0,
69
+ startChangeLeft = 0,
70
+ startChangeTop = 0,
71
+ startChangeWidth = 0,
72
+ startChangeHeight = 0,
73
+ initScale = 1,
74
+ startTouches = [],
75
+ timer = null
76
+ export default {
77
+ name: 'bt-cropper',
78
+ props: {
79
+ // 图片路径,支持网络路径和本地路径
80
+ imageSrc: {
81
+ type: String,
82
+ default: '',
83
+ },
84
+ // 输出图片的格式,默认jpg
85
+ fileType: {
86
+ type: String,
87
+ default: 'jpg',
88
+ },
89
+ // 生成的图片的宽度
90
+ dWidth: {
91
+ type: Number,
92
+ default: 1000,
93
+ },
94
+ // 裁切比例,0表示自由
95
+ ratio: {
96
+ type: Number,
97
+ default: 0,
98
+ },
99
+ // 是否展示网格
100
+ showGrid: {
101
+ type: Boolean,
102
+ default: false,
103
+ },
104
+ // 图片质量,0-1 越大质量越好
105
+ quality: {
106
+ type: Number,
107
+ default: 1,
108
+ },
109
+ },
110
+ data() {
111
+ return {
112
+ imageInfo: '',
113
+ containerRect: '',
114
+ offsetX: 0,
115
+ offsetY: 0,
116
+ changeWidth: 0,
117
+ changeHeight: 0,
118
+ windowWidth: 375,
119
+ dpr: 2,
120
+ forceChangeWidth: 0,
121
+ forceChangeHeight: 0,
122
+ animate: false,
123
+ imgScale: 1,
124
+ imgTranslateX: 0,
125
+ imgTranslateY: 0,
126
+ // 这个变量不要删掉,不然会造成性能严重下降,这是uni-app框架的Bug
127
+ showCanvas: false,
128
+ cropperPosition: {
129
+ left: 0,
130
+ top: 0,
131
+ width: 0,
132
+ height: 0,
133
+ },
134
+ imageBoundingRect: {
135
+ left: 0,
136
+ top: 0,
137
+ width: 0,
138
+ height: 0,
139
+ },
140
+ }
141
+ },
142
+ watch: {
143
+ imageSrc: {
144
+ handler(src) {
145
+ this.imageInfo = ''
146
+ this.resetImage()
147
+ setTimeout(() => {
148
+ this.getImageInfo()
149
+ }, 150)
150
+ },
151
+ immediate: true,
152
+ },
153
+ ratio: {
154
+ handler(ratio) {
155
+ this.resetRatio()
156
+ this.applyAnim()
157
+ },
158
+ immediate: false,
159
+ },
160
+ },
161
+ computed: {
162
+ isWeapp() {
163
+ return Taro.getEnv() === 'WEAPP'
164
+ },
165
+ // 生成图片的大小
166
+ dSize() {
167
+ return {
168
+ width: this.dWidth,
169
+ height: this.dWidth * (this.cropperPosition.height / this.cropperPosition.width),
170
+ }
171
+ },
172
+ imageStyle() {
173
+ let style = {
174
+ left: this.imageBoundingRect.left + 'px',
175
+ top: this.imageBoundingRect.top + 'px',
176
+ width: this.imageBoundingRect.width + 'px',
177
+ height: this.imageBoundingRect.height + 'px',
178
+ transition: this.transition,
179
+ transform: `matrix(${this.imgScale}, 0, 0, ${this.imgScale}, ${this.imgTranslateX}, ${this.imgTranslateY})`,
180
+ }
181
+ return style
182
+ },
183
+ transition() {
184
+ return this.animate ? '0.2s' : 'none'
185
+ },
186
+ // 四个控制点的位置
187
+ controllerPosition() {
188
+ const up40 = 20,
189
+ up30 = 15,
190
+ up20 = 10
191
+ const transition = this.transition
192
+ return {
193
+ left: {
194
+ left: this.cropperPosition.left - up30 + 'px',
195
+ top: this.cropperPosition.top + this.cropperPosition.height / 2 - up40 + 'px',
196
+ transition,
197
+ },
198
+ right: {
199
+ left: this.cropperPosition.left + this.cropperPosition.width - up20 + 'px',
200
+ top: this.cropperPosition.top + this.cropperPosition.height / 2 - up40 + 'px',
201
+ transition,
202
+ },
203
+ top: {
204
+ left: this.cropperPosition.left + this.cropperPosition.width / 2 - up40 + 'px',
205
+ top: this.cropperPosition.top - up30 + 'px',
206
+ transition,
207
+ },
208
+ bottom: {
209
+ left: this.cropperPosition.left + this.cropperPosition.width / 2 - up40 + 'px',
210
+ top: this.cropperPosition.top + this.cropperPosition.height - up20 + 'px',
211
+ transition,
212
+ },
213
+ leftTop: {
214
+ left: this.cropperPosition.left - up40 + 'px',
215
+ top: this.cropperPosition.top - up40 + 'px',
216
+ transition,
217
+ },
218
+ rightTop: {
219
+ left: this.cropperPosition.left + this.cropperPosition.width - up40 + 'px',
220
+ top: this.cropperPosition.top - up40 + 'px',
221
+ transition,
222
+ },
223
+ leftBottom: {
224
+ left: this.cropperPosition.left - up40 + 'px',
225
+ top: this.cropperPosition.top + this.cropperPosition.height - up40 + 'px',
226
+ transition,
227
+ },
228
+ rightBottom: {
229
+ left: this.cropperPosition.left + this.cropperPosition.width - up40 + 'px',
230
+ top: this.cropperPosition.top + this.cropperPosition.height - up40 + 'px',
231
+ transition,
232
+ },
233
+ }
234
+ },
235
+ },
236
+ methods: {
237
+ applyAnim() {
238
+ this.animate = true
239
+ clearTimeout(timer)
240
+ timer = setTimeout(() => {
241
+ this.animate = false
242
+ }, 200)
243
+ },
244
+ async getContainer() {
245
+ const systemInfo = getSystemInfoSync()
246
+ this.windowWidth = systemInfo.windowWidth
247
+ this.dpr = systemInfo.pixelRatio
248
+ return new Promise((resolve) => {
249
+ Taro.createSelectorQuery()
250
+ .select('.mainContent')
251
+ .boundingClientRect((rect) => {
252
+ this.containerRect = rect
253
+ resolve(rect)
254
+ })
255
+ .exec()
256
+ })
257
+ },
258
+ async getImageInfo() {
259
+ await this.getContainer()
260
+ Taro.getImageInfo({
261
+ src: this.imageSrc,
262
+ success: (res) => {
263
+ this.imageInfo = res
264
+ calcImageSize(this.imageInfo, this.containerRect).then((res) => {
265
+ this.imageBoundingRect = res
266
+ this.resetRatio()
267
+ })
268
+ },
269
+ fail: (err) => {
270
+ console.error(err)
271
+ this.imageInfo = ''
272
+ showToast({
273
+ title: '下载图片失败',
274
+ icon: 'none',
275
+ })
276
+ },
277
+ })
278
+ },
279
+ resetImage() {
280
+ this.imgTranslateX = 0
281
+ this.imgTranslateY = 0
282
+ this.imgScale = 1
283
+ this.resetRatio()
284
+ },
285
+ // 重置裁剪框
286
+ resetRatio() {
287
+ this.$nextTick(() => {
288
+ if (this.imageInfo) {
289
+ this.cropperPosition = calcCropper(this.imageBoundingRect, {
290
+ width: this.ratio,
291
+ height: 1,
292
+ })
293
+ this.checkImagePosition()
294
+ }
295
+ })
296
+ },
297
+ onTouchStart(ev) {
298
+ this.animate = false
299
+ if (timer) {
300
+ clearTimeout(timer)
301
+ }
302
+ startTouches = Array.from(ev.touches)
303
+ if (ev.target.dataset.type === 'image') {
304
+ startOffsetX = this.imgTranslateX
305
+ startOffsetY = this.imgTranslateY
306
+ if (ev.touches.length == 2) {
307
+ initScale = this.imgScale
308
+ startTouchsDistance = calcPointDistance(...getTouchPoints(startTouches))
309
+ }
310
+ } else {
311
+ startChangeLeft = this.cropperPosition.left
312
+ startChangeTop = this.cropperPosition.top
313
+ startChangeWidth = this.cropperPosition.width
314
+ startChangeHeight = this.cropperPosition.height
315
+ }
316
+ },
317
+ onTouchEnd() {
318
+ startTouches = []
319
+ this.checkImagePosition()
320
+ if (timer) {
321
+ clearTimeout(timer)
322
+ }
323
+ timer = setTimeout(this.zoom, 1000)
324
+ },
325
+ getImagePosition() {
326
+ return calcImagePosition(this.imageBoundingRect, {
327
+ imgTranslateX: this.imgTranslateX,
328
+ imgTranslateY: this.imgTranslateY,
329
+ imgScale: this.imgScale,
330
+ })
331
+ },
332
+ getCropperPosition() {
333
+ return calcRightAndBottom(this.cropperPosition)
334
+ },
335
+ // 检查图片位置
336
+ checkImagePosition() {
337
+ const imagePosition = this.getImagePosition()
338
+ const cropperPosition = this.getCropperPosition()
339
+ // 如果裁剪框大于图像大小,就放大到裁剪框大小
340
+ const widthScale = cropperPosition.width / imagePosition.width
341
+ const heightScale = cropperPosition.height / imagePosition.height
342
+ const scale = Math.max(widthScale, heightScale)
343
+ if (scale > 1) {
344
+ this.imageZoom(
345
+ {
346
+ left: cropperPosition.left + cropperPosition.width / 2,
347
+ top: cropperPosition.top + cropperPosition.height / 2,
348
+ },
349
+ scale
350
+ )
351
+ this.applyAnim()
352
+ }
353
+ // 判断是否超出边界
354
+ if (imagePosition.left > cropperPosition.left) {
355
+ this.imgTranslateX = this.imgTranslateX - (imagePosition.left - cropperPosition.left)
356
+ } else if (imagePosition.right < cropperPosition.right) {
357
+ this.imgTranslateX = this.imgTranslateX + (cropperPosition.right - imagePosition.right)
358
+ }
359
+ if (imagePosition.top > cropperPosition.top) {
360
+ this.imgTranslateY = this.imgTranslateY - (imagePosition.top - cropperPosition.top)
361
+ } else if (imagePosition.bottom < cropperPosition.bottom) {
362
+ this.imgTranslateY = this.imgTranslateY + (cropperPosition.bottom - imagePosition.bottom)
363
+ }
364
+ },
365
+ zoom() {
366
+ // 容器比例
367
+ const containerRatio = this.containerRect.width / this.containerRect.height
368
+ // 移动后的裁剪框比例
369
+ const cropperRatio = this.cropperPosition.width / this.cropperPosition.height
370
+ // 放大比例
371
+ let scale = 1
372
+ if (cropperRatio > containerRatio) {
373
+ scale = this.containerRect.width / this.cropperPosition.width
374
+ } else {
375
+ scale = this.containerRect.height / this.cropperPosition.height
376
+ }
377
+ // 放大裁剪框
378
+ this.cropperPosition.width *= scale
379
+ this.cropperPosition.height *= scale
380
+ // // 移动图像
381
+ this.imageZoom(
382
+ {
383
+ left: this.cropperPosition.left,
384
+ top: this.cropperPosition.top,
385
+ },
386
+ scale
387
+ )
388
+ // 将裁剪框上下居中
389
+ const cropperTop = (this.containerRect.height - this.cropperPosition.height) / 2
390
+ // 需要上下移动的距离
391
+ const moveTop = cropperTop - this.cropperPosition.top
392
+ // 将裁剪框左右居中
393
+ const cropperLeft = (this.containerRect.width - this.cropperPosition.width) / 2
394
+ // 需要左右移动的距离
395
+ const moveLeft = cropperLeft - this.cropperPosition.left
396
+ this.cropperPosition.left = cropperLeft
397
+ this.cropperPosition.top = cropperTop
398
+
399
+ // 移动图像使之与裁剪框对齐
400
+ this.imgTranslateX += moveLeft
401
+ this.imgTranslateY += moveTop
402
+ this.checkImagePosition()
403
+ this.applyAnim()
404
+ },
405
+ imageZoom(
406
+ center = {
407
+ left: 0,
408
+ top: 0,
409
+ },
410
+ scale = 1
411
+ ) {
412
+ const imagePosition = this.getImagePosition()
413
+ this.imgScale = this.imgScale * scale
414
+ const offsetLeftPercent = (center.left - imagePosition.left) / imagePosition.width
415
+ this.imgTranslateX =
416
+ this.imgTranslateX + ((imagePosition.width * (scale - 1)) / 2) * (1 - offsetLeftPercent * 2)
417
+ const offsetTopPercent = (center.top - imagePosition.top) / imagePosition.height
418
+ this.imgTranslateY =
419
+ this.imgTranslateY + ((imagePosition.height * (scale - 1)) / 2) * (1 - offsetTopPercent * 2)
420
+ },
421
+ onImageMove(ev) {
422
+ if (ev.touches.length == 2 && startTouches.length == 2) {
423
+ const points = getTouchPoints(ev.touches)
424
+ const imgScale = (initScale * calcPointDistance(...points)) / startTouchsDistance
425
+ this.imageZoom(
426
+ {
427
+ left: this.cropperPosition.left + this.cropperPosition.width / 2,
428
+ top: this.cropperPosition.top + this.cropperPosition.height / 2,
429
+ },
430
+ imgScale / this.imgScale
431
+ )
432
+ } else if (ev.touches.length == 1 && startTouches.length == 1) {
433
+ const [startClientX, startClientY] = getTouchPoints(startTouches)[0]
434
+ const [clientX, clientY] = getTouchPoints(ev.touches)[0]
435
+ this.imgTranslateX = startOffsetX + clientX - startClientX
436
+ this.imgTranslateY = startOffsetY + clientY - startClientY
437
+ }
438
+ },
439
+ // 调整裁剪框大小
440
+ onHandleResize(pX, pY, ev) {
441
+ const [startClientX, startClientY] = getTouchPoints(startTouches)[0]
442
+ const [clientX, clientY] = getTouchPoints(ev.touches)[0]
443
+ const cropperBoundingRect = this.getCropperPosition()
444
+ const imageBoundingRect = this.getImagePosition()
445
+ const changeX = clientX - startClientX
446
+ const changeY = clientY - startClientY
447
+ const minSize = {
448
+ width: 50,
449
+ height: 50,
450
+ }
451
+ const imageRemainHeight = imageBoundingRect.bottom - cropperBoundingRect.top
452
+ const cropperRemainHeight = this.containerRect.bottom - cropperBoundingRect.top
453
+ const maxHeight = Math.min(imageRemainHeight, cropperRemainHeight)
454
+ const imageRemainWidth = imageBoundingRect.right - cropperBoundingRect.left
455
+ const cropperRemainWidth = this.containerRect.right - cropperBoundingRect.left
456
+ const maxWidth = Math.min(imageRemainWidth, cropperRemainWidth)
457
+ let width = 0
458
+ switch (pX) {
459
+ case 1:
460
+ width = startChangeWidth + changeX
461
+ if (width < maxWidth) {
462
+ if (width > minSize.width) {
463
+ this.cropperPosition.width = width
464
+ }
465
+ }
466
+ break
467
+ case -1:
468
+ const left = startChangeLeft + changeX
469
+ const minLeft = Math.min(imageBoundingRect.left, cropperBoundingRect.left)
470
+ width = startChangeWidth - changeX
471
+ if (left > minLeft) {
472
+ if (width > minSize.width) {
473
+ this.cropperPosition.left = left
474
+ this.cropperPosition.width = width
475
+ }
476
+ }
477
+ break
478
+ case 0:
479
+ if (this.ratio != 0) this.cropperPosition.width = this.cropperPosition.height * this.ratio
480
+ break
481
+ }
482
+ switch (pY) {
483
+ case 1:
484
+ const height = startChangeHeight + changeY
485
+ if (height < maxHeight && height > minSize.height) {
486
+ this.cropperPosition.height = height
487
+ }
488
+ break
489
+ case -1:
490
+ const top = startChangeTop + changeY
491
+ const minTop = Math.min(imageBoundingRect.top, cropperBoundingRect.top)
492
+ if (top > minTop) {
493
+ const height = startChangeHeight - changeY
494
+ if (height > minSize.height) {
495
+ this.cropperPosition.top = top
496
+ this.cropperPosition.height = height
497
+ }
498
+ }
499
+ break
500
+ case 0:
501
+ if (this.ratio != 0) this.cropperPosition.height = this.cropperPosition.width / this.ratio
502
+ break
503
+ }
504
+ },
505
+ // 开始裁剪
506
+ async crop() {
507
+ this.showCanvas = true
508
+ return new Promise((resolve) => {
509
+ this.$nextTick(() => {
510
+ this.onCrop().then((res) => {
511
+ resolve(res)
512
+ })
513
+ })
514
+ })
515
+ },
516
+ // 开始裁切
517
+ async onCrop() {
518
+ let canvas, image, ctx, err, res
519
+ if (Taro.getEnv() === 'WEAPP') {
520
+ canvas = await new Promise((resolve) => {
521
+ Taro.createSelectorQuery()
522
+ .select('.bt-canvas')
523
+ .node((res) => {
524
+ resolve(res.node)
525
+ })
526
+ .exec()
527
+ })
528
+ console.warn('在小程序模拟器上可能会裁剪失败,真机无此问题,放心使用')
529
+ image = canvas.createImage()
530
+ image.src = this.imageInfo.path
531
+ await new Promise((resolve) => (image.onload = resolve))
532
+ canvas.width = this.dSize.width
533
+ canvas.height = this.dSize.height
534
+ ctx = canvas.getContext('2d')
535
+ } else {
536
+ image = this.imageInfo.path
537
+ ctx = Taro.createCanvasContext('bt-canvas', this)
538
+ }
539
+
540
+ const imagePosition = this.getImagePosition()
541
+ const cropperPosition = this.getCropperPosition()
542
+ const scale = imagePosition.width / this.imageInfo.width
543
+ const offsetLeft = (cropperPosition.left - imagePosition.left) / scale
544
+ const offsetTop = (cropperPosition.top - imagePosition.top) / scale
545
+ const cropperWidth = cropperPosition.width / scale
546
+ const cropperHeight = cropperPosition.height / scale
547
+ ctx.drawImage(
548
+ image,
549
+ offsetLeft,
550
+ offsetTop,
551
+ cropperWidth,
552
+ cropperHeight,
553
+ 0,
554
+ 0,
555
+ this.dSize.width,
556
+ this.dSize.height
557
+ )
558
+ ctx.fillStyle = '#000'
559
+
560
+ // if (Taro.getEnv() === 'WEAPP') {
561
+ // await new Promise((resolve) => ctx.draw(true, resolve))
562
+ // }
563
+ // 等待一段时间,不然ios会裁剪失败
564
+ await sleep(200)
565
+ // 在vue3里面,只能写成这种回调形式,否则报错
566
+ ;[err, res] = await new Promise((resolve) => {
567
+ Taro.canvasToTempFilePath({
568
+ // #ifdef MP-WEIXIN
569
+ canvas,
570
+ // #endif
571
+ // #ifndef MP-WEIXIN
572
+ canvasId: 'bt-canvas',
573
+ // #endif
574
+ fileType: this.fileType,
575
+ destWidth: this.dSize.width,
576
+ destHeight: this.dSize.height,
577
+ quality: this.quality,
578
+ success(res) {
579
+ console.log('裁剪成功')
580
+ resolve([null, res])
581
+ },
582
+ fail(err) {
583
+ console.log('裁剪失败', err)
584
+ resolve([err, null])
585
+ },
586
+ complete: () => {
587
+ this.showCanvas = false
588
+ },
589
+ })
590
+ })
591
+ return [err, res]
592
+ },
593
+ },
594
+ }
595
+ </script>
596
+
597
+ <style lang="scss">
598
+ .bt-container {
599
+ display: flex;
600
+ flex-direction: column;
601
+ justify-content: space-between;
602
+ height: 100%;
603
+ box-sizing: border-box;
604
+ background-color: #0e1319;
605
+ position: relative;
606
+ overflow: hidden;
607
+
608
+ .iconfont {
609
+ position: absolute;
610
+ z-index: 999;
611
+ top: 20px;
612
+ font-size: 15px;
613
+ padding: 5px;
614
+ background-color: rgba(255, 255, 255, 0.2);
615
+ border-radius: 50%;
616
+ color: #ffffff;
617
+
618
+ &.active {
619
+ color: #007aff;
620
+ }
621
+ }
622
+
623
+ .icon-replay {
624
+ right: 20px;
625
+ }
626
+
627
+ .bt-canvas {
628
+ position: absolute;
629
+ left: 100%;
630
+ top: 0;
631
+ width: 300px;
632
+ height: 300px;
633
+ }
634
+
635
+ .mainContent {
636
+ flex: 1;
637
+ margin: 30px;
638
+ position: relative;
639
+
640
+ .image {
641
+ position: absolute;
642
+ transform-origin: center center;
643
+ }
644
+
645
+ .controller {
646
+ position: absolute;
647
+ z-index: 99;
648
+ padding: 10px;
649
+
650
+ &::after {
651
+ display: block;
652
+ content: '';
653
+ box-shadow: 0 0 5px #333;
654
+ background-color: #e4e7ed;
655
+ }
656
+
657
+ &.controller_dot {
658
+ &::after {
659
+ width: 20px;
660
+ height: 20px;
661
+ border-radius: 99px;
662
+ }
663
+ }
664
+
665
+ &.vertical {
666
+ &::after {
667
+ width: 5px;
668
+ height: 20px;
669
+ }
670
+ }
671
+
672
+ &.horizon {
673
+ &::after {
674
+ width: 20px;
675
+ height: 5px;
676
+ }
677
+ }
678
+ }
679
+
680
+ .cropper {
681
+ position: absolute;
682
+ border: 1px solid #eee;
683
+ box-sizing: content-box;
684
+ transform-origin: center center;
685
+ outline: 999px solid rgba(0, 0, 0, 0.5);
686
+ will-change: transform;
687
+ display: contain;
688
+ pointer-events: none;
689
+
690
+ .line {
691
+ position: absolute;
692
+ }
693
+
694
+ .row {
695
+ width: 100%;
696
+ height: 0px;
697
+ left: 0;
698
+ border-top: 1px dashed #007aff;
699
+ }
700
+
701
+ .col {
702
+ height: 100%;
703
+ width: 0px;
704
+ border-left: 1px dashed #007aff;
705
+ }
706
+
707
+ .row1 {
708
+ top: 33%;
709
+ }
710
+
711
+ .row2 {
712
+ top: 66%;
713
+ }
714
+
715
+ .col1 {
716
+ left: 33%;
717
+ }
718
+
719
+ .col2 {
720
+ left: 66%;
721
+ }
722
+ }
723
+ }
724
+
725
+ .slot {
726
+ position: relative;
727
+ padding-top: 10px;
728
+ }
729
+ }
730
+ </style>