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 @@ + + + + + \ 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 + + + +``` + +## 特殊情况处理 + +### 单张图片问题 + +当只有一张图片时,传统的 `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 @@ + + \ 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 @@