Browse Source

Merge branch 'master' into dev_des

# Conflicts:
#	App.vue
#	bmzm/home/home.vue
#	pages.json
#	pages/index/readingBody.vue
#	pig/home/home.vue
#	xrcc/home/home.vue
#	xxdf/home/home.vue
dev_des
1054425342@qq.com 1 month ago
parent
commit
e062173183
  1. 9
      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. 70
      bmzm/home/home.vue
  10. 289
      components/AudioControl.vue
  11. 155
      components/AudioControl使用文档.md
  12. 65
      components/MusicControl.vue
  13. 187
      components/SwipeToNext.vue
  14. 293
      components/SwipeToNext使用文档.md
  15. 225
      components/跨页面音频控制解决方案.md
  16. 144
      components/音频背景音乐交互说明.md
  17. 1
      main.js
  18. 127
      pages.json
  19. 39
      pages/index/readingBody.vue
  20. 5
      pig/chapter1/chapter1.vue
  21. 5
      pig/chapter2/chapter2.vue
  22. 5
      pig/chapter3/chapter3.vue
  23. 5
      pig/chapter4/chapter4.vue
  24. 1512
      pig/home/home.vue
  25. 25
      project.config.json
  26. 14
      project.private.config.json
  27. 96
      static/js/CommonFunction.js
  28. 5
      taozi/chapter1/chapter1.vue
  29. 5
      taozi/chapter2/chapter2.vue
  30. 5
      taozi/chapter3/chapter3.vue
  31. 5
      taozi/chapter4/chapter4.vue
  32. 5
      taozi/home/home.vue
  33. 92
      utils/globalAudioManager.js
  34. 348
      xqk/chapter1/index.vue
  35. 173
      xqk/chapter2/index.vue
  36. 200
      xqk/chapter3/index.vue
  37. 201
      xqk/chapter4/index.vue
  38. 180
      xqk/chapter5/index.vue
  39. 190
      xqk/chapter6/index.vue
  40. 248
      xqk/components/NavMenu.vue
  41. 95
      xqk/components/SinglePlayGif.vue
  42. 183
      xqk/home/home.vue
  43. 16
      xrcc/chapter1/index.vue
  44. 5
      xrcc/chapter2/index.vue
  45. 6
      xrcc/chapter3/index.vue
  46. 6
      xrcc/chapter4/index.vue
  47. 6
      xrcc/chapter5/index.vue
  48. 6
      xrcc/chapter6/index.vue
  49. 6
      xrcc/chapter7/index.vue
  50. 6
      xrcc/chapter8/index.vue
  51. 34
      xrcc/home/home.vue
  52. 5
      xxdf/chapter1/cover1.vue
  53. 4
      xxdf/chapter1/detail1.vue
  54. 5
      xxdf/chapter1/detail2.vue
  55. 5
      xxdf/chapter1/detail3.vue
  56. 5
      xxdf/chapter1/detail4.vue
  57. 5
      xxdf/chapter1/detail5.vue
  58. 5
      xxdf/chapter2/cover.vue
  59. 5
      xxdf/chapter3/cover.vue
  60. 6
      xxdf/chapter4/cover.vue
  61. 41
      xxdf/home/home.vue

9
App.vue

