uview-plus 3.8.86 → 3.8.108

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 (61) hide show
  1. package/changelog.md +173 -3
  2. package/components/u-action-sheet/u-action-sheet.vue +21 -2
  3. package/components/u-barcode/u-barcode.vue +0 -2
  4. package/components/u-button/u-button.vue +2 -5
  5. package/components/u-canvas/u-canvas.vue +430 -77
  6. package/components/u-cell/u-cell.vue +8 -0
  7. package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
  8. package/components/u-dragsort/u-dragsort.vue +55 -31
  9. package/components/u-icon/u-icon.vue +12 -3
  10. package/components/u-icon/util.js +81 -10
  11. package/components/u-index-item/u-index-item.vue +1 -1
  12. package/components/u-index-list/u-index-list.vue +1 -1
  13. package/components/u-novel-reader/content-normalizer.js +80 -0
  14. package/components/u-novel-reader/layout-engine.js +225 -0
  15. package/components/u-novel-reader/measure-adapter.js +69 -0
  16. package/components/u-novel-reader/novelReader.js +35 -0
  17. package/components/u-novel-reader/persistence.js +144 -0
  18. package/components/u-novel-reader/props.js +100 -0
  19. package/components/u-novel-reader/reader-catalog.vue +234 -0
  20. package/components/u-novel-reader/reader-content.vue +226 -0
  21. package/components/u-novel-reader/reader-core.js +151 -0
  22. package/components/u-novel-reader/reader-settings.vue +325 -0
  23. package/components/u-novel-reader/reader-toolbar.vue +313 -0
  24. package/components/u-novel-reader/theme-vars.scss +49 -0
  25. package/components/u-novel-reader/u-novel-reader.vue +836 -0
  26. package/components/u-poster/u-poster.vue +272 -222
  27. package/components/u-qrcode/qrcode.js +56 -49
  28. package/components/u-qrcode/u-qrcode.vue +159 -88
  29. package/components/u-scroll-list/scrollWxs.wxs +1 -1
  30. package/components/u-slider/u-slider.vue +1 -1
  31. package/components/u-swipe-action-item/index.wxs +43 -25
  32. package/components/u-swipe-action-item/nvue.js +9 -0
  33. package/components/u-swipe-action-item/other.js +32 -21
  34. package/components/u-swipe-action-item/props.js +5 -0
  35. package/components/u-swipe-action-item/swipeActionItem.js +1 -0
  36. package/components/u-swipe-action-item/u-swipe-action-item.vue +28 -4
  37. package/components/u-switch/u-switch.vue +1 -1
  38. package/components/u-tabbar/u-tabbar.vue +2 -1
  39. package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
  40. package/components/u-tabs/u-tabs.vue +11 -8
  41. package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
  42. package/components/u-text/text.js +1 -1
  43. package/components/u-text/u-text.vue +18 -6
  44. package/components/u-upload/u-upload.vue +1 -1
  45. package/components/u-waterfall/u-waterfall.vue +118 -65
  46. package/libs/config/config.js +1 -1
  47. package/libs/config/props.js +1 -0
  48. package/libs/function/index.js +66 -0
  49. package/libs/mixin/mixin.js +4 -50
  50. package/libs/root/index.js +78 -5
  51. package/libs/root/page.js +7 -7
  52. package/libs/root/root.js +73 -3
  53. package/libs/util/app-nvue-webview-canvas.js +486 -0
  54. package/libs/util/gcanvas/bridge/bridge-weex.js +0 -2
  55. package/libs/util/gcanvas/index.js +3 -3
  56. package/package.json +2 -2
  57. package/types/comps/swipeActionItem.d.ts +13 -0
  58. package/types/comps/tabs.d.ts +2 -1
  59. package/types/comps/text.d.ts +1 -1
  60. package/types/func.d.ts +23 -0
  61. package/types/index.d.ts +1 -0
@@ -21,7 +21,8 @@
21
21
  />
22
22
  <!-- #endif -->
23
23
 
24
- <!-- #ifdef APP-PLUS -->
24
+ <!-- #ifdef APP-PLUS || APP-HARMONY -->
25
+ <!-- #ifndef APP-NVUE -->
25
26
  <canvas
