12 changed files with 2906 additions and 123 deletions
@ -0,0 +1,118 @@ |
|||||
|
// utils/request.js
|
||||
|
|
||||
|
/** |
||||
|
* 封装请求函数 |
||||
|
* @param {Object} options - 请求选项 |
||||
|
* @param {string} options.url - 请求URL |
||||
|
* @param {string} options.method - 请求方法: GET, POST, PUT, DELETE |
||||
|
* @param {Object} options.data - 请求参数 |
||||
|
* @param {Object} options.header - 请求头 |
||||
|
* @returns {Promise} - 返回Promise对象 |
||||
|
*/ |
||||
|
export const request = (options = {}) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
// 默认配置
|
||||
|
const defaultOptions = { |
||||
|
url: '', |
||||
|
method: 'GET', |
||||
|
data: {}, |
||||
|
header: { |
||||
|
'content-type': 'application/json' |
||||
|
}, |
||||
|
timeout: 10000 // 10秒超时
|
||||
|
}; |
||||
|
|
||||
|
// 合并配置
|
||||
|
const mergedOptions = {...defaultOptions, ...options}; |
||||
|
|
||||
|
// 处理URL
|
||||
|
if (!mergedOptions.url) { |
||||
|
reject(new Error('URL不能为空')); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 调用uni.request
|
||||
|
uni.request({ |
||||
|
url: mergedOptions.url, |
||||
|
method: mergedOptions.method, |
||||
|
data: mergedOptions.data, |
||||
|
header: mergedOptions.header, |
||||
|
timeout: mergedOptions.timeout, |
||||
|
success: (res) => { |
||||
|
// 请求成功
|
||||
|
if (res.statusCode >= 200 && res.statusCode < 300) { |
||||
|
resolve(res.data); |
||||
|
} else { |
||||
|
// 服务器返回错误
|
||||
|
reject({ |
||||
|
statusCode: res.statusCode, |
||||
|
errMsg: `请求失败,状态码: ${res.statusCode}`, |
||||
|
data: res.data |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
// 请求失败
|
||||
|
reject(err); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* GET请求快捷方法 |
||||
|
* @param {string} url - 请求URL |
||||
|
* @param {Object} data - 请求参数 |
||||
|
* @param {Object} options - 其他选项 |
||||
|
*/ |
||||
|
export const get = (url, data = {}, options = {}) => { |
||||
|
return request({ |
||||
|
url, |
||||
|
method: 'GET', |
||||
|
data, |
||||
|
...options |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* POST请求快捷方法 |
||||
|
* @param {string} url - 请求URL |
||||
|
* @param {Object} data - 请求参数 |
||||
|
* @param {Object} options - 其他选项 |
||||
|
*/ |
||||
|
export const post = (url, data = {}, options = {}) => { |
||||
|
return request({ |
||||
|
url, |
||||
|
method: 'POST', |
||||
|
data, |
||||
|
...options |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* 上传文件快捷方法 |
||||
|
* @param {string} url - 上传URL |
||||
|
* @param {string} filePath - 文件路径 |
||||
|
* @param {string} name - 文件对应的key |
||||
|
* @param {Object} formData - 附加的表单数据 |
||||
|
*/ |
||||
|
export const uploadFile = (url, filePath, name = 'file', formData = {}) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
uni.uploadFile({ |
||||
|
url, |
||||
|
filePath, |
||||
|
name, |
||||
|
formData, |
||||
|
success: (res) => { |
||||
|
try { |
||||
|
// 尝试解析JSON
|
||||
|
const data = JSON.parse(res.data); |
||||
|
resolve(data); |
||||
|
} catch (e) { |
||||
|
resolve(res.data); |
||||
|
} |
||||
|
}, |
||||
|
fail: reject |
||||
|
}); |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,133 @@ |
|||||
|
<template> |
||||
|
<view class="chapter-cover"> |
||||
|
<image class="cover-img" src="/static/images/chapter1/cover2.png" mode=""></image> |
||||
|
<view class="five-senses-content" @click="goFeel"> |
||||
|
<view class="box1"></view> |
||||
|
<view class="senses-txt"> |
||||
|
触觉 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="five-senses-content vision" @click="goVision"> |
||||
|
<view class="box1"></view> |
||||
|
<view class="senses-txt"> |
||||
|
视觉 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="five-senses-content hearing" @click="goHearing"> |
||||
|
<view class="box1"></view> |
||||
|
<view class="senses-txt"> |
||||
|
听觉 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="five-senses-content olfactory" @click="goOlfactory"> |
||||
|
<view class="box1"></view> |
||||
|
<view class="senses-txt"> |
||||
|
嗅觉 |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="five-senses-content gustation" @click="goGustation"> |
||||
|
<view class="box1"></view> |
||||
|
<view class="senses-txt"> |
||||
|
味觉 |
||||
|
</view> |
||||
|
</view> |
||||
|
<image @click="goback" class="btn" src="/static/images/chapter1/abandon-btn.png" mode=""></image> |
||||
|
</view> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
const goback = ()=>{ |
||||
|
uni.navigateBack({ |
||||
|
delta:1 |
||||
|
}) |
||||
|
} |
||||
|
const goFeel = ()=>{ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/chapter1/detail1' |
||||
|
}) |
||||
|
} |
||||
|
const goVision = ()=>{ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/chapter1/detail2' |
||||
|
}) |
||||
|
} |
||||
|
const goHearing = ()=>{ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/chapter1/detail3' |
||||
|
}) |
||||
|
} |
||||
|
const goOlfactory = ()=>{ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/chapter1/detail4' |
||||
|
}) |
||||
|
} |
||||
|
const goGustation = ()=>{ |
||||
|
uni.navigateTo({ |
||||
|
url:'/pages/chapter1/detail5' |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.chapter-cover { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.cover-img { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
.five-senses-content{ |
||||
|
position: absolute; |
||||
|
top: 32%; |
||||
|
right: 28%; |
||||
|
z-index: 2; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.vision{ |
||||
|
top: 54% !important; |
||||
|
right: 47% !important; |
||||
|
} |
||||
|
.hearing{ |
||||
|
top: 62% !important; |
||||
|
right: 8% !important; |
||||
|
} |
||||
|
.olfactory{ |
||||
|
top: 64% !important; |
||||
|
right: 76% !important; |
||||
|
} |
||||
|
.gustation{ |
||||
|
top: 73% !important; |
||||
|
right: 86% !important; |
||||
|
} |
||||
|
.box1{ |
||||
|
background: #fff; |
||||
|
border-radius: 50%; |
||||
|
opacity: 0.4; |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
} |
||||
|
.senses-txt{ |
||||
|
font-size: 24rpx; |
||||
|
color: rgba(255, 255, 255, 0.8); |
||||
|
margin-top: 20rpx; |
||||
|
} |
||||
|
.btn{ |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 11%; |
||||
|
transform: translate(-50%,-50%); |
||||
|
z-index: 2; |
||||
|
width: 280rpx; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,193 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[0]"> |
||||
|
<image v-show="shouldShowContent(0)" class="bg-image" src="/static/images/chapter1/feel.png" mode="" |
||||
|
:lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(0)" class="arrow-content"> |
||||
|
<image class="arrow-down" src="/static/arrow-icon-black.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[1]"> |
||||
|
<image v-show="shouldShowContent(1)" class="bg-image" src="/static/images/chapter1/feel2.png" |
||||
|
mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(1)" :class="['animate-content', {'animate-visible': isVisible}]"> |
||||
|
<image class="feel2-img" src="/static/images/chapter1/feel2-img.png" mode="" :lazy-load="true"> |
||||
|
</image> |
||||
|
</view> |
||||
|
<image v-show="shouldShowContent(1)" @click="goback" class="btn" src="/static/reselect-btn.png" |
||||
|
mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
watch |
||||
|
} from 'vue'; |
||||
|
|
||||
|
const isVisible = ref(false); |
||||
|
const currentIndex = ref(0); |
||||
|
|
||||
|
// 记录哪些页面已经加载过 |
||||
|
const loadedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false |
||||
|
}); |
||||
|
|
||||
|
// 加载预缓冲区大小 |
||||
|
const preloadBuffer = 1; // 对于只有两个页面的轮播,1就足够了 |
||||
|
|
||||
|
// 决定是否应该显示特定页面的内容 |
||||
|
const shouldShowContent = (index) => { |
||||
|
// 显示当前页和前后各preloadBuffer页 |
||||
|
return Math.abs(index - currentIndex.value) <= preloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 监听currentIndex变化,更新已加载页面状态 |
||||
|
watch(currentIndex, (newIndex) => { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - preloadBuffer); i <= Math.min(1, newIndex + preloadBuffer); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
|
||||
|
// 更新动画状态 |
||||
|
isVisible.value = newIndex === 1; |
||||
|
}, { |
||||
|
immediate: true |
||||
|
}); |
||||
|
|
||||
|
const handleSwiperChange = (e) => { |
||||
|
currentIndex.value = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const goback = () => { |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 初始化时标记第一页和相邻页为已加载 |
||||
|
loadedPages[0] = true; |
||||
|
// 由于只有两页且预加载缓冲区为1,所以第二页也会被标记为已加载 |
||||
|
if (preloadBuffer >= 1) { |
||||
|
loadedPages[1] = true; |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.animate-content { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 18%; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
opacity: 0; |
||||
|
transform: scale(0.9); |
||||
|
filter: blur(5px); |
||||
|
transition: opacity 0.6s ease-out 0.2s, |
||||
|
transform 0.6s ease-out 0.2s, |
||||
|
filter 0.6s ease-out 0.2s; |
||||
|
} |
||||
|
|
||||
|
.animate-visible { |
||||
|
opacity: 1; |
||||
|
transform: scale(1); |
||||
|
filter: blur(0px); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.feel2-img { |
||||
|
width: 89%; |
||||
|
height: 248rpx; |
||||
|
} |
||||
|
|
||||
|
.arrow-content { |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
bottom: 5%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.arrow-down { |
||||
|
width: 200rpx; |
||||
|
height: 40rpx; |
||||
|
animation: bounce 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 12%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 180rpx; |
||||
|
height: 56rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes bounce { |
||||
|
|
||||
|
0%, |
||||
|
20%, |
||||
|
50%, |
||||
|
80%, |
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
40% { |
||||
|
transform: translateY(-20rpx); |
||||
|
} |
||||
|
|
||||
|
60% { |
||||
|
transform: translateY(-10rpx); |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,193 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[0]"> |
||||
|
<image v-show="shouldShowContent(0)" class="bg-image" src="/static/images/chapter1/vision.png" |
||||
|
mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(0)" class="arrow-content"> |
||||
|
<image class="arrow-down" src="/static/arrow-icon-black.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[1]"> |
||||
|
<image v-show="shouldShowContent(1)" class="bg-image" src="/static/images/chapter1/vision2.png" |
||||
|
mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(1)" :class="['animate-content', {'animate-visible': isVisible}]"> |
||||
|
<image class="feel2-img" src="/static/images/chapter1/vision2-img.png" mode="" |
||||
|
:lazy-load="true"></image> |
||||
|
</view> |
||||
|
<image v-show="shouldShowContent(1)" @click="goback" class="btn" src="/static/reselect-btn.png" |
||||
|
mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
watch |
||||
|
} from 'vue'; |
||||
|
|
||||
|
const isVisible = ref(false); |
||||
|
const currentIndex = ref(0); |
||||
|
|
||||
|
// 记录哪些页面已经加载过 |
||||
|
const loadedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false |
||||
|
}); |
||||
|
|
||||
|
// 加载预缓冲区大小 |
||||
|
const preloadBuffer = 1; // 对于只有两个页面的轮播,1就足够了 |
||||
|
|
||||
|
// 决定是否应该显示特定页面的内容 |
||||
|
const shouldShowContent = (index) => { |
||||
|
// 显示当前页和前后各preloadBuffer页 |
||||
|
return Math.abs(index - currentIndex.value) <= preloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 监听currentIndex变化,更新已加载页面状态 |
||||
|
watch(currentIndex, (newIndex) => { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - preloadBuffer); i <= Math.min(1, newIndex + preloadBuffer); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
|
||||
|
// 更新动画状态 |
||||
|
isVisible.value = newIndex === 1; |
||||
|
}, { |
||||
|
immediate: true |
||||
|
}); |
||||
|
|
||||
|
const handleSwiperChange = (e) => { |
||||
|
currentIndex.value = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const goback = () => { |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 初始化时标记第一页和相邻页为已加载 |
||||
|
loadedPages[0] = true; |
||||
|
// 由于只有两页且预加载缓冲区为1,所以第二页也会被标记为已加载 |
||||
|
if (preloadBuffer >= 1) { |
||||
|
loadedPages[1] = true; |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.animate-content { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 16%; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
opacity: 0; |
||||
|
transform: scale(0.9); |
||||
|
filter: blur(5px); |
||||
|
transition: opacity 0.6s ease-out 0.2s, |
||||
|
transform 0.6s ease-out 0.2s, |
||||
|
filter 0.6s ease-out 0.2s; |
||||
|
} |
||||
|
|
||||
|
.animate-visible { |
||||
|
opacity: 1; |
||||
|
transform: scale(1); |
||||
|
filter: blur(0px); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.feel2-img { |
||||
|
width: 89%; |
||||
|
height: 248rpx; |
||||
|
} |
||||
|
|
||||
|
.arrow-content { |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
bottom: 5%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.arrow-down { |
||||
|
width: 200rpx; |
||||
|
height: 40rpx; |
||||
|
animation: bounce 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 12%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 180rpx; |
||||
|
height: 56rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes bounce { |
||||
|
|
||||
|
0%, |
||||
|
20%, |
||||
|
50%, |
||||
|
80%, |
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
40% { |
||||
|
transform: translateY(-20rpx); |
||||
|
} |
||||
|
|
||||
|
60% { |
||||
|
transform: translateY(-10rpx); |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,188 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[0]"> |
||||
|
<image v-show="shouldShowContent(0)" class="bg-image" src="/static/images/chapter1/hearing.png" mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(0)" class="arrow-content"> |
||||
|
<image class="arrow-down" src="/static/arrow-icon-black.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[1]"> |
||||
|
<image v-show="shouldShowContent(1)" class="bg-image" src="/static/images/chapter1/hearing2.png" mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(1)" :class="['animate-content', {'animate-visible': isVisible}]"> |
||||
|
<image class="feel2-img" src="/static/images/chapter1/hearing2-img.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
<image v-show="shouldShowContent(1)" @click="goback" class="btn" src="/static/reselect-btn.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
watch |
||||
|
} from 'vue'; |
||||
|
|
||||
|
const isVisible = ref(false); |
||||
|
const currentIndex = ref(0); |
||||
|
|
||||
|
// 记录哪些页面已经加载过 |
||||
|
const loadedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false |
||||
|
}); |
||||
|
|
||||
|
// 加载预缓冲区大小 |
||||
|
const preloadBuffer = 1; |
||||
|
|
||||
|
// 决定是否应该显示特定页面的内容 |
||||
|
const shouldShowContent = (index) => { |
||||
|
// 显示当前页和前后各preloadBuffer页 |
||||
|
return Math.abs(index - currentIndex.value) <= preloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 监听currentIndex变化,更新已加载页面状态 |
||||
|
watch(currentIndex, (newIndex) => { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - preloadBuffer); i <= Math.min(1, newIndex + preloadBuffer); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
|
||||
|
// 更新动画状态 |
||||
|
isVisible.value = newIndex === 1; |
||||
|
}, { immediate: true }); |
||||
|
|
||||
|
const handleSwiperChange = (e) => { |
||||
|
currentIndex.value = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const goback = () => { |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 初始化时标记第一页和相邻页为已加载 |
||||
|
loadedPages[0] = true; |
||||
|
|
||||
|
// 由于只有两页且预加载缓冲区为1,第二页也会被标记为已加载 |
||||
|
if(preloadBuffer >= 1) { |
||||
|
loadedPages[1] = true; |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.animate-content { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 16%; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
opacity: 0; |
||||
|
transform: scale(0.9); |
||||
|
filter: blur(5px); |
||||
|
transition: opacity 0.6s ease-out 0.2s, |
||||
|
transform 0.6s ease-out 0.2s, |
||||
|
filter 0.6s ease-out 0.2s; |
||||
|
} |
||||
|
|
||||
|
.animate-visible { |
||||
|
opacity: 1; |
||||
|
transform: scale(1); |
||||
|
filter: blur(0px); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.feel2-img { |
||||
|
width: 89%; |
||||
|
height: 228rpx; |
||||
|
} |
||||
|
|
||||
|
.arrow-content { |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
bottom: 5%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.arrow-down { |
||||
|
width: 200rpx; |
||||
|
height: 40rpx; |
||||
|
animation: bounce 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 12%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 180rpx; |
||||
|
height: 56rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes bounce { |
||||
|
|
||||
|
0%, |
||||
|
20%, |
||||
|
50%, |
||||
|
80%, |
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
40% { |
||||
|
transform: translateY(-20rpx); |
||||
|
} |
||||
|
|
||||
|
60% { |
||||
|
transform: translateY(-10rpx); |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,239 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[0]"> |
||||
|
<image v-show="shouldShowContent(0)" class="bg-image" src="/static/images/chapter1/olfactory.png" mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(0)" class="arrow-content"> |
||||
|
<image class="arrow-down" src="/static/arrow-icon-black.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[1]"> |
||||
|
<image v-show="shouldShowContent(1)" class="bg-image" src="/static/images/chapter1/olfactory2.png" mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(1)" :class="['animate-content', {'animate-visible': isVisible}]"> |
||||
|
<image class="feel2-img" src="/static/images/chapter1/olfactory2-img.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[2]"> |
||||
|
<image v-show="shouldShowContent(2)" class="bg-image" src="/static/images/chapter1/olfactory4.png" mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(2)" :class="['animate-content2', {'animate-visible': isVisible2}]"> |
||||
|
<image class="feel4-img" src="/static/images/chapter1/olfactory4-img.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[3]"> |
||||
|
<image v-show="shouldShowContent(3)" class="bg-image" src="/static/images/chapter1/olfactory3.png" mode="" :lazy-load="true"></image> |
||||
|
<image v-show="shouldShowContent(3)" class="feel3-img" src="/static/images/chapter1/olfactory3-img.png" mode="" :lazy-load="true"></image> |
||||
|
<image v-show="shouldShowContent(3)" @click="goback" class="btn" src="/static/seek-btn.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
watch |
||||
|
} from 'vue'; |
||||
|
|
||||
|
const isVisible = ref(false); |
||||
|
const isVisible2 = ref(false); |
||||
|
const currentIndex = ref(0); |
||||
|
|
||||
|
// 记录哪些页面已经加载过 |
||||
|
const loadedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false, |
||||
|
2: false, |
||||
|
3: false |
||||
|
}); |
||||
|
|
||||
|
// 加载预缓冲区大小 |
||||
|
const preloadBuffer = 1; |
||||
|
|
||||
|
// 决定是否应该显示特定页面的内容 |
||||
|
const shouldShowContent = (index) => { |
||||
|
// 显示当前页和前后各preloadBuffer页 |
||||
|
return Math.abs(index - currentIndex.value) <= preloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 监听currentIndex变化,更新已加载页面状态 |
||||
|
watch(currentIndex, (newIndex) => { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - preloadBuffer); i <= Math.min(3, newIndex + preloadBuffer); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
|
||||
|
// 更新动画状态 |
||||
|
isVisible.value = newIndex === 1; |
||||
|
isVisible2.value = newIndex === 2; |
||||
|
}, { immediate: true }); |
||||
|
|
||||
|
const handleSwiperChange = (e) => { |
||||
|
currentIndex.value = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const goback = () => { |
||||
|
const app = getApp(); |
||||
|
app.globalData.mainSliderIndex = 3; |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/home/home' |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 初始化时标记第一页和相邻页为已加载 |
||||
|
loadedPages[0] = true; |
||||
|
|
||||
|
// 预加载第二页 |
||||
|
if(preloadBuffer >= 1) { |
||||
|
loadedPages[1] = true; |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.animate-content2 { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 25%; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
opacity: 0; |
||||
|
transform: scale(0.9); |
||||
|
filter: blur(5px); |
||||
|
transition: opacity 0.6s ease-out 0.2s, |
||||
|
transform 0.6s ease-out 0.2s, |
||||
|
filter 0.6s ease-out 0.2s; |
||||
|
} |
||||
|
|
||||
|
.animate-content { |
||||
|
position: absolute; |
||||
|
left: 6%; |
||||
|
top: 32%; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
z-index: 2; |
||||
|
/* 初始状态 */ |
||||
|
opacity: 0; |
||||
|
transform: translateX(-50px) scale(0.95); |
||||
|
filter: blur(2px); |
||||
|
/* 过渡效果 */ |
||||
|
transition: opacity 0.7s ease-out 0.3s, |
||||
|
transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) 0.3s, |
||||
|
filter 0.6s ease-out 0.3s; |
||||
|
} |
||||
|
|
||||
|
.animate-visible { |
||||
|
opacity: 1; |
||||
|
transform: translateX(0) scale(1); |
||||
|
filter: blur(0); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.feel2-img { |
||||
|
width: 300rpx; |
||||
|
} |
||||
|
|
||||
|
.feel3-img { |
||||
|
width: 100%; |
||||
|
height: 650rpx; |
||||
|
z-index: 2; |
||||
|
position: relative |
||||
|
} |
||||
|
.feel4-img{ |
||||
|
width: 100%; |
||||
|
height: 400rpx; |
||||
|
} |
||||
|
.arrow-content { |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
bottom: 5%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.arrow-down { |
||||
|
width: 200rpx; |
||||
|
height: 40rpx; |
||||
|
animation: bounce 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 12%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 270rpx; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes bounce { |
||||
|
|
||||
|
0%, |
||||
|
20%, |
||||
|
50%, |
||||
|
80%, |
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
40% { |
||||
|
transform: translateY(-20rpx); |
||||
|
} |
||||
|
|
||||
|
60% { |
||||
|
transform: translateY(-10rpx); |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,187 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[0]"> |
||||
|
<image v-show="shouldShowContent(0)" class="bg-image" src="/static/images/chapter1/gustation.png" mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(0)" class="arrow-content"> |
||||
|
<image class="arrow-down" src="/static/arrow-icon-black.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[1]"> |
||||
|
<image v-show="shouldShowContent(1)" class="bg-image" src="/static/images/chapter1/gustation2.png" mode="" :lazy-load="true"></image> |
||||
|
<view v-show="shouldShowContent(1)" :class="['animate-content', {'animate-visible': isVisible}]"> |
||||
|
<image class="feel2-img" src="/static/images/chapter1/gustation2-img.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
<image v-show="shouldShowContent(1)" @click="goback" class="btn" src="/static/reselect-btn.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
watch |
||||
|
} from 'vue'; |
||||
|
|
||||
|
const isVisible = ref(false); |
||||
|
const currentIndex = ref(0); |
||||
|
|
||||
|
// 记录哪些页面已经加载过 |
||||
|
const loadedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false |
||||
|
}); |
||||
|
|
||||
|
// 加载预缓冲区大小 |
||||
|
const preloadBuffer = 1; |
||||
|
|
||||
|
// 决定是否应该显示特定页面的内容 |
||||
|
const shouldShowContent = (index) => { |
||||
|
// 显示当前页和前后各preloadBuffer页 |
||||
|
return Math.abs(index - currentIndex.value) <= preloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 监听currentIndex变化,更新已加载页面状态 |
||||
|
watch(currentIndex, (newIndex) => { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - preloadBuffer); i <= Math.min(1, newIndex + preloadBuffer); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
|
||||
|
// 更新动画状态 |
||||
|
isVisible.value = newIndex === 1; |
||||
|
}, { immediate: true }); |
||||
|
|
||||
|
const handleSwiperChange = (e) => { |
||||
|
currentIndex.value = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const goback = () => { |
||||
|
uni.navigateBack({ |
||||
|
delta: 1 |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 初始化时标记第一页和相邻页为已加载 |
||||
|
loadedPages[0] = true; |
||||
|
|
||||
|
// 由于只有两页且预加载缓冲区为1,第二页也会被标记为已加载 |
||||
|
if(preloadBuffer >= 1) { |
||||
|
loadedPages[1] = true; |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.animate-content { |
||||
|
position: absolute; |
||||
|
right: 6%; |
||||
|
top: 24%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
opacity: 0; |
||||
|
transform: scale(0.9); |
||||
|
filter: blur(5px); |
||||
|
transition: opacity 0.6s ease-out 0.2s, |
||||
|
transform 0.6s ease-out 0.2s, |
||||
|
filter 0.6s ease-out 0.2s; |
||||
|
} |
||||
|
|
||||
|
.animate-visible { |
||||
|
opacity: 1; |
||||
|
transform: scale(1); |
||||
|
filter: blur(0px); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.feel2-img { |
||||
|
width: 310rpx; |
||||
|
height: 410rpx; |
||||
|
} |
||||
|
|
||||
|
.arrow-content { |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
bottom: 5%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.arrow-down { |
||||
|
width: 200rpx; |
||||
|
height: 40rpx; |
||||
|
animation: bounce 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 12%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 180rpx; |
||||
|
height: 56rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes bounce { |
||||
|
|
||||
|
0%, |
||||
|
20%, |
||||
|
50%, |
||||
|
80%, |
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
40% { |
||||
|
transform: translateY(-20rpx); |
||||
|
} |
||||
|
|
||||
|
60% { |
||||
|
transform: translateY(-10rpx); |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,201 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<!-- 主swiper的第一个item --> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[0]"> |
||||
|
<image v-show="shouldShowContent(0)" class="bg-image" src="/static/images/chapter2/cover2.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<!-- 主swiper的第二个item --> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[1]"> |
||||
|
<image v-show="shouldShowContent(1)" class="bg-image" src="/static/images/chapter2/cover3.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<!-- 主swiper的第三个item --> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[2]"> |
||||
|
<image v-show="shouldShowContent(2)" class="bg-image" src="/static/images/chapter2/cover4.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<!-- 主swiper的第四个item(包含嵌套swiper) --> |
||||
|
<swiper-item> |
||||
|
<!-- 当主swiper的第四个item被加载时才加载嵌套swiper --> |
||||
|
<template v-if="loadedPages[3]"> |
||||
|
<swiper v-show="shouldShowContent(3)" class="nested-swiper" :vertical="true" :duration="300" @change="handleVerticalChange" :current="verticalIndex"> |
||||
|
<!-- 嵌套swiper的第一个item --> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedNestedPages[0]"> |
||||
|
<image v-show="shouldShowNestedContent(0)" class="bg-image" src="/static/images/chapter2/cover5.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<!-- 嵌套swiper的第二个item --> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedNestedPages[1]"> |
||||
|
<image v-show="shouldShowNestedContent(1)" class="bg-image" src="/static/images/chapter2/cover6.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<!-- 嵌套swiper的第三个item --> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedNestedPages[2]"> |
||||
|
<image v-show="shouldShowNestedContent(2)" class="bg-image" src="/static/images/chapter2/cover7.png" mode="" :lazy-load="true"></image> |
||||
|
<image v-show="shouldShowNestedContent(2)" @click="goback" class="btn" src="/static/seek-btn.png" mode="" :lazy-load="true"></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
</template> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
watch |
||||
|
} from 'vue'; |
||||
|
|
||||
|
const currentIndex = ref(0); |
||||
|
const verticalIndex = ref(0); |
||||
|
|
||||
|
// 记录主swiper哪些页面已经加载过 |
||||
|
const loadedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false, |
||||
|
2: false, |
||||
|
3: false |
||||
|
}); |
||||
|
|
||||
|
// 记录嵌套swiper哪些页面已经加载过 |
||||
|
const loadedNestedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false, |
||||
|
2: false |
||||
|
}); |
||||
|
|
||||
|
// 预加载缓冲区大小 |
||||
|
const preloadBuffer = 1; |
||||
|
const nestedPreloadBuffer = 1; |
||||
|
|
||||
|
// 决定是否应该显示主swiper特定页面的内容 |
||||
|
const shouldShowContent = (index) => { |
||||
|
return Math.abs(index - currentIndex.value) <= preloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 决定是否应该显示嵌套swiper特定页面的内容 |
||||
|
const shouldShowNestedContent = (index) => { |
||||
|
return Math.abs(index - verticalIndex.value) <= nestedPreloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 监听主swiper currentIndex变化,更新已加载页面状态 |
||||
|
watch(currentIndex, (newIndex) => { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - preloadBuffer); i <= Math.min(3, newIndex + preloadBuffer); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
|
||||
|
// 当切换到第4个item时,初始化嵌套swiper的加载状态 |
||||
|
if (newIndex === 3) { |
||||
|
loadedNestedPages[0] = true; |
||||
|
if (nestedPreloadBuffer >= 1) { |
||||
|
loadedNestedPages[1] = true; |
||||
|
} |
||||
|
} |
||||
|
}, { immediate: true }); |
||||
|
|
||||
|
// 监听嵌套swiper verticalIndex变化,更新已加载页面状态 |
||||
|
watch(verticalIndex, (newIndex) => { |
||||
|
// 只有当主swiper在第4个item时才更新嵌套swiper状态 |
||||
|
if (currentIndex.value === 3) { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - nestedPreloadBuffer); i <= Math.min(2, newIndex + nestedPreloadBuffer); i++) { |
||||
|
loadedNestedPages[i] = true; |
||||
|
} |
||||
|
} |
||||
|
}, { immediate: true }); |
||||
|
|
||||
|
const handleSwiperChange = (e) => { |
||||
|
currentIndex.value = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const handleVerticalChange = (e) => { |
||||
|
verticalIndex.value = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const goback = () => { |
||||
|
const app = getApp(); |
||||
|
app.globalData.mainSliderIndex = 4; |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/home/home' |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 初始化主swiper,标记第一页和相邻页为已加载 |
||||
|
currentIndex.value = 0; |
||||
|
for (let i = 0; i <= Math.min(preloadBuffer, 3); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
|
||||
|
// 初始化嵌套swiper的第一页为已加载(以防万一) |
||||
|
verticalIndex.value = 0; |
||||
|
loadedNestedPages[0] = true; |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
.nested-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
.btn { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 12%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 250rpx; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,276 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<swiper-item v-for="(_, outerIndex) in 7" :key="outerIndex"> |
||||
|
<swiper class="nested-swiper" :duration="300" @change="(e) => handleVerticalChange(e, outerIndex)" |
||||
|
:current="nestedIndices[outerIndex]"> |
||||
|
<!-- 嵌套swiper的第一个item(封面页) --> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<image class="bg-image" :src="`/static/images/chapter3/cover${outerIndex+2}.png`" mode="" |
||||
|
:lazy-load="true"></image> |
||||
|
<image class="cover-txt" :class="{'cover-txt2': outerIndex > 0}" |
||||
|
:src="`/static/images/chapter3/cover${outerIndex+2}-txt.png`" mode="" :lazy-load="true"> |
||||
|
</image> |
||||
|
<image class="btn" src="/static/seek2-btn.png" mode="" :lazy-load="true"></image> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
<!-- 嵌套swiper的第二个item--> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<view class="gif-container"> |
||||
|
<image v-if="shouldLoadContent(outerIndex)" class="gif-style" |
||||
|
:src="`/static/3-${outerIndex+1}-2.gif`" mode="aspectFill" :lazy-load="true"> |
||||
|
</image> |
||||
|
</view> |
||||
|
|
||||
|
<image class="bg-image" :src="`/static/images/chapter3/cover${outerIndex+2}-1.png`" mode="" |
||||
|
:lazy-load="true"></image> |
||||
|
<view class="qrcode-content"> |
||||
|
<image class="qrCode" src="/static/qrcode.png" mode="" :lazy-load="true"></image> |
||||
|
<view class="code-txt"> |
||||
|
扫码下单 |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
</swiper-item> |
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<image class="bg-image" src="/static/images/chapter3/cover9.png" mode="" :lazy-load="true"></image> |
||||
|
<image @click="goRandomImage" class="cover-txt9" src="/static/images/chapter3/cover9-txt.png" mode="" |
||||
|
:lazy-load="true"></image> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
onMounted, |
||||
|
onUnmounted, |
||||
|
watch, |
||||
|
computed |
||||
|
} from 'vue'; |
||||
|
|
||||
|
const currentIndex = ref(0); |
||||
|
|
||||
|
// 嵌套swiper索引 |
||||
|
const nestedIndices = reactive({ |
||||
|
0: 0, |
||||
|
1: 0, |
||||
|
2: 0, |
||||
|
3: 0, |
||||
|
4: 0, |
||||
|
5: 0, |
||||
|
6: 0 |
||||
|
}); |
||||
|
|
||||
|
// 当前活跃的页面,用于内容加载判断 |
||||
|
const activePages = reactive({ |
||||
|
mainIndex: 0, |
||||
|
nestedIndices: { |
||||
|
0: 0, |
||||
|
1: 0, |
||||
|
2: 0, |
||||
|
3: 0, |
||||
|
4: 0, |
||||
|
5: 0, |
||||
|
6: 0 |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 预加载缓冲区大小 |
||||
|
const preloadBuffer = 2; |
||||
|
|
||||
|
const shouldLoadContent = (outerIndex) => { |
||||
|
// 当主索引在附近,且嵌套索引是第二页(GIF页)时预加载 |
||||
|
return Math.abs(outerIndex - currentIndex.value) <= preloadBuffer && |
||||
|
(nestedIndices[outerIndex] === 1 || activePages.nestedIndices[outerIndex] === 1); |
||||
|
}; |
||||
|
|
||||
|
// 主swiper切换事件处理 |
||||
|
const handleSwiperChange = (e) => { |
||||
|
currentIndex.value = e.detail.current; |
||||
|
activePages.mainIndex = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
// 嵌套swiper切换事件处理 |
||||
|
const handleVerticalChange = (e, outerIndex) => { |
||||
|
nestedIndices[outerIndex] = e.detail.current; |
||||
|
activePages.nestedIndices[outerIndex] = e.detail.current; |
||||
|
}; |
||||
|
|
||||
|
const goRandomImage = () => { |
||||
|
const imageArray = [{ |
||||
|
id: 1, |
||||
|
image: '/static/images/chapter3/random/image1.png', |
||||
|
name: '雏菊', |
||||
|
desc: '宋 丛菊图页' |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
image: '/static/images/chapter3/random/image2.png', |
||||
|
name: '杜鹃', |
||||
|
desc: '清 恽寿平 花卉十开 杜鹃' |
||||
|
}, |
||||
|
{ |
||||
|
id: 3, |
||||
|
image: '/static/images/chapter3/random/image3.png', |
||||
|
name: '金桂', |
||||
|
desc: '清 蒋延锡 桂花图 局部 台北故宫博物馆院藏' |
||||
|
}, |
||||
|
{ |
||||
|
id: 4, |
||||
|
image: '/static/images/chapter3/random/image4.png', |
||||
|
name: '绿梅', |
||||
|
desc: '北宋 赵佶 梅花秀眼图页 北京故宫博物院藏' |
||||
|
}, |
||||
|
{ |
||||
|
id: 5, |
||||
|
image: '/static/images/chapter3/random/image5.png', |
||||
|
name: '墨兰', |
||||
|
desc: '宋 秋兰绽蕊图' |
||||
|
}, |
||||
|
{ |
||||
|
id: 6, |
||||
|
image: '/static/images/chapter3/random/image6.png', |
||||
|
name: '茉莉', |
||||
|
desc: '宋 茉莉花图页' |
||||
|
}, |
||||
|
{ |
||||
|
id: 7, |
||||
|
image: '/static/images/chapter3/random/image7.png', |
||||
|
name: '青莲', |
||||
|
desc: '宋 出水芙蓉图页' |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const app = getApp(); |
||||
|
app.globalData.randomImages = imageArray; |
||||
|
uni.setStorageSync('randomImages', JSON.stringify(imageArray)); |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/chapter3/randomImage' |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const goback = () => { |
||||
|
const app = getApp(); |
||||
|
app.globalData.mainSliderIndex = 4; |
||||
|
uni.redirectTo({ |
||||
|
url: '/pages/home/home' |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 初始化状态 |
||||
|
currentIndex.value = 0; |
||||
|
activePages.mainIndex = 0; |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.nested-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.cover-txt { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 22%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 74%; |
||||
|
height: 122rpx; |
||||
|
} |
||||
|
|
||||
|
.cover-txt2 { |
||||
|
width: 60% !important; |
||||
|
height: 134rpx !important; |
||||
|
} |
||||
|
|
||||
|
.cover-txt9 { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 0; |
||||
|
transform: translate(-50%, -20%); |
||||
|
z-index: 2; |
||||
|
width: 360rpx; |
||||
|
height: 280rpx; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 16%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 2; |
||||
|
width: 244rpx; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.qrcode-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
bottom: 0; |
||||
|
transform: translate(-50%, -25%); |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.qrCode { |
||||
|
width: 200rpx; |
||||
|
height: 200rpx; |
||||
|
} |
||||
|
|
||||
|
.code-txt { |
||||
|
color: #333; |
||||
|
font-size: 24rpx; |
||||
|
margin-top: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.gif-container { |
||||
|
background: #333333; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 400rpx; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.gif-style { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
object-fit: cover; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,90 @@ |
|||||
|
<template> |
||||
|
<view class="random-image-container"> |
||||
|
<RandomImage :images="randomImages" ref="randomImageRef" /> |
||||
|
<image @click="goBack" class="back-icon" src="/static/back.png" mode=""></image> |
||||
|
<image class="save-icon" src="/static/save-btn.png" mode=""></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
onMounted, |
||||
|
nextTick |
||||
|
} from 'vue'; |
||||
|
import RandomImage from '../../components/chapter3/RandomImage.vue'; |
||||
|
|
||||
|
// 获取传递的图片数组 |
||||
|
const randomImages = ref([]); |
||||
|
const randomImageRef = ref(null); |
||||
|
onMounted(() => { |
||||
|
// 尝试从本地存储获取 |
||||
|
let storedImages = uni.getStorageSync('randomImages'); |
||||
|
|
||||
|
// 检查全局数据 |
||||
|
const app = getApp(); |
||||
|
|
||||
|
if (storedImages) { |
||||
|
// 从本地存储恢复 |
||||
|
randomImages.value = JSON.parse(storedImages); |
||||
|
} else if (app.globalData && app.globalData.randomImages) { |
||||
|
// 从全局数据获取 |
||||
|
randomImages.value = app.globalData.randomImages; |
||||
|
} else { |
||||
|
// 使用默认图片数组 |
||||
|
randomImages.value = [ |
||||
|
'/static/images/chapter3/random/image1.png', |
||||
|
'/static/images/chapter3/random/image2.png', |
||||
|
'/static/images/chapter3/random/image3.png', |
||||
|
'/static/images/chapter3/random/image4.png', |
||||
|
'/static/images/chapter3/random/image5.png', |
||||
|
'/static/images/chapter3/random/image6.png', |
||||
|
'/static/images/chapter3/random/image7.png' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
// 等待组件挂载完成 |
||||
|
nextTick(() => { |
||||
|
if (randomImageRef.value && randomImageRef.value.selectRandomImage) { |
||||
|
randomImageRef.value.selectRandomImage(); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
// 返回第三章封面 |
||||
|
const goBack = () => { |
||||
|
const app = getApp(); |
||||
|
app.globalData.mainSliderIndex = 4; // 第三章封面的索引 |
||||
|
uni.navigateTo({ |
||||
|
url: '/pages/home/home' |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.random-image-container { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.back-icon { |
||||
|
width: 48rpx; |
||||
|
height: 48rpx; |
||||
|
position: absolute; |
||||
|
left: 40rpx; |
||||
|
top: 40rpx; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.save-icon { |
||||
|
width: 340rpx; |
||||
|
height: 60rpx; |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
z-index: 10; |
||||
|
bottom: 14%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,880 @@ |
|||||
|
<template> |
||||
|
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" :duration="300"> |
||||
|
<swiper-item v-for="(_, outerIndex) in 7" :key="outerIndex"> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[outerIndex]"> |
||||
|
<image v-show="shouldShowContent(outerIndex)" class="bg-image" |
||||
|
:src="`/static/images/chapter4/detail${outerIndex+1}/cover-bg.png`" mode="" :lazy-load="true"> |
||||
|
</image> |
||||
|
|
||||
|
<view v-show="shouldShowContent(outerIndex)" class="content-layer"> |
||||
|
<swiper class="image-swiper" circular :indicator-dots="false" |
||||
|
@change="(e) => handleImageSwiperChange(e, outerIndex)" :autoplay="false" |
||||
|
:current="innerSwiperIndices[outerIndex]" :key="`inner-swiper-${outerIndex}`"> |
||||
|
<swiper-item v-for="index in 7" :key="index"> |
||||
|
<view class="swiper-item-container"> |
||||
|
<view class="image-wrapper" :class="{'selected-image': selectedImages[outerIndex] === index-1 && userSelected[outerIndex], |
||||
|
'image-wrapper-full': [0, 3, 5, 6].includes(outerIndex), |
||||
|
'image-wrapper-compact': [1, 2, 4].includes(outerIndex) |
||||
|
}" @click="selectImage(outerIndex, index-1)"> |
||||
|
<image |
||||
|
:src="`/static/images/chapter4/detail${outerIndex+1}/swiper-img${index}.png`" |
||||
|
class="swiper-image" mode="aspectFit" :lazy-load="true" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
|
||||
|
<!-- 自定义指示器 --> |
||||
|
<view class="indicator-container"> |
||||
|
<view v-for="index in 7" :key="index" class="indicator-dot" |
||||
|
:class="{ 'active': innerSwiperIndices[outerIndex] === index - 1 }" |
||||
|
@click="() => changeSlide(index - 1, outerIndex)"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view v-show="shouldShowContent(outerIndex)" class="content-layer2"> |
||||
|
<!-- 点击确认找一找相似度 按钮 --> |
||||
|
<image @click="findSimilarity(outerIndex)" class="find-btn" src="/static/find-btn.png" |
||||
|
:lazy-load="true" mode=""></image> |
||||
|
<view class="problem-description"> |
||||
|
<view class="proportion-txt" :class="{ |
||||
|
'proportion-txt-position': outerIndex === 0, |
||||
|
'proportion-txt-position2': outerIndex === 1, |
||||
|
'proportion-txt-position3': outerIndex === 2, |
||||
|
'proportion-txt-position4': outerIndex === 3, |
||||
|
'proportion-txt-position5': outerIndex === 4, |
||||
|
'proportion-txt-position6': outerIndex === 5, |
||||
|
'proportion-txt-position7': outerIndex === 6 |
||||
|
}"> |
||||
|
{{ percentages[outerIndex]}} |
||||
|
</view> |
||||
|
<image class="description-txt" |
||||
|
:class="{'description-txt6': outerIndex === 5,'description-txt7': outerIndex === 6}" |
||||
|
:src="`/static/images/chapter4/detail${outerIndex+1}/cover-txt.png`" :lazy-load="true" |
||||
|
mode=""></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view v-show="shouldShowContent(outerIndex)" class="arrow-content"> |
||||
|
<image class="arrow-down" src="/static/arrow-icon-black.png" :lazy-load="true" mode=""></image> |
||||
|
</view> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
|
||||
|
<swiper-item> |
||||
|
<view class="page-container home-page"> |
||||
|
<template v-if="loadedPages[7]"> |
||||
|
<image v-show="shouldShowContent(7)" class="bg-image" |
||||
|
src="/static/images/chapter4/harvestReport/cover-bg.png" :lazy-load="true" mode=""></image> |
||||
|
<view v-show="shouldShowContent(7)" class="harvestReport-layer"> |
||||
|
<view class="grid-cover img-shadow"> |
||||
|
<image class="grid-cover-bg" src="/static/images/chapter4/harvestReport/cover-grid.png" |
||||
|
:lazy-load="true" mode=""></image> |
||||
|
<view class="report-text-overlay" v-if="canAccessFinalReport"> |
||||
|
<view class="report-title">您的气味收获</view> |
||||
|
<view class="report-content"> |
||||
|
<view v-for="(item, index) in selectedResults" :key="index" class="result-item"> |
||||
|
{{ item }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<image @click="ShareMoments" class="chapter4-btn" src="/static/images/chapter4/share-img.png" |
||||
|
:lazy-load="true" mode=""></image> |
||||
|
</view> |
||||
|
<image @click="goBack" class="back-icon" src="/static/back.png" mode=""></image> |
||||
|
</template> |
||||
|
</view> |
||||
|
</swiper-item> |
||||
|
</swiper> |
||||
|
<MusicControl /> |
||||
|
<ShareGuide v-model="showShareGuide" :text="['点击右上角...', '选择分享到朋友圈']" @close="onShareGuideClose" /> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { |
||||
|
ref, |
||||
|
reactive, |
||||
|
computed, |
||||
|
onMounted, |
||||
|
watch |
||||
|
} from 'vue'; |
||||
|
|
||||
|
import { |
||||
|
onShow, |
||||
|
onHide, |
||||
|
onShareAppMessage, |
||||
|
onShareTimeline |
||||
|
} from '@dcloudio/uni-app'; |
||||
|
import ShareGuide from '@/components/ShareGuide.vue'; |
||||
|
// import { |
||||
|
// request |
||||
|
// } from '@/utils/request'; |
||||
|
const showShareGuide = ref(false); |
||||
|
const currentIndex = ref(0); |
||||
|
|
||||
|
// 为每个外部轮播项维护独立的内部轮播索引 |
||||
|
const innerSwiperIndices = reactive({ |
||||
|
0: 0, |
||||
|
1: 0, |
||||
|
2: 0, |
||||
|
3: 0, |
||||
|
4: 0, |
||||
|
5: 0, |
||||
|
6: 0, |
||||
|
}); |
||||
|
|
||||
|
// 记录每个模块的选中图片索引 |
||||
|
const selectedImages = reactive({ |
||||
|
0: null, |
||||
|
1: null, |
||||
|
2: null, |
||||
|
3: null, |
||||
|
4: null, |
||||
|
5: null, |
||||
|
6: null |
||||
|
}); |
||||
|
//选择状态跟随 |
||||
|
const userSelected = reactive({ |
||||
|
0: false, |
||||
|
1: false, |
||||
|
2: false, |
||||
|
3: false, |
||||
|
4: false, |
||||
|
5: false, |
||||
|
6: false |
||||
|
}); |
||||
|
// 存储每个模块的百分比数据 |
||||
|
const percentages = reactive({ |
||||
|
0: '', |
||||
|
1: '', |
||||
|
2: '', |
||||
|
3: '', |
||||
|
4: '', |
||||
|
5: '', |
||||
|
6: '' |
||||
|
}); |
||||
|
|
||||
|
// 记录哪些模块已经请求过数据 |
||||
|
const requestedData = reactive({ |
||||
|
0: false, |
||||
|
1: false, |
||||
|
2: false, |
||||
|
3: false, |
||||
|
4: false, |
||||
|
5: false, |
||||
|
6: false |
||||
|
}); |
||||
|
//记录哪些模块已经完成了相似度计算 |
||||
|
const similarityCompleted = reactive({ |
||||
|
0: false, |
||||
|
1: false, |
||||
|
2: false, |
||||
|
3: false, |
||||
|
4: false, |
||||
|
5: false, |
||||
|
6: false |
||||
|
}); |
||||
|
// 记录哪些页面已经加载过 |
||||
|
const loadedPages = reactive({ |
||||
|
0: false, |
||||
|
1: false, |
||||
|
2: false, |
||||
|
3: false, |
||||
|
4: false, |
||||
|
5: false, |
||||
|
6: false, |
||||
|
7: false |
||||
|
}); |
||||
|
|
||||
|
// 加载预缓冲区大小 |
||||
|
const preloadBuffer = 1; |
||||
|
|
||||
|
// 决定是否应该显示特定页面的内容 |
||||
|
const shouldShowContent = (index) => { |
||||
|
return Math.abs(index - currentIndex.value) <= preloadBuffer; |
||||
|
}; |
||||
|
|
||||
|
// 选择图片 |
||||
|
const selectImage = (moduleIndex, imageIndex) => { |
||||
|
// 更新选中状态 |
||||
|
selectedImages[moduleIndex] = imageIndex; |
||||
|
// 标记为用户主动选择 |
||||
|
userSelected[moduleIndex] = true; |
||||
|
// 同步更新swiper索引 |
||||
|
innerSwiperIndices[moduleIndex] = imageIndex; |
||||
|
|
||||
|
console.log(`选中模块${moduleIndex + 1}的图片${imageIndex + 1}`); |
||||
|
}; |
||||
|
|
||||
|
// 查找相似度 |
||||
|
const findSimilarity = async (moduleIndex) => { |
||||
|
// 如果没有选中图片或没有明确选择,标记当前显示的为选中 |
||||
|
if (selectedImages[moduleIndex] === null || !userSelected[moduleIndex]) { |
||||
|
selectedImages[moduleIndex] = innerSwiperIndices[moduleIndex]; |
||||
|
userSelected[moduleIndex] = true; |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '已选择当前图片', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
fetchSimilarityData(moduleIndex); |
||||
|
}; |
||||
|
const fetchSimilarityData = async (moduleIndex) => { |
||||
|
uni.showLoading({ |
||||
|
title: '计算相似度中...' |
||||
|
}); |
||||
|
|
||||
|
try { |
||||
|
const imageIndex = selectedImages[moduleIndex]; |
||||
|
|
||||
|
// 调用API获取百分比数据 |
||||
|
const result = await fetchPercentage(moduleIndex + 1, imageIndex + 1); |
||||
|
|
||||
|
// 更新百分比显示 |
||||
|
percentages[moduleIndex] = result.percentage; |
||||
|
|
||||
|
// 标记该模块已完成相似度计算 |
||||
|
similarityCompleted[moduleIndex] = true; |
||||
|
|
||||
|
// 标记该模块已请求过数据 |
||||
|
requestedData[moduleIndex] = true; |
||||
|
|
||||
|
uni.hideLoading(); |
||||
|
|
||||
|
uni.showToast({ |
||||
|
title: '计算完成', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error('获取相似度失败:', error); |
||||
|
uni.hideLoading(); |
||||
|
uni.showToast({ |
||||
|
title: '获取数据失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
// 接口请求函数 |
||||
|
const fetchPercentage = async (moduleId, imageId) => { |
||||
|
try { |
||||
|
// 模拟API请求 |
||||
|
// const res = await request({ |
||||
|
// url: '/api/similarity', |
||||
|
// method: 'POST', |
||||
|
// data: { |
||||
|
// moduleId, |
||||
|
// imageId |
||||
|
// } |
||||
|
// }); |
||||
|
|
||||
|
// 模拟API响应 |
||||
|
return new Promise((resolve) => { |
||||
|
setTimeout(() => { |
||||
|
// 生成随机百分比,实际项目中应该使用API返回的数据 |
||||
|
const percentage = Math.floor(Math.random() * 41) + 60 + '%'; |
||||
|
resolve({ |
||||
|
success: true, |
||||
|
percentage, |
||||
|
moduleId, |
||||
|
imageId |
||||
|
}); |
||||
|
}, 800); |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error('API请求失败:', error); |
||||
|
throw error; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 分享到朋友圈 |
||||
|
const ShareMoments = () => { |
||||
|
uni.showShareMenu({ |
||||
|
withShareTicket: true, |
||||
|
menus: ['shareAppMessage', 'shareTimeline'] |
||||
|
}); |
||||
|
|
||||
|
showShareGuide.value = true; |
||||
|
}; |
||||
|
|
||||
|
const onShareGuideClose = () => { |
||||
|
console.log('分享引导已关闭'); |
||||
|
}; |
||||
|
|
||||
|
const goBack = () => { |
||||
|
// 添加防抖 |
||||
|
if (goBack.isNavigating) return; |
||||
|
goBack.isNavigating = true; |
||||
|
|
||||
|
const app = getApp(); |
||||
|
app.globalData.mainSliderIndex = 5; |
||||
|
uni.redirectTo({ |
||||
|
url: '/pages/home/home', |
||||
|
complete: () => { |
||||
|
// 延迟重置防抖标记 |
||||
|
setTimeout(() => { |
||||
|
goBack.isNavigating = false; |
||||
|
}, 500); |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
goBack.isNavigating = false; |
||||
|
|
||||
|
// 监听currentIndex变化,更新已加载页面状态 |
||||
|
watch(currentIndex, (newIndex) => { |
||||
|
// 标记当前页面和前后buffer页为已加载 |
||||
|
for (let i = Math.max(0, newIndex - preloadBuffer); i <= Math.min(7, newIndex + preloadBuffer); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
}, { |
||||
|
immediate: true |
||||
|
}); |
||||
|
|
||||
|
const handleSwiperChange = (e) => { |
||||
|
const newIndex = e.detail.current; |
||||
|
if (newIndex === 7) { |
||||
|
// 检查是否所有模块都完成了相似度计算 |
||||
|
if (!canAccessFinalReport.value) { |
||||
|
// 找到第一个未完成相似度计算的模块 |
||||
|
let firstIncompleteIndex = 0; |
||||
|
for (let i = 0; i < 7; i++) { |
||||
|
if (!similarityCompleted[i]) { |
||||
|
firstIncompleteIndex = i; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 显示提示 |
||||
|
uni.showToast({ |
||||
|
title: `${moduleTips[firstIncompleteIndex]}并点击确认按钮`, |
||||
|
icon: 'none', |
||||
|
duration: 2500 |
||||
|
}); |
||||
|
|
||||
|
// 跳转到未完成的模块 |
||||
|
setTimeout(() => { |
||||
|
currentIndex.value = firstIncompleteIndex; |
||||
|
}, 1000); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 正常情况下更新索引 |
||||
|
currentIndex.value = newIndex; |
||||
|
}; |
||||
|
|
||||
|
const handleImageSwiperChange = (e, outerIndex) => { |
||||
|
// 只更新内层swiper索引,不更新选中状态 |
||||
|
innerSwiperIndices[outerIndex] = e.detail.current; |
||||
|
|
||||
|
// 如果没有明确选择过,则选中状态也跟随更新 |
||||
|
// 如果之前已经选择过,则保持用户的选择 |
||||
|
if (!userSelected[outerIndex]) { |
||||
|
selectedImages[outerIndex] = e.detail.current; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const changeSlide = (index, outerIndex) => { |
||||
|
innerSwiperIndices[outerIndex] = index; |
||||
|
// 更新选中状态并标记为用户选择 |
||||
|
selectedImages[outerIndex] = index; |
||||
|
userSelected[outerIndex] = true; |
||||
|
}; |
||||
|
|
||||
|
const getCurrentInnerIndex = (outerIndex) => { |
||||
|
return innerSwiperIndices[outerIndex || currentIndex.value]; |
||||
|
}; |
||||
|
// 检查是否可以访问最终报告页 |
||||
|
const canAccessFinalReport = computed(() => { |
||||
|
// 检查所有模块是否都已选择 |
||||
|
f |
||||
|
for (let i = 0; i < 7; i++) { |
||||
|
if (!similarityCompleted[i]) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
}); |
||||
|
// 模块提示文本 |
||||
|
const moduleTips = [ |
||||
|
'请选择最有味道的歌曲', |
||||
|
'请选择最有味道的人物', |
||||
|
'请选择最有味道的食物', |
||||
|
'请选择最有味道的句子', |
||||
|
'请选择最有味道的风景', |
||||
|
'请选择最有味道的动漫', |
||||
|
'请选择最有味道的情感' |
||||
|
]; |
||||
|
const flavorTexts = { |
||||
|
// 歌曲文案 |
||||
|
0: [ |
||||
|
"音符在舌尖跳跃,这首歌如同陈年佳酿", |
||||
|
], |
||||
|
// 人物文案 |
||||
|
1: [ |
||||
|
"如墨般浓郁的性格,似茶般清冽的灵魂", |
||||
|
], |
||||
|
// 食物文案 |
||||
|
2: [ |
||||
|
"舌尖上的东方,是一场穿越时空的对话", |
||||
|
], |
||||
|
// 句子文案 |
||||
|
3: [ |
||||
|
"文字如酒,越品越醇,越读越深", |
||||
|
], |
||||
|
// 风景文案 |
||||
|
4: [ |
||||
|
"山水间藏着天地的密语,云雾里是岁月的轻叹", |
||||
|
], |
||||
|
// 动漫文案 |
||||
|
5: [ |
||||
|
"二次元中的东方灵魂,是跨越文化的共鸣", |
||||
|
], |
||||
|
// 情感文案 |
||||
|
6: [ |
||||
|
"情感如茶,或浓或淡,皆有韵味", |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
// 获取风味百分比描述 |
||||
|
const getFlavorDescription = (percentage) => { |
||||
|
const value = parseInt(percentage); |
||||
|
return; |
||||
|
}; |
||||
|
|
||||
|
// 修改后的selectedResults方法 |
||||
|
const selectedResults = computed(() => { |
||||
|
const results = []; |
||||
|
|
||||
|
for (let i = 0; i < 7; i++) { |
||||
|
if (userSelected[i]) { |
||||
|
// 从对应模块的文案库中随机选择一条 |
||||
|
const textOptions = flavorTexts[i]; |
||||
|
const randomIndex = Math.floor(Math.random() * textOptions.length); |
||||
|
const text = textOptions[randomIndex]; |
||||
|
results.push(`${text}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return results; |
||||
|
}); |
||||
|
// 页面加载完成后预加载第一页 |
||||
|
onMounted(() => { |
||||
|
// 设置当前页为0,确保第一页和相邻页面的内容被加载 |
||||
|
currentIndex.value = 0; |
||||
|
|
||||
|
// 初始化标记第一页和相邻页为已加载 |
||||
|
for (let i = 0; i <= Math.min(preloadBuffer, 7); i++) { |
||||
|
loadedPages[i] = true; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 微信分享配置 |
||||
|
// #ifdef MP-WEIXIN |
||||
|
onShareAppMessage(() => { |
||||
|
return { |
||||
|
title: '细嗅东方|「Epic Soul」阅读体 issue02', |
||||
|
mpId: 'wx8954209bb3ad489e', |
||||
|
path: '/pages/chapter4/cover', |
||||
|
imageUrl: '/static/share-img.jpg' |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
onShareTimeline(() => { |
||||
|
return { |
||||
|
title: '细嗅东方|「Epic Soul」阅读体 issue02', |
||||
|
query: '', |
||||
|
imageUrl: '/static/share-img.jpg' |
||||
|
} |
||||
|
}); |
||||
|
// #endif |
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<style scoped> |
||||
|
.main-swiper { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-container { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.home-page { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.swiper-item-container { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 10rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.image-swiper { |
||||
|
width: 100%; |
||||
|
height: 500rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.image-wrapper { |
||||
|
position: relative; |
||||
|
box-sizing: border-box; |
||||
|
transition: all 0.3s; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.image-wrapper-full { |
||||
|
width: 80%; |
||||
|
} |
||||
|
|
||||
|
.image-wrapper-compact { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.selected-image { |
||||
|
border: 4rpx solid #f0ad4e; |
||||
|
box-shadow: 0 0 10rpx rgba(240, 173, 78, 0.6); |
||||
|
background-color: rgba(240, 173, 78, 0.1); |
||||
|
} |
||||
|
|
||||
|
.swiper-image { |
||||
|
max-width: 100%; |
||||
|
max-height: 100%; |
||||
|
display: block; |
||||
|
object-fit: contain; |
||||
|
} |
||||
|
|
||||
|
.bg-image { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
/* 内容层样式 */ |
||||
|
.content-layer { |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
flex-direction: column; |
||||
|
margin-top: 20%; |
||||
|
box-sizing: border-box; |
||||
|
/* 添加此行 */ |
||||
|
padding: 0; |
||||
|
/* 确保没有内边距 */ |
||||
|
} |
||||
|
|
||||
|
.content-layer2 { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
position: relative; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.find-btn { |
||||
|
width: 300rpx; |
||||
|
height: 50rpx; |
||||
|
margin-top: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.problem-description { |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
position: relative; |
||||
|
margin-top: 150rpx; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt { |
||||
|
font-size: 48rpx; |
||||
|
color: #0f944f; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt-position { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt-position2 { |
||||
|
position: absolute; |
||||
|
right: 120rpx; |
||||
|
top: 0; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt-position3 { |
||||
|
position: absolute; |
||||
|
right: 20rpx; |
||||
|
top: 0; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt-position4 { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt-position5 { |
||||
|
position: absolute; |
||||
|
right: 120rpx; |
||||
|
top: 0; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.description-txt { |
||||
|
width: 400rpx; |
||||
|
height: 120rpx; |
||||
|
} |
||||
|
|
||||
|
.description-txt6 { |
||||
|
width: 460rpx !important; |
||||
|
height: 50rpx !important; |
||||
|
} |
||||
|
|
||||
|
.description-txt7 { |
||||
|
width: 460rpx !important; |
||||
|
height: 120rpx !important; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt-position6 { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
line-height: 1; |
||||
|
font-size: 38rpx !important; |
||||
|
} |
||||
|
|
||||
|
.proportion-txt-position7 { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.image-swiper { |
||||
|
width: 100%; |
||||
|
height: 500rpx; |
||||
|
} |
||||
|
|
||||
|
.swiper-image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
object-fit: contain; |
||||
|
} |
||||
|
|
||||
|
.back-icon { |
||||
|
width: 48rpx; |
||||
|
height: 48rpx; |
||||
|
position: absolute; |
||||
|
left: 40rpx; |
||||
|
top: 40rpx; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
.indicator-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
margin: 50rpx; |
||||
|
} |
||||
|
|
||||
|
.indicator-dot { |
||||
|
width: 14rpx; |
||||
|
height: 14rpx; |
||||
|
border-radius: 50%; |
||||
|
background-color: rgba(255, 255, 255, 0.5); |
||||
|
margin: 0 8rpx; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.indicator-dot.active { |
||||
|
background-color: #ffffff; |
||||
|
transform: scale(1.2); |
||||
|
} |
||||
|
|
||||
|
.harvestReport-layer { |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
display: flex; |
||||
|
margin-top: 42%; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.grid-cover { |
||||
|
width: 85%; |
||||
|
height: 60%; |
||||
|
} |
||||
|
|
||||
|
.grid-cover-bg { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.chapter4-btn { |
||||
|
width: 280rpx; |
||||
|
height: 60rpx; |
||||
|
margin-top: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.img-shadow { |
||||
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), |
||||
|
0 6px 20px 0 rgba(0, 0, 0, 0.19); |
||||
|
transition: box-shadow 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.arrow-content { |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
bottom: 10%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, 0); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.arrow-down { |
||||
|
width: 200rpx; |
||||
|
height: 40rpx; |
||||
|
animation: bounce 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes bounce { |
||||
|
|
||||
|
0%, |
||||
|
20%, |
||||
|
50%, |
||||
|
80%, |
||||
|
100% { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
|
||||
|
40% { |
||||
|
transform: translateY(-20rpx); |
||||
|
} |
||||
|
|
||||
|
60% { |
||||
|
transform: translateY(-10rpx); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.report-text-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 40rpx; |
||||
|
box-sizing: border-box; |
||||
|
z-index: 8; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.report-title { |
||||
|
font-size: 48rpx; |
||||
|
color: #f0ad4e; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 40rpx; |
||||
|
text-shadow: 0 0 10rpx rgba(0, 0, 0, 0.3); |
||||
|
animation: fade-in 1.5s ease-out; |
||||
|
} |
||||
|
|
||||
|
.report-content { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.result-item { |
||||
|
font-size: 28rpx; |
||||
|
color: #333333; |
||||
|
line-height: 1.8; |
||||
|
margin-bottom: 20rpx; |
||||
|
text-shadow: 0 0 8rpx rgba(0, 0, 0, 0.5); |
||||
|
opacity: 0; |
||||
|
animation: slide-in 0.5s ease-out forwards; |
||||
|
} |
||||
|
|
||||
|
.result-item:nth-child(1) { |
||||
|
animation-delay: 0.2s; |
||||
|
} |
||||
|
|
||||
|
.result-item:nth-child(2) { |
||||
|
animation-delay: 0.4s; |
||||
|
} |
||||
|
|
||||
|
.result-item:nth-child(3) { |
||||
|
animation-delay: 0.6s; |
||||
|
} |
||||
|
|
||||
|
.result-item:nth-child(4) { |
||||
|
animation-delay: 0.8s; |
||||
|
} |
||||
|
|
||||
|
.result-item:nth-child(5) { |
||||
|
animation-delay: 1.0s; |
||||
|
} |
||||
|
|
||||
|
.result-item:nth-child(6) { |
||||
|
animation-delay: 1.2s; |
||||
|
} |
||||
|
|
||||
|
.result-item:nth-child(7) { |
||||
|
animation-delay: 1.4s; |
||||
|
} |
||||
|
|
||||
|
@keyframes fade-in { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(-20rpx); |
||||
|
} |
||||
|
|
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes slide-in { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateX(-30rpx); |
||||
|
} |
||||
|
|
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateX(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.grid-cover { |
||||
|
position: relative; |
||||
|
height: 60%; |
||||
|
z-index: 7; |
||||
|
} |
||||
|
</style> |
Loading…
Reference in new issue