@ -7,11 +7,12 @@ export default {
bgMusic: null,
isMusicPlaying: false,
musicSrc: "https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3",
initMusicSrc:"https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3",
initMusicSrc: "https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3",
// 使
userSessionId: null,
networkStartTime: null, //
networkEndTime: null, //
currentAudio: null, //
},
onLaunch: function () {
// 使
@ -44,8 +45,8 @@ export default {
getUserInfo() {
if (!this.getUserId()) return;
this.Post({}, "/framework/user/getInfo", "DES").then((res) => {
let token = JSON.parse(uni.getStorageSync("userInfo")).token
res.data.token = token
let token = JSON.parse(uni.getStorageSync("userInfo")).token;
res.data.token = token;
uni.setStorageSync("userInfo", JSON.stringify(res.data));
});
},
@ -236,7 +237,7 @@ export default {
console.log("bgMusic", this.globalData.bgMusic);
//
if (this.globalData.bgMusic) {
console.log('销毁bgMusic')
console.log("销毁bgMusic");
this.globalData.bgMusic.stop();
this.globalData.bgMusic.destroy();
this.globalData.bgMusic = null;

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 {

70
bmzm/home/home.vue

@ -1,6 +1,5 @@
<template>
<view>
<BackButton />
<swiper
class="swiper"
:current="currentIndex"
@ -30,37 +29,54 @@
</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 MusicControl from "@/components/MusicControl.vue";
import NavMenu from "../components/NavMenu.vue";
import BackButton from "@/components/BackButton.vue";
import AudioControl from '@/components/AudioControl.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
MusicControl,
NavMenu,
BackButton,
},
data() {
return {
currentIndex: 0,
swiperImages: [
"https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home1s.gif",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home2.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home3.png",
"https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home4.png",
],
animationConfig: {
delay: 0.5,
duration: 3,
keyframes: {
start: 1,
first: 0.8,
second: 1.2,
third: 0.9,
end: 1.1,
components: {
MusicControl,
NavMenu,
AudioControl
},
data() {
return {
currentIndex: 0,
swiperImages: [
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home1s.gif',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home2.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home3.png',
'https://static.ticket.sz-trip.com/epicSoul/bmzm/home/home4.png'
],
animationConfig: {
delay: 0.5,
duration: 3,
keyframes: {
start: 1,
first: 0.8,
second: 1.2,
third: 0.9,
end: 1.1
}
}
};
},
onShow() {
uni.removeStorageSync('answerObj');
const app = getApp();
app.updateMusicSrc('https://static.ticket.sz-trip.com/epicSoul/bmzm.mp3');
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
},
methods: {
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
},
},
};

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;
}
}
}

187
components/SwipeToNext.vue

@ -0,0 +1,187 @@
<template>
<view
class="swipe-to-next"
@touchstart="handleTouchStart"
@touchend="handleTouchEnd"
>
<slot></slot>
<!-- 提示文字 -->
<view v-if="showTip && shouldShowTip" class="bottom-tip">
<text>{{ tipText }}</text>
</view>
</view>
</template>
<script>
export default {
name: 'SwipeToNext',
props: {
//
isLastSlide: {
type: Boolean,
default: false
},
//
targetPath: {
type: String,
required: true
},
//
showTip: {
type: Boolean,
default: true
},
//
tipText: {
type: String,
default: '上滑进入下一章节'
},
// px
swipeThreshold: {
type: Number,
default: 100
},
// ms
delayTime: {
type: Number,
default: 500
},
//
enableDelay: {
type: Boolean,
default: true
},
// isLastSlide
alwaysEnable: {
type: Boolean,
default: false
}
},
data() {
return {
touchStartY: 0, // Y
touchEndY: 0, // Y
canJump: false, //
delayTimer: null //
}
},
computed: {
//
shouldShowTip() {
return this.alwaysEnable || this.isLastSlide;
},
//
shouldEnableTouch() {
return this.alwaysEnable || this.isLastSlide;
}
},
watch: {
isLastSlide(newVal) {
this.handleSlideChange(newVal);
},
alwaysEnable: {
handler(newVal) {
if (newVal) {
//
this.handleSlideChange(true);
}
},
immediate: true
}
},
beforeDestroy() {
//
if (this.delayTimer) {
clearTimeout(this.delayTimer);
}
},
methods: {
//
handleSlideChange(isActive) {
console.log('ppsls');
if (isActive || this.alwaysEnable) {
//
this.canJump = false;
if (this.enableDelay && !this.alwaysEnable) {
//
this.delayTimer = setTimeout(() => {
if (this.shouldEnableTouch) {
this.canJump = true;
}
}, this.delayTime);
} else {
//
this.canJump = true;
}
} else {
//
this.canJump = false;
if (this.delayTimer) {
clearTimeout(this.delayTimer);
this.delayTimer = null;
}
}
},
//
handleTouchStart(e) {
console.log('。。。。。。。。。。///////////');
//
if (this.shouldEnableTouch && (this.canJump || !this.enableDelay || this.alwaysEnable)) {
this.touchStartY = e.touches[0].clientY;
}
},
//
handleTouchEnd(e) {
//
if (!this.shouldEnableTouch || !this.touchStartY) {
return;
}
//
if (this.enableDelay && !this.canJump && !this.alwaysEnable) {
return;
}
this.touchEndY = e.changedTouches[0].clientY;
const deltaY = this.touchStartY - this.touchEndY;
//
if (deltaY > this.swipeThreshold) {
console.log('向上滑动触发跳转,目标路径:', this.targetPath);
this.$emit('swipe-to-next', this.targetPath);
uni.navigateTo({
url:this.targetPath
});
}
//
this.touchStartY = 0;
this.touchEndY = 0;
},
}
}
</script>
<style lang="scss" scoped>
.swipe-to-next {
width: 100%;
height: 100%;
position: relative;
}
.bottom-tip {
position: absolute;
bottom: 50rpx;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.6);
padding: 10rpx 20rpx;
border-radius: 50rpx;
z-index: 999;
text {
color: #fff;
font-size: 24rpx;
}
}
</style>

293
components/SwipeToNext使用文档.md

@ -0,0 +1,293 @@
# SwipeToNext 组件使用文档
## 组件介绍
`SwipeToNext` 是一个通用的触底跳转组件,封装了手势检测、延迟防抖、提示文字等功能,可以在任何需要滑动跳转的页面中使用。
## 组件特性
- ✅ 手势滑动检测
- ✅ 防误触发机制(延迟允许跳转)
- ✅ 可配置的滑动阈值
- ✅ 自定义提示文字
- ✅ 支持事件监听
- ✅ 完全可配置的参数
## 使用方法
### 1. 引入组件
```vue
<script>
import SwipeToNext from '@/components/SwipeToNext.vue';
export default {
components: {
SwipeToNext
}
}
</script>
```
### 2. 基础使用
```vue
<template>
<SwipeToNext
:is-last-slide="isLastSlide"
:target-path="'/next/page'"
@swipe-to-next="handleSwipeToNext"
>
<!-- 你的页面内容 -->
<view class="content">
<!-- 轮播图或其他内容 -->
</view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
isLastSlide: false
}
},
methods: {
// 处理swiper切换或其他逻辑
handlePageChange() {
// 根据你的逻辑设置 isLastSlide
this.isLastSlide = true; // 当到达最后一页时
},
handleSwipeToNext(targetPath) {
console.log('即将跳转到:', targetPath);
// 可以在这里添加额外的逻辑,如数据统计
}
}
}
</script>
```
### 3. 完整配置使用
```vue
<template>
<SwipeToNext
:is-last-slide="isLastSlide"
:target-path="'/next/page'"
:show-tip="true"
tip-text="向上滑动查看更多内容"
:swipe-threshold="100"
:delay-time="800"
:enable-delay="true"
@swipe-to-next="handleSwipeToNext"
>
<!-- 你的页面内容 -->
<view class="content">
<!-- 内容区域 -->
</view>
</SwipeToNext>
</template>
```
## Props 参数
| 参数名 | 类型 | 默认值 | 必填 | 说明 |
|--------|------|--------|------|------|
| `isLastSlide` | Boolean | `false` | ✅ | 是否在最后一页/最后一个状态 |
| `targetPath` | String | - | ✅ | 跳转的目标路径 |
| `showTip` | Boolean | `true` | ❌ | 是否显示提示文字 |
| `tipText` | String | `'继续向上滑动进入下一章节'` | ❌ | 提示文字内容 |
| `swipeThreshold` | Number | `80` | ❌ | 滑动阈值(像素) |
| `delayTime` | Number | `500` | ❌ | 延迟允许跳转的时间(毫秒) |
| `enableDelay` | Boolean | `true` | ❌ | 是否启用延迟机制 |
| `alwaysEnable` | Boolean | `false` | ❌ | 是否总是启用跳转(忽略isLastSlide状态) |
## Events 事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| `swipe-to-next` | `targetPath` | 触发跳转时的回调事件 |
## 使用场景示例
### 场景1:图片轮播页面
```vue
<template>
<SwipeToNext
:is-last-slide="currentIndex === images.length - 1"
:target-path="'/gallery/next'"
>
<swiper @change="handleSwiperChange">
<swiper-item v-for="(img, index) in images" :key="index">
<image :src="img" mode="aspectFit" />
</swiper-item>
</swiper>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
currentIndex: 0,
images: ['img1.jpg', 'img2.jpg', 'img3.jpg']
}
},
methods: {
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
}
}
}
</script>
```
### 场景2:文章阅读页面
```vue
<template>
<SwipeToNext
:is-last-slide="isReadComplete"
:target-path="'/article/next'"
tip-text="继续滑动阅读下一篇文章"
:swipe-threshold="60"
>
<scroll-view @scrolltolower="handleScrollToBottom">
<view class="article-content">
<!-- 文章内容 -->
</view>
</scroll-view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
isReadComplete: false
}
},
methods: {
handleScrollToBottom() {
// 滚动到底部时认为阅读完成
this.isReadComplete = true;
}
}
}
</script>
```
### 场景4:单张图片或总是启用触底跳转
```vue
<template>
<SwipeToNext
:always-enable="true"
:target-path="'/next/chapter'"
tip-text="向上滑动查看下一内容"
:enable-delay="false"
>
<view class="single-image">
<image :src="imageUrl" mode="aspectFit" />
</view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
imageUrl: 'single-image.jpg'
}
}
}
</script>
```
```vue
<template>
<SwipeToNext
:is-last-slide="currentStep === totalSteps - 1"
:target-path="'/guide/complete'"
tip-text="向上滑动完成引导"
>
<view class="guide-step">
<view class="step-content">
步骤 {{ currentStep + 1 }} / {{ totalSteps }}
</view>
<button @click="nextStep">下一步</button>
</view>
</SwipeToNext>
</template>
<script>
export default {
data() {
return {
currentStep: 0,
totalSteps: 5
}
},
methods: {
nextStep() {
if (this.currentStep < this.totalSteps - 1) {
this.currentStep++;
}
}
}
}
</script>
```
## 特殊情况处理
### 单张图片问题
当只有一张图片时,传统的 `isLastSlide` 逻辑不适用。这时可以使用 `alwaysEnable` 参数:
```vue
<!-- 单张图片的解决方案 -->
<SwipeToNext
:always-enable="true"
:target-path="'/next/page'"
:enable-delay="false"
>
<image src="single-image.jpg" />
</SwipeToNext>
```
### 参数优先级
`alwaysEnable="true"` 时:
- 忽略 `isLastSlide` 的值
- 总是显示提示文字
- 总是允许触底跳转
- 建议设置 `enableDelay="false"` 以获得更好的响应速度
1. **确保正确设置 `isLastSlide`**:这是控制是否允许跳转的关键属性
2. **路径格式**:`targetPath` 需要是有效的 uni-app 路由路径
3. **性能考虑**:如果不需要延迟机制,可以设置 `enableDelay: false` 来提高响应速度
4. **样式覆盖**:组件内的提示文字样式可以通过全局样式覆盖
5. **事件监听**:建议监听 `swipe-to-next` 事件进行数据统计或其他操作
## 自定义样式
如果需要自定义提示文字的样式,可以在页面中添加:
```scss
// 覆盖组件样式
.swipe-to-next .bottom-tip {
bottom: 200rpx !important; // 调整位置
background: rgba(255, 255, 255, 0.9) !important; // 改变背景色
text {
color: #333 !important; // 改变文字颜色
font-size: 32rpx !important; // 改变字体大小
}
}
```
这个组件极大地简化了触底跳转功能的实现,让你可以专注于业务逻辑而不用重复编写相同的手势检测代码。

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'
import main from "@/common/index.js"
Vue.prototype.$main = main

127
pages.json

@ -22,7 +22,7 @@
"path": "pages/index/sensoryStore",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
"navigationBarTextStyle": "white"
}
},
{
@ -35,7 +35,7 @@
"path": "pages/index/iSoul",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
"navigationBarTextStyle": "white"
}
},
{
@ -68,14 +68,14 @@
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/agent/index",
"style": {
"navigationBarTitleText": "DES智能体",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
}
{
"path": "pages/agent/index",
"style": {
"navigationBarTitleText": "DES智能体",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
}
],
"subPackages": [
{
@ -249,18 +249,18 @@
"navigationBarTitleText": "确认订单"
}
},
{
"path": "orderQy/confrimWriteOff",
"style": {
"navigationBarTitleText": "核销订单"
}
},
{
"path": "orderQy/writeOffCode",
"style": {
"navigationBarTitleText": "核销码"
}
},
{
"path": "orderQy/confrimWriteOff",
"style": {
"navigationBarTitleText": "核销订单"
}
},
{
"path": "orderQy/writeOffCode",
"style": {
"navigationBarTitleText": "核销码"
}
},
{
"path": "memorialAlbum/index",
@ -302,21 +302,20 @@
"navigationBarTextStyle": "black"
}
},
{
"path": "other/introduction",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "other/evita",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
}
{
"path": "other/introduction",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "other/evita",
"style": {
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
}
]
},
{
@ -701,6 +700,60 @@
}
}
]
},
{
"root": "xqk",
"pages": [
{
"path": "home/home",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "chapter1/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "chapter2/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "chapter3/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "chapter4/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "chapter5/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "chapter6/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
}
]
}
],
"tabBar": {

39
pages/index/readingBody.vue

@ -35,7 +35,8 @@
</view>
<view class="desc-box">
<view class="">
Epic soul交响 | 阅读体以数字文化资产IP为核心以消费产品创新为导向每个生命都有史诗般的灵魂作为创作宗旨通过文字图像音视频交互式展览等多种技术工具创作完成的轻量化数字文化内容产品邀您一起收藏这颗星球所有未被传唱的英雄史诗
Epic soul交响 |
阅读体以数字文化资产IP为核心以消费产品创新为导向每个生命都有史诗般的灵魂作为创作宗旨通过文字图像音视频交互式展览等多种技术工具创作完成的轻量化数字文化内容产品邀您一起收藏这颗星球所有未被传唱的英雄史诗
</view>
<view class="">
Epic
@ -58,7 +59,11 @@
style="width: 700rpx; height: 23.5rpx; margin: 30rpx auto; display: block"
src="https://des.js-dyyj.com/data/2025/08/29/ee445087-f64d-40a1-861b-586d1a9c7e31.png"
></image>
<view class="new-book-body" @click="gotoUrlNew(readingList[0])" v-if="readingList.length">
<view
class="new-book-body"
@click="gotoUrlNew(readingList[0])"
v-if="readingList.length"
>
<view
class="newBookCard"
style=""
@ -129,14 +134,14 @@
v-for="(item, index) in readingList.slice(1)"
v-if="item.image && item.front_model && item.front_model.mini"
:key="index"
@click="gotoUrlNew(item)"
@click="gotoUrlNew(item)"
>
<view
:style="{ backgroundImage: `url(${showImg(item.image)})` }"
class="img-wrapper">
</view>
<!-- <image
<view
:style="{ backgroundImage: `url(${showImg(item.image)})` }"
class="img-wrapper"
>
</view>
<!-- <image
mode="aspectFill"
:src="showImg(item.image)"
@click="gotoUrlNew(item)"
@ -563,13 +568,13 @@ export default {
background-position: top;
background-color: #fff;
}
.img-wrapper{
width: 100%;
height: 346rpx;
margin: 0 auto;
background-size: 100% auto;
background-repeat: no-repeat;
background-position: top;
background-color: #fff;
.img-wrapper {
width: 100%;
height: 346rpx;
margin: 0 auto;
background-size: 100% auto;
background-repeat: no-repeat;
background-position: top;
background-color: #fff;
}
</style>

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 {

1512
pig/home/home.vue

File diff suppressed because it is too large

25
project.config.json

@ -0,0 +1,25 @@
{
"setting": {
"es6": true,
"postcss": true,
"minified": true,
"uglifyFileName": false,
"enhance": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useCompilerPlugins": false,
"minifyWXML": true
},
"compileType": "miniprogram",
"simulatorPluginLibVersion": {},
"packOptions": {
"ignore": [],
"include": []
},
"appid": "wx8954209bb3ad489e",
"editorSetting": {}
}

14
project.private.config.json

@ -0,0 +1,14 @@
{
"libVersion": "3.10.0",
"projectname": "EpicSoul",
"setting": {
"urlCheck": true,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"compileHotReLoad": true
}
}

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;
}

348
xqk/chapter1/index.vue

@ -0,0 +1,348 @@
<template>
<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';
export default {
components: {
MusicControl,
NavMenu,
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'),
this.showImg('/uploads/20250903/92d6f1c6f8f7de040f3c31c8faf98927.png'),
],
animationConfig: {
delay: 0.5,
duration: 3,
keyframes: {
start: 1,
first: 0.8,
second: 1.2,
third: 0.9,
end: 1.1
}
},
popupIndex: 1,
}
},
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) {
},
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
},
//
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);
}
}
}
}
</script>
<style lang="scss" scoped>
.swiper {
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;
height: 100vh;
background-size: 100% 100%;
background-color: #000;
background-repeat: no-repeat;
position: relative;
.module1 {
position: absolute;
width: 52.1rpx;
top: 1020rpx;
left: 235rpx;
animation: breath1 3s ease-in-out infinite;
}
.module2 {
position: absolute;
width: 52.1rpx;
top: 760rpx;
left: 317rpx;
animation: breath2 4s ease-in-out infinite;
}
.module3 {
position: absolute;
width: 52.1rpx;
top: 700rpx;
left: 498rpx;
animation: breath3 5s ease-in-out infinite;
}
// -
@keyframes breath1 {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
}
@keyframes breath2 {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
}
@keyframes breath3 {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
}
.img4-text {
width: 428.43rpx;
position: absolute;
top: 170rpx;
left: 100rpx;
}
.btn-img {
position: absolute;
width: 149.8rpx;
bottom: 290rpx;
left: 100rpx;
}
}
.swiper-img {
width: 100vw;
height: 100vh;
}
.img2-1 {
width: 267.37rpx;
position: fixed;
top: 395rpx;
left: 70rpx;
}
.img2-2 {
width: 332.24rpx;
position: fixed;
top: 210rpx;
left: 360rpx;
}
.img2-3 {
width: 600.59rpx;
position: fixed;
bottom: 215rpx;
right: 40rpx;
}
</style>

