Browse Source

苏青壳 及语音播报 josn

master
zhangminghao 2 months ago
parent
commit
22da441688
  1. 3
      App.vue
  2. 372
      bmzm/chapter1/index.vue
  3. 5
      bmzm/chapter2/index.vue
  4. 5
      bmzm/chapter3/index.vue
  5. 5
      bmzm/chapter4/index.vue
  6. 5
      bmzm/chapter5/index.vue
  7. 5
      bmzm/chapter6/index.vue
  8. 5
      bmzm/chapter7/index.vue
  9. 5
      bmzm/home/home.vue
  10. 289
      components/AudioControl.vue
  11. 155
      components/AudioControl使用文档.md
  12. 65
      components/MusicControl.vue
  13. 2
      components/SwipeToNext.vue
  14. 225
      components/跨页面音频控制解决方案.md
  15. 144
      components/音频背景音乐交互说明.md
  16. 1
      main.js
  17. 7
      pages.json
  18. 16
      pages/index/readingBody.vue
  19. 5
      pig/chapter1/chapter1.vue
  20. 5
      pig/chapter2/chapter2.vue
  21. 5
      pig/chapter3/chapter3.vue
  22. 5
      pig/chapter4/chapter4.vue
  23. 1749
      pig/home/home.vue
  24. 96
      static/js/CommonFunction.js
  25. 5
      taozi/chapter1/chapter1.vue
  26. 5
      taozi/chapter2/chapter2.vue
  27. 5
      taozi/chapter3/chapter3.vue
  28. 5
      taozi/chapter4/chapter4.vue
  29. 5
      taozi/home/home.vue
  30. 92
      utils/globalAudioManager.js
  31. 206
      xqk/chapter1/index.vue
  32. 55
      xqk/chapter2/index.vue
  33. 154
      xqk/chapter3/index.vue
  34. 164
      xqk/chapter4/index.vue
  35. 74
      xqk/chapter5/index.vue
  36. 138
      xqk/chapter6/index.vue
  37. 143
      xqk/chapter7/index.vue
  38. 75
      xqk/chapter8/index.vue
  39. 6
      xqk/components/NavMenu.vue
  40. 52
      xqk/home/home.vue
  41. 16
      xrcc/chapter1/index.vue
  42. 5
      xrcc/chapter2/index.vue
  43. 6
      xrcc/chapter3/index.vue
  44. 6
      xrcc/chapter4/index.vue
  45. 6
      xrcc/chapter5/index.vue
  46. 6
      xrcc/chapter6/index.vue
  47. 6
      xrcc/chapter7/index.vue
  48. 6
      xrcc/chapter8/index.vue
  49. 31
      xrcc/home/home.vue
  50. 5
      xxdf/chapter1/cover1.vue
  51. 4
      xxdf/chapter1/detail1.vue
  52. 5
      xxdf/chapter1/detail2.vue
  53. 5
      xxdf/chapter1/detail3.vue
  54. 5
      xxdf/chapter1/detail4.vue
  55. 5
      xxdf/chapter1/detail5.vue
  56. 5
      xxdf/chapter2/cover.vue
  57. 5
      xxdf/chapter3/cover.vue
  58. 6
      xxdf/chapter4/cover.vue
  59. 5
      xxdf/home/home.vue

3
App.vue

@ -5,7 +5,8 @@
randomImages: [],
bgMusic: null,
isMusicPlaying: false,
musicSrc: 'https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3'
musicSrc: 'https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3',
currentAudio: null //
},
onLaunch: function() {
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')

372
bmzm/chapter1/index.vue

@ -1,147 +1,263 @@
<template>
<view>
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange">
<swiper-item v-for="(item, index) in swiperItems" :key="index" v-if="index < indexShow">
<view :class="['swiper-item',{'swiper-item1-10': item.images}]" :style="{ backgroundImage: `url(${item.imageUrl})` }" @click="changeIndex(index)">
<!-- 如果是第一章的第10个swiper-item显示图片并绑定点击事件 -->
<template v-if="item.images">
<image v-for="(image, imgIndex) in item.images" :key="imgIndex" :src="image.src"
mode="aspectFill" @click="setStorage(imgIndex);gotoPath(item.link.replace('{index}', imgIndex + item.linkIndex))"></image>
</template>
<view>
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange">
<swiper-item v-for="(item, index) in swiperItems" :key="index" v-if="index < indexShow">
<view :class="['swiper-item',{'swiper-item1-10': item.images}]"
:style="{ backgroundImage: `url(${item.imageUrl})` }" @click="changeIndex(index)">
<!-- 如果是第一章的第10个swiper-item显示图片并绑定点击事件 -->
<template v-if="item.images">
<image v-for="(image, imgIndex) in item.images" :key="imgIndex" :src="image.src"
mode="aspectFill"
@click="setStorage(imgIndex);gotoPath(item.link.replace('{index}', imgIndex + item.linkIndex))">
</image>
</template>
<!-- 视频 -->
<template v-if="index == 3">
<video src="https://static.ticket.sz-trip.com/epicSoul/bmzm.mp4" style="width: 100vw;height: 30vh;" objectFit="cover"></video>
<video src="https://static.ticket.sz-trip.com/epicSoul/bmzm.mp4" @play="handleVideoPlay"
@pause="handleVideoPause" @ended="handleVideoEnded" style="width: 100vw;height: 30vh;"
objectFit="cover"></video>
</template>
<!-- 动图 -->
<template v-if="index == 8">
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img7s.gif" mode="widthFix" style="width: 100vw;"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img7s.gif" mode="widthFix"
style="width: 100vw;"></image>
</template>
</view>
</swiper-item>
</swiper>
</view>
</swiper-item>
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
</view>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
},
data() {
return {
currentIndex: 0,
//
forbiddenIndex: 2,
initialIndex: 1,
popupIndex: 1,
// swiper-item
swiperItems: [
{
imageUrl: `https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index1.png`
},
{
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index5.png'
},
//
...Array.from({ length: 9 }, (_, i) => ({
imageUrl: `https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img${i + 1}.png`
})),
{
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img10.png',
images: Array.from({ length: 4 }, (_, i) => ({
src: `https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img10-${i + 1}.png`
})),
link: '/bmzm/chapter2/index?index={index}',
linkIndex: 11
}
],
indexShow: 3
};
},
onLoad(option) {
this.initialIndex = option.index;
// swiper-item
this.swiperItems[0].imageUrl = `https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index${this.initialIndex}.png`;
},
methods: {
touchmove() {
return this.currentIndex == 2 ? true : false
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu,
AudioControl
},
data() {
return {
currentIndex: 0,
//
forbiddenIndex: 2,
initialIndex: 1,
popupIndex: 1,
//
beforeVideoState: {
audioWasPlaying: false,
bgMusicWasPlaying: false
},
// swiper-item
swiperItems: [{
imageUrl: `https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index1.png`
},
{
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index5.png'
},
//
...Array.from({
length: 9
}, (_, i) => ({
imageUrl: `https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img${i + 1}.png`
})),
{
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img10.png',
images: Array.from({
length: 4
}, (_, i) => ({
src: `https://static.ticket.sz-trip.com/epicSoul/bmzm/chapter1/img10-${i + 1}.png`
})),
link: '/bmzm/chapter2/index?index={index}',
linkIndex: 11
}
],
indexShow: 3
};
},
changeIndex(index) {
if(index == 2) this.currentIndex = 3
this.indexShow = 100
onLoad(option) {
this.initialIndex = option.index;
// swiper-item
this.swiperItems[0].imageUrl =
`https://static.ticket.sz-trip.com/epicSoul/bmzm/index/index${this.initialIndex}.png`;
},
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
if(this.currentIndex == 2) this.indexShow = 3
},
// 使
setStorage(i) {
let text = ''
switch (i){
case 0:
text = '灵感的捕捉者'
break;
case 1:
text = '众智的编织者'
break;
case 2:
text = '智慧的开启者'
break;
case 3:
text = '自然的尊重者'
break;
default:
break;
}
this.appendToStorage('answerObj', { answer2: text });
methods: {
touchmove() {
return this.currentIndex == 2 ? true : false
},
changeIndex(index) {
if (index == 2) this.currentIndex = 3
this.indexShow = 100
},
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
if (this.currentIndex == 2) this.indexShow = 3
},
// 使
setStorage(i) {
let text = ''
switch (i) {
case 0:
text = '灵感的捕捉者'
break;
case 1:
text = '众智的编织者'
break;
case 2:
text = '智慧的开启者'
break;
case 3:
text = '自然的尊重者'
break;
default:
break;
}
this.appendToStorage('answerObj', {
answer2: text
});
},
//
handleVideoPlay() {
console.log('视频开始播放');
//
this.recordCurrentState();
//
this.pauseAllAudio();
},
//
handleVideoPause() {
console.log('视频暂停');
//
this.restorePreviousState();
},
//
handleVideoEnded() {
console.log('视频播放结束');
//
this.restorePreviousState();
},
//
recordCurrentState() {
try {
//
if (uni.$globalAudio && uni.$globalAudio.isAudioPlaying()) {
this.beforeVideoState.audioWasPlaying = true;
console.log('bmzm chapter1: 记录:音频正在播放');
} else {
this.beforeVideoState.audioWasPlaying = false;
}
//
const app = getApp();
if (app && app.globalData && app.globalData.isMusicPlaying) {
this.beforeVideoState.bgMusicWasPlaying = true;
console.log('bmzm chapter1: 记录:背景音乐正在播放');
} else {
this.beforeVideoState.bgMusicWasPlaying = false;
}
} catch (error) {
console.error('bmzm chapter1: 记录状态失败:', error);
}
},
//
pauseAllAudio() {
try {
//
if (this.beforeVideoState.audioWasPlaying && uni.$globalAudio) {
uni.$globalAudio.pauseCurrentAudio();
console.log('bmzm chapter1: 暂停音频');
}
//
if (this.beforeVideoState.bgMusicWasPlaying) {
const app = getApp();
if (app && app.globalData && app.globalData.bgMusic) {
app.globalData.bgMusic.pause();
console.log('bmzm chapter1: 暂停背景音乐');
}
}
} catch (error) {
console.error('bmzm chapter1: 暂停音频失败:', error);
}
},
//
restorePreviousState() {
try {
//
if (this.beforeVideoState.audioWasPlaying && uni.$globalAudio) {
uni.$globalAudio.playCurrentAudio();
console.log('bmzm chapter1: 恢复音频播放');
}
//
if (this.beforeVideoState.bgMusicWasPlaying) {
const app = getApp();
if (app && app.globalData && app.globalData.bgMusic) {
app.globalData.bgMusic.play();
console.log('bmzm chapter1: 恢复背景音乐播放');
}
}
//
this.beforeVideoState.audioWasPlaying = false;
this.beforeVideoState.bgMusicWasPlaying = false;
} catch (error) {
console.error('bmzm chapter1: 恢复状态失败:', error);
}
},
}
}
};
};
</script>
<style lang="scss" scoped>
.swiper {
width: 100vw;
height: 100vh;
}
.swiper-item {
width: 100vw;
height: 100vh;
background-size: 100% 100%;
}
.swiper-item1-10 {
padding: 506rpx 65rpx 370rpx;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
&>image:nth-child(n+1) {
width: 310rpx;
height: 316.58rpx;
}
&>image:nth-child(n+2) {
width: 291.61rpx;
height: 334.55rpx;
}
&>image:nth-child(n+3) {
width: 292.61rpx;
height: 334.55rpx;
}
&>image:nth-child(n+4) {
width: 309.59rpx;
height: 317.58rpx;
margin-top: 15rpx;
}
}
.swiper {
width: 100vw;
height: 100vh;
}
.swiper-item {
width: 100vw;
height: 100vh;
background-size: 100% 100%;
}
.swiper-item1-10 {
padding: 506rpx 65rpx 370rpx;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
&>image:nth-child(n+1) {
width: 310rpx;
height: 316.58rpx;
}
&>image:nth-child(n+2) {
width: 291.61rpx;
height: 334.55rpx;
}
&>image:nth-child(n+3) {
width: 292.61rpx;
height: 334.55rpx;
}
&>image:nth-child(n+4) {
width: 309.59rpx;
height: 317.58rpx;
margin-top: 15rpx;
}
}
</style>

