From 10d45b1c3b353fbcf4977712a39d0f07d064611e Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Sat, 8 Aug 2026 12:02:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(diy-recommend-code):=20generateSharePoster?= =?UTF-8?q?=20=E7=AD=89=E6=96=B9=E6=B3=95=E7=A7=BB=E5=88=B0=20methods=20?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: 用户日志显示 "TypeError: this.generateSharePoster is not a function"。 原因: generateSharePoster / _exportCanvas / _roundRect / _downloadImage 等方法之前被定义在 methods 块外的 export default 对象上(与 onShareAppMessage 这种生命周期钩子同级),Vue 不会自动绑定它们到 组件实例,导致 this.xxx 找不到。 修复: - 删除 methods 块外的那 5 个方法的旧定义 - 重新加到 methods 块内(放在 goLogin 后,methods 块关闭 } 之前) - onShareAppMessage / onShareTimeline 是小程序生命周期钩子, 必须留在 export default 对象上,不能进 methods(保留不动) 验证: - H5 测试 isH5Platform=true,提前 return 不进入 generateSharePoster,正常 - 小程序下 this.generateSharePoster 现在能找到方法 - loadCode 成功后 _prepareSharePoster 调用正常 --- components-diy/diy-recommend-code.vue | 283 +++++++++++++------------- 1 file changed, 142 insertions(+), 141 deletions(-) diff --git a/components-diy/diy-recommend-code.vue b/components-diy/diy-recommend-code.vue index 1c0eeda..7aac3a0 100644 --- a/components-diy/diy-recommend-code.vue +++ b/components-diy/diy-recommend-code.vue @@ -764,148 +764,8 @@ export default { }; }, /** - * 生成推荐码组件的分享海报 - * 画布内容:白底 + 标题 + 推荐码大字 + 二维码(如果有) + 简短提示 - * 返回 Promise + * 小程序分享到朋友圈(uniapp 生命周期钩子,必须在 export default 对象上,不能进 methods) */ - generateSharePoster() { - 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); - } - }); - }, - /** - * canvas 转图片(uni.canvasToTempFilePath Promise 化) - */ - _exportCanvas(W, H) { - return new Promise((resolve, reject) => { - 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); - }); - }, - /** - * 圆角矩形路径(canvas API) - */ - _roundRect(ctx, x, y, w, h, r) { - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - ctx.closePath(); - }, - /** - * 下载图片到临时路径(Promise 化) - * 支持本地路径直接返回 - */ - _downloadImage(url) { - return new Promise((resolve, reject) => { - if (!url) return reject(new Error('url 为空')); - // 已是临时/本地路径 - if (!/^https?:\/\//i.test(url)) return resolve(url); - uni.downloadFile({ - url, - success: (res) => { - if (res.statusCode === 200 && res.tempFilePath) resolve(res.tempFilePath); - else reject(new Error('下载失败:' + res.statusCode)); - }, - fail: reject - }); - }); - }, onShareTimeline() { const v = this.nc; // 从H5分享链接中提取完整查询参数,与分享给好友保持一致 @@ -1418,6 +1278,147 @@ onShareClick() { } }); } + }, + + /** + * 生成推荐码组件的分享海报(canvas 绘制 → 临时文件路径) + * 画布内容:白底 + 标题 + 推荐码大字 + 二维码(如有) + 简短提示 + * 返回 Promise + */ + generateSharePoster() { + 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); + } + }); + }, + /** + * canvas 转图片(uni.canvasToTempFilePath Promise 化) + */ + _exportCanvas(W, H) { + return new Promise((resolve, reject) => { + console.log('[推荐码分享] _exportCanvas 开始执行'); + 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); + }); + }, + /** + * 圆角矩形路径(canvas API) + */ + _roundRect(ctx, x, y, w, h, r) { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.arcTo(x + w, y, x + w, y + h, r); + ctx.arcTo(x + w, y + h, x, y + h, r); + ctx.arcTo(x, y + h, x, y, r); + ctx.arcTo(x, y, x + w, y, r); + ctx.closePath(); + }, + /** + * 下载图片到临时路径(Promise 化) + * 支持本地路径直接返回 + */ + _downloadImage(url) { + return new Promise((resolve, reject) => { + if (!url) return reject(new Error('url 为空')); + if (!/^https?:\/\//i.test(url)) return resolve(url); + uni.downloadFile({ + url, + success: (res) => { + if (res.statusCode === 200 && res.tempFilePath) resolve(res.tempFilePath); + else reject(new Error('下载失败:' + res.statusCode)); + }, + fail: reject + }); + }); } } };