173
xqk/chapter2/index.vue

@ -0,0 +1,173 @@
<template>
<view style="width: 100vw;">
<swiper class="swiper" :current="currentIndex" :vertical="true" @change="handleSwiperChange">
<swiper-item v-for="(image, index) in swiperImages" :key="index">
<view class="swiper-item" :style="{ backgroundImage: `url(${image})` }">
<template v-if="index == 1" >
<image @click="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,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 2,
swiperImages: [
this.showImg('/uploads/20250903/3bd4fe43f2a6a8806799f06a548f9477.png'),
this.showImg('/uploads/20250903/8fe8d66210edd96a9f322a661b4d9ba4.png'),
],
//
showImg7_1: false,
showImg7_2: false,
showImg7_3: false,
//
timers: [],
popupIndex: 1
}
},
onLoad(option) {
this.currentIndex = option.currentIndex || 0
// if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1;
},
onUnload() {
//
this.timers.forEach(timer => clearTimeout(timer))
},
methods: {
handleJumpToPage(idx) {
},
handleSwiperChange(e) {
this.currentIndex = e.detail.current;
},
//
openPopup(i) {
this.popupIndex = i
this.$refs.chapterPopup.open();
},
getImageUrl(path) {
if (typeof path === 'object') {
path = path.url;
}
return `https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter2/${path}`;
}
}
}
</script>
<style lang="scss" scoped>
.swiper {
width: 100vw;
height: 100vh;
}
.swiper-item {
width: 100vw;
height: 100vh;
background-size: 100% 100%;
background-color: #000;
background-repeat: no-repeat;
position: relative;
.img1-text{
width: 484rpx;
position: absolute;
bottom: 100rpx;
left: 250rpx;
}
.img10-text {
width: 484rpx;
position: absolute;
top: 170rpx;
left: 100rpx;
}
.btn-img {
position: absolute;
width: 149.8rpx;
bottom: 290rpx;
left: 100rpx;
}
.module-img {
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
width: 564.25rpx;
}
.module1 {
top: 630rpx;
}
.module2 {
top: 780rpx;
}
.module3 {
top: 930rpx;
}
.module4 {
top: 1080rpx;
}
}
.swiper-img {
width: 100vw;
height: 100vh;
}
/* 渐入动画样式 */
.fade-in-image {
/* 初始状态:透明 */
opacity: 0;
/* 添加过渡动画:1秒内透明度从0到1 */
animation: fadeIn 1s ease-out forwards;
/* 根据需要调整图片的定位 */
position: absolute;
}
/* 渐入动画关键帧 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx); /* 可选:添加轻微上移动画 */
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in-image:nth-child(1) {
width: 22.97rpx;
top: 825rpx;
right: 191rpx;
}
.fade-in-image:nth-child(2) {
width: 34.95rpx;
top: 790rpx;
right: 170rpx;
}
.fade-in-image:nth-child(3) {
width: 144.81rpx;
top: 680rpx;
right: 71rpx;
}
</style>

