/** 海报 side:1=男方(/hb-n),2=女方(/hb-nv) */ export const POSTER_SIDES = [ { key: 1, keyStr: '1', route: '/hb-n', label: '男方', short: 'n' }, { key: 2, keyStr: '2', route: '/hb-nv', label: '女方', short: 'nv' }, ] export const POSTER_TEMPLATES = [ { value: 'classic', label: '经典竖版', desc: '顶图 + 正文逐行出现' }, { 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 if (input === 2 || input === '2') return 2 const v = String(input || '').trim().toLowerCase() 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) } function baseFields(side) { return { side: resolvePosterSide(side) || 1, template: 'classic', mobileLayout: 'auto', enabled: true, heroImg: '', /** 二维码中心图;空则显示圆圈大囍 */ qrCenterImg: '', title: '婚礼邀请函', footer: '—诚挚邀请 ♥ 恭请光临—', loadingEffect: 'redSeal', enterEffect: 'fadeUp', showInviteButton: true, inviteButtonText: '打开请柬', invitePath: '/', // split 模板字段 verticalSlogan: '吾家有喜', subSlogan1: '佳偶天成', subSlogan2: '百年好合', greeting: '尊敬的各位亲朋好友', groomName: '', brideName: '', dateText: '2026年10月3日 星期日', lunarText: '农历九月初八', venueText: '幸福大酒店二楼宴会厅', loveFooter: 'Love you', } } /** 男方(1) 默认 */ export function defaultSide1PosterConfig() { return { ...baseFields(1), side: 1, sonLabel: '儿子:', sonName: '张浩强', daughterInLawLabel: '儿媳:', daughterInLawName: '刘佳怡', groomName: '张浩强', brideName: '刘佳怡', verticalSlogan: '吾家有喜', lines: [ '各位亲朋好友', '吾家有喜 儿子结婚', '良辰已定 吉日待访', '兹定于2026年10月3日 星期日', '农历九月初八 中午11:08分', '为儿子儿媳举办结婚典礼', '敬备喜宴 诚邀您携家人光临', '地址:幸福大酒店二楼宴会厅', '张国栋 & 陈晓玲 夫 妇 敬邀', ], } } /** 女方(2) 默认 */ export function defaultSide2PosterConfig() { return { ...baseFields(2), side: 2, sonLabel: '女儿:', sonName: '刘佳怡', daughterInLawLabel: '女婿:', daughterInLawName: '张浩强', groomName: '张浩强', brideName: '刘佳怡', verticalSlogan: '小女出阁', lines: [ '各位亲朋好友', '吾家有喜 女儿出嫁', '良辰已定 吉日待访', '兹定于2026年10月3日 星期日', '农历九月初八 中午11:08分', '为女儿女婿举办结婚典礼', '敬备喜宴 诚邀您携家人光临', '地址:幸福大酒店二楼宴会厅', '刘建国 & 王秀兰 夫 妇 敬邀', ], } } export function defaultMalePosterConfig() { return defaultSide1PosterConfig() } export function defaultFemalePosterConfig() { return defaultSide2PosterConfig() } export function defaultPosterConfig() { return defaultSide1PosterConfig() } export function defaultPosterBundle() { return { '1': defaultSide1PosterConfig(), '2': defaultSide2PosterConfig(), } } 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 if (raw['1'] || raw['2'] || raw[1] || raw[2] || raw.male || raw.female) return false return 'title' in raw || 'lines' in raw || 'heroImg' in raw || 'sonName' in raw || 'template' in raw } 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 return null } export function normalizePosterConfig(raw, side = 1) { const s = resolvePosterSide(side) || 1 const base = s === 2 ? defaultSide2PosterConfig() : defaultSide1PosterConfig() const loaded = raw && typeof raw === 'object' ? raw : {} const lines = Array.isArray(loaded.lines) ? loaded.lines.map((l) => String(l ?? '')) : base.lines 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 return { ...base, ...loaded, side: s, template, mobileLayout, lines: lines.length ? lines : base.lines, groomName, brideName, 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, qrCenterImg: loaded.qrCenterImg || '', 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, } } /** 归一化为 { 1, 2 };兼容旧 male/female 与扁平数据 */ export function normalizePosterBundle(raw) { const loaded = raw && typeof raw === 'object' ? raw : {} if (isLegacyFlatPoster(loaded)) { return { '1': normalizePosterConfig(loaded, 1), '2': defaultSide2PosterConfig(), } } return { '1': normalizePosterConfig(pickSideSource(loaded, 1) || {}, 1), '2': normalizePosterConfig(pickSideSource(loaded, 2) || {}, 2), } } export function pickPosterSide(bundle, side) { const normalized = normalizePosterBundle(bundle) const s = sideKey(side) return normalized[s] }