uview-plus 3.8.86 → 3.8.106

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 (82) hide show
  1. package/changelog.md +150 -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 +368 -74
  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 +236 -219
  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/context-2d/FillStyleLinearGradient.js +0 -0
  56. package/libs/util/gcanvas/context-2d/FillStylePattern.js +0 -0
  57. package/libs/util/gcanvas/context-2d/FillStyleRadialGradient.js +0 -0
  58. package/libs/util/gcanvas/context-2d/RenderingContext.js +0 -0
  59. package/libs/util/gcanvas/context-webgl/ActiveInfo.js +0 -0
  60. package/libs/util/gcanvas/context-webgl/Buffer.js +0 -0
  61. package/libs/util/gcanvas/context-webgl/Framebuffer.js +0 -0
  62. package/libs/util/gcanvas/context-webgl/GLenum.js +0 -0
  63. package/libs/util/gcanvas/context-webgl/GLmethod.js +0 -0
  64. package/libs/util/gcanvas/context-webgl/GLtype.js +0 -0
  65. package/libs/util/gcanvas/context-webgl/Program.js +0 -0
  66. package/libs/util/gcanvas/context-webgl/Renderbuffer.js +0 -0
  67. package/libs/util/gcanvas/context-webgl/RenderingContext.js +0 -0
  68. package/libs/util/gcanvas/context-webgl/Shader.js +0 -0
  69. package/libs/util/gcanvas/context-webgl/ShaderPrecisionFormat.js +0 -0
  70. package/libs/util/gcanvas/context-webgl/Texture.js +0 -0
  71. package/libs/util/gcanvas/context-webgl/UniformLocation.js +0 -0
  72. package/libs/util/gcanvas/context-webgl/classUtils.js +0 -0
  73. package/libs/util/gcanvas/env/canvas.js +0 -0
  74. package/libs/util/gcanvas/env/image.js +0 -0
  75. package/libs/util/gcanvas/env/tool.js +0 -0
  76. package/libs/util/gcanvas/index.js +3 -3
  77. package/package.json +1 -1
  78. package/types/comps/swipeActionItem.d.ts +13 -0
  79. package/types/comps/tabs.d.ts +2 -1
  80. package/types/comps/text.d.ts +1 -1
  81. package/types/func.d.ts +23 -0
  82. package/types/index.d.ts +1 -0
@@ -1,27 +1,31 @@
1
1
  <template>
2
2
  <view class="up-poster">
3
3
  <!-- canvas用于绘制海报 -->
4
- <up-canvas
5
- v-if="showCanvas"
6
- ref="posterCanvas"
7
- class="up-poster__hidden-canvas"
8
- :canvas-id="canvasId"
9
- :width="canvasWidth"
10
- :height="canvasHeight"
11
- bg-color="transparent"
12
- :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
13
- </up-canvas>
4
+ <view v-if="showCanvas" class="up-poster__renderer">
5
+ <up-canvas
6
+ ref="posterCanvas"
7
+ class="up-poster__hidden-canvas"
8
+ :canvas-id="canvasId"
9
+ :width="canvasWidth"
10
+ :height="canvasHeight"
11
+ bg-color="transparent"
12
+ :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }">
13
+ </up-canvas>
14
+ </view>
14
15
  <!-- 隐藏的二维码组件,用于生成二维码图片 -->
15
- <up-qrcode
16
- ref="qrCode"
17
- :val="qrCodeValue"
18
- :size="qrCodeSize"
19
- :margin="0"
20
- :loadMake="false"
21
- background="#ffffff"
22
- foreground="#000000"
23
- :class="['up-poster__hidden-qrcode', qrCodeShow ? '' : 'up-poster__hidden-qrcode--hidden']"
24
- />
16
+ <view class="up-poster__renderer">
17
+ <up-qrcode
18
+ ref="qrCode"
19
+ :val="qrCodeValue"
20
+ :size="qrCodeSize"
21
+ :margin="0"
22
+ :loadMake="false"
23
+ :onval="false"
24
+ background="#ffffff"
25
+ foreground="#000000"
26
+ :class="['up-poster__hidden-qrcode', qrCodeShow ? '' : 'up-poster__hidden-qrcode--hidden']"
27
+ />
28
+ </view>
25
29
  </view>
