1、更换loading页面开始按钮
 2、长图页五个商圈分别增加对应的视频地址
 3、视频播放按钮吉视频播放器,分别封装为组件,5个商圈组件进行引用
This commit is contained in:
Wenzhe 2026-02-04 12:18:51 +08:00
parent d89ce10c34
commit cea4f8b67d
10 changed files with 368 additions and 73 deletions

View File

@ -1,5 +1,7 @@
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue'
// //
const props = defineProps({ const props = defineProps({
@ -12,6 +14,11 @@ const props = defineProps({
scrollPosition: { scrollPosition: {
type: Number, type: Number,
default: 0 default: 0
},
//
videoUrl: {
type: String,
default: ''
} }
}) })
@ -23,6 +30,8 @@ const sealCollected = ref(false)
// //
const sq2ImageVisible = ref(false) const sq2ImageVisible = ref(false)
//
const showVideoPlayer = ref(false)
// //
const parallaxOffset = computed(() => { const parallaxOffset = computed(() => {
@ -46,15 +55,25 @@ const openWebview = () => {
}, },
fail: (err) => { fail: (err) => {
console.error('Failed to open webview:', err) console.error('Failed to open webview:', err)
showToast({ uni.showToast({
message: '页面打开失败', title: '页面打开失败',
icon: 'error', icon: 'none',
duration: 1500 duration: 1500
}) })
} }
}) })
} }
//
const openVideoPlayer = () => {
showVideoPlayer.value = true
}
//
const closeVideoPlayer = () => {
showVideoPlayer.value = false
}
// //
onMounted(() => { onMounted(() => {
// //
@ -90,6 +109,17 @@ onMounted(() => {
class="btn-view" class="btn-view"
@click="openWebview" @click="openWebview"
/> />
<!-- 视频播放按钮 -->
<VideoPlayButton @play="openVideoPlayer" />
<!-- 视频播放器弹窗 -->
<VideoPlayerModal
:video-url="props.videoUrl"
:visible="showVideoPlayer"
@close="closeVideoPlayer"
/>
</section> </section>
</template> </template>

View File

@ -1,6 +1,8 @@
<script setup> <script setup>
import { ref, onMounted, onUnmounted, computed, getCurrentInstance as vueGetCurrentInstance, watch } from 'vue' import { ref, onMounted, onUnmounted, computed, getCurrentInstance as vueGetCurrentInstance, watch } from 'vue'
import ImagePreloader from '@/utils/preload' import ImagePreloader from '@/utils/preload'
import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue'
// Vue // Vue
const instance = vueGetCurrentInstance() const instance = vueGetCurrentInstance()
@ -14,6 +16,10 @@ const props = defineProps({
scrollPosition: { scrollPosition: {
type: Number, type: Number,
default: 0 default: 0
},
videoUrl: {
type: String,
default: ''
} }
}) })
@ -23,6 +29,8 @@ const emit = defineEmits(['collect-seal'])
const sq3ImageVisible = ref(false) const sq3ImageVisible = ref(false)
// //
const sealCollected = ref(false) const sealCollected = ref(false)
//
const showVideoPlayer = ref(false)
// //
const parallaxOffset = computed(() => { const parallaxOffset = computed(() => {
@ -467,6 +475,16 @@ const resetDuckPosition = () => {
console.log('鸭子已回归原始位置:', duckX.value, duckY.value) console.log('鸭子已回归原始位置:', duckX.value, duckY.value)
} }
//
const openVideoPlayer = () => {
showVideoPlayer.value = true
}
//
const closeVideoPlayer = () => {
showVideoPlayer.value = false
}
// //
const preloadImages = async () => { const preloadImages = async () => {
if (imagesLoaded.value || isPreloading.value) return if (imagesLoaded.value || isPreloading.value) return
@ -590,6 +608,17 @@ onUnmounted(() => {
class="drag-trigger-area" class="drag-trigger-area"
@touchstart="startDrag" @touchstart="startDrag"
></view> ></view>
<!-- 视频播放按钮 -->
<VideoPlayButton @play="openVideoPlayer" />
<!-- 视频播放器弹窗 -->
<VideoPlayerModal
:video-url="props.videoUrl"
:visible="showVideoPlayer"
@close="closeVideoPlayer"
/>
</view> </view>
</template> </template>
@ -692,4 +721,6 @@ onUnmounted(() => {
height: 100rpx; height: 100rpx;
z-index: 30; z-index: 30;
} }
</style> </style>

View File

@ -111,7 +111,7 @@ onMounted(() => {
</div> </div>
<!-- 进度条区域 --> <!-- 进度条区域 -->
<div class="progress-container"> <div v-if="!startButtonVisible" class="progress-container">
<div class="progress-bar"> <div class="progress-bar">
<div class="progress-fill" :style="{ width: `${loadingProgress}%` }"></div> <div class="progress-fill" :style="{ width: `${loadingProgress}%` }"></div>
</div> </div>
@ -190,8 +190,8 @@ onMounted(() => {
.start-button { .start-button {
position: absolute; position: absolute;
bottom: 20vh; bottom: 20vh;
width: 300rpx; width: 496rpx;
height: 100rpx; height: 97rpx;
cursor: pointer; cursor: pointer;
animation: pulse 1.5s infinite; animation: pulse 1.5s infinite;
} }

View File

@ -1,6 +1,8 @@
<script setup> <script setup>
import { ref, onMounted, computed, watch } from 'vue' import { ref, onMounted, computed, watch } from 'vue'
import ImageGalleryModal from './ImageGalleryModal.vue' import ImageGalleryModal from './ImageGalleryModal.vue'
import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue'
import ImagePreloader from '@/utils/preload'; import ImagePreloader from '@/utils/preload';
// //
@ -14,6 +16,11 @@ const props = defineProps({
scrollPosition: { scrollPosition: {
type: Number, type: Number,
default: 0 default: 0
},
//
videoUrl: {
type: String,
default: ''
} }
}) })
@ -24,6 +31,8 @@ const emit = defineEmits(['collect-seal'])
const sq3ImageVisible = ref(false) const sq3ImageVisible = ref(false)
// //
const sealCollected = ref(false) const sealCollected = ref(false)
//
const showVideoPlayer = ref(false)
// //
const imagesLoaded = ref(false) const imagesLoaded = ref(false)
@ -96,6 +105,16 @@ const closeGallery = () => {
galleryVisible.value = false galleryVisible.value = false
} }
//
const openVideoPlayer = () => {
showVideoPlayer.value = true
}
//
const closeVideoPlayer = () => {
showVideoPlayer.value = false
}
// //
onMounted(() => { onMounted(() => {
// //
@ -157,6 +176,18 @@ onMounted(() => {
@close="closeGallery" @close="closeGallery"
/> />
<!-- 视频播放按钮 -->
<VideoPlayButton @play="openVideoPlayer" />
<!-- 视频播放器弹窗 -->
<VideoPlayerModal
:video-url="props.videoUrl"
:visible="showVideoPlayer"
@close="closeVideoPlayer"
/>
</section> </section>
</template> </template>
@ -305,53 +336,6 @@ onMounted(() => {
} }
} }
/* 烟花效果 */
.fireworks {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 20;
}
.firework {
position: absolute;
font-size: 2rem;
opacity: 0;
animation: firework 3s infinite;
}
.firework-1 {
top: 10%;
left: 20%;
animation-delay: 0s;
}
.firework-2 {
top: 15%;
right: 25%;
animation-delay: 1s;
}
.firework-3 {
top: 8%;
right: 15%;
animation-delay: 2s;
}
.firework-4 {
top: 12%;
left: 25%;
animation-delay: 3s;
}
@keyframes firework {
0%, 100% { opacity: 0; transform: scale(0); }
50% { opacity: 1; transform: scale(1.5); }
}
/* 福印收集标记 */ /* 福印收集标记 */
.seal-collected-mark { .seal-collected-mark {
position: absolute; position: absolute;
@ -443,19 +427,4 @@ onMounted(() => {
} }
} }
/* 响应式设计 */
@media (max-width: 640px) {
.fu-word {
font-size: 1.5rem;
}
.lantern {
font-size: 2rem;
}
.interaction-area {
width: 100px;
height: 80px;
}
}
</style> </style>

View File

@ -1,5 +1,7 @@
<script setup> <script setup>
import { ref, onMounted, onUnmounted, computed, watch } from 'vue' import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue'
// //
const props = defineProps({ const props = defineProps({
@ -12,6 +14,11 @@ const props = defineProps({
scrollPosition: { scrollPosition: {
type: Number, type: Number,
default: 0 default: 0
},
//
videoUrl: {
type: String,
default: ''
} }
}) })
@ -27,6 +34,8 @@ const musicPlayer = ref(null)
// //
const sq1ImageVisible = ref(false) const sq1ImageVisible = ref(false)
//
const showVideoPlayer = ref(false)
// //
const sceneHeight = ref(0) const sceneHeight = ref(0)
@ -144,6 +153,16 @@ const calculateHeight = () => {
} }
} }
//
const openVideoPlayer = () => {
showVideoPlayer.value = true
}
//
const closeVideoPlayer = () => {
showVideoPlayer.value = false
}
// //
onUnmounted(() => { onUnmounted(() => {
if (musicPlayer.value) { if (musicPlayer.value) {
@ -193,6 +212,12 @@ onUnmounted(() => {
<!-- 舞狮动画 --> <!-- 舞狮动画 -->
<div class="lion-dance"></div> <div class="lion-dance"></div>
<!-- 视频播放按钮 -->
<VideoPlayButton @play="openVideoPlayer" />
<!-- 视频播放器弹窗 -->
<VideoPlayerModal :video-url="props.videoUrl" :visible="showVideoPlayer" @close="closeVideoPlayer" />
</section> </section>
</template> </template>

View File

@ -78,31 +78,36 @@ const scenes = ref([
id: 'dongzhimen', id: 'dongzhimen',
name: '东直门商圈', name: '东直门商圈',
description: '京城东部的交通枢纽和商业中心', description: '京城东部的交通枢纽和商业中心',
collectedItem: '团圆福筷' collectedItem: '团圆福筷',
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
}, },
{ {
id: 'longfusi', id: 'longfusi',
name: '隆福寺商圈', name: '隆福寺商圈',
description: '传统文化与现代艺术融合的潮流地标', description: '传统文化与现代艺术融合的潮流地标',
collectedItem: '文化福灯' collectedItem: '文化福灯',
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
}, },
{ {
id: 'wangfujing', id: 'wangfujing',
name: '王府井商圈', name: '王府井商圈',
description: '北京最繁华的商业中心之一', description: '北京最繁华的商业中心之一',
collectedItem: '金袋福卡' collectedItem: '金袋福卡',
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
}, },
{ {
id: 'chongwen', id: 'chongwen',
name: '崇文门商圈', name: '崇文门商圈',
description: '融合传统文化与现代商业的活力区域', description: '融合传统文化与现代商业的活力区域',
collectedItem: '国潮福字' collectedItem: '国潮福字',
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
}, },
{ {
id: 'qianmen', id: 'qianmen',
name: '前门商圈', name: '前门商圈',
description: '北京最具历史文化底蕴的商圈之一', description: '北京最具历史文化底蕴的商圈之一',
collectedItem: '非遗福印' collectedItem: '非遗福印',
videoUrl: 'http://192.168.2.149:8090/sample-3.mp4'
}, },
{ {
id: 'home', id: 'home',
@ -611,6 +616,7 @@ onUnmounted(() => {
<DongzhimenScene <DongzhimenScene
:active="activeSceneIndex === 1" :active="activeSceneIndex === 1"
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0" :scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
:video-url="scenes[1].videoUrl"
@collect-seal="collectSeal(1)" @collect-seal="collectSeal(1)"
/> />
@ -618,6 +624,7 @@ onUnmounted(() => {
<LongfusiScene <LongfusiScene
:active="activeSceneIndex === 2" :active="activeSceneIndex === 2"
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0" :scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
:video-url="scenes[2].videoUrl"
@collect-seal="collectSeal(2)" @collect-seal="collectSeal(2)"
/> />
@ -625,6 +632,7 @@ onUnmounted(() => {
<WangfujingScene <WangfujingScene
:active="activeSceneIndex === 3" :active="activeSceneIndex === 3"
:scroll-position="scrollContainer?.value?.scrollTop || 0" :scroll-position="scrollContainer?.value?.scrollTop || 0"
:video-url="scenes[3].videoUrl"
@collect-seal="collectSeal(3)" @collect-seal="collectSeal(3)"
/> />
@ -632,6 +640,7 @@ onUnmounted(() => {
<ChongwenScene <ChongwenScene
:active="activeSceneIndex === 4" :active="activeSceneIndex === 4"
:scroll-position="scrollContainer?.value?.scrollTop || 0" :scroll-position="scrollContainer?.value?.scrollTop || 0"
:video-url="scenes[4].videoUrl"
@collect-seal="collectSeal(4)" @collect-seal="collectSeal(4)"
/> />
@ -639,6 +648,7 @@ onUnmounted(() => {
<QianmenScene <QianmenScene
:active="activeSceneIndex === 5" :active="activeSceneIndex === 5"
:scroll-position="scrollContainer?.value?.scrollTop || 0" :scroll-position="scrollContainer?.value?.scrollTop || 0"
:video-url="scenes[5].videoUrl"
@collect-seal="collectSeal(5)" @collect-seal="collectSeal(5)"
@height-changed="handleQianmenHeightChanged" @height-changed="handleQianmenHeightChanged"
/> />

View File

@ -0,0 +1,94 @@
<script setup>
import { defineEmits } from 'vue'
//
const emit = defineEmits(['play'])
//
const handlePlayClick = () => {
emit('play')
}
</script>
<template>
<div class="video-play-button-container" @click="handlePlayClick">
<!-- 背景层单独进行呼吸动画 -->
<div class="video-play-button-bg"></div>
<!-- 内容层保持静态 -->
<div class="video-play-button-content">
<img src="/static/images/icon_music1.png" alt="播放视频" class="video-icon" />
<span class="video-text">观看视频</span>
</div>
</div>
</template>
<style scoped>
/* 视频播放按钮容器 */
.video-play-button-container {
position: absolute;
bottom: 100rpx;
right: 5rpx;
width: 120rpx;
height: 120rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 50;
cursor: pointer;
}
/* 背景层,单独进行呼吸动画 */
.video-play-button-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
animation: breathe 2s infinite ease-in-out;
}
/* 内容层,保持静态 */
.video-play-button-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 鼠标悬停效果 */
.video-play-button-container:hover .video-play-button-bg {
background-color: rgba(0, 0, 0, 0.9);
transform: scale(1.05);
animation: none;
}
.video-icon {
width: 40rpx;
height: 40rpx;
margin-bottom: 10rpx;
}
.video-text {
font-size: 24rpx;
color: white;
text-align: center;
}
/* 呼吸动画 */
@keyframes breathe {
0%, 100% {
transform: scale(1);
opacity: 0.8;
}
50% {
transform: scale(1.1);
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,105 @@
<script setup>
import { defineProps, defineEmits } from 'vue'
//
const props = defineProps({
//
videoUrl: {
type: String,
default: ''
},
//
visible: {
type: Boolean,
default: false
}
})
//
const emit = defineEmits(['close'])
//
const handleClose = () => {
emit('close')
}
//
const handleModalClick = () => {
emit('close')
}
//
const handleContentClick = (e) => {
e.stopPropagation()
}
</script>
<template>
<div v-if="visible" class="video-modal">
<div class="video-modal-content" @click="handleContentClick">
<div class="video-close-button" @click="handleClose">
<span>×</span>
</div>
<video
:src="videoUrl"
class="video-player"
controls
autoplay
loop
></video>
</div>
</div>
</template>
<style scoped>
/* 视频播放器弹窗样式 */
.video-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.video-modal-content {
position: relative;
border-radius: 20rpx;
overflow: hidden;
width: 720rpx;
height: 405rpx;
}
.video-close-button {
position: absolute;
top: 10rpx;
right: 10rpx;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
}
.video-close-button span {
color: white;
font-size: 30rpx;
font-weight: bold;
line-height: 30rpx;
height: 30rpx;
}
.video-player {
width: 100%;
height: 100%;
background-color: #000;
}
</style>

View File

@ -1,6 +1,8 @@
<script setup> <script setup>
import { ref, onMounted, computed, watch } from 'vue' import { ref, onMounted, computed, watch } from 'vue'
import ImageGalleryModal from './ImageGalleryModal.vue' import ImageGalleryModal from './ImageGalleryModal.vue'
import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue'
import ImagePreloader from '@/utils/preload'; import ImagePreloader from '@/utils/preload';
// //
@ -14,6 +16,11 @@ const props = defineProps({
scrollPosition: { scrollPosition: {
type: Number, type: Number,
default: 0 default: 0
},
//
videoUrl: {
type: String,
default: ''
} }
}) })
@ -22,6 +29,8 @@ const emit = defineEmits(['collect-seal'])
// //
const sq3ImageVisible = ref(false) const sq3ImageVisible = ref(false)
//
const showVideoPlayer = ref(false)
// //
const imagesLoaded = ref(false) const imagesLoaded = ref(false)
@ -94,6 +103,16 @@ const closeGallery = () => {
galleryVisible.value = false galleryVisible.value = false
} }
//
const openVideoPlayer = () => {
showVideoPlayer.value = true
}
//
const closeVideoPlayer = () => {
showVideoPlayer.value = false
}
// //
onMounted(() => { onMounted(() => {
// //
@ -140,6 +159,17 @@ onMounted(() => {
mode="without-title" mode="without-title"
@close="closeGallery" @close="closeGallery"
/> />
<!-- 视频播放按钮 -->
<VideoPlayButton @play="openVideoPlayer" />
<!-- 视频播放器弹窗 -->
<VideoPlayerModal
:video-url="props.videoUrl"
:visible="showVideoPlayer"
@close="closeVideoPlayer"
/>
</section> </section>
</template> </template>
@ -537,4 +567,5 @@ onMounted(() => {
opacity: 0; opacity: 0;
} }
} }
</style> </style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 42 KiB