5
bmzm/chapter2/index.vue

@ -10,16 +10,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
bmzm/chapter3/index.vue

@ -29,16 +29,19 @@
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
bmzm/chapter4/index.vue

@ -19,16 +19,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
bmzm/chapter5/index.vue

@ -8,16 +8,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
bmzm/chapter6/index.vue

@ -11,16 +11,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
bmzm/chapter7/index.vue

@ -51,16 +51,19 @@
</uni-popup>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
bmzm/home/home.vue

@ -19,16 +19,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

289
components/AudioControl.vue

@ -0,0 +1,289 @@
<template>
<view class="audio-control" :style="{top:top}" @click.stop="toggleAudio">
<image v-if="isAudioPlaying" :src="showImg('/uploads/20250904/c7ddc330d25a0916885a31f1d28f6665.png')" mode="" style="width: 80rpx;height: 80rpx;"></image>
<image v-else :src="showImg('/uploads/20250904/057669bd710740650db1cafd414c08c9.png')" mode="" style="width: 80rpx;height: 80rpx;"></image>
</view>
</template>
<script>
export default {
name: 'AudioControl',
inheritAttrs: false,
props: {
//
audioSrc: {
type: String,
required: true
},
top:{
type:String,
default:'180rpx'
},
//
visible: {
type: Boolean,
default: true
}
},
data() {
return {
isAudioPlaying: false,
audioContext: null,
backgroundMusicWasPlaying: false, //
audioInstanceId: null //
}
},
mounted() {
this.initAudio();
//
uni.$on('backgroundMusicToggle', this.handleBackgroundMusicToggle);
//
this.checkGlobalAudioState();
},
beforeUnmount() {
this.destroyAudio();
//
uni.$off('backgroundMusicToggle', this.handleBackgroundMusicToggle);
},
methods: {
initAudio() {
try {
//
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
// URL
if (app.globalData.currentAudio.src === this.audioSrc) {
this.audioContext = app.globalData.currentAudio;
this.isAudioPlaying = !this.audioContext.paused;
this.setupAudioEvents();
return;
} else {
// URL
app.globalData.currentAudio.stop();
app.globalData.currentAudio.destroy();
}
}
//
this.audioContext = uni.createInnerAudioContext();
this.audioContext.src = this.audioSrc;
//
if (app && app.globalData) {
app.globalData.currentAudio = this.audioContext;
}
this.setupAudioEvents();
} catch (error) {
console.error('初始化音频失败:', error);
}
},
setupAudioEvents() {
if (!this.audioContext) return;
//
this.audioContext.onPlay(() => {
this.isAudioPlaying = true;
console.log('音频开始播放');
});
this.audioContext.onPause(() => {
this.isAudioPlaying = false;
console.log('音频暂停');
});
this.audioContext.onStop(() => {
this.isAudioPlaying = false;
console.log('音频停止');
});
this.audioContext.onEnded(() => {
this.isAudioPlaying = false;
//
this.restoreBackgroundMusic();
console.log('音频播放结束');
});
this.audioContext.onError((err) => {
console.error('音频播放错误:', err);
this.isAudioPlaying = false;
//
this.restoreBackgroundMusic();
});
},
checkGlobalAudioState() {
try {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
const globalAudio = app.globalData.currentAudio;
// URLURL
if (globalAudio.src === this.audioSrc) {
this.isAudioPlaying = !globalAudio.paused;
console.log('同步全局音频状态:', this.isAudioPlaying);
}
}
} catch (error) {
console.error('检查全局音频状态失败:', error);
}
},
toggleAudio() {
if (!this.audioContext) {
console.error('音频上下文未初始化');
return;
}
if (this.isAudioPlaying) {
//
this.pauseAudio();
} else {
//
this.playAudio();
}
},
playAudio() {
try {
//
this.pauseBackgroundMusic();
//
this.audioContext.play();
//
uni.$emit('audioPlaying', true);
} catch (error) {
console.error('播放音频失败:', error);
}
},
pauseAudio() {
try {
//
this.audioContext.pause();
//
this.restoreBackgroundMusic();
//
uni.$emit('audioPlaying', false);
} catch (error) {
console.error('暂停音频失败:', error);
}
},
pauseBackgroundMusic() {
try {
const app = getApp();
if (app && app.globalData && app.globalData.bgMusic) {
//
this.backgroundMusicWasPlaying = app.globalData.isMusicPlaying;
//
if (this.backgroundMusicWasPlaying) {
app.globalData.bgMusic.pause();
console.log('背景音乐已暂停');
}
}
} catch (error) {
console.error('暂停背景音乐失败:', error);
}
},
restoreBackgroundMusic() {
try {
const app = getApp();
if (app && app.globalData && app.globalData.bgMusic && this.backgroundMusicWasPlaying) {
//
app.globalData.bgMusic.play();
console.log('背景音乐已恢复');
}
} catch (error) {
console.error('恢复背景音乐失败:', error);
}
},
destroyAudio() {
//
console.log('AudioControl组件销毁,但保持全局音频实例');
//
this.audioContext = null;
},
//
handleBackgroundMusicToggle() {
//
if (this.isAudioPlaying) {
try {
//
this.audioContext.pause();
//
uni.$emit('audioPlaying', false);
console.log('背景音乐切换时暂停音频(不恢复背景音乐)');
} catch (error) {
console.error('暂停音频失败:', error);
}
}
}
},
watch: {
//
audioSrc: {
handler(newSrc) {
if (this.audioContext && newSrc) {
this.audioContext.src = newSrc;
}
},
immediate: true
}
}
}
</script>
<style lang="scss" scoped>
.audio-control {
position: absolute;
top: 180rpx;
right: 30rpx;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
// background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 998;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
}
}
.audio-icon {
font-size: 36rpx;
color: #fff;
transition: all 0.3s ease;
}
.playing {
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
</style>

155
components/AudioControl使用文档.md

@ -0,0 +1,155 @@
# AudioControl 音频控制组件使用文档
## 组件功能
- 在父组件右上角显示音频控制图标
- 点击图标播放指定音频,同时暂停背景音乐
- 再次点击暂停音频,恢复背景音乐
- 音频播放结束后自动恢复背景音乐
## 组件属性 (Props)
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| audioSrc | String | 是 | - | 音频文件路径 |
| visible | Boolean | 否 | true | 是否显示组件 |
## 使用方法
### 1. 在父组件中引入和注册组件
```vue
<template>
<view class="parent-container">
<!-- 父组件内容 -->
<view class="content">
<!-- 你的页面内容 -->
</view>
<!-- 音频控制组件 -->
<AudioControl
:audioSrc="audioUrl"
:visible="showAudio"
/>
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
export default {
components: {
AudioControl
},
data() {
return {
audioUrl: 'https://your-domain.com/audio/sample.mp3', // 替换为你的音频URL
showAudio: true
}
}
}
</script>
<style>
.parent-container {
position: relative; /* 重要:确保AudioControl能正确定位 */
width: 100vw;
height: 100vh;
}
</style>
```
### 2. 使用项目中的showImg方法(推荐)
如果你的音频文件也存储在项目服务器上,可以使用项目的showImg方法:
```vue
<script>
export default {
data() {
return {
audioUrl: this.showImg('/uploads/audio/your-audio-file.mp3'),
showAudio: true
}
}
}
</script>
```
### 3. 动态控制音频源
你可以根据不同的页面或条件播放不同的音频:
```vue
<script>
export default {
data() {
return {
audioUrl: '',
showAudio: true
}
},
mounted() {
// 根据页面设置不同的音频
this.setAudioForCurrentPage();
},
methods: {
setAudioForCurrentPage() {
const currentRoute = this.$route.path; // 假设使用vue-router
switch(currentRoute) {
case '/chapter1':
this.audioUrl = this.showImg('/uploads/audio/chapter1.mp3');
break;
case '/chapter2':
this.audioUrl = this.showImg('/uploads/audio/chapter2.mp3');
break;
default:
this.audioUrl = this.showImg('/uploads/audio/default.mp3');
}
}
}
}
</script>
```
## 样式说明
组件默认定位在父组件的右上角(top: 30rpx, right: 30rpx),如果需要调整位置,可以在父组件中覆盖样式:
```vue
<style>
/* 调整音频控制组件位置 */
.parent-container ::v-deep .audio-control {
top: 50rpx !important;
right: 50rpx !important;
}
</style>
```
## 注意事项
1. **父组件样式**:确保父组件设置了 `position: relative`,这样AudioControl组件才能正确定位
2. **音频格式**:建议使用 mp3 格式的音频文件,兼容性最好
3. **音频路径**:确保音频文件路径正确且可访问
4. **背景音乐**:组件会自动处理与MusicControl组件的交互,无需额外配置
## 图标说明
- 🔊:音频未播放状态
- 🎧:音频播放中状态,带有脉动动画效果
## 事件处理
组件内部已处理所有音频播放逻辑,包括:
- 播放音频时自动暂停背景音乐
- 暂停音频时自动恢复背景音乐
- 音频播放结束时自动恢复背景音乐
- 组件销毁时自动清理资源
## 示例场景
适用于以下场景:
- 章节页面播放对应的音频解说
- 展示页面播放介绍音频
- 互动页面播放提示音频
- 任何需要临时播放音频并暂停背景音乐的场景

65
components/MusicControl.vue

@ -11,24 +11,35 @@ export default {
name: 'MusicControl',
data() {
return {
isPlaying: false
isPlaying: false,
isAudioPlaying: false //
}
},
mounted() {
console.log('初始化')
// console.log('')
//
this.syncMusicState();
//
this.checkGlobalAudioState();
//
this.timer = setInterval(() => {
this.syncMusicState();
this.checkGlobalAudioState();
}, 1000);
//
uni.$on('audioPlaying', this.handleAudioStateChange);
},
beforeUnmount() {
//
if (this.timer) {
clearInterval(this.timer);
}
//
uni.$off('audioPlaying', this.handleAudioStateChange);
},
methods: {
syncMusicState() {
@ -41,19 +52,57 @@ export default {
toggleMusic() {
const app = getApp();
if (!app || !app.globalData || !app.globalData.bgMusic) {
console.error('背景音乐未初始化');
// console.error('');
return;
}
const bgMusic = app.globalData.bgMusic;
console.log(bgMusic)
// console.log(bgMusic)
//
if (this.isPlaying) {
bgMusic.pause();
// AudioControl
if (this.isAudioPlaying) {
uni.$emit('backgroundMusicToggle');
//
setTimeout(() => {
//
if (this.isPlaying) {
bgMusic.pause();
} else {
bgMusic.play();
}
}, 100);
} else {
bgMusic.play();
//
if (this.isPlaying) {
bgMusic.pause();
} else {
bgMusic.play();
}
}
},
//
handleAudioStateChange(isAudioPlaying) {
this.isAudioPlaying = isAudioPlaying;
// console.log(':', isAudioPlaying);
},
//
checkGlobalAudioState() {
try {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
const globalAudio = app.globalData.currentAudio;
//
this.isAudioPlaying = !globalAudio.paused;
// console.log(':', this.isAudioPlaying);
} else {
this.isAudioPlaying = false;
}
} catch (error) {
// console.error(':', error);
this.isAudioPlaying = false;
}
}
}

2
components/SwipeToNext.vue

@ -34,7 +34,7 @@ export default {
//
tipText: {
type: String,
default: '上滑进入下一章节'
default: '上滑进入下一章节'
},
// px
swipeThreshold: {

225
components/跨页面音频控制解决方案.md

@ -0,0 +1,225 @@
# 跨页面音频控制解决方案
## 进一步优化:解决跨页面状态同步问题
### 问题描述
在跨页面场景下,当音频正在播放时跳转到新页面,新页面的MusicControl组件不知道有音频在播放,点击背景音乐按钮时会直接播放背景音乐,导致音频和背景音乐同时播放。
### 解决方案
#### 1. MusicControl组件增强检测
```javascript
// 在mounted生命周期中添加全局音频状态检测
mounted() {
this.syncMusicState();
this.checkGlobalAudioState(); // 新增:检查全局音频状态
// 定时器也要检查全局音频状态
this.timer = setInterval(() => {
this.syncMusicState();
this.checkGlobalAudioState();
}, 1000);
}
// 新增方法:检查全局音频状态
checkGlobalAudioState() {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
const globalAudio = app.globalData.currentAudio;
this.isAudioPlaying = !globalAudio.paused;
} else {
this.isAudioPlaying = false;
}
}
```
#### 2. 全局音频管理工具优化
```javascript
// 在音频状态变化时发送全局事件
pauseCurrentAudio() {
const audio = this.getCurrentAudio();
if (audio && !audio.paused) {
audio.pause();
this.notifyAudioStateChange(false); // 通知状态变化
return true;
}
return false;
}
// 新增:通知音频状态变化
notifyAudioStateChange(isPlaying) {
if (typeof uni !== 'undefined') {
uni.$emit('audioPlaying', isPlaying);
}
}
```
### 修复后的交互流程
```mermaid
graph TD
A[跨页面跳转] --> B[MusicControl组件加载]
B --> C[检查全局音频状态]
C --> D{有音频在播放?}
D -->|是| E[设置isAudioPlaying=true]
D -->|否| F[设置isAudioPlaying=false]
E --> G[点击背景音乐按钮]
F --> G
G --> H{检查isAudioPlaying}
H -->|有音频| I[先暂停音频再播放背景音乐]
H -->|无音频| J[直接播放背景音乐]
```
## 问题描述
AudioControl组件在页面跳转时会出现以下问题:
1. 组件状态重置,图标显示不正确
2. 音频实例丢失连接,但音频可能仍在播放
3. 无法在其他页面控制正在播放的音频
## 解决方案
### 1. 全局音频实例管理
在`App.vue`的`globalData`中添加`currentAudio`属性,用于保存当前的音频实例:
```javascript
globalData: {
// ... 其他属性
currentAudio: null // 全局音频实例
}
```
### 2. AudioControl组件优化
#### 状态同步机制
- 组件挂载时检查全局音频状态
- 复用已存在的音频实例(如果URL匹配)
- 组件销毁时不销毁全局音频实例
#### 核心方法改进
```javascript
// 检查全局音频状态
checkGlobalAudioState() {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
const globalAudio = app.globalData.currentAudio;
if (globalAudio.src === this.audioSrc) {
this.isAudioPlaying = !globalAudio.paused;
}
}
}
// 初始化音频时复用全局实例
initAudio() {
const app = getApp();
if (app && app.globalData && app.globalData.currentAudio) {
if (app.globalData.currentAudio.src === this.audioSrc) {
// 复用现有实例
this.audioContext = app.globalData.currentAudio;
this.isAudioPlaying = !this.audioContext.paused;
return;
}
}
// 创建新实例...
}
```
### 3. 全局音频管理工具
创建了`utils/globalAudioManager.js`工具类,提供统一的音频控制接口:
```javascript
// 在任何页面或组件中使用
uni.$globalAudio.pauseCurrentAudio(); // 暂停当前音频
uni.$globalAudio.playCurrentAudio(); // 播放当前音频
uni.$globalAudio.isAudioPlaying(); // 检查播放状态
uni.$globalAudio.getCurrentAudioSrc(); // 获取当前音频源
```
## 使用示例
### 在页面中控制音频
```vue
<template>
<view>
<!-- 音频控制组件 -->
<AudioControl :audioSrc="audioUrl" />
<!-- 手动控制按钮 -->
<button @click="toggleAudio">切换音频</button>
<button @click="stopAudio">停止音频</button>
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
export default {
components: { AudioControl },
data() {
return {
audioUrl: this.showImg('/uploads/audio/chapter1.mp3')
}
},
methods: {
toggleAudio() {
if (uni.$globalAudio.isAudioPlaying()) {
uni.$globalAudio.pauseCurrentAudio();
} else {
uni.$globalAudio.playCurrentAudio();
}
},
stopAudio() {
uni.$globalAudio.stopCurrentAudio();
}
}
}
</script>
```
### 在其他页面检查音频状态
```javascript
// 在任何页面的onShow生命周期中
onShow() {
// 检查是否有音频在播放
if (uni.$globalAudio.isAudioPlaying()) {
console.log('有音频正在播放:', uni.$globalAudio.getCurrentAudioSrc());
}
}
```
## 技术优势
### ✅ **状态持久化**
- 音频实例在页面跳转时不会丢失
- 组件状态能够正确同步全局音频状态
### ✅ **跨页面控制**
- 在任何页面都可以控制当前播放的音频
- 提供统一的音频管理接口
### ✅ **资源优化**
- 避免创建多个音频实例
- 自动清理无用的音频资源
### ✅ **用户体验**
- 页面跳转时音频播放不中断
- 图标状态显示正确
- 音频控制逻辑一致
## 注意事项
1. **页面生命周期**:音频实例与页面生命周期解耦,需要手动管理
2. **内存管理**:确保在应用退出时正确清理音频资源
3. **状态同步**:多个AudioControl组件需要监听相同的全局状态
4. **错误处理**:增强错误处理机制,确保音频异常时的状态恢复
## 兼容性
- ✅ uni-app
- ✅ 小程序环境
- ✅ H5环境
- ✅ APP环境

144
components/音频背景音乐交互说明.md

@ -0,0 +1,144 @@
# 音频与背景音乐交互功能说明
## Bug修复记录
### 问题描述
当音频播放时点击背景音乐按钮,背景音乐暂停音频并开始播放。此时再点击关闭背景音乐,图标显示关闭状态但背景音乐仍在播放。
### 问题原因
1. 点击背景音乐按钮时会发送事件暂停音频
2. 音频暂停时会调用restoreBackgroundMusic恢复背景音乐
3. 然后MusicControl再执行自己的切换逻辑
4. 导致背景音乐被恢复后又被操作,状态混乱
### 解决方案
1. **MusicControl组件优化**
- 检测是否有音频在播放
- 如有音频,先暂停音频,延迟执行背景音乐切换
- 如无音频,直接切换背景音乐状态
2. **AudioControl组件优化**
- 在handleBackgroundMusicToggle中只暂停音频
- 不自动恢复背景音乐,让MusicControl自己控制
### 修复后的交互流程
```mermaid
graph TD
A[点击背景音乐按钮] --> B{是否有音频播放?}
B -->|是| C[发送暂停音频事件]
C --> D[AudioControl暂停音频\n不恢复背景音乐]
D --> E[延迟100ms后切换背景音乐]
B -->|否| F[直接切换背景音乐状态]
```
## 实现的功能
### 🎵 **背景音乐控制音频**
当点击背景音乐控制按钮时:
- 如果有音频正在播放,会自动暂停音频
- 然后正常切换背景音乐的播放/暂停状态
### 🎧 **音频控制背景音乐**
当点击音频控制按钮时:
- 播放音频时自动暂停背景音乐
- 暂停音频时自动恢复背景音乐
- 音频播放结束时自动恢复背景音乐
## 技术实现
### 事件通信机制
使用uni-app的全局事件机制实现组件间通信:
```javascript
// AudioControl组件发送事件
uni.$emit('audioPlaying', true/false);
// MusicControl组件发送事件
uni.$emit('backgroundMusicToggle');
// 组件监听事件
uni.$on('eventName', this.handlerFunction);
```
### 交互流程
#### 点击背景音乐按钮:
```mermaid
graph TD
A[点击背景音乐按钮] --> B[发送backgroundMusicToggle事件]
B --> C[AudioControl收到事件]
C --> D{音频是否在播放?}
D -->|是| E[暂停音频]
D -->|否| F[继续背景音乐操作]
E --> G[恢复背景音乐]
F --> H[切换背景音乐状态]
```
#### 点击音频按钮:
```mermaid
graph TD
A[点击音频按钮] --> B{当前音频状态?}
B -->|未播放| C[暂停背景音乐]
C --> D[播放音频]
D --> E[发送audioPlaying:true事件]
B -->|正在播放| F[暂停音频]
F --> G[恢复背景音乐]
G --> H[发送audioPlaying:false事件]
```
## 使用方法
在页面中同时使用两个组件:
```vue
<template>
<view class="page-container">
<!-- 页面内容 -->
<!-- 音频控制组件 -->
<AudioControl :audioSrc="audioUrl" />
<!-- 背景音乐控制组件 -->
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
export default {
components: {
AudioControl,
MusicControl
},
data() {
return {
audioUrl: this.showImg('/uploads/audio/your-audio.mp3')
}
}
}
</script>
```
## 优势特点
### ✅ **智能交互**
- 两个组件能够智能感知对方的状态
- 避免同时播放音频和背景音乐造成的冲突
- 提供良好的用户体验
### ✅ **解耦设计**
- 组件间通过事件通信,保持松耦合
- 每个组件都能独立工作
- 易于维护和扩展
### ✅ **状态同步**
- 实时同步音频和背景音乐的播放状态
- 确保状态的一致性和准确性
## 注意事项
1. **事件监听清理**:组件销毁时会自动清理事件监听,避免内存泄漏
2. **状态管理**:两个组件都维护各自的状态,通过事件保持同步
3. **错误处理**:包含完善的错误处理机制,确保功能稳定运行

1
main.js

@ -4,6 +4,7 @@ import App from './App'
import store from './store'
import '@/static/js/request.js'
import '@/static/js/CommonFunction.js'
import '@/utils/globalAudioManager.js'
import {myMixins} from '@/mixins/myMixins.js'
Vue.mixin(myMixins)

7
pages.json

@ -604,13 +604,6 @@
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "chapter7/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
}
]
}

16
pages/index/readingBody.vue

@ -60,7 +60,7 @@
</view>
<view class="reading-box">
<image v-for="(item,index) in readingList" :key="index" :src="showImg(item.image)" @click="gotoUrlNew(item)"></image>
<image :src="showImg('/uploads/20250903/1aadd1b9a4c94ff0aa6b3f880a725b50.png')" @click="gotoUrlNew({jump_type:2,front_model:{mini:'/xqk/home/home'}})"></image>
<!-- <image :src="showImg('/uploads/20250903/1aadd1b9a4c94ff0aa6b3f880a725b50.png')" @click="gotoUrlNew({jump_type:2,front_model:{mini:'/xqk/home/home'}})"></image> -->
</view>
<CustomTabBar :currentTab="1" />
@ -110,6 +110,8 @@
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
}
//
this.pauseAllOtherAudio();
},
methods: {
sendRequest() {
@ -188,6 +190,18 @@
uni.navigateTo({
url:'/subPackages/letter/detail?id='+item.id
})
},
// AudioControl
pauseAllOtherAudio() {
try {
// AudioControl
if (uni.$globalAudio && uni.$globalAudio.isAudioPlaying()) {
uni.$globalAudio.pauseCurrentAudio();
console.log('readingBody: 暂停其他音频,只保留背景音乐');
}
} catch (error) {
console.error('readingBody: 暂停音频失败:', error);
}
}
}
}