26
30
  </template>
27
31
 
@@ -89,80 +93,74 @@ export default {
89
93
  * @author jry ijry@qq.com
90
94
  */
91
95
  async exportImage() {
92
- return new Promise(async(resolve, reject) => {
93
- try {
94
- // 获取海报尺寸信息
95
- const posterSize = this.json.css;
96
- // 将rpx转换为px
97
- const width = this.convertRpxToPx(posterSize.width || '750rpx');
98
- const height = this.convertRpxToPx(posterSize.height || '1114rpx');
99
-
100
- // 设置canvas尺寸
101
- this.canvasWidth = width;
102
- this.canvasHeight = height;
103
- this.showCanvas = true;
104
-
105
- // 等待DOM更新
106
- await this.$nextTick();
107
-
108
- const posterCanvas = await this.getPosterCanvas();
109
- const ctx = posterCanvas;
110
-
111
- // 绘制背景
112
- if (posterSize.background) {
113
- // 支持渐变背景色
96
+ let timeoutId = null;
97
+ try {
98
+ const timeoutPromise = new Promise((resolve, reject) => {
99
+ timeoutId = setTimeout(() => reject(new Error('导出图片超时')), 20000);
100
+ });
101
+ const exportPromise = (async () => {
102
+ // 获取海报尺寸信息
103
+ const posterSize = this.json.css || {};
104
+ const width = this.convertRpxToPx(posterSize.width || '750rpx');
105
+ const height = this.convertRpxToPx(posterSize.height || '1114rpx');
106
+
107
+ this.canvasWidth = width;
108
+ this.canvasHeight = height;
109
+ this.showCanvas = true;
110
+ await this.$nextTick();
111
+
112
+ const posterCanvas = await this.getPosterCanvas();
113
+ const ctx = posterCanvas;
114
+ if (posterSize.background) {
114
115
  if (posterSize.background.includes('linear-gradient') || posterSize.background.includes('radial-gradient')) {
115
116
  this.drawGradientBackground(ctx, posterSize, 0, 0, width, height);
116
117
  } else {
117
118
  ctx.setFillStyle(posterSize.background);
118
- ctx.fillRect(0, 0, width, height);
119
+ ctx.fillRect(0, 0, width, height);
119
120
  }
120
- }
121
-
122
- // 绘制所有元素
123
- for (const item of this.json.views) {
124
- await this.drawItem(ctx, item, width, height);
125
- }
126
-
127
- // 绘制到canvas
128
- ctx.draw(false, () => {
129
- // 等待绘制完成
130
- setTimeout(() => {
131
- posterCanvas.toTempFilePath({
132
- width,
133
- height,
134
- destWidth: width,
135
- destHeight: height,
136
- success: (res) => {
137
- // 隐藏canvas
138
- this.showCanvas = false;
139
- // 返回图片路径
140
- resolve({
141
- width: width,
142
- height: height,
143
- path: res.tempFilePath,
144
- // H5下添加blob格式
145
- blob: this.dataURLToBlob(res.tempFilePath)
146
- });
147
- },
148
- fail: (err) => {
149
- // 隐藏canvas
150
- this.showCanvas = false;
151
- reject(new Error('导出图片失败: ' + JSON.stringify(err)));
152
- }
153
- });
154
- }, 300);
155
- });
156
-
157
- // 超时处理
158
- setTimeout(() => {
159
- this.showCanvas = false;
160
- reject(new Error('导出图片超时'));
161
- }, 10000);
162
- } catch (error) {
163
- this.showCanvas = false;
164
- reject(error);
165
- }
121
+ }
122
+
123
+ for (const item of this.json.views || []) {
124
+ await this.drawItem(ctx, item, width, height);
125
+ }
126
+
127
+ await this.flushPosterCanvas(posterCanvas);
128
+ const res = await posterCanvas.toTempFilePath({ width, height });
129
+ const path = res && (res.tempFilePath || res.apFilePath);
130
+ if (!path) {
131
+ throw new Error('导出图片未返回文件路径');
132
+ }
133
+ return {
134
+ width,
135
+ height,
136
+ path,
137
+ blob: this.dataURLToBlob(path)
138
+ };
139
+ })();
140
+ return await Promise.race([exportPromise, timeoutPromise]);
141
+ } finally {
142
+ if (timeoutId) clearTimeout(timeoutId);
143
+ this.showCanvas = false;
144
+ }
145
+ },
146
+
147
+ flushPosterCanvas(posterCanvas) {
148
+ return new Promise((resolve, reject) => {
149
+ let settled = false;
150
+ const finish = (error) => {
151
+ if (settled) return;
152
+ settled = true;
153
+ if (error) reject(error);
154
+ else resolve();
155
+ };
156
+ try {
157
+ const result = posterCanvas.draw(false, () => finish());
158
+ if (result && typeof result.then === 'function') {
159
+ result.then(() => finish(), finish);
160
+ }
161
+ } catch (error) {
162
+ finish(error);
163
+ }
166
164
  });
167
165
  },
168
166
 
@@ -172,7 +170,10 @@ export default {
172
170
  if (!posterCanvas) {
173
171
  throw new Error('无法获取海报画布实例');
174
172
  }
175
- await posterCanvas.initCanvas(true);
173
+ const initialized = await posterCanvas.initCanvas();
174
+ if (!initialized || !posterCanvas.getRawContext()) {
175
+ throw new Error('无法初始化海报画布');
176
+ }
176
177
  return posterCanvas;
177
178
  },
178
179
 
@@ -215,7 +216,7 @@ export default {
215
216
 
216
217
  case 'text':
217
218
  // 设置文本样式
218
- if (css.color) ctx.setFillStyle(css.color);
219
+ ctx.setFillStyle(css.color || '#000000');
219
220
  if (css.fontSize) {
220
221
  const fontSize = this.convertRpxToPx(css.fontSize);
221
222
  ctx.setFontSize(fontSize);
@@ -226,7 +227,7 @@ export default {
226
227
 
227
228
  // 处理文本换行
228
229
  if (css.lineClamp) {
229
- this.drawTextWithLineClamp(ctx, item.text, left, top, width, css);
230
+ await this.drawTextWithLineClamp(ctx, item.text, left, top, width, css);
230
231
  } else {
231
232
  // 修复:文本垂直居中对齐问题
232
233
  const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
@@ -236,28 +237,27 @@ export default {
236
237
 
237
238
  case 'image':
238
239
  // 绘制图片
239
- return new Promise((resolve) => {
240
+ return new Promise((resolve, reject) => {
240
241
  uni.getImageInfo({
241
242
  src: item.src,
242
243
  success: async (res) => {
243
- // console.log('图片加载成功: ' + item.src, res);
244
- // 处理圆角
245
- if (css.radius) {
246
- const radius = this.convertRpxToPx(css.radius);
247
- this.clipRoundRect(ctx, left, top, width, height, radius);
244
+ let clipped = false;
245
+ try {
246
+ if (css.radius) {
247
+ const radius = this.convertRpxToPx(css.radius);
248
+ this.clipRoundRect(ctx, left, top, width, height, radius);
249
+ clipped = true;
250
+ }
251
+ await ctx.drawImage(res.path, left, top, width, height);
252
+ resolve();
253
+ } catch (error) {
254
+ reject(new Error(`海报图片绘制失败: ${error && error.message ? error.message : error}`));
255
+ } finally {
256
+ if (clipped) ctx.restore();
248
257
  }
249
- // 不能用item.src,要用res.path。
250
- await ctx.drawImage(res.path, left, top, width, height);
251
- // 恢复剪切区域
252
- ctx.restore();
253
- resolve();
254
258
  },
255
259
  fail: (e) => {
256
- // 图片加载失败时绘制占位符
257
- ctx.setFillStyle('#f5f5f5');
258
- ctx.fillRect(left, top, width, height);
259
- console.log('图片加载失败: ' + item.src, e);
260
- resolve();
260
+ reject(new Error(`海报图片加载失败: ${e && e.errMsg ? e.errMsg : e}`));
261
261
  }
262
262
  });
263
263
  });
@@ -267,26 +267,10 @@ export default {
267
267
  if (item.text) {
268
268
  // 使用u-qrcode生成二维码图片
269
269
  const qrCodeImageUrl = await this.generateQRCode(item.text, width, height);
270
- return new Promise((resolve) => {
271
- uni.getImageInfo({
272
- src: qrCodeImageUrl,
273
- success: async (res) => {
274
- await ctx.drawImage(res.path, left, top, width, height);
275
- resolve();
276
- },
277
- fail: () => {
278
- // 二维码加载失败时绘制占位符
279
- ctx.setFillStyle('#f5f5f5');
280
- ctx.fillRect(left, top, width, height);
281
- ctx.setFillStyle('#999');
282
- ctx.setFontSize(12);
283
- ctx.setTextAlign('center');
284
- ctx.fillText('QR', left + width/2, top + height/2);
285
- ctx.setTextAlign('left');
286
- resolve();
287
- }
288
- });
289
- });
270
+ const drawn = await ctx.drawImage(qrCodeImageUrl, left, top, width, height);
271
+ if (drawn === false) {
272
+ throw new Error('二维码图片无法绘制到海报画布');
273
+ }
290
274
  } else {
291
275
  // 绘制二维码占位符
292
276
  ctx.setFillStyle('#f5f5f5');
@@ -369,51 +353,71 @@ export default {
369
353
  * @param {Object} css 样式配置
370
354
  * @author jry ijry@qq.com
371
355
  */
372
- drawTextWithLineClamp(ctx, text, x, y, maxWidth, css) {
373
- const lineClamp = parseInt(css.lineClamp) || 1;
374
- const lineHeight = css.lineHeight ? this.convertRpxToPx(css.lineHeight) : 20;
375
- const lines = [];
376
- let currentLine = '';
377
- const ellipsis = '...';
378
- const appendEllipsis = (line) => {
379
- let fitLine = line;
380
- while (ctx.measureText(fitLine + ellipsis).width > maxWidth && fitLine.length > 0) {
381
- fitLine = fitLine.substring(0, fitLine.length - 1);
382
- }
383
- return fitLine + ellipsis;
384
- };
385
-
386
- for (let i = 0; i < text.length; i++) {
387
- const char = text[i];
388
- const testLine = currentLine + char;
389
- const metrics = ctx.measureText(testLine);
390
-
391
- if (metrics.width > maxWidth && currentLine !== '') {
392
- lines.push(currentLine);
393
-
394
- // 如果已达最大行数,添加省略号并结束
395
- if (lines.length === lineClamp) {
396
- lines[lines.length - 1] = appendEllipsis(currentLine);
397
- break;
398
- }
399
-
400
- currentLine = char;
401
- } else {
402
- currentLine = testLine;
403
- }
404
-
405
- // 处理最后一行
406
- if (i === text.length - 1 && lines.length < lineClamp) {
407
- lines.push(currentLine);
408
- }
409
- }
410
-
411
- // 绘制每一行
412
- for (let i = 0; i < lines.length; i++) {
413
- // 修复:正确计算文本垂直位置
414
- const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
415
- ctx.fillText(lines[i], x, y + (i * lineHeight) + textBaseLine);
416
- }
356
+ async drawTextWithLineClamp(ctx, text, x, y, maxWidth, css) {
357
+ const lineClamp = Math.max(1, parseInt(css.lineClamp, 10) || 1);
358
+ const lineHeight = css.lineHeight ? this.convertRpxToPx(css.lineHeight) : 20;
359
+ const characters = Array.from(String(text || ''));
360
+ const lines = [];
361
+ const ellipsis = '...';
362
+ const measurementCache = new Map();
363
+ const measureTextWidth = async (value) => {
364
+ if (measurementCache.has(value)) {
365
+ return measurementCache.get(value);
366
+ }
367
+ let metrics;
368
+ if (ctx && typeof ctx.measureTextAsync === 'function') {
369
+ metrics = await ctx.measureTextAsync(value);
370
+ } else {
371
+ metrics = ctx.measureText(value);
372
+ }
373
+ const measuredWidth = Number(metrics && metrics.width) || 0;
374
+ measurementCache.set(value, measuredWidth);
375
+ return measuredWidth;
376
+ };
377
+ const findTextFitLength = async (values, suffix = '') => {
378
+ let low = 0;
379
+ let high = values.length;
380
+ while (low < high) {
381
+ const middle = Math.ceil((low + high) / 2);
382
+ const candidate = values.slice(0, middle).join('') + suffix;
383
+ if (await measureTextWidth(candidate) <= maxWidth) {
384
+ low = middle;
385
+ } else {
386
+ high = middle - 1;
387
+ }
388
+ }
389
+ return low;
390
+ };
391
+
392
+ let offset = 0;
393
+ while (offset < characters.length && lines.length < lineClamp) {
394
+ const remaining = characters.slice(offset);
395
+ const remainingText = remaining.join('');
396
+ if (await measureTextWidth(remainingText) <= maxWidth) {
397
+ lines.push(remainingText);
398
+ break;
399
+ }
400
+
401
+ const isLastLine = lines.length === lineClamp - 1;
402
+ if (isLastLine) {
403
+ let suffix = ellipsis;
404
+ while (suffix && await measureTextWidth(suffix) > maxWidth) {
405
+ suffix = suffix.slice(0, -1);
406
+ }
407
+ const fitLength = await findTextFitLength(remaining, suffix);
408
+ lines.push(remaining.slice(0, fitLength).join('') + suffix);
409
+ break;
410
+ }
411
+
412
+ const fitLength = Math.max(1, await findTextFitLength(remaining));
413
+ lines.push(remaining.slice(0, fitLength).join(''));
414
+ offset += fitLength;
415
+ }
416
+
417
+ const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
418
+ for (let i = 0; i < lines.length; i++) {
419
+ ctx.fillText(lines[i], x, y + (i * lineHeight) + textBaseLine);
420
+ }
417
421
  },
418
422
 
419
423
  /**
@@ -425,54 +429,41 @@ export default {
425
429
  * @returns {Promise<String>} 二维码图片URL
426
430
  * @author jry ijry@qq.com
427
431
  */
428
- generateQRCode(text, width, height) {
429
- return new Promise((resolve) => {
430
- // 为每个二维码生成唯一标识
431
- const qrCodeKey = `${text}_${width}_${height}`;
432
-
433
- // 检查是否已经生成过该二维码
434
- if (this.qrCodeMap.has(qrCodeKey)) {
435
- resolve(this.qrCodeMap.get(qrCodeKey));
436
- return;
432
+ async generateQRCode(text, width, height) {
433
+ const qrCodeKey = `${text}_${width}_${height}`;
434
+ if (this.qrCodeMap.has(qrCodeKey)) {
435
+ return this.qrCodeMap.get(qrCodeKey);
436
+ }
437
+
438
+ this.qrCodeValue = text;
439
+ this.qrCodeSize = Math.max(width, height);
440
+ this.qrCodeShow = true;
441
+
442
+ try {
443
+ await this.$nextTick();
444
+ const qrCode = this.$refs.qrCode;
445
+ if (!qrCode) {
446
+ throw new Error('无法获取二维码组件实例');
437
447
  }
438
-
439
- // 使用 u-qrcode 组件生成二维码
440
- try {
441
- // 设置二维码参数
442
- this.qrCodeValue = text;
443
- this.qrCodeSize = Math.max(width, height);
444
- this.qrCodeShow = true;
445
-
446
- // 等待DOM更新
447
- this.$nextTick(() => {
448
- // 获取二维码组件实例并导出图片
449
- if (this.$refs.qrCode) {
450
- // 延迟一点时间确保二维码渲染完成
451
- setTimeout(() => {
452
- // 调用 u-qrcode 的 toTempFilePath 方法导出图片
453
- this.$refs.qrCode.toTempFilePath({
454
- success: (res) => {
455
- // 缓存二维码图片路径
456
- this.qrCodeMap.set(qrCodeKey, res.tempFilePath);
457
- this.qrCodeShow = false;
458
- resolve(res.tempFilePath);
459
- },
460
- fail: (err) => {
461
- console.error('二维码生成失败:', err);
462
- this.qrCodeShow = false;
463
- }
464
- });
465
- }, 300);
466
- } else {
467
- // 如果没有 u-qrcode 组件,返回占位符
468
- this.qrCodeShow = false;
469
- }
470
- });
471
- } catch (error) {
472
- console.error('生成二维码出错:', error);
473
- this.qrCodeShow = false;
448
+ if (typeof qrCode.refreshCanvas === 'function') {
449
+ await qrCode.refreshCanvas();
474
450
  }
475
- });
451
+ const generatedPath = await qrCode._makeCode();
452
+ let tempFilePath = typeof generatedPath === 'string' ? generatedPath : '';
453
+ if (!tempFilePath) {
454
+ const res = await qrCode.toTempFilePath();
455
+ tempFilePath = res && (res.tempFilePath || res.apFilePath);
456
+ }
457
+ if (!tempFilePath) {
458
+ throw new Error('二维码组件未返回图片路径');
459
+ }
460
+ this.qrCodeMap.set(qrCodeKey, tempFilePath);
461
+ return tempFilePath;
462
+ } catch (error) {
463
+ throw new Error(`二维码生成失败: ${error && error.message ? error.message : error}`);
464
+ } finally {
465
+ this.qrCodeShow = false;
466
+ }
476
467
  },
477
468
 
478
469
  /**
@@ -625,6 +616,31 @@ export default {
625
616
  overflow: hidden;
626
617
  }
627
618
 
619
+ /* #ifdef APP-NVUE */
620
+ &__renderer {
621
+ position: fixed;
622
+ top: -1px;
623
+ left: -1px;
624
+ width: 1px;
625
+ height: 1px;
626
+ overflow: hidden;
627
+ z-index: -1;
628
+ opacity: 0.01;
629
+ pointer-events: none;
630
+ }
631
+ /* #endif */
632
+
633
+ /* #ifndef APP-NVUE */
634
+ &__renderer {
635
+ position: fixed;
636
+ top: -10000px;
637
+ left: -10000px;
638
+ width: 1px;
639
+ height: 1px;
640
+ overflow: hidden;
641
+ z-index: -1;
642
+ }
643
+
628
644
  &__hidden-canvas {
629
645
  position: fixed;
630
646
  top: -10000px;
@@ -642,5 +658,6 @@ export default {
642
658
  display: none;
643
659
  }
644
660
  }
661
+ /* #endif */
645
662
  }
646
663
  </style>
@@ -1164,60 +1164,67 @@ let QRCode = {};
1164
1164
  }
1165
1165
  }
1166
1166
  }
1167
- setTimeout(() => {
1168
- // canvas2 绘制是自动的不需要手动绘制
1169
- if(options.isNvue || isApp){
1170
- const exportCode = () => {
1171
- // 保存到临时区域
1172
- setTimeout(() => {
1173
- if (options.canvasHost && typeof options.canvasHost.toTempFilePath === 'function') {
1174
- options.canvasHost.toTempFilePath({
1175
- width: options.width || options.size,
1176
- height: options.height || options.size,
1177
- destWidth: options.width || options.size,
1178
- destHeight: options.height || options.size,
1179
- quality: Number(1),
1180
- success: function (res) {
1181
- if (options.cbResult) {
1182
- if (!empty(res.tempFilePath)) {
1183
- options.cbResult(res.tempFilePath)
1184
- } else if (!empty(res.apFilePath)) {
1185
- options.cbResult(res.apFilePath)
1186
- } else {
1187
- options.cbResult(res.tempFilePath)
1188
- }
1189
- }
1190
- },
1191
- fail: function (res) {
1192
- console.log('绘制失败', res)
1193
- if (options.cbResult) {
1194
- options.cbResult(res)
1195
- }
1196
- },
1197
- complete: function () {
1198
- uni.hideLoading();
1199
- },
1200
- }).catch(function(res) {
1201
- console.log('绘制失败', res)
1202
- });
1203
- }
1204
- }, options.text.length + 100);
1205
- };
1167
+ // canvas2 绘制是自动的不需要手动绘制
1168
+ if(options.isNvue || isApp){
1169
+ let exportStarted = false;
1170
+ const reportFailure = function(error) {
1171
+ uni.hideLoading();
1172
+ if (options.cbResult) {
1173
+ options.cbResult(error instanceof Error ? error : new Error(String(error || '绘制失败')))
1174
+ }
1175
+ };
1176
+ const exportCode = async function() {
1177
+ if (exportStarted) return;
1178
+ exportStarted = true;
1179
+ try {
1180
+ if (!options.canvasHost || typeof options.canvasHost.toTempFilePath !== 'function') {
1181
+ throw new Error('无法获取二维码画布导出方法');
1182
+ }
1183
+ const res = await options.canvasHost.toTempFilePath({
1184
+ width: options.width || options.size,
1185
+ height: options.height || options.size,
1186
+ quality: Number(1)
1187
+ });
1188
+ const tempFilePath = res && (res.tempFilePath || res.apFilePath);
1189
+ if (empty(tempFilePath)) {
1190
+ throw new Error('二维码导出未返回文件路径');
1191
+ }
1192
+ if (options.cbResult) {
1193
+ options.cbResult(tempFilePath)
1194
+ }
1195
+ uni.hideLoading();
1196
+ } catch (error) {
1197
+ reportFailure(error);
1198
+ }
1199
+ };
1200
+ try {
1201
+ let drawResult;
1206
1202
  if (options.canvasHost && typeof options.canvasHost.draw === 'function') {
1207
- options.canvasHost.draw(true, exportCode);
1203
+ drawResult = options.canvasHost.draw(true, exportCode);
1208
1204
  } else if (ctx && typeof ctx.draw === 'function') {
1209
- ctx.draw(true, exportCode);
1205
+ drawResult = ctx.draw(true, exportCode);
1210
1206
  } else {
1211
1207
  exportCode();
1212
1208
  }
1213
- }
1214
- else{
1215
- options.cbResult("")
1216
- }
1217
-
1218
- }, options.usingComponents ? 100 : 200);
1219
- }
1220
- createCanvas(this.options);
1209
+ if (drawResult && typeof drawResult.then === 'function') {
1210
+ drawResult.then(exportCode, reportFailure);
1211
+ }
1212
+ } catch (error) {
1213
+ reportFailure(error);
1214
+ }
1215
+ }
1216
+ else{
1217
+ setTimeout(function() {
1218
+ options.cbResult("")
1219
+ }, options.usingComponents ? 100 : 200);
1220
+ }
1221
+ }
1222
+ createCanvas(this.options).catch(function(error) {
1223
+ uni.hideLoading();
1224
+ if (opt.cbResult) {
1225
+ opt.cbResult(error instanceof Error ? error : new Error(String(error || '二维码绘制失败')))
1226
+ }
1227
+ });
1221
1228
  // 空判定
1222
1229
  let empty = function (v) {
1223
1230
  let tp = typeof v,