qs_xinchun2026_h5/pages/chongwen/chongwen.vue

437 lines
9.1 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: 'chongwen',
name: '崇文商圈',
description: '融合传统文化与现代商业的活力区域',
image: 'https://picsum.photos/id/1016/750/1600', // 长图占位图
thumbnail: 'https://picsum.photos/id/1016/200/100',
location: '北京市东城区崇文门外大街',
history: '崇文商圈历史悠久,曾是明清时期的文化中心,如今成为现代化的商业区域。',
attractions: [
{ name: '北京新世界百货', description: '大型综合性购物中心' },
{ name: '红桥市场', description: '著名的珍珠和工艺品市场' },
{ name: '天坛公园', description: '世界文化遗产,明清皇帝祭天的场所' }
],
audio: 'https://example.com/audio/chongwen.mp3', // 音频占位符
video: 'https://example.com/video/chongwen.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('/wangfujing')
}
// 跳转到上一个场景
const goToPreviousScene = () => {
router.push('/qianmen')
}
</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: #2196f3;
color: #fff;
}
.prev-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.info-btn {
background-color: #4caf50;
color: #fff;
}
.next-btn {
background-color: #ff9800;
color: #fff;
}
.nav-btn:hover:not(:disabled) {
opacity: 0.8;
}
</style>