<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>
							 |