更新页面效果

This commit is contained in:
李琦
2026-07-31 15:03:46 +08:00
parent 27f084a3c0
commit 4eedf7b1f1
2 changed files with 153 additions and 59 deletions

View File

@@ -78,6 +78,17 @@
<a-form-item label="竖排文案 · 下联">
<a-input v-model:value="cfg.formalText2" placeholder="如 良缘永结 匹配同称" />
</a-form-item>
<a-form-item label="邀请文案页高度">
<div class="flex gap-2 items-center">
<a-select :value="heightFieldSelectValue(cfg.formalPageHeight)" @change="(v) => onHeightFieldModeChange(cfg, 'formalPageHeight', v)" class="flex-1">
<a-select-option v-for="opt in HEIGHT_PRESETS" :key="opt.value" :value="opt.value">{{ opt.label }}</a-select-option>
</a-select>
<a-input-number v-if="heightFieldSelectValue(cfg.formalPageHeight) === '__custom__'"
:value="customVhValue(cfg.formalPageHeight)" @change="(v) => onHeightFieldCustomVh(cfg, 'formalPageHeight', v)"
:min="20" :max="200" addon-after="vh" class="w-[150px]" />
</div>
<div class="text-[11px] text-[#8A8680] mt-1">控制前台竖排邀请文案页的展示高度默认满屏</div>
</a-form-item>
</a-form>
</a-card>
</a-tab-pane>
@@ -398,7 +409,7 @@ function defaultConfig() {
return {
groom: '李明', bride: '王华', date: '2026-05-20', lunar: '农历四月初四 星期三',
hotel: '杭州星光万丽大酒店', address: '杭州市西湖区星光大道88号\n3楼大宴会厅',
formalText1: '两姓联姻 一堂缔约', formalText2: '良缘永结 匹配同称',
formalText1: '两姓联姻 一堂缔约', formalText2: '良缘永结 匹配同称', formalPageHeight: '100vh',
heroImg: 'https://images.unsplash.com/photo-1583939003579-730e3918a45a?auto=format&fit=crop&w=800&q=80',
endImg: 'https://images.unsplash.com/photo-1519741497674-611481863552?auto=format&fit=crop&w=800&q=80',
musicList: [{ url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', name: '背景音乐 1' }],
@@ -446,19 +457,31 @@ const HEIGHT_PRESETS = [
{ label: '自定义', value: '__custom__' },
]
function heightSelectValue(page) {
const match = HEIGHT_PRESETS.find((p) => p.value === page.height)
return match ? match.value : '__custom__'
return heightFieldSelectValue(page.height)
}
function onHeightModeChange(page, val) {
if (val !== '__custom__') page.height = val
}
function customVh(page) {
const m = /^([\d.]+)vh$/.exec(page.height || '')
return m ? Number(m[1]) : 100
return customVhValue(page.height)
}
function onCustomVh(page, val) {
page.height = (val || 100) + 'vh'
}
function heightFieldSelectValue(value) {
const match = HEIGHT_PRESETS.find((p) => p.value === value)
return match ? match.value : '__custom__'
}
function customVhValue(value) {
const m = /^([\d.]+)vh$/.exec(value || '')
return m ? Number(m[1]) : 100
}
function onHeightFieldModeChange(target, key, val) {
if (val !== '__custom__') target[key] = val
}
function onHeightFieldCustomVh(target, key, val) {
target[key] = (val || 100) + 'vh'
}
function ensurePageShape(p) {
if (p.type === 'single') { p.img1 = p.img1 || ''; delete p.img2; delete p.images }
@@ -664,6 +687,7 @@ onMounted(async () => {
}
loaded.endImg = loaded.endImg || loaded.heroImg || cfg.endImg
Object.assign(cfg, loaded)
cfg.formalPageHeight = cfg.formalPageHeight || '100vh'
cfg.photoPages = (cfg.photoPages || []).map(ensurePageShape)
setActivePhotoIndex(currentPhotoIndex.value)
// 数值型参数兜底(兼容旧配置)

View File

@@ -1,22 +1,28 @@
<template>
<div class="wrapper relative w-full h-[100vh] bg-[#Fdfbf7] overflow-hidden text-[#2c2c2c]">
<!-- 入场揭幕帘幕雅致双开门 + 徽标 -->
<!-- 入场揭幕帘幕请柬封面 -->
<div v-show="showIntro" class="curtain-root" :class="{ 'is-opening': introOpening }" @click="openInvitation" @transitionend="onIntroTransitionEnd">
<div class="curtain-bg" :style="curtainBg"></div>
<div class="curtain-veil"></div>
<div class="curtain-door curtain-door-left"><span class="door-sheen"></span></div>
<div class="curtain-door curtain-door-right"><span class="door-sheen"></span></div>
<div class="curtain-emblem">
<div class="emblem-inner">
<span class="emblem-floral"></span>
<span class="emblem-kicker">WEDDING INVITATION</span>
<div class="emblem-names">{{ data.groom }}<span class="amp">&amp;</span>{{ data.bride }}</div>
<div class="emblem-rule"></div>
<div class="emblem-date">{{ data.date }}</div>
<div class="emblem-invite-title">岁序更替</div>
<div class="emblem-invite-copy">始于初见止于终老时光煮雨人事静好盼与您分享我们的幸福时刻</div>
<div class="emblem-hint"><span class="dot"></span>轻触开启请柬</div>
<div class="invite-cover">
<span class="cover-filigree cover-filigree-top" aria-hidden="true"></span>
<span class="cover-filigree cover-filigree-bottom" aria-hidden="true"></span>
<div class="emblem-inner">
<span class="emblem-kicker">WEDDING INVITATION</span>
<div class="emblem-seal" aria-hidden="true">
<i class="fa-solid fa-feather-pointed"></i>
</div>
<div class="emblem-names">{{ data.groom }}<span class="amp">&amp;</span>{{ data.bride }}</div>
<div class="emblem-rule"></div>
<div class="emblem-date">{{ data.date }}</div>
<div class="emblem-invite-title">岁序更替</div>
<div class="emblem-invite-copy">始于初见止于终老时光煮雨人事静好盼与您分享我们的幸福时刻</div>
<div class="emblem-hint"><span class="dot"></span>轻触开启请柬</div>
</div>
</div>
</div>
</div>
@@ -100,7 +106,7 @@
</div>
<!-- 2. 竖排文案页 -->
<div class="page-section bg-[#F7F6F2] items-center px-8 relative justify-center" :class="data.scrollMode === 'snap' ? 'snap-start' : ''">
<div class="page-section bg-[#F7F6F2] items-center px-8 relative justify-center" :class="data.scrollMode === 'snap' ? 'snap-start' : ''" :style="formalPageHeightStyle">
<div class="absolute top-0 left-0 w-32 h-32 border-l-[0.5px] border-t-[0.5px] border-[#a88c6b]/30 m-6"></div>
<div class="absolute bottom-0 right-0 w-32 h-32 border-r-[0.5px] border-b-[0.5px] border-[#a88c6b]/30 m-6"></div>
<div v-reveal="'blur'" class="flex flex-col items-center w-full">
@@ -175,10 +181,10 @@
<button class="map-cta" type="button" @click="openNavigation">
<span class="map-cta-icon"><i class="fa-solid fa-location-dot"></i></span>
<span class="map-cta-copy">
<b>导航到酒店</b>
<b>查看酒店位置</b>
<small>{{ data.hotel }}</small>
</span>
<i class="fa-solid fa-chevron-right map-cta-arrow"></i>
<span class="map-cta-arrow">导航</span>
</button>
</div>
</div>
@@ -244,6 +250,7 @@ const DEFAULTS = {
freeScrollInterval: 50, // 自由滚动速度:滚动间隔 ms越小越快
pageSwitchDuration: 800, // 页面切换速度:翻页动画时长 ms
firstScreenDuration: 4500, // 首屏停留时间:首屏效果结束后再开始自动滚动
formalPageHeight: '100vh',
}
const data = ref({
@@ -289,11 +296,13 @@ const firstScreenDuration = computed(() => {
const curtainBg = computed(() => ({ backgroundImage: `url(${data.value.heroImg})` }))
const endImage = computed(() => data.value.endImg || data.value.heroImg)
// 相册页页面高度:支持 100vh / 80vh / 60vh / auto / 自定义 vh
const pageHeightStyle = (page) => {
const h = page.height || '100vh'
// 页面高度:支持 100vh / 80vh / 60vh / auto / 自定义 vh
const sectionHeightStyle = (height) => {
const h = height || '100vh'
return h === 'auto' ? { minHeight: 'auto' } : { minHeight: h }
}
const formalPageHeightStyle = computed(() => sectionHeightStyle(data.value.formalPageHeight))
const pageHeightStyle = (page) => sectionHeightStyle(page.height)
const closeDrawer = () => { isDrawerOpen.value = false }
const handleScroll = () => {
@@ -493,6 +502,7 @@ onMounted(async () => {
loaded.firstScreenDuration = Number.isFinite(Number(loaded.firstScreenDuration))
? Number(loaded.firstScreenDuration)
: DEFAULTS.firstScreenDuration
loaded.formalPageHeight = loaded.formalPageHeight || DEFAULTS.formalPageHeight
data.value = { ...data.value, ...DEFAULTS, ...loaded }
}
} catch (e) { /* 使用默认配置 */ }
@@ -580,48 +590,107 @@ onUnmounted(() => {
}
/* ---------- 入场揭幕(雅致象牙金) ---------- */
.curtain-root { position: fixed; inset: 0; z-index: 100; overflow: hidden; cursor: pointer; font-family: var(--font-sans); background: #fbf6ef; }
.curtain-root { position: fixed; inset: 0; z-index: 100; overflow: hidden; cursor: pointer; font-family: var(--font-sans); background: #fbf6ef; perspective: 1200px; }
.curtain-bg {
position: absolute; inset: -24px; background-size: cover; background-position: center;
filter: blur(16px) brightness(0.7) saturate(1.05); transform: scale(1.12);
filter: blur(18px) brightness(0.68) saturate(1.02); transform: scale(1.12);
}
.curtain-veil {
position: absolute; inset: 0;
background:
radial-gradient(circle at 50% 18%, rgba(255,250,242,0.76), rgba(241,226,207,0.36) 38%, rgba(82,60,42,0.2) 100%),
linear-gradient(180deg, rgba(251,246,239,0.42), rgba(232,214,194,0.28));
}
.curtain-veil { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(251,246,239,0.35), rgba(232,214,194,0.25)); }
.curtain-door {
position: absolute; top: 0; bottom: 0; width: 50%; z-index: 2;
background: linear-gradient(160deg, #fbf6ef 0%, #f5e9dc 45%, #ecd9c4 100%);
box-shadow: inset 0 0 50px rgba(184,154,120,0.12);
background:
linear-gradient(90deg, rgba(92,64,42,0.06), transparent 18%, transparent 78%, rgba(255,255,255,0.28)),
repeating-linear-gradient(90deg, rgba(166,128,87,0.04) 0 1px, transparent 1px 10px),
linear-gradient(160deg, #fbf6ef 0%, #f5e9dc 45%, #ecd9c4 100%);
box-shadow: inset 0 0 58px rgba(184,154,120,0.16), inset 0 0 0 1px rgba(255,255,255,0.42);
transition: transform 1.4s cubic-bezier(0.76, 0, 0.24, 1);
transform-style: preserve-3d;
}
.curtain-door::before { content: ''; position: absolute; top: 0; bottom: 0; width: 1px; background: linear-gradient(180deg, transparent, rgba(184,154,120,0.5), transparent); }
.curtain-door::after {
content: ''; position: absolute; inset: 20px 18px;
border-top: 1px solid rgba(168,140,107,0.14);
border-bottom: 1px solid rgba(168,140,107,0.12);
pointer-events: none;
}
.curtain-door-left { left: 0; }
.curtain-door-right { right: 0; }
.curtain-door-left::before { right: 0; }
.curtain-door-right::before { left: 0; }
.door-sheen { position: absolute; inset: 0; background: linear-gradient(115deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 30%, rgba(255,255,255,0) 70%, rgba(255,255,255,0.25) 100%); }
.curtain-root.is-opening .curtain-door-left { transform: translateX(-100%); }
.curtain-root.is-opening .curtain-door-right { transform: translateX(100%); }
.door-sheen { position: absolute; inset: 0; background: linear-gradient(115deg, rgba(255,255,255,0.56) 0%, rgba(255,255,255,0) 30%, rgba(255,255,255,0) 70%, rgba(255,255,255,0.28) 100%); }
.curtain-root.is-opening .curtain-door-left { transform: translateX(-100%) rotateY(-12deg); }
.curtain-root.is-opening .curtain-door-right { transform: translateX(100%) rotateY(12deg); }
.curtain-emblem {
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 3;
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 3; padding: 28px;
transition: opacity 0.8s ease, transform 0.9s ease; animation: emblemIn 1.1s ease both;
}
.curtain-root.is-opening .curtain-emblem { opacity: 0; transform: scale(1.25); }
.emblem-inner { text-align: center; color: #5b4a36; padding: 10px; }
.emblem-floral { display: block; font-size: 22px; color: #b89a78; margin-bottom: 14px; }
.emblem-kicker { display: block; letter-spacing: 0.6em; font-size: 9px; color: #b89a78; text-transform: uppercase; margin-bottom: 18px; padding-left: 0.6em; font-family: var(--font-display); }
.emblem-names { font-family: var(--font-serif); font-size: 30px; letter-spacing: 0.18em; font-weight: 300; color: #4a3f30; }
.emblem-names .amp { font-style: italic; color: #b89a78; margin: 0 8px; }
.emblem-rule { width: 46px; height: 1px; background: linear-gradient(90deg, transparent, #b89a78, transparent); margin: 18px auto; }
.emblem-date { font-family: var(--font-number); font-size: 11px; letter-spacing: 0.3em; color: #8a7458; }
.invite-cover {
position: relative; width: min(82vw, 360px); min-height: min(72vh, 560px);
display: flex; align-items: center; justify-content: center; padding: 34px 24px;
color: #5b4a36; text-align: center;
background:
linear-gradient(135deg, rgba(255,255,255,0.5), transparent 28%, rgba(183,145,96,0.08) 100%),
repeating-linear-gradient(0deg, rgba(120,88,55,0.025) 0 1px, transparent 1px 7px),
#fbf7ef;
border: 1px solid rgba(168,140,107,0.35);
box-shadow: 0 28px 70px rgba(59,39,22,0.24), inset 0 0 0 10px rgba(255,255,255,0.34), inset 0 0 0 11px rgba(168,140,107,0.14);
}
.invite-cover::before,
.invite-cover::after {
content: ''; position: absolute; pointer-events: none;
}
.invite-cover::before {
inset: 18px; border: 1px solid rgba(168,140,107,0.25);
}
.invite-cover::after {
inset: 28px; border: 1px solid rgba(168,140,107,0.11);
background:
linear-gradient(45deg, transparent 0 49.6%, rgba(168,140,107,0.15) 50%, transparent 50.4%),
linear-gradient(-45deg, transparent 0 49.6%, rgba(168,140,107,0.12) 50%, transparent 50.4%);
opacity: .52;
}
.cover-filigree {
position: absolute; left: 50%; width: 122px; height: 22px; transform: translateX(-50%);
border-top: 1px solid rgba(168,140,107,0.42);
}
.cover-filigree::before,
.cover-filigree::after {
content: ''; position: absolute; top: -4px; width: 26px; height: 8px;
border-top: 1px solid rgba(168,140,107,0.42); border-radius: 50%;
}
.cover-filigree::before { left: 20px; transform: rotate(-18deg); }
.cover-filigree::after { right: 20px; transform: rotate(18deg); }
.cover-filigree-top { top: 42px; }
.cover-filigree-bottom { bottom: 42px; transform: translateX(-50%) rotate(180deg); }
.emblem-inner { position: relative; z-index: 1; width: 100%; text-align: center; color: #5b4a36; padding: 42px 16px 34px; }
.emblem-kicker { display: block; letter-spacing: 0.48em; font-size: 10px; color: #a88c6b; text-transform: uppercase; margin-bottom: 24px; padding-left: 0.48em; font-family: var(--font-display); }
.emblem-seal {
width: 56px; height: 56px; margin: 0 auto 22px; border-radius: 9999px;
display: flex; align-items: center; justify-content: center;
border: 1px solid rgba(168,140,107,0.46); color: #a88c6b;
background: rgba(255,255,255,0.34); box-shadow: inset 0 0 0 5px rgba(168,140,107,0.07);
}
.emblem-seal i { font-size: 18px; }
.emblem-names { font-family: var(--font-serif); font-size: clamp(31px, 8vw, 44px); line-height: 1.22; letter-spacing: 0.14em; font-weight: 500; color: #403527; }
.emblem-names .amp { font-style: italic; color: #b89a78; margin: 0 9px; font-family: var(--font-display); font-weight: 300; }
.emblem-rule { width: 72px; height: 1px; background: linear-gradient(90deg, transparent, #b89a78, transparent); margin: 22px auto 18px; }
.emblem-date { font-family: var(--font-number); font-size: 12px; letter-spacing: 0.34em; padding-left: 0.34em; color: #806a4d; }
.emblem-invite-title {
margin-top: 24px; color: #4a3f30; font-family: var(--font-serif);
font-size: 18px; line-height: 1.35; letter-spacing: 0.14em; font-weight: 500;
margin-top: 30px; color: #4a3f30; font-family: var(--font-brush);
font-size: 24px; line-height: 1.35; letter-spacing: 0.1em; font-weight: 800;
}
.emblem-invite-copy {
max-width: 270px; margin: 12px auto 0; color: #6a5d4a;
font-family: var(--font-kai); font-size: 14px; line-height: 1.95;
letter-spacing: 0.08em;
max-width: 276px; margin: 13px auto 0; color: #5f503d;
font-family: var(--font-kai); font-size: 15px; line-height: 2.05;
letter-spacing: 0.09em; font-weight: 600;
}
.emblem-hint { margin-top: 26px; font-size: 11px; letter-spacing: 0.3em; color: #a08a66;
.emblem-hint { margin-top: 30px; font-size: 12px; letter-spacing: 0.26em; color: #9b825e;
display: flex; align-items: center; justify-content: center; gap: 8px; }
.emblem-hint .dot { width: 6px; height: 6px; border-radius: 9999px; background: #b89a78; animation: hintPulse 1.6s infinite; }
@@ -709,37 +778,38 @@ onUnmounted(() => {
.rsvp-cta-deco { font-size: 13px; opacity: .9; }
.rsvp-cta-line { width: 18px; height: 1px; background: currentColor; opacity: .6; }
.map-cta {
width: 100%; min-height: 58px; border-radius: 12px;
border: 1px solid rgba(168,140,107,0.28);
background:
linear-gradient(135deg, rgba(255,255,255,0.72), rgba(247,246,242,0.42)),
rgba(255,255,255,0.42);
color: #7a6244; cursor: pointer; padding: 9px 12px;
display: grid; grid-template-columns: 34px 1fr 16px; align-items: center; gap: 10px;
text-align: left; box-shadow: 0 8px 22px rgba(110,82,43,0.08);
transition: background .25s ease, border-color .25s ease, color .25s ease, box-shadow .25s ease;
position: relative; width: 100%; min-height: 54px; border: 0;
border-top: 1px solid rgba(168,140,107,0.22);
border-bottom: 1px solid rgba(168,140,107,0.16);
background: rgba(255,255,255,0.2);
color: #7a6244; cursor: pointer; padding: 10px 2px;
display: grid; grid-template-columns: 28px 1fr auto; align-items: center; gap: 10px;
text-align: left;
transition: background .25s ease, border-color .25s ease, color .25s ease;
}
.map-cta:hover {
background: rgba(255,255,255,0.75); border-color: rgba(168,140,107,0.58);
box-shadow: 0 12px 28px rgba(110,82,43,0.14);
background: rgba(255,255,255,0.42);
border-color: rgba(168,140,107,0.34);
}
.map-cta-icon {
width: 34px; height: 34px; border-radius: 10px;
width: 28px; height: 28px; border-radius: 9999px;
display: flex; align-items: center; justify-content: center;
background: rgba(168,140,107,0.13); color: #a88c6b;
border: 1px solid rgba(168,140,107,0.38); color: #a88c6b;
background: rgba(255,255,255,0.28);
}
.map-cta-icon i { font-size: 15px; }
.map-cta-icon i { font-size: 12px; }
.map-cta-copy {
display: flex; flex-direction: column; min-width: 0; gap: 3px;
}
.map-cta-copy b {
font-family: var(--font-serif); font-size: 13px; letter-spacing: 0.16em; font-weight: 600;
font-family: var(--font-serif); font-size: 13px; letter-spacing: 0.2em; font-weight: 600;
}
.map-cta-copy small {
color: #8A8680; font-size: 9px; letter-spacing: 0.08em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.map-cta-arrow {
font-size: 11px; color: #b89a78;
color: #a88c6b; font-size: 10px; letter-spacing: 0.18em; padding: 0 1px 0 10px;
border-left: 1px solid rgba(168,140,107,0.2);
}
/* ---------- 地图选择 ---------- */