uview-plus 3.8.106 → 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 (27) hide show
  1. package/changelog.md +23 -0
  2. package/components/u-canvas/u-canvas.vue +67 -8
  3. package/components/u-poster/u-poster.vue +37 -4
  4. package/libs/util/gcanvas/bridge/bridge-weex.js +0 -0
  5. package/libs/util/gcanvas/context-2d/FillStyleLinearGradient.js +0 -0
  6. package/libs/util/gcanvas/context-2d/FillStylePattern.js +0 -0
  7. package/libs/util/gcanvas/context-2d/FillStyleRadialGradient.js +0 -0
  8. package/libs/util/gcanvas/context-2d/RenderingContext.js +0 -0
  9. package/libs/util/gcanvas/context-webgl/ActiveInfo.js +0 -0
  10. package/libs/util/gcanvas/context-webgl/Buffer.js +0 -0
  11. package/libs/util/gcanvas/context-webgl/Framebuffer.js +0 -0
  12. package/libs/util/gcanvas/context-webgl/GLenum.js +0 -0
  13. package/libs/util/gcanvas/context-webgl/GLmethod.js +0 -0
  14. package/libs/util/gcanvas/context-webgl/GLtype.js +0 -0
  15. package/libs/util/gcanvas/context-webgl/Program.js +0 -0
  16. package/libs/util/gcanvas/context-webgl/Renderbuffer.js +0 -0
  17. package/libs/util/gcanvas/context-webgl/RenderingContext.js +0 -0
  18. package/libs/util/gcanvas/context-webgl/Shader.js +0 -0
  19. package/libs/util/gcanvas/context-webgl/ShaderPrecisionFormat.js +0 -0
  20. package/libs/util/gcanvas/context-webgl/Texture.js +0 -0
  21. package/libs/util/gcanvas/context-webgl/UniformLocation.js +0 -0
  22. package/libs/util/gcanvas/context-webgl/classUtils.js +0 -0
  23. package/libs/util/gcanvas/env/canvas.js +0 -0
  24. package/libs/util/gcanvas/env/image.js +0 -0
  25. package/libs/util/gcanvas/env/tool.js +0 -0
  26. package/libs/util/gcanvas/index.js +0 -0
  27. package/package.json +2 -2
package/changelog.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 3.8.108(2026-08-18)
2
+ fix: 修复鸿蒙二维码不显示、海报文字不换行等绘图问题
3
+
4
+ 鸿蒙 CanvasContext.measureText 同步返回值恒为 0,真实宽度只通过可选 callback 经 evalJSAsync 异步回传;
5
+ 而 uni-app-plus 走 evalJSSync 同步就能拿到真值。这是同样代码在安卓
6
+ 正常、鸿蒙异常的根因。
7
+
8
+ 宽度 0 会让 drawTextWithLineClamp 里 `0 <= maxWidth` 恒成立,整段
9
+ 文本被当成一行放得下直接输出,标题横穿并压到二维码上。
10
+
11
+ - u-canvas: 新增 #ifdef APP-HARMONY 的 callback 测量分支;measureText
12
+ 不再把 0 当有效值外传;新增 estimateTextWidth 兜底
13
+ - u-poster: measureTextWidth 拿到非正数宽度时改用估算,不再 `|| 0` 静默吞掉
14
+ - 兜底估算区分全角/半角(全角 1.0、半角 0.56、空白 0.28)。原先的
15
+ length * fontSize * 0.6 会让中文短算约 40%,仍然不换行
16
+ - callback 超时从 1000ms 降到 300ms,并用 _harmonyMeasureUnavailable
17
+ 锁存状态:一次换行要二分测量十几次,逐次干等会把导出拖成十几秒
18
+ - 新增 verify:harmony-poster-text-wrap 校验脚本
19
+
20
+
21
+ ## 3.8.107(2026-08-18)
22
+ fix: 修复empty示例页面
23
+
1
24
  ## 3.8.106
2
25
  feat: 让 App 原生 nvue 页面完整复用 App.up.vue 根容器
3
26
 
@@ -21,7 +21,7 @@
21
21
  />
22
22
  <!-- #endif -->
23
23
 
24
- <!-- #ifdef APP-PLUS -->
24
+ <!-- #ifdef APP-PLUS || APP-HARMONY -->
25
25
  <!-- #ifndef APP-NVUE -->
26
26
  <canvas
27
27
  class="u-canvas__canvas"
@@ -203,7 +203,7 @@ export default {
203
203
  size: true
204
204
  },
