1. 海报
This commit is contained in:
@@ -10,6 +10,13 @@ export const POSTER_TEMPLATES = [
|
||||
{ value: 'split', label: '左图右文', desc: '竖排书法 + 右侧宴席信息' },
|
||||
]
|
||||
|
||||
/** 手机端布局:仅影响窄屏;PC 仍按模板本身版式 */
|
||||
export const MOBILE_LAYOUTS = [
|
||||
{ value: 'auto', label: '自动布局', desc: '按屏幕宽度自动切换(当前默认)' },
|
||||
{ value: 'portrait', label: '竖向展示', desc: '手机端强制上下排布' },
|
||||
{ value: 'landscape', label: '横屏展示', desc: '手机端强制左右分栏' },
|
||||
]
|
||||
|
||||
/** 解析为 1 | 2,无法识别返回 0 */
|
||||
export function resolvePosterSide(input) {
|
||||
if (input === 1 || input === '1') return 1
|
||||
@@ -29,8 +36,11 @@ function baseFields(side) {
|
||||
return {
|
||||
side: resolvePosterSide(side) || 1,
|
||||
template: 'classic',
|
||||
mobileLayout: 'auto',
|
||||
enabled: true,
|
||||
heroImg: '',
|
||||
/** 二维码中心图;空则显示圆圈大囍 */
|
||||
qrCenterImg: '',
|
||||
title: '婚礼邀请函',
|
||||
footer: '—诚挚邀请 ♥ 恭请光临—',
|
||||
loadingEffect: 'redSeal',
|
||||
@@ -157,6 +167,9 @@ export function normalizePosterConfig(raw, side = 1) {
|
||||
const template = POSTER_TEMPLATES.some((t) => t.value === loaded.template)
|
||||
? loaded.template
|
||||
: base.template
|
||||
const mobileLayout = MOBILE_LAYOUTS.some((t) => t.value === loaded.mobileLayout)
|
||||
? loaded.mobileLayout
|
||||
: base.mobileLayout
|
||||
|
||||
const groomName = loaded.groomName || loaded.sonName || base.groomName
|
||||
const brideName = loaded.brideName || loaded.daughterInLawName || base.brideName
|
||||
@@ -166,6 +179,7 @@ export function normalizePosterConfig(raw, side = 1) {
|
||||
...loaded,
|
||||
side: s,
|
||||
template,
|
||||
mobileLayout,
|
||||
lines: lines.length ? lines : base.lines,
|
||||
groomName,
|
||||
brideName,
|
||||
@@ -179,6 +193,7 @@ export function normalizePosterConfig(raw, side = 1) {
|
||||
: base.enterEffect,
|
||||
invitePath: loaded.invitePath || '/',
|
||||
inviteButtonText: loaded.inviteButtonText || base.inviteButtonText,
|
||||
qrCenterImg: loaded.qrCenterImg || '',
|
||||
verticalSlogan: loaded.verticalSlogan || base.verticalSlogan,
|
||||
subSlogan1: loaded.subSlogan1 || base.subSlogan1,
|
||||
subSlogan2: loaded.subSlogan2 || base.subSlogan2,
|
||||
|
||||
83
vite-tailwindcss/src/composables/renderFestiveQr.js
Normal file
83
vite-tailwindcss/src/composables/renderFestiveQr.js
Normal file
@@ -0,0 +1,83 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user