1、增加loading组件,用于预加载scroll-view的背景图片,然后使得scroll-view进行隐藏的滚动到首页
2、调整首页滚动逻辑,使得可以完全屏蔽滚动;同时修改向上滚动逻辑
This commit is contained in:
Wenzhe 2026-02-03 23:44:05 +08:00
parent ab75f0056c
commit e3d0ffe1e6
2 changed files with 325 additions and 160 deletions

View File

@ -0,0 +1,216 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
// 组件属性
const props = defineProps({
// 是否显示
visible: {
type: Boolean,
default: true
}
})
// 组件事件
const emit = defineEmits(['loaded', 'start'])
// 加载进度
const loadingProgress = ref(0)
// 是否加载完成
const isLoadingComplete = ref(false)
// 是否显示开始按钮
const showStartButton = ref(false)
// 生成带时间戳的图片URL(避免缓存)
const generateImageUrl = (name) => {
const url = new URL(`/static/bg/${name}.jpg`, import.meta.url)
// 添加时间戳参数避免浏览器缓存
return url.href
}
// 计算开始按钮是否可见
const startButtonVisible = computed(() => {
return isLoadingComplete.value && showStartButton.value
})
// 加载图片函数
const loadImages = async () => {
// 图片名称数组
const bgImageNames = [
'bg1',
'bg2',
'bg3',
'bg4',
'bg5',
'bg_finish',
'bg_main'
]
// 生成图片URL数组
const bgImages = bgImageNames.map(name => generateImageUrl(name))
const totalImages = bgImages.length
let loadedImages = 0
// 加载每张图片
for (const imagePath of bgImages) {
try {
await loadImage(imagePath)
loadedImages++
// 更新加载进度
const progress = Math.round((loadedImages / totalImages) * 100)
loadingProgress.value = progress
// 模拟加载延迟,使进度条动画更流畅
await new Promise(resolve => setTimeout(resolve, 100))
} catch (error) {
console.error('加载图片失败:', imagePath, error)
// 即使加载失败,也继续加载其他图片
loadedImages++
loadingProgress.value = Math.round((loadedImages / totalImages) * 100)
}
}
// 加载完成
isLoadingComplete.value = true
loadingProgress.value = 100
// 延迟显示开始按钮,让用户看到100%的进度
setTimeout(() => {
showStartButton.value = true
// 通知父组件加载完成
emit('loaded')
}, 500)
}
// 单个图片加载函数
const loadImage = (src) => {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = resolve
img.onerror = reject
img.src = src
})
}
// 点击开始按钮
const handleStart = () => {
emit('start')
}
// 组件挂载后开始加载图片
onMounted(() => {
loadImages()
})
</script>
<template>
<div v-if="visible" class="loading-container">
<!-- 背景图片 -->
<div class="loading-bg">
<img src="/static/loading/loading_bg.jpg" alt="加载背景" class="bg-image" />
</div>
<!-- 进度条区域 -->
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: `${loadingProgress}%` }"></div>
</div>
<div class="progress-text">{{ loadingProgress }}%</div>
</div>
<!-- 开始按钮 -->
<div v-if="startButtonVisible" class="start-button" @click="handleStart">
<img src="/static/loading/btn_start.png" alt="开始" class="start-btn-image" />
</div>
</div>
</template>
<style scoped>
.loading-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.bg-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.progress-container {
position: absolute;
bottom: 30vh;
width: 80%;
max-width: 500rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.progress-bar {
width: 100%;
height: 20rpx;
background-color: rgba(255, 255, 255, 0.3);
border-radius: 10rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.progress-fill {
height: 100%;
background-color: #FFD700;
border-radius: 10rpx;
transition: width 0.3s ease;
}
.progress-text {
font-size: 36rpx;
font-weight: bold;
color: #FFD700;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
}
.start-button {
position: absolute;
bottom: 20vh;
width: 300rpx;
height: 100rpx;
cursor: pointer;
animation: pulse 1.5s infinite;
}
.start-btn-image {
width: 100%;
height: 100%;
object-fit: contain;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
</style>

View File

@ -14,6 +14,7 @@ import EndPage from './EndPage.vue'
import LotteryFormModal from './LotteryFormModal.vue' import LotteryFormModal from './LotteryFormModal.vue'
import AICoupletForm from './AICoupletForm.vue' import AICoupletForm from './AICoupletForm.vue'
import CoupletDisplay from './CoupletDisplay.vue' import CoupletDisplay from './CoupletDisplay.vue'
import LoadingComponent from './LoadingComponent.vue'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const collectionStore = useCollectionStore() const collectionStore = useCollectionStore()
@ -52,6 +53,10 @@ const hasSubmittedUserInfo = ref(false)
const hasScrolled = ref(false) const hasScrolled = ref(false)
// 标记是否正在自动滚动 // 标记是否正在自动滚动
const isAutoScrolling = ref(false) const isAutoScrolling = ref(false)
// 是否显示加载组件
const showLoading = ref(true)
// 是否已完成图片加载
const isImagesLoaded = ref(false)
// 推荐关键词 // 推荐关键词
@ -140,8 +145,95 @@ const collectedSeals = computed(() => {
} }
}) })
// 组件挂载后初始化 // 处理加载完成事件
onMounted(() => { 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)
// 初始化页面
initPage()
}
// 初始化页面函数
const initPage = () => {
// 记录页面访问 // 记录页面访问
recordPageVisit({ recordPageVisit({
user_agent: navigator.userAgent, user_agent: navigator.userAgent,
@ -163,114 +255,15 @@ onMounted(() => {
} }
}) })
// 添加滚动事件监听 - 使用uniapp的scroll-view事件绑定 // 标记页面准备就绪
// scroll-view 组件通过 @scroll 事件监听滚动,无需手动添加事件监听器 titleImageShown.value = true
isAutoScrolling.value = false
console.log('页面准备就绪')
}
// 组件挂载后初始化
// 页面加载时滚动到最底部(首页) onMounted(() => {
const scrollToHomePage = () => { console.log('SinglePageContainer 组件挂载')
// 标记正在自动滚动
isAutoScrolling.value = true
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-view(uniapp 原生支持)
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
// 自动滚动结束
isAutoScrolling.value = false
console.log('设置 isPageReady = true,准备显示页面')
}, 50)
} else {
console.log('成功滚动到首页')
// 滚动成功后显示容器
// 标记页面准备就绪,隐藏加载提示并显示页面
isPageReady.value = true
titleImageShown.value = true
// 自动滚动结束
isAutoScrolling.value = false
console.log('设置 isPageReady = true,准备显示页面')
}
}, 300)
}
// 初始滚动到首页 - 延迟更长时间确保内容渲染
setTimeout(scrollToHomePage, 800)
// 额外的确保措施,等待更长时间后再次尝试
setTimeout(scrollToHomePage, 1500)
}) })
// 处理滚动事件(从下往上滑动) // 处理滚动事件(从下往上滑动)
@ -512,15 +505,15 @@ onUnmounted(() => {
<template> <template>
<div class="single-page-wrapper"> <div class="single-page-wrapper">
<!-- 加载提示 --> <!-- 加载组件 -->
<div class="loading-hint" v-if="!isPageReady"> <LoadingComponent
<div class="loading-spinner"></div> v-if="showLoading"
<p>正在加载...</p> @loaded="handleLoadingComplete"
</div> @start="handleStart"
/>
<scroll-view <scroll-view
class="single-page-container" class="single-page-container"
:class="{ 'visible': isPageReady }"
ref="scrollContainer" ref="scrollContainer"
scroll-y="true" scroll-y="true"
scroll-with-animation="true" scroll-with-animation="true"
@ -654,44 +647,6 @@ onUnmounted(() => {
overflow: hidden; 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 { .single-page-container {
position: relative; position: relative;
width: 100%; width: 100%;
@ -699,17 +654,11 @@ onUnmounted(() => {
overflow-y: scroll; /* 改为 scroll 确保可以滚动 */ overflow-y: scroll; /* 改为 scroll 确保可以滚动 */
overflow-x: hidden; overflow-x: hidden;
-webkit-overflow-scrolling: touch; /* 启用iOS平滑滚动 */ -webkit-overflow-scrolling: touch; /* 启用iOS平滑滚动 */
/* 使用 visibility 而不是 opacity,确保完全不可见且不可交互 */
visibility: hidden;
touch-action: pan-y; /* 允许垂直方向的触摸滚动 */ touch-action: pan-y; /* 允许垂直方向的触摸滚动 */
user-select: none; /* 防止触摸时文本被选中 */ user-select: none; /* 防止触摸时文本被选中 */
} }
.single-page-container.visible {
visibility: visible;
/* 添加淡入效果 */
animation: fadeIn 0.4s ease-in-out;
}
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; } from { opacity: 0; }