200
xqk/chapter3/index.vue

@ -0,0 +1,200 @@
<template>
<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})` }">
<!-- 第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>
</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,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 3,
swiperImages: [
this.showImg('/uploads/20250904/c07ff8c707b5368df0faaf5b425e32c3.png'),
this.showImg('/uploads/20250904/4c2b9c8b647b736f7cb20a42aa5f8fb3.png'),
this.showImg('/uploads/20250904/c5c2622499089799e6eaf6a704508a07.gif'),
],
inputValue: '',
swipeDirection: ''
}
},
onLoad(option) {
this.currentIndex = option.currentIndex || 0
if (this.currentIndex == this.swiperImages.length - 1) this.navIndex = 1;
},
methods: {
handleJumpToPage(idx) {
this.navIndex = idx
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1
},
handleTouchStart(e) {
this.startY = e.touches[0].clientY;
},
handleTouchMove(e) {
const moveY = e.touches[0].clientY;
this.swipeDirection = moveY < this.startY ? 'down' : 'up';
},
handleSwiperChange(e) {
const newIndex = e.detail.current;
this.currentIndex = newIndex;
},
submit() {
if (!this.inputValue.trim()) return;
this.$refs.customPopup.close()
this.currentIndex = 6;
}
}
}
</script>
<style lang="scss" scoped>
/* 原有样式保持不变 */
.swiper {
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;
background-size: 100% 100%;
background-color: #000;
background-repeat: no-repeat;
position: relative;
display: flex;
justify-content: center;
align-items: center;
.img2-text {
display: flex;
justify-content: center;
align-items: center;
width: 407rpx;
position: absolute;
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;
// }
/* 其他样式保持不变 */
.img7-box {
position: relative;
top: 170rpx;
margin: 0 auto;
}
.img7-textBg {
background-image: url('https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter3/img7-textBg.png');
background-size: 100% 100%;
padding: 30rpx 50rpx;
font-size: 50rpx;
text-align: center;
color: #fff;
width: 520.31rpx;
margin: 0 auto;
}
.img7-text {
display: block;
margin: 60rpx auto 0;
width: 447.4rpx;
}
.img8-text {
width: 379.49rpx;
position: absolute;
top: 170rpx;
left: 100rpx;
}
.btn-img {
position: absolute;
width: 149.8rpx;
bottom: 290rpx;
left: 100rpx;
}
}
.swipe-blocker {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9;
}
.popup-content {
width: 85vw;
background-color: #fff;
border-radius: 16rpx;
padding: 40rpx 30rpx;
box-sizing: border-box;
.input-area {
width: 100%;
min-height: 180rpx;
padding: 20rpx;
border: 2rpx solid #eee;
border-radius: 8rpx;
font-size: 28rpx;
resize: none;
box-sizing: border-box;
margin-bottom: 15rpx;
}
.word-count {
text-align: right;
font-size: 24rpx;
color: #999;
margin-bottom: 35rpx;
}
.confirm-btn {
width: 100%;
}
}
</style>

201
xqk/chapter4/index.vue

@ -0,0 +1,201 @@
<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 === 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,
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: [
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
},
methods: {
handleJumpToPage(idx) {
},
handleSwiperChange(e) {
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;
}
return `https://static.ticket.sz-trip.com/epicSoul/xrcc/chapter4/${path}`;
}
},
}
</script>
<style lang="scss" scoped>
.swiper {
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;
background-size: 100% 100%;
background-color: #000;
background-repeat: no-repeat;
position: relative;
.img5-text {
width: 576.23rpx;
position: absolute;
top: 170rpx;
left: 100rpx;
}
.btn-img {
position: absolute;
width: 149.8rpx;
bottom: 290rpx;
left: 100rpx;
}
.module-box {
position: absolute;
top: 460rpx;
text-align: center;
image {
width: 650rpx;
margin-bottom: 80rpx;
}
}
// 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 {
width: 100vw;
height: 100vh;
}
</style>

