diff --git a/config/index.js b/config/index.js index 01a536e..1011490 100644 --- a/config/index.js +++ b/config/index.js @@ -12,7 +12,7 @@ const envParam = { prod: 'production', } -const env = envParam['dev'] +const env = envParam['test'] // 全局配置 const config = { diff --git a/mixins/config.js b/mixins/config.js index 6446be6..bec5ec0 100644 --- a/mixins/config.js +++ b/mixins/config.js @@ -34,17 +34,19 @@ export default { return { title: this.configParamContent('app_name'), desc: this.configParamContent('share_desc'), - imageUrl: this.configParamContent('app_logo'), + imageUrl: this.configParamContent('login_logo'), path: '/pages/index/index' } } }, + // 分享到好友 onShareAppMessage() { return { ...this.GShare, ...this.mixinCustomShare() } }, + // 分享到朋友圈 onShareTimeline() { return { ...this.GShare, diff --git a/static/视频封面.png b/static/视频封面.png deleted file mode 100644 index 146df08..0000000 Binary files a/static/视频封面.png and /dev/null differ diff --git a/stores/index.js b/stores/index.js index 0c5a0b3..753278e 100644 --- a/stores/index.js +++ b/stores/index.js @@ -72,6 +72,8 @@ const store = new Vuex.Store({ acc[item.code] = item return acc }, {}) + console.log('configList列表为:', config); + commit('setConfigList', config) }, // 查询部门列表 diff --git a/subPages/home/book.vue b/subPages/home/book.vue index 81205bf..3a43b43 100644 --- a/subPages/home/book.vue +++ b/subPages/home/book.vue @@ -26,46 +26,61 @@ class="swiper-item" > + + + + {{ pageTitles[index] }} + + 升級會員解鎖 + + - - - - - 划线重点 - - - {{ item.englishText }} - {{ item.chineseText }} + + + + 划线重点 - - - - + + + - {{ item.content }} - + v-for="(token, tokenIndex) in splitEnglishSentence(item.content)" + :key="tokenIndex" + + :class="['english-token', { 'clickable-word': token.isWord && findWordDefinition(token.text) }]" + @tap="token.isWord && findWordDefinition(token.text) ? handleWordClick(token.text) : null" + user-select + >{{ token.text }} + {{ item.content }} + - - - - + + + + + + + {{ item.content }} + + + - - - - + + + + - - - {{ item.title }} - - {{ item.buttonText }} + + + + + @@ -95,10 +110,21 @@ {{ formatTime(currentTime) }} - - - - + {{ formatTime(totalTime) }} @@ -121,8 +147,10 @@ 下一页 - - {{ playSpeed }}x + + + {{ playbackRateSupported ? playSpeed + 'x' : '不支持' }} + @@ -199,6 +227,7 @@ {{ currentWordMeaning.word }} - + {{ currentWordMeaning.phonetic }} @@ -247,7 +282,9 @@ export default { data() { return { - voiceId: '', + isMember: false, + memberId: '', + voiceId: null, courseId: '', showNavbar: true, currentPage: 1, @@ -258,9 +295,12 @@ export default { isPlaying: false, currentTime: 0, totalTime: 0, + sliderValue: 0, // 滑動條的值 + isDragging: false, // 是否正在拖動滑動條 isLoop: false, playSpeed: 1.0, - speedOptions: [1.0, 1.25, 1.5, 2.0], + speedOptions: [0.5, 0.8, 1.0, 1.25, 1.5, 2.0], // 根據uni-app文檔的官方支持值 + playbackRateSupported: true, // 播放速度控制是否支持 // 音频数组管理 currentPageAudios: [], // 当前页面的音频数组 currentAudioIndex: 0, // 当前播放的音频索引 @@ -268,6 +308,8 @@ export default { currentAudio: null, // 当前音频实例 // 音频缓存管理 audioCache: {}, // 页面音频缓存 {pageIndex: {audios: [], totalDuration: 0}} + wordAudioCache: {}, // 單詞語音緩存 + currentWordAudio: null, // 當前播放的單詞音頻實例 // 音频加载状态 isAudioLoading: false, // 音频是否正在加载 hasAudioData: false, // 当前页面是否已有音频数据 @@ -285,6 +327,12 @@ export default { ], // 存储每个页面的标题 pageTitles: [], + // 存储每个页面的type信息 + pageTypes: [], + // 存储每个页面的单词释义数据 + pageWords: [], + // 存储每个页面的付费状态 + pagePay: [], } }, computed: { @@ -293,6 +341,11 @@ export default { }, // 判断当前页面是否为文字类型 isTextPage() { + // 如果是卡片页面(type为'1'),不显示音频控制栏 + if (this.currentPageType === '1') { + return false; + } + const currentPageData = this.bookPages[this.currentPage - 1]; // currentPageData是一个数组 其中的一个元素的type是text就会返回true return currentPageData && currentPageData.some(item => item.type === 'text'); @@ -305,9 +358,28 @@ export default { // 动态页面标题 currentPageTitle() { return this.pageTitles[this.currentPage - 1] || this.bookTitle; + }, + + // 当前页面类型 + currentPageType() { + return this.pageTypes[this.currentPage - 1] || ''; + }, + + // 当前页面的单词释义数据 + currentPageWords() { + return this.pageWords[this.currentPage - 1] || []; } }, methods: { + // 獲取用戶會員信息 判斷是否和傳參傳過來的會員id相同 + async getMemberInfo(){ + const memberRes = await this.$api.member.getUserMemberInfo() + if (memberRes.code === 200) { + this.isMember = memberRes.result.map(item => item.memberId).includes(this.memberId) + console.log('isMember:', this.isMember); + } + + }, // 获取当前页面的音频内容 async getCurrentPageAudio() { // 检查缓存中是否已有当前页面的音频数据 @@ -346,24 +418,24 @@ export default { // 进行音频请求 - 修正字段名:使用content而不是text const radioRes = await this.$api.music.textToVoice({ text: item.content, - voiceId: this.voiceId, + voiceType: this.voiceId, }); console.log(`音频请求响应 ${index + 1}:`, radioRes); if(radioRes.code === 200){ - // API返回的是Base64编码的WAV音频数据,在uniapp环境中需要特殊处理 - const base64Data = radioRes.result; - // 在uniapp中,可以直接使用base64数据作为音频源 - const audioUrl = `data:audio/wav;base64,${base64Data}`; + // 新格式:API直接返回音頻URL + const audioUrl = radioRes.result.audio; + // 檢查API是否返回時長信息 + const duration = radioRes.result.duration || 0; // 同时保存到原始数据中以保持兼容性 item.audioUrl = audioUrl; - console.log(`音频URL设置成功 ${index + 1}:`, audioUrl); + console.log(`音频URL设置成功 ${index + 1}:`, audioUrl, '时长:', duration); return { url: audioUrl, text: item.content, - duration: 0, // 音频时长,需要在加载后获取 + duration: duration, // 優先使用API返回的時長 index: index // 保持原始顺序 }; } else { @@ -434,12 +506,14 @@ export default { this.hasAudioData = false; } - // 重置播放状态 + // 重置播放状态,翻頁時統一設置播放速度為1.0 this.currentAudioIndex = 0; this.isPlaying = false; this.currentTime = 0; this.isAudioLoading = false; this.currentHighlightIndex = -1; + // 翻頁時強制設置播放速度為1.0(適應微信小程序特性) + this.playSpeed = 1.0; }, // 手动获取音频 @@ -474,7 +548,7 @@ export default { async getVoiceList() { const voiceRes = await this.$api.music.list() if(voiceRes.code === 200){ - this.voiceId = voiceRes.result[0].id + this.voiceId = voiceRes.result[0].voiceType console.log('获取默认音色ID:', this.voiceId); } }, @@ -503,13 +577,10 @@ export default { // console.log('选择课程:', courseId) this.getCourseList(courseId) }, - showWordMeaning(wordMeaning) { - if (wordMeaning) { - this.currentWordMeaning = wordMeaning + showWordMeaning() { if (this.$refs.meaningPopup) { this.$refs.meaningPopup.open() } - } }, closeMeaningPopup() { if (this.$refs.meaningPopup) { @@ -517,21 +588,161 @@ export default { } this.currentWordMeaning = null }, + + // 将英文句子分割成单词数组 + splitEnglishSentence(sentence) { + // 使用正则表达式分割句子,保留标点符号 + const tokens = sentence.match(/\b\w+\b|[^\w\s]/g) || []; + return tokens.map((token, index) => ({ + text: token, + index: index, + isWord: /\b\w+\b/.test(token), // 判断是否为单词 + hasDefinition: false // 是否有释义,稍后会设置 + })); + }, + + // 查找单词释义 + findWordDefinition(word) { + const currentPageWords = this.pageWords[this.currentPage - 1] || []; + // 不区分大小写匹配 + return currentPageWords.find(wordData => + wordData.word.toLowerCase() === word.toLowerCase() + ); + }, + + async playWordAudio(word) { + try { + console.log('開始播放單詞語音:', word); + + // 停止當前播放的單詞語音 + if (this.currentWordAudio) { + this.currentWordAudio.destroy(); + this.currentWordAudio = null; + } + + // 調用語音轉換API + const audioRes = await this.$api.music.textToVoice({ + text: word, + voiceType: this.voiceId + }); + + console.log('單詞語音API響應:', audioRes); + + // 檢查響應並播放音頻 + if (audioRes && audioRes.result && audioRes.result.audio) { + // 新格式:直接使用返回的音頻URL + const audioUrl = audioRes.result.audio; + + // 創建並播放音頻 + const audio = uni.createInnerAudioContext(); + audio.src = audioUrl; + + audio.onPlay(() => { + console.log('開始播放單詞語音:', word); + }); + + audio.onEnded(() => { + console.log('單詞語音播放完成:', word); + audio.destroy(); + if (this.currentWordAudio === audio) { + this.currentWordAudio = null; + } + }); + + audio.onError((error) => { + console.error('單詞語音播放失敗:', error); + audio.destroy(); + if (this.currentWordAudio === audio) { + this.currentWordAudio = null; + } + uni.showToast({ + title: '語音播放失敗', + icon: 'none' + }); + }); + + // 保存當前音頻實例並播放 + this.currentWordAudio = audio; + audio.play(); + } else { + console.error('單詞語音請求失敗:', audioRes); + uni.showToast({ + title: '語音播放失敗', + icon: 'none' + }); + } + } catch (error) { + console.error('播放單詞語音異常:', error); + uni.showToast({ + title: '語音播放失敗', + icon: 'none' + }); + } + }, + + + + // 重複播放單詞語音(用於釋義彈窗中的揚聲器圖標) + repeatWordAudio() { + if (this.currentWordMeaning && this.currentWordMeaning.word) { + console.log('重複播放單詞語音:', this.currentWordMeaning.word); + this.playWordAudio(this.currentWordMeaning.word); + } else { + console.warn('沒有當前單詞可以播放'); + } + }, + + // 处理单词点击事件 + handleWordClick(word) { + const definition = this.findWordDefinition(word); + console.log('查找单词:', word, '释义:', definition); + + // 獲取單詞的讀音 + if (word) { + this.playWordAudio(word); + } + + if (definition) { + this.currentWordMeaning = { + word: definition.word, + phonetic: definition.soundmark || '', + partOfSpeech: '', // 可以根据需要添加词性 + meaning: definition.paraphrase || '', + knowledgeGain: definition.knowledge || '' + }; + this.showWordMeaning(); + } else { + console.log('未找到单词释义:', word); + } + }, // 计算音频总时长 async calculateTotalDuration() { let totalDuration = 0; for (let i = 0; i < this.currentPageAudios.length; i++) { const audio = this.currentPageAudios[i]; + + // 優先使用API返回的時長信息 + if (audio.duration && audio.duration > 0) { + console.log(`使用API返回的時長 ${i + 1}:`, audio.duration, '秒'); + totalDuration += audio.duration; + continue; + } + + // 如果沒有API時長信息,嘗試獲取音頻時長 try { const duration = await this.getAudioDuration(audio.url); audio.duration = duration; totalDuration += duration; + console.log(`獲取到音頻時長 ${i + 1}:`, duration, '秒'); } catch (error) { console.error('获取音频时长失败:', error); - // 如果无法获取时长,估算一个默认值(假设每100字符约5秒) - const estimatedDuration = Math.max(5, audio.text.length / 20); + // 如果无法获取时长,根據文字長度估算(更精確的估算) + const textLength = audio.text.length; + // 假設每分鐘可以讀150-200個字符,這裡用180作為平均值 + const estimatedDuration = Math.max(2, textLength / 3); // 每3個字符約1秒 audio.duration = estimatedDuration; totalDuration += estimatedDuration; + console.log(`估算音頻時長 ${i + 1}:`, estimatedDuration, '秒 (文字長度:', textLength, ')'); } } this.totalTime = totalDuration; @@ -544,22 +755,78 @@ export default { const audio = uni.createInnerAudioContext(); audio.src = audioUrl; + let resolved = false; + + // 监听音频加载完成事件 audio.onCanplay(() => { - resolve(audio.duration || 5); // 如果无法获取时长,默认5秒 - audio.destroy(); + console.log('音频可以播放,duration:', audio.duration); + if (!resolved && audio.duration && audio.duration > 0) { + resolved = true; + resolve(audio.duration); + audio.destroy(); + } + }); + + // 监听音频元数据加载完成事件 + audio.onLoadedmetadata = () => { + console.log('音频元数据加载完成,duration:', audio.duration); + if (!resolved && audio.duration && audio.duration > 0) { + resolved = true; + resolve(audio.duration); + audio.destroy(); + } + }; + + // 监听音频时长更新事件 + audio.onDurationChange = () => { + console.log('音频时长更新,duration:', audio.duration); + if (!resolved && audio.duration && audio.duration > 0) { + resolved = true; + resolve(audio.duration); + audio.destroy(); + } + }; + + // 如果以上方法都無法獲取時長,嘗試播放一小段來獲取時長 + audio.onPlay(() => { + console.log('音频开始播放,duration:', audio.duration); + if (!resolved) { + setTimeout(() => { + if (!resolved && audio.duration && audio.duration > 0) { + resolved = true; + resolve(audio.duration); + audio.destroy(); + } + }, 100); // 播放100ms後檢查時長 + } }); audio.onError((error) => { console.error('音频加载失败:', error); - reject(error); - audio.destroy(); + if (!resolved) { + resolved = true; + reject(error); + audio.destroy(); + } }); - // 设置超时 + // 設置較長的超時時間,並在超時前嘗試播放 setTimeout(() => { - reject(new Error('获取音频时长超时')); - audio.destroy(); - }, 3000); + if (!resolved) { + console.log('嘗試播放音頻以獲取時長'); + audio.play(); + } + }, 1000); + + // 最終超時處理 + setTimeout(() => { + if (!resolved) { + console.warn('獲取音頻時長超時,使用默認值'); + resolved = true; + reject(new Error('获取音频时长超时')); + audio.destroy(); + } + }, 5000); }); }, @@ -612,9 +879,42 @@ export default { this.currentAudio.destroy(); } - const audio = uni.createInnerAudioContext(); + // 優先使用微信原生API以支持playbackRate + let audio; + if (typeof wx !== 'undefined' && wx.createInnerAudioContext) { + console.log('使用微信原生音頻API'); + audio = wx.createInnerAudioContext(); + } else { + console.log('使用uni-app音頻API'); + audio = uni.createInnerAudioContext(); + } audio.src = this.currentPageAudios[this.currentAudioIndex].url; - audio.playbackRate = this.playSpeed; + + // 在音頻可以播放時檢測playbackRate支持 + audio.onCanplay(() => { + console.log('🎵 音頻可以播放,開始檢測playbackRate支持'); + this.checkPlaybackRateSupport(audio); + + // 檢測完成後,設置用戶期望的播放速度 + setTimeout(() => { + if (this.playbackRateSupported) { + console.log('設置播放速度:', this.playSpeed); + audio.playbackRate = this.playSpeed; + + // 驗證設置結果 + setTimeout(() => { + console.log('最終播放速度:', audio.playbackRate); + if (Math.abs(audio.playbackRate - this.playSpeed) > 0.01) { + console.log('⚠️ 播放速度設置可能未生效'); + } else { + console.log('✅ 播放速度設置成功'); + } + }, 50); + } else { + console.log('❌ 當前環境不支持播放速度控制'); + } + }, 50); + }); // 音频事件监听 audio.onPlay(() => { @@ -662,6 +962,11 @@ export default { this.currentTime = totalTime; + // 如果不是正在拖動滑動條,則同步更新滑動條的值 + if (!this.isDragging) { + this.sliderValue = this.currentTime; + } + // 更新当前高亮的文本索引 this.updateHighlightIndex(); }, @@ -725,13 +1030,312 @@ export default { }, toggleSpeed() { + // 檢查是否支持播放速度控制 + if (!this.playbackRateSupported) { + uni.showToast({ + title: '當前設備不支持播放速度控制', + icon: 'none', + duration: 2000 + }); + return; + } + const currentIndex = this.speedOptions.indexOf(this.playSpeed); const nextIndex = (currentIndex + 1) % this.speedOptions.length; + const oldSpeed = this.playSpeed; this.playSpeed = this.speedOptions[nextIndex]; + console.log(`播放速度切換: ${oldSpeed}x -> ${this.playSpeed}x`); + // 如果当前有音频在播放,更新播放速度 if (this.currentAudio) { + const wasPlaying = this.isPlaying; + const currentTime = this.currentAudio.currentTime; + + // 設置新的播放速度 this.currentAudio.playbackRate = this.playSpeed; + + // 如果正在播放,需要重啟播放才能使播放速度生效 + if (wasPlaying) { + this.currentAudio.pause(); + setTimeout(() => { + this.currentAudio.seek(currentTime); + this.currentAudio.play(); + }, 50); + } + + console.log('音頻實例播放速度已更新為:', this.currentAudio.playbackRate); + + // 顯示速度變更提示 + uni.showToast({ + title: `播放速度: ${this.playSpeed}x`, + icon: 'none', + duration: 1000 + }); + } + }, + + // 滑動條值實時更新 (@input 事件) + onSliderInput(value) { + // 在拖動過程中實時更新顯示的時間,但不影響實際播放 + if (this.isDragging) { + // 可以在這裡實時更新顯示時間,讓用戶看到拖動到的時間點 + // 但不改變實際的 currentTime,避免影響播放邏輯 + console.log('實時更新滑動條值:', value); + } + }, + + // 滑動條拖動過程中的處理 (@changing 事件) + onSliderChanging(value) { + // 第一次觸發 changing 事件時,暫停播放並標記為拖動狀態 + if (!this.isDragging) { + if (this.isPlaying) { + this.pauseAudio(); + console.log('開始拖動滑動條,暫停播放'); + } + this.isDragging = true; + } + + // 更新滑動條的值,但不改變實際播放位置 + this.sliderValue = value; + console.log('拖動中,滑動條值:', value); + }, + + // 滑動條拖動結束的處理 (@change 事件) + onSliderChange(value) { + console.log('滑動條變化,跳轉到位置:', value, '是否為拖動:', this.isDragging); + + // 如果不是拖動狀態(即單點),需要先暫停播放 + if (!this.isDragging && this.isPlaying) { + this.pauseAudio(); + console.log('單點滑動條,暫停播放'); + } + + // 重置拖動狀態 + this.isDragging = false; + this.sliderValue = value; + + // 跳轉到指定位置,但不自動恢復播放 + this.seekToTime(value, false); + + console.log('滑動條操作完成,保持暫停狀態,需要手動點擊播放'); + }, + + // 跳轉到指定時間 + seekToTime(targetTime, shouldResume = false) { + if (!this.currentPageAudios || this.currentPageAudios.length === 0) { + console.log('沒有音頻數據,無法跳轉'); + return; + } + + // 確保目標時間在有效範圍內 + targetTime = Math.max(0, Math.min(targetTime, this.totalTime)); + console.log('跳轉到時間:', targetTime, '秒', '總時長:', this.totalTime, '是否恢復播放:', shouldResume); + + let accumulatedTime = 0; + let targetAudioIndex = -1; + let targetAudioTime = 0; + + // 找到目標時間對應的音頻片段 + for (let i = 0; i < this.currentPageAudios.length; i++) { + const audioDuration = this.currentPageAudios[i].duration || 0; + console.log(`音頻片段 ${i}: 時長=${audioDuration}, 累計時間=${accumulatedTime}, 範圍=[${accumulatedTime}, ${accumulatedTime + audioDuration}]`); + + if (targetTime >= accumulatedTime && targetTime <= accumulatedTime + audioDuration) { + targetAudioIndex = i; + targetAudioTime = targetTime - accumulatedTime; + break; + } + accumulatedTime += audioDuration; + } + + // 如果沒有找到合適的音頻片段,使用最後一個 + if (targetAudioIndex === -1 && this.currentPageAudios.length > 0) { + targetAudioIndex = this.currentPageAudios.length - 1; + targetAudioTime = this.currentPageAudios[targetAudioIndex].duration || 0; + console.log('使用最後一個音頻片段作為目標'); + } + + console.log('目標音頻索引:', targetAudioIndex, '目標音頻時間:', targetAudioTime); + + if (targetAudioIndex === -1) { + console.error('無法找到目標音頻片段'); + return; + } + + // 如果需要切換到不同的音頻片段 + if (targetAudioIndex !== this.currentAudioIndex) { + console.log(`切換音頻片段: ${this.currentAudioIndex} -> ${targetAudioIndex}`); + this.currentAudioIndex = targetAudioIndex; + this.createAudioInstance(); + + // 等待音頻實例創建完成後再跳轉 + this.waitForAudioReady(() => { + if (this.currentAudio) { + this.currentAudio.seek(targetAudioTime); + this.currentTime = targetTime; + console.log('切換音頻並跳轉到:', targetAudioTime, '秒'); + + // 如果拖動前正在播放,則恢復播放 + if (shouldResume) { + this.currentAudio.play(); + this.isPlaying = true; + console.log('恢復播放狀態'); + } + } + }); + } else { + // 在當前音頻片段內跳轉 + if (this.currentAudio) { + this.currentAudio.seek(targetAudioTime); + this.currentTime = targetTime; + console.log('在當前音頻內跳轉到:', targetAudioTime, '秒'); + + // 如果拖動前正在播放,則恢復播放 + if (shouldResume) { + this.currentAudio.play(); + this.isPlaying = true; + console.log('恢復播放狀態'); + } + } + } + }, + + // 等待音頻實例準備就緒 + waitForAudioReady(callback, maxAttempts = 10, currentAttempt = 0) { + if (currentAttempt >= maxAttempts) { + console.error('音頻實例準備超時'); + return; + } + + if (this.currentAudio && this.currentAudio.src) { + // 音頻實例已準備好 + setTimeout(callback, 50); // 稍微延遲確保完全準備好 + } else { + // 繼續等待 + setTimeout(() => { + this.waitForAudioReady(callback, maxAttempts, currentAttempt + 1); + }, 100); + } + }, + + // 初始檢測播放速度支持(不依賴音頻實例) + checkInitialPlaybackRateSupport() { + try { + const systemInfo = uni.getSystemInfoSync(); + console.log('初始檢測 - 系統信息:', systemInfo); + + // 檢查基礎庫版本 - playbackRate需要2.11.0及以上 + const SDKVersion = systemInfo.SDKVersion || '0.0.0'; + const versionArray = SDKVersion.split('.').map(v => parseInt(v)); + const isVersionSupported = versionArray[0] > 2 || + (versionArray[0] === 2 && versionArray[1] > 11) || + (versionArray[0] === 2 && versionArray[1] === 11 && versionArray[2] >= 0); + + if (!isVersionSupported) { + this.playbackRateSupported = false; + console.log(`初始檢測 - 基礎庫版本過低 (${SDKVersion}),需要2.11.0及以上才支持播放速度控制`); + return; + } + + // Android 6以下版本不支持 + if (systemInfo.platform === 'android') { + const androidVersion = systemInfo.system.match(/Android (\d+)/); + if (androidVersion && parseInt(androidVersion[1]) < 6) { + this.playbackRateSupported = false; + console.log('初始檢測 - Android版本過低,需要Android 6及以上才支持播放速度控制'); + return; + } + } + + // 檢查微信原生API是否可用 + if (typeof wx === 'undefined' || !wx.createInnerAudioContext) { + console.log('初始檢測 - 微信原生API不可用,可能影響playbackRate支持'); + } else { + console.log('初始檢測 - 微信原生API可用'); + } + + // 如果通過基本檢測,暫時設為支持,等音頻實例創建後再詳細檢測 + this.playbackRateSupported = true; + console.log('初始檢測 - 基本條件滿足,等待音頻實例檢測'); + + } catch (error) { + console.error('初始檢測播放速度支持時出錯:', error); + this.playbackRateSupported = false; + } + }, + + // 檢查播放速度控制支持 + checkPlaybackRateSupport(audio) { + try { + // 檢查基礎庫版本和平台支持 + const systemInfo = uni.getSystemInfoSync(); + console.log('系統信息:', systemInfo); + console.log('平台:', systemInfo.platform); + console.log('基礎庫版本:', systemInfo.SDKVersion); + console.log('系統版本:', systemInfo.system); + + // 根據uni-app文檔,微信小程序需要基礎庫2.11.0+才支持playbackRate + const SDKVersion = systemInfo.SDKVersion || '0.0.0'; + const versionArray = SDKVersion.split('.').map(v => parseInt(v)); + const isVersionSupported = versionArray[0] > 2 || + (versionArray[0] === 2 && versionArray[1] > 11) || + (versionArray[0] === 2 && versionArray[1] === 11 && versionArray[2] >= 0); + + console.log('基礎庫版本檢查:', { + version: SDKVersion, + parsed: versionArray, + supported: isVersionSupported + }); + + if (!isVersionSupported) { + this.playbackRateSupported = false; + console.log(`❌ 基礎庫版本不支持 (${SDKVersion}),微信小程序需要2.11.0+才支持playbackRate`); + return; + } + + // Android平台需要6.0+版本支持 + if (systemInfo.platform === 'android') { + const androidVersion = systemInfo.system.match(/Android (\d+)/); + console.log('Android版本檢查:', androidVersion); + if (androidVersion && parseInt(androidVersion[1]) < 6) { + this.playbackRateSupported = false; + console.log(`❌ Android版本不支持 (${androidVersion[1]}),需要Android 6+才支持playbackRate`); + return; + } + } + + // 檢查音頻實例是否支持playbackRate + console.log('🔍 音頻實例檢查:'); + console.log('- playbackRate初始值:', audio.playbackRate); + console.log('- playbackRate類型:', typeof audio.playbackRate); + + // 嘗試設置並檢測是否真正支持 + const testRate = 1.25; + const originalRate = audio.playbackRate || 1.0; + + try { + audio.playbackRate = testRate; + console.log('- 設置測試速度:', testRate); + console.log('- 設置後的值:', audio.playbackRate); + + // 檢查設置是否生效 + if (Math.abs(audio.playbackRate - testRate) < 0.01) { + this.playbackRateSupported = true; + console.log('✅ playbackRate功能正常'); + } else { + this.playbackRateSupported = false; + console.log('❌ playbackRate設置無效,可能不被支持'); + } + + } catch (error) { + this.playbackRateSupported = false; + console.log('❌ playbackRate設置出錯:', error); + } + + } catch (error) { + console.error('檢查播放速度支持時出錯:', error); + this.playbackRateSupported = false; } }, @@ -776,8 +1380,12 @@ export default { } if (this.currentPage > 1) { - // 停止当前音频播放 + // 停止当前音频播放並銷毀實例 this.pauseAudio(); + if (this.currentAudio) { + this.currentAudio.destroy(); + this.currentAudio = null; + } this.currentPage--; // 获取对应页面的数据(如果还没有获取过) @@ -796,8 +1404,12 @@ export default { } if (this.currentPage < this.bookPages.length) { - // 停止当前音频播放 + // 停止当前音频播放並銷毀實例 this.pauseAudio(); + if (this.currentAudio) { + this.currentAudio.destroy(); + this.currentAudio = null; + } this.currentPage++; // 获取对应页面的数据(如果还没有获取过) @@ -824,7 +1436,7 @@ export default { console.log('解锁全书') // 这里可以跳转到会员页面或者调用解锁接口 uni.navigateTo({ - url: '/pages/index/member' + url: '/subPages/member/recharge' }) }, async goToPage(page) { @@ -833,8 +1445,12 @@ export default { return; } - // 停止当前音频播放 + // 停止当前音频播放並銷毀實例 this.pauseAudio(); + if (this.currentAudio) { + this.currentAudio.destroy(); + this.currentAudio = null; + } this.currentPage = page console.log('跳转到页面:', page) @@ -852,8 +1468,12 @@ export default { return; } - // 停止当前音频播放 + // 停止当前音频播放並銷毀實例 this.pauseAudio(); + if (this.currentAudio) { + this.currentAudio.destroy(); + this.currentAudio = null; + } this.currentPage = e.detail.current + 1 // 获取对应页面的数据(如果还没有获取过) @@ -874,6 +1494,10 @@ export default { this.bookPages = this.courseIdList.map(() => []) // 初始化标题数组 this.pageTitles = this.courseIdList.map(() => '') + // 初始化页面类型数组 + this.pageTypes = this.courseIdList.map(() => '') + // 初始化页面单词数组 + this.pageWords = this.courseIdList.map(() => []) // 重置音频状态 this.resetAudioState() // 初始化第一页 @@ -888,11 +1512,18 @@ export default { }) if (res.code === 200) { // 使用$set确保响应式更新 + console.log('获取到的页面数据:', JSON.parse(res.result.content)) // 确保当前页面存在 if (this.currentPage - 1 < this.bookPages.length) { this.$set(this.bookPages, this.currentPage - 1, JSON.parse(res.result.content)) // 保存页面标题 this.$set(this.pageTitles, this.currentPage - 1, res.result.title || '') + // 保存页面类型 + this.$set(this.pageTypes, this.currentPage - 1, res.result.type || '') + // 保存页面单词释义数据 + this.$set(this.pageWords, this.currentPage - 1, res.result.words || []) + // 保存页面付费状态 + this.$set(this.pagePay, this.currentPage - 1, res.result.pay || 'N') } } }, @@ -916,6 +1547,12 @@ export default { this.audioCache = {}; console.log('音频缓存已清理'); }, + + // 清理單詞語音緩存 + clearWordAudioCache() { + this.wordAudioCache = {}; + console.log('單詞語音緩存已清理'); + }, // 限制缓存大小,保留最近访问的页面 limitCacheSize(maxSize = 10) { @@ -928,23 +1565,92 @@ export default { }); console.log('缓存大小已限制,删除了', keysToDelete.length, '个缓存项'); } + }, + // 自動加載第一頁音頻並播放 + async autoLoadAndPlayFirstPage() { + try { + console.log('開始自動加載第一頁音頻'); + + // 確保當前是第一頁且是文字頁面 + if (this.currentPage === 1 && this.isTextPage) { + console.log('當前是第一頁文字頁面,開始加載音頻'); + + // 加載音頻 + await this.getCurrentPageAudio(); + + // 檢查是否成功加載音頻 + if (this.currentPageAudios && this.currentPageAudios.length > 0) { + console.log('音頻加載成功,開始自動播放'); + + // 稍微延遲一下確保音頻完全準備好 + setTimeout(() => { + this.playAudio(); + }, 500); + } else { + console.log('第一頁沒有音頻數據'); + } + } else { + console.log('當前頁面不是第一頁文字頁面,跳過自動播放'); + } + } catch (error) { + console.error('自動加載和播放音頻失敗:', error); } }, + }, async onLoad(args) { uni.$on('selectVoice', (voiceId) => { - // console.log('收到音色选择:', voiceId); + console.log('音色切換:', this.voiceId, '->', voiceId); + this.voiceId = voiceId; + + // 音色切換時清除所有音頻緩存,因為不同音色的音頻文件不同 + this.clearAudioCache(); + this.clearWordAudioCache(); + + // 停止當前播放的音頻 + if (this.currentAudio) { + this.currentAudio.destroy(); + this.currentAudio = null; + } + if (this.currentWordAudio) { + this.currentWordAudio.destroy(); + this.currentWordAudio = null; + } + + // 重置音頻狀態 + this.currentPageAudios = []; + this.totalTime = 0; + this.hasAudioData = false; + this.isPlaying = false; + this.currentTime = 0; + this.currentAudioIndex = 0; + this.currentHighlightIndex = -1; + this.sliderValue = 0; + this.isDragging = false; - this.voiceId = voiceId + // 重新請求當前頁面的音頻數據 + if (this.isTextPage) { + console.log('音色切換後重新獲取音頻數據'); + this.getAudio(); + } }) this.courseId = args.courseId + this.memberId = args.memberId // 重置音频状态 this.resetAudioState() + + // 初始檢測播放速度支持 + this.checkInitialPlaybackRateSupport() + // 先获取点进来的课程的页面列表 - await Promise.all([this.getCourseList(this.courseId), this.getCoursePageList(args.bookId)]) - await this.getVoiceList() + await Promise.all([this.getCourseList(this.courseId), this.getCoursePageList(args.bookId), this.getMemberInfo(), this.getVoiceList()]) + + // 頁面加載完成後,自動加載第一頁音頻並播放 + await this.autoLoadAndPlayFirstPage(); }, + + // 页面卸载时清理音频资源 onUnload() { uni.$off('selectVoice') @@ -952,9 +1658,16 @@ export default { this.currentAudio.destroy(); this.currentAudio = null; } + // 清理單詞語音資源 + if (this.currentWordAudio) { + this.currentWordAudio.destroy(); + this.currentWordAudio = null; + } this.isPlaying = false; // 清理音频缓存 this.clearAudioCache(); + // 清理單詞語音緩存 + this.clearWordAudioCache(); }, // 页面隐藏时暂停音频 @@ -1089,6 +1802,33 @@ export default { color: #3B3D3D; // margin-bottom: 16rpx; } + + .english-text-container { + display: flex; + flex-wrap: wrap; + align-items: baseline; + } + + .english-token { + font-family: PingFang SC; + font-weight: 600; + font-size: 32rpx; + line-height: 48rpx; + color: #3B3D3D; + margin-right: 10rpx; + } + + .clickable-word { + background: $primary-color; + text-decoration: underline; + cursor: pointer; + transition: all 0.2s ease; + } + + .clickable-word:hover { + background-color: rgba(0, 122, 255, 0.1); + border-radius: 4rpx; + } .chinese-text { display: block; @@ -1433,6 +2173,20 @@ export default { .phonetic-container{ display: flex; gap: 24rpx; + align-items: center; + + .speaker-icon { + cursor: pointer; + transition: opacity 0.2s ease; + padding: 8rpx; + border-radius: 8rpx; + + &:hover { + opacity: 0.7; + background-color: rgba(0, 122, 255, 0.1); + } + } + .phonetic-text { font-family: PingFang SC; font-size: 28rpx; @@ -1565,6 +2319,11 @@ export default { gap: 8rpx; } +.control-btn.disabled { + pointer-events: none; + opacity: 0.6; +} + .control-text { font-size: 28rpx; color: #4A4A4A; diff --git a/subPages/home/directory.vue b/subPages/home/directory.vue index 89f171b..2adad73 100644 --- a/subPages/home/directory.vue +++ b/subPages/home/directory.vue @@ -152,7 +152,7 @@ export default { startLearning(id) { // 默认学第一堂课 uni.navigateTo({ - url: '/subPages/home/book?courseId=' + id + '&bookId=' + this.id + url: '/subPages/home/book?courseId=' + id + '&bookId=' + this.id + '&memberId=' + this.bookInfo.vip }) }, // 获取书籍详情 diff --git a/subPages/home/music.vue b/subPages/home/music.vue index efb447b..821b5f8 100644 --- a/subPages/home/music.vue +++ b/subPages/home/music.vue @@ -5,15 +5,15 @@ @@ -23,7 +23,7 @@ {{ voice.name }} {{ voice.info }} - + 已选择 @@ -82,7 +82,9 @@ export default { } }, onLoad(options) { - this.selectedVoiceId = options.voiceId + this.selectedVoiceId = Number(options.voiceId) + console.log( 'options.voiceId', options.voiceId ); + this.getVoice() } } diff --git a/subPages/login/login.vue b/subPages/login/login.vue index d4b18cf..1a96432 100644 --- a/subPages/login/login.vue +++ b/subPages/login/login.vue @@ -7,7 +7,7 @@ - + 展品维保报修小程序 @@ -40,19 +40,19 @@ - + - + - + - + @@ -71,19 +71,17 @@ export default { // 授权登录 handleLogin() { - // uni.redirectTo({ url: '/subPages/login/userInfo' }) - // return - if (!this.isAgreed) { this.$refs.serviceModal.open(); this.$refs.guideModal.open(); return } + uni.login({ provider: 'weixin', success: async (res) => { console.log('登录成功', res); - const { result: loginRes} = await this.$api.login.login({ + const { result: loginRes } = await this.$api.login.login({ code: res.code })