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
@@ -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,86 @@ 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 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);
373
+ const measureTextWidth = async (value) => {
374
+ if (measurementCache.has(value)) {
375
+ return measurementCache.get(value);
376
+ }
377
+ let metrics;
378
+ if (ctx && typeof ctx.measureTextAsync === 'function') {
379
+ metrics = await ctx.measureTextAsync(value);
380
+ } else {
381
+ metrics = ctx.measureText(value);
382
+ }
383
+ let measuredWidth = Number(metrics && metrics.width) || 0;
384
+ // 测量不可用时(鸿蒙同步 measureText 恒返回 0)必须改用估算:
385
+ // 0 当作真实宽度会让下面的 <= maxWidth 恒成立,整段文本挤成一行。
386
+ if (!(measuredWidth > 0) && value) {
387
+ measuredWidth = estimateWidth(value);
388
+ }
389
+ measurementCache.set(value, measuredWidth);
390
+ return measuredWidth;
391
+ };
392
+ const findTextFitLength = async (values, suffix = '') => {
393
+ let low = 0;
394
+ let high = values.length;
395
+ while (low < high) {
396
+ const middle = Math.ceil((low + high) / 2);
397
+ const candidate = values.slice(0, middle).join('') + suffix;
398
+ if (await measureTextWidth(candidate) <= maxWidth) {
399
+ low = middle;
400
+ } else {
401
+ high = middle - 1;
402
+ }
403
+ }
404
+ return low;
405
+ };
406
+
407
+ let offset = 0;
408
+ while (offset < characters.length && lines.length < lineClamp) {
409
+ const remaining = characters.slice(offset);
410
+ const remainingText = remaining.join('');
411
+ if (await measureTextWidth(remainingText) <= maxWidth) {
412
+ lines.push(remainingText);
413
+ break;
414
+ }
415
+
416
+ const isLastLine = lines.length === lineClamp - 1;
417
+ if (isLastLine) {
418
+ let suffix = ellipsis;
419
+ while (suffix && await measureTextWidth(suffix) > maxWidth) {
420
+ suffix = suffix.slice(0, -1);
421
+ }
422
+ const fitLength = await findTextFitLength(remaining, suffix);
423
+ lines.push(remaining.slice(0, fitLength).join('') + suffix);
424
+ break;
425
+ }
426
+
427
+ const fitLength = Math.max(1, await findTextFitLength(remaining));
428
+ lines.push(remaining.slice(0, fitLength).join(''));
429
+ offset += fitLength;
430
+ }
431
+
432
+ const textBaseLine = css.fontSize ? this.convertRpxToPx(css.fontSize) / 2 : 10;
433
+ for (let i = 0; i < lines.length; i++) {
434
+ ctx.fillText(lines[i], x, y + (i * lineHeight) + textBaseLine);
435
+ }
417
436
  },
418
437
 
419
438
  /**
@@ -425,54 +444,41 @@ export default {
425
444
  * @returns {Promise<String>} 二维码图片URL
426
445
  * @author jry ijry@qq.com
427
446
  */
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;
447
+ async generateQRCode(text, width, height) {
448
+ const qrCodeKey = `${text}_${width}_${height}`;
449
+ if (this.qrCodeMap.has(qrCodeKey)) {
450
+ return this.qrCodeMap.get(qrCodeKey);
451
+ }
452
+
453
+ this.qrCodeValue = text;
454
+ this.qrCodeSize = Math.max(width, height);
455
+ this.qrCodeShow = true;
456
+
457
+ try {
458
+ await this.$nextTick();
459
+ const qrCode = this.$refs.qrCode;
460
+ if (!qrCode) {
461
+ throw new Error('无法获取二维码组件实例');
437
462
  }
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;
463
+ if (typeof qrCode.refreshCanvas === 'function') {
464
+ await qrCode.refreshCanvas();
474
465
  }
475
- });
466
+ const generatedPath = await qrCode._makeCode();
467
+ let tempFilePath = typeof generatedPath === 'string' ? generatedPath : '';
468
+ if (!tempFilePath) {
469
+ const res = await qrCode.toTempFilePath();
470
+ tempFilePath = res && (res.tempFilePath || res.apFilePath);
471
+ }
472
+ if (!tempFilePath) {
473
+ throw new Error('二维码组件未返回图片路径');
474
+ }
475
+ this.qrCodeMap.set(qrCodeKey, tempFilePath);
476
+ return tempFilePath;
477
+ } catch (error) {
478
+ throw new Error(`二维码生成失败: ${error && error.message ? error.message : error}`);
479
+ } finally {
480
+ this.qrCodeShow = false;
481
+ }
476
482
  },
477
483
 
478
484
  /**
@@ -484,15 +490,33 @@ export default {
484
490
  */
485
491
  convertRpxToPx(rpxValue) {
486
492
  if (typeof rpxValue === 'number') return rpxValue;
487
-
493
+
488
494
  // 使用rpx2px方法
489
495
  if (typeof rpxValue === 'string' && rpxValue.endsWith('rpx')) {
490
496
  const value = parseFloat(rpxValue);
491
- return rpx2px(value);
497
+ return value * this.getRpxRatio();
492
498
  }
493
-
499
+
494
500
  return parseFloat(rpxValue) || 0;
495
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
+ },
496
520
 
497
521
  /**
498
522
  * 绘制渐变背景
@@ -625,6 +649,31 @@ export default {
625
649
  overflow: hidden;
626
650
  }
627
651
 
652
+ /* #ifdef APP-NVUE */
653
+ &__renderer {
654
+ position: fixed;
655
+ top: -1px;
656
+ left: -1px;
657
+ width: 1px;
658
+ height: 1px;
659
+ overflow: hidden;
660
+ z-index: -1;
661
+ opacity: 0.01;
662
+ pointer-events: none;
663
+ }
664
+ /* #endif */
665
+
666
+ /* #ifndef APP-NVUE */
667
+ &__renderer {
668
+ position: fixed;
669
+ top: -10000px;
670
+ left: -10000px;
671
+ width: 1px;
672
+ height: 1px;
673
+ overflow: hidden;
674
+ z-index: -1;
675
+ }
676
+
628
677
  &__hidden-canvas {
629
678
  position: fixed;
630
679
  top: -10000px;
@@ -642,5 +691,6 @@ export default {
642
691
  display: none;
643
692
  }
644
693
  }
694
+ /* #endif */
645
695
  }
646
696
  </style>