180
xqk/chapter5/index.vue

@ -0,0 +1,180 @@
<template>
<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';
export default {
components: {
MusicControl,
NavMenu,
// SwipeToNext,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 2,
swiperImages: [
this.showImg('/uploads/20250903/dd5b260002da55d4c3d56b338451bc11.gif'),
],
animateShow: false,
isLastSlide: false //
}
},
onLoad(option) {
this.currentIndex = option.currentIndex || 0
//
if (this.currentIndex == this.swiperImages.length - 1) {
this.navIndex = 2;
this.isLastSlide = true;
}
//
if (this.swiperImages.length === 1) {
this.isLastSlide = true;
}
},
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.isLastSlide = true;
} else {
//
this.isLastSlide = false;
}
},
},
}
</script>
<style lang="scss" scoped>
.swiper {
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;
background-size: 100% 100%;
background-color: #000;
background-repeat: no-repeat;
position: relative;
.img2s {
position: absolute;
top: 380rpx;
right: 25rpx;
width: 358rpx;
transform: translateX(100%);
opacity: 0;
}
.img2-btn {
width: 558rpx;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
bottom: 70rpx;
}
.img3-btn {
width: 558rpx;
line-height: 72rpx;
text-align: center;
border-radius: 20rpx;
border: 2rpx solid;
font-size: 30rpx;
color: #fff;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
bottom: 180rpx;
}
.flex-column {
position: absolute;
bottom: 280rpx;
width: 100%;
align-items: center;
.img5-text {
width: 100%;
}
.img5-btn {
width: 230rpx;
margin-top: 99rpx;
}
}
.img6-text {
width: 408.46rpx;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
bottom: 365rpx
}
}
/* 从右往左进入的动画 */
.animate-enter-from-right {
animation: enterFromRight 2s ease-out forwards;
}
@keyframes enterFromRight {
0% {
/* 起始位置:右侧外部 */
transform: translateX(100%);
opacity: 0;
}
100% {
/* 结束位置:正常位置 */
transform: translateX(0);
opacity: 1;
}
}
.swiper-img {
width: 100vw;
height: 100vh;
}
</style>