26
27
  class="u-canvas__canvas"
27
28
  :id="canvasId"
@@ -33,15 +34,15 @@
33
34
  @touchend="onTouchEnd"
34
35
  />
35
36
  <!-- #endif -->
37
+ <!-- #endif -->
36
38
 
37
39
  <!-- #ifdef APP-NVUE -->
38
- <gcanvas
40
+ <web-view
39
41
  class="u-canvas__canvas"
40
- ref="gcanvas"
42
+ ref="web"
43
+ src="/static/app-plus/up-canvas/local.html"
41
44
  :style="{ width: actualWidth + unit, height: actualHeight + unit }"
42
- @touchstart="onTouchStart"
43
- @touchmove="onTouchMove"
44
- @touchend="onTouchEnd"
45
+ @onPostMessage="onWebViewMessage"
45
46
  />
46
47
  <!-- #endif -->
47
48
  </view>
@@ -50,10 +51,9 @@
50
51
  <script>
51
52
  // #ifdef APP-NVUE
52
53
  import {
53
- enable,
54
- WeexBridge,
55
- Image as GImage
56
- } from '../../libs/util/gcanvas/index.js';
54
+ createWebViewCanvasBridge,
55
+ createWebViewCanvasContext
56
+ } from '../../libs/util/app-nvue-webview-canvas.js';
57
57
  // #endif
58
58
 
