fix(diy-recommend-code): canvas 海报生成加详细调试日志 + ctx.draw 包裹

问题: 小程序下 generateSharePoster "开始调" 后无 "resolved",
卡在 canvas 绘制或导出步骤。

修复 + 调试:

1. generateSharePoster 流程加详细日志:
   - 进入
   - 准备 createCanvasContext
   - createCanvasContext 返回值
   - qrcodeRenderUrl 当前值
   - drawQrcodeAndExport 进入
   - ctx.draw(false, cb) 准备
   - ctx.draw callback 触发

2. 关键修复: ctx.draw(false, callback) 包裹导出
   - 旧代码:画完 fill() 后直接调 _exportCanvas(canvas 还是空的)
   - 新代码:画完后 ctx.draw(false, () => _exportCanvas())
     在 callback 里导出真正渲染完的 canvas

3. _exportCanvas 简化:
   - 不再走 createSelectorQuery(可能挂起)
   - 直接用 canvasId 调 canvasToTempFilePath
   - 加 5s 超时兜底,避免永远卡住

4. _prepareSharePoster 加 isH5Platform 短路:
   - H5 下 canvas 模板不渲染,createCanvasContext 会失败
   - 短路后不调用 generateSharePoster,避免无效报错

请在微信开发者工具里再次测试,看哪一行日志卡住
(预期: 进入 → createCanvasContext 返回 → qrcodeRenderUrl →
drawQrcodeAndExport → ctx.draw 准备 → ctx.draw callback → _exportCanvas → 成功)
,把卡住那一行的日志贴出来。
This commit is contained in:
2026-08-08 11:52:57 +08:00
parent ef8c7568ac
commit 60dc054383

View File

