From 22da441688e48256efa0854c144a573ad315f1b8 Mon Sep 17 00:00:00 2001 From: zhangminghao <2275599059@qq.com> Date: Fri, 5 Sep 2025 15:25:26 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8B=8F=E9=9D=92=E5=A3=B3=20=E5=8F=8A?= =?UTF-8?q?=E8=AF=AD=E9=9F=B3=E6=92=AD=E6=8A=A5=20josn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 3 +- bmzm/chapter1/index.vue | 372 ++-- bmzm/chapter2/index.vue | 5 +- bmzm/chapter3/index.vue | 5 +- bmzm/chapter4/index.vue | 5 +- bmzm/chapter5/index.vue | 5 +- bmzm/chapter6/index.vue | 5 +- bmzm/chapter7/index.vue | 5 +- bmzm/home/home.vue | 5 +- components/AudioControl.vue | 289 +++ components/AudioControl使用文档.md | 155 ++ components/MusicControl.vue | 65 +- components/SwipeToNext.vue | 2 +- .../跨页面音频控制解决方案.md | 225 +++ components/音频背景音乐交互说明.md | 144 ++ main.js | 1 + pages.json | 7 - pages/index/readingBody.vue | 16 +- pig/chapter1/chapter1.vue | 5 +- pig/chapter2/chapter2.vue | 5 +- pig/chapter3/chapter3.vue | 5 +- pig/chapter4/chapter4.vue | 5 +- pig/home/home.vue | 1749 ++++++++--------- static/js/CommonFunction.js | 96 +- taozi/chapter1/chapter1.vue | 5 +- taozi/chapter2/chapter2.vue | 5 +- taozi/chapter3/chapter3.vue | 5 +- taozi/chapter4/chapter4.vue | 5 +- taozi/home/home.vue | 5 +- utils/globalAudioManager.js | 92 + xqk/chapter1/index.vue | 206 +- xqk/chapter2/index.vue | 55 +- xqk/chapter3/index.vue | 154 +- xqk/chapter4/index.vue | 164 +- xqk/chapter5/index.vue | 74 +- xqk/chapter6/index.vue | 138 +- xqk/chapter7/index.vue | 143 -- xqk/chapter8/index.vue | 75 - xqk/components/NavMenu.vue | 6 +- xqk/home/home.vue | 52 +- xrcc/chapter1/index.vue | 16 +- xrcc/chapter2/index.vue | 5 +- xrcc/chapter3/index.vue | 6 +- xrcc/chapter4/index.vue | 6 +- xrcc/chapter5/index.vue | 6 +- xrcc/chapter6/index.vue | 6 +- xrcc/chapter7/index.vue | 6 +- xrcc/chapter8/index.vue | 6 +- xrcc/home/home.vue | 31 +- xxdf/chapter1/cover1.vue | 5 +- xxdf/chapter1/detail1.vue | 4 +- xxdf/chapter1/detail2.vue | 5 +- xxdf/chapter1/detail3.vue | 5 +- xxdf/chapter1/detail4.vue | 5 +- xxdf/chapter1/detail5.vue | 5 +- xxdf/chapter2/cover.vue | 5 +- xxdf/chapter3/cover.vue | 5 +- xxdf/chapter4/cover.vue | 6 +- xxdf/home/home.vue | 5 +- 59 files changed, 2758 insertions(+), 1743 deletions(-) create mode 100644 components/AudioControl.vue create mode 100644 components/AudioControl使用文档.md create mode 100644 components/跨页面音频控制解决方案.md create mode 100644 components/音频背景音乐交互说明.md create mode 100644 utils/globalAudioManager.js delete mode 100644 xqk/chapter7/index.vue delete mode 100644 xqk/chapter8/index.vue diff --git a/App.vue b/App.vue index 2e0616a..c8f52ac 100644 --- a/App.vue +++ b/App.vue @@ -5,7 +5,8 @@ randomImages: [], bgMusic: null, isMusicPlaying: false, - musicSrc: 'https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3' + musicSrc: 'https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3', + currentAudio: null // 全局音频实例 }, onLaunch: function() { console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!') diff --git a/bmzm/chapter1/index.vue b/bmzm/chapter1/index.vue index ca9a618..388a729 100644 --- a/bmzm/chapter1/index.vue +++ b/bmzm/chapter1/index.vue @@ -1,147 +1,263 @@ \ No newline at end of file diff --git a/bmzm/chapter2/index.vue b/bmzm/chapter2/index.vue index 15c6f29..de98934 100644 --- a/bmzm/chapter2/index.vue +++ b/bmzm/chapter2/index.vue @@ -10,16 +10,19 @@ + + + \ No newline at end of file diff --git a/components/AudioControl使用文档.md b/components/AudioControl使用文档.md new file mode 100644 index 0000000..4b2ed0d --- /dev/null +++ b/components/AudioControl使用文档.md @@ -0,0 +1,155 @@ +# AudioControl 音频控制组件使用文档 + +## 组件功能 +- 在父组件右上角显示音频控制图标 +- 点击图标播放指定音频,同时暂停背景音乐 +- 再次点击暂停音频,恢复背景音乐 +- 音频播放结束后自动恢复背景音乐 + +## 组件属性 (Props) + +| 属性名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| audioSrc | String | 是 | - | 音频文件路径 | +| visible | Boolean | 否 | true | 是否显示组件 | + +## 使用方法 + +### 1. 在父组件中引入和注册组件 + +```vue + + + + + +``` + +### 2. 使用项目中的showImg方法(推荐) + +如果你的音频文件也存储在项目服务器上,可以使用项目的showImg方法: + +```vue + +``` + +### 3. 动态控制音频源 + +你可以根据不同的页面或条件播放不同的音频: + +```vue + +``` + +## 样式说明 + +组件默认定位在父组件的右上角(top: 30rpx, right: 30rpx),如果需要调整位置,可以在父组件中覆盖样式: + +```vue + +``` + +## 注意事项 + +1. **父组件样式**:确保父组件设置了 `position: relative`,这样AudioControl组件才能正确定位 +2. **音频格式**:建议使用 mp3 格式的音频文件,兼容性最好 +3. **音频路径**:确保音频文件路径正确且可访问 +4. **背景音乐**:组件会自动处理与MusicControl组件的交互,无需额外配置 + +## 图标说明 + +- 🔊:音频未播放状态 +- 🎧:音频播放中状态,带有脉动动画效果 + +## 事件处理 + +组件内部已处理所有音频播放逻辑,包括: +- 播放音频时自动暂停背景音乐 +- 暂停音频时自动恢复背景音乐 +- 音频播放结束时自动恢复背景音乐 +- 组件销毁时自动清理资源 + +## 示例场景 + +适用于以下场景: +- 章节页面播放对应的音频解说 +- 展示页面播放介绍音频 +- 互动页面播放提示音频 +- 任何需要临时播放音频并暂停背景音乐的场景 \ No newline at end of file diff --git a/components/MusicControl.vue b/components/MusicControl.vue index 54b8986..ffb7bf4 100644 --- a/components/MusicControl.vue +++ b/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; } } } diff --git a/components/SwipeToNext.vue b/components/SwipeToNext.vue index f918e49..3558f0b 100644 --- a/components/SwipeToNext.vue +++ b/components/SwipeToNext.vue @@ -34,7 +34,7 @@ export default { // 提示文字内容 tipText: { type: String, - default: '上滑动进入下一章节' + default: '上滑进入下一章节' }, // 滑动阈值(px) swipeThreshold: { diff --git a/components/跨页面音频控制解决方案.md b/components/跨页面音频控制解决方案.md new file mode 100644 index 0000000..a32147a --- /dev/null +++ b/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 + + + +``` + +### 在其他页面检查音频状态 + +```javascript +// 在任何页面的onShow生命周期中 +onShow() { + // 检查是否有音频在播放 + if (uni.$globalAudio.isAudioPlaying()) { + console.log('有音频正在播放:', uni.$globalAudio.getCurrentAudioSrc()); + } +} +``` + +## 技术优势 + +### ✅ **状态持久化** +- 音频实例在页面跳转时不会丢失 +- 组件状态能够正确同步全局音频状态 + +### ✅ **跨页面控制** +- 在任何页面都可以控制当前播放的音频 +- 提供统一的音频管理接口 + +### ✅ **资源优化** +- 避免创建多个音频实例 +- 自动清理无用的音频资源 + +### ✅ **用户体验** +- 页面跳转时音频播放不中断 +- 图标状态显示正确 +- 音频控制逻辑一致 + +## 注意事项 + +1. **页面生命周期**:音频实例与页面生命周期解耦,需要手动管理 +2. **内存管理**:确保在应用退出时正确清理音频资源 +3. **状态同步**:多个AudioControl组件需要监听相同的全局状态 +4. **错误处理**:增强错误处理机制,确保音频异常时的状态恢复 + +## 兼容性 + +- ✅ uni-app +- ✅ 小程序环境 +- ✅ H5环境 +- ✅ APP环境 \ No newline at end of file diff --git a/components/音频背景音乐交互说明.md b/components/音频背景音乐交互说明.md new file mode 100644 index 0000000..c77ad71 --- /dev/null +++ b/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 + + + +``` + +## 优势特点 + +### ✅ **智能交互** +- 两个组件能够智能感知对方的状态 +- 避免同时播放音频和背景音乐造成的冲突 +- 提供良好的用户体验 + +### ✅ **解耦设计** +- 组件间通过事件通信,保持松耦合 +- 每个组件都能独立工作 +- 易于维护和扩展 + +### ✅ **状态同步** +- 实时同步音频和背景音乐的播放状态 +- 确保状态的一致性和准确性 + +## 注意事项 + +1. **事件监听清理**:组件销毁时会自动清理事件监听,避免内存泄漏 +2. **状态管理**:两个组件都维护各自的状态,通过事件保持同步 +3. **错误处理**:包含完善的错误处理机制,确保功能稳定运行 \ No newline at end of file diff --git a/main.js b/main.js index 96732c3..a38e368 100644 --- a/main.js +++ b/main.js @@ -4,6 +4,7 @@ import App from './App' import store from './store' import '@/static/js/request.js' import '@/static/js/CommonFunction.js' +import '@/utils/globalAudioManager.js' import {myMixins} from '@/mixins/myMixins.js' Vue.mixin(myMixins) diff --git a/pages.json b/pages.json index 420a58a..fcf6767 100644 --- a/pages.json +++ b/pages.json @@ -604,13 +604,6 @@ "navigationBarTitleText": "", "navigationStyle": "custom" } - }, - { - "path": "chapter7/index", - "style": { - "navigationBarTitleText": "", - "navigationStyle": "custom" - } } ] } diff --git a/pages/index/readingBody.vue b/pages/index/readingBody.vue index b68e37b..46a71d2 100644 --- a/pages/index/readingBody.vue +++ b/pages/index/readingBody.vue @@ -60,7 +60,7 @@ - + @@ -110,6 +110,8 @@ app.initBackgroundMusic(); // 初始化背景音乐 uni.$bgMusic.play(); // 播放音乐 } + // 暂停所有其他音频(保留背景音乐) + this.pauseAllOtherAudio(); }, methods: { sendRequest() { @@ -188,6 +190,18 @@ uni.navigateTo({ url:'/subPackages/letter/detail?id='+item.id }) + }, + // 暂停所有其他音频(AudioControl组件的音频) + pauseAllOtherAudio() { + try { + // 暂停全局音频(AudioControl组件的音频) + if (uni.$globalAudio && uni.$globalAudio.isAudioPlaying()) { + uni.$globalAudio.pauseCurrentAudio(); + console.log('readingBody: 暂停其他音频,只保留背景音乐'); + } + } catch (error) { + console.error('readingBody: 暂停音频失败:', error); + } } } } diff --git a/pig/chapter1/chapter1.vue b/pig/chapter1/chapter1.vue index 9945e6d..6a34e97 100644 --- a/pig/chapter1/chapter1.vue +++ b/pig/chapter1/chapter1.vue @@ -104,10 +104,12 @@ + + @font-face { + font-family: "SourceHanSerif-Regular"; + src: url(/static/fonts/SourceHanSerifSC-Regular.otf); + } + + .main-swiper { + width: 100%; + height: 100vh; + } + + .page-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + position: relative; + overflow: hidden; + } + + .loadedPages-three { + height: 100%; + position: relative; + background: #efefef; + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + } + + .loadedPages-three-content { + height: 100%; + + .loadedPages-three-title { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin-top: 30rpx; + + .txt { + font-size: 24rpx; + color: #333; + font-family: SourceHanSerif-Regular; + display: flex; + flex-direction: column; + align-items: center; + + text { + display: block; + } + } + + .txt:first-child { + margin-left: 30rpx; + } + + .txt:last-child { + margin-right: 30rpx; + } + } + + .loadedPages-three-center { + position: relative; + + .desc { + display: flex; + flex-direction: column; + font-family: SourceHanSerif-Regular; + font-size: 90rpx; + color: #ec4899; + + text { + display: block; + } + } + + .en-desc { + display: flex; + flex-direction: column; + position: absolute; + top: 50%; + right: 0; + transform: translate(-25%, -50%); + font-size: 24rpx; + font-style: italic; + color: #4b5563; + + text { + display: block; + } + } + } + + .loadedPages-three-bottom { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 30rpx; + + .bottom-img { + width: 400rpx; + height: 120rpx; + } + + .bottom-tit { + font-size: 38rpx; + } + + .bottom-txt { + font-size: 24rpx; + font-style: italic; + color: #4b5563; + } + } + } + + .bg-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; + } + + .content-layer { + position: relative; + z-index: 2; + width: 100%; + height: 100%; + display: flex; + align-items: center; + flex-direction: column; + } + + .content-layer2 { + z-index: 2; + position: absolute; + bottom: 5%; + right: 5%; + } + + .layer-img { + width: 650rpx; + height: 100%; + } + + .arrow-down { + width: 100rpx; + height: 40rpx; + animation: bounce 1.5s infinite; + } + + .layer-icon { + width: 100rpx; + height: 100rpx; + animation: bounce 1.5s infinite; + } + + .overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.3); + z-index: 10; + } + + .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: 11; + 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: #333; + opacity: 0.7; + font-size: 24rpx; + } + + .chapter-number { + color: #333; + opacity: 0.7; + font-size: 28rpx; + margin-top: 8rpx; + } + + .item-active .chapter-title, + .item-active .chapter-number.active { + opacity: 1; + } + + @keyframes bounce { + + 0%, + 20%, + 50%, + 80%, + 100% { + transform: translateY(0); + } + + 40% { + transform: translateY(-20rpx); + } + + 60% { + transform: translateY(-10rpx); + } + } + + .blur-to-clear { + animation: blurToClear 1.5s ease-out forwards; + } + + @keyframes blurToClear { + 0% { + filter: blur(10px); + opacity: 0.3; + } + + 100% { + filter: blur(0); + opacity: 1; + } + } + + .hidden { + opacity: 0; + } + + .bounce-in { + animation: bounceIn 1s ease forwards; + } + + @keyframes bounceIn { + 0% { + opacity: 0; + transform: scale(0.3) translateY(100px); + } + + 50% { + opacity: 1; + transform: scale(1.05) translateY(-10px); + } + + 70% { + transform: scale(0.9) translateY(5px); + } + + 100% { + opacity: 1; + transform: scale(1) translateY(0); + } + } + + .fade-slide-up { + animation: fadeSlideUp 1s ease-out forwards; + } + + @keyframes fadeSlideUp { + 0% { + opacity: 0; + transform: translateY(30px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } + } + + .chapterCover-btn { + position: absolute; + left: 50%; + bottom: 10%; + transform: translate(-50%, -50%); + width: 300rpx; + height: 100rpx; + z-index: 2; + } + + .qrcode-txt { + width: 30vw; + z-index: 2; + position: fixed; + left: 0; + right: 0; + margin: 100rpx auto 0; + } + + .qrcode-txts { + width: 28vw; + z-index: 2; + position: fixed; + left: 0; + right: 0; + margin: 335rpx auto 0; + } + + .message-board { + width: 100%; + } + + .qrCode-image { + position: absolute; + left: 0; + right: 0; + bottom: 192rpx; + margin: 0 auto; + z-index: 2; + width: 30vw; + } + + .image-popup-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.8); + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + } + + .image-popup-container { + position: relative; + max-width: 90vw; + max-height: 90vh; + } + + .popup-image { + width: 750rpx; + height: 654rpx; + max-width: 100%; + max-height: 100%; + } + + .close-btn { + position: absolute; + top: -20rpx; + right: -20rpx; + width: 60rpx; + height: 60rpx; + background-color: rgba(255, 255, 255, 0.9); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 40rpx; + color: #333; + cursor: pointer; + } + \ No newline at end of file diff --git a/static/js/CommonFunction.js b/static/js/CommonFunction.js index e5dd4b1..723a463 100644 --- a/static/js/CommonFunction.js +++ b/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); + } + } + }); } // 返回上一页 diff --git a/taozi/chapter1/chapter1.vue b/taozi/chapter1/chapter1.vue index ec711c9..e86bfe4 100644 --- a/taozi/chapter1/chapter1.vue +++ b/taozi/chapter1/chapter1.vue @@ -1,6 +1,7 @@ @@ -94,7 +78,15 @@ width: 100vw; height: 100vh; } - + .imgJump{ + position: absolute; + bottom:100rpx; + left:250rpx; + width: 273rpx; + height: 85rpx; + opacity: 0.8; + z-index: 999999; + } .swiper-item { width: 100vw; height: 100vh; @@ -120,7 +112,7 @@ margin: 0 auto; bottom: 70rpx; } - + .img3-btn { width: 558rpx; line-height: 72rpx; @@ -135,17 +127,17 @@ margin: 0 auto; bottom: 180rpx; } - + .flex-column { position: absolute; bottom: 280rpx; width: 100%; align-items: center; - + .img5-text { width: 100%; } - + .img5-btn { width: 230rpx; margin-top: 99rpx; diff --git a/xqk/chapter6/index.vue b/xqk/chapter6/index.vue index a9ea516..91e1b20 100644 --- a/xqk/chapter6/index.vue +++ b/xqk/chapter6/index.vue @@ -3,109 +3,65 @@ - @@ -123,15 +79,32 @@ background-color: #000; background-repeat: no-repeat; position: relative; - + display: flex; + align-items: center; + justify-content: center; + .item-box{ + .box-two{ + display: flex; + align-items: center; + justify-content: center; + + } + position: absolute; + top:289rpx; + left:99rpx; + .img2-text{ + width: 257rpx; + } + } .btn-img { position: absolute; width: 149.8rpx; bottom: 290rpx; left: 84rpx; } - - .img7-1, .img7-2 { + + .img7-1, + .img7-2 { width: 437.67rpx; position: absolute; left: 0; @@ -139,7 +112,7 @@ margin: 0 auto; top: 365rpx; } - + .bgm-box { width: 437.67rpx; position: absolute; @@ -148,7 +121,7 @@ bottom: 650rpx; margin: 0 auto; flex-wrap: wrap; - + view { width: 207.89rpx; line-height: 42.77rpx; @@ -158,16 +131,17 @@ font-size: 20rpx; border: 1rpx solid #fff; } + view:nth-child(n+3) { margin-top: 17rpx; } - + .bgm-active { border-color: #00C48C; color: #00C48C; } } - + .img7-btn { width: 439.66rpx; position: absolute; @@ -182,14 +156,14 @@ width: 100vw; height: 100vh; } - + .popup-content { width: 85vw; background-color: #fff; border-radius: 16rpx; padding: 40rpx 30rpx; box-sizing: border-box; - + .input-area { width: 100%; min-height: 180rpx; @@ -201,14 +175,14 @@ box-sizing: border-box; margin-bottom: 15rpx; } - + .word-count { text-align: right; font-size: 24rpx; color: #999; margin-bottom: 35rpx; } - + .confirm-btn { width: 100%; } diff --git a/xqk/chapter7/index.vue b/xqk/chapter7/index.vue deleted file mode 100644 index 5c804af..0000000 --- a/xqk/chapter7/index.vue +++ /dev/null @@ -1,143 +0,0 @@ - - - - - \ No newline at end of file diff --git a/xqk/chapter8/index.vue b/xqk/chapter8/index.vue deleted file mode 100644 index 0b339f9..0000000 --- a/xqk/chapter8/index.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - - - \ No newline at end of file diff --git a/xqk/components/NavMenu.vue b/xqk/components/NavMenu.vue index ac3e469..c7b787d 100644 --- a/xqk/components/NavMenu.vue +++ b/xqk/components/NavMenu.vue @@ -60,7 +60,7 @@ path: "/xqk/chapter3/index" }, { - text: '04 风味人间', + text: '04 蟹在人间', targetIndex: 4, path: "/xqk/chapter4/index" }, @@ -72,7 +72,7 @@ { text: '有感商品', targetIndex: 6, - path: "/subPackages/techan/detail?id=39" + path: "/subPackages/techan/detail?id=40" }, { text: '购物车', @@ -108,7 +108,7 @@ this.$emit('menu-hide'); }, onJumpToPage(item) { - console.log(this.navIndex) + console.log(this.navIndex,item.targetIndex,item.path) if(item.path && item.targetIndex != this.navIndex) this.gotoPath(item.path) this.onCloseMenu(); }, diff --git a/xqk/home/home.vue b/xqk/home/home.vue index 3dfbd33..a4e39d5 100644 --- a/xqk/home/home.vue +++ b/xqk/home/home.vue @@ -1,33 +1,33 @@