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.
- package/changelog.md +173 -3
- package/components/u-action-sheet/u-action-sheet.vue +21 -2
- package/components/u-barcode/u-barcode.vue +0 -2
- package/components/u-button/u-button.vue +2 -5
- package/components/u-canvas/u-canvas.vue +430 -77
- package/components/u-cell/u-cell.vue +8 -0
- package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
- package/components/u-dragsort/u-dragsort.vue +55 -31
- package/components/u-icon/u-icon.vue +12 -3
- package/components/u-icon/util.js +81 -10
- package/components/u-index-item/u-index-item.vue +1 -1
- package/components/u-index-list/u-index-list.vue +1 -1
- package/components/u-novel-reader/content-normalizer.js +80 -0
- package/components/u-novel-reader/layout-engine.js +225 -0
- package/components/u-novel-reader/measure-adapter.js +69 -0
- package/components/u-novel-reader/novelReader.js +35 -0
- package/components/u-novel-reader/persistence.js +144 -0
- package/components/u-novel-reader/props.js +100 -0
- package/components/u-novel-reader/reader-catalog.vue +234 -0
- package/components/u-novel-reader/reader-content.vue +226 -0
- package/components/u-novel-reader/reader-core.js +151 -0
- package/components/u-novel-reader/reader-settings.vue +325 -0
- package/components/u-novel-reader/reader-toolbar.vue +313 -0
- package/components/u-novel-reader/theme-vars.scss +49 -0
- package/components/u-novel-reader/u-novel-reader.vue +836 -0
- package/components/u-poster/u-poster.vue +272 -222
- package/components/u-qrcode/qrcode.js +56 -49
- package/components/u-qrcode/u-qrcode.vue +159 -88
- package/components/u-scroll-list/scrollWxs.wxs +1 -1
- package/components/u-slider/u-slider.vue +1 -1
- package/components/u-swipe-action-item/index.wxs +43 -25
- package/components/u-swipe-action-item/nvue.js +9 -0
- package/components/u-swipe-action-item/other.js +32 -21
- package/components/u-swipe-action-item/props.js +5 -0
- package/components/u-swipe-action-item/swipeActionItem.js +1 -0
- package/components/u-swipe-action-item/u-swipe-action-item.vue +28 -4
- package/components/u-switch/u-switch.vue +1 -1
- package/components/u-tabbar/u-tabbar.vue +2 -1
- package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
- package/components/u-tabs/u-tabs.vue +11 -8
- package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
- package/components/u-text/text.js +1 -1
- package/components/u-text/u-text.vue +18 -6
- package/components/u-upload/u-upload.vue +1 -1
- package/components/u-waterfall/u-waterfall.vue +118 -65
- package/libs/config/config.js +1 -1
- package/libs/config/props.js +1 -0
- package/libs/function/index.js +66 -0
- package/libs/mixin/mixin.js +4 -50
- package/libs/root/index.js +78 -5
- package/libs/root/page.js +7 -7
- package/libs/root/root.js +73 -3
- package/libs/util/app-nvue-webview-canvas.js +486 -0
- package/libs/util/gcanvas/bridge/bridge-weex.js +0 -2
- package/libs/util/gcanvas/index.js +3 -3
- package/package.json +2 -2
- package/types/comps/swipeActionItem.d.ts +13 -0
- package/types/comps/tabs.d.ts +2 -1
- package/types/comps/text.d.ts +1 -1
- package/types/func.d.ts +23 -0
- 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-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
119
|
+
ctx.fillRect(0, 0, width, height);
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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
|
|
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>
|