feat(diy-recommend-code): 海报颜色布局改为读取 diyData 动态适配

之前 generateSharePoster 颜色和布局都是 hardcode(米黄区、
黄橙渐变、FFC107 等)。后端改 stylePreset 后分享图不会跟随变化。

修改: 抽出 palette 对象,所有视觉参数都从 diyData.value 读:
- bgColor / bgColorStart / bgColorEnd / borderColor
- borderRadius / padding
- titleColor / titleFontSize
- codeColor / codeFontSize
- accentColor / textColor

新增方法:
- _drawPosterBg(ctx, W, H, palette)
  优先用 createLinearGradient 画 bgColorStart→bgColorEnd 渐变,
  旧 canvas 不支持则 fallback 到 bgColor 单色
- _drawPosterCard(ctx, W, H, palette, stylePreset)
  按 stylePreset 区分装饰:minimal/card/horizontal 右上角加 🎁 emoji
  gradient 不加装饰

后续如需更多差异,只在这两个方法里扩展即可,不影响主流程。
This commit is contained in:
2026-08-08 13:58:55 +08:00
parent 077fac40c2
commit 30b66ad7b3

View File

@@ -1289,66 +1289,88 @@ onShareClick() {
const v = this.nc; const v = this.nc;
const code = this.code || v.placeholderCode || 'ABCD1234'; const code = this.code || v.placeholderCode || 'ABCD1234';
const title = v.title || '我的推荐码'; const title = v.title || '我的推荐码';
const sp = this.stylePreset;
// ★ 所有视觉参数都从 diyData.value 读取,后端可改
const palette = {
bg: v.bgColor || '#FFFFFF',
bgStart: v.bgColorStart, // 渐变起始(可选)
bgEnd: v.bgColorEnd, // 渐变结束(可选)
border: v.borderColor || '#E5E7EB',
borderRadius: Number(v.borderRadius) || 12,
padding: Number(v.padding) || 16,
titleColor: v.titleColor || '#1A1A1A',
titleFontSize: Number(v.titleFontSize) || 18,
codeColor: v.codeColor || '#DC143C',
codeFontSize: Number(v.codeFontSize) || 36,
accent: v.accentColor || '#FFD700',
text: v.textColor || '#303133',
subtitle: '#999999',
codeBlockBg: '#FFF8F0', // 推荐码区背景(米黄)
codeBlockBorder: '#FFB74D'
};
const W = 375; const W = 375;
const H = 480; const H = 480;
console.log('[推荐码分享] generateSharePoster 进入'); console.log('[推荐码分享] generateSharePoster 进入,stylePreset=', sp);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
console.log('[推荐码分享] 准备 createCanvasContext');
const ctx = uni.createCanvasContext('sharePosterCanvas', this); const ctx = uni.createCanvasContext('sharePosterCanvas', this);
console.log('[推荐码分享] createCanvasContext 返回:', !!ctx); if (!ctx) return reject(new Error('createCanvasContext 失败'));
if (!ctx) { console.log('[推荐码分享] createCanvasContext 返回');
console.error('[推荐码分享] createCanvasContext 失败'); // ====================== 绘制海报 ======================
return reject(new Error('createCanvasContext 失败')); // 1. 整张背景(根据后端 bgColorStart/End 渐变,否则用 bgColor 单色)
} this._drawPosterBg(ctx, W, H, palette);
// 1. 白底 // 2. 卡片(根据 stylePreset 选择布局)
ctx.setFillStyle('#FFFFFF'); this._drawPosterCard(ctx, W, H, palette, sp);
ctx.fillRect(0, 0, W, H); // 3. 标题(读 titleColor/titleFontSize)
// 2. 顶部标题 ctx.setFontSize(palette.titleFontSize);
ctx.setFontSize(18); ctx.setFillStyle(palette.titleColor);
ctx.setFillStyle('#1A1A1A');
ctx.setTextAlign('left'); ctx.setTextAlign('left');
ctx.fillText(title, 24, 44); ctx.fillText(title, 24, 44);
// 3. 提示文字 // 4. 副标题
ctx.setFontSize(12); ctx.setFontSize(12);
ctx.setFillStyle('#999999'); ctx.setFillStyle(palette.subtitle);
ctx.fillText('分享给好友,邀请入驻获得奖励', 24, 70); ctx.fillText('分享给好友,邀请入驻获得奖励', 24, 70);
// 4. 米黄背景区 // 5. 推荐码区域(读 codeColor/codeFontSize/borderRadius)
ctx.setFillStyle('#FFF8F0');
const blockY = 90, blockH = 120, blockX = 24, blockW = W - 48; const blockY = 90, blockH = 120, blockX = 24, blockW = W - 48;
this._roundRect(ctx, blockX, blockY, blockW, blockH, 12); const codeBlockRadius = Math.min(palette.borderRadius, 16);
// 推荐码区背景
ctx.setFillStyle(palette.codeBlockBg);
this._roundRect(ctx, blockX, blockY, blockW, blockH, codeBlockRadius);
ctx.fill(); ctx.fill();
// 5. 推荐码(居中) // 推荐码区边框(浅橙)
ctx.setFontSize(36); ctx.setStrokeStyle(palette.codeBlockBorder);
ctx.setFillStyle(v.codeColor || '#DC143C'); ctx.setLineWidth(1);
this._roundRect(ctx, blockX, blockY, blockW, blockH, codeBlockRadius);
ctx.stroke();
// 推荐码大字(居中)
ctx.setFontSize(palette.codeFontSize);
ctx.setFillStyle(palette.codeColor);
ctx.setTextAlign('center'); ctx.setTextAlign('center');
// setFontWeight 是新版 canvas 2d API,旧版不支持;用 font 字符串兜底
if (typeof ctx.setFontWeight === 'function') { if (typeof ctx.setFontWeight === 'function') {
ctx.setFontWeight('bold'); ctx.setFontWeight('bold');
} else { } else {
try { ctx.font = 'bold 36px sans-serif'; } catch(e) {} try { ctx.font = 'bold ' + palette.codeFontSize + 'px sans-serif'; } catch(e) {}
} }
ctx.fillText(code, W / 2, blockY + blockH / 2 + 12); ctx.fillText(code, W / 2, blockY + blockH / 2 + 12);
// 6. 二维码(如果有 url) // 6. 二维码(如果有)
const qr = this.qrcodeRenderUrl; const qr = this.qrcodeRenderUrl;
console.log('[推荐码分享] qrcodeRenderUrl=', qr);
const drawQrcodeAndExport = (qrLocalPath) => { const drawQrcodeAndExport = (qrLocalPath) => {
console.log('[推荐码分享] drawQrcodeAndExport 进入, qrLocalPath=', qrLocalPath);
if (qrLocalPath) { if (qrLocalPath) {
const qrSize = 180; const qrSize = 180;
const qrX = (W - qrSize) / 2; const qrX = (W - qrSize) / 2;
const qrY = blockY + blockH + 24; const qrY = blockY + blockH + 24;
// 白底框
ctx.setFillStyle('#FFFFFF'); ctx.setFillStyle('#FFFFFF');
this._roundRect(ctx, qrX - 6, qrY - 6, qrSize + 12, qrSize + 12, 12); this._roundRect(ctx, qrX - 6, qrY - 6, qrSize + 12, qrSize + 12, codeBlockRadius);
ctx.fill(); ctx.fill();
ctx.drawImage(qrLocalPath, qrX, qrY, qrSize, qrSize); ctx.drawImage(qrLocalPath, qrX, qrY, qrSize, qrSize);
// 底部提示
ctx.setFontSize(11); ctx.setFontSize(11);
ctx.setFillStyle('#999999'); ctx.setFillStyle(palette.subtitle);
ctx.setTextAlign('center'); ctx.setTextAlign('center');
ctx.fillText(v.posterQrTip || '扫码进入并填写入驻信息', W / 2, qrY + qrSize + 24); ctx.fillText(v.posterQrTip || '扫码进入并填写入驻信息', W / 2, qrY + qrSize + 24);
} }
console.log('[推荐码分享] 准备 ctx.draw(false, cb)'); console.log('[推荐码分享] 准备 ctx.draw(false, cb)');
// 关键: ctx.draw(false, cb) 是异步绘制,必须在 cb 里导出
ctx.draw(false, () => { ctx.draw(false, () => {
console.log('[推荐码分享] ctx.draw callback 触发,准备 _exportCanvas'); console.log('[推荐码分享] ctx.draw callback 触发,准备 _exportCanvas');
this._exportCanvas(W, H).then(resolve).catch(reject); this._exportCanvas(W, H).then(resolve).catch(reject);
@@ -1365,6 +1387,56 @@ onShareClick() {
} }
}); });
}, },
/**
* 海报背景:根据 diyData.bgColorStart/End 画渐变,否则用 bgColor 单色
*/
_drawPosterBg(ctx, W, H, palette) {
try {
// 尝试线性渐变(可能旧 canvas 不支持,需 try/catch)
if (palette.bgStart && palette.bgEnd && typeof ctx.createLinearGradient === 'function') {
const grad = ctx.createLinearGradient(0, 0, W, H);
grad.addColorStop(0, palette.bgStart);
grad.addColorStop(1, palette.bgEnd);
ctx.setFillStyle(grad);
} else {
ctx.setFillStyle(palette.bg);
}
ctx.fillRect(0, 0, W, H);
} catch (e) {
ctx.setFillStyle(palette.bg);
ctx.fillRect(0, 0, W, H);
}
},
/**
* 卡片绘制:根据 stylePreset 选不同视觉布局
* (虽然现在 canvas 限制很多,但保留 hook 以便后续扩展)
*/
_drawPosterCard(ctx, W, H, palette, sp) {
// 海报本身就是"卡片",这里可以做不同预设的装饰差异
// 当前:所有预设统一用单色/渐变背景 + 米黄推荐码区
// 后续如需 minimal 加 🎁 装饰 emoji 等,在这里按 sp 区分
try {
if (sp === 'minimal') {
// minimal 风格:右上角加 🎁 emoji 装饰(模仿组件)
ctx.setFontSize(40);
ctx.setFillStyle('rgba(255,107,107,0.25)');
ctx.setTextAlign('right');
ctx.fillText('🎁', W - 24, 60);
} else if (sp === 'card') {
// card 风格:右上角加 🎁
ctx.setFontSize(36);
ctx.setFillStyle('rgba(218,165,32,0.35)');
ctx.setTextAlign('right');
ctx.fillText('🎁', W - 24, 60);
} else if (sp === 'horizontal') {
// horizontal 风格:右上角加 🎁
ctx.setFontSize(36);
ctx.setFillStyle('rgba(184,160,138,0.35)');
ctx.setTextAlign('right');
ctx.fillText('🎁', W - 24, 60);
}
} catch (e) {}
},
/** /**
* canvas 转图片(uni.canvasToTempFilePath Promise 化) * canvas 转图片(uni.canvasToTempFilePath Promise 化)
*/ */