import QRCode from 'qrcode' function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image() img.crossOrigin = 'anonymous' img.onload = () => resolve(img) img.onerror = reject img.src = src }) } /** 在 canvas 上绘制喜庆二维码,中心为配置图或圆圈大囍 */ export async function renderFestiveQr(canvas, url, options = {}) { const size = options.size || 148 const centerImg = String(options.centerImg || '').trim() await QRCode.toCanvas(canvas, url, { width: size, margin: 1, color: { dark: '#8b0000', light: '#fff8e7', }, errorCorrectionLevel: 'H', }) const ctx = canvas.getContext('2d') if (!ctx) return const w = canvas.width const h = canvas.height const cx = w / 2 const cy = h / 2 const diameter = Math.round(Math.min(w, h) * 0.3) const r = diameter / 2 const ring = Math.max(2, Math.round(w * 0.012)) ctx.save() // 外圈底 ctx.beginPath() ctx.arc(cx, cy, r + ring, 0, Math.PI * 2) ctx.fillStyle = '#fff8e7' ctx.fill() ctx.strokeStyle = '#c41e3a' ctx.lineWidth = ring ctx.stroke() let drewImg = false if (centerImg) { try { const img = await loadImage(centerImg) ctx.beginPath() ctx.arc(cx, cy, r - 0.5, 0, Math.PI * 2) ctx.clip() const side = (r - 0.5) * 2 const scale = Math.max(side / img.width, side / img.height) const dw = img.width * scale const dh = img.height * scale ctx.drawImage(img, cx - dw / 2, cy - dh / 2, dw, dh) drewImg = true } catch { drewImg = false } } if (!drewImg) { ctx.beginPath() ctx.arc(cx, cy, r - 0.5, 0, Math.PI * 2) ctx.fillStyle = 'rgba(255, 248, 231, 0.96)' ctx.fill() ctx.strokeStyle = '#c41e3a' ctx.lineWidth = Math.max(1.5, ring * 0.7) ctx.stroke() ctx.fillStyle = '#c41e3a' ctx.font = `700 ${Math.round(r * 1.45)}px "Noto Serif SC","Source Han Serif SC","Songti SC",serif` ctx.textAlign = 'center' ctx.textBaseline = 'middle' ctx.fillText('囍', cx, cy + r * 0.04) } ctx.restore() }