5
pig/chapter1/chapter1.vue

@ -104,10 +104,12 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="4" />
<MusicControl />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import TitleHeader from "@/components/TitleHeader.vue";
import SideNav from "../components/SideNav.vue";
import MusicControl from "@/components/MusicControl.vue";
@ -115,7 +117,8 @@ export default {
components: {
TitleHeader,
SideNav,
MusicControl
MusicControl,
AudioControl
},
data() {
return {

5
pig/chapter2/chapter2.vue

@ -121,17 +121,20 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="5" />
<MusicControl></MusicControl>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import TitleHeader from "@/components/TitleHeader.vue";
import SideNav from "../components/SideNav.vue";
import MusicControl from "@/components/MusicControl.vue";
export default {
components: {
TitleHeader,
SideNav,MusicControl
SideNav,MusicControl,
AudioControl
},
data() {
return {

5
pig/chapter3/chapter3.vue

@ -158,10 +158,12 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="6" />
<MusicControl></MusicControl>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import TitleHeader from "@/components/TitleHeader.vue";
import SideNav from "../components/SideNav.vue";
import MusicControl from "@/components/MusicControl.vue";
@ -169,7 +171,8 @@ export default {
components: {
TitleHeader,
SideNav,
MusicControl
MusicControl,
AudioControl
},
data() {
return {

5
pig/chapter4/chapter4.vue

@ -430,10 +430,12 @@
<!-- 侧边导航组件 -->
<SideNav :currentIndex="7" />
<MusicControl></MusicControl>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/5d7caee5-ce7f-4e55-bf71-e574b486473c.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import TitleHeader from "@/components/TitleHeader.vue";
import SideNav from "../components/SideNav.vue";
import MusicControl from "@/components/MusicControl.vue";
@ -441,7 +443,8 @@ export default {
components: {
TitleHeader,
SideNav,
MusicControl
MusicControl,
AudioControl
},
data() {
return {

1749
pig/home/home.vue

File diff suppressed because it is too large

96
static/js/CommonFunction.js

@ -167,9 +167,101 @@ Vue.prototype._requestLocation = function(callback) {
// 路由页面跳转
Vue.prototype.gotoPath = path => {
console.log(path,'pathpath');
// 参数验证
if (!path || typeof path !== 'string') {
console.error('gotoPath: 路径参数无效', path);
return;
}
// 清理路径,移除多余的空格和特殊字符
path = path.trim();
// 检查是否为有效路径格式
if (!path.startsWith('/')) {
console.error('gotoPath: 路径必须以/开头', path);
return;
}
// 获取当前页面栈
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentPath = currentPage ? currentPage.route : '';
// 避免重复跳转到同一页面
if (currentPath && ('/' + currentPath) === path) {
console.warn('gotoPath: 避免重复跳转到当前页面', path);
return;
}
// 检查页面栈深度,避免栈溢出
if (pages.length >= 10) {
console.warn('gotoPath: 页面栈过深,使用redirectTo替代', path);
uni.redirectTo({
url: path,
fail: (err) => {
console.error('redirectTo失败:', err, 'path:', path);
// 最后尝试reLaunch
uni.reLaunch({
url: path,
fail: (err2) => {
console.error('reLaunch也失败:', err2, 'path:', path);
}
});
}
});
return;
}
// 防抖处理,避免快速重复点击
if (Vue.prototype._lastNavigateTime && Date.now() - Vue.prototype._lastNavigateTime < 500) {
console.warn('gotoPath: 防抖拦截,请勿频繁点击');
return;
}
Vue.prototype._lastNavigateTime = Date.now();
// 尝试正常跳转
uni.navigateTo({
url: path
})
url: path,
success: (res) => {
console.log('跳转成功:', path);
},
fail: (err) => {
console.error('navigateTo失败:', err, 'path:', path);
// 如果是tabBar页面,使用switchTab
if (err.errMsg && err.errMsg.includes('tabbar')) {
console.log('检测到tabBar页面,使用switchTab');
uni.switchTab({
url: path,
fail: (err2) => {
console.error('switchTab也失败:', err2, 'path:', path);
}
});
} else if (err.errMsg && err.errMsg.includes('limit exceed')) {
// 页面栈超限,使用redirectTo
console.log('页面栈超限,使用redirectTo');
uni.redirectTo({
url: path,
fail: (err3) => {
console.error('redirectTo也失败:', err3, 'path:', path);
}
});
} else {
// 其他错误,尝试延迟重试
console.log('延迟重试跳转');
setTimeout(() => {
uni.navigateTo({
url: path,
fail: (err4) => {
console.error('延迟重试也失败:', err4, 'path:', path);
}
});
}, 300);
}
}
});
}
// 返回上一页

5
taozi/chapter1/chapter1.vue

@ -1,6 +1,7 @@
<template>
<view>
<TitleHeader />
<AudioControl :top="'230rpx'" audioSrc="https://des.js-dyyj.com/data/2025/09/05/286e6a8d-4433-4d69-b705-74b3f4237667.MP3" />
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange"
:duration="300" :style="{ height: `calc(100vh - ${titleHeight}px)` }">
<swiper-item>
@ -87,10 +88,12 @@
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import TitleHeader from '@/components/TitleHeader.vue';
export default {
components: {
TitleHeader
TitleHeader,
AudioControl
},
data() {
return {

5
taozi/chapter2/chapter2.vue

@ -72,17 +72,20 @@
</view>
</swiper-item>
</swiper>
<AudioControl :top="'230rpx'" audioSrc="https://des.js-dyyj.com/data/2025/09/05/286e6a8d-4433-4d69-b705-74b3f4237667.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import TitleHeader from '@/components/TitleHeader.vue';
export default {
components: {
MusicControl,
TitleHeader
TitleHeader,
AudioControl
},
data() {
return {

5
taozi/chapter3/chapter3.vue

@ -99,14 +99,17 @@
</view>
</swiper-item>
</swiper>
<AudioControl :top="'230rpx'" audioSrc="https://des.js-dyyj.com/data/2025/09/05/286e6a8d-4433-4d69-b705-74b3f4237667.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import TitleHeader from '@/components/TitleHeader.vue';
export default {
components: {
TitleHeader
TitleHeader,
AudioControl
},
data() {
return {

5
taozi/chapter4/chapter4.vue

@ -97,14 +97,17 @@
</view>
</swiper-item>
</swiper>
<AudioControl :top="'230rpx'" audioSrc="https://des.js-dyyj.com/data/2025/09/05/286e6a8d-4433-4d69-b705-74b3f4237667.MP3" />
</view>
</template>
<script>
import TitleHeader from '@/components/TitleHeader.vue';
import AudioControl from '@/components/AudioControl.vue';
export default {
components: {
TitleHeader
TitleHeader,
AudioControl
},
data() {
return {

5
taozi/home/home.vue

@ -160,10 +160,12 @@
<messagePop />
<!-- <BackgroundMusic /> -->
<MusicControl />
<AudioControl :top="'400rpx'" audioSrc="https://des.js-dyyj.com/data/2025/09/05/286e6a8d-4433-4d69-b705-74b3f4237667.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import BuyPeaches from '@/components/BuyPeaches.vue';
import messagePop from '@/components/messagePop.vue';
// import BackgroundMusic from '@/components/BackgroundMusic.vue';
@ -178,7 +180,8 @@
// BackgroundMusic,
messageBoard,
MusicControl,
TitleHeader
TitleHeader,
AudioControl
},
data() {
return {

92
utils/globalAudioManager.js

@ -0,0 +1,92 @@
// 全局音频管理工具
export const GlobalAudioManager = {
// 获取当前全局音频实例
getCurrentAudio() {
const app = getApp();
return app && app.globalData ? app.globalData.currentAudio : null;
},
// 设置全局音频实例
setCurrentAudio(audioContext) {
const app = getApp();
if (app && app.globalData) {
app.globalData.currentAudio = audioContext;
}
},
// 清除全局音频实例
clearCurrentAudio() {
const app = getApp();
if (app && app.globalData) {
if (app.globalData.currentAudio) {
try {
app.globalData.currentAudio.stop();
app.globalData.currentAudio.destroy();
} catch (error) {
console.error('销毁全局音频失败:', error);
}
}
app.globalData.currentAudio = null;
}
},
// 检查是否有音频在播放
isAudioPlaying() {
const audio = this.getCurrentAudio();
return audio && !audio.paused;
},
// 暂停当前音频
pauseCurrentAudio() {
const audio = this.getCurrentAudio();
if (audio && !audio.paused) {
audio.pause();
// 通知状态变化
this.notifyAudioStateChange(false);
return true;
}
return false;
},
// 播放当前音频
playCurrentAudio() {
const audio = this.getCurrentAudio();
if (audio && audio.paused) {
audio.play();
// 通知状态变化
this.notifyAudioStateChange(true);
return true;
}
return false;
},
// 停止当前音频
stopCurrentAudio() {
const audio = this.getCurrentAudio();
if (audio) {
audio.stop();
// 通知状态变化
this.notifyAudioStateChange(false);
return true;
}
return false;
},
// 通知音频状态变化
notifyAudioStateChange(isPlaying) {
if (typeof uni !== 'undefined') {
uni.$emit('audioPlaying', isPlaying);
}
},
// 获取当前音频的src
getCurrentAudioSrc() {
const audio = this.getCurrentAudio();
return audio ? audio.src : null;
}
};
// 将工具挂载到全局
if (typeof uni !== 'undefined') {
uni.$globalAudio = GlobalAudioManager;
}

206
xqk/chapter1/index.vue

@ -1,37 +1,28 @@
<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> -->
<view style="width: 100vw;position: relative;">
<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 == 2">
<video :src="showImg('/uploads/20250905/c47451404cc87d89205b1003e3a0b589.mp4')"
style="width: 100vw;height: 30vh;" objectFit="cover" @play="handleVideoPlay"
@pause="handleVideoPause" @ended="handleVideoEnded"></video>
<image @click="gotoPath('/xqk/chapter2/index')" v-if="currentIndex == swiperImages.length-1"
:src="showImg('/uploads/20250905/692abbf32b38257ffb2153651f468a63.png')" class="imgJump"
mode=""></image>
</template>
</view>
</swiper-item>
</swiper>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
import SwipeToNext from '@/components/SwipeToNext.vue';
@ -39,12 +30,18 @@
components: {
MusicControl,
NavMenu,
SwipeToNext
SwipeToNext,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 1,
//
beforeVideoState: {
audioWasPlaying: false,
bgMusicWasPlaying: false
},
swiperImages: [
this.showImg('/uploads/20250903/24303e4b7218eaf3d857c846417eb490.png'),
this.showImg('/uploads/20250903/17495ef65648c64c31920d312301e991.png'),
@ -62,39 +59,165 @@
}
},
popupIndex: 1,
isLastSlide:false,
}
},
onLoad(option) {
this.currentIndex = option.currentIndex || 0
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1;
},
onHide() {
// xqk
console.log('xqk chapter1: 页面隐藏,保持音频状态');
},
onUnload() {
// xqk
this.handleXqkAudioOnLeave();
},
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();
},
//
handleVideoPlay() {
console.log('视频开始播放');
//
this.recordCurrentState();
//
this.pauseAllAudio();
},
//
handleVideoPause() {
console.log('视频暂停');
//
this.restorePreviousState();
},
//
handleVideoEnded() {
console.log('视频播放结束');
//
this.restorePreviousState();
},
//
recordCurrentState() {
try {
//
if (uni.$globalAudio && uni.$globalAudio.isAudioPlaying()) {
this.beforeVideoState.audioWasPlaying = true;
console.log('记录:音频正在播放');
} else {
this.beforeVideoState.audioWasPlaying = false;
}
//
const app = getApp();
if (app && app.globalData && app.globalData.isMusicPlaying) {
this.beforeVideoState.bgMusicWasPlaying = true;
console.log('记录:背景音乐正在播放');
} else {
this.beforeVideoState.bgMusicWasPlaying = false;
}
} catch (error) {
console.error('记录状态失败:', error);
}
},
//
pauseAllAudio() {
try {
//
if (this.beforeVideoState.audioWasPlaying && uni.$globalAudio) {
uni.$globalAudio.pauseCurrentAudio();
console.log('暂停音频');
}
//
if (this.beforeVideoState.bgMusicWasPlaying) {
const app = getApp();
if (app && app.globalData && app.globalData.bgMusic) {
app.globalData.bgMusic.pause();
console.log('暂停背景音乐');
}
}
} catch (error) {
console.error('暂停音频失败:', error);
}
},
//
restorePreviousState() {
try {
//
if (this.beforeVideoState.audioWasPlaying && uni.$globalAudio) {
uni.$globalAudio.playCurrentAudio();
console.log('恢复音频播放');
}
//
if (this.beforeVideoState.bgMusicWasPlaying) {
const app = getApp();
if (app && app.globalData && app.globalData.bgMusic) {
app.globalData.bgMusic.play();
console.log('恢复背景音乐播放');
}
}
//
this.beforeVideoState.audioWasPlaying = false;
this.beforeVideoState.bgMusicWasPlaying = false;
} catch (error) {
console.error('恢复状态失败:', error);
}
},
// URL
getImageUrl(path) {
if (typeof path === 'object') {
path = path.url;
}
return `https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter1/${path}`;
},
// xqk
handleXqkAudioOnLeave() {
try {
//
const currentPath = uni.$xqkAudio.getCurrentPath();
//
const pages = getCurrentPages();
let shouldPause = true; //
// xqk
if (pages.length > 1) {
const previousPage = pages[pages.length - 2];
if (previousPage && previousPage.route) {
// xqk
if (uni.$xqkAudio.isInXqkPackage(previousPage.route)) {
shouldPause = false;
}
}
}
if (shouldPause) {
// xqk
uni.$xqkAudio.pauseXqkAudio();
console.log('xqk chapter1: 离开xqk分包,已暂停音频');
} else {
console.log('xqk chapter1: 仍在xqk分包内,保持音频播放');
}
} catch (error) {
console.error('xqk chapter1: 音频管理失败:', error);
}
}
}
}
@ -105,6 +228,15 @@
width: 100vw;
height: 100vh;
}
.imgJump{
position: absolute;
bottom:53rpx;
right:0;
width: 273rpx;
height: 85rpx;
opacity:0.9;
z-index: 999999;
}
.swiper-item {
width: 100vw;

55
xqk/chapter2/index.vue

@ -4,23 +4,26 @@
<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>
<image @click="gotoPath('/xqk/chapter5/index')" :src="showImg('/uploads/20250903/dcfd8b8a708f4f2d43edf35a906f75ba.png')" mode="widthFix" class="img1-text"></image>
</template>
</view>
</swiper-item>
</swiper>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {
@ -42,7 +45,7 @@
},
onLoad(option) {
this.currentIndex = option.currentIndex || 0
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1;
// if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1;
},
onUnload() {
//
@ -50,55 +53,9 @@
},
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) {

154
xqk/chapter3/index.vue

@ -1,86 +1,46 @@
<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>
<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 === 2">
<image :src="showImg('/uploads/20250904/3728c0eb6f25e433d539b0c0781039d0.png')"
mode="widthFix" class="img2-text"></image>
<image @click="gotoPath('/xqk/chapter4/index')" v-if="currentIndex == swiperImages.length-1"
:src="showImg('/uploads/20250905/692abbf32b38257ffb2153651f468a63.png')" class="imgJump"
mode=""></image>
</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>
</view>
</swiper-item>
</swiper>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
</template>
<script>
//
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
import SwipeToNext from '@/components/SwipeToNext.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
SwipeToNext,
AudioControl
},
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',
this.showImg('/uploads/20250904/c07ff8c707b5368df0faaf5b425e32c3.png'),
this.showImg('/uploads/20250904/4c2b9c8b647b736f7cb20a42aa5f8fb3.png'),
this.showImg('/uploads/20250904/c5c2622499089799e6eaf6a704508a07.gif'),
],
inputValue: '',
swipeDirection: ''
@ -104,23 +64,7 @@
},
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;
@ -137,7 +81,15 @@
width: 100vw;
height: 100vh;
}
.imgJump{
position: absolute;
bottom:100rpx;
left:247rpx;
width: 273rpx;
height: 85rpx;
opacity: 1;
z-index: 999999;
}
.swiper-item {
width: 100vw;
height: 100vh;
@ -145,31 +97,37 @@
background-color: #000;
background-repeat: no-repeat;
position: relative;
.img6-text {
width: 442.41rpx;
display: flex;
justify-content: center;
align-items: center;
.img2-text {
display: flex;
justify-content: center;
align-items: center;
width: 407rpx;
position: absolute;
top: 170rpx;
top: 500rpx;
left: 0;
right: 0;
margin: 0 auto;
}
.img6-btn {
position: absolute;
width: 520.31rpx;
bottom: 210rpx;
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%;
@ -180,20 +138,20 @@
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;
@ -241,4 +199,4 @@
width: 100%;
}
}
</style>
</style>

164
xqk/chapter4/index.vue

@ -1,80 +1,93 @@
<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>
<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 === swiperImages.length - 1">
<!-- 所有maskImage每个占一行 -->
<view class="mask-container">
<view class="mask-item" v-for="(maskItem, maskIndex) in maskImage"
:key="'mask-' + maskIndex" :class="maskIndex == 2?'topImg':''">
<image :src="maskItem.main" mode="aspectFill" class="main-image"></image>
<image v-if="!hiddenMasks[maskIndex]" :src="maskItem.mask" mode="aspectFill"
class="mask-image" @click="hideMask(maskIndex)"
:style="maskIndex == 1?'opacity: 0.8':''"></image>
</view>
</view>
<image @click="gotoPath('/xqk/chapter6/index')" v-if="currentIndex == swiperImages.length-1"
:src="showImg('/uploads/20250905/692abbf32b38257ffb2153651f468a63.png')" class="imgJump"
mode=""></image>
</template>
</view>
</swiper-item>
</swiper>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
SwipeToNext,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 4,
hiddenMasks: [false, false, false, false], //
maskImage: [{
main: this.showImg('/uploads/20250904/35745646266436c060f56067d96a3e99.png'),
mask: this.showImg('/uploads/20250904/d6d95bbbc38b1626021d127ccf425091.png'),
},
{
main: this.showImg('/uploads/20250904/5cb81451599707b7ff65178b0cc74e7b.png'),
mask: this.showImg('/uploads/20250904/32caadcddd64aa911270f7c67960df10.png'),
},
{
main: this.showImg('/uploads/20250904/c768952bdb4ae59506685fad6416adf0.png'),
mask: this.showImg('/uploads/20250904/5e37bb76dffb86caa495361503e68cf0.png'),
},
{
main: this.showImg('/uploads/20250904/09792d96ceb8503d8ea01fcc1c083399.png'),
mask: this.showImg('/uploads/20250904/6f3062a2ed9f35aec241b2b0b3d15a58.png'),
},
],
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',
this.showImg('/uploads/20250904/49ed0f441175de9fba8ed644962c10a2.png'),
this.showImg('/uploads/20250904/c9e6fa6112b9803202b6d50591e7b986.png'),
this.showImg('/uploads/20250904/48c55f56d649a6d1c92d8ed16fb4df08.png'),
this.showImg('/uploads/20250904/645920955d2dbda9f86422e39f167da1.png'),
this.showImg('/uploads/20250904/ec18a2d2b6a0836d2b284bf116728441.png'),
],
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
}
this.currentIndex = e.detail.current;
},
openPopup(i) {
this.popupIndex = i
this.$refs.chapterPopup.open();
},
hideMask(index) {
//
this.$set(this.hiddenMasks, index, true);
},
getImageUrl(path) {
if (typeof path === 'object') {
path = path.url;
@ -90,7 +103,15 @@
width: 100vw;
height: 100vh;
}
.imgJump{
position: absolute;
bottom:53rpx;
right:0;
width: 273rpx;
height: 85rpx;
opacity: 1;
z-index: 999999;
}
.swiper-item {
width: 100vw;
height: 100vh;
@ -98,31 +119,80 @@
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;
}
}
// maskImage
.mask-container {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
padding-top: 20rpx;
.topImg {
margin-top: 211rpx;
}
.mask-item {
position: relative;
width: 693rpx;
height: 300rpx;
border-radius: 10rpx;
overflow: hidden;
.main-image {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.mask-image {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
cursor: pointer;
transition: opacity 0.3s ease;
&:active {
opacity: 0.8;
}
}
}
}
}
.swiper-img {

74
xqk/chapter5/index.vue

@ -1,25 +1,26 @@
<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>
<view style="width: 100vw;position: relative;">
<!-- <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>
<image @click="gotoPath('/xqk/chapter3/index')" v-if="currentIndex == swiperImages.length-1"
:src="showImg('/uploads/20250905/692abbf32b38257ffb2153651f468a63.png')" class="imgJump"
mode=""></image>
<!-- </SwipeToNext> -->
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
import SwipeToNext from '@/components/SwipeToNext.vue';
@ -27,12 +28,13 @@
components: {
MusicControl,
NavMenu,
SwipeToNext
SwipeToNext,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 5,
navIndex: 2,
swiperImages: [
this.showImg('/uploads/20250903/dd5b260002da55d4c3d56b338451bc11.gif'),
],
@ -44,7 +46,7 @@
this.currentIndex = option.currentIndex || 0
//
if (this.currentIndex == this.swiperImages.length - 1) {
this.navIndex = 1;
this.navIndex = 2;
this.isLastSlide = true;
}
//
@ -52,10 +54,6 @@
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
@ -63,28 +61,14 @@
},
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>
@ -94,7 +78,15 @@
width: 100vw;
height: 100vh;
}
.imgJump{
position: absolute;
bottom:100rpx;
left:250rpx;
width: 273rpx;
height: 85rpx;
opacity: 0.8;
z-index: 999999;
}
.swiper-item {
width: 100vw;
height: 100vh;
@ -120,7 +112,7 @@
margin: 0 auto;
bottom: 70rpx;
}
.img3-btn {
width: 558rpx;
line-height: 72rpx;
@ -135,17 +127,17 @@
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;

138
xqk/chapter6/index.vue

@ -3,109 +3,65 @@
<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>
<template v-if="index == 2">
<view class="item-box">
<view class="box-two">
<image @click="gotoPath('/subPackages/techan/detail?id=40')" :src="showImg('/uploads/20250904/0bfd974c3fc411811463e066c96f3d35.png')"
mode="widthFix" class="img2-text"></image>
<image @click="gotoPath('/subPackages/techan/detail?id=40')" style="margin-left: 39rpx;" :src="showImg('/uploads/20250904/262814b745a934c3d5bdd114109ff564.png')"
mode="widthFix" class="img2-text"></image>
</view>
<view class="box-two" style="margin-top: 46rpx;">
<image @click="gotoPath('/subPackages/techan/detail?id=40')" :src="showImg('/uploads/20250904/425a5716993c38bf8e4a6a66882d8685.png')"
mode="widthFix" class="img2-text"></image>
<image @click="gotoPath('/subPackages/techan/detail?id=40')" style="margin-left: 39rpx;" :src="showImg('/uploads/20250904/728878284ea30f148bc87376d85684d9.png')"
mode="widthFix" class="img2-text"></image>
</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>
<template v-if="index== 3">
<image style="width: 100vw;height: 100vh;padding-top: env(safe-area-inset-top);padding-bottom: env(safe-area-inset-bottom);" :src="showImg('/uploads/20250904/f5926e170dc0dc886a33422a334dae56.png')" :show-menu-by-longpress="true" mode="widthFix"></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" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 5,
swiperImages: [
'https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter5/img7.png',
this.showImg('/uploads/20250904/07e27812a048efbef08593649c9e6504.png'),
this.showImg('/uploads/20250904/5c32fa0bc571085d2c4f2b03fd6291a5.png'),
this.showImg('/uploads/20250904/c282ee859a5da1e1eb0cc1ded809dd09.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()
this.currentIndex = e.detail.current;
},
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>
@ -123,15 +79,32 @@
background-color: #000;
background-repeat: no-repeat;
position: relative;
display: flex;
align-items: center;
justify-content: center;
.item-box{
.box-two{
display: flex;
align-items: center;
justify-content: center;
}
position: absolute;
top:289rpx;
left:99rpx;
.img2-text{
width: 257rpx;
}
}
.btn-img {
position: absolute;
width: 149.8rpx;
bottom: 290rpx;
left: 84rpx;
}
.img7-1, .img7-2 {
.img7-1,
.img7-2 {
width: 437.67rpx;
position: absolute;
left: 0;
@ -139,7 +112,7 @@
margin: 0 auto;
top: 365rpx;
}
.bgm-box {
width: 437.67rpx;
position: absolute;
@ -148,7 +121,7 @@
bottom: 650rpx;
margin: 0 auto;
flex-wrap: wrap;
view {
width: 207.89rpx;
line-height: 42.77rpx;
@ -158,16 +131,17 @@
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;
@ -182,14 +156,14 @@
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;
@ -201,14 +175,14 @@
box-sizing: border-box;
margin-bottom: 15rpx;
}
.word-count {
text-align: right;
font-size: 24rpx;
color: #999;
margin-bottom: 35rpx;
}
.confirm-btn {
width: 100%;
}

143
xqk/chapter7/index.vue

@ -1,143 +0,0 @@
<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>

75
xqk/chapter8/index.vue

@ -1,75 +0,0 @@
<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>

6
xqk/components/NavMenu.vue

@ -60,7 +60,7 @@
path: "/xqk/chapter3/index"
},
{
text: '04 风味人间',
text: '04 蟹在人间',
targetIndex: 4,
path: "/xqk/chapter4/index"
},
@ -72,7 +72,7 @@
{
text: '有感商品',
targetIndex: 6,
path: "/subPackages/techan/detail?id=39"
path: "/subPackages/techan/detail?id=40"
},
{
text: '购物车',
@ -108,7 +108,7 @@
this.$emit('menu-hide');
},
onJumpToPage(item) {
console.log(this.navIndex)
console.log(this.navIndex,item.targetIndex,item.path)
if(item.path && item.targetIndex != this.navIndex) this.gotoPath(item.path)
this.onCloseMenu();
},

52
xqk/home/home.vue

@ -1,33 +1,33 @@
<template>
<view style="width: 100vw;">
<view style="width: 100vw;position: relative;">
<!-- <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"
>
<!-- <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})` }">
<template v-if="index == 0" >
<image :src="showImg('/uploads/20250905/75a01f24cf88d71499d8ae1bf256e913.png')" style="width: 678rpx;" mode="widthFix" ></image>
</template>
</view>
</swiper-item>
</swiper>
</SwipeToNext>
<!-- </SwipeToNext> -->
<image @click="gotoPath('/xqk/chapter1/index')" :src="showImg('/uploads/20250905/692abbf32b38257ffb2153651f468a63.png')" class="imgJump" mode=""></image>
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import SinglePlayGif from '../components/SinglePlayGif.vue';
import NavMenu from '../components/NavMenu.vue';
@ -37,7 +37,8 @@
MusicControl,
SinglePlayGif,
NavMenu,
SwipeToNext
SwipeToNext,
AudioControl
},
data() {
return {
@ -46,8 +47,9 @@
duration: 5000,
currentIndex: 0,
navIndex: 0,
audioUrl: 'https://des.js-dyyj.com/data/2025/09/04/fbc13519-cfe5-4088-89b2-59f138bc23cb.MP3',
swiperImages: [
this.showImg('/uploads/20250903/b4f601dee7b4ad1b42c878fd54693c92.png'),
this.showImg('/uploads/20250904/e734717a187c357b63d64d6214de0ca1.gif'),
// this.showImg('/uploads/20250903/24303e4b7218eaf3d857c846417eb490.png'),
// this.showImg('/uploads/20250903/17495ef65648c64c31920d312301e991.png'),
// this.showImg('/uploads/20250903/92d6f1c6f8f7de040f3c31c8faf98927.png'),
@ -59,7 +61,7 @@
this.currentIndex = option.currentIndex || 0
//
if (this.currentIndex == this.swiperImages.length - 1) {
this.navIndex = 1;
// this.navIndex = 1;
this.isLastSlide = true;
}
//
@ -69,7 +71,7 @@
},
onShow() {
const app = getApp();
app.updateMusicSrc('https://static.ticket.sz-trip.com/epicSoul/xrcc/bgm.mp3');
app.updateMusicSrc('https://des.js-dyyj.com/data/2025/09/04/bb2921f6-eeac-4f21-b2b7-11c1e3138976.mp3');
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
},
@ -99,15 +101,15 @@
// #ifdef MP-WEIXIN
onShareAppMessage() {
return {
title: '今夜,我们都有一艘秘密飞船|「Epic Soul」阅读体 issue05',
title: '一只蟹的生命远征|「Epic Soul」阅读体 issue06',
mpId: 'wx8954209bb3ad489e',
path: '/xrcc/home/home',
path: '/xqk/home/home',
imageUrl: this.showImg('/uploads/20250903/66ff1f3cd63ea776a0203e8e0dd92dda.jpg')
};
},
onShareTimeline() {
return {
title: '今夜,我们都有一艘秘密飞船|「Epic Soul」阅读体 issue05',
title: '一只蟹的生命远征|「Epic Soul」阅读体 issue06',
query: '',
imageUrl: this.showImg('/uploads/20250903/66ff1f3cd63ea776a0203e8e0dd92dda.jpg')
};
@ -122,7 +124,15 @@
width: 100vw;
height: 100vh;
}
.imgJump{
position: absolute;
bottom:120rpx;
left:247rpx;
width: 273rpx;
height: 85rpx;
opacity: 1;
z-index: 999999;
}
.swiper-item {
/* 新增安全区域适配 */
padding-top: env(safe-area-inset-top);
@ -139,7 +149,9 @@
background-color: #000;
background-repeat: no-repeat;
position: relative;
display: flex;
align-items: center;
justify-content: center;
.img1-text {
position: absolute;
width: 632.16rpx;

16
xrcc/chapter1/index.vue

@ -5,8 +5,8 @@
<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>
v-for="i in 3" :key="i" mode="widthFix" :class="['module'+(i+1)]" @click="openPopup(i+1)">
</image>
</template>
<template v-if="index === 3">
@ -26,19 +26,21 @@
</image>
</view>
</uni-popup>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {
@ -71,13 +73,13 @@
methods: {
handleJumpToPage(idx) {
this.navIndex = idx
if(idx == this.swiperImages.length - 1) this.navIndex = idx + 1
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1
},
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
this.currentIndex = e.detail.current;
if (this.currentIndex == this.swiperImages.length - 1) {
this.navIndex = 2;
}else {
} else {
this.navIndex = 1
}
},

5
xrcc/chapter2/index.vue

@ -55,16 +55,19 @@
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

6
xrcc/chapter3/index.vue

@ -53,7 +53,7 @@
<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>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
@ -61,12 +61,14 @@
<script>
//
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

6
xrcc/chapter4/index.vue

@ -26,18 +26,20 @@
</uni-popup>
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

6
xrcc/chapter5/index.vue

@ -29,19 +29,21 @@
</view>
</swiper-item>
</swiper>
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

6
xrcc/chapter6/index.vue

@ -41,18 +41,20 @@
</uni-popup>
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

6
xrcc/chapter7/index.vue

@ -27,18 +27,20 @@
</swiper>
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

6
xrcc/chapter8/index.vue

@ -9,18 +9,20 @@
</swiper>
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

31
xrcc/home/home.vue

@ -10,29 +10,35 @@
<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/home/img1-text.png" mode="widthFix" class="img1-text"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img1-text.png" mode="widthFix"
class="img1-text"></image>
</template>
<template v-if="index == 1">
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img2-text.png" mode="widthFix" class="img1-text" style="width: 115.85rpx;"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img2-text.png" mode="widthFix"
class="img1-text" style="width: 115.85rpx;"></image>
</template>
<template v-if="index == 2">
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img3-text.png" mode="widthFix" class="img1-text" style="width: 577.23rpx;"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img3-text.png" mode="widthFix"
class="img1-text" style="width: 577.23rpx;"></image>
</template>
<template v-if="index == 3">
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img4-text.png" mode="widthFix" class="img4-text"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/btn-img.png" mode="widthFix" class="btn-img" @click="gotoPath('/xrcc/chapter1/index')"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/img4-text.png" mode="widthFix"
class="img4-text"></image>
<image src="https://static.ticket.sz-trip.com/epicSoul/xrcc/home/btn-img.png" mode="widthFix"
class="btn-img" @click="gotoPath('/xrcc/chapter1/index')"></image>
</template>
</view>
</swiper-item>
</swiper>
<NavMenu :nav-index="navIndex" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/7d970645-f026-4ae0-8d60-a8b5c47bebfc.MP3" />
<MusicControl />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import SinglePlayGif from '../components/SinglePlayGif.vue';
import NavMenu from '../components/NavMenu.vue';
@ -40,7 +46,8 @@
components: {
MusicControl,
SinglePlayGif,
NavMenu
NavMenu,
AudioControl
},
data() {
return {
@ -70,13 +77,13 @@
methods: {
handleJumpToPage(idx) {
this.navIndex = idx
if(idx == this.swiperImages.length - 1) this.navIndex = idx + 1
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1
},
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
this.currentIndex = e.detail.current;
if (this.currentIndex == this.swiperImages.length - 1) {
this.navIndex = 1;
}else {
} else {
this.navIndex = 0
}
},
@ -125,14 +132,14 @@
right: 0;
margin: 0 auto;
}
.img4-text {
position: absolute;
width: 476.36rpx;
top: 170rpx;
left: 84rpx;
}
.btn-img {
position: absolute;
width: 149.8rpx;

5
xxdf/chapter1/cover1.vue

@ -25,16 +25,19 @@
</view>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
<script>
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
import AudioControl from '@/components/AudioControl.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

4
xxdf/chapter1/detail1.vue

@ -30,14 +30,16 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {MusicControl,
components: {MusicControl,AudioControl,
NavMenu},
data() {
return {

5
xxdf/chapter1/detail2.vue

@ -30,16 +30,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
<script>
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
import AudioControl from '@/components/AudioControl.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
xxdf/chapter1/detail3.vue

@ -27,17 +27,20 @@
</swiper-item>
</swiper>
<MusicControl />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
xxdf/chapter1/detail4.vue

@ -48,16 +48,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
xxdf/chapter1/detail5.vue

@ -28,16 +28,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
xxdf/chapter2/cover.vue

@ -98,16 +98,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
xxdf/chapter3/cover.vue

@ -61,16 +61,19 @@
</swiper>
<MusicControl />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

6
xxdf/chapter4/cover.vue

@ -94,6 +94,7 @@
<MusicControl />
<ShareGuide @close="onShareGuideClose" />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
</view>
</template>
@ -107,12 +108,13 @@ import {
} from '@/static/js/common';
import ShareGuide from '@/components/ShareGuide.vue';
import MusicControl from '@/components/MusicControl.vue';
import AudioControl from '@/components/AudioControl.vue';
export default {
components: {
ShareGuide,
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

5
xxdf/home/home.vue

@ -114,12 +114,14 @@
</swiper-item>
</swiper>
<MusicControl />
<AudioControl audioSrc="https://des.js-dyyj.com/data/2025/09/05/fac61c02-6cfd-41bf-9270-0ecd69881da2.MP3" />
<ShareGuide v-model="showShareGuide" @close="onShareGuideClose" />
<NavMenu :nav-index="0" @jump-to-page="handleJumpToPage" />
</view>
</template>
<script>
import AudioControl from '@/components/AudioControl.vue';
import ShareGuide from '@/components/ShareGuide.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
@ -127,7 +129,8 @@ export default {
components: {
ShareGuide,
MusicControl,
NavMenu
NavMenu,
AudioControl
},
data() {
return {

Loading…
Cancel
Save