qs_xinchun2026_h5/pages/qianmen/qianmen.vue

437 lines
9.2 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, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useSceneStore } from '../../src/store/scene'
import { useCollectionStore } from '../../src/store/collection'
import LongImageViewer from '../components/LongImageViewer.vue'
import MediaPlayer from '../components/MediaPlayer.vue'
const router = useRouter()
const sceneStore = useSceneStore()
const collectionStore = useCollectionStore()
// 场景数据
const sceneData = ref({
id: 'qianmen',
name: '前门商圈',
description: '北京最具历史文化底蕴的商圈之一',
image: 'https://picsum.photos/id/1015/750/1600', // 长图占位图
thumbnail: 'https://picsum.photos/id/1015/200/100',
location: '北京市东城区前门大街',
history: '前门大街形成于明代已有600多年历史是北京最古老的商业街之一。',
attractions: [
{ name: '前门大街', description: '北京著名的商业街,汇集众多老字号' },
{ name: '大栅栏', description: '明清时期的商业中心,保留大量传统建筑' },
{ name: '天安门广场', description: '世界最大的城市广场,中国的象征' }
],
audio: 'https://example.com/audio/qianmen.mp3', // 音频占位符
video: 'https://example.com/video/qianmen.mp4' // 视频占位符
})
// 交互状态
const isInteractive = ref(false)
const sealCollected = ref(false)
const showInfoPanel = ref(false)
// 福印收集状态
const sealStatus = computed(() => {
return collectionStore.isSceneSealCollected(sceneData.value.id)
})
// 组件挂载后初始化
onMounted(() => {
// 激活当前场景
sceneStore.activateScene(sceneData.value.id)
// 设置当前场景索引
const sceneIndex = sceneStore.scenes.findIndex(scene => scene.id === sceneData.value.id)
if (sceneIndex !== -1) {
sceneStore.setCurrentScene(sceneIndex)
}
// 检查福印是否已收集
sealCollected.value = sealStatus.value
})
// 点击场景交互区域
const handleSceneInteraction = () => {
if (!isInteractive.value) {
isInteractive.value = true
// 模拟交互过程
setTimeout(() => {
collectSeal()
}, 1000)
}
}
// 收集福印
const collectSeal = () => {
if (!sealCollected.value) {
const success = collectionStore.collectSealBySceneId(sceneData.value.id)
if (success) {
sealCollected.value = true
showToast({
message: '恭喜获得前门福印!',
icon: 'success',
duration: 2000
})
} else {
showFailToast({
message: '福印收集失败',
duration: 2000
})
}
} else {
showToast({
message: '福印已收集',
duration: 1500
})
}
}
// 查看场景信息
const showSceneInfo = () => {
showInfoPanel.value = true
}
// 关闭信息面板
const closeInfoPanel = () => {
showInfoPanel.value = false
}
// 跳转到下一个场景
const goToNextScene = () => {
router.push('/chongwen')
}
// 跳转到上一个场景
const goToPreviousScene = () => {
Toast('已经是第一个场景了')
}
</script>
<template>
<div class="scene-page">
<!-- 场景标题 -->
<div class="scene-header">
<h1 class="scene-title">{{ sceneData.name }}</h1>
<p class="scene-description">{{ sceneData.description }}</p>
</div>
<!-- 长图查看器 -->
<div class="long-image-container">
<LongImageViewer
:image-url="sceneData.image"
:description="sceneData.name"
@click="handleSceneInteraction"
/>
</div>
<!-- 交互提示 -->
<div class="interaction-tip" v-if="!isInteractive">
<div class="tip-icon">👆</div>
<p>点击图片查看详情</p>
</div>
<!-- 福印收集提示 -->
<div class="seal-collect-tip" v-if="sealCollected">
<div class="seal-icon">🏮</div>
<p>已收集前门福印</p>
</div>
<!-- 场景信息面板 -->
<div class="info-panel" v-if="showInfoPanel">
<div class="info-panel-content">
<div class="info-panel-header">
<h2>场景信息</h2>
<button class="close-btn" @click="closeInfoPanel">×</button>
</div>
<div class="info-panel-body">
<div class="info-item">
<label>名称:</label>
<span>{{ sceneData.name }}</span>
</div>
<div class="info-item">
<label>位置:</label>
<span>{{ sceneData.location }}</span>
</div>
<div class="info-item">
<label>历史:</label>
<span>{{ sceneData.history }}</span>
</div>
<div class="attractions-section">
<h3>主要景点:</h3>
<ul class="attractions-list">
<li v-for="(attraction, index) in sceneData.attractions" :key="index">
<div class="attraction-name">{{ attraction.name }}</div>
<div class="attraction-description">{{ attraction.description }}</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- 媒体播放器 -->
<div class="media-player-container">
<MediaPlayer
:audio-url="sceneData.audio"
:video-url="sceneData.video"
:scene-name="sceneData.name"
/>
</div>
<!-- 场景导航 -->
<div class="scene-navigation">
<button class="nav-btn prev-btn" @click="goToPreviousScene">
上一个
</button>
<button class="nav-btn info-btn" @click="showSceneInfo">
场景信息
</button>
<button class="nav-btn next-btn" @click="goToNextScene">
下一个
</button>
</div>
</div>
</template>
<style scoped>
.scene-page {
width: 100%;
min-height: 100vh;
background-color: #f5f5f5;
padding-top: 0.88rem;
padding-bottom: 1.2rem;
}
.scene-header {
padding: 0.2rem;
text-align: center;
background-color: #fff;
margin-bottom: 0.1rem;
}
.scene-title {
font-size: 0.4rem;
font-weight: bold;
color: #333;
margin-bottom: 0.1rem;
}
.scene-description {
font-size: 0.28rem;
color: #666;
}
.long-image-container {
width: 100%;
height: 5rem;
overflow: hidden;
background-color: #000;
position: relative;
}
.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: 0.3rem 0.5rem;
border-radius: 0.2rem;
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: 0.6rem;
margin-bottom: 0.1rem;
}
.seal-collect-tip {
position: absolute;
top: 1rem;
right: 0.2rem;
display: flex;
align-items: center;
background-color: rgba(255, 107, 53, 0.9);
color: #fff;
padding: 0.15rem 0.3rem;
border-radius: 0.2rem;
font-size: 0.28rem;
z-index: 10;
}
.seal-icon {
font-size: 0.4rem;
margin-right: 0.1rem;
}
.info-panel {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.info-panel-content {
width: 90%;
max-width: 500px;
background-color: #fff;
border-radius: 0.2rem;
overflow: hidden;
max-height: 80vh;
overflow-y: auto;
}
.info-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.3rem;
background-color: #ff6b35;
color: #fff;
}
.info-panel-header h2 {
font-size: 0.36rem;
margin: 0;
}
.close-btn {
background: none;
border: none;
color: #fff;
font-size: 0.4rem;
cursor: pointer;
}
.info-panel-body {
padding: 0.3rem;
}
.info-item {
margin-bottom: 0.2rem;
font-size: 0.28rem;
}
.info-item label {
font-weight: bold;
color: #333;
}
.info-item span {
color: #666;
}
.attractions-section {
margin-top: 0.3rem;
}
.attractions-section h3 {
font-size: 0.32rem;
margin-bottom: 0.2rem;
color: #333;
}
.attractions-list {
list-style: none;
padding: 0;
margin: 0;
}
.attractions-list li {
margin-bottom: 0.2rem;
padding: 0.2rem;
background-color: #f9f9f9;
border-radius: 0.1rem;
}
.attraction-name {
font-weight: bold;
color: #333;
margin-bottom: 0.05rem;
}
.attraction-description {
color: #666;
font-size: 0.26rem;
}
.media-player-container {
margin: 0.2rem;
background-color: #fff;
border-radius: 0.1rem;
padding: 0.2rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.scene-navigation {
display: flex;
justify-content: space-between;
padding: 0.2rem;
margin-top: 0.2rem;
}
.nav-btn {
flex: 1;
padding: 0.2rem;
margin: 0 0.1rem;
font-size: 0.3rem;
border: none;
border-radius: 0.15rem;
cursor: pointer;
transition: background-color 0.3s ease;
}
.prev-btn {
background-color: #e0e0e0;
color: #666;
}
.prev-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.info-btn {
background-color: #4caf50;
color: #fff;
}
.next-btn {
background-color: #2196f3;
color: #fff;
}
.nav-btn:hover:not(:disabled) {
opacity: 0.8;
}
</style>