const api = require('../../utils/api') const app = getApp() Page({ data: { statusBarHeight: 44, navBarHeight: 44, totalNavHeight: 88, posters: [], currentPosterIndex: 0, qrCodeUrl: '', referralCode: '', canvasWidth: 1080, canvasHeight: 1920, isLoading: true, userInfo: null }, onLoad(options) { const systemInfo = wx.getSystemInfoSync() const statusBarHeight = systemInfo.statusBarHeight || 44 const menuButton = wx.getMenuButtonBoundingClientRect() const navBarHeight = menuButton.height + (menuButton.top - statusBarHeight) * 2 const totalNavHeight = statusBarHeight + navBarHeight this.setData({ statusBarHeight, navBarHeight, totalNavHeight, userInfo: app.globalData.userInfo || wx.getStorageSync('user_info') }) this.loadData(); }, async loadData() { try { // 1. 获取推荐码 const statsRes = await api.commission.getStats(); let referralCode = 'default'; if (statsRes.success && statsRes.data) { referralCode = statsRes.data.referralCode; this.setData({ referralCode }); } // 2. 设置小程序二维码地址 // scene 格式必须为 r=XXX 才能被 app.js 正确解析 const baseUrl = app.globalData.baseUrl || 'https://ai-c.maimanji.com'; const qrCodeUrl = `${baseUrl}/api/user/qrcode?scene=r=${referralCode}&page=pages/index/index`; this.setData({ qrCodeUrl }); // 3. 获取动态海报背景列表 const assetRes = await api.pageAssets.getAssets('posters'); if (assetRes && assetRes.success && assetRes.data && assetRes.data.length > 0) { const posters = assetRes.data.map(item => { let url = (item.asset_url || '').trim(); // 如果是相对路径,补充完整域名 if (url && !url.startsWith('http')) { url = baseUrl + (url.startsWith('/') ? '' : '/') + url; } return { id: item.asset_key, url: url, qrBottom: 4.5, // 对应 1920 高度下的位置 qrRight: 8 // 对应 1080 宽度下的位置 }; }); this.setData({ posters, isLoading: false }); } else { // 兜底默认海报 this.setData({ posters: [ { id: 'default', url: 'https://ai-c.maimanji.com/uploads/assets/poster-1.png', qrBottom: 4.5, qrRight: 8 } ], isLoading: false }); } } catch (err) { console.error('[promote-poster] loadData failed:', err); this.setData({ isLoading: false }); } }, onPosterChange(e) { this.setData({ currentPosterIndex: e.detail.current }) }, onImageError(e) { console.error('[promote-poster] 海报图加载失败:', e.detail.errMsg); wx.showToast({ title: '海报背景加载失败', icon: 'none' }); }, onQrError(e) { console.error('[promote-poster] 二维码加载失败:', e.detail.errMsg); wx.showToast({ title: '二维码加载失败', icon: 'none' }); }, /** * 下载文件辅助函数 */ downloadFile(url) { return new Promise((resolve, reject) => { wx.downloadFile({ url, success: res => { if (res.statusCode === 200) resolve(res.tempFilePath); else reject(new Error('Download failed: ' + url)); }, fail: err => { console.error('Download failed:', url, err); reject(err); } }); }); }, async savePoster() { if (this.data.posters.length === 0) return; wx.showLoading({ title: '生成中...', mask: true }); try { const template = this.data.posters[this.data.currentPosterIndex]; // 1. 下载资源 const [bgPath, qrPath] = await Promise.all([ this.downloadFile(template.url), this.downloadFile(this.data.qrCodeUrl) ]); // 2. 初始化 Canvas const query = wx.createSelectorQuery() query.select('#posterCanvas') .fields({ node: true, size: true }) .exec(async (res) => { if (!res[0] || !res[0].node) { wx.hideLoading(); wx.showToast({ title: 'Canvas初始化失败', icon: 'none' }); return; } const canvas = res[0].node const ctx = canvas.getContext('2d') const dpr = wx.getSystemInfoSync().pixelRatio const canvasW = 1080; const canvasH = 1920; canvas.width = canvasW * dpr canvas.height = canvasH * dpr ctx.scale(dpr, dpr) // 3. 绘制背景 const bgImg = canvas.createImage(); bgImg.src = bgPath; await new Promise((resolve, reject) => { bgImg.onload = resolve; bgImg.onerror = reject; }); ctx.drawImage(bgImg, 0, 0, canvasW, canvasH); // 4. 绘制二维码 const qrImg = canvas.createImage(); qrImg.src = qrPath; await new Promise((resolve, reject) => { qrImg.onload = resolve; qrImg.onerror = reject; }); // 识别白框位置:根据 1080x1920 设计稿,白框在右下角 // 二维码尺寸 260x260 const qrW = 260; const qrX = 720; // 1080 - 100 - 260 const qrY = 1580; // 1920 - 80 - 260 // 绘制二维码背景(白框内可能需要微调,这里先绘制一个纯白背景确保清晰) ctx.fillStyle = '#FFFFFF'; ctx.beginPath(); this.roundRect(ctx, qrX - 5, qrY - 5, qrW + 10, qrW + 10, 10); ctx.fill(); ctx.drawImage(qrImg, qrX, qrY, qrW, qrW); // 6. 导出 setTimeout(() => { wx.canvasToTempFilePath({ canvas: canvas, success: (fileRes) => { wx.saveImageToPhotosAlbum({ filePath: fileRes.tempFilePath, success: () => { wx.hideLoading(); wx.showToast({ title: '已保存到相册', icon: 'success' }) }, fail: (err) => { wx.hideLoading(); if (err.errMsg.indexOf('auth deny') !== -1) { wx.showModal({ title: '提示', content: '请授权保存图片到相册', success: (sm) => { if (sm.confirm) wx.openSetting(); } }); } else { wx.showToast({ title: '保存失败', icon: 'none' }) } } }) }, fail: (err) => { console.error('canvasToTempFilePath fail:', err); wx.hideLoading(); wx.showToast({ title: '生成图片失败', icon: 'none' }); } }) }, 300); }) } catch (err) { console.error('[promote-poster] savePoster error:', err); wx.hideLoading(); wx.showToast({ title: '海报资源加载失败', icon: 'none' }); } }, roundRect(ctx, x, y, w, h, r) { 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); }, onShareAppMessage() { const title = "发现一个超赞的AI情感陪伴官,快来看看吧!"; const imageUrl = this.data.posters[this.data.currentPosterIndex]?.url || ''; return { title: title, path: `/pages/index/index?referralCode=${this.data.referralCode}`, imageUrl: imageUrl } }, goBack() { wx.navigateBack(); } });