205
205
  (res) => {
206
- // #ifdef APP-PLUS
206
+ // #ifdef APP-PLUS || APP-HARMONY
207
207
  resolve(res || {
208
208
  width: this.actualWidth,
209
209
  height: this.actualHeight
@@ -232,7 +232,7 @@ export default {
232
232
  return this._webViewContext;
233
233
  // #endif
234
234
 
235
- // #ifdef APP-PLUS
235
+ // #ifdef APP-PLUS || APP-HARMONY
236
236
  return uni.createCanvasContext(this.canvasId, this);
237
237
  // #endif
238
238
 
@@ -364,7 +364,7 @@ export default {
364
364
  measureText: (text, state) => {
365
365
  const matched = String(state.font || '').match(/(\d+(?:\.\d+)?)px/);
366
366
  const fontSize = matched ? Number(matched[1]) : this.fontSize;
367
- return { width: String(text).length * fontSize * 0.6 };
367
+ return { width: this.estimateTextWidth(text, fontSize) };
368
368
  }
369
369
  });
370
370
  this.ctx = this._webViewContext;
@@ -646,18 +646,77 @@ export default {
646
646
  }
647
647
  return this.callContext('strokeText', String(text), x, y, maxWidth);
648
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
+ },
649
668
  measureText(text) {
650
669
  if (this.ctx && typeof this.ctx.measureText === 'function') {
651
- 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;
652
676
  }
653
677
  return {
654
- width: String(text).length * this.fontSize * 0.6
678
+ width: this.estimateTextWidth(text)
655
679
  };
656
680
  },
657
681
  measureTextAsync(text) {
658
682
  if (this.ctx && typeof this.ctx.measureTextAsync === 'function') {
659
683
  return this.ctx.measureTextAsync(String(text));
660
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
+
661
720
  return Promise.resolve(this.measureText(text));
662
721
  },
663
722
  createLinearGradient(x0, y0, x1, y1) {
@@ -800,8 +859,8 @@ export default {
800
859
  let destWidth = hasDestWidth ? options.destWidth : width;
801
860
  let destHeight = hasDestHeight ? options.destHeight : height;
802
861
 
803
- // MP、H5 和 APP-PLUS 保持逻辑绘制坐标,只提升默认导出像素。
804
- // #ifdef MP || H5 || APP-PLUS
862
+ // MP、H5、APP-PLUS 和 APP-HARMONY 保持逻辑绘制坐标,只提升默认导出像素。
863
+ // #ifdef MP || H5 || APP-PLUS || APP-HARMONY
805
864
  if (!hasDestWidth) {
806
865
  destWidth = Math.round(width * this.dpr);
807
866
  }
@@ -360,6 +360,16 @@ export default {
360
360
  const lines = [];
361
361
  const ellipsis = '...';
362
362
  const measurementCache = new Map();
363
+ const fontSize = css.fontSize ? this.convertRpxToPx(css.fontSize) : 20;
364
+ // 兜底估算:全角字符约占一个字号,半角约 0.56。测量失败时若按半角估算,
365
+ // 中文会短算 40% 而不换行,所以必须区分宽度。
366
+ const estimateWidth = (value) => Array.from(String(value)).reduce((width, char) => {
367
+ if (/[ᄀ-ᅟ⺀-〾぀-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹯＀-⦆¢-₩]/.test(char)) {
368
+ return width + fontSize;
369
+ }
370
+ if (/\s/.test(char)) return width + fontSize * 0.28;
371
+ return width + fontSize * 0.56;
372
+ }, 0);
363
373
  const measureTextWidth = async (value) => {
364
374
  if (measurementCache.has(value)) {
365
375
  return measurementCache.get(value);
@@ -370,7 +380,12 @@ export default {
370
380
  } else {
371
381
  metrics = ctx.measureText(value);
372
382
  }
373
- const measuredWidth = Number(metrics && metrics.width) || 0;
383
+ let measuredWidth = Number(metrics && metrics.width) || 0;
384
+ // 测量不可用时(鸿蒙同步 measureText 恒返回 0)必须改用估算:
385
+ // 把 0 当作真实宽度会让下面的 <= maxWidth 恒成立,整段文本挤成一行。
386
+ if (!(measuredWidth > 0) && value) {
387
+ measuredWidth = estimateWidth(value);
388
+ }
374
389
  measurementCache.set(value, measuredWidth);
375
390
  return measuredWidth;
376
391
  };
@@ -475,15 +490,33 @@ export default {
475
490
  */
476
491
  convertRpxToPx(rpxValue) {
477
492
  if (typeof rpxValue === 'number') return rpxValue;
478
-
493
+
479
494
  // 使用rpx2px方法
480
495
  if (typeof rpxValue === 'string' && rpxValue.endsWith('rpx')) {
481
496
  const value = parseFloat(rpxValue);
482
- return rpx2px(value);
497
+ return value * this.getRpxRatio();
483
498
  }
484
-
499
+
485
500
  return parseFloat(rpxValue) || 0;
486
501
  },
502
+
503
+ /**
504
+ * 获取 rpx -> px 的统一换算比例
505
+ * @description uni.upx2px 对每个值单独判断设备宽度:当 windowWidth 超过
506
+ * rpxCalcMaxDeviceWidth(默认960)时,只有恰好等于 750 的值使用真实设备宽度,
507
+ * 其余值回退到 rpxCalcBaseDeviceWidth(默认375)。海报需要所有尺寸共用同一
508
+ * 比例,逐值换算会让宽高用上不同基准而破坏宽高比:鸿蒙 windowWidth 上报为
509
+ * 物理像素1084,750rpx算得1084、1114rpx算得557,海报因此变成横向。
510
+ * 这里只取一次比例,且刻意用非 750 的值探测,避开上面的特例分支,
511
+ * 使超宽设备统一走 baseWidth,避免画布被放大到数十MB。
512
+ * @returns {Number} 每 1rpx 对应的 px 值
513
+ * @author jry ijry@qq.com
514
+ */
515
+ getRpxRatio() {
516
+ const PROBE = 1000; // 不能用 750,750 是 upx2px 的特例值
517
+ const ratio = rpx2px(PROBE) / PROBE;
518
+ return ratio > 0 ? ratio : 1;
519
+ },
487
520
 
488
521
  /**
489
522
  * 绘制渐变背景
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙跨端移动组件库,组件丰富维护更新稳定。",
5
- "version": "3.8.106",
5
+ "version": "3.8.108",
6
6
  "description": "零云®uview-plus已兼容vue3支持多语言,120+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
7
7
  "keywords": [
8
8
  "uview",
@@ -11,7 +11,7 @@
11
11
  "echarts",
12
12
  "ui",
13
13
  "可视化"
14
- ],
14
+ ],
15
15
  "main": "index.js",
16
16
  "repository": "https://github.com/ijry/uview-plus",
17
17
  "engines": {