1307 lines
40 KiB
Vue
1307 lines
40 KiB
Vue
<script setup>
|
||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
|
||
import { useSceneStore } from '../store/scene'
|
||
import { useCollectionStore } from '../store/collection'
|
||
import { usePlayerStore } from '../store/player'
|
||
import { recordPageVisit, saveUserInfo, generateCoupletPoster } from '../api/api.js'
|
||
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'
|
||
import LotteryFormModal from './LotteryFormModal.vue'
|
||
import AICoupletForm from './AICoupletForm.vue'
|
||
import CoupletDisplay from './CoupletDisplay.vue'
|
||
import LoadingComponent from './LoadingComponent.vue'
|
||
|
||
const sceneStore = useSceneStore()
|
||
const collectionStore = useCollectionStore()
|
||
const playerStore = usePlayerStore()
|
||
|
||
// 当前活动场景索引
|
||
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)
|
||
// 页面访问UUID
|
||
const pageVisitUuid = ref('')
|
||
// 是否已提交用户信息
|
||
const hasSubmittedUserInfo = ref(false)
|
||
// 是否已手动滑动(用于隐藏滑动提示)
|
||
const hasScrolled = ref(false)
|
||
// 标记是否正在自动滚动
|
||
const isAutoScrolling = ref(false)
|
||
// 是否显示加载组件
|
||
const showLoading = ref(true)
|
||
// 是否已完成图片加载
|
||
const isImagesLoaded = ref(false)
|
||
// 音乐播放器实例
|
||
const audioPlayer = ref(null)
|
||
|
||
|
||
// 场景数据数组
|
||
// 顺序:结束页 → 东直门 → 隆福寺 → 王府井 → 崇文门 → 前门 → 首页
|
||
const scenes = ref([
|
||
{
|
||
id: 'end',
|
||
name: '结束页',
|
||
description: '恭喜完成全部场景游览'
|
||
},
|
||
{
|
||
id: 'dongzhimen',
|
||
name: '东直门商圈',
|
||
description: '京城东部的交通枢纽和商业中心',
|
||
collectedItem: '团圆福筷',
|
||
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
|
||
},
|
||
{
|
||
id: 'longfusi',
|
||
name: '隆福寺商圈',
|
||
description: '传统文化与现代艺术融合的潮流地标',
|
||
collectedItem: '文化福灯',
|
||
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
|
||
},
|
||
{
|
||
id: 'wangfujing',
|
||
name: '王府井商圈',
|
||
description: '北京最繁华的商业中心之一',
|
||
collectedItem: '金袋福卡',
|
||
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
|
||
},
|
||
{
|
||
id: 'chongwen',
|
||
name: '崇文门商圈',
|
||
description: '融合传统文化与现代商业的活力区域',
|
||
collectedItem: '国潮福字',
|
||
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
|
||
},
|
||
{
|
||
id: 'qianmen',
|
||
name: '前门商圈',
|
||
description: '北京最具历史文化底蕴的商圈之一',
|
||
collectedItem: '非遗福印',
|
||
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
|
||
},
|
||
{
|
||
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)
|
||
})
|
||
|
||
// 计算是否禁用BGM控制按钮
|
||
const isBgmButtonDisabled = computed(() => {
|
||
return playerStore.isBgmButtonDisabled
|
||
})
|
||
|
||
// 计算福印收集对象(传递给 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)
|
||
}
|
||
})
|
||
|
||
// 处理加载完成事件
|
||
const handleLoadingComplete = () => {
|
||
isImagesLoaded.value = true
|
||
console.log('图片加载完成,准备初始化页面')
|
||
|
||
// 确保 scrollContainer 已初始化并滚动到最下方
|
||
ensureScrollContainerReady()
|
||
}
|
||
|
||
// 确保 scrollContainer 已初始化并滚动到最下方
|
||
const ensureScrollContainerReady = () => {
|
||
// 等待 DOM 渲染完成
|
||
nextTick(() => {
|
||
const checkScrollContainer = () => {
|
||
if (!scrollContainer.value) {
|
||
console.log('scrollContainer 尚未初始化,延迟重试...')
|
||
setTimeout(checkScrollContainer, 100)
|
||
return
|
||
}
|
||
|
||
const container = scrollContainer.value
|
||
console.log('scrollContainer 已初始化:', {
|
||
scrollHeight: container.scrollHeight,
|
||
clientHeight: container.clientHeight
|
||
})
|
||
|
||
// 如果 scrollHeight 为 0,说明内容还未渲染
|
||
if (container.scrollHeight === 0) {
|
||
console.log('内容尚未渲染,延迟重试...')
|
||
setTimeout(checkScrollContainer, 100)
|
||
return
|
||
}
|
||
|
||
// 标记正在自动滚动
|
||
isAutoScrolling.value = true
|
||
|
||
// 设置首页 section ID,使用 scroll-into-view 滚动到首页
|
||
homeSectionId.value = 'home-section'
|
||
console.log('使用 scroll-into-view 滚动到首页')
|
||
|
||
// 设置活动场景为首页
|
||
activeSceneIndex.value = scenes.value.length - 1
|
||
|
||
// 等待 scroll-into-view 生效
|
||
setTimeout(() => {
|
||
// 再次检查并滚动到最下方,确保滚动位置正确
|
||
try {
|
||
const targetScrollTop = container.scrollHeight - container.clientHeight
|
||
if (typeof container.scrollTo === 'function') {
|
||
container.scrollTo({
|
||
top: targetScrollTop,
|
||
duration: 0
|
||
})
|
||
} else {
|
||
container.scrollTop = targetScrollTop
|
||
}
|
||
console.log('已滚动到最下方,scrollTop:', container.scrollTop)
|
||
} catch (error) {
|
||
console.error('滚动失败:', error)
|
||
}
|
||
|
||
// 自动滚动结束
|
||
isAutoScrolling.value = false
|
||
// 强制将 hasScrolled 设置为 false,确保滑动提示能够显示
|
||
hasScrolled.value = false
|
||
console.log('scrollContainer 准备就绪,hasScrolled:', hasScrolled.value)
|
||
}, 300)
|
||
}
|
||
|
||
// 开始检查 scrollContainer
|
||
checkScrollContainer()
|
||
})
|
||
}
|
||
|
||
// 处理开始按钮点击事件
|
||
const handleStart = () => {
|
||
// 隐藏加载组件
|
||
showLoading.value = false
|
||
|
||
// 确保滑动提示能够显示
|
||
hasScrolled.value = false
|
||
console.log('点击开始按钮,hasScrolled:', hasScrolled.value)
|
||
|
||
// 检查并播放背景音乐
|
||
if (!playerStore.isMusicPlaying) {
|
||
console.log('背景音乐未播放,开始播放')
|
||
toggleMusic()
|
||
}
|
||
|
||
// 初始化页面
|
||
initPage()
|
||
}
|
||
|
||
// 初始化页面函数
|
||
const initPage = () => {
|
||
// 记录页面访问
|
||
recordPageVisit({
|
||
user_agent: navigator.userAgent,
|
||
page_name: 'home'
|
||
}).then(res => {
|
||
if (res && res.uuid) {
|
||
pageVisitUuid.value = res.uuid
|
||
console.log('页面访问UUID:', res.uuid)
|
||
}
|
||
// 处理后端返回的场景数据,只更新5个商圈的videoUrl
|
||
if (res && res.scenes && Array.isArray(res.scenes)) {
|
||
console.log('后端返回的场景数据:', res.scenes)
|
||
// 商业区ID列表
|
||
const commercialDistricts = ['dongzhimen', 'longfusi', 'wangfujing', 'chongwen', 'qianmen']
|
||
// 创建场景ID到数据的映射
|
||
const apiScenesMap = {}
|
||
res.scenes.forEach(scene => {
|
||
apiScenesMap[scene.id] = scene
|
||
})
|
||
// 只更新商业区的videoUrl
|
||
scenes.value.forEach((scene) => {
|
||
if (commercialDistricts.includes(scene.id) && apiScenesMap[scene.id]) {
|
||
const apiScene = apiScenesMap[scene.id]
|
||
// 后端返回的字段名是 video_url
|
||
const videoUrl = apiScene.video_url || apiScene.videoUrl
|
||
if (videoUrl) {
|
||
console.log(`更新场景 ${scene.id} 的videoUrl:`, videoUrl)
|
||
scene.videoUrl = videoUrl
|
||
}
|
||
}
|
||
})
|
||
console.log('场景数据更新完成:', scenes.value)
|
||
}
|
||
}).catch(err => {
|
||
console.log('页面访问记录失败:', err)
|
||
})
|
||
|
||
// 检查并初始化场景交互状态
|
||
scenes.value.forEach((scene, index) => {
|
||
if (index > 0 && index < scenes.value.length) {
|
||
const isCollected = collectionStore.isSceneSealCollected(scene.id)
|
||
sealCollectedStates.value[index] = isCollected
|
||
}
|
||
})
|
||
|
||
// 标记页面准备就绪
|
||
titleImageShown.value = true
|
||
isAutoScrolling.value = false
|
||
console.log('页面准备就绪')
|
||
}
|
||
|
||
// 初始化音乐播放器
|
||
const initMusicPlayer = () => {
|
||
try {
|
||
// 创建音频播放器实例
|
||
audioPlayer.value = uni.createInnerAudioContext()
|
||
const bgmUrl = new URL('/static/music/bgm.mp3', import.meta.url)
|
||
audioPlayer.value.src = bgmUrl.href
|
||
audioPlayer.value.loop = true
|
||
|
||
// 播放完成事件
|
||
audioPlayer.value.onEnded(() => {
|
||
audioPlayer.value.seek(0)
|
||
audioPlayer.value.play()
|
||
})
|
||
|
||
// 播放错误事件
|
||
audioPlayer.value.onError((err) => {
|
||
console.error('音乐播放错误:', err)
|
||
})
|
||
|
||
console.log('音乐播放器初始化完成')
|
||
} catch (error) {
|
||
console.error('初始化音乐播放器失败:', error)
|
||
}
|
||
}
|
||
|
||
// 播放/暂停音乐
|
||
const toggleMusic = () => {
|
||
// 如果按钮被禁用,不处理点击
|
||
if (isBgmButtonDisabled.value) return
|
||
|
||
if (!audioPlayer.value) {
|
||
initMusicPlayer()
|
||
}
|
||
|
||
if (playerStore.isMusicPlaying) {
|
||
// 暂停音乐
|
||
audioPlayer.value.pause()
|
||
playerStore.setMusicPlaying(false)
|
||
console.log('音乐已暂停')
|
||
} else {
|
||
// 播放音乐
|
||
try {
|
||
const result = audioPlayer.value.play()
|
||
// 检查play()方法是否返回Promise(Web平台)
|
||
if (result && typeof result.then === 'function') {
|
||
// Web平台:使用Promise处理
|
||
result.then(() => {
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('音乐已开始播放')
|
||
}).catch(error => {
|
||
console.error('音乐播放失败(需要用户交互):', error)
|
||
// 不更新isMusicPlaying状态,保持为false
|
||
})
|
||
} else {
|
||
// 其他平台:同步处理
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('音乐已开始播放')
|
||
}
|
||
} catch (error) {
|
||
console.error('音乐播放失败(需要用户交互):', error)
|
||
// 不更新isMusicPlaying状态,保持为false
|
||
}
|
||
}
|
||
}
|
||
|
||
// 组件挂载后初始化
|
||
onMounted(() => {
|
||
console.log('SinglePageContainer 组件挂载')
|
||
|
||
// 初始化音乐播放器
|
||
initMusicPlayer()
|
||
|
||
// 移除自动播放音乐的代码,避免触发浏览器自动播放限制
|
||
// 音乐将在用户点击开始按钮或音乐控制按钮时播放
|
||
})
|
||
|
||
// 处理滚动事件(从下往上滑动)
|
||
const handleScroll = (event) => {
|
||
if (isScrolling.value) return
|
||
|
||
// 只有在非自动滚动状态下,才标记为已手动滑动
|
||
if (!isAutoScrolling.value && !hasScrolled.value && event.detail.scrollTop > 50) {
|
||
hasScrolled.value = true
|
||
}
|
||
|
||
// 在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 closeLotteryForm = () => {
|
||
showLotteryForm.value = false
|
||
}
|
||
|
||
// 提交抽奖表单
|
||
const submitLotteryForm = async (formData) => {
|
||
// 检查是否已经提交过
|
||
if (hasSubmittedUserInfo.value) {
|
||
uni.showToast({
|
||
title: '您已经提交过信息了,请勿重复提交',
|
||
icon: 'none',
|
||
duration: 2000
|
||
})
|
||
return
|
||
}
|
||
|
||
// 检查是否有UUID
|
||
if (!pageVisitUuid.value) {
|
||
uni.showToast({
|
||
title: '系统错误,请刷新页面重试',
|
||
icon: 'none',
|
||
duration: 2000
|
||
})
|
||
return
|
||
}
|
||
|
||
uni.showLoading({
|
||
title: '提交中...',
|
||
mask: true
|
||
})
|
||
|
||
try {
|
||
// 调用用户信息保存接口
|
||
await saveUserInfo({
|
||
name: formData.name,
|
||
phone: formData.phone,
|
||
address: formData.address,
|
||
msg: formData.msg,
|
||
page_visit_uuid: pageVisitUuid.value
|
||
})
|
||
|
||
// 标记为已提交
|
||
hasSubmittedUserInfo.value = true
|
||
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '提交成功!我们将在活动结束后通知您抽奖结果',
|
||
duration: 2000
|
||
})
|
||
showLotteryForm.value = false
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('提交用户信息失败:', error)
|
||
|
||
// 直接展示后端返回的错误信息
|
||
let errorMessage = '提交失败,请稍后重试'
|
||
|
||
// 检查是否有后端返回的错误信息
|
||
if (error.data && error.data.error) {
|
||
errorMessage = error.data.error
|
||
} else if (error.response && error.response.data && error.response.data.error) {
|
||
errorMessage = error.response.data.error
|
||
} else if (error.message) {
|
||
// 如果是网络错误或其他错误,显示通用提示
|
||
errorMessage = '提交失败,请稍后重试'
|
||
}
|
||
|
||
uni.showToast({
|
||
title: errorMessage,
|
||
icon: 'none',
|
||
duration: 2000
|
||
})
|
||
}
|
||
}
|
||
|
||
// 打开AI春联生成弹窗
|
||
const openAICoupletForm = () => {
|
||
showAICoupletForm.value = true
|
||
}
|
||
|
||
// 处理生成春联
|
||
const handleGenerateCouplet = async (keyword) => {
|
||
uni.showLoading({
|
||
title: '生成中...',
|
||
mask: true
|
||
})
|
||
|
||
try {
|
||
// 调用后端 API 生成春联海报
|
||
const response = await generateCoupletPoster({
|
||
title: keyword,
|
||
page_visit_uuid: pageVisitUuid.value
|
||
})
|
||
|
||
uni.hideLoading()
|
||
|
||
// 构建春联数据 - 使用 API 返回的参数
|
||
generatedCouplet.value = {
|
||
share_url: response.share_url,
|
||
image_url: response.image_url
|
||
}
|
||
|
||
showCoupletDisplay.value = true
|
||
showAICoupletForm.value = false
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
|
||
// 显示错误信息
|
||
uni.showToast({
|
||
title: error.message || '生成春联失败,请稍后重试',
|
||
icon: 'none',
|
||
duration: 2000
|
||
})
|
||
}
|
||
}
|
||
|
||
// 处理分享春联
|
||
const handleShareCouplet = () => {
|
||
// 分享逻辑可以在这里扩展
|
||
console.log('分享春联:', generatedCouplet.value)
|
||
}
|
||
|
||
// 返回顶部
|
||
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'))
|
||
}
|
||
|
||
|
||
|
||
// 页面显示时的生命周期钩子
|
||
import { onShow } from '@dcloudio/uni-app'
|
||
|
||
// 监听页面显示事件
|
||
onShow(() => {
|
||
// 当从webview返回时,恢复BGM状态
|
||
console.log('onShow - 页面显示:', {
|
||
isOpeningWebview: playerStore.isWebviewOpening,
|
||
wasMusicPlayingBeforeWebview: playerStore.wasMusicPlayingBeforeWebview,
|
||
wasMusicPlayingBeforeDrum: playerStore.wasMusicPlayingBeforeDrum,
|
||
isVideoPlaying: playerStore.isVideoPlaying,
|
||
wasMusicPlayingBeforeVideo: playerStore.wasMusicPlayingBeforeVideo,
|
||
currentIsMusicPlaying: playerStore.isMusicPlaying,
|
||
audioPlayerExists: !!audioPlayer.value
|
||
})
|
||
|
||
if (playerStore.isWebviewOpening) {
|
||
// 正常的webview返回,根据之前的BGM状态恢复
|
||
console.log('正常webview返回,恢复BGM状态:', {
|
||
wasMusicPlayingBeforeWebview: playerStore.wasMusicPlayingBeforeWebview,
|
||
wasMusicPlayingBeforeDrum: playerStore.wasMusicPlayingBeforeDrum,
|
||
currentIsMusicPlaying: playerStore.isMusicPlaying,
|
||
audioPlayerExists: !!audioPlayer.value
|
||
})
|
||
|
||
// 检查是否需要恢复BGM播放
|
||
// 情况1:如果wasMusicPlayingBeforeWebview为true,说明进入webview前BGM是播放状态,应该恢复播放
|
||
// 情况2:如果wasMusicPlayingBeforeDrum为true,说明在鼓声播放前BGM是播放状态,应该恢复播放
|
||
// 情况3:如果wasMusicPlayingBeforeWebview和wasMusicPlayingBeforeDrum都为false,但isDrumPlaying为true,说明从webview返回时鼓声应该还在播放,应该保持BGM暂停
|
||
// 情况4:如果以上都不满足,说明用户主动暂停了BGM,应该保持暂停
|
||
const shouldResumeBGM = playerStore.wasMusicPlayingBeforeWebview || playerStore.wasMusicPlayingBeforeDrum
|
||
|
||
console.log('是否应该恢复BGM播放:', shouldResumeBGM)
|
||
|
||
// 恢复BGM播放
|
||
if (shouldResumeBGM && !playerStore.isMusicPlaying) {
|
||
// 如果audioPlayer不存在,初始化它
|
||
if (!audioPlayer.value) {
|
||
initMusicPlayer()
|
||
}
|
||
if (audioPlayer.value) {
|
||
try {
|
||
console.log('尝试恢复BGM播放(正常场景)')
|
||
const result = audioPlayer.value.play()
|
||
// 检查play()方法是否返回Promise(Web平台)
|
||
if (result && typeof result.then === 'function') {
|
||
// Web平台:使用Promise处理
|
||
result.then(() => {
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(从webview返回)')
|
||
}).catch(error => {
|
||
console.error('恢复BGM播放失败:', error)
|
||
// 即使播放失败,也要标记为播放状态,确保UI正确
|
||
playerStore.setMusicPlaying(true)
|
||
})
|
||
} else {
|
||
// 非Web平台:直接标记为播放状态
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(从webview返回)')
|
||
}
|
||
} catch (error) {
|
||
console.error('恢复BGM播放失败:', error)
|
||
// 即使播放失败,也要标记为播放状态,确保UI正确
|
||
playerStore.setMusicPlaying(true)
|
||
}
|
||
}
|
||
} else {
|
||
// 如果之前BGM是暂停状态,保持暂停
|
||
console.log('BGM保持暂停状态(从webview返回)')
|
||
}
|
||
|
||
// 重置webview状态
|
||
playerStore.setWebviewOpening(false)
|
||
// 重置鼓声播放状态,确保BGM按钮可以点击
|
||
playerStore.setDrumPlaying(false)
|
||
console.log('重置isDrumPlaying为false,当前isBgmButtonDisabled:', isBgmButtonDisabled.value)
|
||
// 通知组件webview已关闭
|
||
handleWebviewClose()
|
||
// 重置鼓声前的BGM状态,避免影响下次webview返回
|
||
playerStore.wasMusicPlayingBeforeDrum = false
|
||
} else if (playerStore.isVideoPlaying) {
|
||
// 从视频返回,恢复BGM状态
|
||
console.log('从视频返回,恢复BGM状态:', playerStore.wasMusicPlayingBeforeVideo)
|
||
if (playerStore.wasMusicPlayingBeforeVideo && !playerStore.isMusicPlaying) {
|
||
// 如果audioPlayer不存在,初始化它
|
||
if (!audioPlayer.value) {
|
||
initMusicPlayer()
|
||
}
|
||
if (audioPlayer.value) {
|
||
try {
|
||
console.log('尝试恢复BGM播放(视频场景)')
|
||
const result = audioPlayer.value.play()
|
||
// 检查play()方法是否返回Promise(Web平台)
|
||
if (result && typeof result.then === 'function') {
|
||
// Web平台:使用Promise处理
|
||
result.then(() => {
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(从视频返回)')
|
||
}).catch(error => {
|
||
console.error('恢复BGM播放失败:', error)
|
||
// 即使播放失败,也要标记为播放状态,确保UI正确
|
||
playerStore.setMusicPlaying(true)
|
||
})
|
||
} else {
|
||
// 非Web平台:直接标记为播放状态
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(从视频返回)')
|
||
}
|
||
} catch (error) {
|
||
console.error('恢复BGM播放失败:', error)
|
||
// 即使播放失败,也要标记为播放状态,确保UI正确
|
||
playerStore.setMusicPlaying(true)
|
||
}
|
||
}
|
||
}
|
||
// 重置视频播放状态
|
||
playerStore.setVideoPlaying(false)
|
||
console.log('重置isVideoPlaying为false,当前isBgmButtonDisabled:', isBgmButtonDisabled.value)
|
||
}
|
||
})
|
||
|
||
// 处理视频打开事件
|
||
const handleVideoOpen = (isQianmenVideo = false) => {
|
||
// 标记视频正在播放
|
||
playerStore.setVideoPlaying(true)
|
||
|
||
// 保存原始BGM状态
|
||
// 如果播放了鼓声,保存鼓声播放前的BGM状态
|
||
// 如果没有播放鼓声,保存当前BGM状态
|
||
if (playerStore.wasMusicPlayingBeforeDrum) {
|
||
// 播放了鼓声:使用鼓声播放前的BGM状态
|
||
playerStore.wasMusicPlayingBeforeVideo = playerStore.wasMusicPlayingBeforeDrum
|
||
console.log('视频打开时,保存鼓声播放前的BGM状态:', playerStore.wasMusicPlayingBeforeDrum)
|
||
} else {
|
||
// 没有播放鼓声:使用当前BGM状态
|
||
playerStore.saveMusicStateBeforeVideo()
|
||
console.log('视频打开时,保存当前BGM状态:', playerStore.isMusicPlaying)
|
||
}
|
||
|
||
// 停止全局BGM播放
|
||
if (audioPlayer.value && playerStore.isMusicPlaying) {
|
||
audioPlayer.value.pause()
|
||
playerStore.setMusicPlaying(false)
|
||
console.log('全局BGM已停止(视频播放中)')
|
||
}
|
||
|
||
// 标记鼓声应该停止
|
||
playerStore.setDrumPlaying(false)
|
||
|
||
// 确保BGM保持暂停状态(如果是由鼓声导致的暂停)
|
||
console.log('视频打开时,BGM状态:', playerStore.isMusicPlaying)
|
||
console.log('视频打开时,鼓声播放前的BGM状态:', playerStore.wasMusicPlayingBeforeDrum)
|
||
}
|
||
|
||
// 处理视频关闭事件
|
||
const handleVideoClose = () => {
|
||
// 标记视频已停止播放
|
||
playerStore.setVideoPlaying(false)
|
||
|
||
// 检查是否需要恢复BGM播放
|
||
// 如果视频打开前BGM是播放状态,或者鼓声播放前BGM是播放状态,都需要恢复BGM播放
|
||
const shouldResumeBGM = playerStore.restoreMusicStateAfterVideo()
|
||
|
||
console.log('视频关闭时,是否应该恢复BGM播放:', shouldResumeBGM)
|
||
console.log('视频关闭时,BGM状态:', playerStore.isMusicPlaying)
|
||
console.log('视频关闭时,鼓声播放前的BGM状态:', playerStore.wasMusicPlayingBeforeDrum)
|
||
|
||
// 恢复全局BGM到原始状态
|
||
if (audioPlayer.value && shouldResumeBGM && !playerStore.isMusicPlaying) {
|
||
try {
|
||
const result = audioPlayer.value.play()
|
||
// 检查play()方法是否返回Promise(Web平台)
|
||
if (result && typeof result.then === 'function') {
|
||
// Web平台:使用Promise处理
|
||
result.then(() => {
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(视频已关闭)')
|
||
}).catch(error => {
|
||
console.error('BGM恢复播放失败(需要用户交互):', error)
|
||
// 即使播放失败,也要标记为播放状态,确保UI正确
|
||
playerStore.setMusicPlaying(true)
|
||
})
|
||
} else {
|
||
// 其他平台:同步处理
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(视频已关闭)')
|
||
}
|
||
} catch (error) {
|
||
console.error('BGM恢复播放失败(需要用户交互):', error)
|
||
// 即使播放失败,也要标记为播放状态,确保UI正确
|
||
playerStore.setMusicPlaying(true)
|
||
}
|
||
} else {
|
||
console.log('BGM保持暂停状态(视频已关闭)')
|
||
}
|
||
|
||
// 重置鼓声播放前的BGM状态,确保它不会一直保留着之前的状态
|
||
playerStore.wasMusicPlayingBeforeDrum = false
|
||
console.log('视频关闭后,重置鼓声播放前的BGM状态为:', playerStore.wasMusicPlayingBeforeDrum)
|
||
}
|
||
|
||
// 处理webview打开事件
|
||
const handleWebviewOpen = () => {
|
||
// 标记webview正在打开
|
||
playerStore.setWebviewOpening(true)
|
||
console.log('Webview即将打开')
|
||
|
||
// 注意:不要在这里保存鼓声播放前的BGM状态,因为此时BGM已经被暂停
|
||
// 保持之前保存的playerStore.wasMusicPlayingBeforeDrum值
|
||
console.log('当前鼓声播放状态:', playerStore.isDrumPlaying)
|
||
console.log('当前鼓声播放前的BGM状态:', playerStore.wasMusicPlayingBeforeDrum)
|
||
}
|
||
|
||
// 处理webview关闭事件
|
||
const handleWebviewClose = () => {
|
||
// 标记webview已关闭
|
||
playerStore.setWebviewOpening(false)
|
||
console.log('Webview已关闭')
|
||
}
|
||
|
||
// 暂停BGM(用于鼓声播放或webview打开)
|
||
const pauseBgm = (source = 'drum') => {
|
||
// 只有当来源是鼓声时,才标记鼓声正在播放
|
||
if (source === 'drum') {
|
||
playerStore.setDrumPlaying(true)
|
||
// 保存原始BGM状态
|
||
playerStore.saveMusicStateBeforeDrum()
|
||
console.log('保存鼓声播放前的BGM状态:', playerStore.isMusicPlaying)
|
||
} else if (source === 'webview') {
|
||
// 保存webview打开前的BGM状态
|
||
playerStore.saveMusicStateBeforeWebview()
|
||
console.log('保存webview打开前的BGM状态:', playerStore.isMusicPlaying)
|
||
// 注意:不要在这里保存鼓声播放前的BGM状态,因为此时BGM已经被暂停
|
||
// 保持之前保存的playerStore.wasMusicPlayingBeforeDrum值
|
||
console.log('当前鼓声播放状态:', playerStore.isDrumPlaying)
|
||
console.log('当前鼓声播放前的BGM状态:', playerStore.wasMusicPlayingBeforeDrum)
|
||
|
||
// 确保webview打开时不会修改playerStore.wasMusicPlayingBeforeDrum的值
|
||
// 只有在播放新的鼓声时,才会更新playerStore.wasMusicPlayingBeforeDrum的值
|
||
}
|
||
|
||
if (audioPlayer.value && playerStore.isMusicPlaying) {
|
||
audioPlayer.value.pause()
|
||
playerStore.setMusicPlaying(false)
|
||
console.log(`全局BGM已暂停(${source === 'drum' ? '鼓声播放中' : 'webview打开中'})`)
|
||
}
|
||
|
||
// 确保鼓声播放前的BGM状态不会被修改
|
||
if (source === 'webview' && playerStore.isDrumPlaying) {
|
||
console.log('webview打开时,鼓声播放中,保持鼓声播放前的BGM状态:', playerStore.wasMusicPlayingBeforeDrum)
|
||
}
|
||
}
|
||
|
||
// 恢复BGM(用于鼓声结束)
|
||
const resumeBgm = () => {
|
||
// 标记鼓声已停止播放
|
||
playerStore.setDrumPlaying(false)
|
||
|
||
// 恢复BGM到原始状态
|
||
if (audioPlayer.value && playerStore.restoreMusicStateAfterDrum() && !playerStore.isMusicPlaying) {
|
||
try {
|
||
const result = audioPlayer.value.play()
|
||
// 检查play()方法是否返回Promise(Web平台)
|
||
if (result && typeof result.then === 'function') {
|
||
// Web平台:使用Promise处理
|
||
result.then(() => {
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(鼓声已结束)')
|
||
}).catch(error => {
|
||
console.error('BGM恢复播放失败(需要用户交互):', error)
|
||
// 不更新isMusicPlaying状态,保持为false
|
||
})
|
||
} else {
|
||
// 其他平台:同步处理
|
||
playerStore.setMusicPlaying(true)
|
||
console.log('全局BGM已恢复播放(鼓声已结束)')
|
||
}
|
||
} catch (error) {
|
||
console.error('BGM恢复播放失败(需要用户交互):', error)
|
||
// 不更新isMusicPlaying状态,保持为false
|
||
}
|
||
}
|
||
|
||
// 注意:不要重置playerStore.wasMusicPlayingBeforeDrum的值
|
||
// 因为这个值可能在webview场景中需要使用
|
||
console.log('鼓声结束后,鼓声播放前的BGM状态:', playerStore.wasMusicPlayingBeforeDrum)
|
||
}
|
||
|
||
// 使用scroll-view组件后,不再需要手动处理触摸事件
|
||
// scroll-view会自动处理触摸滚动
|
||
|
||
// 组件卸载前清理资源
|
||
onUnmounted(() => {
|
||
// 无需手动移除事件监听器,uniapp会自动处理
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="single-page-wrapper">
|
||
<!-- 音乐控制按钮 -->
|
||
<div
|
||
class="music-control"
|
||
:class="{ 'music-playing': playerStore.isMusicPlaying, 'disabled': isBgmButtonDisabled }"
|
||
@click="toggleMusic"
|
||
>
|
||
<img src="/static/images/music_on.png" alt="音乐" class="music-icon" />
|
||
</div>
|
||
|
||
<!-- 加载组件 -->
|
||
<LoadingComponent
|
||
v-if="showLoading"
|
||
@loaded="handleLoadingComplete"
|
||
@start="handleStart"
|
||
/>
|
||
|
||
<scroll-view
|
||
class="single-page-container"
|
||
ref="scrollContainer"
|
||
scroll-y="true"
|
||
scroll-with-animation="true"
|
||
enhanced="true"
|
||
:refresher-enabled="false"
|
||
:refresher-triggered="false"
|
||
show-scrollbar="false"
|
||
:bounce="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"
|
||
:has-submitted="hasSubmittedUserInfo"
|
||
:generated-couplet="generatedCouplet"
|
||
@lottery="openLotteryForm"
|
||
@couplet="openAICoupletForm"
|
||
@show-couplet="showCoupletDisplay = true"
|
||
@restart="scrollToTop"
|
||
/>
|
||
|
||
<!-- 东直门商圈 -->
|
||
<DongzhimenScene
|
||
:active="activeSceneIndex === 1"
|
||
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
|
||
:video-url="scenes[1].videoUrl"
|
||
:page-visit-uuid="pageVisitUuid"
|
||
@collect-seal="collectSeal(1)"
|
||
@video-open="handleVideoOpen"
|
||
@video-close="handleVideoClose"
|
||
/>
|
||
|
||
<!-- 隆福寺商圈 -->
|
||
<LongfusiScene
|
||
:active="activeSceneIndex === 2"
|
||
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
|
||
:video-url="scenes[2].videoUrl"
|
||
:page-visit-uuid="pageVisitUuid"
|
||
@collect-seal="collectSeal(2)"
|
||
@video-open="handleVideoOpen"
|
||
@video-close="handleVideoClose"
|
||
/>
|
||
|
||
<!-- 王府井商圈 -->
|
||
<WangfujingScene
|
||
:active="activeSceneIndex === 3"
|
||
:scroll-position="scrollContainer?.value?.scrollTop || 0"
|
||
:video-url="scenes[3].videoUrl"
|
||
:page-visit-uuid="pageVisitUuid"
|
||
@collect-seal="collectSeal(3)"
|
||
@video-open="handleVideoOpen"
|
||
@video-close="handleVideoClose"
|
||
/>
|
||
|
||
<!-- 崇文门商圈 -->
|
||
<ChongwenScene
|
||
:active="activeSceneIndex === 4"
|
||
:scroll-position="scrollContainer?.value?.scrollTop || 0"
|
||
:video-url="scenes[4].videoUrl"
|
||
:is-music-playing="playerStore.isMusicPlaying"
|
||
:page-visit-uuid="pageVisitUuid"
|
||
@collect-seal="collectSeal(4)"
|
||
@video-open="handleVideoOpen"
|
||
@video-close="handleVideoClose"
|
||
@pause-bgm="(source) => pauseBgm(source)"
|
||
@resume-bgm="resumeBgm"
|
||
@webview-open="handleWebviewOpen"
|
||
@webview-close="handleWebviewClose"
|
||
/>
|
||
|
||
<!-- 前门商圈 -->
|
||
<QianmenScene
|
||
:active="activeSceneIndex === 5"
|
||
:scroll-position="scrollContainer?.value?.scrollTop || 0"
|
||
:video-url="scenes[5].videoUrl"
|
||
:is-music-playing="playerStore.isMusicPlaying"
|
||
:is-video-playing="playerStore.isVideoPlaying"
|
||
:is-webview-opening="playerStore.isWebviewOpening"
|
||
:page-visit-uuid="pageVisitUuid"
|
||
@collect-seal="collectSeal(5)"
|
||
@height-changed="handleQianmenHeightChanged"
|
||
@video-open="handleVideoOpen(true)"
|
||
@video-close="handleVideoClose"
|
||
@pause-bgm="(source) => pauseBgm(source)"
|
||
@resume-bgm="resumeBgm"
|
||
/>
|
||
|
||
<!-- 新年文案模块 -->
|
||
<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"
|
||
/>
|
||
</div>
|
||
<!-- 标题图片 -->
|
||
<img
|
||
src="/static/images/img_title.png"
|
||
alt="马年新春2026"
|
||
class="title-image"
|
||
:class="{ 'title-image-active': titleImageShown }"
|
||
/>
|
||
</section>
|
||
|
||
<!-- 向上滑动提示 -->
|
||
<div class="scroll-tip-bottom" v-if="!hasScrolled">
|
||
<img src="/static/images/icon_hand1.png" class="tip-icon" alt="滑动提示" />
|
||
<p>向下滑动探索商圈</p>
|
||
</div>
|
||
|
||
<!-- 抽奖留资弹窗 -->
|
||
<LotteryFormModal
|
||
:visible="showLotteryForm"
|
||
:has-submitted="hasSubmittedUserInfo"
|
||
@close="closeLotteryForm"
|
||
@submit="submitLotteryForm"
|
||
/>
|
||
|
||
<!-- AI春联生成弹窗 -->
|
||
<AICoupletForm
|
||
:visible="showAICoupletForm"
|
||
@close="showAICoupletForm = false"
|
||
@generate="handleGenerateCouplet"
|
||
/>
|
||
|
||
<!-- 春联展示页面 -->
|
||
<CoupletDisplay
|
||
:visible="showCoupletDisplay"
|
||
:couplet="generatedCouplet"
|
||
@close="showCoupletDisplay = false"
|
||
@share="handleShareCouplet"
|
||
/>
|
||
</scroll-view>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.single-page-wrapper {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.single-page-container {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow-y: scroll; /* 改为 scroll 确保可以滚动 */
|
||
overflow-x: hidden;
|
||
-webkit-overflow-scrolling: touch; /* 启用iOS平滑滚动 */
|
||
touch-action: pan-y; /* 允许垂直方向的触摸滚动 */
|
||
user-select: none; /* 防止触摸时文本被选中 */
|
||
}
|
||
|
||
|
||
|
||
@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;
|
||
}
|
||
|
||
/* 首页样式 */
|
||
.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: 20px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
animation: bounce 1.5s infinite;
|
||
z-index: 100;
|
||
color: white;
|
||
}
|
||
|
||
.tip-icon {
|
||
width: 64rpx;
|
||
height: 78rpx;
|
||
}
|
||
|
||
@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: 515rpx;
|
||
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);
|
||
}
|
||
|
||
|
||
|
||
/* 音乐控制按钮样式 */
|
||
.music-control {
|
||
position: fixed;
|
||
width: 40px;
|
||
height: 40px;
|
||
top: 10px;
|
||
right: 10px;
|
||
border-radius: 50%;
|
||
/* background-color: rgba(0, 0, 0, 0.3); */
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.music-control:hover:not(.disabled) {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.music-control:active:not(.disabled) {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.music-control.disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
|
||
.music-icon {
|
||
width: 49rpx;
|
||
height: 49rpx;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
/* 音乐播放时的转动动画 */
|
||
.music-playing .music-icon {
|
||
animation: rotate 2s linear infinite;
|
||
}
|
||
|
||
@keyframes rotate {
|
||
from {
|
||
transform: rotate(0deg);
|
||
}
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
</style> |