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