/** * 生成好友之树分享图(Canvas 绘制,可下载发到微信朋友圈) */ export interface ShareTreeData { friendName: string myName: string stageIndex: number stageName: string stageEmoji: string messageCount: number waterCount: number totalScore: number hue: number } function hsl(h: number, s: number, l: number): string { return `hsl(${h}, ${s}%, ${l}%)` } /** 把树画到 canvas 上 */ function drawTree(ctx: CanvasRenderingContext2D, cx: number, baseY: number, stageIndex: number, hue: number) { if (stageIndex === 0) { // 种子 ctx.fillStyle = hsl(25, 45, 40) ctx.beginPath() ctx.ellipse(cx, baseY, 14, 10, 0, 0, Math.PI * 2) ctx.fill() return } const trunkW = 4 + stageIndex * 2.2 const canopyR = 22 + stageIndex * 9 const canopyN = 3 + stageIndex // 树干 ctx.fillStyle = hsl(25, 45, 35 + stageIndex * 2) ctx.fillRect(cx - trunkW / 2, baseY - 90, trunkW, 90) // 树冠(多个圆叠加) ctx.fillStyle = hsl(hue, 55, 42) ctx.beginPath() ctx.arc(cx, baseY - 100, canopyR, 0, Math.PI * 2) ctx.fill() for (let i = 1; i < canopyN; i++) { const offset = i % 2 === 1 ? 1 : -1 const layer = Math.ceil(i / 2) ctx.fillStyle = hsl(hue, 55, 42 + (i % 2) * 6) ctx.beginPath() ctx.arc( cx + offset * canopyR * 0.6 * layer, baseY - 100 + layer * canopyR * 0.3, canopyR * (0.75 - layer * 0.1), 0, Math.PI * 2 ) ctx.fill() } // 高光 ctx.fillStyle = hsl(hue, 60, 60) ctx.globalAlpha = 0.3 ctx.beginPath() ctx.arc(cx - canopyR * 0.3, baseY - 100 - canopyR * 0.3, canopyR * 0.3, 0, Math.PI * 2) ctx.fill() ctx.globalAlpha = 1 } /** 生成完整分享卡片,返回 dataURL */ export function generateTreeShareCard(data: ShareTreeData): string { const W = 600, H = 800 const canvas = document.createElement('canvas') canvas.width = W canvas.height = H const ctx = canvas.getContext('2d')! // 背景渐变 const bg = ctx.createLinearGradient(0, 0, 0, H) bg.addColorStop(0, '#E8F5E9') bg.addColorStop(0.5, '#C8E6C9') bg.addColorStop(1, '#A5D6A7') ctx.fillStyle = bg ctx.fillRect(0, 0, W, H) // 顶部光晕 const glow = ctx.createRadialGradient(W / 2, 200, 0, W / 2, 200, 300) glow.addColorStop(0, 'rgba(255,255,255,0.5)') glow.addColorStop(1, 'rgba(255,255,255,0)') ctx.fillStyle = glow ctx.fillRect(0, 0, W, 400) // logo + 标题 ctx.fillStyle = '#004D40' ctx.font = 'bold 28px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif' ctx.textAlign = 'center' ctx.fillText('🌿 我们在青叶种了一棵树', W / 2, 70) // 名字 ctx.font = '20px -apple-system, "PingFang SC", sans-serif' ctx.fillStyle = '#00695C' ctx.fillText(`${data.myName} × ${data.friendName}`, W / 2, 105) // 地面 ctx.fillStyle = 'rgba(139,90,43,0.15)' ctx.beginPath() ctx.ellipse(W / 2, 560, 140, 16, 0, 0, Math.PI * 2) ctx.fill() // 画树 drawTree(ctx, W / 2, 560, data.stageIndex, data.hue) // 阶段标签 ctx.font = 'bold 22px sans-serif' ctx.fillStyle = '#00796B' ctx.fillText(`${data.stageEmoji} ${data.stageName} Lv.${data.stageIndex + 1}`, W / 2, 620) // 统计三栏 const stats = [ { label: '条消息', value: data.messageCount }, { label: '次浇水', value: data.waterCount }, { label: '成长值', value: data.totalScore }, ] const statY = 670 stats.forEach((s, i) => { const x = W / 4 + (W / 4) * i - 30 ctx.fillStyle = '#009688' ctx.font = 'bold 26px sans-serif' ctx.fillText(String(s.value), x, statY) ctx.fillStyle = '#5F7A74' ctx.font = '13px sans-serif' ctx.fillText(s.label, x, statY + 22) }) // 底部邀请 ctx.fillStyle = '#004D40' ctx.font = '15px sans-serif' ctx.fillText('来青叶,和你在意的人一起种树', W / 2, 745) ctx.font = '12px sans-serif' ctx.fillStyle = '#5F7A74' ctx.fillText('微信给不了你的,是一片会生长的关系', W / 2, 770) return canvas.toDataURL('image/png') }