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