59
59
  export default {
@@ -125,13 +125,27 @@ export default {
125
125
  this._selectorResult = null;
126
126
  this._canvasElement = null;
127
127
  this._imageCache = Object.create(null);
128
+ this._imageDataCache = Object.create(null);
128
129
  this._isNvue = false;
130
+ this._initPromise = null;
131
+ this._webViewBridge = null;
132
+ this._webViewContext = null;
133
+ this._webViewInitSignature = null;
134
+ this._webViewSessionId = null;
129
135
  },
130
136
  mounted() {
131
137
  this.$nextTick(() => {
132
138
  this.initCanvas();
133
139
  });
134
140
  },
141
+ beforeUnmount() {
142
+ if (this._webViewBridge) {
143
+ this._webViewBridge.destroy();
144
+ }
145
+ this._webViewBridge = null;
146
+ this._webViewContext = null;
147
+ this.ctx = null;
148
+ },
135
149
  methods: {
136
150
  parseSize(value) {
137
151
  if (typeof value === 'number') {
@@ -157,22 +171,29 @@ export default {
157
171
  onTouchEnd(event) {
158
172
  this.$emit('touchend', event);
159
173
  },
174
+ onWebViewMessage(event) {
175
+ if (this._webViewBridge) {
176
+ this._webViewBridge.handleMessage(event);
177
+ }
178
+ },
179
+ onWebViewTouch(message) {
180
+ const eventType = message.eventType;
181
+ if (!['touchstart', 'touchmove', 'touchend'].includes(eventType)) return;
182
+ this.$emit(eventType, {
183
+ type: eventType,
184
+ detail: {
185
+ x: message.x,
186
+ y: message.y
187
+ },
188
+ touches: message.touches || [],
189
+ changedTouches: message.changedTouches || [],
190
+ canvasWidth: message.canvasWidth,
191
+ canvasHeight: message.canvasHeight
192
+ });
193
+ },
160
194
  async getCanvasNode(id = this.canvasId, isCanvas = true) {
161
195
  return new Promise((resolve) => {
162
196
  try {
163
- // #ifdef APP-NVUE
164
- setTimeout(() => {
165
- const gcanvas = this.$refs.gcanvas;
166
- if (!gcanvas) {
167
- resolve(false);
168
- return;
169
- }
170
- this._isNvue = true;
171
- resolve(enable(gcanvas, { bridge: WeexBridge }));
172
- }, 100);
173
- // #endif
174
-
175
- // #ifndef APP-NVUE
176
197
  uni.createSelectorQuery()
177
198
  .in(this)
178
199
  .select(`#${id}`)
@@ -182,11 +203,18 @@ export default {
182
203
  size: true
183
204
  },
184
205
  (res) => {
206
+ // #ifdef APP-PLUS || APP-HARMONY
207
+ resolve(res || {
208
+ width: this.actualWidth,
209
+ height: this.actualHeight
210
+ });
211
+ return;
212
+ // #endif
213
+
185
214
  resolve(res || false);
186
215
  }
187
216
  )
188
217
  .exec();
189
- // #endif
190
218
  } catch (error) {
191
219
  console.error('获取画布节点失败:', error);
192
220
  resolve(false);
@@ -200,14 +228,12 @@ export default {
200
228
  return this.ctx;
201
229
  },
202
230
  getCanvasContext() {
203
- // #ifdef APP-PLUS
204
- return uni.createCanvasContext(this.canvasId, this);
231
+ // #ifdef APP-NVUE
232
+ return this._webViewContext;
205
233
  // #endif
206
234
 
207
- // #ifdef APP-NVUE
208
- return this._canvasElement && typeof this._canvasElement.getContext === 'function'
209
- ? this._canvasElement.getContext('2d')
210
- : null;
235
+ // #ifdef APP-PLUS || APP-HARMONY
236
+ return uni.createCanvasContext(this.canvasId, this);
211
237
  // #endif
212
238
 
213
239
  // #ifdef MP || H5
@@ -229,10 +255,32 @@ export default {
229
255
  }
230
256
  },
231
257
  async initCanvas(force = false) {
258
+ if (this._initPromise) {
259
+ return this._initPromise;
260
+ }
261
+
262
+ const initPromise = this._initializeCanvas(force);
263
+ this._initPromise = initPromise;
264
+
265
+ try {
266
+ return await initPromise;
267
+ } finally {
268
+ if (this._initPromise === initPromise) {
269
+ this._initPromise = null;
270
+ }
271
+ }
272
+ },
273
+ async _initializeCanvas(force = false) {
232
274
  try {
233
275
  if (this.useRootHeightAndWidth) {
234
276
  await this.setNewSize();
235
277
  }
278
+ // #ifdef APP-NVUE
279
+ if (typeof createWebViewCanvasBridge === 'function') {
280
+ return await this._initializeWebViewCanvas(force);
281
+ }
282
+ // #endif
283
+
236
284
  if (this.ctx && !force) {
237
285
  this.$emit('ready', {
238
286
  width: this.actualWidth,
@@ -247,10 +295,17 @@ export default {
247
295
  }
248
296
 
249
297
  this._selectorResult = this._canvasNode;
250
- this._canvasElement = this._canvasNode.node || this._canvasNode;
251
- this.dpr = uni.getSystemInfoSync().pixelRatio || 1;
298
+ this._canvasElement = this._isNvue
299
+ ? this._canvasNode
300
+ : (this._canvasNode.node || null);
301
+ const systemInfo = uni.getSystemInfoSync() || {};
302
+ const pixelRatio = Number(systemInfo.pixelRatio) || 1;
303
+ // 微信开发者工具的模拟器预览通常按高密度屏幕缩放,至少使用 2 倍 backing store。
304
+ this.dpr = systemInfo.platform === 'devtools'
305
+ ? Math.max(pixelRatio, 2)
306
+ : pixelRatio;
252
307
 
253
- // #ifdef MP || H5
308
+ // #ifdef MP
254
309
  if (this._canvasElement) {
255
310
  this._canvasElement.width = Math.ceil(this.actualWidth * this.dpr);
256
311
  this._canvasElement.height = Math.ceil(this.actualHeight * this.dpr);
@@ -262,7 +317,7 @@ export default {
262
317
  return false;
263
318
  }
264
319
 
265
- // #ifdef MP || H5
320
+ // #ifdef MP
266
321
  if (typeof this.ctx.setTransform === 'function') {
267
322
  this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
268
323
  } else if (typeof this.ctx.scale === 'function') {
@@ -282,6 +337,64 @@ export default {
282
337
  return false;
283
338
  }
284
339
  },
340
+ async _initializeWebViewCanvas(force = false) {
341
+ this._isNvue = true;
342
+ const systemInfo = uni.getSystemInfoSync() || {};
343
+ this.dpr = Math.max(1, Number(systemInfo.pixelRatio) || 1);
344
+
345
+ if (!this._webViewBridge) {
346
+ this._webViewBridge = createWebViewCanvasBridge({
347
+ getWebView: () => this.$refs.web,
348
+ onTouch: message => this.onWebViewTouch(message),
349
+ onReady: (message) => {
350
+ if (
351
+ this._webViewSessionId &&
352
+ message.sessionId &&
353
+ this._webViewSessionId !== message.sessionId
354
+ ) {
355
+ this._webViewInitSignature = null;
356
+ }
357
+ this._webViewSessionId = message.sessionId || null;
358
+ },
359
+ onError: error => console.error('Canvas WebView错误:', error)
360
+ });
361
+ this._webViewContext = createWebViewCanvasContext({
362
+ bridge: this._webViewBridge,
363
+ resolveImage: source => this.resolveNvueImageSource(source),
364
+ measureText: (text, state) => {
365
+ const matched = String(state.font || '').match(/(\d+(?:\.\d+)?)px/);
366
+ const fontSize = matched ? Number(matched[1]) : this.fontSize;
367
+ return { width: this.estimateTextWidth(text, fontSize) };
368
+ }
369
+ });
370
+ this.ctx = this._webViewContext;
371
+ }
372
+
373
+ await this._webViewBridge.ready();
374
+ const signature = [
375
+ this.actualWidth,
376
+ this.actualHeight,
377
+ this.dpr,
378
+ this.disableScroll ? 1 : 0
379
+ ].join(':');
380
+ if (force || this._webViewInitSignature !== signature) {
381
+ await this._webViewBridge.request('init', {
382
+ width: this.actualWidth,
383
+ height: this.actualHeight,
384
+ dpr: this.dpr,
385
+ disableScroll: this.disableScroll
386
+ });
387
+ this._webViewInitSignature = signature;
388
+ this.applyFont();
389
+ await this.clearCanvas();
390
+ }
391
+
392
+ this.$emit('ready', {
393
+ width: this.actualWidth,
394
+ height: this.actualHeight
395
+ });
396
+ return true;
397
+ },
285
398
  refresh() {
286
399
  return this.initCanvas(true);
287
400
  },
@@ -300,7 +413,7 @@ export default {
300
413
  this.setFillStyle(this.bgColor);
301
414
  this.fill();
302
415
  }
303
- this.draw();
416
+ return this.draw();
304
417
  },
305
418
  callContext(method, ...args) {
306
419
  if (this.ctx && typeof this.ctx[method] === 'function') {
@@ -341,12 +454,28 @@ export default {
341
454
  arc(x, y, radius, startAngle, endAngle, anticlockwise = false) {
342
455
  return this.callContext('arc', x, y, radius, startAngle, endAngle, anticlockwise);
343
456
  },
457
+ arcTo(x1, y1, x2, y2, radius) {
458
+ return this.callContext('arcTo', x1, y1, x2, y2, radius);
459
+ },
344
460
  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
345
461
  return this.callContext('bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y);
346
462
  },
347
463
  quadraticCurveTo(cpx, cpy, x, y) {
348
464
  return this.callContext('quadraticCurveTo', cpx, cpy, x, y);
349
465
  },
466
+ ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise = false) {
467
+ return this.callContext(
468
+ 'ellipse',
469
+ x,
470
+ y,
471
+ radiusX,
472
+ radiusY,
473
+ rotation,
474
+ startAngle,
475
+ endAngle,
476
+ anticlockwise
477
+ );
478
+ },
350
479
  clip() {
351
480
  return this.callContext('clip');
352
481
  },
@@ -365,6 +494,15 @@ export default {
365
494
  scale(x, y) {
366
495
  return this.callContext('scale', x, y);
367
496
  },
497
+ setTransform(a, b, c, d, e, f) {
498
+ return this.callContext('setTransform', a, b, c, d, e, f);
499
+ },
500
+ transform(a, b, c, d, e, f) {
501
+ return this.callContext('transform', a, b, c, d, e, f);
502
+ },
503
+ resetTransform() {
504
+ return this.callContext('resetTransform');
505
+ },
368
506
  setFillStyle(color) {
369
507
  if (!this.ctx) return;
370
508
  if (typeof this.ctx.setFillStyle === 'function') {
@@ -405,6 +543,21 @@ export default {
405
543
  this.ctx.lineJoin = lineJoin;
406
544
  }
407
545
  },
546
+ setMiterLimit(miterLimit) {
547
+ if (!this.ctx) return;
548
+ if (typeof this.ctx.setMiterLimit === 'function') {
549
+ this.ctx.setMiterLimit(miterLimit);
550
+ } else {
551
+ this.ctx.miterLimit = miterLimit;
552
+ }
553
+ },
554
+ setLineDash(segments = []) {
555
+ return this.callContext('setLineDash', segments);
556
+ },
557
+ getLineDash() {
558
+ const result = this.callContext('getLineDash');
559
+ return Array.isArray(result) ? result : [];
560
+ },
408
561
  setTextAlign(align = 'left') {
409
562
  if (!this.ctx) return;
410
563
  if (typeof this.ctx.setTextAlign === 'function') {
@@ -448,6 +601,14 @@ export default {
448
601
  this.ctx.globalAlpha = alpha;
449
602
  }
450
603
  },
604
+ setGlobalCompositeOperation(operation) {
605
+ if (!this.ctx) return;
606
+ if (typeof this.ctx.setGlobalCompositeOperation === 'function') {
607
+ this.ctx.setGlobalCompositeOperation(operation);
608
+ } else {
609
+ this.ctx.globalCompositeOperation = operation;
610
+ }
611
+ },
451
612
  setShadow(offsetX = 0, offsetY = 0, blur = 0, color = 'rgba(0,0,0,0)') {
452
613
  if (!this.ctx) return;
453
614
  if (typeof this.ctx.setShadow === 'function') {
@@ -479,14 +640,85 @@ export default {
479
640
  fillText(text, x, y) {
480
641
  return this.callContext('fillText', String(text), x, y);
481
642
  },
643
+ strokeText(text, x, y, maxWidth) {
644
+ if (maxWidth === undefined) {
645
+ return this.callContext('strokeText', String(text), x, y);
646
+ }
647
+ return this.callContext('strokeText', String(text), x, y, maxWidth);
648
+ },
649
+ /**
650
+ * 按字形宽度估算文本宽度
651
+ * @description 当平台无法给出真实测量值时的兜底。必须区分全角/半角:
652
+ * 汉字、假名、全角标点约占一个字号,按半角的 0.6 估算会短 40%,
653
+ * 中文海报因此算出"整段都放得下"而不换行。
654
+ * @param {String} text 待测量文本
655
+ * @param {Number} [fontSize] 字号,默认取组件当前字号
656
+ * @returns {Number} 估算宽度(px)
657
+ * @author jry ijry@qq.com
658
+ */
659
+ estimateTextWidth(text, fontSize) {
660
+ const size = Number(fontSize) || Number(this.fontSize) || 12;
661
+ const FULL_WIDTH = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹯＀-⦆¢-₩]/;
662
+ return Array.from(String(text)).reduce((width, char) => {
663
+ if (FULL_WIDTH.test(char)) return width + size;
664
+ if (/\s/.test(char)) return width + size * 0.28;
665
+ return width + size * 0.56;
666
+ }, 0);
667
+ },
482
668
  measureText(text) {
483
669
  if (this.ctx && typeof this.ctx.measureText === 'function') {
484
- return this.ctx.measureText(String(text));
670
+ const metrics = this.ctx.measureText(String(text));
671
+ const width = Number(metrics && metrics.width);
672
+ // 鸿蒙同步测量恒为 0(真实值只走 callback),上下文未就绪时其他
673
+ // 平台也可能返回 0。绝不能把 0 交给调用方——换行逻辑会把它当成
674
+ // "宽度足够",于是整段文本挤成一行。
675
+ if (width > 0) return metrics;
485
676
  }
486
677
  return {
487
- width: String(text).length * this.fontSize * 0.6
678
+ width: this.estimateTextWidth(text)
488
679
  };
489
680
  },
681
+ measureTextAsync(text) {
682
+ if (this.ctx && typeof this.ctx.measureTextAsync === 'function') {
683
+ return this.ctx.measureTextAsync(String(text));
684
+ }
685
+
686
+ // #ifdef APP-HARMONY
687
+ // 鸿蒙的 CanvasContext.measureText 同步返回值恒为 0,真实宽度只通过
688
+ // callback(webview.evalJSAsync) 回传,因此必须走回调形式。
689
+ if (this.ctx && typeof this.ctx.measureText === 'function' && !this._harmonyMeasureUnavailable) {
690
+ return new Promise((resolve) => {
691
+ let settled = false;
692
+ // evalJSAsync 并非所有鸿蒙版本都提供,回调可能永远不来,
693
+ // 必须兜底避免上层 await 永久挂起。且一旦超时就标记为不可用:
694
+ // 单次换行要二分测量十几次,逐次干等会把导出拖成十几秒。
695
+ const timer = setTimeout(() => {
696
+ if (settled) return;
697
+ settled = true;
698
+ this._harmonyMeasureUnavailable = true;
699
+ resolve({ width: this.estimateTextWidth(text) });
700
+ }, 300);
701
+ const done = (metrics) => {
702
+ if (settled) return;
703
+ settled = true;
704
+ clearTimeout(timer);
705
+ const width = Number(metrics && metrics.width);
706
+ resolve({ width: width > 0 ? width : this.estimateTextWidth(text) });
707
+ };
708
+ try {
709
+ const sync = this.ctx.measureText(String(text), done);
710
+ // 若该平台其实同步返回了有效值,直接采用
711
+ const syncWidth = Number(sync && sync.width);
712
+ if (syncWidth > 0) done(sync);
713
+ } catch (error) {
714
+ done(null);
715
+ }
716
+ });
717
+ }
718
+ // #endif
719
+
720
+ return Promise.resolve(this.measureText(text));
721
+ },
490
722
  createLinearGradient(x0, y0, x1, y1) {
491
723
  if (this.ctx && typeof this.ctx.createLinearGradient === 'function') {
492
724
  return this.ctx.createLinearGradient(x0, y0, x1, y1);
@@ -499,17 +731,75 @@ export default {
499
731
  }
500
732
  return null;
501
733
  },
734
+ createPattern(image, repetition = 'repeat') {
735
+ if (this.ctx && typeof this.ctx.createPattern === 'function') {
736
+ return this.ctx.createPattern(image, repetition);
737
+ }
738
+ return null;
739
+ },
740
+ async resolveNvueImageSource(src) {
741
+ if (typeof src !== 'string' || src.indexOf('data:image/') === 0) {
742
+ return src;
743
+ }
744
+ if (this._imageDataCache[src]) {
745
+ return this._imageDataCache[src];
746
+ }
747
+
748
+ const task = (async () => {
749
+ const localPath = await new Promise((resolve, reject) => {
750
+ uni.getImageInfo({
751
+ src,
752
+ success: res => resolve(res.path || res.tempFilePath),
753
+ fail: reject
754
+ });
755
+ });
756
+ if (!localPath) {
757
+ throw new Error(`Canvas图片未返回本地路径: ${src}`);
758
+ }
759
+ return this.readNvueFileAsDataURL(localPath);
760
+ })();
761
+ this._imageDataCache[src] = task;
762
+ try {
763
+ const dataUrl = await task;
764
+ this._imageDataCache[src] = dataUrl;
765
+ return dataUrl;
766
+ } catch (error) {
767
+ delete this._imageDataCache[src];
768
+ throw error;
769
+ }
770
+ },
771
+ readNvueFileAsDataURL(path) {
772
+ return new Promise((resolve, reject) => {
773
+ // #ifdef APP-NVUE
774
+ if (typeof plus === 'undefined' || !plus.io) {
775
+ reject(new Error('HTML5+ FileReader不可用'));
776
+ return;
777
+ }
778
+ plus.io.resolveLocalFileSystemURL(path, (entry) => {
779
+ entry.file((file) => {
780
+ const reader = new plus.io.FileReader();
781
+ reader.onloadend = event => resolve(event.target.result);
782
+ reader.onerror = reject;
783
+ reader.readAsDataURL(file);
784
+ }, reject);
785
+ }, reject);
786
+ return;
787
+ // #endif
788
+
789
+ resolve(path);
790
+ });
791
+ },
502
792
  loadImage(src) {
793
+ // #ifdef APP-NVUE
794
+ return this.resolveNvueImageSource(src);
795
+ // #endif
796
+
503
797
  if (this._imageCache[src]) {
504
798
  return Promise.resolve(this._imageCache[src]);
505
799
  }
506
800
  return new Promise((resolve, reject) => {
507
801
  let image = null;
508
802
 
509
- // #ifdef APP-NVUE
510
- image = new GImage();
511
- // #endif
512
-
513
803
  // #ifdef MP
514
804
  const canvas = this.getCanvasElement();
515
805
  if (canvas && typeof canvas.createImage === 'function') {
@@ -539,7 +829,11 @@ export default {
539
829
  if (!this.ctx || typeof this.ctx.drawImage !== 'function') {
540
830
  return false;
541
831
  }
542
- if (typeof source !== 'string' || (typeof this.ctx.setFillStyle === 'function' && !this._isNvue)) {
832
+ if (typeof source !== 'string') {
833
+ this.ctx.drawImage(source, ...args);
834
+ return true;
835
+ }
836
+ if (typeof this.ctx.setFillStyle === 'function' && !this._isNvue) {
543
837
  this.ctx.drawImage(source, ...args);
544
838
  return true;
545
839
  }
@@ -560,14 +854,32 @@ export default {
560
854
  return new Promise((resolve, reject) => {
561
855
  const width = options.width || this.actualWidth;
562
856
  const height = options.height || this.actualHeight;
857
+ const hasDestWidth = options.destWidth !== undefined && options.destWidth !== null;
858
+ const hasDestHeight = options.destHeight !== undefined && options.destHeight !== null;
859
+ let destWidth = hasDestWidth ? options.destWidth : width;
860
+ let destHeight = hasDestHeight ? options.destHeight : height;
861
+
862
+ // MP、H5、APP-PLUS 和 APP-HARMONY 保持逻辑绘制坐标,只提升默认导出像素。
863
+ // #ifdef MP || H5 || APP-PLUS || APP-HARMONY
864
+ if (!hasDestWidth) {
865
+ destWidth = Math.round(width * this.dpr);
866
+ }
867
+ if (!hasDestHeight) {
868
+ destHeight = Math.round(height * this.dpr);
869
+ }
870
+ // #endif
871
+
872
+ const defaultFileType = 'png';
563
873
  const request = {
564
874
  x: options.x || 0,
565
875
  y: options.y || 0,
566
876
  width,
567
877
  height,
568
- destWidth: options.destWidth || width,
569
- destHeight: options.destHeight || height,
570
- fileType: options.fileType || 'png',
878
+ destWidth,
879
+ destHeight,
880
+ fileType: options.fileType === undefined || options.fileType === null
881
+ ? defaultFileType
882
+ : options.fileType,
571
883
  quality: options.quality === undefined ? 1 : options.quality
572
884
  };
573
885
  const success = (res) => {
@@ -630,18 +942,14 @@ export default {
630
942
 
631
943
  // #ifdef APP-NVUE
632
944
  if (this.ctx && typeof this.ctx.toTempFilePath === 'function') {
633
- this.ctx.toTempFilePath(
634
- request.x,
635
- request.y,
636
- request.width,
637
- request.height,
638
- request.destWidth,
639
- request.destHeight,
640
- request.fileType,
641
- request.quality,
945
+ Promise.resolve(this.ctx.toTempFilePath(request)).then(
642
946
  (res) => {
643
947
  success(res);
644
948
  complete(res);
949
+ },
950
+ (error) => {
951
+ fail(error);
952
+ complete(error);
645
953
  }
646
954
  );
647
955
  return;
@@ -667,51 +975,96 @@ export default {
667
975
  fileType,
668
976
  quality,
669
977
  width: this.actualWidth,
670
- height: this.actualHeight,
671
- destWidth: this.actualWidth,
672
- destHeight: this.actualHeight
978
+ height: this.actualHeight
673
979
  });
674
980
  return res.tempFilePath || res.apFilePath;
675
981
  },
676
982
  getImageData(options = {}) {
677
983
  return new Promise((resolve, reject) => {
984
+ const success = (res) => {
985
+ if (typeof options.success === 'function') options.success(res);
986
+ resolve(res);
987
+ };
988
+ const fail = (err) => {
989
+ if (typeof options.fail === 'function') options.fail(err);
990
+ reject(err);
991
+ };
992
+ const complete = (res) => {
993
+ if (typeof options.complete === 'function') options.complete(res);
994
+ };
995
+ // #ifdef APP-NVUE
996
+ if (this.ctx && typeof this.ctx.getImageData === 'function') {
997
+ Promise.resolve(this.ctx.getImageData({
998
+ x: options.x || 0,
999
+ y: options.y || 0,
1000
+ width: options.width || this.actualWidth,
1001
+ height: options.height || this.actualHeight
1002
+ })).then((res) => {
1003
+ success(res);
1004
+ complete(res);
1005
+ }, (error) => {
1006
+ fail(error);
1007
+ complete(error);
1008
+ });
1009
+ return;
1010
+ }
1011
+ // #endif
1012
+
678
1013
  const request = {
679
1014
  canvasId: this.canvasId,
680
1015
  x: options.x || 0,
681
1016
  y: options.y || 0,
682
1017
  width: options.width || this.actualWidth,
683
1018
  height: options.height || this.actualHeight,
684
- success: (res) => {
685
- if (typeof options.success === 'function') options.success(res);
686
- resolve(res);
687
- },
688
- fail: (err) => {
689
- if (typeof options.fail === 'function') options.fail(err);
690
- reject(err);
691
- },
692
- complete: options.complete
1019
+ success,
1020
+ fail,
1021
+ complete
693
1022
  };
694
1023
  uni.canvasGetImageData(request, this);
695
1024
  });
696
1025
  },
697
1026
  putImageData(options = {}) {
698
1027
  return new Promise((resolve, reject) => {
1028
+ const success = (res) => {
1029
+ if (typeof options.success === 'function') options.success(res);
1030
+ resolve(res);
1031
+ };
1032
+ const fail = (err) => {
1033
+ if (typeof options.fail === 'function') options.fail(err);
1034
+ reject(err);
1035
+ };
1036
+ const complete = (res) => {
1037
+ if (typeof options.complete === 'function') options.complete(res);
1038
+ };
1039
+ // #ifdef APP-NVUE
1040
+ if (this.ctx && typeof this.ctx.putImageData === 'function') {
1041
+ Promise.resolve(this.ctx.putImageData({
1042
+ x: options.x || 0,
1043
+ y: options.y || 0,
1044
+ width: options.width || (options.data && options.data.width) || this.actualWidth,
1045
+ height: options.height || (options.data && options.data.height) || this.actualHeight,
1046
+ data: options.data
1047
+ })).then((res) => {
1048
+ success(res);
1049
+ complete(res);
1050
+ }, (error) => {
1051
+ fail(error);
1052
+ complete(error);
1053
+ });
1054
+ return;
1055
+ }
1056
+ // #endif
1057
+
699
1058
  const request = {
700
1059
  canvasId: this.canvasId,
701
1060
  x: options.x || 0,
702
1061
  y: options.y || 0,
703
- width: options.width || this.actualWidth,
704
- height: options.height || this.actualHeight,
1062
+ width: options.width || (options.data && options.data.width) || this.actualWidth,
1063
+ height: options.height || (options.data && options.data.height) || this.actualHeight,
705
1064
  data: options.data,
706
- success: (res) => {
707
- if (typeof options.success === 'function') options.success(res);
708
- resolve(res);
709
- },
710
- fail: (err) => {
711
- if (typeof options.fail === 'function') options.fail(err);
712
- reject(err);
713
- },
714
- complete: options.complete
1065
+ success,
1066
+ fail,
1067
+ complete
715
1068
  };
716
1069
  uni.canvasPutImageData(request, this);
717
1070
  });