18 changed files with 2552 additions and 1 deletions
@ -0,0 +1,187 @@ |
|||
<template> |
|||
<view |
|||
class="swipe-to-next" |
|||
@touchstart="handleTouchStart" |
|||
@touchend="handleTouchEnd" |
|||
> |
|||
<slot></slot> |
|||
<!-- 提示文字 --> |
|||
<view v-if="showTip && shouldShowTip" class="bottom-tip"> |
|||
<text>{{ tipText }}</text> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'SwipeToNext', |
|||
props: { |
|||
// 是否在最后一页 |
|||
isLastSlide: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 跳转的目标路径 |
|||
targetPath: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
// 是否显示提示文字 |
|||
showTip: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 提示文字内容 |
|||
tipText: { |
|||
type: String, |
|||
default: '上滑动进入下一章节' |
|||
}, |
|||
// 滑动阈值(px) |
|||
swipeThreshold: { |
|||
type: Number, |
|||
default: 100 |
|||
}, |
|||
// 延迟时间(ms) |
|||
delayTime: { |
|||
type: Number, |
|||
default: 500 |
|||
}, |
|||
// 是否启用延迟机制 |
|||
enableDelay: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 是否总是启用跳转(忽略isLastSlide状态) |
|||
alwaysEnable: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
touchStartY: 0, // 触摸开始Y坐标 |
|||
touchEndY: 0, // 触摸结束Y坐标 |
|||
canJump: false, // 是否允许跳转 |
|||
delayTimer: null // 延迟定时器 |
|||
} |
|||
}, |
|||
computed: { |
|||
// 是否应该显示提示 |
|||
shouldShowTip() { |
|||
return this.alwaysEnable || this.isLastSlide; |
|||
}, |
|||
// 是否应该允许触摸检测 |
|||
shouldEnableTouch() { |
|||
return this.alwaysEnable || this.isLastSlide; |
|||
} |
|||
}, |
|||
watch: { |
|||
isLastSlide(newVal) { |
|||
this.handleSlideChange(newVal); |
|||
}, |
|||
alwaysEnable: { |
|||
handler(newVal) { |
|||
if (newVal) { |
|||
// 如果启用总是跳转,立即处理 |
|||
this.handleSlideChange(true); |
|||
} |
|||
}, |
|||
immediate: true |
|||
} |
|||
}, |
|||
beforeDestroy() { |
|||
// 清理定时器 |
|||
if (this.delayTimer) { |
|||
clearTimeout(this.delayTimer); |
|||
} |
|||
}, |
|||
methods: { |
|||
// 处理滑动状态变化 |
|||
handleSlideChange(isActive) { |
|||
console.log('ppsls'); |
|||
if (isActive || this.alwaysEnable) { |
|||
// 到达最后一页或总是启用 |
|||
this.canJump = false; |
|||
if (this.enableDelay && !this.alwaysEnable) { |
|||
// 启用延迟机制(仅当不是总是启用时) |
|||
this.delayTimer = setTimeout(() => { |
|||
if (this.shouldEnableTouch) { |
|||
this.canJump = true; |
|||
} |
|||
}, this.delayTime); |
|||
} else { |
|||
// 不启用延迟或总是启用,立即允许跳转 |
|||
this.canJump = true; |
|||
} |
|||
} else { |
|||
// 离开最后一页且不是总是启用 |
|||
this.canJump = false; |
|||
if (this.delayTimer) { |
|||
clearTimeout(this.delayTimer); |
|||
this.delayTimer = null; |
|||
} |
|||
} |
|||
}, |
|||
// 触摸开始 |
|||
handleTouchStart(e) { |
|||
console.log('。。。。。。。。。。///////////'); |
|||
// 检查是否应该允许触摸 |
|||
if (this.shouldEnableTouch && (this.canJump || !this.enableDelay || this.alwaysEnable)) { |
|||
this.touchStartY = e.touches[0].clientY; |
|||
} |
|||
}, |
|||
// 触摸结束 |
|||
handleTouchEnd(e) { |
|||
// 检查是否满足跳转条件 |
|||
if (!this.shouldEnableTouch || !this.touchStartY) { |
|||
return; |
|||
} |
|||
|
|||
// 如果启用延迟机制但还不能跳转,且不是总是启用模式,则返回 |
|||
if (this.enableDelay && !this.canJump && !this.alwaysEnable) { |
|||
return; |
|||
} |
|||
|
|||
this.touchEndY = e.changedTouches[0].clientY; |
|||
const deltaY = this.touchStartY - this.touchEndY; |
|||
|
|||
// 向上滑动且滑动距离大于阈值才跳转 |
|||
if (deltaY > this.swipeThreshold) { |
|||
console.log('向上滑动触发跳转,目标路径:', this.targetPath); |
|||
this.$emit('swipe-to-next', this.targetPath); |
|||
uni.navigateTo({ |
|||
url:this.targetPath |
|||
}); |
|||
} |
|||
|
|||
// 重置触摸位置 |
|||
this.touchStartY = 0; |
|||
this.touchEndY = 0; |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swipe-to-next { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
} |
|||
|
|||
.bottom-tip { |
|||
position: absolute; |
|||
bottom: 50rpx; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
background: rgba(0, 0, 0, 0.6); |
|||
padding: 10rpx 20rpx; |
|||
border-radius: 50rpx; |
|||
z-index: 999; |
|||
|
|||
text { |
|||
color: #fff; |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,293 @@ |
|||
# SwipeToNext 组件使用文档 |
|||
|
|||
## 组件介绍 |
|||
|
|||
`SwipeToNext` 是一个通用的触底跳转组件,封装了手势检测、延迟防抖、提示文字等功能,可以在任何需要滑动跳转的页面中使用。 |
|||
|
|||
## 组件特性 |
|||
|
|||
- ✅ 手势滑动检测 |
|||
- ✅ 防误触发机制(延迟允许跳转) |
|||
- ✅ 可配置的滑动阈值 |
|||
- ✅ 自定义提示文字 |
|||
- ✅ 支持事件监听 |
|||
- ✅ 完全可配置的参数 |
|||
|
|||
## 使用方法 |
|||
|
|||
### 1. 引入组件 |
|||
|
|||
```vue |
|||
<script> |
|||
import SwipeToNext from '@/components/SwipeToNext.vue'; |
|||
|
|||
export default { |
|||
components: { |
|||
SwipeToNext |
|||
} |
|||
} |
|||
</script> |
|||
``` |
|||
|
|||
### 2. 基础使用 |
|||
|
|||
```vue |
|||
<template> |
|||
<SwipeToNext |
|||
:is-last-slide="isLastSlide" |
|||
:target-path="'/next/page'" |
|||
@swipe-to-next="handleSwipeToNext" |
|||
> |
|||
<!-- 你的页面内容 --> |
|||
<view class="content"> |
|||
<!-- 轮播图或其他内容 --> |
|||
</view> |
|||
</SwipeToNext> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
isLastSlide: false |
|||
} |
|||
}, |
|||
methods: { |
|||
// 处理swiper切换或其他逻辑 |
|||
handlePageChange() { |
|||
// 根据你的逻辑设置 isLastSlide |
|||
this.isLastSlide = true; // 当到达最后一页时 |
|||
}, |
|||
handleSwipeToNext(targetPath) { |
|||
console.log('即将跳转到:', targetPath); |
|||
// 可以在这里添加额外的逻辑,如数据统计 |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
``` |
|||
|
|||
### 3. 完整配置使用 |
|||
|
|||
```vue |
|||
<template> |
|||
<SwipeToNext |
|||
:is-last-slide="isLastSlide" |
|||
:target-path="'/next/page'" |
|||
:show-tip="true" |
|||
tip-text="向上滑动查看更多内容" |
|||
:swipe-threshold="100" |
|||
:delay-time="800" |
|||
:enable-delay="true" |
|||
@swipe-to-next="handleSwipeToNext" |
|||
> |
|||
<!-- 你的页面内容 --> |
|||
<view class="content"> |
|||
<!-- 内容区域 --> |
|||
</view> |
|||
</SwipeToNext> |
|||
</template> |
|||
``` |
|||
|
|||
## Props 参数 |
|||
|
|||
| 参数名 | 类型 | 默认值 | 必填 | 说明 | |
|||
|--------|------|--------|------|------| |
|||
| `isLastSlide` | Boolean | `false` | ✅ | 是否在最后一页/最后一个状态 | |
|||
| `targetPath` | String | - | ✅ | 跳转的目标路径 | |
|||
| `showTip` | Boolean | `true` | ❌ | 是否显示提示文字 | |
|||
| `tipText` | String | `'继续向上滑动进入下一章节'` | ❌ | 提示文字内容 | |
|||
| `swipeThreshold` | Number | `80` | ❌ | 滑动阈值(像素) | |
|||
| `delayTime` | Number | `500` | ❌ | 延迟允许跳转的时间(毫秒) | |
|||
| `enableDelay` | Boolean | `true` | ❌ | 是否启用延迟机制 | |
|||
| `alwaysEnable` | Boolean | `false` | ❌ | 是否总是启用跳转(忽略isLastSlide状态) | |
|||
|
|||
## Events 事件 |
|||
|
|||
| 事件名 | 参数 | 说明 | |
|||
|--------|------|------| |
|||
| `swipe-to-next` | `targetPath` | 触发跳转时的回调事件 | |
|||
|
|||
## 使用场景示例 |
|||
|
|||
### 场景1:图片轮播页面 |
|||
|
|||
```vue |
|||
<template> |
|||
<SwipeToNext |
|||
:is-last-slide="currentIndex === images.length - 1" |
|||
:target-path="'/gallery/next'" |
|||
> |
|||
<swiper @change="handleSwiperChange"> |
|||
<swiper-item v-for="(img, index) in images" :key="index"> |
|||
<image :src="img" mode="aspectFit" /> |
|||
</swiper-item> |
|||
</swiper> |
|||
</SwipeToNext> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
images: ['img1.jpg', 'img2.jpg', 'img3.jpg'] |
|||
} |
|||
}, |
|||
methods: { |
|||
handleSwiperChange(e) { |
|||
this.currentIndex = e.detail.current; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
``` |
|||
|
|||
### 场景2:文章阅读页面 |
|||
|
|||
```vue |
|||
<template> |
|||
<SwipeToNext |
|||
:is-last-slide="isReadComplete" |
|||
:target-path="'/article/next'" |
|||
tip-text="继续滑动阅读下一篇文章" |
|||
:swipe-threshold="60" |
|||
> |
|||
<scroll-view @scrolltolower="handleScrollToBottom"> |
|||
<view class="article-content"> |
|||
<!-- 文章内容 --> |
|||
</view> |
|||
</scroll-view> |
|||
</SwipeToNext> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
isReadComplete: false |
|||
} |
|||
}, |
|||
methods: { |
|||
handleScrollToBottom() { |
|||
// 滚动到底部时认为阅读完成 |
|||
this.isReadComplete = true; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
``` |
|||
|
|||
### 场景4:单张图片或总是启用触底跳转 |
|||
|
|||
```vue |
|||
<template> |
|||
<SwipeToNext |
|||
:always-enable="true" |
|||
:target-path="'/next/chapter'" |
|||
tip-text="向上滑动查看下一内容" |
|||
:enable-delay="false" |
|||
> |
|||
<view class="single-image"> |
|||
<image :src="imageUrl" mode="aspectFit" /> |
|||
</view> |
|||
</SwipeToNext> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
imageUrl: 'single-image.jpg' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
``` |
|||
|
|||
```vue |
|||
<template> |
|||
<SwipeToNext |
|||
:is-last-slide="currentStep === totalSteps - 1" |
|||
:target-path="'/guide/complete'" |
|||
tip-text="向上滑动完成引导" |
|||
> |
|||
<view class="guide-step"> |
|||
<view class="step-content"> |
|||
步骤 {{ currentStep + 1 }} / {{ totalSteps }} |
|||
</view> |
|||
<button @click="nextStep">下一步</button> |
|||
</view> |
|||
</SwipeToNext> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
currentStep: 0, |
|||
totalSteps: 5 |
|||
} |
|||
}, |
|||
methods: { |
|||
nextStep() { |
|||
if (this.currentStep < this.totalSteps - 1) { |
|||
this.currentStep++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
``` |
|||
|
|||
## 特殊情况处理 |
|||
|
|||
### 单张图片问题 |
|||
|
|||
当只有一张图片时,传统的 `isLastSlide` 逻辑不适用。这时可以使用 `alwaysEnable` 参数: |
|||
|
|||
```vue |
|||
<!-- 单张图片的解决方案 --> |
|||
<SwipeToNext |
|||
:always-enable="true" |
|||
:target-path="'/next/page'" |
|||
:enable-delay="false" |
|||
> |
|||
<image src="single-image.jpg" /> |
|||
</SwipeToNext> |
|||
``` |
|||
|
|||
### 参数优先级 |
|||
|
|||
当 `alwaysEnable="true"` 时: |
|||
- 忽略 `isLastSlide` 的值 |
|||
- 总是显示提示文字 |
|||
- 总是允许触底跳转 |
|||
- 建议设置 `enableDelay="false"` 以获得更好的响应速度 |
|||
|
|||
|
|||
|
|||
1. **确保正确设置 `isLastSlide`**:这是控制是否允许跳转的关键属性 |
|||
2. **路径格式**:`targetPath` 需要是有效的 uni-app 路由路径 |
|||
3. **性能考虑**:如果不需要延迟机制,可以设置 `enableDelay: false` 来提高响应速度 |
|||
4. **样式覆盖**:组件内的提示文字样式可以通过全局样式覆盖 |
|||
5. **事件监听**:建议监听 `swipe-to-next` 事件进行数据统计或其他操作 |
|||
|
|||
## 自定义样式 |
|||
|
|||
如果需要自定义提示文字的样式,可以在页面中添加: |
|||
|
|||
```scss |
|||
// 覆盖组件样式 |
|||
.swipe-to-next .bottom-tip { |
|||
bottom: 200rpx !important; // 调整位置 |
|||
background: rgba(255, 255, 255, 0.9) !important; // 改变背景色 |
|||
|
|||
text { |
|||
color: #333 !important; // 改变文字颜色 |
|||
font-size: 32rpx !important; // 改变字体大小 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
这个组件极大地简化了触底跳转功能的实现,让你可以专注于业务逻辑而不用重复编写相同的手势检测代码。 |
@ -0,0 +1,25 @@ |
|||
{ |
|||
"setting": { |
|||
"es6": true, |
|||
"postcss": true, |
|||
"minified": true, |
|||
"uglifyFileName": false, |
|||
"enhance": true, |
|||
"packNpmRelationList": [], |
|||
"babelSetting": { |
|||
"ignore": [], |
|||
"disablePlugins": [], |
|||
"outputPath": "" |
|||
}, |
|||
"useCompilerPlugins": false, |
|||
"minifyWXML": true |
|||
}, |
|||
"compileType": "miniprogram", |
|||
"simulatorPluginLibVersion": {}, |
|||
"packOptions": { |
|||
"ignore": [], |
|||
"include": [] |
|||
}, |
|||
"appid": "wx8954209bb3ad489e", |
|||
"editorSetting": {} |
|||
} |
@ -0,0 +1,14 @@ |
|||
{ |
|||
"libVersion": "3.10.0", |
|||
"projectname": "EpicSoul", |
|||
"setting": { |
|||
"urlCheck": true, |
|||
"coverView": true, |
|||
"lazyloadPlaceholderEnable": false, |
|||
"skylineRenderEnable": false, |
|||
"preloadBackgroundData": false, |
|||
"autoAudits": false, |
|||
"showShadowRootInWxmlPanel": true, |
|||
"compileHotReLoad": true |
|||
} |
|||
} |
@ -0,0 +1,218 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<SwipeToNext :is-last-slide="isLastSlide" :target-path="'/xqk/chapter2/index'"> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
<!-- <template v-if="index === 1"> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter1/img2-dian.png" |
|||
v-for="i in 3" :key="i" mode="widthFix" :class="['module'+(i+1)]" |
|||
@click="openPopup(i+1)"></image> |
|||
</template> --> |
|||
|
|||
<template v-if="index == 2"> |
|||
<video :src="showImg('/uploads/20250903/7af32cc4f824b724560f78c597c85864.mp4')" |
|||
style="width: 100vw;height: 30vh;" objectFit="cover"></video> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</SwipeToNext> |
|||
<!-- 第二页气泡弹框 --> |
|||
<!-- <uni-popup ref="chapterPopup"> |
|||
<view style="width: 100vw;height: 100vh;" @click="$refs.chapterPopup.close()"> |
|||
<image :src="getImageUrl(`img2-${popupIndex}.png`)" mode="widthFix" :class="[`img2-${popupIndex}`]"> |
|||
</image> |
|||
</view> |
|||
</uni-popup> --> |
|||
|
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
import SwipeToNext from '@/components/SwipeToNext.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu, |
|||
SwipeToNext |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 1, |
|||
swiperImages: [ |
|||
this.showImg('/uploads/20250903/24303e4b7218eaf3d857c846417eb490.png'), |
|||
this.showImg('/uploads/20250903/17495ef65648c64c31920d312301e991.png'), |
|||
this.showImg('/uploads/20250903/92d6f1c6f8f7de040f3c31c8faf98927.png'), |
|||
], |
|||
animationConfig: { |
|||
delay: 0.5, |
|||
duration: 3, |
|||
keyframes: { |
|||
start: 1, |
|||
first: 0.8, |
|||
second: 1.2, |
|||
third: 0.9, |
|||
end: 1.1 |
|||
} |
|||
}, |
|||
popupIndex: 1, |
|||
isLastSlide:false, |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
this.currentIndex = option.currentIndex || 0 |
|||
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1; |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1 |
|||
}, |
|||
handleSwiperChange(e) { |
|||
this.currentIndex = e.detail.current; |
|||
if (this.currentIndex == this.swiperImages.length - 1) { |
|||
this.navIndex = 2; |
|||
this.isLastSlide = true; |
|||
} else { |
|||
this.navIndex = 1 |
|||
this.isLastSlide = false; |
|||
} |
|||
}, |
|||
// 第二页气泡弹框 |
|||
openPopup(i) { |
|||
this.popupIndex = i |
|||
this.$refs.chapterPopup.open(); |
|||
}, |
|||
// 生成图片完整 URL |
|||
getImageUrl(path) { |
|||
if (typeof path === 'object') { |
|||
path = path.url; |
|||
} |
|||
return `https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter1/${path}`; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% 100%; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.module1 { |
|||
position: absolute; |
|||
width: 52.1rpx; |
|||
top: 1020rpx; |
|||
left: 235rpx; |
|||
animation: breath1 3s ease-in-out infinite; |
|||
} |
|||
|
|||
.module2 { |
|||
position: absolute; |
|||
width: 52.1rpx; |
|||
top: 760rpx; |
|||
left: 317rpx; |
|||
animation: breath2 4s ease-in-out infinite; |
|||
} |
|||
|
|||
.module3 { |
|||
position: absolute; |
|||
width: 52.1rpx; |
|||
top: 700rpx; |
|||
left: 498rpx; |
|||
animation: breath3 5s ease-in-out infinite; |
|||
} |
|||
|
|||
// 呼吸效果动画 - 不同频率 |
|||
@keyframes breath1 { |
|||
|
|||
0%, |
|||
100% { |
|||
transform: scale(1); |
|||
} |
|||
|
|||
50% { |
|||
transform: scale(1.2); |
|||
} |
|||
} |
|||
|
|||
@keyframes breath2 { |
|||
|
|||
0%, |
|||
100% { |
|||
transform: scale(1); |
|||
} |
|||
|
|||
50% { |
|||
transform: scale(1.2); |
|||
} |
|||
} |
|||
|
|||
@keyframes breath3 { |
|||
|
|||
0%, |
|||
100% { |
|||
transform: scale(1); |
|||
} |
|||
|
|||
50% { |
|||
transform: scale(1.2); |
|||
} |
|||
} |
|||
|
|||
.img4-text { |
|||
width: 428.43rpx; |
|||
position: absolute; |
|||
top: 170rpx; |
|||
left: 100rpx; |
|||
} |
|||
|
|||
.btn-img { |
|||
position: absolute; |
|||
width: 149.8rpx; |
|||
bottom: 290rpx; |
|||
left: 100rpx; |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.img2-1 { |
|||
width: 267.37rpx; |
|||
position: fixed; |
|||
top: 395rpx; |
|||
left: 70rpx; |
|||
} |
|||
|
|||
.img2-2 { |
|||
width: 332.24rpx; |
|||
position: fixed; |
|||
top: 210rpx; |
|||
left: 360rpx; |
|||
} |
|||
|
|||
.img2-3 { |
|||
width: 600.59rpx; |
|||
position: fixed; |
|||
bottom: 215rpx; |
|||
right: 40rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,216 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
<template v-if="index == 1" > |
|||
<image @click="hanldGifPage" :src="showImg('/uploads/20250903/dcfd8b8a708f4f2d43edf35a906f75ba.png')" mode="widthFix" class="img1-text"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 2, |
|||
swiperImages: [ |
|||
this.showImg('/uploads/20250903/3bd4fe43f2a6a8806799f06a548f9477.png'), |
|||
this.showImg('/uploads/20250903/8fe8d66210edd96a9f322a661b4d9ba4.png'), |
|||
|
|||
], |
|||
// 控制图片显示的变量 |
|||
showImg7_1: false, |
|||
showImg7_2: false, |
|||
showImg7_3: false, |
|||
// 存储定时器 |
|||
timers: [], |
|||
popupIndex: 1 |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
this.currentIndex = option.currentIndex || 0 |
|||
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1; |
|||
}, |
|||
onUnload() { |
|||
// 清除定时器 |
|||
this.timers.forEach(timer => clearTimeout(timer)) |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
if(idx == this.swiperImages.length - 1) this.navIndex = idx + 1 |
|||
}, |
|||
hanldGifPage(){ |
|||
uni.navigateTo({ |
|||
url:'/xqk/chapter5/index' |
|||
}) |
|||
}, |
|||
handleSwiperChange(e) { |
|||
// 清除之前的定时器 |
|||
this.timers.forEach(timer => clearTimeout(timer)) |
|||
this.timers = [] |
|||
|
|||
this.currentIndex = e.detail.current; |
|||
|
|||
if (this.currentIndex == this.swiperImages.length - 1) { |
|||
this.navIndex = 3; |
|||
}else { |
|||
this.navIndex = 2 |
|||
} |
|||
|
|||
if (this.currentIndex === 6) { |
|||
// 重置显示状态(确保每次进入都重新动画) |
|||
this.showImg7_1 = false |
|||
this.showImg7_2 = false |
|||
this.showImg7_3 = false |
|||
|
|||
// 延迟0.5秒显示第一张图 |
|||
const timer1 = setTimeout(() => { |
|||
this.showImg7_1 = true |
|||
}, 500) |
|||
|
|||
// 延迟1秒显示第二张图 |
|||
const timer2 = setTimeout(() => { |
|||
this.showImg7_2 = true |
|||
}, 1000) |
|||
|
|||
// 延迟2秒显示第三张图 |
|||
const timer3 = setTimeout(() => { |
|||
this.showImg7_3 = true |
|||
}, 2000) |
|||
|
|||
this.timers.push(timer1, timer2, timer3) |
|||
} else { |
|||
// 非目标索引时隐藏所有图片 |
|||
this.showImg7_1 = false |
|||
this.showImg7_2 = false |
|||
this.showImg7_3 = false |
|||
} |
|||
}, |
|||
// 第八页气泡弹框 |
|||
openPopup(i) { |
|||
this.popupIndex = i |
|||
this.$refs.chapterPopup.open(); |
|||
}, |
|||
getImageUrl(path) { |
|||
if (typeof path === 'object') { |
|||
path = path.url; |
|||
} |
|||
return `https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter2/${path}`; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% 100%; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
.img1-text{ |
|||
width: 484rpx; |
|||
position: absolute; |
|||
bottom: 100rpx; |
|||
left: 250rpx; |
|||
} |
|||
.img10-text { |
|||
width: 484rpx; |
|||
position: absolute; |
|||
top: 170rpx; |
|||
left: 100rpx; |
|||
} |
|||
|
|||
.btn-img { |
|||
position: absolute; |
|||
width: 149.8rpx; |
|||
bottom: 290rpx; |
|||
left: 100rpx; |
|||
} |
|||
|
|||
.module-img { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
width: 564.25rpx; |
|||
} |
|||
.module1 { |
|||
top: 630rpx; |
|||
} |
|||
.module2 { |
|||
top: 780rpx; |
|||
} |
|||
.module3 { |
|||
top: 930rpx; |
|||
} |
|||
.module4 { |
|||
top: 1080rpx; |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
/* 渐入动画样式 */ |
|||
.fade-in-image { |
|||
/* 初始状态:透明 */ |
|||
opacity: 0; |
|||
/* 添加过渡动画:1秒内透明度从0到1 */ |
|||
animation: fadeIn 1s ease-out forwards; |
|||
/* 根据需要调整图片的定位 */ |
|||
position: absolute; |
|||
} |
|||
|
|||
/* 渐入动画关键帧 */ |
|||
@keyframes fadeIn { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateY(20rpx); /* 可选:添加轻微上移动画 */ |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
.fade-in-image:nth-child(1) { |
|||
width: 22.97rpx; |
|||
top: 825rpx; |
|||
right: 191rpx; |
|||
} |
|||
|
|||
.fade-in-image:nth-child(2) { |
|||
width: 34.95rpx; |
|||
top: 790rpx; |
|||
right: 170rpx; |
|||
} |
|||
|
|||
.fade-in-image:nth-child(3) { |
|||
width: 144.81rpx; |
|||
top: 680rpx; |
|||
right: 71rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,244 @@ |
|||
<template> |
|||
<view style="width: 100vw; position: relative;"> |
|||
<!-- 滑动拦截遮罩(仅在第5页且未输入内容时显示) --> |
|||
<view |
|||
v-if="currentIndex === 5 && !inputValue.trim()" |
|||
class="swipe-blocker" |
|||
@click="$refs.customPopup.open()" |
|||
></view> |
|||
|
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
<!-- 第5页内容 --> |
|||
<template v-if="index === 5"> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img6-text.png" |
|||
mode="widthFix" class="img6-text"></image> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img6-btn.png" |
|||
mode="widthFix" class="img6-btn" @click="$refs.customPopup.open()"></image> |
|||
</template> |
|||
|
|||
<!-- 第6页内容 --> |
|||
<template v-if="index === 6"> |
|||
<view class="img7-box"> |
|||
<view class="img7-textBg"> |
|||
<view>你的</view> |
|||
<view style="margin: 10rpx 0;">{{inputValue}}</view> |
|||
<view>已备好</view> |
|||
</view> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img7-text.png" |
|||
mode="widthFix" class="img7-text"></image> |
|||
</view> |
|||
</template> |
|||
|
|||
<template v-if="index === 7"> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img8-text.png" mode="widthFix" class="img8-text"></image> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/btn-img.png" mode="widthFix" |
|||
class="btn-img" @click="gotoPath('/xrcc/chapter4/index')"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
|
|||
<!-- 输入弹框 --> |
|||
<uni-popup ref="customPopup" type="center"> |
|||
<view class="popup-content"> |
|||
<textarea v-model="inputValue" class="input-area" placeholder="填写你的“物件” (可以是一本书、一首歌、一段回忆、一个困惑...)" |
|||
maxlength="20"></textarea> |
|||
|
|||
<view class="word-count"> |
|||
{{ inputValue.length }}/20 |
|||
</view> |
|||
|
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img6-btns.png" mode="widthFix" @click="submit" class="confirm-btn"></image> |
|||
</view> |
|||
</uni-popup> |
|||
|
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 脚本部分保持不变 |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 3, |
|||
swiperImages: [ |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img1.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img2s.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img3s.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img4s.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img5s.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img6.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img7.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img8.gif', |
|||
], |
|||
inputValue: '', |
|||
swipeDirection: '' |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
this.currentIndex = option.currentIndex || 0 |
|||
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1; |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1 |
|||
}, |
|||
handleTouchStart(e) { |
|||
this.startY = e.touches[0].clientY; |
|||
}, |
|||
handleTouchMove(e) { |
|||
const moveY = e.touches[0].clientY; |
|||
this.swipeDirection = moveY < this.startY ? 'down' : 'up'; |
|||
}, |
|||
handleSwiperChange(e) { |
|||
const newIndex = e.detail.current; |
|||
|
|||
if (this.currentIndex === 5 && newIndex === 6 && !this.inputValue.trim()) { |
|||
this.currentIndex = 5; |
|||
uni.showToast({ |
|||
title: '请先填写内容才能继续', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
this.currentIndex = newIndex; |
|||
if (this.currentIndex === this.swiperImages.length - 1) { |
|||
this.navIndex = 4; |
|||
} else { |
|||
this.navIndex = 3; |
|||
} |
|||
}, |
|||
submit() { |
|||
if (!this.inputValue.trim()) return; |
|||
this.$refs.customPopup.close() |
|||
this.currentIndex = 6; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
/* 原有样式保持不变 */ |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% 100%; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.img6-text { |
|||
width: 442.41rpx; |
|||
position: absolute; |
|||
top: 170rpx; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
} |
|||
.img6-btn { |
|||
position: absolute; |
|||
width: 520.31rpx; |
|||
bottom: 210rpx; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
/* 其他样式保持不变 */ |
|||
.img7-box { |
|||
position: relative; |
|||
top: 170rpx; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
.img7-textBg { |
|||
background-image: url('https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img7-textBg.png'); |
|||
background-size: 100% 100%; |
|||
padding: 30rpx 50rpx; |
|||
font-size: 50rpx; |
|||
text-align: center; |
|||
color: #fff; |
|||
width: 520.31rpx; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
.img7-text { |
|||
display: block; |
|||
margin: 60rpx auto 0; |
|||
width: 447.4rpx; |
|||
} |
|||
|
|||
.img8-text { |
|||
width: 379.49rpx; |
|||
position: absolute; |
|||
top: 170rpx; |
|||
left: 100rpx; |
|||
} |
|||
|
|||
.btn-img { |
|||
position: absolute; |
|||
width: 149.8rpx; |
|||
bottom: 290rpx; |
|||
left: 100rpx; |
|||
} |
|||
} |
|||
|
|||
.swipe-blocker { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
z-index: 9; |
|||
} |
|||
|
|||
.popup-content { |
|||
width: 85vw; |
|||
background-color: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 40rpx 30rpx; |
|||
box-sizing: border-box; |
|||
|
|||
.input-area { |
|||
width: 100%; |
|||
min-height: 180rpx; |
|||
padding: 20rpx; |
|||
border: 2rpx solid #eee; |
|||
border-radius: 8rpx; |
|||
font-size: 28rpx; |
|||
resize: none; |
|||
box-sizing: border-box; |
|||
margin-bottom: 15rpx; |
|||
} |
|||
|
|||
.word-count { |
|||
text-align: right; |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
margin-bottom: 35rpx; |
|||
} |
|||
|
|||
.confirm-btn { |
|||
width: 100%; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,132 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
<template v-if="index === 3"> |
|||
<view class="module-box"> |
|||
<image :src="`https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img4-${i+1}.png`" |
|||
v-for="i in 5" :key="i" mode="widthFix" :class="['module-img', 'module'+(i+1)]" |
|||
@click="openPopup(i+1)"></image> |
|||
</view> |
|||
</template> |
|||
|
|||
<template v-if="index === 5"> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img5-text.png" mode="widthFix" class="img5-text"></image> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/btn-img.png" mode="widthFix" |
|||
class="btn-img" @click="gotoPath('/xrcc/chapter5/index')"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
|
|||
<uni-popup ref="chapterPopup"> |
|||
<image :src="getImageUrl(`img4-${popupIndex}s.png`)" mode="widthFix" style="width: 600rpx;"> |
|||
</image> |
|||
</uni-popup> |
|||
|
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
|
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 4, |
|||
swiperImages: [ |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img1.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img2.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img3.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img4.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img5s.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/img6.gif', |
|||
], |
|||
popupIndex: 1 |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
this.currentIndex = option.currentIndex || 0 |
|||
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1; |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
if(idx == this.swiperImages.length - 1) this.navIndex = idx + 1 |
|||
}, |
|||
handleSwiperChange(e) { |
|||
this.currentIndex = e.detail.current; |
|||
if (this.currentIndex == this.swiperImages.length - 1) { |
|||
this.navIndex = 5; |
|||
}else { |
|||
this.navIndex = 4 |
|||
} |
|||
}, |
|||
openPopup(i) { |
|||
this.popupIndex = i |
|||
this.$refs.chapterPopup.open(); |
|||
}, |
|||
getImageUrl(path) { |
|||
if (typeof path === 'object') { |
|||
path = path.url; |
|||
} |
|||
return `https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/${path}`; |
|||
} |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% 100%; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.img5-text { |
|||
width: 576.23rpx; |
|||
position: absolute; |
|||
top: 170rpx; |
|||
left: 100rpx; |
|||
} |
|||
|
|||
.btn-img { |
|||
position: absolute; |
|||
width: 149.8rpx; |
|||
bottom: 290rpx; |
|||
left: 100rpx; |
|||
} |
|||
|
|||
.module-box { |
|||
position: absolute; |
|||
top: 460rpx; |
|||
text-align: center; |
|||
|
|||
image { |
|||
width: 650rpx; |
|||
margin-bottom: 80rpx; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
</style> |
@ -0,0 +1,188 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<SwipeToNext |
|||
:is-last-slide="isLastSlide" |
|||
:always-enable="swiperImages.length === 1" |
|||
:target-path="'/xqk/chapter3/index'" |
|||
:enable-delay="swiperImages.length > 1" |
|||
> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
|
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</SwipeToNext> |
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
import SwipeToNext from '@/components/SwipeToNext.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu, |
|||
SwipeToNext |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 5, |
|||
swiperImages: [ |
|||
this.showImg('/uploads/20250903/dd5b260002da55d4c3d56b338451bc11.gif'), |
|||
], |
|||
animateShow: false, |
|||
isLastSlide: false // 是否在最后一页 |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
this.currentIndex = option.currentIndex || 0 |
|||
// 对于单张图片或初始在最后一页的情况 |
|||
if (this.currentIndex == this.swiperImages.length - 1) { |
|||
this.navIndex = 1; |
|||
this.isLastSlide = true; |
|||
} |
|||
// 如果只有一张图片,也认为是最后一页 |
|||
if (this.swiperImages.length === 1) { |
|||
this.isLastSlide = true; |
|||
} |
|||
}, |
|||
// onLoad(option) { |
|||
// this.currentIndex = option.currentIndex || 0 |
|||
// if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1; |
|||
// }, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1 |
|||
}, |
|||
handleSwiperChange(e) { |
|||
this.currentIndex = e.detail.current; |
|||
if(this.currentIndex == 1) { |
|||
this.animateShow = true |
|||
}else { |
|||
this.animateShow = false |
|||
} |
|||
|
|||
if (this.currentIndex == this.swiperImages.length - 1) { |
|||
// 判断是否切换到最后一页 |
|||
this.isLastSlide = true; |
|||
this.navIndex = 6; |
|||
} else { |
|||
// 判断是否切换到最后一页 |
|||
this.isLastSlide = false; |
|||
this.navIndex = 5 |
|||
} |
|||
}, |
|||
// 去首页 |
|||
goHome() { |
|||
uni.switchTab({ |
|||
url: '/pages/index/index' |
|||
}) |
|||
} |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% 100%; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.img2s { |
|||
position: absolute; |
|||
top: 380rpx; |
|||
right: 25rpx; |
|||
width: 358rpx; |
|||
transform: translateX(100%); |
|||
opacity: 0; |
|||
} |
|||
|
|||
.img2-btn { |
|||
width: 558rpx; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
bottom: 70rpx; |
|||
} |
|||
|
|||
.img3-btn { |
|||
width: 558rpx; |
|||
line-height: 72rpx; |
|||
text-align: center; |
|||
border-radius: 20rpx; |
|||
border: 2rpx solid; |
|||
font-size: 30rpx; |
|||
color: #fff; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
bottom: 180rpx; |
|||
} |
|||
|
|||
.flex-column { |
|||
position: absolute; |
|||
bottom: 280rpx; |
|||
width: 100%; |
|||
align-items: center; |
|||
|
|||
.img5-text { |
|||
width: 100%; |
|||
} |
|||
|
|||
.img5-btn { |
|||
width: 230rpx; |
|||
margin-top: 99rpx; |
|||
} |
|||
} |
|||
|
|||
.img6-text { |
|||
width: 408.46rpx; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
bottom: 365rpx |
|||
} |
|||
} |
|||
|
|||
/* 从右往左进入的动画 */ |
|||
.animate-enter-from-right { |
|||
animation: enterFromRight 2s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes enterFromRight { |
|||
0% { |
|||
/* 起始位置:右侧外部 */ |
|||
transform: translateX(100%); |
|||
opacity: 0; |
|||
} |
|||
|
|||
100% { |
|||
/* 结束位置:正常位置 */ |
|||
transform: translateX(0); |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
</style> |
@ -0,0 +1,216 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
<template v-if="index === 0"> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter5/img7-1.png" mode="widthFix" |
|||
class="img7-1" @click="$refs.customPopup.open()"></image> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter5/img7-2.png" mode="widthFix" |
|||
class="img7-2" style="top: 620rpx;" @click="$refs.customPopups.open()"></image> |
|||
|
|||
<view class="bgm-box flex-between"> |
|||
<view :class="{'bgm-active': index == bgmIndex}" v-for="(item,index) in bgmList" :key="index" @click="bgmIndex = index">{{item}}</view> |
|||
</view> |
|||
|
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter5/img7-btn.png" mode="widthFix" |
|||
class="img7-btn" @click="confirm"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
|
|||
<!-- 输入弹框 --> |
|||
<uni-popup ref="customPopup" type="center"> |
|||
<view class="popup-content"> |
|||
<textarea v-model="inputValue" class="input-area" placeholder="我的星槎" |
|||
maxlength="20"></textarea> |
|||
|
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter5/confirm-btn.png" mode="widthFix" @click="submit" class="confirm-btn"></image> |
|||
</view> |
|||
</uni-popup> |
|||
|
|||
<!-- 输入弹框 --> |
|||
<uni-popup ref="customPopups" type="center"> |
|||
<view class="popup-content"> |
|||
<textarea v-model="inputValues" class="input-area" placeholder="我的目的地" |
|||
maxlength="20"></textarea> |
|||
|
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter5/confirm-btn.png" mode="widthFix" @click="submit" class="confirm-btn"></image> |
|||
</view> |
|||
</uni-popup> |
|||
|
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
|
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 5, |
|||
swiperImages: [ |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter5/img7.png', |
|||
], |
|||
inputValue: '', |
|||
inputValues: '', |
|||
bgmList: [ |
|||
'古典', |
|||
'民谣', |
|||
'电子', |
|||
'自然白噪音' |
|||
], |
|||
bgmIndex: null |
|||
} |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
if(idx == this.swiperImages.length - 1) this.navIndex = idx + 1 |
|||
}, |
|||
handleSwiperChange(e) { |
|||
this.currentIndex = e.detail.current; |
|||
}, |
|||
submit() { |
|||
this.$refs.customPopup.close() |
|||
this.$refs.customPopups.close() |
|||
}, |
|||
getImageUrl(path) { |
|||
if (typeof path === 'object') { |
|||
path = path.url; |
|||
} |
|||
return `https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter6/${path}`; |
|||
}, |
|||
confirm() { |
|||
if (!this.inputValue.trim() || !this.inputValues.trim() || this.bgmIndex == null) { |
|||
uni.showToast({ |
|||
title: '请先填写或选择您的日志信息', |
|||
icon: 'none' |
|||
}); |
|||
}else { |
|||
let data = { |
|||
text1: this.inputValue.trim(), |
|||
text2: this.inputValues.trim(), |
|||
imgSrc: this.getImageUrl(`img${this.bgmIndex + 1}s.png`), |
|||
imgTitle: this.bgmList[this.bgmIndex] |
|||
} |
|||
this.gotoPath('/xrcc/chapter7/index?data=' + JSON.stringify(data)) |
|||
} |
|||
} |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% 100%; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.btn-img { |
|||
position: absolute; |
|||
width: 149.8rpx; |
|||
bottom: 290rpx; |
|||
left: 84rpx; |
|||
} |
|||
|
|||
.img7-1, .img7-2 { |
|||
width: 437.67rpx; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
top: 365rpx; |
|||
} |
|||
|
|||
.bgm-box { |
|||
width: 437.67rpx; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 650rpx; |
|||
margin: 0 auto; |
|||
flex-wrap: wrap; |
|||
|
|||
view { |
|||
width: 207.89rpx; |
|||
line-height: 42.77rpx; |
|||
border-radius: 2rpx; |
|||
text-align: center; |
|||
color: #fff; |
|||
font-size: 20rpx; |
|||
border: 1rpx solid #fff; |
|||
} |
|||
view:nth-child(n+3) { |
|||
margin-top: 17rpx; |
|||
} |
|||
|
|||
.bgm-active { |
|||
border-color: #00C48C; |
|||
color: #00C48C; |
|||
} |
|||
} |
|||
|
|||
.img7-btn { |
|||
width: 439.66rpx; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 244rpx; |
|||
margin: 0 auto; |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.popup-content { |
|||
width: 85vw; |
|||
background-color: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 40rpx 30rpx; |
|||
box-sizing: border-box; |
|||
|
|||
.input-area { |
|||
width: 100%; |
|||
min-height: 180rpx; |
|||
padding: 20rpx; |
|||
border: 2rpx solid #747c8e; |
|||
border-radius: 8rpx; |
|||
font-size: 28rpx; |
|||
resize: none; |
|||
box-sizing: border-box; |
|||
margin-bottom: 15rpx; |
|||
} |
|||
|
|||
.word-count { |
|||
text-align: right; |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
margin-bottom: 35rpx; |
|||
} |
|||
|
|||
.confirm-btn { |
|||
width: 100%; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,143 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
<template v-if="index === 0"> |
|||
<view class="box"> |
|||
<!-- <view class="title">我的星槎</view> --> |
|||
<view class="subtitle subtitle1">{{info.text1}}</view> |
|||
<!-- <view class="title">我的目的地</view> --> |
|||
<view class="subtitle subtitle2">{{info.text2}}</view> |
|||
<!-- <view class="title">我的航行BGM</view> --> |
|||
<view class="subtitle subtitle3">{{info.imgTitle}}</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<template v-if="index === 1"> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter6/img5-text.png" mode="widthFix" class="img5-text.png"></image> |
|||
</template> |
|||
|
|||
<!-- 二维码 --> |
|||
<template v-if="index === 2"> |
|||
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/qrcode.png" mode="widthFix" class="qrcode" :show-menu-by-longpress="true"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
|
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
|
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 5, |
|||
swiperImages: [ |
|||
'', |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter6/img5.png', |
|||
'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter5/img7.png' |
|||
], |
|||
info: {} |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
if(option) { |
|||
let data = JSON.parse(option.data) |
|||
this.info = data |
|||
console.log(data) |
|||
this.swiperImages[0] = data.imgSrc |
|||
} |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
}, |
|||
handleSwiperChange(e) { |
|||
this.currentIndex = e.detail.current; |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% 100%; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.box { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
top: 300rpx; |
|||
text-align: center; |
|||
width: 100%; |
|||
color: rgba(220, 221, 221, 1); |
|||
|
|||
.title { |
|||
font-size: 25rpx; |
|||
margin-top: 50rpx; |
|||
} |
|||
.subtitle { |
|||
position: absolute; |
|||
font-size: 40rpx; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
} |
|||
.subtitle1 { |
|||
top: 150rpx; |
|||
} |
|||
.subtitle2 { |
|||
top: 280rpx; |
|||
} |
|||
.subtitle3 { |
|||
top: 410rpx; |
|||
} |
|||
} |
|||
|
|||
.img5-text.png { |
|||
width: 312.58rpx; |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
bottom: 492rpx; |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.qrcode { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
width: 25vw; |
|||
bottom: 28vh; |
|||
} |
|||
</style> |
@ -0,0 +1,75 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
|
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
|
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
|
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
NavMenu |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
navIndex: 3, |
|||
swiperImages: [ |
|||
'https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img1.gif', |
|||
] |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
this.currentIndex = option.currentIndex || 0 |
|||
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1; |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
}, |
|||
handleSwiperChange(e) { |
|||
this.currentIndex = e.detail.current; |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background-size: 100% auto; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.btn-img { |
|||
position: absolute; |
|||
width: 149.8rpx; |
|||
bottom: 290rpx; |
|||
left: 84rpx; |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
</style> |
@ -0,0 +1,248 @@ |
|||
<template> |
|||
<view> |
|||
<view class="overlay" v-if="showMenu" @click="onCloseMenu"></view> |
|||
<view class="fixed-nav" :class="{'hidden': showMenu}" @click="onShowMenu"> |
|||
<image class="nav-icon" :class="{'rotated': iconRotated, 'bounce-back': iconBounceBack}" :src="navIconSrc" |
|||
mode="aspectFill"></image> |
|||
</view> |
|||
<view class="nav-menu" :class="{'show': showMenu}"> |
|||
<view class="nav-item" :class="{'item-active': isItemActive(item)}" v-for="item in menuItems" |
|||
:key="item.targetIndex" @click="() => onJumpToPage(item)"> |
|||
<view v-if="item.text.includes('#Chapter')" class="chapter-text"> |
|||
<text class="chapter-title">#Chapter</text> |
|||
<text :class="{'active': isItemActive(item)}" class="chapter-number"> |
|||
{{ item.text.replace('#Chapter', '') }} |
|||
</text> |
|||
</view> |
|||
<text v-else :class="{'active': isItemActive(item)}">{{ item.text }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
// 当前激活的索引 |
|||
navIndex: { |
|||
type: Number, |
|||
required: true |
|||
}, |
|||
// 导航图标地址 |
|||
navIconSrc: { |
|||
type: String, |
|||
default: 'https://static.ticket.sz-trip.com/epicSoul/taozi/nav-icon.png' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
showMenu: false, |
|||
iconRotated: false, |
|||
iconBounceBack: false, |
|||
menuItems: [{ |
|||
text: 'INTRO序曲', |
|||
targetIndex: 0, |
|||
path: "/xqk/home/home" |
|||
}, |
|||
{ |
|||
text: '01 青壳初生', |
|||
targetIndex: 1, |
|||
path: "/xqk/chapter1/index" |
|||
}, |
|||
{ |
|||
text: '02 负海志 向湖生', |
|||
targetIndex: 2, |
|||
path: "/xqk/chapter2/index" |
|||
}, |
|||
{ |
|||
text: '03 名曰江湖', |
|||
targetIndex: 3, |
|||
path: "/xqk/chapter3/index" |
|||
}, |
|||
{ |
|||
text: '04 风味人间', |
|||
targetIndex: 4, |
|||
path: "/xqk/chapter4/index" |
|||
}, |
|||
{ |
|||
text: '05 共济', |
|||
targetIndex: 5, |
|||
path: "/xqk/chapter6/index" |
|||
}, |
|||
{ |
|||
text: '有感商品', |
|||
targetIndex: 6, |
|||
path: "/subPackages/techan/detail?id=39" |
|||
}, |
|||
{ |
|||
text: '购物车', |
|||
targetIndex: 7, |
|||
path: "/subPackages/user/gwc" |
|||
} |
|||
], |
|||
}; |
|||
}, |
|||
watch: { |
|||
navIndex(newVal) { |
|||
console.log(newVal) |
|||
if(newVal) this.navIndex = newVal |
|||
} |
|||
}, |
|||
methods: { |
|||
onShowMenu() { |
|||
this.iconRotated = true; |
|||
setTimeout(() => { |
|||
this.showMenu = true; |
|||
this.$emit('menu-show'); |
|||
}, 300); |
|||
}, |
|||
onCloseMenu() { |
|||
this.showMenu = false; |
|||
setTimeout(() => { |
|||
this.iconBounceBack = true; |
|||
this.iconRotated = false; |
|||
setTimeout(() => { |
|||
this.iconBounceBack = false; |
|||
}, 500); |
|||
}, 300); |
|||
this.$emit('menu-hide'); |
|||
}, |
|||
onJumpToPage(item) { |
|||
console.log(this.navIndex) |
|||
if(item.path && item.targetIndex != this.navIndex) this.gotoPath(item.path) |
|||
this.onCloseMenu(); |
|||
}, |
|||
isItemActive(item) { |
|||
return this.navIndex === item.targetIndex; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.3); |
|||
z-index: 100; |
|||
} |
|||
|
|||
.fixed-nav { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
background-color: rgb(0 0 0 / 0.7); |
|||
border-radius: 10rpx 0 0 10rpx; |
|||
position: fixed; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
margin: auto 0; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 9; |
|||
transition: transform 0.3s ease, opacity 0.3s ease; |
|||
} |
|||
|
|||
.fixed-nav.hidden { |
|||
transform: translateX(100%); |
|||
opacity: 0; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.nav-icon { |
|||
width: 35rpx; |
|||
height: 35rpx; |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
.nav-icon.rotated { |
|||
transform: rotate(180deg); |
|||
} |
|||
|
|||
.nav-icon.bounce-back { |
|||
animation: bounceRotation 0.5s ease; |
|||
} |
|||
|
|||
@keyframes bounceRotation { |
|||
0% { |
|||
transform: rotate(180deg); |
|||
} |
|||
|
|||
50% { |
|||
transform: rotate(-20deg); |
|||
} |
|||
|
|||
75% { |
|||
transform: rotate(10deg); |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(0deg); |
|||
} |
|||
} |
|||
|
|||
.nav-menu { |
|||
position: fixed; |
|||
top: 50%; |
|||
right: 0; |
|||
transform: translate(100%, -50%); |
|||
z-index: 999; |
|||
background-color: rgba(255, 255, 255, 0.95); |
|||
border-radius: 16rpx 0 0 16rpx; |
|||
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.1); |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
.nav-menu.show { |
|||
transform: translate(0, -50%); |
|||
} |
|||
|
|||
.nav-item { |
|||
padding: 20rpx; |
|||
text-align: center; |
|||
|
|||
text { |
|||
color: #333; |
|||
opacity: 0.7; |
|||
font-size: 28rpx; |
|||
} |
|||
} |
|||
|
|||
.item-active { |
|||
background-color: rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.nav-item .active { |
|||
color: #333; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.chapter-text { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
line-height: 1.3; |
|||
} |
|||
|
|||
.chapter-title { |
|||
color: #fff; |
|||
opacity: 0.7; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.chapter-number { |
|||
color: #fff; |
|||
opacity: 0.7; |
|||
font-size: 28rpx; |
|||
margin-top: 8rpx; |
|||
} |
|||
|
|||
.item-active .chapter-title, |
|||
.item-active .chapter-number.active { |
|||
opacity: 1; |
|||
} |
|||
</style> |
@ -0,0 +1,95 @@ |
|||
<template> |
|||
<view class="gif-container"> |
|||
<view class="dynamic-container"> |
|||
<image |
|||
:src="gifSrc" |
|||
mode="widthFix" |
|||
class="gif-image" |
|||
:style="{ display: isPlaying ? 'block' : 'none' }" |
|||
@load="startGifPlay" |
|||
></image> |
|||
<image |
|||
:src="staticCover" |
|||
mode="widthFix" |
|||
class="gif-image" |
|||
:style="{ display: isPlaying ? 'none' : 'block' }" |
|||
></image> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
// GIF图片路径 |
|||
gifSrc: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
// 静态封面图 |
|||
staticCover: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// GIF播放时长(毫秒) |
|||
duration: { |
|||
type: Number, |
|||
default: 2000 |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
isPlaying: false, |
|||
playTimer: null |
|||
} |
|||
}, |
|||
methods: { |
|||
// 开始播放GIF |
|||
startGifPlay() { |
|||
this.isPlaying = true; |
|||
|
|||
// 清除之前的定时器 |
|||
if (this.playTimer) { |
|||
clearTimeout(this.playTimer); |
|||
} |
|||
|
|||
// 播放完成后显示静态封面 |
|||
this.playTimer = setTimeout(() => { |
|||
this.isPlaying = false; |
|||
}, this.duration); |
|||
} |
|||
}, |
|||
onUnload() { |
|||
// 页面卸载时清除定时器 |
|||
if (this.playTimer) { |
|||
clearTimeout(this.playTimer); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.gif-container { |
|||
width: 100vw; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.gif-image { |
|||
width: 100vw; |
|||
height: auto; |
|||
} |
|||
|
|||
.dynamic-container { |
|||
position: relative; |
|||
width: 100vw; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.dynamic-container image { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
} |
|||
</style> |
@ -0,0 +1,171 @@ |
|||
<template> |
|||
<view style="width: 100vw;"> |
|||
<!-- <SinglePlayGif |
|||
gifSrc="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img1.gif" |
|||
staticCover="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img1.png" |
|||
duration="5000" |
|||
/> --> |
|||
<!-- 触底方法跳转页面组件 --> |
|||
<SwipeToNext |
|||
:is-last-slide="isLastSlide" |
|||
:always-enable="swiperImages.length === 1" |
|||
:target-path="'/xqk/chapter1/index'" |
|||
:enable-delay="swiperImages.length > 1" |
|||
@swipe-to-next="handleSwipeToNext" |
|||
> |
|||
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange"> |
|||
<swiper-item v-for="(image, index) in swiperImages" :key="index"> |
|||
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }"> |
|||
|
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</SwipeToNext> |
|||
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" /> |
|||
|
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import SinglePlayGif from '../components/SinglePlayGif.vue'; |
|||
import NavMenu from '../components/NavMenu.vue'; |
|||
import SwipeToNext from '@/components/SwipeToNext.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
SinglePlayGif, |
|||
NavMenu, |
|||
SwipeToNext |
|||
}, |
|||
data() { |
|||
return { |
|||
isPlaying: false, |
|||
playTimer: null, |
|||
duration: 5000, |
|||
currentIndex: 0, |
|||
navIndex: 0, |
|||
swiperImages: [ |
|||
this.showImg('/uploads/20250903/b4f601dee7b4ad1b42c878fd54693c92.png'), |
|||
// this.showImg('/uploads/20250903/24303e4b7218eaf3d857c846417eb490.png'), |
|||
// this.showImg('/uploads/20250903/17495ef65648c64c31920d312301e991.png'), |
|||
// this.showImg('/uploads/20250903/92d6f1c6f8f7de040f3c31c8faf98927.png'), |
|||
], |
|||
isLastSlide: false // 是否在最后一页 |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
this.currentIndex = option.currentIndex || 0 |
|||
// 对于单张图片或初始在最后一页的情况 |
|||
if (this.currentIndex == this.swiperImages.length - 1) { |
|||
this.navIndex = 1; |
|||
this.isLastSlide = true; |
|||
} |
|||
// 如果只有一张图片,也认为是最后一页 |
|||
if (this.swiperImages.length === 1) { |
|||
this.isLastSlide = true; |
|||
} |
|||
}, |
|||
onShow() { |
|||
const app = getApp(); |
|||
app.updateMusicSrc('https://static.ticket.sz-trip.com/epicSoul/xrcc/bgm.mp3'); |
|||
app.initBackgroundMusic(); // 初始化背景音乐 |
|||
uni.$bgMusic.play(); // 播放音乐 |
|||
}, |
|||
methods: { |
|||
handleJumpToPage(idx) { |
|||
this.navIndex = idx |
|||
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1 |
|||
}, |
|||
handleSwiperChange(e) { |
|||
console.log(e); |
|||
this.currentIndex = e.detail.current; |
|||
if (this.currentIndex == this.swiperImages.length - 1) { |
|||
this.navIndex = 1; |
|||
// 判断是否切换到最后一页 |
|||
this.isLastSlide = true; |
|||
} else { |
|||
this.navIndex = 0; |
|||
this.isLastSlide = false; |
|||
} |
|||
}, |
|||
// 处理滑动跳转事件 |
|||
handleSwipeToNext(targetPath) { |
|||
console.log('收到滑动跳转事件,目标路径:', targetPath); |
|||
// 可以在这里添加额外的逻辑,比如数据统计等 |
|||
}, |
|||
// <!--微信分享配置-- > |
|||
// #ifdef MP-WEIXIN |
|||
onShareAppMessage() { |
|||
return { |
|||
title: '今夜,我们都有一艘秘密飞船|「Epic Soul」阅读体 issue05', |
|||
mpId: 'wx8954209bb3ad489e', |
|||
path: '/xrcc/home/home', |
|||
imageUrl: this.showImg('/uploads/20250903/66ff1f3cd63ea776a0203e8e0dd92dda.jpg') |
|||
}; |
|||
}, |
|||
onShareTimeline() { |
|||
return { |
|||
title: '今夜,我们都有一艘秘密飞船|「Epic Soul」阅读体 issue05', |
|||
query: '', |
|||
imageUrl: this.showImg('/uploads/20250903/66ff1f3cd63ea776a0203e8e0dd92dda.jpg') |
|||
}; |
|||
} |
|||
// #endif |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.swiper { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.swiper-item { |
|||
/* 新增安全区域适配 */ |
|||
padding-top: env(safe-area-inset-top); |
|||
/* 顶部安全距离 */ |
|||
padding-bottom: env(safe-area-inset-bottom); |
|||
/* 底部安全距离 */ |
|||
box-sizing: border-box; |
|||
/* 修改背景尺寸为覆盖模式 */ |
|||
background-size: cover; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
// background-size: 100% auto; |
|||
background-position: center center; |
|||
background-color: #000; |
|||
background-repeat: no-repeat; |
|||
position: relative; |
|||
|
|||
.img1-text { |
|||
position: absolute; |
|||
width: 632.16rpx; |
|||
top: 170rpx; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
.img4-text { |
|||
position: absolute; |
|||
width: 476.36rpx; |
|||
top: 170rpx; |
|||
left: 84rpx; |
|||
} |
|||
|
|||
.btn-img { |
|||
position: absolute; |
|||
width: 149.8rpx; |
|||
bottom: 290rpx; |
|||
left: 84rpx; |
|||
} |
|||
} |
|||
|
|||
.swiper-img { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
} |
|||
</style> |
Loading…
Reference in new issue