190
xqk/chapter6/index.vue

@ -0,0 +1,190 @@
<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 == 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>
</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>
<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,
AudioControl
},
data() {
return {
currentIndex: 0,
navIndex: 5,
swiperImages: [
this.showImg('/uploads/20250904/07e27812a048efbef08593649c9e6504.png'),
this.showImg('/uploads/20250904/5c32fa0bc571085d2c4f2b03fd6291a5.png'),
this.showImg('/uploads/20250904/c282ee859a5da1e1eb0cc1ded809dd09.png'),
,
],
inputValue: '',
inputValues: '',
}
},
methods: {
handleJumpToPage(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;
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 {
width: 437.67rpx;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
top: 365rpx;
}
.bgm-box {
width: 437.67rpx;
position: absolute;
left: 0;
right: 0;
bottom: 650rpx;
margin: 0 auto;
flex-wrap: wrap;
view {
width: 207.89rpx;
line-height: 42.77rpx;
border-radius: 2rpx;
text-align: center;
color: #fff;
font-size: 20rpx;
border: 1rpx solid #fff;
}
view:nth-child(n+3) {
margin-top: 17rpx;
}
.bgm-active {
border-color: #00C48C;
color: #00C48C;
}
}
.img7-btn {
width: 439.66rpx;
position: absolute;
left: 0;
right: 0;
bottom: 244rpx;
margin: 0 auto;
}
}
.swiper-img {
width: 100vw;
height: 100vh;
}
.popup-content {
width: 85vw;
background-color: #fff;
border-radius: 16rpx;
padding: 40rpx 30rpx;
box-sizing: border-box;
.input-area {
width: 100%;
min-height: 180rpx;
padding: 20rpx;
border: 2rpx solid #747c8e;
border-radius: 8rpx;
font-size: 28rpx;
resize: none;
box-sizing: border-box;
margin-bottom: 15rpx;
}
.word-count {
text-align: right;
font-size: 24rpx;
color: #999;
margin-bottom: 35rpx;
}
.confirm-btn {
width: 100%;
}
}
</style>

248
xqk/components/NavMenu.vue

@ -0,0 +1,248 @@
<template>
<view>
<view class="overlay" v-if="showMenu" @click="onCloseMenu"></view>
<view class="fixed-nav" :class="{'hidden': showMenu}" @click="onShowMenu">
<image class="nav-icon" :class="{'rotated': iconRotated, 'bounce-back': iconBounceBack}" :src="navIconSrc"
mode="aspectFill"></image>
</view>
<view class="nav-menu" :class="{'show': showMenu}">
<view class="nav-item" :class="{'item-active': isItemActive(item)}" v-for="item in menuItems"
:key="item.targetIndex" @click="() => onJumpToPage(item)">
<view v-if="item.text.includes('#Chapter')" class="chapter-text">
<text class="chapter-title">#Chapter</text>
<text :class="{'active': isItemActive(item)}" class="chapter-number">
{{ item.text.replace('#Chapter', '') }}
</text>
</view>
<text v-else :class="{'active': isItemActive(item)}">{{ item.text }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
//
navIndex: {
type: Number,
required: true
},
//
navIconSrc: {
type: String,
default: 'https://static.ticket.sz-trip.com/epicSoul/taozi/nav-icon.png'
}
},
data() {
return {
showMenu: false,
iconRotated: false,
iconBounceBack: false,
menuItems: [{
text: 'INTRO序曲',
targetIndex: 0,
path: "/xqk/home/home"
},
{
text: '01 青壳初生',
targetIndex: 1,
path: "/xqk/chapter1/index"
},
{
text: '02 负海志 向湖生',
targetIndex: 2,
path: "/xqk/chapter2/index"
},
{
text: '03 名曰江湖',
targetIndex: 3,
path: "/xqk/chapter3/index"
},
{
text: '04 蟹在人间',
targetIndex: 4,
path: "/xqk/chapter4/index"
},
{
text: '05 共济',
targetIndex: 5,
path: "/xqk/chapter6/index"
},
{
text: '有感商品',
targetIndex: 6,
path: "/subPackages/techan/detail?id=40"
},
{
text: '购物车',
targetIndex: 7,
path: "/subPackages/user/gwc"
}
],
};
},
watch: {
navIndex(newVal) {
console.log(newVal)
if(newVal) this.navIndex = newVal
}
},
methods: {
onShowMenu() {
this.iconRotated = true;
setTimeout(() => {
this.showMenu = true;
this.$emit('menu-show');
}, 300);
},
onCloseMenu() {
this.showMenu = false;
setTimeout(() => {
this.iconBounceBack = true;
this.iconRotated = false;
setTimeout(() => {
this.iconBounceBack = false;
}, 500);
}, 300);
this.$emit('menu-hide');
},
onJumpToPage(item) {
console.log(this.navIndex,item.targetIndex,item.path)
if(item.path && item.targetIndex != this.navIndex) this.gotoPath(item.path)
this.onCloseMenu();
},
isItemActive(item) {
return this.navIndex === item.targetIndex;
}
}
};
</script>
<style lang="scss" scoped>
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
z-index: 100;
}
.fixed-nav {
width: 80rpx;
height: 80rpx;
background-color: rgb(0 0 0 / 0.7);
border-radius: 10rpx 0 0 10rpx;
position: fixed;
right: 0;
top: 0;
bottom: 0;
margin: auto 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 9;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.fixed-nav.hidden {
transform: translateX(100%);
opacity: 0;
pointer-events: none;
}
.nav-icon {
width: 35rpx;
height: 35rpx;
transition: transform 0.3s ease;
}
.nav-icon.rotated {
transform: rotate(180deg);
}
.nav-icon.bounce-back {
animation: bounceRotation 0.5s ease;
}
@keyframes bounceRotation {
0% {
transform: rotate(180deg);
}
50% {
transform: rotate(-20deg);
}
75% {
transform: rotate(10deg);
}
100% {
transform: rotate(0deg);
}
}
.nav-menu {
position: fixed;
top: 50%;
right: 0;
transform: translate(100%, -50%);
z-index: 999;
background-color: rgba(255, 255, 255, 0.95);
border-radius: 16rpx 0 0 16rpx;
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.nav-menu.show {
transform: translate(0, -50%);
}
.nav-item {
padding: 20rpx;
text-align: center;
text {
color: #333;
opacity: 0.7;
font-size: 28rpx;
}
}
.item-active {
background-color: rgba(0, 0, 0, 0.1);
}
.nav-item .active {
color: #333;
opacity: 1;
}
.chapter-text {
display: flex;
flex-direction: column;
align-items: center;
line-height: 1.3;
}
.chapter-title {
color: #fff;
opacity: 0.7;
font-size: 24rpx;
}
.chapter-number {
color: #fff;
opacity: 0.7;
font-size: 28rpx;
margin-top: 8rpx;
}
.item-active .chapter-title,
.item-active .chapter-number.active {
opacity: 1;
}
</style>

95
xqk/components/SinglePlayGif.vue

@ -0,0 +1,95 @@
<template>
<view class="gif-container">
<view class="dynamic-container">
<image
:src="gifSrc"
mode="widthFix"
class="gif-image"
:style="{ display: isPlaying ? 'block' : 'none' }"
@load="startGifPlay"
></image>
<image
:src="staticCover"
mode="widthFix"
class="gif-image"
:style="{ display: isPlaying ? 'none' : 'block' }"
></image>
</view>
</view>
</template>
<script>
export default {
props: {
// GIF
gifSrc: {
type: String,
required: true
},
//
staticCover: {
type: String,
default: ''
},
// GIF()
duration: {
type: Number,
default: 2000
}
},
data() {
return {
isPlaying: false,
playTimer: null
}
},
methods: {
// GIF
startGifPlay() {
this.isPlaying = true;
//
if (this.playTimer) {
clearTimeout(this.playTimer);
}
//
this.playTimer = setTimeout(() => {
this.isPlaying = false;
}, this.duration);
}
},
onUnload() {
//
if (this.playTimer) {
clearTimeout(this.playTimer);
}
}
}
</script>
<style scoped>
.gif-container {
width: 100vw;
display: flex;
justify-content: center;
}
.gif-image {
width: 100vw;
height: auto;
}
.dynamic-container {
position: relative;
width: 100vw;
display: flex;
justify-content: center;
}
.dynamic-container image {
position: absolute;
top: 0;
left: 0;
}
</style>

183
xqk/home/home.vue

@ -0,0 +1,183 @@
<template>
<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"> -->
<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> -->
<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';
// import SwipeToNext from '@/components/SwipeToNext.vue';
export default {
components: {
MusicControl,
SinglePlayGif,
NavMenu,
// SwipeToNext,
AudioControl
},
data() {
return {
isPlaying: false,
playTimer: null,
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/20250904/e734717a187c357b63d64d6214de0ca1.gif'),
// this.showImg('/uploads/20250903/24303e4b7218eaf3d857c846417eb490.png'),
// this.showImg('/uploads/20250903/17495ef65648c64c31920d312301e991.png'),
// this.showImg('/uploads/20250903/92d6f1c6f8f7de040f3c31c8faf98927.png'),
],
isLastSlide: false //
}
},
onLoad(option) {
this.currentIndex = option.currentIndex || 0
//
if (this.currentIndex == this.swiperImages.length - 1) {
// this.navIndex = 1;
this.isLastSlide = true;
}
//
if (this.swiperImages.length === 1) {
this.isLastSlide = true;
}
},
onShow() {
const app = getApp();
app.updateMusicSrc('https://des.js-dyyj.com/data/2025/09/04/bb2921f6-eeac-4f21-b2b7-11c1e3138976.mp3');
app.initBackgroundMusic(); //
uni.$bgMusic.play(); //
},
methods: {
handleJumpToPage(idx) {
this.navIndex = idx
if (idx == this.swiperImages.length - 1) this.navIndex = idx + 1
},
handleSwiperChange(e) {
console.log(e);
this.currentIndex = e.detail.current;
if (this.currentIndex == this.swiperImages.length - 1) {
this.navIndex = 1;
//
this.isLastSlide = true;
} else {
this.navIndex = 0;
this.isLastSlide = false;
}
},
//
handleSwipeToNext(targetPath) {
console.log('收到滑动跳转事件,目标路径:', targetPath);
//
},
// <!---- >
// #ifdef MP-WEIXIN
onShareAppMessage() {
return {
title: '一只蟹的生命远征|「Epic Soul」阅读体 issue06',
mpId: 'wx8954209bb3ad489e',
path: '/xqk/home/home',
imageUrl: this.showImg('/uploads/20250903/66ff1f3cd63ea776a0203e8e0dd92dda.jpg')
};
},
onShareTimeline() {
return {
title: '一只蟹的生命远征|「Epic Soul」阅读体 issue06',
query: '',
imageUrl: this.showImg('/uploads/20250903/66ff1f3cd63ea776a0203e8e0dd92dda.jpg')
};
}
// #endif
}
}
</script>
<style lang="scss" scoped>
.swiper {
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);
/* 顶部安全距离 */
padding-bottom: env(safe-area-inset-bottom);
/* 底部安全距离 */
box-sizing: border-box;
/* 修改背景尺寸为覆盖模式 */
background-size: cover;
width: 100vw;
height: 100vh;
// background-size: 100% auto;
background-position: center center;
background-color: #000;
background-repeat: no-repeat;
position: relative;
display: flex;
align-items: center;
justify-content: center;
.img1-text {
position: absolute;
width: 632.16rpx;
top: 170rpx;
left: 0;
right: 0;
margin: 0 auto;
}
.img4-text {
position: absolute;
width: 476.36rpx;
top: 170rpx;
left: 84rpx;
}
.btn-img {
position: absolute;
width: 149.8rpx;
bottom: 290rpx;
left: 84rpx;
}
}
.swiper-img {
width: 100vw;
height: 100vh;
}
</style>

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 {

34
xrcc/home/home.vue

@ -56,22 +56,24 @@
</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";
import BackButton from "@/components/BackButton.vue";
export default {
components: {
MusicControl,
SinglePlayGif,
NavMenu,
BackButton,
AudioControl,
},
data() {
return {
@ -150,6 +152,7 @@ export default {
background-repeat: no-repeat;
position: relative;
<<<<<<< HEAD
.img1-text {
position: absolute;
width: 632.16rpx;
@ -158,6 +161,31 @@ export default {
right: 0;
margin: 0 auto;
}
=======
.img1-text {
position: absolute;
width: 632.16rpx;
top: 170rpx;
left: 0;
right: 0;
margin: 0 auto;
}
.img4-text {
position: absolute;
width: 476.36rpx;
top: 170rpx;
left: 84rpx;
}
.btn-img {
position: absolute;
width: 149.8rpx;
bottom: 290rpx;
left: 84rpx;
}
}
>>>>>>> master
.img4-text {
position: absolute;

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 {

41
xxdf/home/home.vue

@ -225,42 +225,25 @@
</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 ShareGuide from "@/components/ShareGuide.vue";
import MusicControl from "@/components/MusicControl.vue";
import NavMenu from "../components/NavMenu.vue";
import BackButton from "@/components/BackButton.vue";
import AudioControl from '@/components/AudioControl.vue';
import ShareGuide from '@/components/ShareGuide.vue';
import MusicControl from '@/components/MusicControl.vue';
import NavMenu from '../components/NavMenu.vue';
export default {
components: {
ShareGuide,
MusicControl,
NavMenu,
BackButton,
},
data() {
return {
showShareGuide: false,
currentIndex: 0,
loadedPages: {
0: false,
1: false,
2: false,
3: false,
4: false,
5: false,
6: false,
},
preloadBuffer: 1,
};
},
methods: {
ShareMoments() {
this.showShareGuide = true;
components: {
ShareGuide,
MusicControl,
NavMenu,
AudioControl
},
onShareGuideClose() {
console.log("分享引导已关闭");

Loading…
Cancel
Save