diff --git a/hunliji-api/posterconfig/poster.go b/hunliji-api/posterconfig/poster.go
index 45747ed..d6df4b0 100644
--- a/hunliji-api/posterconfig/poster.go
+++ b/hunliji-api/posterconfig/poster.go
@@ -23,7 +23,7 @@ func Register(api *gin.RouterGroup, database *gorm.DB, adminGuard func(*gin.Cont
api.POST("/poster/config", handleSavePosterConfig)
}
-// resolveSide 返回 "1"(男方)或 "2"(女方);无法识别返回空
+// resolveSide 返回 "1"(男方)、"2"(女方)或 "3"(回门宴);无法识别返回空
func resolveSide(raw string) string {
v := strings.ToLower(strings.TrimSpace(raw))
switch v {
@@ -31,6 +31,8 @@ func resolveSide(raw string) string {
return "1"
case "2", "female", "nv", "f", "bride":
return "2"
+ case "3", "hm", "hmy", "return", "huimen":
+ return "3"
default:
return ""
}
@@ -40,7 +42,7 @@ func isLegacyFlat(m map[string]interface{}) bool {
if m == nil {
return false
}
- for _, k := range []string{"1", "2", "male", "female"} {
+ for _, k := range []string{"1", "2", "3", "male", "female", "hm"} {
if _, ok := m[k]; ok {
return false
}
@@ -73,7 +75,7 @@ func takeSide(raw map[string]interface{}, keys ...string) map[string]interface{}
return emptyObj()
}
-// normalizeBundle 统一为 { "1", "2" };兼容旧 male/female 与扁平配置
+// normalizeBundle 统一为 { "1", "2", "3" };兼容旧 male/female 与扁平配置
func normalizeBundle(raw map[string]interface{}) map[string]interface{} {
if raw == nil {
raw = emptyObj()
@@ -81,6 +83,7 @@ func normalizeBundle(raw map[string]interface{}) map[string]interface{} {
out := map[string]interface{}{
"1": emptyObj(),
"2": emptyObj(),
+ "3": emptyObj(),
}
if isLegacyFlat(raw) {
out["1"] = raw
@@ -92,6 +95,9 @@ func normalizeBundle(raw map[string]interface{}) map[string]interface{} {
if f := takeSide(raw, "2", "female"); len(f) > 0 {
out["2"] = f
}
+ if h := takeSide(raw, "3", "hm", "hmy"); len(h) > 0 {
+ out["3"] = h
+ }
return out
}
diff --git a/vite-tailwindcss/src/composables/posterDefaults.js b/vite-tailwindcss/src/composables/posterDefaults.js
index 36d7ec6..afd2d9a 100644
--- a/vite-tailwindcss/src/composables/posterDefaults.js
+++ b/vite-tailwindcss/src/composables/posterDefaults.js
@@ -1,8 +1,9 @@
-/** 海报 side:1=男方(/hb-n),2=女方(/hb-nv) */
+/** 海报 side:1=男方(/hb-n),2=女方(/hb-nv),3=回门宴(/hb-hm) */
export const POSTER_SIDES = [
{ key: 1, keyStr: '1', route: '/hb-n', label: '男方', short: 'n' },
{ key: 2, keyStr: '2', route: '/hb-nv', label: '女方', short: 'nv' },
+ { key: 3, keyStr: '3', route: '/hb-hm', label: '回门宴', short: 'hm' },
]
export const POSTER_TEMPLATES = [
@@ -17,11 +18,13 @@ export const MOBILE_LAYOUTS = [
{ value: 'landscape', label: '横屏展示', desc: '手机端强制左右分栏' },
]
-/** 解析为 1 | 2,无法识别返回 0 */
+/** 解析为 1 | 2 | 3,无法识别返回 0 */
export function resolvePosterSide(input) {
if (input === 1 || input === '1') return 1
if (input === 2 || input === '2') return 2
+ if (input === 3 || input === '3') return 3
const v = String(input || '').trim().toLowerCase()
+ if (v === 'hm' || v === 'hmy' || v === 'return' || v === 'huimen') return 3
if (v === 'female' || v === 'nv' || v === 'f' || v === 'bride') return 2
if (v === 'male' || v === 'n' || v === 'm' || v === 'groom') return 1
return 0
@@ -32,6 +35,11 @@ export function sideKey(side) {
return String(s)
}
+export function posterSideMeta(side) {
+ const s = resolvePosterSide(side) || 1
+ return POSTER_SIDES.find((x) => x.key === s) || POSTER_SIDES[0]
+}
+
function baseFields(side) {
return {
side: resolvePosterSide(side) || 1,
@@ -42,6 +50,9 @@ function baseFields(side) {
/** 二维码中心图;空则显示圆圈大囍 */
qrCenterImg: '',
title: '婚礼邀请函',
+ /** 浏览器标签 / 分享用页面标题与描述(可与海报主标题不同) */
+ pageTitle: '',
+ pageDescription: '',
footer: '—诚挚邀请 ♥ 恭请光临—',
loadingEffect: 'redSeal',
enterEffect: 'fadeUp',
@@ -68,22 +79,25 @@ export function defaultSide1PosterConfig() {
...baseFields(1),
side: 1,
sonLabel: '儿子:',
- sonName: '张浩强',
+ sonName: '李琦',
daughterInLawLabel: '儿媳:',
- daughterInLawName: '刘佳怡',
- groomName: '张浩强',
- brideName: '刘佳怡',
+ daughterInLawName: '李逸烁',
+ groomName: '李琦',
+ brideName: '李逸烁',
verticalSlogan: '吾家有喜',
+ dateText: '2026年9月4日 星期五',
+ lunarText: '农历七月廿三',
+ venueText: '山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
lines: [
'各位亲朋好友',
'吾家有喜 儿子结婚',
'良辰已定 吉日待访',
- '兹定于2026年10月3日 星期日',
- '农历九月初八 中午11:08分',
+ '兹定于2026年9月4日 星期五',
+ '农历七月廿三 中午11:08分',
'为儿子儿媳举办结婚典礼',
'敬备喜宴 诚邀您携家人光临',
- '地址:幸福大酒店二楼宴会厅',
- '张国栋 & 陈晓玲 夫 妇 敬邀',
+ '地址:山西省 阳泉市 芙蓉酒楼(阳泉店) 二楼',
+ '李国贞 & 赵红燕 夫 妇 敬邀',
],
}
}
@@ -93,27 +107,68 @@ export function defaultSide2PosterConfig() {
return {
...baseFields(2),
side: 2,
+ template: 'split',
+ mobileLayout: 'landscape',
sonLabel: '女儿:',
- sonName: '刘佳怡',
+ sonName: '李逸烁',
daughterInLawLabel: '女婿:',
- daughterInLawName: '张浩强',
- groomName: '张浩强',
- brideName: '刘佳怡',
+ daughterInLawName: '李琦',
+ groomName: '李琦',
+ brideName: '李逸烁',
verticalSlogan: '小女出阁',
+ dateText: '2026年9月4日 星期五',
+ lunarText: '农历七月廿三',
+ venueText: '山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
lines: [
'各位亲朋好友',
'吾家有喜 女儿出嫁',
'良辰已定 吉日待访',
- '兹定于2026年10月3日 星期日',
- '农历九月初八 中午11:08分',
+ '兹定于2026年9月4日 星期五',
+ '农历七月廿三 中午11:08分',
'为女儿女婿举办结婚典礼',
'敬备喜宴 诚邀您携家人光临',
- '地址:幸福大酒店二楼宴会厅',
+ '地址:山西省 阳泉市 芙蓉酒楼(阳泉店) 太行国际新城B区10号楼 二楼',
'刘建国 & 王秀兰 夫 妇 敬邀',
],
}
}
+/** 回门宴(3) 默认:9月7日 */
+export function defaultSide3PosterConfig() {
+ return {
+ ...baseFields(3),
+ side: 3,
+ template: 'classic',
+ title: '回门宴邀请函',
+ 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号楼 二楼',
+ '李琦 & 李逸烁 敬邀',
+ ],
+ }
+}
+
export function defaultMalePosterConfig() {
return defaultSide1PosterConfig()
}
@@ -128,9 +183,17 @@ export function defaultPosterBundle() {
return {
'1': defaultSide1PosterConfig(),
'2': defaultSide2PosterConfig(),
+ '3': defaultSide3PosterConfig(),
}
}
+export function defaultPosterForSide(side) {
+ const s = resolvePosterSide(side) || 1
+ if (s === 3) return defaultSide3PosterConfig()
+ if (s === 2) return defaultSide2PosterConfig()
+ return defaultSide1PosterConfig()
+}
+
export const LOADING_EFFECTS = [
{ value: 'fade', label: '淡入加载' },
{ value: 'redSeal', label: '红印囍字' },
@@ -145,7 +208,7 @@ export const ENTER_EFFECTS = [
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
+ if (raw['1'] || raw['2'] || raw['3'] || raw[1] || raw[2] || raw[3] || raw.male || raw.female) return false
return 'title' in raw || 'lines' in raw || 'heroImg' in raw || 'sonName' in raw || 'template' in raw
}
@@ -154,12 +217,13 @@ function pickSideSource(loaded, 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
+ if (side === 3 && loaded.hm && typeof loaded.hm === 'object') return loaded.hm
return null
}
export function normalizePosterConfig(raw, side = 1) {
const s = resolvePosterSide(side) || 1
- const base = s === 2 ? defaultSide2PosterConfig() : defaultSide1PosterConfig()
+ const base = defaultPosterForSide(s)
const loaded = raw && typeof raw === 'object' ? raw : {}
const lines = Array.isArray(loaded.lines)
? loaded.lines.map((l) => String(l ?? ''))
@@ -205,18 +269,20 @@ export function normalizePosterConfig(raw, side = 1) {
}
}
-/** 归一化为 { 1, 2 };兼容旧 male/female 与扁平数据 */
+/** 归一化为 { 1, 2, 3 };兼容旧 male/female 与扁平数据 */
export function normalizePosterBundle(raw) {
const loaded = raw && typeof raw === 'object' ? raw : {}
if (isLegacyFlatPoster(loaded)) {
return {
'1': normalizePosterConfig(loaded, 1),
'2': defaultSide2PosterConfig(),
+ '3': defaultSide3PosterConfig(),
}
}
return {
'1': normalizePosterConfig(pickSideSource(loaded, 1) || {}, 1),
'2': normalizePosterConfig(pickSideSource(loaded, 2) || {}, 2),
+ '3': normalizePosterConfig(pickSideSource(loaded, 3) || {}, 3),
}
}
diff --git a/vite-tailwindcss/src/router/index.js b/vite-tailwindcss/src/router/index.js
index 5373b0e..20ac881 100644
--- a/vite-tailwindcss/src/router/index.js
+++ b/vite-tailwindcss/src/router/index.js
@@ -24,6 +24,12 @@ const routes = [
component: () => import('@/views/hb/index.vue'),
meta: { posterSide: 2 },
},
+ {
+ path: '/hb-hm',
+ name: 'PosterReturnBanquet',
+ component: () => import('@/views/hb/index.vue'),
+ meta: { posterSide: 3 },
+ },
{
path: '/login',
name: 'Login',
diff --git a/vite-tailwindcss/src/views/admin/AdminEditor.vue b/vite-tailwindcss/src/views/admin/AdminEditor.vue
index c3869fc..f349d50 100644
--- a/vite-tailwindcss/src/views/admin/AdminEditor.vue
+++ b/vite-tailwindcss/src/views/admin/AdminEditor.vue
@@ -19,8 +19,9 @@
- 男方(1) /hb-n
- 女方(2) /hb-nv
+
+ {{ s.label }}({{ s.key }}) {{ s.route }}
+
@@ -410,23 +411,22 @@
-
+
-
-
+
- {{ posterSideTab === '2' ? '2 · 女方' : '1 · 男方' }}
- 路由 {{ posterSideTab === '2' ? '/hb-nv' : '/hb-n' }}
+ {{ currentPosterSide.key }} · {{ currentPosterSide.label }}
+ 路由 {{ currentPosterSide.route }}
@@ -475,13 +475,18 @@
-
+
不配置时,二维码中心为圆圈大「囍」字
@@ -490,15 +495,15 @@
-
+
-
+
@@ -561,7 +566,7 @@
保存海报配置
- 打开 {{ posterSideTab === '2' ? '/hb-nv' : '/hb-n' }} 预览
+ 打开 {{ currentPosterSide.route }} 预览
@@ -909,7 +914,7 @@ import { resolveDanmakuSwatch } from '@/utils/danmakuColors'
import { getSlotResizeSpecs, resizeSlotToFile, probeResizedMeta, metaHasFileSize } from '@/composables/resizeImage'
import {
defaultPosterBundle, normalizePosterBundle, normalizePosterConfig,
- LOADING_EFFECTS, ENTER_EFFECTS, POSTER_SIDES, POSTER_TEMPLATES, MOBILE_LAYOUTS, sideKey,
+ LOADING_EFFECTS, ENTER_EFFECTS, POSTER_SIDES, POSTER_TEMPLATES, MOBILE_LAYOUTS, sideKey, posterSideMeta,
} from '@/composables/posterDefaults'
echarts.use([PieChart, TooltipComponent, LegendComponent, CanvasRenderer])
@@ -970,6 +975,17 @@ const uploadCfg = reactive(defaultUploadConfig())
const posterSideTab = ref('1')
const posterBundle = reactive(defaultPosterBundle())
const posterCfg = computed(() => posterBundle[sideKey(posterSideTab.value)] || posterBundle['1'])
+const currentPosterSide = computed(() => posterSideMeta(posterSideTab.value))
+const posterNameLabels = computed(() => {
+ const s = sideKey(posterSideTab.value)
+ if (s === '2') {
+ return { left: '女儿称谓 / 姓名', right: '女婿称谓 / 姓名', leftPh: '女儿:', rightPh: '女婿:' }
+ }
+ if (s === '3') {
+ return { left: '新郎称谓 / 姓名', right: '新娘称谓 / 姓名', leftPh: '新郎:', rightPh: '新娘:' }
+ }
+ return { left: '儿子称谓 / 姓名', right: '儿媳称谓 / 姓名', leftPh: '儿子:', rightPh: '儿媳:' }
+})
const posterLinesText = computed({
get: () => {
const lines = posterCfg.value?.lines
@@ -1415,6 +1431,7 @@ async function loadPosterConfig() {
const bundle = normalizePosterBundle(res.data || {})
posterBundle['1'] = bundle['1']
posterBundle['2'] = bundle['2']
+ posterBundle['3'] = bundle['3']
} catch (e) {
message.error(e?.message || '海报配置加载失败')
} finally {
@@ -1430,6 +1447,7 @@ async function onSavePosterConfig() {
const bundle = normalizePosterBundle(res.data || payload)
posterBundle['1'] = bundle['1']
posterBundle['2'] = bundle['2']
+ posterBundle['3'] = bundle['3']
message.success('海报配置已保存')
} catch (e) {
message.error(e?.message || '海报配置保存失败')
diff --git a/vite-tailwindcss/src/views/hb/index.vue b/vite-tailwindcss/src/views/hb/index.vue
index 1315ba3..d811f30 100644
--- a/vite-tailwindcss/src/views/hb/index.vue
+++ b/vite-tailwindcss/src/views/hb/index.vue
@@ -104,7 +104,7 @@ import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import html2canvas from 'html2canvas'
import { getPosterConfig } from '@/api/wedding'
-import { normalizePosterConfig, resolvePosterSide } from '@/composables/posterDefaults'
+import { normalizePosterConfig, posterSideMeta, resolvePosterSide } from '@/composables/posterDefaults'
import { renderFestiveQr } from '@/composables/renderFestiveQr'
import ClassicPoster from '@/views/hb/templates/ClassicPoster.vue'
import SplitPoster from '@/views/hb/templates/SplitPoster.vue'
@@ -204,7 +204,7 @@ async function savePosterImage() {
ignoreElements: (node) => !!node?.hasAttribute?.('data-html2canvas-ignore'),
})
const a = document.createElement('a')
- a.download = `婚礼海报-${cfg.side === 2 ? '女方' : '男方'}-${Date.now()}.png`
+ a.download = `婚礼海报-${posterSideMeta(cfg.side).label}-${Date.now()}.png`
a.href = canvas.toDataURL('image/png')
a.click()
} catch (e) {