diff --git a/hunliji-api/hl-api b/hunliji-api/hl-api index 41ea73a..c4f5b78 100644 Binary files a/hunliji-api/hl-api and b/hunliji-api/hl-api differ diff --git a/hunliji-api/posterconfig/poster.go b/hunliji-api/posterconfig/poster.go index 111f5b8..45747ed 100644 --- a/hunliji-api/posterconfig/poster.go +++ b/hunliji-api/posterconfig/poster.go @@ -23,13 +23,14 @@ func Register(api *gin.RouterGroup, database *gorm.DB, adminGuard func(*gin.Cont api.POST("/poster/config", handleSavePosterConfig) } +// resolveSide 返回 "1"(男方)或 "2"(女方);无法识别返回空 func resolveSide(raw string) string { v := strings.ToLower(strings.TrimSpace(raw)) switch v { - case "female", "nv", "f", "bride": - return "female" - case "male", "n", "m", "groom": - return "male" + case "1", "male", "n", "m", "groom": + return "1" + case "2", "female", "nv", "f", "bride": + return "2" default: return "" } @@ -39,13 +40,12 @@ func isLegacyFlat(m map[string]interface{}) bool { if m == nil { return false } - if _, ok := m["male"]; ok { - return false + for _, k := range []string{"1", "2", "male", "female"} { + if _, ok := m[k]; ok { + return false + } } - if _, ok := m["female"]; ok { - return false - } - for _, k := range []string{"title", "lines", "heroImg", "sonName"} { + for _, k := range []string{"title", "lines", "heroImg", "sonName", "template"} { if _, ok := m[k]; ok { return true } @@ -64,24 +64,33 @@ func asObject(v interface{}) map[string]interface{} { return emptyObj() } -// normalizeBundle 统一为 { male, female };旧扁平配置迁入 male +func takeSide(raw map[string]interface{}, keys ...string) map[string]interface{} { + for _, k := range keys { + if m := asObject(raw[k]); len(m) > 0 { + return m + } + } + return emptyObj() +} + +// normalizeBundle 统一为 { "1", "2" };兼容旧 male/female 与扁平配置 func normalizeBundle(raw map[string]interface{}) map[string]interface{} { if raw == nil { raw = emptyObj() } out := map[string]interface{}{ - "male": emptyObj(), - "female": emptyObj(), + "1": emptyObj(), + "2": emptyObj(), } if isLegacyFlat(raw) { - out["male"] = raw + out["1"] = raw return out } - if m := asObject(raw["male"]); len(m) > 0 { - out["male"] = m + if m := takeSide(raw, "1", "male"); len(m) > 0 { + out["1"] = m } - if f := asObject(raw["female"]); len(f) > 0 { - out["female"] = f + if f := takeSide(raw, "2", "female"); len(f) > 0 { + out["2"] = f } return out } diff --git a/vite-tailwindcss/src/api/wedding.js b/vite-tailwindcss/src/api/wedding.js index 4a76e9a..ec8d577 100644 --- a/vite-tailwindcss/src/api/wedding.js +++ b/vite-tailwindcss/src/api/wedding.js @@ -84,9 +84,9 @@ export function saveUploadConfig(payload) { return service.post('/upload/config', payload) } -/** side 可选:male|female|n|nv;不传则返回 { male, female } 整包 */ +/** side 可选:1|2(亦兼容 male/female/n/nv);不传则返回 { "1", "2" } 整包 */ export function getPosterConfig(side) { - const params = side ? { side } : undefined + const params = side != null && side !== '' ? { side } : undefined return service.get('/poster/config', { params }) } diff --git a/vite-tailwindcss/src/composables/posterDefaults.js b/vite-tailwindcss/src/composables/posterDefaults.js index 7827ab0..9fa542c 100644 --- a/vite-tailwindcss/src/composables/posterDefaults.js +++ b/vite-tailwindcss/src/composables/posterDefaults.js @@ -1,19 +1,34 @@ -/** 海报 side:male=/hb-n 男方,female=/hb-nv 女方 */ +/** 海报 side:1=男方(/hb-n),2=女方(/hb-nv) */ export const POSTER_SIDES = [ - { key: 'male', route: '/hb-n', label: '男方', short: 'n' }, - { key: 'female', route: '/hb-nv', label: '女方', short: 'nv' }, + { 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: '竖排书法 + 右侧宴席信息' }, +] + +/** 解析为 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 'female' - if (v === 'male' || v === 'n' || v === 'm' || v === 'groom') return 'male' - return '' + if (v === 'female' || v === 'nv' || v === 'f' || v === 'bride') return 2 + if (v === 'male' || v === 'n' || v === 'm' || v === 'groom') return 1 + return 0 } -function baseFields() { +export function sideKey(side) { + const s = resolvePosterSide(side) || 1 + return String(s) +} + +function baseFields(side) { return { + side: resolvePosterSide(side) || 1, + template: 'classic', enabled: true, heroImg: '', title: '婚礼邀请函', @@ -23,17 +38,32 @@ function baseFields() { showInviteButton: true, inviteButtonText: '打开请柬', invitePath: '/', + // split 模板字段 + verticalSlogan: '吾家有喜', + subSlogan1: '佳偶天成', + subSlogan2: '百年好合', + greeting: '尊敬的各位亲朋好友', + groomName: '', + brideName: '', + dateText: '2026年10月3日 星期日', + lunarText: '农历九月初八', + venueText: '幸福大酒店二楼宴会厅', + loveFooter: 'Love you', } } -/** 男方默认文案 */ -export function defaultMalePosterConfig() { +/** 男方(1) 默认 */ +export function defaultSide1PosterConfig() { return { - ...baseFields(), + ...baseFields(1), + side: 1, sonLabel: '儿子:', sonName: '张浩强', daughterInLawLabel: '儿媳:', daughterInLawName: '刘佳怡', + groomName: '张浩强', + brideName: '刘佳怡', + verticalSlogan: '吾家有喜', lines: [ '各位亲朋好友', '吾家有喜 儿子结婚', @@ -48,14 +78,18 @@ export function defaultMalePosterConfig() { } } -/** 女方默认文案 */ -export function defaultFemalePosterConfig() { +/** 女方(2) 默认 */ +export function defaultSide2PosterConfig() { return { - ...baseFields(), + ...baseFields(2), + side: 2, sonLabel: '女儿:', sonName: '刘佳怡', daughterInLawLabel: '女婿:', daughterInLawName: '张浩强', + groomName: '张浩强', + brideName: '刘佳怡', + verticalSlogan: '小女出阁', lines: [ '各位亲朋好友', '吾家有喜 女儿出嫁', @@ -70,15 +104,20 @@ export function defaultFemalePosterConfig() { } } -/** @deprecated 兼容旧调用,等同男方默认 */ +export function defaultMalePosterConfig() { + return defaultSide1PosterConfig() +} +export function defaultFemalePosterConfig() { + return defaultSide2PosterConfig() +} export function defaultPosterConfig() { - return defaultMalePosterConfig() + return defaultSide1PosterConfig() } export function defaultPosterBundle() { return { - male: defaultMalePosterConfig(), - female: defaultFemalePosterConfig(), + '1': defaultSide1PosterConfig(), + '2': defaultSide2PosterConfig(), } } @@ -94,24 +133,42 @@ export const ENTER_EFFECTS = [ { value: 'curtain', label: '金幕拉开' }, ] -/** 是否为旧版扁平配置(未分男女方) */ function isLegacyFlatPoster(raw) { if (!raw || typeof raw !== 'object') return false - if (raw.male || raw.female) return false - return 'title' in raw || 'lines' in raw || 'heroImg' in raw || 'sonName' in raw + 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 } -export function normalizePosterConfig(raw, side = 'male') { - const s = resolvePosterSide(side) || 'male' - const base = s === 'female' ? defaultFemalePosterConfig() : defaultMalePosterConfig() +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 groomName = loaded.groomName || loaded.sonName || base.groomName + const brideName = loaded.brideName || loaded.daughterInLawName || base.brideName + return { ...base, ...loaded, + side: s, + template, 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) @@ -122,26 +179,34 @@ export function normalizePosterConfig(raw, side = 'male') { : base.enterEffect, invitePath: loaded.invitePath || '/', inviteButtonText: loaded.inviteButtonText || base.inviteButtonText, + 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, } } -/** 归一化整包 { male, female };兼容旧扁平数据迁入 male */ +/** 归一化为 { 1, 2 };兼容旧 male/female 与扁平数据 */ export function normalizePosterBundle(raw) { const loaded = raw && typeof raw === 'object' ? raw : {} if (isLegacyFlatPoster(loaded)) { return { - male: normalizePosterConfig(loaded, 'male'), - female: defaultFemalePosterConfig(), + '1': normalizePosterConfig(loaded, 1), + '2': defaultSide2PosterConfig(), } } return { - male: normalizePosterConfig(loaded.male, 'male'), - female: normalizePosterConfig(loaded.female, 'female'), + '1': normalizePosterConfig(pickSideSource(loaded, 1) || {}, 1), + '2': normalizePosterConfig(pickSideSource(loaded, 2) || {}, 2), } } export function pickPosterSide(bundle, side) { const normalized = normalizePosterBundle(bundle) - const s = resolvePosterSide(side) || 'male' + const s = sideKey(side) return normalized[s] } diff --git a/vite-tailwindcss/src/router/index.js b/vite-tailwindcss/src/router/index.js index 49a93d4..5373b0e 100644 --- a/vite-tailwindcss/src/router/index.js +++ b/vite-tailwindcss/src/router/index.js @@ -16,13 +16,13 @@ const routes = [ path: '/hb-n', name: 'PosterMale', component: () => import('@/views/hb/index.vue'), - meta: { posterSide: 'male' }, + meta: { posterSide: 1 }, }, { path: '/hb-nv', name: 'PosterFemale', component: () => import('@/views/hb/index.vue'), - meta: { posterSide: 'female' }, + meta: { posterSide: 2 }, }, { path: '/login', diff --git a/vite-tailwindcss/src/views/admin/AdminEditor.vue b/vite-tailwindcss/src/views/admin/AdminEditor.vue index 486e745..99a168e 100644 --- a/vite-tailwindcss/src/views/admin/AdminEditor.vue +++ b/vite-tailwindcss/src/views/admin/AdminEditor.vue @@ -19,8 +19,8 @@ @@ -410,20 +410,34 @@ - + - - + + + + {{ posterSideTab === '2' ? '2 · 女方' : '1 · 男方' }} + 路由 {{ posterSideTab === '2' ? '/hb-nv' : '/hb-n' }} + + + + + {{ t.label }} + + +
+ {{ POSTER_TEMPLATES.find(t => t.value === posterCfg.template)?.desc }} +
+
@@ -450,39 +464,85 @@
- - + + -
- -
- - -
+ + + + + + +
保存海报配置 - 打开 {{ posterSideTab === 'female' ? '/hb-nv' : '/hb-n' }} 预览 + 打开 {{ posterSideTab === '2' ? '/hb-nv' : '/hb-n' }} 预览
@@ -830,7 +890,7 @@ import { resolveDanmakuSwatch } from '@/utils/danmakuColors' import { getSlotResizeSpecs, resizeSlotToFile, probeResizedMeta, metaHasFileSize } from '@/composables/resizeImage' import { defaultPosterBundle, normalizePosterBundle, normalizePosterConfig, - LOADING_EFFECTS, ENTER_EFFECTS, POSTER_SIDES, + LOADING_EFFECTS, ENTER_EFFECTS, POSTER_SIDES, POSTER_TEMPLATES, sideKey, } from '@/composables/posterDefaults' echarts.use([PieChart, TooltipComponent, LegendComponent, CanvasRenderer]) @@ -888,22 +948,27 @@ let regionChart = null const cfg = reactive(defaultConfig()) const uploadCfg = reactive(defaultUploadConfig()) -const posterSideTab = ref('male') +const posterSideTab = ref('1') const posterBundle = reactive(defaultPosterBundle()) -const posterCfg = computed(() => posterBundle[posterSideTab.value] || posterBundle.male) +const posterCfg = computed(() => posterBundle[sideKey(posterSideTab.value)] || posterBundle['1']) const posterLinesText = computed({ get: () => { const lines = posterCfg.value?.lines return Array.isArray(lines) ? lines.join('\n') : '' }, set: (v) => { - const side = posterSideTab.value + const side = sideKey(posterSideTab.value) if (!posterBundle[side]) posterBundle[side] = normalizePosterConfig(null, side) posterBundle[side].lines = String(v || '') .split('\n') .map((s) => s.trimEnd()) }, }) +function setPosterHero(url) { + const side = sideKey(posterSideTab.value) + if (!posterBundle[side]) posterBundle[side] = normalizePosterConfig(null, side) + posterBundle[side].heroImg = url +} const currentPhotoIndex = computed(() => Math.min(Number(activePhotoTab.value) || 0, Math.max(cfg.photoPages.length - 1, 0))) const isConfigTab = computed(() => CONFIG_SECTIONS.includes(activeTab.value)) const visitPagination = computed(() => ({ @@ -1324,8 +1389,8 @@ async function loadPosterConfig() { try { const res = await getPosterConfig() const bundle = normalizePosterBundle(res.data || {}) - posterBundle.male = bundle.male - posterBundle.female = bundle.female + posterBundle['1'] = bundle['1'] + posterBundle['2'] = bundle['2'] } catch (e) { message.error(e?.message || '海报配置加载失败') } finally { @@ -1339,8 +1404,8 @@ async function onSavePosterConfig() { const payload = normalizePosterBundle(posterBundle) const res = await savePosterConfig(payload) const bundle = normalizePosterBundle(res.data || payload) - posterBundle.male = bundle.male - posterBundle.female = bundle.female + posterBundle['1'] = bundle['1'] + posterBundle['2'] = bundle['2'] message.success('海报配置已保存') } catch (e) { message.error(e?.message || '海报配置保存失败') @@ -1547,8 +1612,9 @@ async function onTabActivate(tab) { } function openPreview() { window.open('/', '_blank') } -function openPosterPreview(side = 'male') { - const meta = POSTER_SIDES.find((s) => s.key === side) || POSTER_SIDES[0] +function openPosterPreview(side = 1) { + const s = Number(sideKey(side)) + const meta = POSTER_SIDES.find((x) => x.key === s) || POSTER_SIDES[0] window.open(meta.route, '_blank') } diff --git a/vite-tailwindcss/src/views/hb/index.vue b/vite-tailwindcss/src/views/hb/index.vue index 3ece147..4d0679e 100644 --- a/vite-tailwindcss/src/views/hb/index.vue +++ b/vite-tailwindcss/src/views/hb/index.vue @@ -1,6 +1,5 @@