qs_xinchun2026_h5/components/SinglePageContainer.vue

1248 lines
31 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
import { useSceneStore } from '../store/scene'
import { useCollectionStore } from '../store/collection'
import LongImageViewer from './LongImageViewer.vue'
import MediaPlayer from './MediaPlayer.vue'
import QianmenScene from './QianmenScene.vue'
import DongzhimenScene from './DongzhimenScene.vue'
import LongfusiScene from './LongfusiScene.vue'
import WangfujingScene from './WangfujingScene.vue'
import ChongwenScene from './ChongwenScene.vue'
import EndPage from './EndPage.vue'
const sceneStore = useSceneStore()
const collectionStore = useCollectionStore()
// 当前活动场景索引
const activeSceneIndex = ref(0)
// 标题图片是否已显示过(用于保持显示状态)
const titleImageShown = ref(false)
// 滑动容器引用
const scrollContainer = ref(null)
// 是否在滚动中
const isScrolling = ref(false)
// 首页 section ID用于 scroll-into-view
const homeSectionId = ref('')
// 页面是否准备就绪(滚动完成)
const isPageReady = ref(false)
// 前门板块高度
const qianmenSceneHeight = ref(0)
// 前门板块引用
const qianmenSceneRef = ref(null)
// 是否显示抽奖留资弹窗
const showLotteryForm = ref(false)
// 是否显示AI春联生成弹窗
const showAICoupletForm = ref(false)
// 是否显示春联展示页面
const showCoupletDisplay = ref(false)
// AI春联关键词
const coupletKeyword = ref('')
// 生成的春联
const generatedCouplet = ref(null)
// 抽奖表单数据
const lotteryForm = ref({
name: '',
phone: '',
address: ''
})
// 推荐关键词
const recommendedKeywords = ref(['吉祥', '如意', '平安', '健康', '幸福', '快乐', '富贵', '安康'])
// 场景数据数组
// 顺序:结束页 → 东直门 → 隆福寺 → 王府井 → 崇文门 → 前门 → 首页
const scenes = ref([
{
id: 'end',
name: '结束页',
description: '恭喜完成全部场景游览'
},
{
id: 'dongzhimen',
name: '东直门商圈',
description: '京城东部的交通枢纽和商业中心',
image: 'https://picsum.photos/id/1019/750/1600',
interactionTip: '移动烤鸭,领取团圆福筷',
collectedItem: '团圆福筷'
},
{
id: 'longfusi',
name: '隆福寺商圈',
description: '传统文化与现代艺术融合的潮流地标',
image: 'https://picsum.photos/id/1018/750/1600',
interactionTip: '点击文创物品,点亮文化福灯',
collectedItem: '文化福灯'
},
{
id: 'wangfujing',
name: '王府井商圈',
description: '北京最繁华的商业中心之一',
image: 'https://picsum.photos/id/1018/750/1600',
interactionTip: '双指放大,收集金袋福卡',
collectedItem: '金袋福卡'
},
{
id: 'chongwen',
name: '崇文门商圈',
description: '融合传统文化与现代商业的活力区域',
image: 'https://picsum.photos/id/1016/750/1600',
interactionTip: '滑动探索商圈,收集国潮福字',
collectedItem: '国潮福字'
},
{
id: 'qianmen',
name: '前门商圈',
description: '北京最具历史文化底蕴的商圈之一',
image: 'https://picsum.photos/id/1015/750/1600',
thumbnail: 'https://picsum.photos/id/1015/200/100',
interactionTip: '点击京韵大鼓,收集非遗福印',
collectedItem: '非遗福印'
},
{
id: 'home',
name: '首页',
description: '马年新春2026'
}
])
// 交互状态
const sceneInteractiveStates = ref(scenes.value.map(() => false))
// 福印收集状态
const sealCollectedStates = ref(scenes.value.map(() => false))
// 计算当前收集的物品
const collectedItems = computed(() => {
const items = []
scenes.value.forEach((scene, index) => {
if (index > 0 && index < scenes.value.length - 1) {
if (sealCollectedStates.value[index]) {
items.push(scene.collectedItem)
}
}
})
return items
})
// 计算收集进度
const collectionProgress = computed(() => {
const total = scenes.value.length - 1 // 排除结束页
const collected = sealCollectedStates.value.filter((state, index) =>
index > 0 && index < scenes.value.length && state
).length
return Math.round((collected / total) * 100)
})
// 计算福印收集对象(传递给 EndPage
const collectedSeals = computed(() => {
return {
qianmen: sealCollectedStates.value[5] || false, // 前门 (索引 5)
chongwen: sealCollectedStates.value[4] || false, // 崇文门 (索引 4)
wangfujing: sealCollectedStates.value[3] || false, // 王府井 (索引 3)
longfusi: sealCollectedStates.value[2] || false, // 隆福寺 (索引 2)
dongzhimen: sealCollectedStates.value[1] || false // 东直门 (索引 1)
}
})
// 组件挂载后初始化
onMounted(() => {
// 检查并初始化场景交互状态
scenes.value.forEach((scene, index) => {
if (index > 0 && index < scenes.value.length) {
const isCollected = collectionStore.isSceneSealCollected(scene.id)
sealCollectedStates.value[index] = isCollected
}
})
// 添加滚动事件监听 - 使用uniapp的scroll-view事件绑定
// scroll-view 组件通过 @scroll 事件监听滚动,无需手动添加事件监听器
// 页面加载时滚动到最底部(首页)
const scrollToHomePage = () => {
if (!scrollContainer.value) {
console.log('scrollContainer 尚未初始化')
return
}
// 保存当前容器引用,避免异步回调中丢失
const container = scrollContainer.value
console.log('scrollContainer 已初始化:', {
element: container,
scrollHeight: container.scrollHeight,
clientHeight: container.clientHeight,
scrollTop: container.scrollTop,
classList: container.classList
})
// 如果scrollHeight为0说明内容还未渲染
if (container.scrollHeight === 0) {
console.log('内容尚未渲染,延迟重试...')
setTimeout(scrollToHomePage, 100)
return
}
// 首页是最后一个场景,索引为 scenes.value.length - 1
// 应该位于页面最底部
const homeIndex = scenes.value.length - 1
// 方法1: 使用 scroll-into-viewuniapp 原生支持)
console.log('使用 scroll-into-view 滚动到首页')
homeSectionId.value = 'home-section'
// 设置活动场景为首页
activeSceneIndex.value = homeIndex
// 设置活动场景为首页
activeSceneIndex.value = homeIndex
// 验证滚动是否成功,如果不成功,延迟后重试
setTimeout(() => {
if (!container) {
console.log('scrollContainer 已卸载')
return
}
console.log('验证滚动结果:', {
currentScrollTop: container.scrollTop,
scrollHeight: container.scrollHeight,
clientHeight: container.clientHeight,
isAtBottom: Math.abs(container.scrollTop - (container.scrollHeight - container.clientHeight)) < 50
})
// 检查是否已经滚动到底部
const isAtBottom = Math.abs(container.scrollTop - (container.scrollHeight - container.clientHeight)) < 50
if (!isAtBottom) {
console.log('滚动验证失败,使用备用方法重试...')
// 方法2: 使用 scrollTo 或 scrollTop
try {
const targetScrollTop = container.scrollHeight - container.clientHeight
if (typeof container.scrollTo === 'function') {
container.scrollTo({
top: targetScrollTop,
duration: 0
})
} else {
container.scrollTop = targetScrollTop
}
} catch (error) {
console.log('备用方法失败:', error)
container.scrollTop = container.scrollHeight - container.clientHeight
}
// 再次验证后显示容器
setTimeout(() => {
// 标记页面准备就绪,隐藏加载提示并显示页面
isPageReady.value = true
titleImageShown.value = true
console.log('设置 isPageReady = true准备显示页面')
}, 50)
} else {
console.log('成功滚动到首页')
// 滚动成功后显示容器
// 标记页面准备就绪,隐藏加载提示并显示页面
isPageReady.value = true
titleImageShown.value = true
console.log('设置 isPageReady = true准备显示页面')
}
}, 300)
}
// 初始滚动到首页 - 延迟更长时间确保内容渲染
setTimeout(scrollToHomePage, 800)
// 额外的确保措施,等待更长时间后再次尝试
setTimeout(scrollToHomePage, 1500)
})
// 处理滚动事件(从下往上滑动)
const handleScroll = (event) => {
if (isScrolling.value) return
// 在uniapp中scroll-view的scroll事件传递的是event.detail对象
const scrollTop = event.detail.scrollTop
const viewportHeight = window.innerHeight
const totalHeight = scrollContainer.value.scrollHeight
const totalScenes = scenes.value.length
// 计算当前活动场景(从下往上滚动)
// 首页在底部索引6向上滑动依次看到前门(5)、崇文门(4)、王府井(3)、隆福寺(2)、东直门(1)、结束页(0)
const distanceFromBottom = totalHeight - scrollTop - viewportHeight
const sceneIndexFromBottom = Math.round(distanceFromBottom / viewportHeight)
// 将索引转换为正确的场景顺序
// sceneIndexFromBottom: 0=首页, 1=前门, 2=崇文门, 3=王府井, 4=隆福寺, 5=东直门, 6=结束页
const sceneIndex = totalScenes - 1 - sceneIndexFromBottom
// 确保索引在有效范围内
const clampedIndex = Math.max(0, Math.min(sceneIndex, totalScenes - 1))
if (clampedIndex !== activeSceneIndex.value) {
activeSceneIndex.value = clampedIndex
// 激活当前场景
if (activeSceneIndex.value > 0 && activeSceneIndex.value < scenes.value.length - 1) {
sceneStore.activateScene(scenes.value[activeSceneIndex.value].id)
}
// 如果滚动到首页,标记标题图片已显示
if (clampedIndex === 6) {
titleImageShown.value = true
}
}
}
// 滚动到指定场景(从下往上滑动逻辑)
const scrollToScene = (index) => {
if (!scrollContainer.value || isScrolling.value) return
isScrolling.value = true
const viewportHeight = window.innerHeight
const totalHeight = scrollContainer.value.scrollHeight
const totalScenes = scenes.value.length
// 计算目标滚动位置(从下往上滚动)
// index=0: 结束页(最顶部)
// index=6: 首页(最底部)
const targetScrollTop = totalHeight - (totalScenes - index) * viewportHeight
scrollContainer.value.scrollTo({
top: targetScrollTop,
duration: 1000
})
setTimeout(() => {
isScrolling.value = false
}, 1000)
}
// 收集福印
const collectSeal = (index) => {
if (index < 1 || index >= scenes.value.length - 1) return
const scene = scenes.value[index]
const success = collectionStore.collectSealBySceneId(scene.id)
if (success) {
sealCollectedStates.value[index] = true
uni.showToast({
title: `恭喜获得${scene.name}${scene.collectedItem}`,
duration: 2000
})
}
}
// 打开抽奖留资弹窗
const openLotteryForm = () => {
showLotteryForm.value = true
}
// 提交抽奖表单
const submitLotteryForm = () => {
// 验证表单
if (!lotteryForm.value.name || !lotteryForm.value.phone || !lotteryForm.value.address) {
uni.showToast({
title: '请填写完整信息',
icon: 'none',
duration: 2000
})
return
}
// 验证手机号
const phoneRegex = /^1[3-9]\d{9}$/
if (!phoneRegex.test(lotteryForm.value.phone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none',
duration: 2000
})
return
}
// 模拟提交
uni.showLoading({
title: '提交中...',
mask: true
})
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '报名成功!我们将在活动结束后通知您抽奖结果',
duration: 2000
})
showLotteryForm.value = false
// 重置表单
lotteryForm.value = {
name: '',
phone: '',
address: ''
}
}, 1500)
}
// 打开AI春联生成弹窗
const openAICoupletForm = () => {
showAICoupletForm.value = true
}
// 选择推荐关键词
const selectRecommendedKeyword = (word) => {
coupletKeyword.value = word
}
// 生成春联
const generateCouplet = () => {
if (!coupletKeyword.value || coupletKeyword.value.length < 2) {
uni.showToast({
title: '请输入两个字的关键词',
icon: 'none',
duration: 2000
})
return
}
uni.showLoading({
title: '生成中...',
mask: true
})
// 模拟AI生成春联
setTimeout(() => {
uni.hideLoading()
// 生成春联(这里使用预设模板)
const coupletTemplates = [
{
top: `${coupletKeyword.value.charAt(0)}气盈门添福寿`,
bottom: `${coupletKeyword.value.charAt(1)}光满院报平安`,
横批: '新春大吉'
},
{
top: `${coupletKeyword.value.charAt(0)}祥如意年年好`,
bottom: `${coupletKeyword.value.charAt(1)}事顺心步步高`,
横批: '万事如意'
},
{
top: `${coupletKeyword.value.charAt(0)}星高照家昌盛`,
bottom: `${coupletKeyword.value.charAt(1)}运亨通福满堂`,
横批: '吉星高照'
}
]
generatedCouplet.value = coupletTemplates[Math.floor(Math.random() * coupletTemplates.length)]
showCoupletDisplay.value = true
showAICoupletForm.value = false
}, 1500)
}
// 返回顶部
const scrollToTop = () => {
scrollToScene(0)
}
// 处理图片加载错误
const handleImageError = (event) => {
event.target.src = 'https://placeholder.pics/svg/640x1136/FDE9DF/FF6B35/2026新春东城商圈'
}
// 处理前门板块高度变化
const handleQianmenHeightChanged = (height) => {
qianmenSceneHeight.value = height
// 这里可以添加逻辑,将所有其他场景的高度设置为与前门板块相同的高度
// 可以通过CSS变量或直接修改DOM元素的高度
document.documentElement.style.setProperty('--scene-height', `${height}px`)
console.log('前门板块高度变化:', height, 'px')
console.log('当前CSS变量:', getComputedStyle(document.documentElement).getPropertyValue('--scene-height'))
}
// 使用scroll-view组件后不再需要手动处理触摸事件
// scroll-view会自动处理触摸滚动
// 组件卸载前清理资源
onUnmounted(() => {
// 无需手动移除事件监听器uniapp会自动处理
})
</script>
<template>
<div class="single-page-wrapper">
<!-- 加载提示 -->
<div class="loading-hint" v-if="!isPageReady">
<div class="loading-spinner"></div>
<p>正在加载...</p>
</div>
<scroll-view
class="single-page-container"
:class="{ 'visible': isPageReady }"
ref="scrollContainer"
scroll-y="true"
scroll-with-animation="true"
enhanced="true"
refresher-enabled="false"
show-scrollbar="false"
@scroll="handleScroll"
:scroll-into-view="homeSectionId"
>
<!-- 结束页 -->
<EndPage
:is-active="activeSceneIndex === 0"
:title="scenes[0].description"
:collection-progress="collectionProgress"
:collected-count="collectedItems.length"
:total-count="scenes.length - 1"
:collected-seals="collectedSeals"
@lottery="openLotteryForm"
@couplet="openAICoupletForm"
@restart="scrollToTop"
/>
<!-- 东直门商圈 -->
<DongzhimenScene
:active="activeSceneIndex === 1"
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
@collect-seal="collectSeal(1)"
/>
<!-- 隆福寺商圈 -->
<LongfusiScene
:active="activeSceneIndex === 2"
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
@collect-seal="collectSeal(2)"
/>
<!-- 王府井商圈 -->
<WangfujingScene
:active="activeSceneIndex === 3"
:scroll-position="scrollContainer?.value?.scrollTop || 0"
@collect-seal="collectSeal(3)"
/>
<!-- 崇文门商圈 -->
<ChongwenScene
:active="activeSceneIndex === 4"
:scroll-position="scrollContainer?.value?.scrollTop || 0"
@collect-seal="collectSeal(4)"
/>
<!-- 前门商圈 -->
<QianmenScene
:active="activeSceneIndex === 5"
:scroll-position="scrollContainer?.value?.scrollTop || 0"
@collect-seal="collectSeal(5)"
@height-changed="handleQianmenHeightChanged"
/>
<!-- 新年文案模块 -->
<section class="new-year-section">
<div class="new-year-content">
<img
src="/static/images/main_text.png"
alt="新年文案"
class="new-year-text"
/>
</div>
</section>
<!-- 首页 -->
<section id="home-section" class="scene-section home-section" :class="{ 'active': activeSceneIndex === 6 }">
<!-- 背景图片层 -->
<div class="home-bg">
<img
src="/static/bg/bg_main.jpg"
alt="2026新春东城商圈"
class="bg-image"
@error="handleImageError"
/>
</div>
<!-- 标题图片 -->
<img
src="/static/images/img_title.png"
alt="马年新春2026"
class="title-image"
:class="{ 'title-image-active': titleImageShown }"
/>
</section>
<!-- 向上滑动提示 -->
<!-- <div class="scroll-tip-bottom">
<div class="tip-icon"></div>
<p>向上滑动探索商圈</p>
</div> -->
<!-- 抽奖留资弹窗 -->
<div class="form-modal" v-if="showLotteryForm">
<div class="modal-overlay" @click="showLotteryForm = false"></div>
<div class="modal-content">
<div class="modal-header">
<h3>参与抽奖</h3>
<button class="close-btn" @click="showLotteryForm = false">×</button>
</div>
<div class="modal-body">
<p class="modal-description">请填写以下信息参与线下抽奖活动</p>
<div class="form-group">
<label>姓名</label>
<input v-model="lotteryForm.name" type="text" placeholder="请输入您的姓名" class="form-input" />
</div>
<div class="form-group">
<label>联系电话</label>
<input v-model="lotteryForm.phone" type="tel" placeholder="请输入您的手机号" class="form-input" />
</div>
<div class="form-group">
<label>邮寄地址</label>
<textarea v-model="lotteryForm.address" placeholder="请输入您的邮寄地址" class="form-textarea" rows="3"></textarea>
</div>
<div class="form-actions">
<button class="submit-btn" @click="submitLotteryForm">提交报名</button>
</div>
</div>
</div>
</div>
<!-- AI春联生成弹窗 -->
<div class="form-modal" v-if="showAICoupletForm">
<div class="modal-overlay" @click="showAICoupletForm = false"></div>
<div class="modal-content">
<div class="modal-header">
<h3>AI春联生成</h3>
<button class="close-btn" @click="showAICoupletForm = false">×</button>
</div>
<div class="modal-body">
<p class="modal-description">输入两个字的关键词,生成个性化春联</p>
<div class="form-group">
<input v-model="coupletKeyword" type="text" placeholder="请输入关键词(如:吉祥、如意)" maxlength="2" class="keyword-input" />
</div>
<div class="recommended-keywords">
<span v-for="word in recommendedKeywords" :key="word" @click="selectRecommendedKeyword(word)" class="keyword-tag">{{ word }}</span>
</div>
<div class="form-actions">
<button class="submit-btn" :disabled="!coupletKeyword || coupletKeyword.length < 2" @click="generateCouplet">生成春联</button>
</div>
</div>
</div>
</div>
<!-- 春联展示页面 -->
<div class="couplet-display-modal" v-if="showCoupletDisplay">
<div class="modal-overlay" @click="showCoupletDisplay = false"></div>
<div class="modal-content couplet-content">
<div class="modal-header">
<h3>您的专属春联</h3>
<button class="close-btn" @click="showCoupletDisplay = false">×</button>
</div>
<div class="modal-body">
<div class="couplet-display">
<div class="couplet-item top-couplet">{{ generatedCouplet.top }}</div>
<div class="couplet-item bottom-couplet">{{ generatedCouplet.bottom }}</div>
<div class="couplet-item横批">{{ generatedCouplet.横批 }}</div>
</div>
<div class="collected-items-section" v-if="collectedItems.length > 0">
<h4 class="items-title">您收集的福印:</h4>
<div class="collected-items">
<span v-for="(item, index) in collectedItems" :key="index" class="item-tag"><3E><> {{ item }}</span>
</div>
</div>
<div class="couplet-actions">
<button class="share-btn" @click="uni.showToast({title: '分享功能开发中...', icon: 'none', duration: 2000})"><3E><> 分享春联</button>
<button class="close-couplet-btn" @click="showCoupletDisplay = false"> 完成</button>
</div>
</div>
</div>
</div>
</scroll-view>
</div>
</template>
<style scoped>
.single-page-wrapper {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
/* 加载提示样式 */
.loading-hint {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
background: rgba(253, 233, 223, 0.95);
padding: 30px 40px;
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #FF6B35;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
.loading-hint p {
color: #FF6B35;
font-size: 16px;
font-weight: 500;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.single-page-container {
position: relative;
width: 100%;
height: 100%;
overflow-y: scroll; /* 改为 scroll 确保可以滚动 */
overflow-x: hidden;
-webkit-overflow-scrolling: touch; /* 启用iOS平滑滚动 */
/* 使用 visibility 而不是 opacity确保完全不可见且不可交互 */
visibility: hidden;
touch-action: pan-y; /* 允许垂直方向的触摸滚动 */
user-select: none; /* 防止触摸时文本被选中 */
}
.single-page-container.visible {
visibility: visible;
/* 添加淡入效果 */
animation: fadeIn 0.4s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* ========== 隐藏滚动条样式(所有浏览器)========== */
/* WebKit 浏览器Chrome、Safari、新版 Edge*/
.single-page-container::-webkit-scrollbar {
width: 0 !important;
height: 0 !important;
display: none !important;
}
.single-page-container::-webkit-scrollbar-track {
background: transparent !important;
}
.single-page-container::-webkit-scrollbar-thumb {
background: transparent !important;
}
.single-page-container::-webkit-scrollbar-corner {
background: transparent !important;
}
/* Firefox */
.single-page-container {
scrollbar-width: none !important;
}
/* IE 和旧版 Edge */
.single-page-container {
-ms-overflow-style: none !important;
}
/* 通用样式 */
.single-page-container {
scrollbar-color: transparent transparent !important;
}
/* 场景部分样式 */
.scene-section {
position: relative;
width: 100%;
height: var(--scene-height, 100vh);
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
}
.scene-section.active {
/* 当前活动场景样式 */
}
.scene-content {
width: 100%;
max-width: 640px;
padding: 20px;
box-sizing: border-box;
}
.scene-title {
font-size: 28px;
color: #333;
margin-bottom: 10px;
text-align: center;
}
.scene-description {
font-size: 16px;
color: #666;
margin-bottom: 20px;
text-align: center;
}
.long-image-container {
position: relative;
width: 100%;
height: 50vh;
margin-bottom: 20px;
background-color: #000;
border-radius: 10px;
overflow: hidden;
}
.interaction-tip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 15px 25px;
border-radius: 10px;
z-index: 10;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: translate(-50%, -50%) scale(1);
}
50% {
transform: translate(-50%, -50%) scale(1.1);
}
100% {
transform: translate(-50%, -50%) scale(1);
}
}
.tip-icon {
font-size: 36px;
margin-bottom: 5px;
}
.seal-collect-tip {
position: absolute;
top: 20px;
right: 20px;
display: flex;
align-items: center;
background-color: rgba(255, 107, 53, 0.9);
color: #fff;
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
z-index: 10;
}
.seal-icon {
font-size: 24px;
margin-right: 5px;
}
.media-section {
margin-top: 20px;
}
/* 弹窗样式 */
.form-modal,
.couplet-display-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
}
.modal-content {
position: relative;
background-color: white;
border-radius: 15px;
padding: 20px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
z-index: 1001;
}
.close-btn {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.modal-header h3 {
margin: 0;
font-size: 20px;
color: #333;
}
.modal-body {
padding-bottom: 10px;
}
.modal-description {
font-size: 16px;
color: #666;
margin-bottom: 20px;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
.form-input,
.keyword-input,
.form-textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.recommended-keywords {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
justify-content: center;
}
.keyword-tag {
padding: 8px 15px;
background-color: #f0f0f0;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.keyword-tag:hover {
background-color: #e0e0e0;
}
.form-actions {
text-align: center;
}
.submit-btn {
padding: 12px 30px;
font-size: 16px;
background-color: #FF6B35;
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
}
.submit-btn:hover:not(:disabled) {
background-color: #E65A2B;
transform: translateY(-2px);
}
.submit-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.modal-video {
width: 100%;
max-height: 70vh;
border-radius: 10px;
}
/* 春联展示样式 */
.couplet-content {
max-width: 600px;
}
.couplet-display {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 15px;
}
.couplet-item {
font-family: 'SimSun', 'STSong', serif;
font-size: 24px;
line-height: 1.8;
margin: 10px 0;
}
.top-couplet {
transform: rotate(90deg);
transform-origin: right center;
display: inline-block;
margin-right: 40px;
}
.bottom-couplet {
transform: rotate(-90deg);
transform-origin: left center;
display: inline-block;
margin-left: 40px;
}
.横批 {
font-size: 28px;
font-weight: bold;
color: #FF6B35;
margin-top: 20px;
}
.collected-items-section {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.items-title {
font-size: 16px;
color: #333;
margin-bottom: 15px;
}
.collected-items {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
}
.item-tag {
padding: 8px 15px;
background-color: #fff3e0;
border: 1px solid #ffb74d;
border-radius: 20px;
font-size: 14px;
color: #e65100;
}
.couplet-actions {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 20px;
}
.share-btn {
padding: 10px 20px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.share-btn:hover {
background-color: #1976D2;
}
.close-couplet-btn {
padding: 10px 20px;
background-color: #9E9E9E;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.close-couplet-btn:hover {
background-color: #757575;
}
/* 首页样式 */
.home-section {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
height: 100vh;
overflow: hidden;
}
.home-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.bg-image {
width: 100%;
height: 100%;
object-fit: cover; /* 裁切方式适配不同高度 */
object-position: center center;
}
/* 向上滑动提示样式 */
.scroll-tip-bottom {
position: fixed;
bottom: 50px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
animation: bounce 1.5s infinite;
z-index: 100;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateX(-50%) translateY(0);
}
40% {
transform: translateX(-50%) translateY(-10px);
}
60% {
transform: translateX(-50%) translateY(-5px);
}
}
/* 新年文案模块样式 */
.new-year-section {
width: 100%;
background-color: #f94332;
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 15px 0;
}
.new-year-content {
text-align: center;
}
.new-year-text {
display: inline-block;
width: 546rpx;
height: auto;
object-fit: contain;
}
/* 标题图片样式 */
.title-image {
width: 80%;
max-width: 400px;
height: auto;
margin: 40rpx auto 20px;
display: block;
transform: scale(0);
transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
z-index: 2;
}
.title-image-active {
transform: scale(1);
}
.scroll-tip {
position: absolute;
bottom: 50px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
animation: bounce 1.5s infinite;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateX(-50%) translateY(0);
}
40% {
transform: translateX(-50%) translateY(-20px);
}
60% {
transform: translateX(-50%) translateY(-10px);
}
}
/* 响应式设计 */
@media (max-width: 480px) {
.scene-title {
font-size: 24px;
}
.scene-description {
font-size: 14px;
}
.couplet-item {
font-size: 20px;
}
.top-couplet, .bottom-couplet {
margin-right: 20px;
margin-left: 20px;
}
}
</style>