2026-08-03 10:22:04 +08:00
|
|
|
|
/** 海报 side:1=男方(/hb-n),2=女方(/hb-nv),3=回门宴(/hb-hm) */
|
2026-08-03 09:31:32 +08:00
|
|
|
|
|
|
|
|
|
|
export const POSTER_SIDES = [
|
2026-08-03 09:54:43 +08:00
|
|
|
|
{ key: 1, keyStr: '1', route: '/hb-n', label: '男方', short: 'n' },
|
|
|
|
|
|
{ key: 2, keyStr: '2', route: '/hb-nv', label: '女方', short: 'nv' },
|
2026-08-03 10:22:04 +08:00
|
|
|
|
{ key: 3, keyStr: '3', route: '/hb-hm', label: '回门宴', short: 'hm' },
|
2026-08-03 09:31:32 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
2026-08-03 09:54:43 +08:00
|
|
|
|
export const POSTER_TEMPLATES = [
|
|
|
|
|
|
{ value: 'classic', label: '经典竖版', desc: '顶图 + 正文逐行出现' },
|
|
|
|
|
|
{ value: 'split', label: '左图右文', desc: '竖排书法 + 右侧宴席信息' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-08-03 10:18:23 +08:00
|
|
|
|
/** 手机端布局:仅影响窄屏;PC 仍按模板本身版式 */
|
|
|
|
|
|
export const MOBILE_LAYOUTS = [
|
|
|
|
|
|
{ value: 'auto', label: '自动布局', desc: '按屏幕宽度自动切换(当前默认)' },
|
|
|
|
|
|
{ value: 'portrait', label: '竖向展示', desc: '手机端强制上下排布' },
|
|
|
|
|
|
{ value: 'landscape', label: '横屏展示', desc: '手机端强制左右分栏' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-08-03 10:22:04 +08:00
|
|
|
|
/** 解析为 1 | 2 | 3,无法识别返回 0 */
|
2026-08-03 09:31:32 +08:00
|
|
|
|
export function resolvePosterSide(input) {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
if (input === 1 || input === '1') return 1
|
|
|
|
|
|
if (input === 2 || input === '2') return 2
|
2026-08-03 10:22:04 +08:00
|
|
|
|
if (input === 3 || input === '3') return 3
|
2026-08-03 09:31:32 +08:00
|
|
|
|
const v = String(input || '').trim().toLowerCase()
|
2026-08-03 10:22:04 +08:00
|
|
|
|
if (v === 'hm' || v === 'hmy' || v === 'return' || v === 'huimen') return 3
|
2026-08-03 09:54:43 +08:00
|
|
|
|
if (v === 'female' || v === 'nv' || v === 'f' || v === 'bride') return 2
|
|
|
|
|
|
if (v === 'male' || v === 'n' || v === 'm' || v === 'groom') return 1
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function sideKey(side) {
|
|
|
|
|
|
const s = resolvePosterSide(side) || 1
|
|
|
|
|
|
return String(s)
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 10:22:04 +08:00
|
|
|
|
export function posterSideMeta(side) {
|
|
|
|
|
|
const s = resolvePosterSide(side) || 1
|
|
|
|
|
|
return POSTER_SIDES.find((x) => x.key === s) || POSTER_SIDES[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 09:54:43 +08:00
|
|
|
|
function baseFields(side) {
|
2026-08-03 09:31:32 +08:00
|
|
|
|
return {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
side: resolvePosterSide(side) || 1,
|
|
|
|
|
|
template: 'classic',
|
2026-08-03 10:18:23 +08:00
|
|
|
|
mobileLayout: 'auto',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
enabled: true,
|
|
|
|
|
|
heroImg: '',
|
2026-08-03 10:18:23 +08:00
|
|
|
|
/** 二维码中心图;空则显示圆圈大囍 */
|
|
|
|
|
|
qrCenterImg: '',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
title: '婚礼邀请函',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
/** 浏览器标签 / 分享用页面标题与描述(可与海报主标题不同) */
|
|
|
|
|
|
pageTitle: '',
|
|
|
|
|
|
pageDescription: '',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
footer: '—诚挚邀请 ♥ 恭请光临—',
|
|
|
|
|
|
loadingEffect: 'redSeal',
|
|
|
|
|
|
enterEffect: 'fadeUp',
|
|
|
|
|
|
showInviteButton: true,
|
|
|
|
|
|
inviteButtonText: '打开请柬',
|
|
|
|
|
|
invitePath: '/',
|
2026-08-03 09:54:43 +08:00
|
|
|
|
// split 模板字段
|
|
|
|
|
|
verticalSlogan: '吾家有喜',
|
|
|
|
|
|
subSlogan1: '佳偶天成',
|
|
|
|
|
|
subSlogan2: '百年好合',
|
|
|
|
|
|
greeting: '尊敬的各位亲朋好友',
|
|
|
|
|
|
groomName: '',
|
|
|
|
|
|
brideName: '',
|
|
|
|
|
|
dateText: '2026年10月3日 星期日',
|
|
|
|
|
|
lunarText: '农历九月初八',
|
|
|
|
|
|
venueText: '幸福大酒店二楼宴会厅',
|
|
|
|
|
|
loveFooter: 'Love you',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 09:54:43 +08:00
|
|
|
|
/** 男方(1) 默认 */
|
|
|
|
|
|
export function defaultSide1PosterConfig() {
|
2026-08-03 09:31:32 +08:00
|
|
|
|
return {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
...baseFields(1),
|
|
|
|
|
|
side: 1,
|
2026-08-03 10:25:11 +08:00
|
|
|
|
pageTitle: '💕李琦❤️李逸烁 · 男方婚礼邀请',
|
|
|
|
|
|
pageDescription: '💍公历2026年9月4日(农历七月廿三)· 李琦 & 李逸烁 结婚典礼,诚邀您光临。',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
sonLabel: '儿子:',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
sonName: '李琦',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
daughterInLawLabel: '儿媳:',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
daughterInLawName: '李逸烁',
|
|
|
|
|
|
groomName: '李琦',
|
|
|
|
|
|
brideName: '李逸烁',
|
2026-08-03 09:54:43 +08:00
|
|
|
|
verticalSlogan: '吾家有喜',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
dateText: '2026年9月4日 星期五',
|
|
|
|
|
|
lunarText: '农历七月廿三',
|
|
|
|
|
|
venueText: '山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
lines: [
|
|
|
|
|
|
'各位亲朋好友',
|
|
|
|
|
|
'吾家有喜 儿子结婚',
|
|
|
|
|
|
'良辰已定 吉日待访',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
'兹定于2026年9月4日 星期五',
|
|
|
|
|
|
'农历七月廿三 中午11:08分',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
'为儿子儿媳举办结婚典礼',
|
|
|
|
|
|
'敬备喜宴 诚邀您携家人光临',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
'地址:山西省 阳泉市 芙蓉酒楼(阳泉店) 二楼',
|
|
|
|
|
|
'李国贞 & 赵红燕 夫 妇 敬邀',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 09:54:43 +08:00
|
|
|
|
/** 女方(2) 默认 */
|
|
|
|
|
|
export function defaultSide2PosterConfig() {
|
2026-08-03 09:31:32 +08:00
|
|
|
|
return {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
...baseFields(2),
|
|
|
|
|
|
side: 2,
|
2026-08-03 10:22:04 +08:00
|
|
|
|
template: 'split',
|
|
|
|
|
|
mobileLayout: 'landscape',
|
2026-08-03 10:25:11 +08:00
|
|
|
|
pageTitle: '💕李琦❤️李逸烁 · 女方婚礼邀请',
|
|
|
|
|
|
pageDescription: '💍公历2026年9月4日(农历七月廿三)· 李逸烁 & 李琦 结婚典礼,诚邀您光临。',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
sonLabel: '女儿:',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
sonName: '李逸烁',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
daughterInLawLabel: '女婿:',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
daughterInLawName: '李琦',
|
|
|
|
|
|
groomName: '李琦',
|
|
|
|
|
|
brideName: '李逸烁',
|
2026-08-03 09:54:43 +08:00
|
|
|
|
verticalSlogan: '小女出阁',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
dateText: '2026年9月4日 星期五',
|
|
|
|
|
|
lunarText: '农历七月廿三',
|
|
|
|
|
|
venueText: '山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
lines: [
|
|
|
|
|
|
'各位亲朋好友',
|
|
|
|
|
|
'吾家有喜 女儿出嫁',
|
|
|
|
|
|
'良辰已定 吉日待访',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
'兹定于2026年9月4日 星期五',
|
|
|
|
|
|
'农历七月廿三 中午11:08分',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
'为女儿女婿举办结婚典礼',
|
|
|
|
|
|
'敬备喜宴 诚邀您携家人光临',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
'地址:山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
|
2026-08-03 09:31:32 +08:00
|
|
|
|
'刘建国 & 王秀兰 夫 妇 敬邀',
|
|
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 10:22:04 +08:00
|
|
|
|
/** 回门宴(3) 默认:9月7日 */
|
|
|
|
|
|
export function defaultSide3PosterConfig() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...baseFields(3),
|
|
|
|
|
|
side: 3,
|
|
|
|
|
|
template: 'classic',
|
|
|
|
|
|
title: '回门宴邀请函',
|
2026-08-03 10:25:11 +08:00
|
|
|
|
pageTitle: '💕李琦❤️李逸烁 · 回门宴邀请',
|
|
|
|
|
|
pageDescription: '💍公历2026年9月7日(农历七月廿六)· 李琦 & 李逸烁 回门宴,诚邀您光临。',
|
2026-08-03 10:22:04 +08:00
|
|
|
|
footer: '—回门宴请 ♥ 恭请光临—',
|
|
|
|
|
|
sonLabel: '新郎:',
|
|
|
|
|
|
sonName: '李琦',
|
|
|
|
|
|
daughterInLawLabel: '新娘:',
|
|
|
|
|
|
daughterInLawName: '李逸烁',
|
|
|
|
|
|
groomName: '李琦',
|
|
|
|
|
|
brideName: '李逸烁',
|
|
|
|
|
|
verticalSlogan: '回门之喜',
|
|
|
|
|
|
subSlogan1: '琴瑟和鸣',
|
|
|
|
|
|
subSlogan2: '永结同心',
|
|
|
|
|
|
dateText: '2026年9月7日 星期一',
|
|
|
|
|
|
lunarText: '农历七月廿六',
|
|
|
|
|
|
venueText: '山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
|
|
|
|
|
|
enterEffect: 'curtain',
|
|
|
|
|
|
loadingEffect: 'redSeal',
|
|
|
|
|
|
lines: [
|
|
|
|
|
|
'各位亲朋好友',
|
|
|
|
|
|
'新婚燕尔 回门宴请',
|
|
|
|
|
|
'良辰已定 吉日待访',
|
|
|
|
|
|
'兹定于2026年9月7日 星期一',
|
|
|
|
|
|
'农历七月廿六 中午11:08分',
|
|
|
|
|
|
'为新婚夫妇举办回门宴',
|
|
|
|
|
|
'敬备薄酒 诚邀您携家人光临',
|
|
|
|
|
|
'地址:山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
|
|
|
|
|
|
'李琦 & 李逸烁 敬邀',
|
|
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 09:54:43 +08:00
|
|
|
|
export function defaultMalePosterConfig() {
|
|
|
|
|
|
return defaultSide1PosterConfig()
|
|
|
|
|
|
}
|
|
|
|
|
|
export function defaultFemalePosterConfig() {
|
|
|
|
|
|
return defaultSide2PosterConfig()
|
|
|
|
|
|
}
|
2026-08-03 09:31:32 +08:00
|
|
|
|
export function defaultPosterConfig() {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
return defaultSide1PosterConfig()
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function defaultPosterBundle() {
|
|
|
|
|
|
return {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
'1': defaultSide1PosterConfig(),
|
|
|
|
|
|
'2': defaultSide2PosterConfig(),
|
2026-08-03 10:22:04 +08:00
|
|
|
|
'3': defaultSide3PosterConfig(),
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 10:22:04 +08:00
|
|
|
|
export function defaultPosterForSide(side) {
|
|
|
|
|
|
const s = resolvePosterSide(side) || 1
|
|
|
|
|
|
if (s === 3) return defaultSide3PosterConfig()
|
|
|
|
|
|
if (s === 2) return defaultSide2PosterConfig()
|
|
|
|
|
|
return defaultSide1PosterConfig()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 09:31:32 +08:00
|
|
|
|
export const LOADING_EFFECTS = [
|
|
|
|
|
|
{ value: 'fade', label: '淡入加载' },
|
|
|
|
|
|
{ value: 'redSeal', label: '红印囍字' },
|
|
|
|
|
|
{ value: 'goldShimmer', label: '金字微光' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
export const ENTER_EFFECTS = [
|
|
|
|
|
|
{ value: 'fadeUp', label: '上浮淡入' },
|
|
|
|
|
|
{ value: 'scaleIn', label: '缩放进入' },
|
|
|
|
|
|
{ value: 'curtain', label: '金幕拉开' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
function isLegacyFlatPoster(raw) {
|
|
|
|
|
|
if (!raw || typeof raw !== 'object') return false
|
2026-08-03 10:22:04 +08:00
|
|
|
|
if (raw['1'] || raw['2'] || raw['3'] || raw[1] || raw[2] || raw[3] || raw.male || raw.female) return false
|
2026-08-03 09:54:43 +08:00
|
|
|
|
return 'title' in raw || 'lines' in raw || 'heroImg' in raw || 'sonName' in raw || 'template' in raw
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 09:54:43 +08:00
|
|
|
|
function pickSideSource(loaded, side) {
|
|
|
|
|
|
const k = sideKey(side)
|
|
|
|
|
|
if (loaded[k] && typeof loaded[k] === 'object') return loaded[k]
|
|
|
|
|
|
if (side === 1 && loaded.male && typeof loaded.male === 'object') return loaded.male
|
|
|
|
|
|
if (side === 2 && loaded.female && typeof loaded.female === 'object') return loaded.female
|
2026-08-03 10:22:04 +08:00
|
|
|
|
if (side === 3 && loaded.hm && typeof loaded.hm === 'object') return loaded.hm
|
2026-08-03 09:54:43 +08:00
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizePosterConfig(raw, side = 1) {
|
|
|
|
|
|
const s = resolvePosterSide(side) || 1
|
2026-08-03 10:22:04 +08:00
|
|
|
|
const base = defaultPosterForSide(s)
|
2026-08-03 09:31:32 +08:00
|
|
|
|
const loaded = raw && typeof raw === 'object' ? raw : {}
|
|
|
|
|
|
const lines = Array.isArray(loaded.lines)
|
|
|
|
|
|
? loaded.lines.map((l) => String(l ?? ''))
|
|
|
|
|
|
: base.lines
|
2026-08-03 09:54:43 +08:00
|
|
|
|
const template = POSTER_TEMPLATES.some((t) => t.value === loaded.template)
|
|
|
|
|
|
? loaded.template
|
|
|
|
|
|
: base.template
|
2026-08-03 10:18:23 +08:00
|
|
|
|
const mobileLayout = MOBILE_LAYOUTS.some((t) => t.value === loaded.mobileLayout)
|
|
|
|
|
|
? loaded.mobileLayout
|
|
|
|
|
|
: base.mobileLayout
|
2026-08-03 09:54:43 +08:00
|
|
|
|
|
|
|
|
|
|
const groomName = loaded.groomName || loaded.sonName || base.groomName
|
|
|
|
|
|
const brideName = loaded.brideName || loaded.daughterInLawName || base.brideName
|
|
|
|
|
|
|
2026-08-03 09:31:32 +08:00
|
|
|
|
return {
|
|
|
|
|
|
...base,
|
|
|
|
|
|
...loaded,
|
2026-08-03 09:54:43 +08:00
|
|
|
|
side: s,
|
|
|
|
|
|
template,
|
2026-08-03 10:18:23 +08:00
|
|
|
|
mobileLayout,
|
2026-08-03 09:31:32 +08:00
|
|
|
|
lines: lines.length ? lines : base.lines,
|
2026-08-03 09:54:43 +08:00
|
|
|
|
groomName,
|
|
|
|
|
|
brideName,
|
2026-08-03 09:31:32 +08:00
|
|
|
|
enabled: loaded.enabled !== false,
|
|
|
|
|
|
showInviteButton: loaded.showInviteButton !== false,
|
|
|
|
|
|
loadingEffect: LOADING_EFFECTS.some((e) => e.value === loaded.loadingEffect)
|
|
|
|
|
|
? loaded.loadingEffect
|
|
|
|
|
|
: base.loadingEffect,
|
|
|
|
|
|
enterEffect: ENTER_EFFECTS.some((e) => e.value === loaded.enterEffect)
|
|
|
|
|
|
? loaded.enterEffect
|
|
|
|
|
|
: base.enterEffect,
|
|
|
|
|
|
invitePath: loaded.invitePath || '/',
|
|
|
|
|
|
inviteButtonText: loaded.inviteButtonText || base.inviteButtonText,
|
2026-08-03 10:25:11 +08:00
|
|
|
|
pageTitle: loaded.pageTitle || base.pageTitle || '',
|
|
|
|
|
|
pageDescription: loaded.pageDescription || base.pageDescription || '',
|
2026-08-03 10:18:23 +08:00
|
|
|
|
qrCenterImg: loaded.qrCenterImg || '',
|
2026-08-03 09:54:43 +08:00
|
|
|
|
verticalSlogan: loaded.verticalSlogan || base.verticalSlogan,
|
|
|
|
|
|
subSlogan1: loaded.subSlogan1 || base.subSlogan1,
|
|
|
|
|
|
subSlogan2: loaded.subSlogan2 || base.subSlogan2,
|
|
|
|
|
|
greeting: loaded.greeting || base.greeting,
|
|
|
|
|
|
dateText: loaded.dateText || base.dateText,
|
|
|
|
|
|
lunarText: loaded.lunarText || base.lunarText,
|
|
|
|
|
|
venueText: loaded.venueText || base.venueText,
|
|
|
|
|
|
loveFooter: loaded.loveFooter || base.loveFooter,
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 10:22:04 +08:00
|
|
|
|
/** 归一化为 { 1, 2, 3 };兼容旧 male/female 与扁平数据 */
|
2026-08-03 09:31:32 +08:00
|
|
|
|
export function normalizePosterBundle(raw) {
|
|
|
|
|
|
const loaded = raw && typeof raw === 'object' ? raw : {}
|
|
|
|
|
|
if (isLegacyFlatPoster(loaded)) {
|
|
|
|
|
|
return {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
'1': normalizePosterConfig(loaded, 1),
|
|
|
|
|
|
'2': defaultSide2PosterConfig(),
|
2026-08-03 10:22:04 +08:00
|
|
|
|
'3': defaultSide3PosterConfig(),
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
2026-08-03 09:54:43 +08:00
|
|
|
|
'1': normalizePosterConfig(pickSideSource(loaded, 1) || {}, 1),
|
|
|
|
|
|
'2': normalizePosterConfig(pickSideSource(loaded, 2) || {}, 2),
|
2026-08-03 10:22:04 +08:00
|
|
|
|
'3': normalizePosterConfig(pickSideSource(loaded, 3) || {}, 3),
|
2026-08-03 09:31:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function pickPosterSide(bundle, side) {
|
|
|
|
|
|
const normalized = normalizePosterBundle(bundle)
|
2026-08-03 09:54:43 +08:00
|
|
|
|
const s = sideKey(side)
|
2026-08-03 09:31:32 +08:00
|
|
|
|
return normalized[s]
|
|
|
|
|
|
}
|