diff --git a/App.vue b/App.vue
index 79015dc..4099298 100644
--- a/App.vue
+++ b/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;
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 deeaa34..840946a 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
new file mode 100644
index 0000000..3558f0b
--- /dev/null
+++ b/components/SwipeToNext.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+ {{ tipText }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/SwipeToNext使用文档.md b/components/SwipeToNext使用文档.md
new file mode 100644
index 0000000..beb649f
--- /dev/null
+++ b/components/SwipeToNext使用文档.md
@@ -0,0 +1,293 @@
+# SwipeToNext 组件使用文档
+
+## 组件介绍
+
+`SwipeToNext` 是一个通用的触底跳转组件,封装了手势检测、延迟防抖、提示文字等功能,可以在任何需要滑动跳转的页面中使用。
+
+## 组件特性
+
+- ✅ 手势滑动检测
+- ✅ 防误触发机制(延迟允许跳转)
+- ✅ 可配置的滑动阈值
+- ✅ 自定义提示文字
+- ✅ 支持事件监听
+- ✅ 完全可配置的参数
+
+## 使用方法
+
+### 1. 引入组件
+
+```vue
+
+```
+
+### 2. 基础使用
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+### 3. 完整配置使用
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+## 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
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 场景2:文章阅读页面
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 场景4:单张图片或总是启用触底跳转
+
+```vue
+
+
+
+
+
+
+
+
+
+```
+
+```vue
+
+
+
+
+ 步骤 {{ currentStep + 1 }} / {{ totalSteps }}
+
+
+
+
+
+
+
+```
+
+## 特殊情况处理
+
+### 单张图片问题
+
+当只有一张图片时,传统的 `isLastSlide` 逻辑不适用。这时可以使用 `alwaysEnable` 参数:
+
+```vue
+
+
+
+
+```
+
+### 参数优先级
+
+当 `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; // 改变字体大小
+ }
+}
+```
+
+这个组件极大地简化了触底跳转功能的实现,让你可以专注于业务逻辑而不用重复编写相同的手势检测代码。
\ No newline at end of file
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 54a2965..8d4d7f8 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'
import main from "@/common/index.js"
Vue.prototype.$main = main
diff --git a/pages.json b/pages.json
index 20a1471..453b54f 100644
--- a/pages.json
+++ b/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,19 +249,19 @@
"navigationBarTitleText": "确认订单"
}
},
- {
- "path": "orderQy/confrimWriteOff",
- "style": {
- "navigationBarTitleText": "核销订单"
- }
- },
- {
- "path": "orderQy/writeOffCode",
- "style": {
- "navigationBarTitleText": "核销码"
- }
- },
-
+ {
+ "path": "orderQy/confrimWriteOff",
+ "style": {
+ "navigationBarTitleText": "核销订单"
+ }
+ },
+ {
+ "path": "orderQy/writeOffCode",
+ "style": {
+ "navigationBarTitleText": "核销码"
+ }
+ },
+
{
"path": "memorialAlbum/index",
"style": {
@@ -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": {
diff --git a/pages/index/readingBody.vue b/pages/index/readingBody.vue
index 24f4645..7b52922 100644
--- a/pages/index/readingBody.vue
+++ b/pages/index/readingBody.vue
@@ -35,7 +35,8 @@
- Epic soul交响 | 阅读体以数字文化资产IP为核心,以消费产品创新为导向,把“每个生命都有史诗般的灵魂”作为创作宗旨,通过文字、图像、音视频、交互式展览等多种技术工具创作完成的轻量化数字文化内容产品,邀您一起收藏这颗星球所有未被传唱的英雄史诗。
+ Epic soul交响 |
+ 阅读体以数字文化资产IP为核心,以消费产品创新为导向,把“每个生命都有史诗般的灵魂”作为创作宗旨,通过文字、图像、音视频、交互式展览等多种技术工具创作完成的轻量化数字文化内容产品,邀您一起收藏这颗星球所有未被传唱的英雄史诗。
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"
>
-
+
-
-
-
-
+
+ @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/project.config.json b/project.config.json
new file mode 100644
index 0000000..3c63362
--- /dev/null
+++ b/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": {}
+}
\ No newline at end of file
diff --git a/project.private.config.json b/project.private.config.json
new file mode 100644
index 0000000..d64d056
--- /dev/null
+++ b/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
+ }
+}
\ No newline at end of file
diff --git a/static/js/CommonFunction.js b/static/js/CommonFunction.js
index 768bf04..152cae7 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 @@
+
@@ -87,10 +88,12 @@
+
+
\ No newline at end of file
diff --git a/xqk/chapter2/index.vue b/xqk/chapter2/index.vue
new file mode 100644
index 0000000..fcf3560
--- /dev/null
+++ b/xqk/chapter2/index.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xqk/chapter3/index.vue b/xqk/chapter3/index.vue
new file mode 100644
index 0000000..b33a636
--- /dev/null
+++ b/xqk/chapter3/index.vue
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xqk/chapter4/index.vue b/xqk/chapter4/index.vue
new file mode 100644
index 0000000..208a674
--- /dev/null
+++ b/xqk/chapter4/index.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xqk/chapter5/index.vue b/xqk/chapter5/index.vue
new file mode 100644
index 0000000..1a352aa
--- /dev/null
+++ b/xqk/chapter5/index.vue
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xqk/chapter6/index.vue b/xqk/chapter6/index.vue
new file mode 100644
index 0000000..91e1b20
--- /dev/null
+++ b/xqk/chapter6/index.vue
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xqk/components/NavMenu.vue b/xqk/components/NavMenu.vue
new file mode 100644
index 0000000..c7b787d
--- /dev/null
+++ b/xqk/components/NavMenu.vue
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xqk/components/SinglePlayGif.vue b/xqk/components/SinglePlayGif.vue
new file mode 100644
index 0000000..a3d56a8
--- /dev/null
+++ b/xqk/components/SinglePlayGif.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/xqk/home/home.vue b/xqk/home/home.vue
new file mode 100644
index 0000000..70a7626
--- /dev/null
+++ b/xqk/home/home.vue
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xrcc/chapter1/index.vue b/xrcc/chapter1/index.vue
index 7d23a95..1951911 100644
--- a/xrcc/chapter1/index.vue
+++ b/xrcc/chapter1/index.vue
@@ -5,8 +5,8 @@
+ v-for="i in 3" :key="i" mode="widthFix" :class="['module'+(i+1)]" @click="openPopup(i+1)">
+
@@ -26,19 +26,21 @@
-
+