- <template>
 -     <view class="book-container">
 -         <!-- 条件编译 -->
 - 
 -         <!-- #ifndef H5 -->
 -         <uv-status-bar></uv-status-bar>
 -         <!-- 自定义顶部导航栏 -->
 -         <view class="custom-navbar" :class="{ 'navbar-hidden': !showNavbar }">
 -             <uv-status-bar></uv-status-bar>
 -             <view class="navbar-content">
 -                 <view class="navbar-left" @click="goBack">
 -                     <uv-icon name="arrow-left" size="20" color="#262626"></uv-icon>
 -                 </view>
 -                 <view class="navbar-title">{{ currentPageTitle }}</view>
 -             </view>
 -         </view>
 -         <!-- #endif -->
 - 
 -         <!-- Swiper内容区域 -->
 -         <swiper class="content-swiper" :current="currentPage - 1" @change="onSwiperChange">
 -             <swiper-item v-for="(page, index) in bookPages" :key="index" class="swiper-item">
 -                 <scroll-view scroll-y :scroll-top="scrollTops[index] || 0" :scroll-with-animation="true"
 -                     style="height: 100vh;" class="scroll-container" @scroll="onScroll" @touchstart="onTouchStart"
 -                     @touchmove="onTouchMove" @touchend="onTouchEnd">
 -                     <view class="content-area" @click="toggleNavbar">
 -                         <view class="title">{{ currentPageTitle }}</view>
 -                         <!-- 会员限制页面 -->
 -                         <view v-if="!isMember && pagePay[index] === 'Y' && userInfo.freeUser != 'Y'" class="member-content">
 -                             <text class="member-title">{{ pageTitles[index] }}</text>
 -                             <view class="member-button" @click.stop="unlockBook">
 -                                 <text class="member-button-text">升级会员解锁</text>
 -                             </view>
 -                         </view>
 - 
 -                         <!-- 图片卡片页面 -->
 -                         <view class="card-content" v-else-if="pageTypes[index] === '1'">
 -                             <view class="card-line">
 -                                 <image :src="configParamContent('highlight_icon')" class="card-line-image"
 -                                     mode="aspectFill" />
 -                                 <text class="card-line-text">划线重点</text>
 -                             </view>
 -                             <view v-for="(item, itemIndex) in page" :key="itemIndex" class="text-content">
 -                                 <image class="card-image" v-if="item && item.type === 'image'" :src="item.imageUrl"
 -                                     mode="widthFix"></image>
 -                                 <!-- <view :class="['english-text-container', 'clickable-text', { 'lead-text': isCardTextHighlighted(page, itemIndex) }]" v-else-if="item && item.type === 'text' && item.language === 'en' && item.content" @click.stop="handleTextClick(item.content, item, index)" >
 -                   <text 
 -                     v-for="(token, tokenIndex) in splitEnglishSentence(item.content)" 
 -                     :key="tokenIndex"
 -                     :class="['english-token', { 'clickable-word': token.isWord && findWordDefinition(token.text) }]"
 -                     @click.stop="token.isWord && findWordDefinition(token.text) ? handleWordClick(token.text) : null"
 -                     user-select
 -                     :style="item.style"
 -                   >{{ token.text }}</text>
 -                 </view> -->
 -                                 <!-- <view :class="{ 'lead-text': isCardTextHighlighted(page, itemIndex) }" v-else-if="item && item.type === 'text' && item.language === 'zh' && item.content" @click.stop="handleTextClick(item.content, item, index)"> -->
 -                                 <view :class="{ 
 -                                     'lead-text': isCardTextHighlighted(page, itemIndex),
 -                                     'introduction-text' : item.isLead,
 -                                 }"
 -                                     v-else-if="item && item.type === 'text' && item.content"
 -                                     @click.stop="handleTextClick(item.content, item, index)">
 -                                     <text v-for="(segment, segmentIndex) in processChineseText(item.content)"
 -                                         :key="segmentIndex"
 -                                         :class="['chinese-segment', { 'clickable-keyword': segment.isKeyword }]"
 -                                         @click.stop="segment.isKeyword ? handleChineseKeywordClick(segment.keywordData) : handleTextClick(item.content, item, index)"
 -                                         user-select :style="item.style" :id="`text-segment-${segmentIndex}`">{{
 -                                         segment.text }}</text>
 -                                 </view>
 -                             </view>
 -                         </view>
 - 
 -                         <view v-else>
 -                             <view v-for="(item, itemIndex) in page" :key="itemIndex">
 -                                 <!-- 文本页面 -->
 -                                 <view v-if="item && item.type === 'text' && item.content" class="text-content">
 -                                     <view :class="{ 
 -                                         'lead-text': isTextHighlighted(page, itemIndex),
 -                                         'introduction-text' : item.isLead,
 -                                     }"
 -                                         @click.stop="handleTextClick(item.content, item, index)"
 -                                         :ref="`textRef_${index}_${itemIndex}`" :id="`text-${itemIndex}`">
 -                                         <text class="content-text clickable-text"
 -                                             :style="item.style" user-select>
 -                                             {{ item.content }}
 -                                         </text>
 -                                     </view>
 -                                 </view>
 - 
 -                                 <!-- 图片页面 -->
 -                                 <view v-else-if="item.type === 'image'" class="image-container"
 -                                     :ref="`imageRef_${index}_${itemIndex}`">
 -                                     <image class="content-image" :src="item.imageUrl" mode="widthFix"></image>
 -                                 </view>
 - 
 -                                 <!-- 视频页面 -->
 -                                 <view v-else-if="item.type === 'video'" class="video-content" @click.stop>
 -                                     <!-- 视频加载状态 -->
 -                                     <view v-if="videoLoading" class="video-loading">
 -                                         <text class="loading-text">视频加载中...</text>
 -                                     </view>
 - 
 -                                     <!-- 视频播放器 -->
 -                                     <video v-else :src="item.url" class="video-player" controls :poster="item.coverUrl"
 -                                         @loadstart="onVideoLoadStart" @loadeddata="onVideoLoadStart"
 -                                         @error="onVideoError"></video>
 -                                 </view>
 -                             </view>
 -                         </view>
 -                     </view>
 -                 </scroll-view>
 -             </swiper-item>
 -         </swiper>
 - 
 -         <!-- 自定义底部控制栏 -->
 -         <CustomTabbar :show-navbar="showNavbar" :current-page="currentPage" :course-id="courseId" :voice-id="voiceId"
 -             :book-pages="bookPages" :is-text-page="isTextPage" :should-load-audio="shouldLoadAudio"
 -             :is-member="isMember" :current-page-requires-member="currentPageRequiresMember" :page-pay="pagePay"
 -             :is-word-audio-playing="isWordAudioPlaying" @toggle-course-popup="toggleCoursePopup"
 -             @toggle-sound="toggleSound" @go-to-page="goToPage" @previous-page="previousPage" @next-page="nextPage"
 -             @audio-state-change="onAudioStateChange" @highlight-change="onHighlightChange"
 -             @scroll-to-text="onScrollToText" @voice-change-complete="onVoiceChangeComplete"
 -             @voice-change-error="onVoiceChangeError" @page-data-needed="onPageDataNeeded" ref="customTabbar" />
 - 
 -         <!-- 课程选择弹出窗 -->
 -         <CoursePopup :style="{ zIndex: 10000 }" :course-list="courseList" :current-course="currentCourse"
 -             :is-reversed="isReversed" @toggle-sort="toggleSort" @select-course="selectCourse" ref="coursePopup" />
 - 
 -         <!-- 释义弹出窗 -->
 -         <MeaningPopup :style="{ zIndex: 10000 }" :current-word-meaning="currentWordMeaning"
 -             @close-meaning-popup="closeMeaningPopup" @repeat-word-audio="repeatWordAudio" ref="meaningPopup" />
 - 
 -         <!-- 悬浮按钮组件 -->
 -         <FloatingButtons 
 -             :is-last-page="isLastPage" 
 -             :has-next-course="hasNextCourse"
 -             @next-course="goToNextCourse"
 -             @back-to-start="backToStart"
 -         />
 -     </view>
 - </template>
 - 
 - <script>
 - import AudioControls from './AudioControls.vue'
 - import CustomTabbar from './components/CustomTabbar.vue'
 - import CoursePopup from './components/CoursePopup.vue'
 - import MeaningPopup from './components/MeaningPopup.vue'
 - import FloatingButtons from './components/FloatingButtons.vue'
 - import audioManager from '@/utils/audioManager.js'
 - 
 - export default {
 -     components: {
 -         AudioControls,
 -         CustomTabbar,
 -         CoursePopup,
 -         MeaningPopup,
 -         FloatingButtons
 -     },
 -     data() {
 -         return {
 -             isMember: false,
 -             memberId: '',
 -             voiceId: null,
 -             courseId: '',
 -             showNavbar: true,
 -             currentPage: 1, // 当前页面索引
 -             currentCourse: 1, // 当前课程索引
 -             currentWordMeaning: null, // 当前显示的单词释义
 -             isReversed: false, // 是否倒序显示
 -             // 文本高亮相关 - 由AudioControls组件管理,这里只保留必要的接口
 -             currentHighlightIndex: -1, // 当前高亮的文本索引,用于模板渲染
 -             wordAudioCache: {}, // 單詞語音緩存
 -             // 注意:音频实例现在由audioManager统一管理,不再在组件中维护
 -             isWordAudioPlaying: false, // 是否有单词音频正在播放
 - 
 -             // 音频状态相关 - 这些状态现在由AudioControls组件管理
 -             // 保留这些属性用于与AudioControls组件的数据同步
 -             isAudioLoading: false, // 音频是否正在加载
 -             hasAudioData: false, // 是否有音频数据
 -             audioLoadFailed: false, // 音频加载是否失败
 - 
 -             // 视频状态相关
 -             videoLoading: false, // 视频是否正在加载
 - 
 -             // 滚动相关
 -             scrollTops: [], // 每个页面的scroll-view滚动位置数组
 -             scrollDebounceTimer: null, // 滚动防抖定时器
 -             isScrolling: false, // 是否正在滚动中
 - 
 -             // 手动滚动检测相关
 -             isUserTouching: false, // 用户是否正在触摸屏幕
 -             touchStartTime: 0, // 触摸开始时间
 -             touchStartY: 0, // 触摸开始Y坐标
 -             userScrollTimer: null, // 用户滚动检测定时器
 -             lastUserScrollTime: 0, // 最后一次用户滚动时间
 -             courseIdList: [],
 -             bookTitle: '',
 -             courseList: [
 - 
 -             ],
 - 
 -             // 二维数组 代表每个页面
 -             bookPages: [
 - 
 -             ],
 -             // 存储每个页面的标题
 -             pageTitles: [],
 -             // 存储每个页面的type信息
 -             pageTypes: [],
 -             // 存储每个页面的单词释义数据
 -             pageWords: [],
 -             // 存储每个页面的付费状态
 -             pagePay: [],
 -         }
 -     },
 -     onShow() {
 -         if (uni.getStorageSync('token')) {
 -             this.$store.dispatch('getUserInfo');
 -         }
 -     },
 -     computed: {
 -         displayCourseList() {
 -             return this.isReversed ? [...this.courseList].reverse() : this.courseList;
 -         },
 -         
 -         // 从Vuex获取音色列表
 -         voiceList() {
 -             return this.$store.state.voiceList;
 -         },
 -         
 -         // 从Vuex获取默认音色ID
 -         defaultVoiceId() {
 -             return this.$store.state.defaultVoiceId;
 -         },
 -         
 -         // 判断当前页面是否为文字类型
 -         isTextPage() {
 -             // 如果是卡片页面(type为'1'),不显示音频控制栏
 -             if (this.currentPageType === '1') {
 -                 return true;
 -             }
 - 
 -             const currentPageData = this.bookPages[this.currentPage - 1];
 -             // currentPageData是一个数组 其中的一个元素的type是text就会返回true
 -             return currentPageData && currentPageData.some(item => item.type === 'text');
 -         },
 - 
 -         // 判断当前页面是否需要加载音频(包括文本页面和卡片页面)
 -         shouldLoadAudio() {
 -             // 文本页面需要加载音频
 -             if (this.isTextPage) {
 -                 return true;
 -             }
 - 
 -             // 卡片页面(type为'1')也需要加载音频以支持点击播放
 -             if (this.currentPageType === '1') {
 -                 return true;
 -             }
 - 
 -             return false;
 -         },
 - 
 - 
 -         // 动态页面标题
 -         currentPageTitle() {
 -             return this.pageTitles[this.currentPage - 1] || this.bookTitle;
 -         },
 - 
 -         // 当前页面类型
 -         currentPageType() {
 -             return this.pageTypes[this.currentPage - 1] || '';
 -         },
 - 
 -         // 当前页面的单词释义数据
 -         currentPageWords() {
 -             return this.pageWords[this.currentPage - 1] || [];
 -         },
 - 
 -         // 当前页面是否需要会员
 -         currentPageRequiresMember() {
 -             // 免费用户不受会员限制
 -             if (this.userInfo && this.userInfo.freeUser === 'Y') {
 -                 return false;
 -             }
 -             return this.pagePay[this.currentPage - 1] === 'Y';
 -         },
 - 
 -         // 判断是否为当前课程的最后一页
 -         isLastPage() {
 -             return this.currentPage === this.bookPages.length;
 -         },
 - 
 -         // 判断是否有下一课
 -         hasNextCourse() {
 -             if (!this.courseList || this.courseList.length === 0) return false;
 -             // 使用 courseId 而不是 currentCourse,因为 courseId 是当前正在学习的课程ID
 -             const currentCourseIndex = this.courseList.findIndex(course => course.id == this.courseId);
 -             return currentCourseIndex >= 0 && currentCourseIndex < this.courseList.length - 1;
 -         }
 -     },
 -     // watch: {
 -     //   scrollTops: {
 -     //     handler(newVal, oldVal) {
 -     //       console.log('📊 scrollTops变化:', {
 -     //         currentPage: this.currentPage,
 -     //         newScrollTops: newVal,
 -     //         currentPageScrollTop: newVal[this.currentPage - 1]
 -     //       });
 -     //     },
 -     //     deep: true
 -     //   }
 -     // },
 -     methods: {
 -         // 触摸开始事件 - 检测用户开始触摸
 -         onTouchStart(e) {
 -             this.isUserTouching = true;
 -             this.touchStartTime = Date.now();
 -             this.touchStartY = e.touches[0].pageY;
 - 
 -             // 清除之前的用户滚动定时器
 -             if (this.userScrollTimer) {
 -                 clearTimeout(this.userScrollTimer);
 -                 this.userScrollTimer = null;
 -             }
 - 
 -             console.log('👆 用户开始触摸屏幕');
 -         },
 - 
 -         // 触摸移动事件 - 检测用户滚动操作
 -         onTouchMove(e) {
 -             if (!this.isUserTouching) return;
 - 
 -             const currentY = e.touches[0].pageY;
 -             const deltaY = Math.abs(currentY - this.touchStartY);
 - 
 -             // 如果移动距离超过阈值,认为是滚动操作
 -             if (deltaY > 10) {
 -                 // 记录用户滚动时间
 -                 this.lastUserScrollTime = Date.now();
 -                 
 -                 // 如果当前正在自动滚动,立即停止
 -                 if (this.isScrolling) {
 -                     console.log('🛑 检测到用户手动滚动,停止自动滚动');
 -                     this.isScrolling = false;
 - 
 -                     // 清除滚动防抖定时器
 -                     if (this.scrollDebounceTimer) {
 -                         clearTimeout(this.scrollDebounceTimer);
 -                         this.scrollDebounceTimer = null;
 -                     }
 -                 }
 -             }
 -         },
 - 
 -         // 触摸结束事件 - 用户停止触摸
 -         onTouchEnd(e) {
 -             this.isUserTouching = false;
 - 
 -             // 设置一个短暂的延迟,在用户停止触摸后的一段时间内仍然阻止自动滚动
 -             // 这样可以避免用户刚停止滚动就立即触发自动滚动
 -             this.userScrollTimer = setTimeout(() => {
 -                 console.log('✋ 用户滚动操作结束,允许自动滚动');
 -                 this.userScrollTimer = null;
 -             }, 500); // 减少到500ms,提高响应性
 - 
 -             console.log('👆 用户停止触摸屏幕');
 -         },
 - 
 -         // 检查是否应该阻止自动滚动
 -         shouldPreventAutoScroll() {
 -             // 降低敏感度:只有在用户正在触摸且最近有滚动行为时才阻止
 -             const now = Date.now();
 -             const recentUserScroll = this.userScrollTimer !== null && (now - this.lastUserScrollTime) < 1000;
 -             return this.isUserTouching && recentUserScroll;
 -         },
 - 
 -         // 处理scroll-view滚动事件
 -         onScroll(e) {
 -             // 更新当前页面的滚动位置
 -             const scrollTop = e.detail.scrollTop;
 -             const currentPageIndex = this.currentPage - 1;
 -             const previousScrollTop = this.scrollTops[currentPageIndex] || 0;
 - 
 -             // 只有当滚动位置发生显著变化时才更新
 -             if (Math.abs(previousScrollTop - scrollTop) > 5) {
 -                 // 检测是否为手动滚动(如果正在自动滚动中,但滚动位置与预期不符,则认为是手动滚动)
 -                 if (this.isScrolling) {
 -                     // 提高手动滚动检测阈值,减少误判
 -                     const scrollDifference = Math.abs(previousScrollTop - scrollTop);
 -                     if (scrollDifference > 80) { // 从50提高到80,减少误判
 -                         console.log('🖐️ 检测到手动滚动,中断自动滚动状态');
 -                         this.isScrolling = false;
 -                         this.lastUserScrollTime = Date.now(); // 记录手动滚动时间
 -                     }
 -                 }
 - 
 -                 this.$set(this.scrollTops, currentPageIndex, scrollTop);
 -             }
 -         },
 - 
 -         // 视频事件处理方法
 -         onVideoLoadStart() {
 - 
 - 
 -             this.videoLoading = true;
 -         },
 - 
 -         onVideoCanPlay() {
 - 
 - 
 -             this.videoLoading = false;
 - 
 -         },
 - 
 -         onVideoError() {
 -             this.videoLoading = false;
 -             uni.showToast({
 -                 title: '视频加载失败',
 -                 icon: 'none',
 -                 duration: 2000
 -             });
 -         },
 - 
 -         // 獲取用戶會員信息 判斷是否和傳參傳過來的會員id相同
 -         async getMemberInfo() {
 -             // 检查是否为免费用户
 -             if (this.userInfo && this.userInfo.freeUser === 'Y') {
 -                 this.isMember = true; // 免费用户享有会员权限
 -                 return;
 -             }
 - 
 -             const memberRes = await this.$api.member.getUserMemberInfo()
 -             if (memberRes.code === 200) {
 -                 this.isMember = memberRes.result.map(item => item.memberId).includes(this.memberId)
 -             }
 -         },
 - 
 -         // 处理AudioControls组件的事件
 -         onAudioStateChange(audioState) {
 -             // 更新高亮状态
 -             this.currentHighlightIndex = audioState.currentHighlightIndex;
 - 
 -             // 更新音频加载状态(用于控制UI显示)
 -             if (audioState.hasOwnProperty('isLoading')) {
 -                 this.isAudioLoading = audioState.isLoading;
 -             }
 - 
 -             // 更新音频数据状态
 -             if (audioState.hasOwnProperty('hasAudioData')) {
 -                 this.hasAudioData = audioState.hasAudioData;
 -             }
 - 
 -             // 更新音频加载失败状态
 -             if (audioState.hasOwnProperty('audioLoadFailed')) {
 -                 this.audioLoadFailed = audioState.audioLoadFailed;
 -             }
 -         },
 - 
 -         // 处理页面数据需要重新加载的事件
 -         async onPageDataNeeded(pageNumber) {
 -             console.log('收到页面数据需要重新加载的请求,页面:', pageNumber);
 - 
 -             // 如果页面数据不存在或为空,重新获取
 -             if (!this.bookPages || this.bookPages.length === 0 || !this.bookPages[pageNumber - 1]) {
 -                 console.log('页面数据不存在,重新获取页面数据');
 -                 try {
 -                     await this.getBookPages();
 -                     console.log('页面数据重新获取完成');
 - 
 -                     // 页面数据更新后,AudioControls组件的bookPages监听器会自动触发音频获取
 -                     // 无需手动调用getCurrentPageAudio,避免重复调用
 -                 } catch (error) {
 -                     console.error('重新获取页面数据失败:', error);
 -                 }
 -             } else {
 -                 console.log('页面数据已存在,无需重新获取');
 -             }
 -         },
 - 
 -         // 处理音色切换完成事件
 -         onVoiceChangeComplete(data) {
 - 
 -             // 可以在这里添加一些UI反馈,比如显示切换成功的提示
 -             if (data.hasAudioData) {
 - 
 -             } else {
 - 
 -             }
 - 
 -             // 如果启用了预加载所有页面
 -             if (data.preloadAllPages) {
 - 
 -                 // 可以显示一个提示,告诉用户正在后台加载
 -                 uni.showToast({
 -                     title: '正在加载新音色...',
 -                     icon: 'loading',
 -                     duration: 2000
 -                 });
 -             }
 -         },
 - 
 -         // 处理音色切换错误事件
 -         onVoiceChangeError(error) {
 -             console.error('音色切换失败:', error);
 -             // 可以在这里显示错误提示给用户
 -             uni.showToast({
 -                 title: '音色切换失败,请重试',
 -                 icon: 'none',
 -                 duration: 2000
 -             });
 -         },
 - 
 -         // 处理音频切换时的自动滚动
 -         // onScrollToText(refName) {
 -         //   try {
 -         //     console.log('🎯 onScrollToText 被调用:', refName);
 -         //     
 -         //     // 调用scrollTo插件
 -         //     this.$scrollTo(refName);
 -         //     
 -         //   } catch (error) {
 -         //     console.error('❌ onScrollToText 执行失败:', error);
 -         //   }
 -         // },
 - 
 -         // 处理文本点击事件
 -         handleTextClick(textContent, item, pageIndex) {
 -             // console.log('🎯 ===== 文本点击事件开始 =====');
 -             // console.log('📝 点击文本:', textContent);
 -             // console.log('📄 textContent类型:', typeof textContent);
 -             // console.log('❓ textContent是否为undefined:', textContent === undefined);
 -             // console.log('📦 完整item对象:', item);
 -             // console.log('📝 item.content:', item ? item.content : 'item为空');
 -             // console.log('📖 当前页面索引:', this.currentPage);
 -             // console.log('👆 点击的页面索引:', pageIndex);
 -             // console.log('📊 当前页面类型:', this.currentPageType);
 -             // console.log('📄 是否为文本页面:', this.isTextPage);
 -             // console.log('📋 当前页面数据:', this.bookPages[this.currentPage - 1]);
 -             // console.log('📏 页面数据长度:', this.bookPages[this.currentPage - 1] ? this.bookPages[this.currentPage - 1].length : '页面不存在');
 - 
 -             // 检查音频播放状态
 -             // console.log('🎵 ===== 音频状态检查 =====');
 -             // console.log('  isWordAudioPlaying:', this.isWordAudioPlaying);
 -             // console.log('  currentWordAudio存在:', !!this.currentWordAudio);
 -             // console.log('  currentWordMeaning存在:', !!this.currentWordMeaning);
 - 
 -             if (this.isWordAudioPlaying) {
 -                 // console.log('⚠️ 检测到单词音频正在播放状态,这可能会阻止句子音频播放');
 -                 // console.log('🔄 尝试重置音频播放状态...');
 -                 this.isWordAudioPlaying = false;
 -                 // console.log('✅ 音频播放状态已重置');
 -             }
 - 
 -             // 检查是否点击的是当前页面
 -             if (pageIndex !== undefined && pageIndex !== this.currentPage - 1) {
 -                 console.warn('⚠️ 点击的不是当前页面,忽略点击事件');
 -                 // console.log(`  期望页面: ${this.currentPage - 1}, 点击页面: ${pageIndex}`);
 -                 return;
 -             }
 - 
 -             // 验证参数有效性
 -             if (!item) {
 -                 console.error('❌ handleTextClick: item参数为空');
 -                 uni.showToast({
 -                     title: '数据错误,请刷新页面',
 -                     icon: 'none'
 -                 });
 -                 return;
 -             }
 - 
 -             // 如果textContent为undefined,尝试从item中获取
 -             if (!textContent && item && item.content) {
 -                 textContent = item.content;
 -             }
 - 
 -             // 最终验证textContent
 -             if (!textContent || typeof textContent !== 'string' || textContent.trim() === '') {
 -                 console.error('❌ handleTextClick: 无效的文本内容', textContent);
 -                 uni.showToast({
 -                     title: '文本内容无效',
 -                     icon: 'none'
 -                 });
 -                 return;
 -             }
 - 
 - 
 -             if (!this.$refs.customTabbar) {
 -                 console.error('❌ customTabbar引用不存在');
 -                 uni.showToast({
 -                     title: '音频控制组件未准备好',
 -                     icon: 'none'
 -                 });
 -                 return;
 -             }
 - 
 -             // console.log('  audioControls存在:', !!this.$refs.customTabbar.$refs.audioControls);
 - 
 -             if (!this.$refs.customTabbar.$refs.audioControls) {
 -                 console.error('❌ audioControls引用不存在');
 -                 uni.showToast({
 -                     title: '音频控制组件未准备好',
 -                     icon: 'none'
 -                 });
 -                 return;
 -             }
 - 
 -             // 检查当前页面是否为文本页面或卡片页面
 -             // 卡片页面(type为'1')现在也支持整句音频播放
 - 
 - 
 - 
 - 
 - 
 - 
 -             // 特别针对划线重点页面的调试
 -             if (this.currentPageType === '1') {
 - 
 -             }
 - 
 -             if (!this.isTextPage && this.currentPageType !== '1') {
 -                 console.warn('⚠️ 当前页面不是文本页面或卡片页面');
 - 
 -                 uni.showToast({
 -                     title: '当前页面不支持音频播放',
 -                     icon: 'none'
 -                 });
 -                 return;
 -             }
 - 
 - 
 - 
 -             // 获取音频控制组件实例
 -             const audioControls = this.$refs.customTabbar.$refs.audioControls;
 - 
 -             // 检查音频是否正在加载中
 -             if (audioControls.isAudioLoading) {
 - 
 -                 uni.showToast({
 -                     title: '音频正在加载中,请稍后再试',
 -                     icon: 'loading',
 -                     duration: 1500
 -                 });
 - 
 -                 // 等待音频加载完成后自动播放
 -                 const checkAndPlay = () => {
 -                     if (!audioControls.isAudioLoading && audioControls.currentPageAudios.length > 0) {
 - 
 -                         const success = audioControls.playSpecificAudio(textContent);
 -                         if (!success) {
 -                             console.error('❌ 音频加载完成后播放失败');
 -                         }
 -                     } else if (!audioControls.isAudioLoading) {
 -                         console.error('❌ 音频加载完成但没有音频数据');
 -                         uni.showToast({
 -                             title: '当前页面没有音频内容',
 -                             icon: 'none'
 -                         });
 -                     } else {
 -                         // 继续等待
 -                         setTimeout(checkAndPlay, 500);
 -                     }
 -                 };
 - 
 -                 // 延迟检查,给音频加载一些时间
 -                 setTimeout(checkAndPlay, 500);
 -                 return;
 -             }
 - 
 -             // 检查是否有音频数据
 -             if (!audioControls.currentPageAudios || audioControls.currentPageAudios.length === 0) {
 -                 console.warn('⚠️ 当前页面没有音频数据,尝试重新加载');
 -                 uni.showToast({
 -                     title: '正在重新加载音频...',
 -                     icon: 'loading'
 -                 });
 - 
 -                 // 尝试重新加载音频
 -                 audioControls.getCurrentPageAudio();
 - 
 -                 // 等待重新加载完成后播放
 -                 const retryPlay = () => {
 -                     if (!audioControls.isAudioLoading && audioControls.currentPageAudios.length > 0) {
 - 
 -                         const success = audioControls.playSpecificAudio(textContent);
 -                         if (!success) {
 -                             console.error('❌ 音频重新加载后播放失败');
 -                         }
 -                     } else if (!audioControls.isAudioLoading) {
 -                         console.error('❌ 音频重新加载失败');
 -                         uni.showToast({
 -                             title: '音频加载失败,请检查网络连接',
 -                             icon: 'none'
 -                         });
 -                     } else {
 -                         // 继续等待
 -                         setTimeout(retryPlay, 500);
 -                     }
 -                 };
 - 
 -                 setTimeout(retryPlay, 1500);
 -                 return;
 -             }
 - 
 -             // 调用AudioControls组件的播放指定音频方法
 - 
 -             const success = audioControls.playSpecificAudio(textContent);
 - 
 -             // console.log('🎵 playSpecificAudio 返回结果:', success);
 - 
 -             if (success) {
 -                 // console.log('✅ 成功播放指定音频段落');
 -             } else {
 -                 console.error('❌ 播放指定音频段落失败');
 -                 // console.log('💡 失败可能原因:');
 -                 // console.log('  1. 文本内容与音频数据不匹配');
 -                 // console.log('  2. 音频数据尚未加载完成');
 -                 // console.log('  3. 音频文件路径错误或文件损坏');
 -                 // console.log('  4. 网络连接问题');
 -             }
 - 
 -             // console.log('🎯 ===== 文本点击事件结束 =====');
 -         },
 - 
 -         onHighlightChange(highlightData) {
 -             // 兼容旧格式(直接传递索引)和新格式(传递对象)
 -             if (typeof highlightData === 'number') {
 -                 // 旧格式:直接是索引
 -                 this.currentHighlightIndex = highlightData;
 -             } else if (typeof highlightData === 'object' && highlightData !== null) {
 -                 // 新格式:包含详细信息的对象
 -                 this.currentHighlightIndex = highlightData.highlightIndex;
 - 
 -                 // 可以在这里处理分段音频的额外信息
 -                 if (highlightData.isSegmented) {
 -                     // console.log('分段音频高亮:', {
 -                     //   highlightIndex: highlightData.highlightIndex,
 -                     //   segmentIndex: highlightData.segmentIndex,
 -                     //   startIndex: highlightData.startIndex,
 -                     //   endIndex: highlightData.endIndex,
 -                     //   currentText: highlightData.currentText
 -                     // });
 -                 }
 -             } else {
 -                 // 清除高亮
 -                 this.currentHighlightIndex = -1;
 -             }
 -         },
 - 
 -         // 处理滚动到高亮文本
 -         onScrollToText(scrollData) {
 -             console.log('📍 收到滚动请求:', scrollData);
 -             
 -             // 检查是否应该阻止自动滚动(用户正在手动操作)
 -             if (this.shouldPreventAutoScroll()) {
 -                 console.log('🚫 用户正在手动滚动,跳过自动滚动到文本');
 -                 return;
 -             }
 - 
 -             // 防抖处理:如果正在滚动中,清除之前的定时器
 -             if (this.scrollDebounceTimer) {
 -                 clearTimeout(this.scrollDebounceTimer);
 -             }
 - 
 -             this.scrollDebounceTimer = setTimeout(() => {
 -                 // 再次检查是否应该阻止自动滚动
 -                 if (this.shouldPreventAutoScroll()) {
 -                     console.log('🚫 防抖延迟后检测到用户手动滚动,跳过自动滚动到文本');
 -                     return;
 -                 }
 -                 this.performScrollToText(scrollData);
 -             }, 50); // 减少防抖延迟,提高响应性
 -         },
 - 
 -         // 执行滚动到高亮文本的具体逻辑
 -         performScrollToText(scrollData) {
 -             // 最终检查:如果用户正在手动操作,直接返回
 -             if (this.shouldPreventAutoScroll()) {
 -                 console.log('🚫 执行滚动前检测到用户手动操作,取消自动滚动');
 -                 return;
 -             }
 - 
 -             // 确保在任何情况下都能重置滚动状态
 -             const resetScrollingState = () => {
 -                 this.isScrolling = false;
 -                 console.log('🔄 滚动状态已重置');
 -             };
 - 
 -             // 设置安全超时,确保状态不会永久卡住
 -             const safetyTimeout = setTimeout(() => {
 -                 if (this.isScrolling) {
 -                     console.warn('⚠️ 滚动状态安全超时,强制重置');
 -                     resetScrollingState();
 -                 }
 -             }, 2000); // 2秒安全超时
 - 
 -             if (!scrollData || typeof scrollData.highlightIndex !== 'number' || scrollData.highlightIndex < 0) {
 -                 console.warn('滚动数据无效:', scrollData);
 -                 clearTimeout(safetyTimeout);
 -                 return;
 -             }
 - 
 -             // 确保在当前页面
 -             if (scrollData.currentPage && scrollData.currentPage !== this.currentPage) {
 -                 console.warn('页面不匹配,跳过滚动:', {
 -                     scrollDataPage: scrollData.currentPage,
 -                     currentPage: this.currentPage
 -                 });
 -                 clearTimeout(safetyTimeout);
 -                 return;
 -             }
 - 
 -             // 如果正在滚动中,跳过本次滚动
 -             if (this.isScrolling) {
 -                 console.warn('正在滚动中,跳过本次滚动');
 -                 clearTimeout(safetyTimeout);
 -                 return;
 -             }
 - 
 -             // 构建元素选择器
 -             let selector = '';
 -             if (scrollData.isSegmented && typeof scrollData.segmentIndex === 'number') {
 -                 // 分段音频:使用分段索引
 -                 selector = `#text-segment-${scrollData.segmentIndex}`;
 -             } else {
 -                 // 普通音频:需要找到对应的文本元素
 -                 // originalTextIndex是指向原始页面数据中的索引,需要映射到实际的DOM元素
 -                 const targetItemIndex = this.findTextItemIndex(scrollData.highlightIndex);
 -                 if (targetItemIndex !== -1) {
 -                     selector = `#text-${targetItemIndex}`;
 -                 } else {
 -                     console.warn('无法找到对应的文本元素索引:', scrollData.highlightIndex);
 -                     selector = `#text-${scrollData.highlightIndex}`; // 备用方案
 -                 }
 -             }
 - 
 -             console.log('开始滚动到文本:', { selector, scrollData });
 - 
 -             // 标记正在滚动
 -             this.isScrolling = true;
 - 
 -             // 等待DOM更新后再查找元素
 -             this.$nextTick(async () => {
 -                 try {
 -                     // 获取所有元素的真实位置信息
 -                     const elementPositions = await this.getAllElementPositions();
 -                     console.log('📏 获取到的元素位置信息:', elementPositions);
 - 
 -                     // 计算精确的滚动位置
 -                     const preciseScrollTop = await this.calculatePreciseScrollPosition(scrollData, elementPositions);
 -                     
 -                     if (preciseScrollTop !== null) {
 -                         // 检查是否需要滚动(避免不必要的滚动)
 -                         const currentScroll = this.scrollTops[this.currentPage - 1] || 0;
 -                         const scrollDifference = Math.abs(preciseScrollTop - currentScroll);
 - 
 -                         // 提高滚动阈值,减少不必要的微小滚动
 -                         if (scrollDifference > 30) { // 从20提高到30
 -                             this.$set(this.scrollTops, this.currentPage - 1, preciseScrollTop);
 -                             console.log('✅ 使用精确位置滚动:', {
 -                                 selector,
 -                                 preciseScrollTop,
 -                                 currentPage: this.currentPage,
 -                                 scrollDifference
 -                             });
 - 
 -                             // 滚动完成后重置状态
 -                             setTimeout(() => {
 -                                 resetScrollingState();
 -                             }, 200);
 -                         } else {
 -                             resetScrollingState();
 -                             console.log('📍 目标已在最佳可视位置,无需滚动');
 -                         }
 -                     } else {
 -                         // 精确计算失败,使用原有的查询方法作为备用
 -                         console.log('🔄 精确计算失败,使用备用查询方法');
 -                         this.fallbackScrollToText(selector, resetScrollingState, safetyTimeout);
 -                     }
 -                 } catch (error) {
 -                     console.error('❌ 精确滚动计算失败:', error);
 -                     // 使用原有的查询方法作为备用
 -                     this.fallbackScrollToText(selector, resetScrollingState, safetyTimeout);
 -                 }
 -             });
 -         },
 - 
 -         // 备用滚动方法(原有的查询方式)
 -         fallbackScrollToText(selector, resetScrollingState, safetyTimeout) {
 -                 // 使用uni.createSelectorQuery获取元素位置
 -                 const query = uni.createSelectorQuery().in(this);
 - 
 -                 // 获取scroll-view容器的位置信息
 -                 query.select('.scroll-container').boundingClientRect();
 -                 // 获取目标元素的位置信息
 -                 query.select(selector).boundingClientRect();
 - 
 -                 query.exec((res) => {
 -                     // 清除安全超时
 -                     clearTimeout(safetyTimeout);
 - 
 -                     const scrollViewRect = res[0];
 -                     const targetRect = res[1];
 - 
 -                     console.log('查询结果:', {
 -                         scrollViewRect: scrollViewRect ? '找到' : '未找到',
 -                         targetRect: targetRect ? '找到' : '未找到',
 -                         selector
 -                     });
 - 
 -                     if (scrollViewRect && targetRect) {
 -                         // 计算目标元素相对于scroll-view的位置
 -                         const currentScrollTop = this.scrollTops[this.currentPage - 1] || 0;
 -                         const targetOffsetTop = targetRect.top - scrollViewRect.top + currentScrollTop;
 - 
 -                         // 计算滚动位置,让目标元素在屏幕上方1/4处(更好的阅读体验)
 -                         const screenHeight = uni.getSystemInfoSync().windowHeight;
 -                         const targetScrollTop = targetOffsetTop - screenHeight / 4;
 - 
 -                         // 更新scroll-view的滚动位置
 -                         const finalScrollTop = Math.max(0, targetScrollTop);
 - 
 -                         // 检查是否需要滚动(避免不必要的滚动)
 -                         const currentScroll = this.scrollTops[this.currentPage - 1] || 0;
 -                         const scrollDifference = Math.abs(finalScrollTop - currentScroll);
 - 
 -                         // 提高滚动阈值,减少不必要的微小滚动
 -                         if (scrollDifference > 30) { // 从20提高到30,与精确滚动保持一致
 -                             this.$set(this.scrollTops, this.currentPage - 1, finalScrollTop);
 - 
 -                             // 滚动完成后重置状态
 -                             setTimeout(() => {
 -                                 resetScrollingState();
 -                             }, 200); // 减少等待时间
 - 
 -                             console.log('✅ 滚动到高亮文本:', {
 -                                 selector,
 -                                 targetOffsetTop,
 -                                 finalScrollTop,
 -                                 currentPage: this.currentPage,
 -                                 scrollDifference
 -                             });
 -                         } else {
 -                             // 不需要滚动,立即重置状态
 -                             resetScrollingState();
 -                             console.log('📍 目标已在最佳可视位置,无需滚动');
 -                         }
 -                     } else {
 -                         console.error('❌ 未找到目标元素或scroll-view:', {
 -                             selector,
 -                             scrollViewFound: !!scrollViewRect,
 -                             targetFound: !!targetRect,
 -                             currentPage: this.currentPage,
 -                             highlightIndex: scrollData.highlightIndex
 -                         });
 - 
 -                         // 尝试备用方案:直接滚动到页面顶部附近
 -                         if (!targetRect) {
 -                             console.log('🔄 尝试备用滚动方案');
 -                             // 改进备用方案:基于highlightIndex计算更准确的位置
 -                             const estimatedPosition = this.calculateEstimatedScrollPosition(scrollData.highlightIndex);
 -                             this.$set(this.scrollTops, this.currentPage - 1, estimatedPosition);
 -                             setTimeout(() => {
 -                                 resetScrollingState();
 -                             }, 200);
 -                         } else {
 -                             // 立即重置状态
 -                             resetScrollingState();
 -                         }
 -                     }
 -                 });
 -         },
 - 
 -         // 查找文本元素在页面中的实际索引
 -         findTextItemIndex(originalTextIndex) {
 -             const currentPageData = this.bookPages[this.currentPage - 1];
 -             if (!currentPageData || !Array.isArray(currentPageData)) {
 -                 return -1;
 -             }
 - 
 -             let textCount = 0;
 -             for (let i = 0; i < currentPageData.length; i++) {
 -                 const item = currentPageData[i];
 -                 if (item && item.type === 'text' && item.content) {
 -                     if (textCount === originalTextIndex) {
 -                         return i; // 返回在页面数组中的实际索引
 -                     }
 -                     textCount++;
 -                 }
 -             }
 - 
 -             return -1; // 未找到
 -         },
 - 
 -         // 获取所有页面元素的位置信息
 -         async getAllElementPositions() {
 -             return new Promise((resolve) => {
 -                 const currentPageData = this.bookPages[this.currentPage - 1];
 -                 if (!currentPageData || !Array.isArray(currentPageData)) {
 -                     resolve([]);
 -                     return;
 -                 }
 - 
 -                 const query = uni.createSelectorQuery().in(this);
 -                 const elementPositions = [];
 - 
 -                 // 获取scroll-container的位置作为基准
 -                 query.select('.scroll-container').boundingClientRect();
 - 
 -                 // 为每个元素添加查询
 -                 currentPageData.forEach((item, index) => {
 -                     if (item && (item.type === 'text' || item.type === 'image' || item.type === 'video')) {
 -                         if (item.type === 'text') {
 -                             query.select(`#text-${index}`).boundingClientRect();
 -                         } else if (item.type === 'image') {
 -                             query.select(`.image-container`).boundingClientRect();
 -                         } else if (item.type === 'video') {
 -                             query.select(`.video-content`).boundingClientRect();
 -                         }
 -                     }
 -                 });
 - 
 -                 query.exec((res) => {
 -                     const containerRect = res[0];
 -                     if (!containerRect) {
 -                         resolve([]);
 -                         return;
 -                     }
 - 
 -                     // 处理查询结果
 -                     let resultIndex = 1; // 跳过第一个容器结果
 -                     currentPageData.forEach((item, index) => {
 -                         if (item && (item.type === 'text' || item.type === 'image' || item.type === 'video')) {
 -                             const elementRect = res[resultIndex];
 -                             if (elementRect) {
 -                                 elementPositions.push({
 -                                     index: index,
 -                                     type: item.type,
 -                                     top: elementRect.top - containerRect.top,
 -                                     height: elementRect.height,
 -                                     bottom: elementRect.top - containerRect.top + elementRect.height
 -                                 });
 -                             }
 -                             resultIndex++;
 -                         }
 -                     });
 - 
 -                     resolve(elementPositions);
 -                 });
 -             });
 -         },
 - 
 -         // 检查元素是否在可视范围内
 -         isElementInViewport(elementPosition, currentScrollTop) {
 -             const screenHeight = uni.getSystemInfoSync().windowHeight;
 -             const viewportTop = currentScrollTop;
 -             const viewportBottom = currentScrollTop + screenHeight;
 -             
 -             // 元素的顶部和底部位置
 -             const elementTop = elementPosition.top;
 -             const elementBottom = elementPosition.bottom;
 -             
 -             // 检查元素是否完全或部分在可视范围内
 -             const isVisible = elementBottom > viewportTop && elementTop < viewportBottom;
 -             
 -             // 计算元素在可视范围内的比例
 -             const visibleTop = Math.max(elementTop, viewportTop);
 -             const visibleBottom = Math.min(elementBottom, viewportBottom);
 -             const visibleHeight = Math.max(0, visibleBottom - visibleTop);
 -             const visibilityRatio = visibleHeight / elementPosition.height;
 -             
 -             return {
 -                 isVisible,
 -                 visibilityRatio,
 -                 elementTop,
 -                 elementBottom,
 -                 viewportTop,
 -                 viewportBottom
 -             };
 -         },
 - 
 -         // 计算最佳滚动位置
 -         calculateOptimalScrollPosition(targetElement, currentScrollTop) {
 -             const screenHeight = uni.getSystemInfoSync().windowHeight;
 -             
 -             // 检查当前元素的可见性
 -             const visibility = this.isElementInViewport(targetElement, currentScrollTop);
 -             
 -             // 如果元素已经完全可见且在合适位置,不需要滚动
 -             if (visibility.isVisible && visibility.visibilityRatio > 0.8) {
 -                 // 检查元素是否在屏幕的合适位置(上方1/3到2/3之间)
 -                 const elementCenter = (targetElement.top + targetElement.bottom) / 2;
 -                 const relativePosition = (elementCenter - currentScrollTop) / screenHeight;
 -                 
 -                 if (relativePosition >= 0.2 && relativePosition <= 0.7) {
 -                     console.log('📍 元素已在最佳可视位置,无需滚动');
 -                     return null; // 不需要滚动
 -                 }
 -             }
 -             
 -             // 计算目标滚动位置:让元素显示在屏幕上方1/3处(更舒适的阅读位置)
 -             const optimalOffsetRatio = 0.3; // 30%的位置,比1/4更舒适
 -             const offsetFromTop = screenHeight * optimalOffsetRatio;
 -             
 -             // 考虑元素高度,确保不会被截断
 -             const elementHeight = targetElement.height;
 -             const adjustedOffset = Math.min(offsetFromTop, screenHeight * 0.1); // 最小10%偏移
 -             
 -             const targetScrollTop = Math.max(0, targetElement.top - adjustedOffset);
 -             
 -             console.log('🎯 计算最佳滚动位置:', {
 -                 currentVisibility: visibility,
 -                 elementHeight,
 -                 optimalOffsetRatio,
 -                 adjustedOffset,
 -                 targetScrollTop
 -             });
 -             
 -             return targetScrollTop;
 -         },
 - 
 -         // 计算精确的滚动位置
 -         async calculatePreciseScrollPosition(scrollData, elementPositions) {
 -             if (!elementPositions || elementPositions.length === 0) {
 -                 return null;
 -             }
 - 
 -             let targetElementIndex = -1;
 -             
 -             if (scrollData.segmentIndex !== undefined) {
 -                 // 分段音频情况
 -                 targetElementIndex = scrollData.segmentIndex;
 -             } else if (scrollData.highlightIndex !== undefined) {
 -                 // 普通音频情况,需要找到对应的文本元素
 -                 targetElementIndex = this.findTextItemIndex(scrollData.highlightIndex);
 -             }
 - 
 -             if (targetElementIndex === -1) {
 -                 return null;
 -             }
 - 
 -             // 查找目标元素的位置信息
 -             const targetElement = elementPositions.find(pos => pos.index === targetElementIndex && pos.type === 'text');
 -             
 -             if (!targetElement) {
 -                 console.warn('未找到目标元素位置信息:', targetElementIndex);
 -                 return null;
 -             }
 - 
 -             // 获取当前滚动位置
 -             const currentScrollTop = this.scrollTops[this.currentPage - 1] || 0;
 -             
 -             // 使用优化的滚动位置计算
 -             const targetScrollTop = this.calculateOptimalScrollPosition(targetElement, currentScrollTop);
 - 
 -             console.log('🎯 精确滚动位置计算:', {
 -                 targetElementIndex,
 -                 targetElement,
 -                 currentScrollTop,
 -                 targetScrollTop
 -             });
 - 
 -             return targetScrollTop;
 -          },
 - 
 -          // 计算估算的滚动位置(备用方案)
 -          calculateEstimatedScrollPosition(highlightIndex) {
 -              const currentPageData = this.bookPages[this.currentPage - 1];
 -              if (!currentPageData || !Array.isArray(currentPageData)) {
 -                  return highlightIndex * 80; // 基础估算
 -              }
 - 
 -              // 基于页面内容计算更准确的位置
 -              let estimatedHeight = 0;
 -              let textCount = 0;
 -              
 -              for (let i = 0; i < currentPageData.length && textCount <= highlightIndex; i++) {
 -                  const item = currentPageData[i];
 -                  if (item && item.type === 'text' && item.content) {
 -                      if (textCount === highlightIndex) {
 -                          break;
 -                      }
 -                      // 根据内容长度估算高度
 -                      const contentLength = item.content.length;
 -                      estimatedHeight += Math.max(60, contentLength * 1.2); // 基础高度 + 内容长度因子
 -                      textCount++;
 -                  } else if (item && item.type === 'image') {
 -                      estimatedHeight += 200; // 图片估算高度
 -                  } else if (item && item.type === 'video') {
 -                      estimatedHeight += 300; // 视频估算高度
 -                  }
 -              }
 - 
 -              return Math.max(0, estimatedHeight - 100); // 留一些上边距
 -          },
 - 
 -         // 获取音色列表 拿第一个做默认的音色id
 -         async getVoiceList() {
 -             // 优先从Vuex获取音色列表
 -             if (this.voiceList && this.voiceList.length > 0) {
 -                 console.log('从Vuex获取音色列表:', this.voiceList);
 -                 this.voiceId = this.defaultVoiceId || Number(this.voiceList[0].voiceType);
 -                 console.log('使用Vuex中的默认音色ID:', this.voiceId);
 -                 
 -                 // 同步默认音色设置到audioManager
 -                 audioManager.setGlobalVoiceId(this.voiceId);
 -                 return;
 -             }
 -             
 -             // 如果Vuex中没有数据,则从API获取(兜底方案)
 -             console.log('Vuex中无音色数据,从API获取...');
 -             try {
 -                 const voiceRes = await this.$api.music.list()
 -                 if (voiceRes.code === 200) {
 -                     console.log('音色列表API返回:', voiceRes.result);
 -                     
 -                     // 更新Vuex中的音色列表
 -                     this.$store.commit('setVoiceList', voiceRes.result);
 -                     
 -                     this.voiceId = Number(voiceRes.result[0].voiceType)
 -                     console.log('获取默认音色ID:', this.voiceId, '类型:', typeof this.voiceId);
 - 
 -                     // 同步默认音色设置到audioManager
 -                     audioManager.setGlobalVoiceId(this.voiceId);
 -                 } else {
 -                     console.error('获取音色列表失败:', voiceRes);
 -                 }
 -             } catch (error) {
 -                 console.error('获取音色列表异常:', error);
 -             }
 -         },
 -         toggleNavbar() {
 -             this.showNavbar = !this.showNavbar
 -         },
 -         goBack() {
 -             uni.navigateBack()
 -         },
 -         toggleCoursePopup() {
 -             if (this.$refs.coursePopup) {
 -                 this.$refs.coursePopup.open()
 -             }
 -             // console.log('123123123');
 - 
 -         },
 -         toggleSort() {
 -             this.isReversed = !this.isReversed
 -         },
 -         selectCourse(courseId) {
 -             this.currentCourse = courseId
 -             this.courseId = courseId  // 同时更新 courseId
 -             // 这里可以添加切换课程的逻辑
 -             // console.log('选择课程:', courseId)
 -             this.getCourseList(courseId)
 -         },
 -         showWordMeaning() {
 -             if (this.$refs.meaningPopup) {
 -                 this.$refs.meaningPopup.open()
 -             }
 -         },
 -         closeMeaningPopup() {
 - 
 -             this.currentWordMeaning = null;
 - 
 -             // 重置音频播放状态,确保后续句子点击能正常播放
 - 
 - 
 -             if (this.isWordAudioPlaying) {
 - 
 -                 this.isWordAudioPlaying = false;
 - 
 -                 // 如果有正在播放的音频,停止它
 -                 if (this.currentWordAudio) {
 - 
 -                     try {
 -                         this.currentWordAudio.pause();
 -                         this.currentWordAudio.destroy();
 - 
 -                     } catch (error) {
 - 
 -                     }
 -                     this.currentWordAudio = 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()
 -             );
 -         },
 - 
 -         // 处理中文文本,标记重点词汇
 -         processChineseText(text) {
 -             const currentPageWords = this.pageWords[this.currentPage - 1] || [];
 - 
 -             if (!text || currentPageWords.length === 0) {
 -                 return [{ text: text, isKeyword: false, keywordData: null }];
 -             }
 - 
 -             // 创建一个数组来存储处理后的文本片段
 -             const segments = [];
 -             let currentIndex = 0;
 - 
 -             // 按照重点词汇的长度排序,优先匹配较长的词汇
 -             const sortedWords = [...currentPageWords].sort((a, b) => b.word.length - a.word.length);
 - 
 -             while (currentIndex < text.length) {
 -                 let matched = false;
 - 
 -                 // 尝试匹配重点词汇
 -                 for (const wordData of sortedWords) {
 -                     const keyword = wordData.word;
 -                     if (text.substr(currentIndex, keyword.length) === keyword) {
 -                         // 找到匹配的重点词汇
 -                         segments.push({
 -                             text: keyword,
 -                             isKeyword: true,
 -                             keywordData: wordData
 -                         });
 -                         currentIndex += keyword.length;
 -                         matched = true;
 -                         break;
 -                     }
 -                 }
 - 
 -                 if (!matched) {
 -                     // 没有匹配到重点词汇,添加单个字符
 -                     segments.push({
 -                         text: text[currentIndex],
 -                         isKeyword: false,
 -                         keywordData: null
 -                     });
 -                     currentIndex++;
 -                 }
 -             }
 - 
 -             // 合并相邻的非重点词汇片段
 -             const mergedSegments = [];
 -             let currentSegment = null;
 - 
 -             for (const segment of segments) {
 -                 if (segment.isKeyword) {
 -                     // 如果当前有未完成的非重点词汇片段,先添加它
 -                     if (currentSegment) {
 -                         mergedSegments.push(currentSegment);
 -                         currentSegment = null;
 -                     }
 -                     // 添加重点词汇
 -                     mergedSegments.push(segment);
 -                 } else {
 -                     // 非重点词汇,合并到当前片段
 -                     if (currentSegment) {
 -                         currentSegment.text += segment.text;
 -                     } else {
 -                         currentSegment = { ...segment };
 -                     }
 -                 }
 -             }
 - 
 -             // 添加最后的非重点词汇片段
 -             if (currentSegment) {
 -                 mergedSegments.push(currentSegment);
 -             }
 - 
 -             return mergedSegments;
 -         },
 - 
 -         // 初始化audioManager事件监听
 -         initAudioManagerListeners() {
 -             // 监听音频播放状态变化
 -             audioManager.on('play', (data) => {
 -                 if (data?.audioType === 'word') {
 -                     this.isWordAudioPlaying = true;
 -                 }
 -             });
 - 
 -             audioManager.on('pause', (data) => {
 -                 if (data?.audioType === 'word') {
 -                     this.isWordAudioPlaying = false;
 -                 }
 -             });
 - 
 -             audioManager.on('ended', (data) => {
 -                 if (data?.audioType === 'word') {
 -                     this.isWordAudioPlaying = false;
 -                 }
 -             });
 - 
 -             audioManager.on('error', (data) => {
 -                 if (data?.audioType === 'word') {
 -                     this.isWordAudioPlaying = false;
 -                     uni.showToast({
 -                         title: '語音播放失敗',
 -                         icon: 'none'
 -                     });
 -                 }
 -             });
 -         },
 - 
 -         async playWordAudio(word) {
 -             try {
 -                 console.log('🎵 开始播放单词音频:', word);
 - 
 -                 // 🎯 使用audioManager的全局音色设置
 -                 const globalVoiceId = audioManager.getGlobalVoiceId();
 -                 const voiceIdToUse = globalVoiceId || this.voiceId;
 - 
 -                 if (!voiceIdToUse || voiceIdToUse === '' || voiceIdToUse === null || voiceIdToUse === undefined) {
 -                     console.warn('⚠️ 音色ID未设置,无法播放音频');
 -                     uni.showToast({
 -                         title: '音色未加载,请稍后重试',
 -                         icon: 'none',
 -                         duration: 2000
 -                     });
 -                     return;
 -                 }
 - 
 -                 console.log('🎵 使用音色ID:', voiceIdToUse, '播放文本:', word);
 - 
 -                 // 調用語音轉換API
 -                 const audioRes = await this.$api.music.textToVoice({
 -                     text: word,
 -                     voiceType: voiceIdToUse
 -                 });
 - 
 -                 console.log('🎵 API响应:', audioRes);
 - 
 -                 // 檢查響應並播放音頻
 -                 if (audioRes && audioRes.result && audioRes.result.url) {
 -                     console.log('✅ 获取到音频URL:', audioRes.result.url);
 - 
 -                     // 使用audioManager播放音频,应用全局语速设置
 -                     await audioManager.playAudio(audioRes.result.url, 'word', {
 -                         playbackRate: audioManager.getGlobalPlaybackRate()
 -                     });
 -                 } else {
 -                     console.error('❌ API响应无效:', audioRes);
 -                     uni.showToast({
 -                         title: '語音播放失敗',
 -                         icon: 'none'
 -                     });
 -                 }
 -             } catch (error) {
 -                 console.error('❌ 播放单词语音异常:', error);
 -                 uni.showToast({
 -                     title: '語音播放失敗',
 -                     icon: 'none'
 -                 });
 -             }
 -         },
 - 
 - 
 - 
 -         // 重複播放單詞語音(用於釋義彈窗中的揚聲器圖標)
 -         repeatWordAudio() {
 -             if (this.currentWordMeaning && this.currentWordMeaning.word) {
 -                 // 将单词和解释合并后播放音频
 -                 const combinedText = `${this.currentWordMeaning.word}。${this.currentWordMeaning.meaning || ''}`;
 - 
 -                 this.playWordAudio(combinedText);
 -             } else {
 -                 console.warn('沒有當前單詞可以播放');
 -             }
 -         },
 - 
 -         // 处理单词点击事件
 -         handleWordClick(word) {
 - 
 -             const definition = this.findWordDefinition(word);
 - 
 -             if (definition) {
 - 
 -                 this.currentWordMeaning = {
 -                     word: definition.word,
 -                     phonetic: definition.soundmark || '',
 -                     partOfSpeech: '', // 可以根据需要添加词性
 -                     meaning: definition.paraphrase || '',
 -                     knowledgeGain: definition.knowledge || '',
 -                     image: definition.image || ''
 -                 };
 - 
 - 
 -                 // 将单词和解释合并后播放音频
 -                 const combinedText = `${word}。${definition.paraphrase || ''}`;
 - 
 -                 this.playWordAudio(combinedText);
 - 
 - 
 -                 this.showWordMeaning();
 -             } else {
 - 
 - 
 -                 // 如果没有释义,只播放单词
 -                 if (word) {
 -                     this.playWordAudio(word);
 -                 } else {
 - 
 -                 }
 -             }
 - 
 -         },
 - 
 -         // 处理中文重点词汇点击事件
 -         handleChineseKeywordClick(keywordData) {
 - 
 - 
 -             if (keywordData) {
 - 
 -                 this.currentWordMeaning = {
 -                     word: keywordData.word,
 -                     phonetic: keywordData.soundmark || '',
 -                     partOfSpeech: '', // 可以根据需要添加词性
 -                     meaning: keywordData.paraphrase || '',
 -                     knowledgeGain: keywordData.knowledge || '',
 -                     image: keywordData.image || ''
 -                 };
 - 
 - 
 -                 // 将词汇和解释合并后播放音频
 -                 const combinedText = `${keywordData.word}。${keywordData.paraphrase || ''}`;
 - 
 -                 this.playWordAudio(combinedText);
 - 
 - 
 -                 this.showWordMeaning();
 -             } else {
 - 
 -             }
 - 
 -         },
 - 
 -         // 计算音频总时长
 -         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) {
 - 
 -                     totalDuration += audio.duration;
 -                     continue;
 -                 }
 - 
 -                 // 如果沒有API時長信息,嘗試獲取音頻時長
 -                 try {
 -                     const duration = await this.getAudioDuration(audio.url);
 -                     audio.duration = duration;
 -                     totalDuration += duration;
 - 
 -                 } catch (error) {
 -                     console.error('获取音频时长失败:', error);
 -                     // 如果无法获取时长,根據文字長度估算(更精確的估算)
 -                     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;
 - 
 -         },
 - 
 -         // 获取音频时长
 -         getAudioDuration(audioUrl) {
 -             return new Promise((resolve, reject) => {
 -                 const audio = uni.createInnerAudioContext();
 -                 audio.src = audioUrl;
 - 
 -                 let resolved = false;
 - 
 -                 // 监听音频加载完成事件
 -                 audio.onCanplay(() => {
 - 
 -                     if (!resolved && audio.duration && audio.duration > 0) {
 -                         resolved = true;
 -                         resolve(audio.duration);
 -                         audio.destroy();
 -                     }
 -                 });
 - 
 -                 // 监听音频元数据加载完成事件
 -                 audio.onLoadedmetadata = () => {
 - 
 -                     if (!resolved && audio.duration && audio.duration > 0) {
 -                         resolved = true;
 -                         resolve(audio.duration);
 -                         audio.destroy();
 -                     }
 -                 };
 - 
 -                 // 监听音频时长更新事件
 -                 audio.onDurationChange = () => {
 - 
 -                     if (!resolved && audio.duration && audio.duration > 0) {
 -                         resolved = true;
 -                         resolve(audio.duration);
 -                         audio.destroy();
 -                     }
 -                 };
 - 
 -                 // 如果以上方法都無法獲取時長,嘗試播放一小段來獲取時長
 -                 audio.onPlay(() => {
 - 
 -                     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);
 -                     if (!resolved) {
 -                         resolved = true;
 -                         reject(error);
 -                         audio.destroy();
 -                     }
 -                 });
 - 
 -                 // 設置較長的超時時間,並在超時前嘗試播放
 -                 setTimeout(() => {
 -                     if (!resolved) {
 - 
 -                         audio.play();
 -                     }
 -                 }, 1000);
 - 
 -                 // 最終超時處理
 -                 setTimeout(() => {
 -                     if (!resolved) {
 -                         console.warn('獲取音頻時長超時,使用默認值');
 -                         resolved = true;
 -                         reject(new Error('获取音频时长超时'));
 -                         audio.destroy();
 -                     }
 -                 }, 5000);
 -             });
 -         },
 - 
 -         // 音频控制方法 - 这些方法现在由AudioControls组件处理
 -         // 保留一些简单的接口方法用于与AudioControls组件通信
 - 
 -         // 判断当前文本是否应该高亮 - 这个方法需要保留,因为它用于模板渲染
 -         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) {
 - 
 -                         }
 -                         return shouldHighlight;
 -                     }
 -                     textIndex++;
 -                 }
 -             }
 -             return false;
 -         },
 - 
 -         // 判断划线重点卡片中的文本是否应该高亮
 -         isCardTextHighlighted(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) {
 -                         return textIndex === this.currentHighlightIndex;
 -                     }
 -                     textIndex++;
 -                 }
 -             }
 -             return false;
 -         },
 - 
 -         async previousPage() {
 -             if (this.currentPage > 1) {
 -                 this.currentPage--;
 -                 // 获取对应页面的数据(如果还没有获取过)
 -                 if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
 -                     await this.getBookPages(this.courseIdList[this.currentPage - 1]);
 -                 }
 -             }
 -         },
 -         async nextPage() {
 -             if (this.currentPage < this.bookPages.length) {
 -                 this.currentPage++;
 -                 // 获取对应页面的数据(如果还没有获取过)
 -                 if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
 -                     await this.getBookPages(this.courseIdList[this.currentPage - 1]);
 -                 }
 -             }
 -         },
 -         toggleSound() {
 -             // 检查是否正在加载音频,如果是则阻止音色切换
 -             if (this.isAudioLoading) {
 -                 uni.showToast({
 -                     title: '音频加载中,请稍后再试',
 -                     icon: 'none',
 -                     duration: 2000
 -                 });
 -                 return;
 -             }
 - 
 -             // 检查AudioControls组件是否正在加载音频
 -             if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls && this.$refs.customTabbar.$refs.audioControls.isAudioLoading) {
 -                 uni.showToast({
 -                     title: '音频加载中,请稍后再试',
 -                     icon: 'none',
 -                     duration: 2000
 -                 });
 -                 return;
 -             }
 - 
 -             console.log('音色切换')
 -             uni.navigateTo({
 -                 url: '/subPages/home/music?voiceId=' + this.voiceId
 -             })
 -         },
 -         unlockBook() {
 -             console.log('解锁全书')
 -             // 这里可以跳转到会员页面或者调用解锁接口
 -             uni.navigateTo({
 -                 url: '/subPages/member/recharge'
 -             })
 -         },
 - 
 -         // 跳转到下一课
 -         async goToNextCourse() {
 -             if (!this.hasNextCourse) {
 -                 uni.showToast({
 -                     title: '已经是最后一课了',
 -                     icon: 'none',
 -                     duration: 2000
 -                 });
 -                 return;
 -             }
 - 
 -             try {
 -                 // 找到当前课程在课程列表中的索引
 -                 const currentCourseIndex = this.courseList.findIndex(course => course.id == this.courseId);
 -                 if (currentCourseIndex >= 0 && currentCourseIndex < this.courseList.length - 1) {
 -                     // 获取下一课的ID
 -                     const nextCourse = this.courseList[currentCourseIndex + 1];
 -                     console.log('跳转到下一课:', nextCourse);
 -                     
 -                     // 切换到下一课
 -                     await this.selectCourse(nextCourse.id);
 -                     
 -                     uni.showToast({
 -                         title: `已切换到第${currentCourseIndex + 2}课`,
 -                         icon: 'success',
 -                         duration: 2000
 -                     });
 -                 }
 -             } catch (error) {
 -                 console.error('跳转下一课失败:', error);
 -                 uni.showToast({
 -                     title: '跳转失败,请重试',
 -                     icon: 'none',
 -                     duration: 2000
 -                 });
 -             }
 -         },
 - 
 -         // 回到开始(当前课程的第一页)
 -         async backToStart() {
 -             try {
 -                 // 回到当前课程的第一页
 -                 this.currentPage = 1;
 -                 console.log('回到开始,跳转到第一页');
 -                 
 -                 // 获取第一页的数据(如果还没有获取过)
 -                 if (this.courseIdList[0] && this.bookPages[0].length === 0) {
 -                     await this.getBookPages(this.courseIdList[0]);
 -                 }
 -                 
 -                 uni.showToast({
 -                     title: '已回到第一页',
 -                     icon: 'success',
 -                     duration: 2000
 -                 });
 -             } catch (error) {
 -                 console.error('回到开始失败:', error);
 -                 uni.showToast({
 -                     title: '操作失败,请重试',
 -                     icon: 'none',
 -                     duration: 2000
 -                 });
 -             }
 -         },
 - 
 -         async goToPage(page) {
 -             this.currentPage = page
 -             console.log('跳转到页面:', page)
 -             // 获取对应页面的数据(如果还没有获取过)
 -             if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
 -                 await this.getBookPages(this.courseIdList[this.currentPage - 1]);
 -             }
 -         },
 -         async onSwiperChange(e) {
 -             this.currentPage = e.detail.current + 1
 -             // 获取对应页面的数据(如果还没有获取过)
 -             if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
 -                 await this.getBookPages(this.courseIdList[this.currentPage - 1]);
 -             }
 -         },
 -         async getCourseList(id) {
 -             const res = await this.$api.book.coursePage({
 -                 id: id
 -             })
 -             if (res.code === 200) {
 -                 // 课程切换时,先清理音频控制组件的所有数据
 -                 if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) {
 -                     this.$refs.customTabbar.$refs.audioControls.resetForCourseChange();
 -                 }
 - 
 -                 // 清空当前页面相关数据
 -                 this.currentPage = 1; // 重置到第一页
 -                 this.currentCourse = 1; // 重置当前课程索引
 -                 this.currentWordMeaning = null; // 清空单词释义
 -                 this.currentWordAudio = null; // 清空单词音频
 -                 this.currentHighlightIndex = -1; // 清空高亮索引
 - 
 -                 // 清理单词音频缓存
 -                 this.clearWordAudioCache();
 - 
 -                 // 重新初始化课程数据
 -                 this.courseIdList = res.result.map(item => item.id)
 -                 // 初始化二维数组 换一种方式
 -                 this.bookPages = this.courseIdList.map(() => [])
 -                 // 初始化标题数组
 -                 this.pageTitles = this.courseIdList.map(() => '')
 -                 // 初始化页面类型数组
 -                 this.pageTypes = this.courseIdList.map(() => '')
 -                 // 初始化页面单词数组
 -                 this.pageWords = this.courseIdList.map(() => [])
 -                 // 初始化滚动位置数组
 -                 this.scrollTops = this.courseIdList.map(() => 0)
 - 
 - 
 - 
 -                 // 初始化第一页
 -                 if (this.courseIdList.length > 0) {
 -                     await this.getBookPages(this.courseIdList[0])
 - 
 -                     // 课程切换后,确保音频控件能正确加载新课程的音频
 - 
 - 
 -                     // 使用$nextTick确保DOM和数据都已更新
 -                     this.$nextTick(async () => {
 -                         if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) {
 - 
 -                             try {
 -                                 // 直接调用getCurrentPageAudio方法,更可靠
 -                                 await this.$refs.customTabbar.$refs.audioControls.getCurrentPageAudio();
 - 
 -                             } catch (error) {
 -                                 console.error('课程切换后音频加载失败:', error);
 -                             }
 -                         }
 -                     });
 - 
 -                     // 预加载后续几页的内容(异步执行,不阻塞当前页面显示)
 -                     this.preloadNextPages()
 -                 }
 -             }
 -         },
 -         async getBookPages(id) {
 -             const res = await this.$api.book.coursesPageDetail({
 -                 id: id
 -             })
 -             if (res.code === 200) {
 -                 // 使用$set确保响应式更新
 -                 const rawPageData = JSON.parse(res.result.content)
 -                 console.log('获取到的原始页面数据:', rawPageData)
 - 
 -                 // 过滤掉无效的数据项
 -                 const filteredPageData = rawPageData.filter(item => {
 -                     return item && typeof item === 'object' && (item.type || item.content)
 -                 })
 -                 console.log('过滤后的页面数据:', filteredPageData)
 - 
 -                 // 确保当前页面存在
 -                 if (this.currentPage - 1 < this.bookPages.length) {
 -                     this.$set(this.bookPages, this.currentPage - 1, filteredPageData)
 -                     // 保存页面标题
 -                     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')
 -                 }
 -             }
 -         },
 -         // 获取课程列表
 -         async getCoursePageList(bookId) {
 -             const res = await this.$api.book.course({
 -                 id: bookId
 -             })
 -             if (res.code === 200) {
 -                 this.courseList = res.result.records
 -                 // 打上序列号
 -                 this.courseList = this.courseList.map((item, index) => ({
 -                     ...item,
 -                     index,
 -                 }))
 -             }
 -         },
 - 
 -         // 清理音频缓存
 -         clearAudioCache() {
 -             this.audioCache = {};
 - 
 -         },
 - 
 -         // 清理單詞語音緩存
 -         clearWordAudioCache() {
 -             this.wordAudioCache = {};
 - 
 -         },
 - 
 -         // 限制缓存大小,保留最近访问的页面
 -         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];
 -                 });
 - 
 -             }
 -         },
 - 
 -         // 预加载后续页面内容
 -         async preloadNextPages() {
 -             try {
 - 
 - 
 -                 // 优化策略:只预加载接下来的2-3页内容,避免过多请求
 -                 const preloadCount = Math.min(3, this.courseIdList.length - 1); // 预加载3页或剩余页数
 - 
 -                 // 串行预加载,避免并发请求过多
 -                 for (let i = 1; i <= preloadCount; i++) {
 -                     if (i < this.courseIdList.length && this.bookPages[i].length === 0) {
 -                         try {
 -                             await this.preloadSinglePage(this.courseIdList[i], i);
 -                             // 每页之间间隔400ms,提高预加载效率
 -                             if (i < preloadCount) {
 -                                 await new Promise(resolve => setTimeout(resolve, 0));
 -                             }
 -                         } catch (error) {
 -                             console.error(`预加载第${i + 1}页失败:`, error);
 -                             // 继续预加载下一页
 -                         }
 -                     }
 -                 }
 - 
 - 
 - 
 -                 // 延迟800ms后再通知AudioControls组件开始预加载音频,提高响应速度
 -                 setTimeout(() => {
 -                     if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) {
 -                         this.$refs.customTabbar.$refs.audioControls.startPreloadAudio();
 -                     }
 -                 }, 800);
 - 
 -             } catch (error) {
 -                 console.error('预加载页面内容失败:', error);
 -             }
 -         },
 - 
 -         // 预加载单个页面
 -         async preloadSinglePage(courseId, pageIndex) {
 -             try {
 -                 const res = await this.$api.book.coursesPageDetail({
 -                     id: courseId
 -                 });
 - 
 -                 if (res.code === 200) {
 -                     const rawPageData = JSON.parse(res.result.content);
 -                     const filteredPageData = rawPageData.filter(item => {
 -                         return item && typeof item === 'object' && (item.type || item.content);
 -                     });
 - 
 -                     // 使用$set确保响应式更新
 -                     this.$set(this.bookPages, pageIndex, filteredPageData);
 -                     this.$set(this.pageTitles, pageIndex, res.result.title || '');
 -                     this.$set(this.pageTypes, pageIndex, res.result.type || '');
 -                     this.$set(this.pageWords, pageIndex, res.result.words || []);
 -                     this.$set(this.pagePay, pageIndex, res.result.pay || 'N');
 - 
 - 
 -                 }
 -             } catch (error) {
 -                 console.error(`预加载第${pageIndex + 1}页失败:`, error);
 -                 throw error;
 -             }
 -         },
 - 
 -         // 自動加載第一頁音頻並播放
 -         async autoLoadAndPlayFirstPage() {
 -             try {
 - 
 - 
 -                 // 確保當前是第一頁且需要加載音頻
 -                 if (this.currentPage === 1 && this.shouldLoadAudio) {
 - 
 - 
 -                     // 加載音頻
 -                     await this.getCurrentPageAudio();
 - 
 -                     // 檢查是否成功加載音頻
 -                     if (this.currentPageAudios && this.currentPageAudios.length > 0) {
 - 
 -                         // getCurrentPageAudio方法已經處理了第一個音頻的播放,這裡不需要再次調用playAudio
 -                     } else {
 - 
 -                     }
 -                 } else {
 - 
 -                 }
 -             } catch (error) {
 -                 console.error('自動加載和播放音頻失敗:', error);
 -             }
 -         },
 -     },
 -     async onLoad(args) {
 -         this.$scrollTo('imageRef')
 - 
 -         // 初始化audioManager事件监听
 -         this.initAudioManagerListeners();
 - 
 -         // 监听音色切换事件,传递给AudioControls组件处理
 -         uni.$on('selectVoice', async (voiceId) => {
 -             if (this.voiceId === voiceId) {
 - 
 -                 return;
 -             }
 - 
 -             // 检查是否正在加载音频,如果是则阻止音色切换
 -             if (this.isAudioLoading || (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls && this.$refs.customTabbar.$refs.audioControls.isAudioLoading)) {
 - 
 -                 uni.showToast({
 -                     title: '音频加载中,请稍后再试',
 -                     icon: 'none',
 -                     duration: 2000
 -                 });
 -                 return;
 -             }
 - 
 -             // 更新本地音色ID
 -             this.voiceId = voiceId;
 - 
 -             // 同步音色设置到audioManager
 -             audioManager.setGlobalVoiceId(voiceId);
 - 
 -             // 清理單詞語音資源
 -             this.clearWordAudioCache();
 -             // 停止当前播放的音频(现在由audioManager统一管理)
 -             audioManager.stopCurrentAudio();
 - 
 -             // 通知AudioControls组件处理音色切换
 -             if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) {
 -                 try {
 - 
 -                     // 传入选项:preloadAllPages: true 表示要预加载所有页面的音频
 -                     await this.$refs.customTabbar.$refs.audioControls.handleVoiceChange(voiceId, {
 -                         preloadAllPages: true
 -                     });
 - 
 -                 } catch (error) {
 -                     console.error('音色切换处理失败:', error);
 -                 }
 -             }
 -         })
 - 
 -         this.courseId = args.courseId
 -         this.currentCourse = args.courseId  // 同时设置 currentCourse
 -         this.memberId = args.memberId
 - 
 -         // 先获取点进来的课程的页面列表
 -         await Promise.all([this.getVoiceList(), this.getMemberInfo(), this.getCourseList(this.courseId), this.getCoursePageList(args.bookId)])
 - 
 -         // 页面加载完成后,通知AudioControls组件自动加载第一页音频
 -         this.$nextTick(() => {
 -             if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) {
 -                 this.$refs.customTabbar.$refs.audioControls.autoLoadAndPlayFirstPage();
 -             }
 -         });
 - 
 -     },
 -     // 页面卸载时清理资源
 -     onUnload() {
 - 
 - 
 -         uni.$off('selectVoice')
 - 
 -         // 0. 清理滚动防抖定时器
 -         if (this.scrollDebounceTimer) {
 -             clearTimeout(this.scrollDebounceTimer);
 -             this.scrollDebounceTimer = null;
 -         }
 - 
 -         // 1. 清理单词语音资源
 -         if (this.currentWordAudio) {
 - 
 -             try {
 -                 this.currentWordAudio.destroy();
 -             } catch (error) {
 -                 console.error('销毁单词音频实例失败:', error);
 -             }
 -             this.currentWordAudio = null;
 -         }
 - 
 -         // 2. 清理单词语音缓存
 -         this.clearWordAudioCache();
 - 
 -         // 3. 停止单词音频播放状态
 -         this.isWordAudioPlaying = false;
 - 
 -         // 4. 通知AudioControls组件清理资源
 -         if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) {
 - 
 -             this.$refs.customTabbar.$refs.audioControls.destroyAudio();
 -         }
 - 
 -         // 5. 清理全局音频实例(防止遗漏)
 -         try {
 -             // 获取所有可能的音频上下文并销毁
 -             if (typeof wx !== 'undefined' && wx.getBackgroundAudioManager) {
 -                 const bgAudio = wx.getBackgroundAudioManager();
 -                 if (bgAudio) {
 -                     bgAudio.stop();
 -                 }
 -             }
 -         } catch (error) {
 -             console.error('清理背景音频失败:', error);
 -         }
 - 
 - 
 -     },
 - 
 -     // 页面隐藏时暂停音频
 -     onHide() {
 - 
 - 
 -         // 1. 暂停单词音频
 -         if (this.currentWordAudio && this.isWordAudioPlaying) {
 - 
 -             try {
 -                 this.currentWordAudio.pause();
 -                 this.isWordAudioPlaying = false;
 -             } catch (error) {
 -                 console.error('暂停单词音频失败:', error);
 -             }
 -         }
 - 
 -         // 2. 通知AudioControls组件暂停音频
 -         if (this.$refs.customTabbar && this.$refs.customTabbar.$refs.audioControls) {
 - 
 -             this.$refs.customTabbar.$refs.audioControls.pauseOnHide();
 -         }
 - 
 - 
 -     }
 - }
 - </script>
 - 
 - <style lang="scss" scoped>
 - .book-container {
 -     width: 100%;
 -     min-height: 100vh;
 -     background-color: #F8F8F8;
 -     position: relative;
 -     overflow: hidden;
 - }
 - 
 - .custom-navbar {
 -     position: fixed;
 -     top: 0;
 -     left: 0;
 -     right: 0;
 -     background-color: #F8F8F8;
 -     z-index: 1000;
 -     transition: transform 0.3s ease;
 - 
 -     &.navbar-hidden {
 -         transform: translateY(-100%);
 -     }
 - }
 - 
 - .navbar-content {
 -     display: flex;
 -     align-items: center;
 -     justify-content: space-between;
 -     padding: 20rpx 32rpx;
 -     // padding-top: calc(20rpx + var(--status-bar-height, 0));
 -     height: 60rpx;
 - }
 - 
 - .navbar-left,
 - .navbar-right {
 -     width: 80rpx;
 -     display: flex;
 -     align-items: center;
 - }
 - 
 - .navbar-right {
 - 
 -     justify-content: flex-end;
 -     flex: 1;
 - }
 - 
 - .navbar-title {
 -     transform: translateX(-50rpx);
 -     flex: 1;
 -     text-align: center;
 -     font-family: PingFang SC;
 -     font-weight: 500;
 -     font-size: 32rpx;
 -     color: #262626;
 -     line-height: 48rpx;
 - }
 - 
 - .content-swiper {
 -     flex: 1;
 -     // min-height: calc(100vh - 100rpx);
 -     // margin-top: 100rpx;
 - 
 -     height: 100vh;
 - }
 - 
 - .swiper-item {
 -     min-height: 100vh;
 -     // background-color: red;
 - 
 - }
 - 
 - .content-area {
 -     flex: 1;
 -     padding: 30rpx 40rpx 100rpx;
 -     /* #ifndef H5 */
 -     padding: 100rpx 40rpx;
 -     /* #endif */
 -     // padding-top: ;
 -     // background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
 -     min-height: 100%;
 -     box-sizing: border-box;
 -     overflow-y: auto;
 - 
 -     .title {
 -         font-family: PingFang SC;
 -         font-weight: 500;
 -         font-size: 34rpx;
 -         text-align: center;
 -         color: #181818;
 -         line-height: 48rpx;
 -         margin-bottom: 32rpx;
 -     }
 - 
 -     .image-container {
 -         width: 100%;
 -         display: flex;
 -         justify-content: center;
 -         align-items: center;
 -         margin: 30rpx 0;
 - 
 -         /* 平板设备适配 */
 -         @media screen and (min-width: 768px) {
 -             margin: 40rpx 0;
 -         }
 -     }
 - 
 -     .content-image {
 -         width: 100%;
 -         height: auto;
 -         //max-width: 600rpx;
 -         /* 限制最大宽度,避免在大屏设备上过大 */
 -         display: block;
 -         border-radius: 12rpx;
 -         /* 添加圆角,提升视觉效果 */
 -         box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
 -         /* 添加阴影,增强层次感 */
 - 
 -         /* 平板设备适配 */
 -         // @media screen and (min-width: 768px) {
 -         //     max-width: 500rpx;
 -         // }
 -     }
 - 
 -     .video-content {
 -         width: 100%;
 -         height: auto;
 -         margin: 30rpx auto;
 -         position: relative;
 - 
 -         .video-player {
 -             // height: 100%;
 -             width: 100%;
 -             height: 60vw;
 -             // margin: 0 auto;
 -             // height: auto;
 -         }
 - 
 -         .video-loading {
 -             position: absolute;
 -             top: 50%;
 -             left: 50%;
 -             transform: translate(-50%, -50%);
 -             color: #666;
 -             font-size: 28rpx;
 -         }
 -     }
 - }
 - 
 - .card-content {
 -     background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
 -     display: flex;
 -     flex-direction: column;
 -     gap: 32rpx;
 -     min-height: 1172rpx;
 -     margin-top: 20rpx;
 -     border-radius: 32rpx;
 -     // height: 100%;
 -     padding: 20rpx;
 -     padding-bottom: 100rpx;
 -     // margin: 0 
 -     border: 1px solid #FFFFFF;
 -     box-sizing: border-box;
 - 
 -     .card-line {
 -         display: flex;
 -         align-items: center;
 -         // margin-bottom: 20rpx;
 -         padding: 20rpx;
 -         padding-bottom: 0;
 -     }
 - 
 -     .card-line-image {
 -         width: 48rpx;
 -         height: 48rpx;
 -         margin-right: 16rpx;
 -     }
 - 
 -     .card-line-text {
 -         font-family: PingFang SC;
 -         font-weight: 600;
 -         font-size: 30rpx;
 -         line-height: 48rpx;
 -         color: #3B3D3D;
 - 
 -     }
 - 
 -     .card-image {
 -         // width: 590rpx;
 -         width: 100%;
 -         height: 268rpx;
 -         border-radius: 24rpx;
 -         margin: 30rpx auto;
 -         // margin-bottom: 20rpx;
 -     }
 - 
 -     // .english-text {
 -     //     display: block;
 -     //     font-family: PingFang SC;
 -     //     font-weight: 600;
 -     //     font-size: 32rpx;
 -     //     line-height: 48rpx;
 -     //     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;
 -         padding: 0 20rpx;
 -     }
 - 
 -     .clickable-word:hover {
 -         background-color: rgba(0, 122, 255, 0.1);
 -         border-radius: 4rpx;
 -     }
 - 
 -     .chinese-segment {
 -         font-family: PingFang SC;
 -         font-weight: 400;
 -         font-size: 28rpx;
 -         line-height: 48rpx;
 -         color: #3B3D3D;
 -     }
 - 
 -     .clickable-keyword {
 -         background: $primary-color;
 -         text-decoration: underline;
 -         cursor: pointer;
 -         color: #fff !important;
 -         transition: all 0.2s ease;
 -         border-radius: 4rpx;
 -         padding: 4rpx;
 -     }
 - 
 -     .clickable-keyword:hover {
 -         background-color: rgba(0, 122, 255, 0.1);
 -     }
 - 
 -     .chinese-text {
 -         display: block;
 -         font-family: PingFang SC;
 -         font-weight: 400;
 -         font-size: 28rpx;
 -         line-height: 48rpx;
 -         color: #4F4F4F;
 -     }
 - }
 - 
 - /* 会员限制页面样式 */
 - .member-content {
 -     display: flex;
 -     flex-direction: column;
 -     align-items: center;
 -     justify-content: center;
 -     height: 90%;
 -     background-color: #F8F8F8;
 -     padding: 40rpx;
 -     margin: -40rpx;
 -     box-sizing: border-box;
 - }
 - 
 - .member-title {
 -     font-family: PingFang SC;
 -     font-weight: 500;
 -     font-size: 40rpx;
 -     line-height: 1;
 -     color: #6f6f6f;
 -     text-align: center;
 -     margin-bottom: 48rpx;
 - }
 - 
 - .member-button {
 -     width: 670rpx;
 -     height: 72rpx;
 -     background: #06DADC;
 -     border-radius: 200rpx;
 -     display: flex;
 -     align-items: center;
 -     justify-content: center;
 - }
 - 
 - .member-button-text {
 -     font-family: PingFang SC;
 -     font-weight: 400;
 -     font-size: 30rpx;
 -     color: #FFFFFF;
 - }
 - 
 - // .video-content {
 - //   width: 100%;
 - //   height: auto;
 - //   // margin: 200rpx -40rpx 0;
 - //   // height: 500rpx;
 - //   background-color: #FFFFFF;
 - //   // padding: 40rpx;
 - //   border-radius: 24rpx;
 - //   display: flex;
 - //   align-items: center;
 - //   justify-content: center;
 - //   .video-player{
 - //     width: 100%;
 - //     margin: 0 auto;
 - //     height: auto;
 - //   }
 - // }
 - 
 - 
 - 
 - .text-content {
 -     // background-color: #F6F6F6;
 -     box-sizing: border-box;
 - 
 -     &>view {
 -         padding: 20rpx;
 -     }
 - }
 - 
 - .content-text {
 -     font-family: PingFang SC;
 -     // font-weight: 400;
 -     font-size: 28rpx;
 -     color: #3B3D3D;
 -     line-height: 48rpx;
 -     letter-spacing: 0;
 -     text-align: justify;
 -     word-break: break-all;
 -     transition: all 0.3s ease;
 - }
 - 
 - .clickable-text {
 -     cursor: pointer;
 - 
 - 
 -     &:active {
 -         background-color: rgba(6, 218, 220, 0.1);
 -         border-radius: 4rpx;
 -     }
 - }
 - 
 - .lead-text {
 -     background: #06dadc12;
 -     // background: #fffbe6;#06dadc
 -     /* 柔和的提示背景 */
 -     //   border: 1px solid #ffe58f;
 -     border-radius: 8px;
 -     //   padding: 10rpx 20rpx;
 - 
 -     /* 添加平滑过渡动画 */
 -     transition: all 0.3s ease;
 - }
 - .introduction-text {
 -     background: #fffbe6;
 -     border: 1px solid #ffe58f;
 -     border-radius: 8px;
 -     padding: 10rpx 20rpx;
 - }
 - 
 - .text-highlight {
 -     background-color: rgba(255, 248, 220, 0.8);
 -     /* 温暖的米黄色,对眼睛友好 */
 -     border-left: 4rpx solid #ffd700;
 -     /* 左侧金色边框作为朗读指示 */
 -     padding: 4rpx 8rpx;
 -     border-radius: 6rpx;
 -     box-shadow: 0 2rpx 6rpx rgba(255, 215, 0, 0.15);
 -     /* 柔和的阴影 */
 - 
 -     /* 添加平滑过渡动画 */
 -     transition: all 0.3s ease;
 - }
 - </style>
 
 
  |