131 lines
3.4 KiB
TypeScript
131 lines
3.4 KiB
TypeScript
export type BadgeRarity = 'common' | 'uncommon' | 'rare'
|
|
|
|
export interface CloudBadgeCardInput {
|
|
cloudName: string
|
|
cloudNameEn: string
|
|
unlockedAt: string
|
|
username: string
|
|
rarity: BadgeRarity
|
|
}
|
|
|
|
const rarityTheme = {
|
|
common: {
|
|
from: '#dbeafe',
|
|
to: '#f8fafc',
|
|
accent: '#0284c7',
|
|
shadow: 'rgba(2, 132, 199, 0.18)',
|
|
},
|
|
uncommon: {
|
|
from: '#fef3c7',
|
|
to: '#fff7ed',
|
|
accent: '#d97706',
|
|
shadow: 'rgba(217, 119, 6, 0.22)',
|
|
},
|
|
rare: {
|
|
from: '#fee2e2',
|
|
to: '#fff7ed',
|
|
accent: '#dc2626',
|
|
shadow: 'rgba(220, 38, 38, 0.22)',
|
|
},
|
|
} satisfies Record<BadgeRarity, { from: string; to: string; accent: string; shadow: string }>
|
|
|
|
function formatDate(iso: string) {
|
|
return new Date(iso).toLocaleDateString('zh-CN', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
})
|
|
}
|
|
|
|
function makeDownloadName(name: string) {
|
|
return `opencloud-badge-${name}.png`
|
|
}
|
|
|
|
export async function downloadCloudBadgeCard(input: CloudBadgeCardInput) {
|
|
const canvas = document.createElement('canvas')
|
|
canvas.width = 1200
|
|
canvas.height = 630
|
|
|
|
const ctx = canvas.getContext('2d')
|
|
if (!ctx) return
|
|
|
|
const theme = rarityTheme[input.rarity]
|
|
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
|
|
gradient.addColorStop(0, theme.from)
|
|
gradient.addColorStop(1, theme.to)
|
|
|
|
ctx.fillStyle = gradient
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
|
|
ctx.fillStyle = 'rgba(255,255,255,0.72)'
|
|
ctx.beginPath()
|
|
ctx.arc(1010, 120, 140, 0, Math.PI * 2)
|
|
ctx.fill()
|
|
|
|
ctx.fillStyle = 'rgba(255,255,255,0.45)'
|
|
ctx.beginPath()
|
|
ctx.arc(160, 520, 180, 0, Math.PI * 2)
|
|
ctx.fill()
|
|
|
|
ctx.fillStyle = '#0f172a'
|
|
ctx.font = '700 44px sans-serif'
|
|
ctx.fillText('OpenCloud 云朵图鉴', 72, 88)
|
|
|
|
ctx.fillStyle = 'rgba(15, 23, 42, 0.62)'
|
|
ctx.font = '400 24px sans-serif'
|
|
ctx.fillText('新徽章已解锁', 72, 126)
|
|
|
|
ctx.shadowColor = theme.shadow
|
|
ctx.shadowBlur = 28
|
|
ctx.fillStyle = '#ffffff'
|
|
ctx.beginPath()
|
|
ctx.roundRect(72, 170, 1056, 380, 32)
|
|
ctx.fill()
|
|
ctx.shadowBlur = 0
|
|
|
|
ctx.fillStyle = theme.accent
|
|
ctx.beginPath()
|
|
ctx.arc(210, 360, 92, 0, Math.PI * 2)
|
|
ctx.fill()
|
|
|
|
ctx.fillStyle = '#ffffff'
|
|
ctx.font = '700 82px sans-serif'
|
|
ctx.textAlign = 'center'
|
|
ctx.textBaseline = 'middle'
|
|
ctx.fillText(input.cloudName.slice(0, 1), 210, 360)
|
|
|
|
ctx.textAlign = 'left'
|
|
ctx.textBaseline = 'alphabetic'
|
|
ctx.fillStyle = '#0f172a'
|
|
ctx.font = '700 64px sans-serif'
|
|
ctx.fillText(input.cloudName, 340, 300)
|
|
|
|
ctx.fillStyle = 'rgba(15, 23, 42, 0.62)'
|
|
ctx.font = '400 30px sans-serif'
|
|
ctx.fillText(input.cloudNameEn, 340, 346)
|
|
|
|
ctx.fillStyle = theme.accent
|
|
ctx.beginPath()
|
|
ctx.roundRect(340, 382, 168, 48, 24)
|
|
ctx.fill()
|
|
|
|
ctx.fillStyle = '#ffffff'
|
|
ctx.font = '600 24px sans-serif'
|
|
ctx.fillText(input.rarity === 'common' ? '常见' : input.rarity === 'uncommon' ? '少见' : '罕见', 384, 414)
|
|
|
|
ctx.fillStyle = '#334155'
|
|
ctx.font = '400 28px sans-serif'
|
|
ctx.fillText(`解锁者:${input.username}`, 340, 476)
|
|
ctx.fillText(`解锁日期:${formatDate(input.unlockedAt)}`, 340, 518)
|
|
|
|
const blob = await new Promise<Blob | null>(resolve => canvas.toBlob(resolve, 'image/png'))
|
|
if (!blob) return
|
|
|
|
const url = URL.createObjectURL(blob)
|
|
const link = document.createElement('a')
|
|
link.href = url
|
|
link.download = makeDownloadName(input.cloudNameEn.toLowerCase())
|
|
link.click()
|
|
URL.revokeObjectURL(url)
|
|
}
|