feat: add cloud encyclopedia system
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user