Files
chat/frontend/src/utils/shareCard.ts
T
2026-06-14 10:01:47 +08:00

141 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 生成好友之树分享图(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')
}