@@ -769,130 +769,110 @@ export default {
* 返回 Promise<tempFilePath>
*/
generateSharePoster() {
const v = this.nc;
const code = this.code || v.placeholderCode || 'ABCD1234';
const title = v.title || '我的推荐码';
const W = 375;
const H = 480;
return new Promise((resolve, reject) => {
try {
const ctx = uni.createCanvasContext('sharePosterCanvas', this);
if (!ctx) return reject(new Error('createCanvasContext 失败'));
// 1. 白底
ctx.setFillStyle('#FFFFFF');
ctx.fillRect(0, 0, W, H);
// 2. 顶部标题
ctx.setFontSize(18);
ctx.setFillStyle('#1A1A1A');
ctx.setTextAlign('left');
ctx.fillText(title, 24, 44);
// 3. 提示文字
ctx.setFontSize(12);
ctx.setFillStyle('#999999');
ctx.fillText('分享给好友,邀请入驻获得奖励', 24, 70);
// 4. 米黄背景区
ctx.setFillStyle('#FFF8F0');
const blockY = 90, blockH = 120, blockX = 24, blockW = W - 48;
this._roundRect(ctx, blockX, blockY, blockW, blockH, 12);
ctx.fill();
// 5. 推荐码(居中)
ctx.setFontSize(36);
ctx.setFillStyle(v.codeColor || '#DC143C');
ctx.setTextAlign('center');
ctx.setFontWeight('bold');
ctx.fillText(code, W / 2, blockY + blockH / 2 + 12);
// 6. 二维码(如果有 url)
const qr = this.qrcodeRenderUrl;
if (qr) {
// 先下载到本地临时路径
this._downloadImage(qr).then((localPath) => {
// 二维码绘制在中部下方,正方形 180x180
const qrSize = 180;
const qrX = (W - qrSize) / 2;
const qrY = blockY + blockH + 24;
// 白色边框区
ctx.setFillStyle('#FFFFFF');
this._roundRect(ctx, qrX - 6, qrY - 6, qrSize + 12, qrSize + 12, 12);
ctx.fill();
ctx.drawImage(localPath, qrX, qrY, qrSize, qrSize);
// 7. 底部提示
ctx.setFontSize(11);
ctx.setFillStyle('#999999');
ctx.setTextAlign('center');
ctx.fillText(v.posterQrTip || '扫码进入并填写入驻信息', W / 2, qrY + qrSize + 24);
this._exportCanvas(W, H).then(resolve).catch(reject);
}).catch(() => {
// 二维码加载失败,直接导出无二维码版
this._exportCanvas(W, H).then(resolve).catch(reject);
});
} else {
this._exportCanvas(W, H).then(resolve).catch(reject);
const v = this.nc;
const code = this.code || v.placeholderCode || 'ABCD1234';
const title = v.title || '我的推荐码';
const W = 375;
const H = 480;
console.log('[推荐码分享] generateSharePoster 进入');
return new Promise((resolve, reject) => {
try {
console.log('[推荐码分享] 准备 createCanvasContext');
const ctx = uni.createCanvasContext('sharePosterCanvas', this);
console.log('[推荐码分享] createCanvasContext 返回:', !!ctx);
if (!ctx) {
console.error('[推荐码分享] createCanvasContext 失败');
return reject(new Error('createCanvasContext 失败'));
}
// 1. 白底
ctx.setFillStyle('#FFFFFF');
ctx.fillRect(0, 0, W, H);
// 2. 顶部标题
ctx.setFontSize(18);
ctx.setFillStyle('#1A1A1A');
ctx.setTextAlign('left');
ctx.fillText(title, 24, 44);
// 3. 提示文字
ctx.setFontSize(12);
ctx.setFillStyle('#999999');
ctx.fillText('分享给好友,邀请入驻获得奖励', 24, 70);
// 4. 米黄背景区
ctx.setFillStyle('#FFF8F0');
const blockY = 90, blockH = 120, blockX = 24, blockW = W - 48;
this._roundRect(ctx, blockX, blockY, blockW, blockH, 12);
ctx.fill();
// 5. 推荐码(居中)
ctx.setFontSize(36);
ctx.setFillStyle(v.codeColor || '#DC143C');
ctx.setTextAlign('center');
ctx.setFontWeight('bold');
ctx.fillText(code, W / 2, blockY + blockH / 2 + 12);
// 6. 二维码(如果有 url)
const qr = this.qrcodeRenderUrl;
console.log('[推荐码分享] qrcodeRenderUrl=', qr);
const drawQrcodeAndExport = (qrLocalPath) => {
console.log('[推荐码分享] drawQrcodeAndExport 进入, qrLocalPath=', qrLocalPath);
if (qrLocalPath) {
const qrSize = 180;
const qrX = (W - qrSize) / 2;
const qrY = blockY + blockH + 24;
ctx.setFillStyle('#FFFFFF');
this._roundRect(ctx, qrX - 6, qrY - 6, qrSize + 12, qrSize + 12, 12);
ctx.fill();
ctx.drawImage(qrLocalPath, qrX, qrY, qrSize, qrSize);
ctx.setFontSize(11);
ctx.setFillStyle('#999999');
ctx.setTextAlign('center');
ctx.fillText(v.posterQrTip || '扫码进入并填写入驻信息', W / 2, qrY + qrSize + 24);
}
console.log('[推荐码分享] 准备 ctx.draw(false, cb)');
// 关键: ctx.draw(false, cb) 是异步绘制,必须在 cb 里导出
ctx.draw(false, () => {
console.log('[推荐码分享] ctx.draw callback 触发,准备 _exportCanvas');
this._exportCanvas(W, H).then(resolve).catch(reject);
});
};
if (qr) {
this._downloadImage(qr).then(drawQrcodeAndExport).catch(() => drawQrcodeAndExport(null));
} else {
drawQrcodeAndExport(null);
}
} catch (e) {
console.error('[推荐码分享] generateSharePoster try 块异常:', e);
reject(e);
}
} catch (e) {
reject(e);
}
});
},
});
},
/**
* canvas 转图片(uni.canvasToTempFilePath Promise 化)
*/
_exportCanvas(W, H) {
return new Promise((resolve, reject) => {
// 微信小程序: type=2d canvas,需要传入 canvas 实例
// 旧版非 type=2d: 用 canvasId 字符串
// 这里两种方式都尝试
const query = uni.createSelectorQuery().in(this);
query.select('#sharePosterCanvas')
.fields({ node: true, size: true })
.exec((res) => {
if (res && res[0] && res[0].node) {
// 新版 type=2d canvas
uni.canvasToTempFilePath({
canvas: res[0].node,
width: W,
height: H,
destWidth: W * 2,
destHeight: H * 2,
fileType: 'png',
quality: 1,
success: (r) => resolve(r.tempFilePath),
fail: (err) => {
// 退化到旧 API
uni.canvasToTempFilePath({
canvasId: 'sharePosterCanvas',
width: W,
height: H,
destWidth: W * 2,
destHeight: H * 2,
fileType: 'png',
quality: 1,
success: (r2) => resolve(r2.tempFilePath),
fail: (err2) => reject(err2 || err)
}, this);
}
}, this);
} else {
// 旧版 canvas:ctx.draw(false, ...) 后导出
const ctx = uni.createCanvasContext('sharePosterCanvas', this);
if (ctx && ctx.draw) {
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'sharePosterCanvas',
width: W,
height: H,
destWidth: W * 2,
destHeight: H * 2,
fileType: 'png',
quality: 1,
success: (r) => resolve(r.tempFilePath),
fail: (err) => reject(err)
}, this);
});
} else {
reject(new Error('canvas 初始化失败'));
}
}
});
console.log('[推荐码分享] _exportCanvas 开始执行');
// 直接用 canvasId 导出(不需要 selectorQuery,更可靠)
// 微信小程序会自动找到对应 canvas
uni.canvasToTempFilePath({
canvasId: 'sharePosterCanvas',
width: W,
height: H,
destWidth: W * 2,
destHeight: H * 2,
fileType: 'png',
quality: 1,
success: (r) => {
console.log('[推荐码分享] canvasToTempFilePath 成功:', r.tempFilePath);
resolve(r.tempFilePath);
},
fail: (err) => {
console.error('[推荐码分享] canvasToTempFilePath 失败:', err);
reject(err);
}
}, this);
// 兜底超时:5s 后还没返回就 reject,避免永远卡住
setTimeout(() => {
console.warn('[推荐码分享] canvasToTempFilePath 超时');
reject(new Error('canvasToTempFilePath 超时'));
}, 5000);
});
},
/**
@@ -1238,6 +1218,11 @@ export default {
console.warn('[推荐码分享] 跳过预生成:推荐码为空');
return;
}
// 安全:isH5Platform 为 true 时不进入(H5 下 canvas 不渲染,createCanvasContext 会失败)
if (this.isH5Platform) {
console.log('[推荐码分享] H5 环境跳过海报预生成');
return;
}
try {
console.log('[推荐码分享] 开始调 generateSharePoster...');
this.generateSharePoster().then(function(posterPath) {