diff --git a/subPages/home/book.vue b/subPages/home/book.vue index ac91e8e..26f4178 100644 --- a/subPages/home/book.vue +++ b/subPages/home/book.vue @@ -17,6 +17,7 @@ - - {{ item.content }} + + + + {{ item.content }} + + @@ -68,7 +76,22 @@ - + + + + + 获取第{{currentPage}}页音频 + + + + + + + 正在加载第{{currentPage}}页音频... + + + + {{ formatTime(currentTime) }} @@ -245,6 +268,11 @@ export default { currentAudio: null, // 当前音频实例 // 音频缓存管理 audioCache: {}, // 页面音频缓存 {pageIndex: {audios: [], totalDuration: 0}} + // 音频加载状态 + isAudioLoading: false, // 音频是否正在加载 + hasAudioData: false, // 当前页面是否已有音频数据 + // 文本高亮相关 + currentHighlightIndex: -1, // 当前高亮的文本索引 courseIdList: [], bookTitle: '', courseList: [ @@ -273,6 +301,7 @@ export default { progressPercent() { return this.totalTime > 0 ? (this.currentTime / this.totalTime) * 100 : 0; }, + // 动态页面标题 currentPageTitle() { return this.pageTitles[this.currentPage - 1] || this.bookTitle; @@ -294,6 +323,9 @@ export default { return; } + // 开始加载状态 + this.isAudioLoading = true; + // 清空当前页面音频数组 this.currentPageAudios = []; this.currentAudioIndex = 0; @@ -376,15 +408,74 @@ export default { this.limitCacheSize(10); } } + + // 结束加载状态 + this.isAudioLoading = false; + + // 设置音频数据状态 + this.hasAudioData = this.currentPageAudios.length > 0; }, + + // 重置音频状态 + resetAudioState() { + // 检查当前页面是否已有缓存的音频数据 + const pageKey = `${this.courseId}_${this.currentPage}`; + const cachedAudio = this.audioCache[pageKey]; + + if (cachedAudio && cachedAudio.audios && cachedAudio.audios.length > 0) { + // 如果有缓存的音频数据,恢复音频状态 + this.currentPageAudios = cachedAudio.audios; + this.totalTime = cachedAudio.totalDuration || 0; + this.hasAudioData = true; + } else { + // 如果没有缓存的音频数据,重置为初始状态 + this.currentPageAudios = []; + this.totalTime = 0; + this.hasAudioData = false; + } + + // 重置播放状态 + this.currentAudioIndex = 0; + this.isPlaying = false; + this.currentTime = 0; + this.isAudioLoading = false; + this.currentHighlightIndex = -1; + }, + + // 手动获取音频 + async handleGetAudio() { + // 检查是否有音色ID + if (!this.voiceId) { + uni.showToast({ + title: '音色未加载,请稍后重试', + icon: 'none' + }); + return; + } + + // 检查当前页面是否有文本内容 + if (!this.isTextPage) { + uni.showToast({ + title: '当前页面没有文本内容', + icon: 'none' + }); + return; + } + + // 检查是否正在加载 + if (this.isAudioLoading) { + return; + } + + // 调用获取音频方法 + await this.getCurrentPageAudio(); + }, // 获取音色列表 拿第一个做默认的音色id async getVoiceList() { const voiceRes = await this.$api.music.list() if(voiceRes.code === 200){ - this.voiceId = voiceRes.result.id - console.log('111'); - - await this.getCurrentPageAudio() + this.voiceId = voiceRes.result[0].id + console.log('获取默认音色ID:', this.voiceId); } }, toggleNavbar() { @@ -500,6 +591,8 @@ export default { this.currentAudio.play(); this.isPlaying = true; + // 更新高亮状态 + this.updateHighlightIndex(); }, // 暂停音频 @@ -508,6 +601,8 @@ export default { this.currentAudio.pause(); } this.isPlaying = false; + // 暂停时清除高亮 + this.currentHighlightIndex = -1; }, // 创建音频实例 @@ -566,6 +661,43 @@ export default { totalTime += this.currentAudio.currentTime; this.currentTime = totalTime; + + // 更新当前高亮的文本索引 + this.updateHighlightIndex(); + }, + + // 更新高亮文本索引 + updateHighlightIndex() { + if (!this.isPlaying || this.currentPageAudios.length === 0) { + this.currentHighlightIndex = -1; + return; + } + + // 根据当前播放的音频索引设置高亮 + this.currentHighlightIndex = this.currentAudioIndex; + console.log('更新高亮索引:', this.currentHighlightIndex, '当前音频索引:', this.currentAudioIndex); + }, + + // 判断当前文本是否应该高亮 + isTextHighlighted(page, index) { + // 只有当前页面且是文本类型才可能高亮 + if (page !== this.bookPages[this.currentPage - 1]) return false; + + // 计算当前页面中text类型元素的索引 + let textIndex = 0; + for (let i = 0; i <= index; i++) { + if (page[i].type === 'text') { + if (i === index) { + const shouldHighlight = textIndex === this.currentHighlightIndex; + if (shouldHighlight) { + console.log('高亮文本:', textIndex, '当前高亮索引:', this.currentHighlightIndex); + } + return shouldHighlight; + } + textIndex++; + } + } + return false; }, // 音频播放结束处理 @@ -584,6 +716,7 @@ export default { // 停止播放 this.isPlaying = false; this.currentTime = this.totalTime; + this.currentHighlightIndex = -1; } } }, @@ -604,6 +737,11 @@ export default { // 上一个音频 previousAudio() { + // 如果正在加载音频,禁止切换 + if (this.isAudioLoading) { + return; + } + if (this.currentPageAudios.length === 0) return; if (this.currentAudioIndex > 0) { @@ -616,6 +754,11 @@ export default { // 下一个音频 nextAudio() { + // 如果正在加载音频,禁止切换 + if (this.isAudioLoading) { + return; + } + if (this.currentPageAudios.length === 0) return; if (this.currentAudioIndex < this.currentPageAudios.length - 1) { @@ -626,6 +769,11 @@ export default { } }, async previousPage() { + // 如果正在加载音频,禁止翻页 + if (this.isAudioLoading) { + return; + } + if (this.currentPage > 1) { // 停止当前音频播放 this.pauseAudio(); @@ -636,11 +784,16 @@ export default { await this.getBookPages(this.courseIdList[this.currentPage - 1]); } - // 重新获取当前页面的音频 - await this.getCurrentPageAudio(); + // 清空当前音频状态 + this.resetAudioState(); } }, async nextPage() { + // 如果正在加载音频,禁止翻页 + if (this.isAudioLoading) { + return; + } + if (this.currentPage < this.bookPages.length) { // 停止当前音频播放 this.pauseAudio(); @@ -651,8 +804,8 @@ export default { await this.getBookPages(this.courseIdList[this.currentPage - 1]); } - // 重新获取当前页面的音频 - await this.getCurrentPageAudio(); + // 清空当前音频状态 + this.resetAudioState(); } }, formatTime(seconds) { @@ -674,6 +827,11 @@ export default { }) }, async goToPage(page) { + // 如果正在加载音频,禁止翻页 + if (this.isAudioLoading) { + return; + } + // 停止当前音频播放 this.pauseAudio(); @@ -684,10 +842,15 @@ export default { await this.getBookPages(this.courseIdList[this.currentPage - 1]); } - // 重新获取当前页面的音频 - await this.getCurrentPageAudio(); + // 清空当前音频状态 + this.resetAudioState(); }, async onSwiperChange(e) { + // 如果正在加载音频,禁止翻页 + if (this.isAudioLoading) { + return; + } + // 停止当前音频播放 this.pauseAudio(); @@ -697,8 +860,8 @@ export default { await this.getBookPages(this.courseIdList[this.currentPage - 1]); } - // 重新获取当前页面的音频 - await this.getCurrentPageAudio(); + // 清空当前音频状态 + this.resetAudioState(); }, async getCourseList(id) { const res = await this.$api.book.coursePage({ @@ -710,6 +873,8 @@ export default { this.bookPages = this.courseIdList.map(() => []) // 初始化标题数组 this.pageTitles = this.courseIdList.map(() => '') + // 重置音频状态 + this.resetAudioState() // 初始化第一页 if (this.courseIdList.length > 0) { await this.getBookPages(this.courseIdList[0]) @@ -743,10 +908,31 @@ export default { index, })) } + }, + + // 清理音频缓存 + clearAudioCache() { + this.audioCache = {}; + console.log('音频缓存已清理'); + }, + + // 限制缓存大小,保留最近访问的页面 + limitCacheSize(maxSize = 10) { + const cacheKeys = Object.keys(this.audioCache); + if (cacheKeys.length > maxSize) { + // 删除最旧的缓存项 + const keysToDelete = cacheKeys.slice(0, cacheKeys.length - maxSize); + keysToDelete.forEach(key => { + delete this.audioCache[key]; + }); + console.log('缓存大小已限制,删除了', keysToDelete.length, '个缓存项'); + } } }, async onLoad(args) { this.courseId = args.courseId + // 重置音频状态 + this.resetAudioState() // 先获取点进来的课程的页面列表 await Promise.all([this.getCourseList(this.courseId), this.getCoursePageList(args.bookId)]) await this.getVoiceList() @@ -767,25 +953,6 @@ export default { // 页面隐藏时暂停音频 onHide() { this.pauseAudio(); - }, - - // 清理音频缓存 - clearAudioCache() { - this.audioCache = {}; - console.log('音频缓存已清理'); - }, - - // 限制缓存大小,保留最近访问的页面 - limitCacheSize(maxSize = 10) { - const cacheKeys = Object.keys(this.audioCache); - if (cacheKeys.length > maxSize) { - // 删除最旧的缓存项 - const keysToDelete = cacheKeys.slice(0, cacheKeys.length - maxSize); - keysToDelete.forEach(key => { - delete this.audioCache[key]; - }); - console.log('缓存大小已限制,删除了', keysToDelete.length, '个缓存项'); - } } } @@ -1003,6 +1170,15 @@ export default { letter-spacing: 0; text-align: justify; word-break: break-all; + transition: all 0.3s ease; +} + +.text-highlight { + background-color: $primary-color; + // color: #fff; + // padding: 8rpx 16rpx; + // border-radius: 8rpx; + // box-shadow: 0 2rpx 8rpx rgba(6, 218, 220, 0.3); } .custom-tabbar { @@ -1394,4 +1570,58 @@ export default { justify-content: center; padding: 10rpx; } + +/* 音频加载状态样式 */ +.audio-loading-container { + background: #fff; + padding: 40rpx; + border-bottom: 1rpx solid #eee; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20rpx; + position: relative; + z-index: 10; +} + +.loading-text { + font-size: 28rpx; + color: #999; +} + +/* 获取音频按钮样式 */ +.audio-get-button-container { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + padding: 30rpx; + border-radius: 20rpx; + border: 2rpx solid #E5E5E5; + transition: all 0.3s ease; + position: relative; + z-index: 10; +} + +.get-audio-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 16rpx; + padding: 20rpx 40rpx; + background: linear-gradient(135deg, #06DADC 0%, #04B8BA 100%); + border-radius: 50rpx; + box-shadow: 0 8rpx 20rpx rgba(6, 218, 220, 0.3); + transition: all 0.3s ease; +} + +.get-audio-btn:active { + transform: scale(0.95); + box-shadow: 0 4rpx 10rpx rgba(6, 218, 220, 0.2); +} + +.get-audio-text { + font-size: 32rpx; + color: #FFFFFF; + font-weight: 500; +} \ No newline at end of file diff --git a/subPages/user/share.vue b/subPages/user/share.vue index 1202c84..030641f 100644 --- a/subPages/user/share.vue +++ b/subPages/user/share.vue @@ -44,22 +44,116 @@ export default { icon: 'none' }) }, - save() { + async save() { + try { + // 检查相册权限 + const authResult = await this.checkPhotoAlbumAuth(); + + if (authResult.authSetting['scope.writePhotosAlbum'] === true) { + // 已有权限,直接保存 + this.saveToAlbum(); + } else if (authResult.authSetting['scope.writePhotosAlbum'] === false) { + // 权限被拒绝,引导用户到设置页面 + this.showAuthGuide(); + } else { + // 未授权,请求权限 + this.requestPhotoAlbumAuth(); + } + } catch (error) { + console.error('权限检查失败:', error); + // 如果权限检查失败,直接尝试保存(兼容处理) + this.saveToAlbum(); + } + }, + + // 检查相册权限 + checkPhotoAlbumAuth() { + return new Promise((resolve, reject) => { + uni.getSetting({ + success: (res) => { + resolve(res); + }, + fail: (err) => { + reject(err); + } + }); + }); + }, + + // 请求相册权限 + requestPhotoAlbumAuth() { + uni.authorize({ + scope: 'scope.writePhotosAlbum', + success: () => { + // 权限请求成功,保存图片 + this.saveToAlbum(); + }, + fail: () => { + // 权限请求被拒绝,引导用户到设置页面 + this.showAuthGuide(); + } + }); + }, + + // 显示权限引导 + showAuthGuide() { + uni.showModal({ + title: '需要相册权限', + content: '保存图片需要访问您的相册权限,请在设置中开启相册权限后重试', + confirmText: '去设置', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + // 打开设置页面 + uni.openSetting({ + success: (settingRes) => { + if (settingRes.authSetting['scope.writePhotosAlbum']) { + // 用户在设置页面开启了权限,再次调用保存 + this.saveToAlbum(); + } else { + uni.showToast({ + title: '未开启相册权限', + icon: 'none' + }); + } + } + }); + } + } + }); + }, + + // 保存图片到相册 + saveToAlbum() { + if (!this.Qrcode) { + uni.showToast({ + title: '图片还未加载完成', + icon: 'none' + }); + return; + } + uni.saveImageToPhotosAlbum({ filePath: this.Qrcode, success: (res) => { uni.showToast({ title: '保存成功', icon: 'success' - }) + }); }, fail: (err) => { - uni.showToast({ - title: '保存失败', - icon: 'none' - }) + console.error('保存失败:', err); + if (err.errMsg.includes('auth')) { + // 如果是权限问题,再次引导用户 + this.showAuthGuide(); + } else { + uni.showToast({ + title: '保存失败,请重试', + icon: 'none' + }); + } } - }) + }); }, async getQrcode() { // const res = await this.$api.promotion.qrCode()