<template>

  <div class="chat-container">

    <!-- 最左侧边栏 -->

    <Sidebar />



    <!-- 中间历史记录区域 -->

    <div class="history-sidebar" :class="{ 'disabled': isProcessing }">

      <div class="history-header">

        <span class="section-title">历史记录</span>

        <img src="@/assets/Chat/2.png" alt="新建任务" class="new-chat-btn" @click="handleNewChatClick"
          :class="{ 'disabled': isProcessing }">

        <!-- 测试按钮 -->

        <!-- <button @click="isProcessing = !isProcessing" style="margin-top: 10px; padding: 5px 10px; font-size: 12px;">

          测试切换: {{ isProcessing ? '处理中' : '空闲' }}

        </button> -->

      </div>



      <div class="history-list">

        <!-- 历史记录加载状态 -->

        <div v-if="isLoadingHistory && historyTotal === 0" class="history-loading">

          <div class="loading-spinner"></div>

          <div class="loading-text">正在加载历史记录...</div>

        </div>



        <!-- 有历史记录时显示 -->

        <div v-else-if="historyTotal > 0" v-for="(item, index) in historyData" :key="index"
          :class="['history-item', { active: item.isActive, disabled: isProcessing }]"
          @click="() => handleHistoryItemClick(item, index)"
          :style="{ cursor: (item.isActive || isProcessing) ? 'default' : 'pointer' }">

          <div class="history-icon">

            <img :src="getHistoryImage(item)" alt="培训图标" class="history-icon-img">

          </div>

          <div class="history-content">

            <div class="history-title">{{ item.title }}</div>

            <div class="history-meta">

              <span class="history-time">{{ item.time }}</span>

              <!-- <span class="history-pages">{{ item.pages }}页</span> -->

            </div>

          </div>

          <div class="delete-btn" @click.stop="deleteHistoryItem(item, index)"
            :class="{ 'always-visible': item.isActive }">

            <img src="/src/assets/AIWriting/8.png" alt="删除" class="delete-icon" />

          </div>

        </div>



        <!-- 无历史记录时显示空状态 -->

        <div v-else class="empty-history">

          <img src="@/assets/Chat/22.png" alt="暂无数据" class="empty-icon">

          <div class="empty-text">暂无数据</div>

        </div>

      </div>

    </div>



    <!-- 右侧工作区域 -->

    <div class="main-work">

      <!-- 头部 -->

      <div class="work-header">

        <h2>安全培训</h2>

      </div>



      <!-- 工作内容区域 -->

      <div class="work-content">

        <!-- 步骤一：AI聊天界面 -->

        <div v-if="currentStep === 'step1'" class="step1-content">

          <!-- 初始状态：AI助手介绍和功能卡片 -->

          <div v-if="!showChat" class="initial-content">

            <!-- AI助手介绍 -->

            <div class="ai-intro">

              <div class="ai-avatar">

                <img src="@/assets/Safety/5.png" alt="AI头像" class="ai-avatar-img">

              </div>

              <div class="ai-greeting">

                <h3>快速生成专业安全培训材料</h3>

                <p>输入培训主题，一键生成培训大纲与PPT模板</p>

              </div>

            </div>



            <!-- 功能卡片 -->

            <div class="function-cards">

              <div v-for="(card, index) in functionCards" :key="card.id || index" class="function-card"
                @click="handleFunctionCard(card.function_title)">

                <div class="card-header">

                  <div class="card-icon">

                    <img :src="getFunctionCardIcon(card.function_title)" :alt="card.function_title"
                      class="card-icon-img">

                  </div>

                  <h4>{{ card.function_title }}</h4>

                </div>

                <div class="card-description">

                  <p>{{ card.function_content }}</p>

                </div>

              </div>



              <!-- 如果没有数据，显示默认卡片 -->

              <div v-if="functionCards.length === 0" class="function-card"
                @click="handleFunctionCard('safety-training')">

                <div class="card-header">

                  <div class="card-icon">

                    <img src="@/assets/Safety/4.png" alt="安全培训课程" class="card-icon-img">

                  </div>

                  <h4>安全培训课程</h4>

                </div>

                <div class="card-description">

                  <p>施工安全培训，操作规范学习</p>

                </div>

              </div>

              <div v-if="functionCards.length === 0" class="function-card"
                @click="handleFunctionCard('safety-assessment')">

                <div class="card-header">

                  <div class="card-icon">

                    <img src="@/assets/Safety/3.png" alt="安全评估" class="card-icon-img">

                  </div>

                  <h4>安全评估测试</h4>

                </div>

                <div class="card-description">

                  <p>安全知识测评，能力水平评估</p>

                </div>

              </div>

              <div v-if="functionCards.length === 0" class="function-card"
                @click="handleFunctionCard('safety-regulations')">

                <div class="card-header">

                  <div class="card-icon">

                    <img src="@/assets/Safety/2.png" alt="安全法规" class="card-icon-img">

                  </div>

                  <h4>安全法规查询</h4>

                </div>

                <div class="card-description">

                  <p>安全法律法规，标准规范查询</p>

                </div>

              </div>

              <div v-if="functionCards.length === 0" class="function-card"
                @click="handleFunctionCard('emergency-procedures')">

                <div class="card-header">

                  <div class="card-icon">

                    <img src="@/assets/Safety/1.png" alt="应急程序" class="card-icon-img">

                  </div>

                  <h4>应急处理程序</h4>

                </div>

                <div class="card-description">

                  <p>事故应急预案，处理流程指导</p>

                </div>

              </div>

            </div>

          </div>



          <!-- 聊天对话区域 -->

          <div v-else class="chat-content">

            <div class="chat-messages">

              <div v-for="(message, index) in chatMessages" :key="index" :class="['message-item', message.type]">

                <!-- 用户消息 -->

                <div v-if="message.type === 'user'" class="user-message">

                  <div class="message-content">

                    <!-- 文件显示 -->

                    <div v-if="message.file" class="message-file">

                      <div class="file-display">

                        <div class="file-icon">
                          <img v-if="message.file.type === '.doc' || message.file.type === '.docx'" :src="message.file.icon" alt="文档图标" class="file-icon-img">
                          <span v-else>{{ message.file.icon }}</span>
                        </div>

                        <div class="file-details">

                          <div class="file-name">{{ message.file.name }}</div>

                          <div class="file-size">{{ formatFileSize(message.file.size) }}</div>

                        </div>

                      </div>

                    </div>

                    <!-- 文本内容 -->

                    <div v-if="message.content" class="message-text">{{ message.content }}</div>

                  </div>

                  <div class="message-actions">

                    <button class="action-btn copy-btn" @click="copyUserMessage(message)">

                      <img src="@/assets/AIWriting/5.png" alt="复制" class="action-icon">

                      复制

                    </button>

                    <button class="action-btn edit-btn">

                      <img src="@/assets/AIWriting/6.png" alt="编辑" class="action-icon">

                      编辑

                    </button>

                  </div>

                </div>



                <!-- AI消息 -->

                <div v-else-if="message.type === 'ai'" class="ai-message">

                  <div class="ai-avatar-small">

                    <img src="@/assets/Safety/5.png" alt="AI" class="ai-icon">

                  </div>

                  <div class="message-content" :data-message-index="index">

                    <div class="ai-text">

                      <div v-if="message.displayContent.length === 0" class="typing-indicator">

                        <div class="thinking-animation">

                          <span class="dot"></span>

                          <span class="dot"></span>

                          <span class="dot"></span>

                        </div>

                        <span>AI正在思考中...</span>

                      </div>

                      <div v-else v-html="message.displayContent" class="ai-content"></div>

                    </div>

                    <!-- 移除底部按钮，因为用户看不到这些按钮，AI回复完成后直接跳转 -->

                  </div>

                </div>

              </div>

            </div>

          </div>



        </div>



        <!-- 步骤二：培训大纲界面 -->

        <div v-if="currentStep === 'step2'" class="step2-content">

          <!-- 加载状态 -->

          <div v-if="isLoadingHistory" class="loading-overlay">

            <div class="loading-content">

              <div class="loading-spinner"></div>

              <div class="loading-text">正在加载历史记录</div>

              <div class="loading-subtitle">请稍候，正在为您准备数据...</div>

            </div>

          </div>

          <div class="outline-container">

            <!-- 左侧大纲内容 -->

            <div class="outline-main">

              <div class="outline-header">

                <div class="outline-title-container">

                  <h3 v-if="editingType !== 'title'" class="outline-title"
                    @click="!isGeneratingOutline && !isGeneratingExam && startEditing(null, 'title', null, outlineTitle || '安全培训大纲')"
                    :class="{ 'disabled': isGeneratingOutline || isGeneratingExam }">

                    {{ outlineTitle || '安全培训大纲' }}

                  </h3>

                  <div v-else class="edit-input-container">

                    <textarea v-model="editingContent" class="edit-textarea title-edit-textarea" @keyup.esc="cancelEdit" @keydown.ctrl.enter="saveEdit" @blur="saveEdit" ref="titleEditInput" autofocus></textarea>

                  </div>

                </div>

                <div class="outline-actions">



                  <button class="action-btn" @click="copyEntireOutline" :disabled="isGeneratingOutline">

                    <img src="@/assets/AIWriting/12.png" alt="复制" class="action-icon">

                    复制

                  </button>

                  <button class="action-btn" @click="downloadOutlineAsWord" :disabled="isGeneratingOutline">

                    <img src="@/assets/AIWriting/13.png" alt="下载" class="action-icon">

                    下载

                  </button>

                </div>

              </div>



              <div class="outline-content" :class="{ 'disabled': isGeneratingOutline }">



                <!-- 生成中遮罩层 -->

                <div v-if="isGeneratingOutline" class="generating-overlay">

                  <div class="generating-content">

                    <p>AI正在生成新大纲，请稍候...</p>

                  </div>

                </div>

                <div v-if="isGeneratingExam" class="generating-overlay">

                  <div class="generating-content">

                    <p>AI正在生成考试题目，请稍候...</p>

                  </div>

                </div>



                <!-- <div class="zoom-controls">

                  <button class="zoom-btn">

                    <img src="@/assets/Safety/7.png" alt="编辑" class="zoom-icon">

                  </button>

                  <button class="zoom-btn">

                    <img src="@/assets/Safety/8.png" alt="新增" class="zoom-icon">

                  </button>

                  <button class="zoom-btn">

                    <img src="@/assets/Safety/9.png" alt="删除" class="zoom-icon">

                  </button>

                </div> -->

                <!-- 动态大纲内容 -->

                <div v-if="outlineData && outlineData.length > 0" class="outline-content-scrollable">

                  <template v-for="(chapter, chapterIndex) in outlineData" :key="chapterIndex">

                    <div v-if="chapter && chapter.sections" class="outline-chapter" :class="{

                      'dragging': draggedChapterIndex === chapterIndex,

                      'drag-over': dragOverChapterIndex === chapterIndex && draggedChapterIndex !== chapterIndex

                    }" draggable="true" @dragstart="handleDragStart($event, chapterIndex)" @dragend="handleDragEnd"
                      @dragover="handleDragOver($event, chapterIndex)" @dragleave="handleDragLeave"
                      @drop="handleDrop($event, chapterIndex)">

                      <div class="chapter-header">

                        <h4 v-if="editingType !== 'chapter' || editingIndex !== chapterIndex" class="chapter-title"
                          @click="!isGeneratingExam && startEditing(chapter, 'chapter', chapterIndex, getCleanChapterTitle(chapter.title))"
                          :class="{ 'disabled': isGeneratingExam }">

                          {{ getDisplayChapterTitle(chapter.title, chapterIndex) }}

                        </h4>

                        <div v-else class="edit-input-container">

                          <div class="edit-input-wrapper">

                            <textarea v-model="editingContent" class="edit-textarea chapter-edit-textarea"
                              @keyup.esc="cancelEdit" @keydown.ctrl.enter="saveEdit" @blur="saveEdit" ref="chapterEditInput"
                              autofocus></textarea>

                            <!-- 编辑选项 - 显示在输入框内 -->

                            <div class="edit-options-inline">

                              <button class="edit-option-btn" @click="addNewItem('section', chapterIndex)">

                                <img src="@/assets/Safety/8.png" alt="添加小节" class="edit-icon">

                              </button>

                              <button v-if="outlineData.length > 2" class="edit-option-btn delete-btn"
                                @click="deleteItem('chapter', chapterIndex)">

                                <img src="@/assets/AIWriting/8.png" alt="删除" class="edit-icon">

                              </button>

                            </div>

                          </div>

                        </div>

                      </div>

                      <div class="outline-section">

                        <template v-for="(section, sectionIndex) in chapter.sections" :key="sectionIndex">

                          <div v-if="section &&

                            section.title !== '内容要点' &&

                            section.title !== '概述' &&

                            section.title !== '内容详情'" class="section-container">

                            <div class="section-header">

                              <div
                                v-if="editingType !== 'section' || editingIndex !== `${chapterIndex}-${sectionIndex}`"
                                class="section-title"
                                @click="!isGeneratingExam && startEditing(section, 'section', `${chapterIndex}-${sectionIndex}`, section.title)"
                                :class="{ 'disabled': isGeneratingExam }">

                                {{ section.title }}

                              </div>

                              <div v-else class="edit-input-container">

                                <div class="edit-input-wrapper">

                                  <textarea v-model="editingContent" class="edit-textarea section-edit-textarea"
                                    @keyup.esc="cancelEdit" @keydown.ctrl.enter="saveEdit" @blur="saveEdit"
                                    ref="sectionEditInput" autofocus></textarea>

                                  <!-- 编辑选项 - 显示在输入框内 -->

                                  <div class="edit-options-inline">

                                    <button class="edit-option-btn"
                                      @click="addNewItem('subsection', `${chapterIndex}-${sectionIndex}`)">

                                      <img src="@/assets/Safety/8.png" alt="添加子标题" class="edit-icon">

                                    </button>

                                    <button v-if="chapter.sections.length > 1" class="edit-option-btn delete-btn"
                                      @click="deleteItem('section', `${chapterIndex}-${sectionIndex}`)">

                                      <img src="@/assets/AIWriting/8.png" alt="删除" class="edit-icon">

                                    </button>

                                  </div>

                                </div>

                              </div>

                            </div>



                            <!-- 子小节单独渲染，使用不同的类名 -->

                            <div v-if="section && section.subsections && section.subsections.length > 0"
                              class="section-subsection">

                              <template v-for="(subsection, subsectionIndex) in section.subsections"
                                :key="subsectionIndex">

                                <div v-if="subsection &&

                                  subsection.title !== '内容要点' &&

                                  subsection.title !== '概述' &&

                                  subsection.title !== '内容详情' &&

                                  !subsection.title.includes('总章节数') &&

                                  !subsection.title.includes('总小节数') &&

                                  !subsection.title.includes('预计PPT页数') &&

                                  !subsection.title.includes('预计讲解时长')" class="subsection-container">

                                  <div class="subsection-header">

                                    <div
                                      v-if="editingType !== 'subsection' || editingIndex !== `${chapterIndex}-${sectionIndex}-${subsectionIndex}`"
                                      class="subsection-title"
                                      @click="!isGeneratingExam && startEditing(subsection, 'subsection', `${chapterIndex}-${sectionIndex}-${subsectionIndex}`, subsection.title)"
                                      :class="{ 'disabled': isGeneratingExam }">

                                      {{ subsection.title }}

                                    </div>

                                    <div v-else class="edit-input-container">

                                      <div class="edit-input-wrapper">

                                        <textarea v-model="editingContent" class="edit-textarea subsection-edit-textarea"
                                          @keyup.esc="cancelEdit" @keydown.ctrl.enter="saveEdit" @blur="saveEdit"
                                          ref="subsectionEditInput" autofocus></textarea>

                                        <!-- 编辑选项 - 显示在输入框内 -->

                                        <div class="edit-options-inline">

                                          <button class="edit-option-btn delete-btn"
                                            @click="deleteItem('subsection', `${chapterIndex}-${sectionIndex}-${subsectionIndex}`)">

                                            <img src="@/assets/AIWriting/8.png" alt="删除" class="edit-icon">

                                          </button>

                                        </div>

                                      </div>

                                    </div>

                                  </div>



                                  <!-- 具体内容要点（-开头） -->

                                  <div v-if="subsection.subsubsections && subsection.subsubsections.length > 0"
                                    class="subsubsection-container">

                                    <template v-for="(subsubsection, subsubsectionIndex) in subsection.subsubsections"
                                      :key="subsubsectionIndex">

                                      <div class="subsubsection-item">

                                        <div class="subsubsection-header">

                                          <div
                                            v-if="editingType !== 'subsubsection' || editingIndex !== `${chapterIndex}-${sectionIndex}-${subsectionIndex}-${subsubsectionIndex}`"
                                            class="subsubsection-title"
                                            @click="!isGeneratingExam && startEditing(subsubsection, 'subsubsection', `${chapterIndex}-${sectionIndex}-${subsectionIndex}-${subsubsectionIndex}`, subsubsection.title)"
                                            :class="{ 'disabled': isGeneratingExam }">

                                            {{ subsubsection.title }}

                                          </div>

                                          <div v-else class="edit-input-container">

                                            <div class="edit-input-wrapper">

                                              <textarea v-model="editingContent"
                                                class="edit-textarea subsubsection-edit-textarea"
                                                @keyup.esc="cancelEdit" @keydown.ctrl.enter="saveEdit" @blur="saveEdit" ref="subsubsectionEditInput"
                                                autofocus></textarea>

                                              <!-- 编辑选项 - 显示在输入框内 -->

                                              <div class="edit-options-inline">

                                              </div>

                                            </div>

                                          </div>

                                        </div>




                                      </div>

                                    </template>

                                  </div>

                                </div>

                              </template>

                            </div>

                          </div>

                        </template>

                      </div>

                    </div>

                  </template>



                  <!-- 添加新章节按钮 -->

                  <div v-if="outlineData.length < 6" class="add-chapter-container">

                    <button class="add-chapter-btn" @click="addNewItem('chapter', null)">

                      <img src="@/assets/Safety/8.png" alt="添加章节" class="add-icon">

                      <span>添加新章节</span>

                    </button>

                  </div>

                </div>



                <!-- 默认大纲内容（当没有AI生成内容时显示） -->

                <div v-else>

                  <div class="outline-chapter">

                    <h4>第一章 安全生产基本原则</h4>

                    <div class="outline-section">

                      <div class="section-item">1.1 安全生产的重要性</div>

                      <div class="section-item">1.2 安全生产相关法规</div>

                      <div class="section-subsection">

                        <div class="subsection-item">1.2.1 《中华人民共和国安全生产法》解读</div>

                        <div class="subsection-item">1.2.2 建筑工程安全管理规范</div>

                      </div>

                    </div>

                  </div>



                  <div class="outline-chapter">

                    <h4>第二章 施工现场安全管理</h4>

                    <div class="outline-section">

                      <div class="section-item">2.1 安全责任制度</div>

                      <div class="section-item">2.2 安全教育培训</div>

                      <div class="section-item">2.3 安全检查与隐患排查</div>

                    </div>

                  </div>



                  <div class="outline-chapter">

                    <h4>第三章 常见安全隐患及防范措施</h4>

                    <div class="outline-section">

                      <div class="section-item">3.1 高空作业安全</div>

                      <div class="section-subsection">

                        <div class="subsection-item">3.1.1 脚手架搭设及使用安全规范</div>

                      </div>

                      <div class="section-item">3.2 用电安全</div>

                      <div class="section-item">3.3 消防安全</div>

                    </div>

                  </div>



                  <div class="outline-chapter">

                    <h4>第四章 安全事故案例分析</h4>

                    <div class="outline-section">

                      <div class="section-item">4.1 典型事故分析与教训</div>

                    </div>

                  </div>



                  <div class="outline-chapter">

                    <h4>第五章 总结与展望</h4>

                  </div>

                </div>

              </div>

            </div>



            <!-- 右侧统计信息面板 -->

            <div class="outline-sidebar">

              <!-- 大纲统计信息 -->

              <div class="sidebar-section">

                <div class="section-header">

                  <img src="@/assets/Safety/10.png" alt="统计" class="section-icon">

                  <h5>大纲统计信息</h5>

                </div>

                <div class="section-content">

                  <div class="stat-item">

                    <span class="stat-label">总章节数：</span>

                    <span class="stat-value">{{ outlineStats.totalChapters || 0 }}章</span>

                  </div>

                  <div class="stat-item">

                    <span class="stat-label">总小节数：</span>

                    <span class="stat-value">{{ outlineStats.totalSections || 0 }}小节</span>

                  </div>

                  <div class="stat-item">

                    <span class="stat-label">预计PPT页数：</span>

                    <span class="stat-value">{{ outlineStats.estimatedPages || '0页' }}</span>

                  </div>

                  <div class="stat-item">

                    <span class="stat-label">预计讲解时长：</span>

                    <span class="stat-value">{{ outlineStats.estimatedTime || '0分钟' }}</span>

                  </div>

                </div>

              </div>



              <!-- 大纲编辑提示 -->

              <div class="sidebar-section">

                <div class="section-header">

                  <img src="@/assets/Safety/11.png" alt="提示" class="section-icon">

                  <h5>大纲编辑提示</h5>

                </div>

                <div class="section-content">

                  <ul class="tip-list">

                    <li>点击标题文本可直接在白色区域编辑内容</li>

                    <li>悬停在章节上将显示编辑选项</li>

                    <li>编辑完成后系统会自动保存更改</li>

                    <li>如果内容较多,滚动白色区域可浏览更多大纲内容</li>

                    <li>章节顺序可以通过拖拽调整</li>

                  </ul>

                </div>

              </div>



              <!-- 大纲评价 -->

              <div class="sidebar-section">

                <div class="section-header">

                  <h5>大纲评价</h5>

                </div>

                <div class="section-content">

                  <p class="evaluation-question">这个大纲对您的需求满意度如何?</p>

                  <div class="evaluation-buttons">

                    <button class="eval-btn satisfied" :class="{ active: getEvaluationStatus() === 'satisfied' }"
                      @click="setEvaluation('satisfied')" :disabled="isGeneratingOutline">

                      <img src="@/assets/AIWriting/10.png" alt="满意" class="eval-icon">

                      满意

                    </button>

                    <button class="eval-btn unsatisfied" :class="{ active: getEvaluationStatus() === 'unsatisfied' }"
                      @click="setEvaluation('unsatisfied')" :disabled="isGeneratingOutline">

                      <img src="@/assets/AIWriting/11.png" alt="不满意" class="eval-icon">

                      不满意

                    </button>

                  </div>

                </div>

              </div>



              <!-- 操作按钮 -->

              <div class="sidebar-actions">

                <button class="action-btn secondary" @click="generateNewOutline"
                  :disabled="isGeneratingOutline || isGeneratingExam">

                  <img src="@/assets/Safety/12.png" alt="刷新" class="action-icon"
                    :class="{ 'rotating': isGeneratingOutline }">

                  生成新大纲

                </button>

                <button class="action-btn primary" @click="openWPS" :disabled="isGeneratingOutline || isGeneratingExam"
                  v-if="!currentPPTInfo">

                  继续创作

                  <img src="@/assets/Safety/13.png" alt="箭头" class="action-icon">

                </button>

                <!-- <button class="action-btn wps" @click="goToStep3" :disabled="isGeneratingOutline">

                  <img src="@/assets/Safety/28.png" alt="WPS AI PPT" class="action-icon">

                  WPS AI PPT

                </button> -->



                <!-- 动态生成的打开PPT按钮 -->

                <button v-if="showOpenPPTButton && currentPPTInfo" class="action-btn primary" @click="openTestPPT"
                  style="background: #EA580C; color: #fff;">

                  <!-- <img src="@/assets/Safety/28.png" alt="打开PPT" class="action-icon"> -->

                  修改PPT模板

                  <img src="@/assets/Safety/13.png" alt="箭头" class="action-icon">

                </button>



                <!-- 测试按钮 - 直接显示用于测试 -->

                <!-- <button class="action-btn test-btn" @click="openTestPPT"

                  style="background: linear-gradient(135deg, #ff7875, #ff9c6e); margin-left: 10px; font-size: 12px;">

                  测试PPT查看

                </button> -->

              </div>

            </div>

          </div>

        </div>



        <!-- 步骤三：PPT模板选择界面 -->

        <div v-if="currentStep === 'step3'" class="step3-content" :class="{ 'disabled': isApplyingTemplate }">

          <!-- 加载状态 -->

          <div v-if="isLoadingHistory" class="loading-overlay">

            <div class="loading-content">

              <div class="loading-spinner"></div>

              <div class="loading-text">正在加载历史记录</div>

              <div class="loading-subtitle">请稍候，正在为您准备数据...</div>

            </div>

          </div>

          <div class="template-container">



            <!-- 应用模板中遮罩层 -->

            <div v-if="isApplyingTemplate" class="applying-overlay">

              <div class="applying-content">

                <p>AI正在填充内容并应用模板，请稍候...</p>

              </div>

            </div>

            <!-- 左侧PPT预览区域 -->

            <div class="template-preview">

              <div class="preview-container">

                <div class="preview-header">

                  <h3 class="preview-title">预览效果</h3>

                  <span class="save-status">已保存于 {{ currentTime }}</span>

                </div>



                <!-- 大图轮播区域 -->

                <div class="main-carousel">

                  <!-- 预览模式：显示轮播图 -->

                  <div v-if="!showDownloadOptions">

                    <button class="carousel-btn prev" @click="prevSlide">

                      <img src="@/assets/Safety/23.png" alt="上一页" class="carousel-icon">

                    </button>



                    <div class="main-slide">

                      <!-- 如果有AI生成的PPT数据，显示AI内容 -->

                      <div v-if="generatedPPT && generatedPPT.length > 0" class="ai-generated-slide">

                        <div class="slide-content" :style="{ background: getCurrentPPTSlideBackground() }">

                          <div v-for="(element, index) in getCurrentPPTSlide().elements" :key="element.id"
                            class="slide-element" :style="getElementStyle(element)">

                            <div v-if="element.type === 'text'" v-html="element.content"></div>

                            <img v-else-if="element.type === 'image' && element.src" :src="element.src"
                              :alt="element.id">

                          </div>

                        </div>

                      </div>

                      <!-- 否则显示默认图片 -->

                      <img v-else :src="currentSlideImage" :alt="`第${currentSlideIndex + 1}页`" class="slide-image">

                    </div>



                    <button class="carousel-btn next" @click="nextSlide">

                      <img src="@/assets/Safety/24.png" alt="下一页" class="carousel-icon">

                    </button>

                  </div>



                  <!-- PPT编辑工具台 -->

                  <div v-if="showDownloadOptions" class="ppt-editor-workspace">

                    <div class="editor-canvas">

                      <!-- PPT预览模式 -->

                      <div v-if="showDownloadOptions && generatedPPT.length > 0" class="slide-preview" :style="{

                        transform: `scale(${zoom})`,

                        background: getCurrentPPTSlideBackground()

                      }">

                        <!-- 调试信息 -->

                        <div
                          style="position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 5px; border-radius: 4px; font-size: 12px; z-index: 1001;">

                          {{ generatedPPT.length }}页, 当前第{{ currentPPTSlideIndex + 1 }}页

                        </div>



                        <!-- PPT预览提示 -->

                        <div class="ppt-preview-tip">

                          <p>💡 点击背景图片、素材图片可更换</p>

                        </div>



                        <!-- 显示PPT元素 -->

                        <div v-for="(element, index) in getCurrentPPTSlideForMainView().elements" :key="element.id"
                          class="preview-element" :class="{ selected: selectedPPTElementIndex === index }"
                          :style="getElementStyle(element)" @click.stop="selectPPTElement(index)"
                          @dblclick="handleDoubleClickDisabled(index)" @mousedown="startPPTDrag($event, index)">

                          <!-- 缩放手柄 -->

                          <div v-if="selectedPPTElementIndex === index" class="resize-handle resize-handle-nw"
                            @mousedown.stop="startPPTResize($event, index, 'nw')"></div>

                          <div v-if="selectedPPTElementIndex === index" class="resize-handle resize-handle-ne"
                            @mousedown.stop="startPPTResize($event, index, 'ne')"></div>

                          <div v-if="selectedPPTElementIndex === index" class="resize-handle resize-handle-sw"
                            @mousedown.stop="startPPTResize($event, index, 'sw')"></div>

                          <div v-if="selectedPPTElementIndex === index" class="resize-handle resize-handle-se"
                            @mousedown.stop="startPPTResize($event, index, 'se')"></div>



                          <!-- 拖拽手柄 -->

                          <div v-if="selectedPPTElementIndex === index" class="drag-handle"
                            @mousedown.stop="startPPTDrag($event, index)">

                            {{ getElementTypeName(element.type) }}

                          </div>



                          <!-- 图片元素 -->

                          <img v-if="element.type === 'image' && element.src" :src="element.src" :alt="element.id"
                            :style="{ opacity: element.opacity || 1 }" @click="changePPTImage(index)" @mousedown.stop />



                          <!-- 文本元素 -->

                          <template v-else-if="element.type === 'text'">

                            <!-- 内联编辑态 -->

                            <div v-if="editingPPTElementIndex === index" class="inline-editor" contenteditable="true"
                              v-html="editingPPTHtml" :style="{

                                color: element.defaultColor,

                                fontFamily: element.defaultFontName,

                                opacity: element.opacity || 1

                              }" @input="onPPTInlineInput" @blur="savePPTInlineEdit(index)" @mousedown.stop
                              @keydown.stop></div>

                            <!-- 普通显示态 -->

                            <div v-else v-html="element.content" :style="{

                              color: element.defaultColor,

                              fontFamily: element.defaultFontName,

                              opacity: element.opacity || 1

                            }"></div>

                          </template>



                          <!-- 形状元素 -->

                          <div v-else-if="element.type === 'shape'" class="shape" :style="getShapeStyle(element)"></div>

                        </div>

                      </div>



                      <!-- 编辑模式：显示编辑界面 -->

                      <div v-else-if="showDownloadOptions && generatedPPT.length === 0" class="edit-mode">

                        <div class="slide-editor">

                          <div class="slide-content" contenteditable="true" @input="updateSlideContent">

                            <h2 class="slide-title">{{ currentEditingSlide.title || '点击编辑标题' }}</h2>

                            <div class="slide-body">

                              <p>{{ currentEditingSlide.content || '点击编辑内容' }}</p>

                            </div>

                          </div>

                        </div>

                      </div>

                    </div>

                  </div>

                </div>



                <!-- 缩略图导航 -->

                <div class="thumbnail-nav">

                  <!-- 模板预览模式：显示模板缩略图 -->

                  <div v-if="!showDownloadOptions">

                    <div class="slide-counter">

                      第{{ currentSlideIndex + 1 }}页/共{{ slideImages.length }}页

                      <div class="progress-dots">

                        <div v-for="(slide, index) in slideImages" :key="index"
                          :class="['progress-dot', { active: index === currentSlideIndex }]" @click="goToSlide(index)">

                        </div>

                      </div>

                    </div>



                    <div class="thumbnail-strip" @wheel="handleThumbnailWheel" ref="thumbnailStrip">

                      <div v-for="(slide, index) in slideImages" :key="index"
                        :class="['thumbnail-item', { active: index === currentSlideIndex }]" @click="goToSlide(index)">

                        <img :src="slide" :alt="`第${index + 1}页`" class="thumbnail-image">

                        <div class="thumbnail-number">{{ index + 1 }}</div>

                      </div>

                    </div>

                  </div>



                  <!-- PPT预览模式：显示PPT缩略图 -->

                  <div v-if="showDownloadOptions && generatedPPT.length > 0">

                    <div class="slide-counter">

                      第{{ currentPPTSlideIndex + 1 }}页/共{{ generatedPPT.length }}页

                      <div class="progress-dots">

                        <div v-for="(slide, index) in generatedPPT" :key="index"
                          :class="['progress-dot', { active: index === currentPPTSlideIndex }]"
                          @click="goToPPTSlide(index)">

                        </div>

                      </div>

                    </div>



                    <div class="thumbnail-strip" @wheel="handleThumbnailWheel" ref="thumbnailStrip">

                      <div v-for="(slide, index) in generatedPPT" :key="index"
                        :class="['thumbnail-item', { active: index === currentPPTSlideIndex }]"
                        @click="goToPPTSlide(index)">

                        <div class="thumbnail-preview" :style="{ background: getSlideBackground(slide) }">

                          <div class="thumbnail-content">

                            <div v-for="element in slide.elements" :key="element.id" class="thumbnail-element"
                              :style="getThumbnailElementStyle(element)">

                              <div v-if="element.type === 'text'" v-html="element.content"></div>

                              <img v-else-if="element.type === 'image' && element.src" :src="element.src"
                                :alt="element.id">

                            </div>

                          </div>

                        </div>

                        <div class="thumbnail-number">{{ index + 1 }}</div>

                      </div>

                    </div>

                  </div>

                </div>

              </div>

            </div>

            <!-- 右侧侧边栏 -->

            <div class="template-sidebar">

              <!-- 模板样式选择 -->

              <div v-if="!showDownloadOptions" class="template-content" :class="{ 'disabled': isApplyingTemplate }">

                <h4 class="sidebar-title">模板样式 ({{ templateStyles.length }})</h4>



                <div class="template-list">

                  <div v-for="(template, index) in templateStyles" :key="index"
                    :class="['template-item', { active: selectedTemplate === index }]"
                    @click="selectTemplate(index); resetSlidePosition()">

                    <div class="template-thumbnail">

                      <img :src="template.thumbnail" :alt="template.title" class="template-img">

                      <!-- <div v-if="template.type === 'dynamic'" class="dynamic-badge">动态</div> -->

                    </div>

                    <div class="template-info">

                      <h5 class="template-title">{{ template.title }}</h5>

                      <div class="template-meta">

                        <span class="update-time">{{ template.updateTime }}</span>

                        <span class="page-count">{{ template.pageCount }}页</span>

                      </div>

                      <div v-if="template.description" class="template-description">

                        {{ template.description }}

                      </div>

                    </div>

                  </div>

                </div>



                <!-- 动态模板预览信息 -->

                <!-- <div v-if="isDynamicTemplate && templatePreview" class="dynamic-preview">

                  <h5 class="preview-title">📊 大纲分析</h5>

                  <div class="preview-stats">

                    <div class="stat-item">

                      <span class="stat-label">章节数:</span>

                      <span class="stat-value">{{ templatePreview.chapterCount }}</span>

                    </div>

                    <div class="stat-item">

                      <span class="stat-label">小节数:</span>

                      <span class="stat-value">{{ templatePreview.totalSections }}</span>

                    </div>

                    <div class="stat-item">

                      <span class="stat-label">复杂度:</span>

                      <span class="stat-value" :class="'complexity-' + templatePreview.complexity">

                        {{ templatePreview.complexity === 'simple' ? '简单' : 

                           templatePreview.complexity === 'medium' ? '中等' : '复杂' }}

                      </span>

                    </div>

                    <div class="stat-item">

                      <span class="stat-label">预计页数:</span>

                      <span class="stat-value">{{ templatePreview.estimatedSlides }}</span>

                    </div>

                  </div>

                  

                  <div v-if="templatePreview.recommendations && templatePreview.recommendations.length > 0" class="recommendations">

                    <h6 class="recommendations-title">💡 优化建议</h6>

                    <ul class="recommendations-list">

                      <li v-for="(rec, index) in templatePreview.recommendations" :key="index">

                        {{ rec }}

                      </li>

                    </ul>

                  </div>

                  

                  <div v-if="templatePreview.validation && templatePreview.validation.warnings && templatePreview.validation.warnings.length > 0" class="warnings">

                    <h6 class="warnings-title">⚠️ 注意事项</h6>

                    <ul class="warnings-list">

                      <li v-for="(warning, index) in templatePreview.validation.warnings" :key="index">

                        {{ warning }}

                      </li>

                    </ul>

                  </div>

                </div> -->



                <!-- 应用模板中遮罩层 -->

                <div v-if="isApplyingTemplate" class="applying-overlay">

                  <div class="applying-content">

                    <p>AI正在填充内容并应用模板，请稍候...</p>

                  </div>

                </div>

              </div>



              <!-- 下载选项 -->

              <div v-if="showDownloadOptions" class="download-content"
                :class="{ 'disabled': isGeneratingTrainingMaterial || isGeneratingExam }">

                <h4 class="sidebar-title">下载选项</h4>



                <!-- 培训讲义生成中遮罩层 -->

                <div v-if="isGeneratingTrainingMaterial" class="applying-overlay">

                  <div class="applying-content">

                    <p>AI正在生成培训讲义，请稍候...</p>

                  </div>

                </div>



                <!-- 考试工坊生成中遮罩层 -->

                <div v-if="isGeneratingExam" class="applying-overlay">

                  <div class="applying-content">

                    <p>AI正在生成考试题目，请稍候...</p>

                  </div>

                </div>



                <div class="download-options">

                  <div v-for="(option, index) in downloadOptions" :key="index"
                    :class="['download-option', { active: selectedDownloadOption === index }]"
                    @click="selectDownloadOption(index)">

                    <div class="option-icon">

                      <img :src="option.icon" :alt="option.title" class="option-img">

                    </div>

                    <div class="option-info">

                      <h5 class="option-title">{{ option.title }}</h5>

                      <p class="option-description">{{ option.description }}</p>

                    </div>

                    <div v-if="selectedDownloadOption === index" class="option-check">

                      <img src="@/assets/Safety/29.png" alt="选中" class="check-icon">

                    </div>

                  </div>

                </div>



                <div class="download-actions">

                  <button class="action-btn secondary" @click="goBackToTemplate">

                    重新挑选模板

                  </button>

                  <button class="action-btn primary" @click="exportPPTX" :disabled="isDownloading">

                    <img src="@/assets/Safety/30.png" alt="下载" class="download-icon">

                    {{ isDownloading ? '生成中...' : '立即下载' }}

                  </button>

                </div>

              </div>

            </div>

          </div>



          <!-- 操作按钮 -->

          <div class="preview-actions" v-if="!showDownloadOptions">

            <button class="action-btn secondary" @click="goToStep2">

              返回编辑大纲

            </button>

            <!-- Mock数据选择器 -->

            <!-- <div class="mock-data-selector" style="margin-bottom: 16px;">

              <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #374151;">选择测试数据:</label>

              <select v-model="selectedMockData" style="width: 100%; padding: 8px; border: 1px solid #d1d5db; border-radius: 6px; background: white;">

                <option v-for="(option, index) in mockDataOptions" :key="option.id" :value="index">

                  {{ option.title }} - {{ option.description }}

                </option>

              </select>

            </div> -->



            <!-- 使用用户大纲选项 -->

            <!-- <div class="user-outline-option" style="margin-bottom: 16px; padding: 16px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px;">

              <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #374151;">使用我之前生成的大纲:</label>

              <div style="display: flex; align-items: center; gap: 8px;">

                <input 

                  type="checkbox" 

                  id="useUserOutline" 

                  v-model="useUserOutline" 

                  style="margin: 0;"

                />

                <label for="useUserOutline" style="margin: 0; font-weight: 500; color: #4b5563;">

                  使用当前大纲数据 ({{ outlineData.length > 0 ? `${outlineData.length}个章节` : '无大纲数据' }})

                </label>

              </div>

              <div v-if="useUserOutline && outlineData.length === 0" style="margin-top: 8px; padding: 8px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 4px; color: #dc2626; font-size: 14px;">

                ⚠️ 当前没有大纲数据，请先生成大纲后再使用此选项

              </div>

              <div v-else-if="useUserOutline && outlineData.length > 0" style="margin-top: 8px; padding: 8px; background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 4px; color: #0369a1; font-size: 14px;">

                ✅ 检测到 {{ outlineData.length }} 个章节的大纲数据，可以用于测试

              </div>

            </div> -->



            <!-- 测试动态模板按钮 -->

            <!-- <button class="action-btn test" @click="testDynamicTemplateFill" style="background: #10b981; margin-right: 8px;">

              测试动态模板

            </button> -->

            <button class="action-btn primary" @click="applyTemplate" :disabled="isApplyingTemplate">

              <span v-if="!isApplyingTemplate">应用此模板</span>

              <span v-else>正在处理中...</span>

            </button>

            <!-- <button class="action-btn secondary" @click="testOutlineToAIPPT" style="margin-left: 10px;">

          转换并应用模板5

        </button> -->

          </div>

        </div>







      </div>



      <!-- 推荐问题 -->

      <div v-if="currentStep === 'step1' && !showChat && !selectedFile" class="recommended-questions">

        <div v-for="(question, index) in hotQuestions" :key="question.id || index" class="question-tag"
          @click="handleRecommendedQuestion(question.question)">

          <img :src="getQuestionIcon(question.question)" alt="问题" class="question-icon">

          {{ question.question }}

        </div>



        <!-- 如果没有数据，显示默认问题 -->

        <div v-if="hotQuestions.length === 0" class="question-tag"
          @click="handleRecommendedQuestion('施工现场安全培训的主要内容有哪些？')">

          <img src="@/assets/Chat/12.png" alt="问题" class="question-icon">

          施工现场安全培训的主要内容有哪些？

        </div>

        <div v-if="hotQuestions.length === 0" class="question-tag"
          @click="handleRecommendedQuestion('高空作业安全防护措施有哪些要求？')">

          <img src="@/assets/Chat/10.png" alt="问题" class="question-icon">

          高空作业安全防护措施有哪些要求？

        </div>

        <div v-if="hotQuestions.length === 0" class="question-tag" @click="handleRecommendedQuestion('《建设工程安全生产管理条例》')">

          <img src="@/assets/Chat/11.png" alt="文档" class="question-icon">

          《建设工程安全生产管理条例》

        </div>

      </div>



      <!-- 底部输入区域 -->

      <div v-if="currentStep === 'step1'" class="chat-input-section">

        <div class="input-container">

          <!-- 文件预览区域 -->

          <div v-if="selectedFile" class="file-preview-section">

            <div class="file-preview">

              <div class="file-icon">
                <img v-if="selectedFile.type === '.doc' || selectedFile.type === '.docx'" :src="selectedFile.icon" alt="文档图标" class="file-icon-img">
                <span v-else>{{ selectedFile.icon }}</span>
              </div>

              <div class="file-info">

                <div class="file-name">{{ selectedFile.name }}</div>

                <div class="file-size">{{ formatFileSize(selectedFile.size) }}</div>

              </div>

              <button class="remove-file-btn" @click="removeSelectedFile">

                <span class="remove-icon">×</span>

              </button>

            </div>

          </div>



          <div class="input-box">

            <button class="attach-btn" @click="triggerFileUpload" :disabled="isSending || hasTypingMessage">

              <div class="icon-container">

                <img src="@/assets/Chat/9.png" alt="附件" class="action-icon"
                  style="width: 20px; height: 20px; max-width: 20px; max-height: 20px;">

              </div>

            </button>

            <input type="text" placeholder="请在此处发送消息 (Enter键可立即发送)" class="message-input" v-model="messageText"
              @keyup.enter="sendMessage" @input="handleInput" :disabled="isSending || hasTypingMessage"
              maxlength="2000">

            <button class="voice-btn" @click="handleVoiceClick" :disabled="isSending || hasTypingMessage"
              :class="{ 'recording': isListening }">

              <div class="icon-container">

                <img src="@/assets/Chat/18.png" alt="语音" class="action-icon"
                  style="width: 20px; height: 20px; max-width: 20px; max-height: 20px;">

                <div v-if="isListening" class="recording-indicator"></div>

              </div>

            </button>

            <div class="divider"></div>

            <button class="send-btn" @click="sendMessage"
              :disabled="isSending || hasTypingMessage || !messageText.trim()">

              <img :src="messageText.trim() && !isSending && !hasTypingMessage ? sendIconFilled : sendIconEmpty"
                alt="发送" class="send-icon">

            </button>

          </div>

        </div>

      </div>



      <!-- 隐藏的文件输入框 -->

      <input ref="fileInput" type="file" accept=".docx" style="display: none" @change="handleFileSelect" />



      <!-- 隐藏的图片输入框 -->

      <input ref="imageInput" type="file" accept="image/*" style="display: none" @change="handleImageSelect" />





      <!-- 删除确认弹窗 -->

      <DeleteConfirmModal :visible="showDeleteModal" title="删除历史记录" :message="deleteConfirmMessage"
        @confirm="confirmDeleteHistory" @cancel="cancelDeleteHistory" @close="cancelDeleteHistory" />



      <!-- 自定义居中提示 -->
      <div v-if="showCopyToast" class="copy-toast-overlay" 
        style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; z-index: 10000; pointer-events: none;">
        <div class="copy-toast" 
          style="background: rgba(0, 0, 0, 0.8); color: white; padding: 16px 24px; border-radius: 8px; font-size: 16px; font-weight: 500; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);">
          已复制大纲
        </div>
      </div>

      <!-- WPS AI PPT集成弹窗 -->

      <div v-if="showWPSModal" class="wps-modal-overlay" @click="showWPSModal = false"
        style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 9999;">

        <div class="wps-modal" @click.stop
          style="width: 98%; height: 95%; max-width: none; max-height: none; background: white; border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); display: flex; flex-direction: column; overflow: hidden;">

          <div class="wps-modal-header"
            style="display: flex; justify-content: space-between; align-items: center; padding: 16px 24px; background: #f8f9fa; border-bottom: 1px solid #e9ecef;">

            <h3 style="margin: 0; font-size: 18px; font-weight: 600; color: #333;">

              <!-- {{ currentPPTInfo && selectedWPSUrl !== 'https://aippt.wps.cn/aippt/' ? `

              ${currentPPTInfo.file_name}` :

                '蜀安 AI PPT' }} -->

            </h3>

            <button class="wps-modal-close" @click="showWPSModal = false"
              style="background: none; border: none; font-size: 24px; color: #666; cursor: pointer; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;">×</button>

          </div>

          <div class="wps-modal-content" style="display: flex; flex-direction: column; flex: 1; min-height: 0;">

            <!-- 下载监听插件控制 -->

            <!-- <div style="padding: 8px 24px; flex-shrink: 0; background: #f8f9fa; border-bottom: 1px solid #e9ecef;">

              <label style="font-size: 12px; display: flex; align-items: center; gap: 4px;">

                <input type="checkbox" v-model="isDownloadListenerActive" @change="toggleDownloadListener"

                  style="margin: 0;" />

                启用下载监听插件

              </label>

            </div> -->

            <div class="wps-iframe-container" style="flex: 1; min-height: 0; position: relative;">

              <iframe :src="selectedWPSUrl" frameborder="0" allowfullscreen class="wps-iframe" title="WPS AI PPT"
                @error="handleIframeError" style="width: 100%; height: calc(100% - 30px); border: none;"></iframe>

              <!-- 遮罩层：根据PPT创建状态显示不同遮罩 -->

              <!-- PPT创建前：显示"蜀安AI PPT"遮罩 -->

              <div v-if="!currentPPTInfo" class="wps-iframe-mask-top-right"
                style="position: absolute; top: 8px; left: 8px; width: 160px; height: 54px; background: #F8F8F8; border-radius: 8px; z-index: 5; pointer-events: none; opacity: 0.98; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; color: #333333">

                蜀安AI PPT

              </div>



              <!-- PPT创建后：延迟3秒显示另一个遮罩 -->

              <!-- <div 

                v-if="currentPPTInfo && showSecondMask"

                class="wps-iframe-mask-top-center" 

                style="position: absolute; top: 8px; left: 930px; width: 80px; height: 24px; background: #EEEEEE; border-radius: 8px; z-index: 5; pointer-events: none; opacity: 0.98; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; color: #333333">

              </div> -->

            </div>

          </div>

        </div>

      </div>

    </div>

  </div>

</template>



<script setup>

import { ref, computed, onMounted, onUnmounted, reactive, nextTick, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'

import Sidebar from '@/components/Sidebar.vue'

import DeleteConfirmModal from '@/components/DeleteConfirmModal.vue'

import { apis } from '@/request/apis.js'

// ===== 已删除：getUserId - 不再需要，改用token =====
// import { getUserId } from '@/utils/userManager.js'



// 导入语音识别组件

import { useSpeechRecognition } from '@/composables/useSpeechRecognition'



// 导入发送按钮图标

import sendIconEmpty from '@/assets/Chat/15.png'

import sendIconFilled from '@/assets/Chat/16.png'



// 导入其他图片资源

import safetyTrainingIcon from '@/assets/Safety/4.png'

import safetyAssessmentIcon from '@/assets/Safety/3.png'

import safetyRegulationsIcon from '@/assets/Safety/2.png'

import emergencyProceduresIcon from '@/assets/Safety/1.png'

import defaultHistoryIcon from '@/assets/Safety/6.png'

import copyIcon from '@/assets/AIWriting/5.png'

import editIcon from '@/assets/AIWriting/6.png'

import regenerateIcon from '@/assets/AIWriting/7.png'

import deleteIcon from '@/assets/AIWriting/8.png'

import voiceIcon from '@/assets/AIWriting/9.png'

import likeIcon from '@/assets/AIWriting/10.png'

import dislikeIcon from '@/assets/AIWriting/11.png'

import questionIcon1 from '@/assets/Chat/12.png'

import questionIcon2 from '@/assets/Chat/10.png'

import questionIcon3 from '@/assets/Chat/11.png'



// 导入template5图片

import template5Slide1 from '@/assets/template5/template5-slide-1.png'

import template5Slide2 from '@/assets/template5/template5-slide-2.png'

import template5Slide3 from '@/assets/template5/template5-slide-3.png'

import template5Slide4 from '@/assets/template5/template5-slide-4.png'

import template5Slide5 from '@/assets/template5/template5-slide-5.png'



// 导入template7图片

import template7Slide1 from '@/assets/template7/template7-slide-1.png'

import template7Slide2 from '@/assets/template7/template7-slide-2.png'

import template7Slide3 from '@/assets/template7/template7-slide-3.png'

import template7Slide4 from '@/assets/template7/template7-slide-4.png'

import template7Slide5 from '@/assets/template7/template7-slide-5.png'



// 导入template8图片

import template8Slide1 from '@/assets/template8/template8-slide-1.png'

import template8Slide2 from '@/assets/template8/template8-slide-2.png'

import template8Slide3 from '@/assets/template8/template8-slide-3.png'

import template8Slide4 from '@/assets/template8/template8-slide-4.png'

import template8Slide5 from '@/assets/template8/template8-slide-5.png'



// 导入JSON文件

import template5JsonData from '@/assets/mocks/template_5.json'

import template7JsonData from '@/assets/mocks/template_7.json'

import template8JsonData from '@/assets/mocks/template_8.json'

import aipptJsonData from '@/assets/mocks/AIPPT.json'



// 导入动态模板生成器

import {

  generateDynamicTemplate,

  selectOptimalTemplate,

  validateOutlineForDynamicTemplate,

  getAvailableTemplateStyles

} from '@/utils/dynamicTemplateGenerator.js'

import {

  matchOutlineAndGeneratePPT,

  previewOutlineMatch

} from '@/utils/outlineMatchingAlgorithm.js'



// 导入Element Plus组件

import { ElMessage, ElMessageBox } from 'element-plus'



// 路由
const router = useRouter()
const route = useRoute()

// 响应式数据

const messageText = ref('')

const selectedHistoryItem = ref(null) // 选中的历史记录项

const ai_conversation_id = ref(0) // 对话ID



// 保存状态管理

const lastSavedOutlineData = ref(null) // 上次保存的大纲数据

const lastSavedPPTData = ref(null) // 上次保存的PPT数据

const isSaving = ref(false) // 是否正在保存中

const isSwitchingHistory = ref(false) // 是否正在切换历史记录



// 删除相关状态

const showDeleteModal = ref(false) // 控制是否显示删除确认弹窗

const deleteTargetItem = ref(null) // 要删除的目标项

const currentStep = ref('step1') // 当前步骤：step1-步骤一，step2-步骤二，step3-步骤三

const showOutline = ref(false) // 控制是否显示大纲

const evaluation = ref('') // 大纲评价状态

const outlineFeedback = ref(null) // 大纲的用户反馈状态（从后端获取）

const currentAiMessageId = ref(null) // 当前AI消息的ID

const outlineId = ref(null) // 大纲的ID

const isGeneratingOutline = ref(false) // 控制是否正在生成新大纲
const isGeneratingExam = ref(false) // 控制是否正在生成考题

const isApplyingTemplate = ref(false) // 控制是否正在应用模板

const isLoadingHistory = ref(false) // 控制是否正在加载历史记录

const isGeneratingTrainingMaterial = ref(false) // 控制是否正在生成培训讲义

const isProcessing = ref(false) // 控制是否正在处理中（生成PPT或下载时禁用其他操作）



// 功能卡片和热点问题数据

const functionCards = ref([])

const hotQuestions = ref([])



// 聊天相关状态

const showChat = ref(false) // 控制是否显示聊天界面

const chatMessages = ref([]) // 聊天消息历史

const isSending = ref(false) // 控制发送状态



const showWPSModal = ref(false) // 控制是否显示WPS AI PPT集成弹窗

const showCopyToast = ref(false) // 控制是否显示复制大纲提示

const selectedWPSUrl = ref('https://aippt.wps.cn/aippt/') // 当前选择的WPS URL，默认进入WPS AI PPT



// 下载监听插件相关

const isDownloadListenerActive = ref(true) // 是否启用下载监听，默认选中

const downloadHistory = ref([]) // 下载历史记录

const lastDownloadTime = ref(null) // 最后一次下载时间



// PPT打开功能相关

const currentPPTInfo = ref(null) // 当前检测到的PPT信息

const showOpenPPTButton = ref(false) // 是否显示打开PPT按钮

const showSecondMask = ref(false) // 是否显示第二个遮罩（延迟3秒）



// 文件上传相关

const selectedFile = ref(null)

const isUploadingFile = ref(false)

const fileContent = ref('') // 存储文件内容



// 文件处理配置

const fileConfig = reactive({

  maxSize: 20 * 1024 * 1024, // 20MB

  allowedTypes: ['.docx'] // 只允许.docx格式的Word文档

})



// OSS上传配置

const uploadData = reactive({

  OssAccessKeyId: '',

  policy: '',

  Signature: '',

  host: '',

  dir: '',

  key: ''

})



// 语音功能

const {

  isSupported: speechSupported,

  isListening,

  isSpeaking: speechIsSpeaking,

  transcript,

  error: speechError,

  startListening,

  stopListening,

  speakText,

  stopSpeaking

} = useSpeechRecognition()



// 记录正在朗读的消息ID

const speakingMessageId = ref(null)



// 计算属性 - 是否有正在打字的AI消息

const hasTypingMessage = computed(() => {

  return chatMessages.value.some(message => message.type === 'ai' && message.isTyping)

})



// 删除确认消息

const deleteConfirmMessage = computed(() => {

  const title = deleteTargetItem.value?.item?.title || ''

  return `确定要删除历史记录"${title}"吗？删除后将无法恢复。`

})



// PPT生成相关数据

const outlineData = ref(null) // 大纲数据

const outlineStats = ref({}) // 大纲统计信息

const outlineTitle = ref('') // 大纲标题



// 计算大纲统计信息

const calculateOutlineStats = (chapters) => {

  if (!chapters || !Array.isArray(chapters)) {

    return {

      totalChapters: 0,

      totalSections: 0,

      estimatedPages: '0页',

      estimatedTime: '0分钟'

    }

  }



  const totalChapters = chapters.length

  let totalSections = 0



  // 计算总小节数

  chapters.forEach((chapter, index) => {

    if (chapter.sections && Array.isArray(chapter.sections)) {

      totalSections += chapter.sections.length

    }

  })



  // 根据章节和小节数预估PPT页数和讲解时长

  const estimatedPages = estimatePPTPages(chapters, totalSections)

  const estimatedTime = estimatePresentationTime(chapters, totalSections)



  return {

    totalChapters,

    totalSections,

    estimatedPages,

    estimatedTime

  }

}



// 预估PPT页数

const estimatePPTPages = (chapters, sections) => {

  // 基础页面：封面(1) + 目录(1) + 结束页(1) = 3页

  let basePages = 3



  // 每个章节：过渡页(1) + 内容页(根据小节数计算)

  let contentPages = 0

  chapters.forEach(chapter => {

    if (chapter.sections && chapter.sections.length > 0) {

      // 每个章节至少1个过渡页

      contentPages += 1



      // 每个小节1-2页内容页

      chapter.sections.forEach(section => {

        if (section.subsections && section.subsections.length > 0) {

          // 根据子小节数量决定页数

          const subsectionCount = section.subsections.length

          if (subsectionCount <= 2) {

            contentPages += 1 // 1-2个子小节用1页

          } else if (subsectionCount <= 4) {

            contentPages += 2 // 3-4个子小节用2页

          } else {

            contentPages += 3 // 5个以上子小节用3页

          }

        } else {

          contentPages += 1 // 没有子小节，默认1页

        }

      })

    } else {

      // 章节没有小节，默认1页过渡页 + 1页内容页

      contentPages += 2

    }

  })



  const totalPages = basePages + contentPages

  return `${totalPages}页`

}



// 预估讲解时长

const estimatePresentationTime = (chapters, sections) => {

  // 基础时间：开场(2分钟) + 结束(3分钟) = 5分钟

  let baseTime = 5



  // 每个章节的讲解时间

  let contentTime = 0

  chapters.forEach(chapter => {

    if (chapter.sections && chapter.sections.length > 0) {

      // 每个章节过渡页：1-2分钟

      contentTime += 2



      // 每个小节：3-5分钟

      chapter.sections.forEach(section => {

        if (section.subsections && section.subsections.length > 0) {

          const subsectionCount = section.subsections.length

          if (subsectionCount <= 2) {

            contentTime += 3 // 1-2个子小节用3分钟

          } else if (subsectionCount <= 4) {

            contentTime += 5 // 3-4个子小节用5分钟

          } else {

            contentTime += 7 // 5个以上子小节用7分钟

          }

        } else {

          contentTime += 4 // 没有子小节，默认4分钟

        }

      })

    } else {

      // 章节没有小节，默认2分钟过渡 + 4分钟内容

      contentTime += 6

    }

  })



  const totalTime = baseTime + contentTime

  return `${totalTime}分钟`

}



// 更新大纲统计信息

const updateOutlineStats = () => {

  if (outlineData.value) {

    outlineStats.value = calculateOutlineStats(outlineData.value)

  }

}





// 编辑相关状态

const editingItem = ref(null) // 当前正在编辑的项目

const editingType = ref('') // 编辑类型：'title', 'chapter', 'section', 'subsection'

const editingIndex = ref(null) // 编辑项目的索引

const editingContent = ref('') // 编辑内容



// 拖拽相关状态

const draggedChapterIndex = ref(null) // 正在拖拽的章节索引

const dragOverChapterIndex = ref(null) // 拖拽悬停的章节索引

const showEditOptions = ref(null) // 显示编辑选项的项目ID



// 步骤三相关数据

const currentSlideIndex = ref(0) // 当前幻灯片索引

const selectedTemplate = ref(0) // 选中的模板索引



// PPT编辑相关数据

const currentEditingSlide = ref({ title: '', content: '' }) // 当前编辑的幻灯片内容

const pptSlides = ref([]) // PPT幻灯片数据



// 步骤四相关数据

const selectedDownloadOption = ref(0) // 选中的下载选项索引

const showDownloadOptions = ref(false) // 控制是否显示下载选项



// 预览模式相关数据

const previewMode = ref('edit') // 预览模式：'edit' - 编辑模式, 'preview' - 预览模式



// PPT预览相关数据

const isPPTPreviewMode = ref(false) // 是否处于PPT预览模式

const generatedPPT = ref([]) // 生成的PPT数据

const currentPPTSlideIndex = ref(0) // 当前PPT幻灯片索引

const selectedPPTElementIndex = ref(-1) // 选中的PPT元素索引

const editingPPTElementIndex = ref(-1) // 正在编辑的PPT元素索引

const editingPPTHtml = ref('') // 正在编辑的PPT HTML内容

const zoom = ref(1) // 缩放比例

const selectedImageIndex = ref(null) // 当前选中的图片索引







// 幻灯片图片数组

const slideImages = ref([

  template5Slide1, // 第1页

  template5Slide2, // 第2页

  template5Slide3, // 第3页

  template5Slide4, // 第4页

  template5Slide5  // 第5页

])



// 模板样式数据

const templateStyles = ref([

  // {

  //   thumbnail: template5Slide1,

  //   title: '通用类PPT',

  //   updateTime: '2025-09-01 14:23 更新',

  //   pageCount: 5,

  //   type: 'static'

  // },

  {

    thumbnail: template5Slide1,

    title: '通用类PPT',

    updateTime: '2025-01-15 10:00 更新',

    pageCount: '5',

    type: 'dynamic', // 恢复dynamic类型

    // description: '支持2-6章节，2-4小节的灵活组合',

    style: 'default'

  },

  {

    thumbnail: template7Slide1, // 使用相同的缩略图，实际会显示红色主题

    title: '红色主题PPT',

    updateTime: '2025-01-15 10:00 更新',

    pageCount: '5',

    type: 'static', // 改回static类型，使用我们设计的template_7.json

    // description: '红色主题的PPT模板',

    style: 'red', // 恢复红色主题样式

    templateData: template7JsonData // 使用我们精心设计的红色主题结构

  },

  // {

  //   thumbnail: template8Slide1, // 使用模板8的缩略图

  //   title: '蓝色科技主题PPT',

  //   updateTime: '2025-01-15 10:00 更新',

  //   pageCount: '5',

  //   type: 'static', // 使用我们设计的template_8.json

  //   description: '蓝色科技风格的PPT模板',

  //   style: 'blueTech', // 蓝色科技主题样式

  //   templateData: template8JsonData // 使用我们精心设计的蓝色科技主题结构

  // }

])



// 动态模板相关状态

const isDynamicTemplate = ref(false)

const templatePreview = ref(null)

const templateAnalysis = ref(null)



// 填充动态模板内容（使用AI填充详细内容）

const fillDynamicTemplateContentWithAI = async (template, outlineData, title) => {

  try {

    console.log('开始填充动态模板内容（AI增强）...')

    console.log('模板数据:', template)

    console.log('大纲数据:', outlineData)

    console.log('标题:', title)



    // 第一步：填充基本内容（标题等）

    const basicFilledTemplate = fillDynamicTemplateBasicContent(template, outlineData, title)



    // 第二步：使用AI填充详细内容

    console.log('开始使用AI填充详细内容...')

    const aiFilledTemplate = await fillDynamicTemplateWithAI(basicFilledTemplate, outlineData, title)



    console.log('动态模板内容填充完成（AI增强）:', aiFilledTemplate)

    return aiFilledTemplate



  } catch (error) {

    console.error('填充动态模板内容失败（AI增强）:', error)

    throw error

  }

}



// 填充动态模板基本内容

const fillDynamicTemplateBasicContent = (template, outlineData, title) => {

  try {

    console.log('开始填充动态模板基本内容...')

    console.log('模板数据:', template)

    console.log('大纲数据:', outlineData)

    console.log('标题:', title)



    const filledTemplate = JSON.parse(JSON.stringify(template)) // 深拷贝



    // 填充封面页

    if (filledTemplate[0] && filledTemplate[0].type === 'cover') {

      filledTemplate[0].elements.forEach(element => {

        if (element.textType === 'title') {

          element.content = `<p style="text-align: center;"><strong><span style="font-size: ${getResponsiveFontSize(48)}px; color: ${element.defaultColor}; text-shadow: 2px 2px 8px rgba(0,0,0,0.5);">${title}</span></strong></p>`

        } else if (element.textType === 'content') {

          element.content = `<p style="text-align: center;"><span style="font-size: ${getResponsiveFontSize(24)}px; color: ${element.defaultColor};">基于AI生成的培训大纲，包含相关内容</span></p>`

        }

      })

    }



    // 填充目录页

    if (filledTemplate[1] && filledTemplate[1].type === 'contents') {

      const chapterTitles = outlineData.map(chapter => chapter.title)

      let itemIndex = 0



      filledTemplate[1].elements.forEach(element => {

        if (element.textType === 'item' && itemIndex < chapterTitles.length) {

          element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: ${element.defaultColor};">${itemIndex + 1}. ${chapterTitles[itemIndex]}</span></p>`

          itemIndex++

        }

      })

    }



    // 填充章节内容页

    let slideIndex = 2 // 从第3张幻灯片开始（封面+目录后）



    outlineData.forEach((chapter, chapterIndex) => {

      // 章节过渡页

      if (filledTemplate[slideIndex] && filledTemplate[slideIndex].type === 'transition') {

        filledTemplate[slideIndex].elements.forEach(element => {

          if (element.textType === 'title') {

            element.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: ${element.defaultColor};">${chapter.title}</span></strong></p>`

          } else if (element.textType === 'content') {

            element.content = `<p style="text-align: center;"><span style="font-size: 18px; color: ${element.defaultColor};">${chapter.content || `本章将介绍${chapter.title}的相关内容`}</span></p>`

          }

        })

        slideIndex++

      }



      // 小节内容页

      if (chapter.sections && chapter.sections.length > 0) {

        chapter.sections.forEach((section, sectionIndex) => {

          if (filledTemplate[slideIndex] && filledTemplate[slideIndex].type === 'content') {

            // 填充小节标题

            filledTemplate[slideIndex].elements.forEach(element => {

              if (element.textType === 'title') {

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: ${element.defaultColor};">${section.title}</span></strong></p>`

              }

            })



            // 填充小节内容（只填充标题，详细内容留给AI）

            let contentIndex = 0

            const subsections = section.subsections || []



            filledTemplate[slideIndex].elements.forEach(element => {

              if (element.textType === 'itemTitle' && contentIndex < subsections.length) {

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${subsections[contentIndex].title}</span></strong></p>`

              } else if (element.textType === 'itemContent' && contentIndex < subsections.length) {

                element.content = `<p style="text-align: center;"><span style="font-size: 16px; color: ${element.defaultColor};">待AI填充</span></p>`

                contentIndex++

              }

            })



            // 确保所有itemContent都有内容，即使没有对应的subsection

            filledTemplate[slideIndex].elements.forEach(element => {

              if (element.textType === 'itemContent' && element.content.includes('待AI填充')) {

                // 保持"待AI填充"标记，让AI来处理

              }

            })



            slideIndex++

          }

        })

      } else {

        // 如果章节没有小节，填充默认内容

        if (filledTemplate[slideIndex] && filledTemplate[slideIndex].type === 'content') {

          filledTemplate[slideIndex].elements.forEach(element => {

            if (element.textType === 'title') {

              element.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: ${element.defaultColor};">${chapter.title}</span></strong></p>`

            } else if (element.textType === 'itemTitle') {

              element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">概述</span></strong></p>`

            } else if (element.textType === 'itemContent') {

              element.content = `<p style="text-align: center;"><span style="font-size: 16px; color: ${element.defaultColor};">待AI填充</span></p>`

            }

          })

          slideIndex++

        }

      }

    })



    // 填充结束页（最后一张）

    const lastSlide = filledTemplate[filledTemplate.length - 1]

    if (lastSlide && lastSlide.type === 'end') {

      lastSlide.elements.forEach(element => {

        if (element.textType === 'title') {

          element.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: ${element.defaultColor}; text-shadow: 2px 2px 8px rgba(0,0,0,0.5);">谢谢聆听</span></strong></p>`

        } else if (element.textType === 'content') {

          element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: ${element.defaultColor};">感谢您的时间与关注</span></p>`

        }

      })

    }



    console.log('动态模板基本内容填充完成:', filledTemplate)

    return filledTemplate



  } catch (error) {

    console.error('填充动态模板基本内容失败:', error)

    throw error

  }

}



// 使用AI填充动态模板详细内容（借用6目录模板的填充方式）

const fillDynamicTemplateWithAI = async (template, outlineData, title) => {

  try {

    console.log('开始使用AI填充动态模板详细内容...')

    console.log('接收到的template类型:', typeof template, '是否为数组:', Array.isArray(template))

    console.log('template内容:', template)



    // 验证template参数

    if (!Array.isArray(template)) {

      throw new Error(`模板数据格式错误，期望数组但得到: ${typeof template}`)

    }



    // 统计需要填充的内容数量

    let pendingFillCount = 0

    template.forEach(slide => {

      if (slide.elements) {

        slide.elements.forEach(element => {

          if (element.textType === 'itemContent' && element.content && element.content.includes('待AI填充')) {

            pendingFillCount++

          }

        })

      }

    })

    console.log(`需要AI填充的内容项数量: ${pendingFillCount}`)



    // 借用6目录模板的填充方式：直接填充itemContent内容

    const filledTemplate = JSON.parse(JSON.stringify(template)) // 深拷贝



    // 为每个需要填充的itemContent生成内容

    let contentIndex = 0

    for (let slideIndex = 0; slideIndex < filledTemplate.length; slideIndex++) {

      const slide = filledTemplate[slideIndex]

      if (slide.elements) {

        for (let elementIndex = 0; elementIndex < slide.elements.length; elementIndex++) {

          const element = slide.elements[elementIndex]

          if (element.textType === 'itemContent' && element.content && element.content.includes('待AI填充')) {

            // 显示进度提示

            console.log(`📝 正在生成第${contentIndex + 1}/${pendingFillCount}项内容...`)

            try {

              // 获取对应的标题用于AI生成

              const titleForAI = getTitleForAIContent(outlineData, slideIndex, contentIndex)

              console.log(`🤖 正在为第${contentIndex + 1}项内容调用AI，标题: ${titleForAI}`)



              // 调用AI API生成内容

              const aiResponse = await apis.reProduceSingleQuestion({

                message: `请为PPT幻灯片生成专业的内容，主题是：${titleForAI}。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.严格控制字数在30-45字以内 5.要有独特性和创新性，避免与其他内容重复 6.从不同角度阐述主题。这是关于"${title}"的PPT演示文稿，当前章节是"${titleForAI}"`

              })



              console.log(`🤖 AI响应:`, aiResponse)



              if (aiResponse && aiResponse.data) {

                // 提取AI回复的内容，参考其他地方的解析方式

                const aiContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data || 'AI生成的内容为空'



                // 添加动态填充效果

                await animateTextFill(element, aiContent, element.defaultColor)

                console.log(`✅ AI生成内容成功: ${aiContent.substring(0, 50)}...`)

              } else {

                // AI调用失败，使用备用内容

                const fallbackContent = generateContentFromOutline(outlineData, slideIndex, contentIndex)

                await animateTextFill(element, fallbackContent, element.defaultColor)

                console.log(`⚠️ AI调用失败，使用备用内容: ${fallbackContent}`)

              }

            } catch (aiError) {

              console.error(`❌ AI调用失败 (第${contentIndex + 1}项):`, aiError)

              // AI调用失败，使用备用内容

              const fallbackContent = generateContentFromOutline(outlineData, slideIndex, contentIndex)

              await animateTextFill(element, fallbackContent, element.defaultColor)

              console.log(`🔄 使用备用内容: ${fallbackContent}`)

            }

            contentIndex++

          }

        }

      }

    }



    console.log(`动态模板内容填充完成，共填充 ${contentIndex} 项内容`)

    return filledTemplate



  } catch (error) {

    console.error('AI填充动态模板详细内容失败:', error)

    throw new Error('AI填充动态模板详细内容失败: ' + error.message)

  }

}



// 逐页生成PPT效果（类似AIPPT）

const generatePPTWithAnimation = async (template, outlineData, title) => {

  console.log('🎬 开始逐页生成PPT效果...')



  // 初始化空的PPT

  generatedPPT.value = []



  // 逐页生成效果

  for (let slideIndex = 0; slideIndex < template.length; slideIndex++) {

    const slide = template[slideIndex]



    console.log(`📄 正在生成第${slideIndex + 1}页: ${slide.type || '未知类型'}`)



    // 1. 先显示空白页面（缩略图条显示实际内容，大图显示"正在生成..."）

    generatedPPT.value.push({

      ...slide,

      elements: slide.elements ? slide.elements.map(el => ({

        ...el,

        content: el.content, // 缩略图条显示实际内容

        // 确保位置信息不被修改

        left: el.left,

        top: el.top,

        width: el.width,

        height: el.height

      })) : []

    })



    // 强制更新预览

    generatedPPT.value = [...generatedPPT.value]



    // 调试信息：检查当前页面的背景

    console.log(`第${slideIndex + 1}页背景信息:`, slide.background)

    console.log(`第${slideIndex + 1}页元素数量:`, slide.elements?.length)



    // 强制Vue重新渲染缩略图条

    await nextTick()



    // 强制更新generatedPPT以触发缩略图重新渲染

    generatedPPT.value = [...generatedPPT.value]



    // 2. 等待一下让用户看到页面出现

    await new Promise(resolve => setTimeout(resolve, 300))



    // 3. 异步填充页面内容（逐元素渲染）

    const filledSlide = await fillSlideContentWithAnimation(slide, outlineData, title, slideIndex)



    // 4. 更新当前页面

    generatedPPT.value[slideIndex] = filledSlide

    generatedPPT.value = [...generatedPPT.value]



    // 强制Vue重新渲染缩略图条

    await nextTick()



    // 强制更新generatedPPT以触发缩略图重新渲染

    generatedPPT.value = [...generatedPPT.value]



    // 5. 确保大图显示当前页面

    currentPPTSlideIndex.value = slideIndex



    // 6. 等待一下让用户看到内容填充

    await new Promise(resolve => setTimeout(resolve, 400))



    console.log(`✅ 第${slideIndex + 1}页生成完成`)

  }



  console.log('🎉 所有页面生成完成！')



  // 生成完成后回到第一页

  currentPPTSlideIndex.value = 0

  console.log('📄 已回到第一页')

}



// 填充单个页面内容（带动画效果）

const fillSlideContentWithAnimation = async (slide, outlineData, title, slideIndex) => {

  const filledSlide = JSON.parse(JSON.stringify(slide))



  if (filledSlide.elements) {

    console.log(`🔍 第${slideIndex + 1}页有${filledSlide.elements.length}个元素`)



    // 逐个处理元素，实现逐元素渲染效果

    for (let elementIndex = 0; elementIndex < filledSlide.elements.length; elementIndex++) {

      const element = filledSlide.elements[elementIndex]

      console.log(`🔍 处理元素${elementIndex + 1}:`, {

        textType: element.textType,

        content: element.content,

        id: element.id,

        type: element.type

      })



      // 处理封面页标题

      if (slideIndex === 0 && element.textType === 'title' && element.content && element.content.includes('标题占位符')) {

        element.content = `<p style="text-align: center;"><strong><span style="font-size: 48px; color: #ffffff; text-shadow: 2px 2px 8px rgba(0,0,0,0.5);">${title}</span></strong></p>`

        console.log(`📝 填充封面标题: ${title}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

      // 处理封面页副标题

      else if (slideIndex === 0 && element.textType === 'content' && element.content && element.content.includes('副标题占位符')) {

        // 使用完整的描述信息作为副标题

        const subtitle = await getFullDescriptionForCover(outlineData, title)

        element.content = `<p style="text-align: center;"><span style="font-size: 24px; color: #f0f0f0;">${subtitle}</span></p>`

        console.log(`📝 填充封面副标题: ${subtitle}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

      // 处理目录页内容

      else if (slideIndex === 1 && element.textType === 'item' && element.content && element.content.includes('目录项')) {

        // 从元素ID中提取目录项索引

        const itemMatch = element.id.match(/item-(\d+)/)

        if (itemMatch) {

          const itemIndex = parseInt(itemMatch[1]) - 1 // 转换为0基索引

          if (itemIndex < outlineData.length) {

            const chapterTitle = outlineData[itemIndex].title

            element.content = `<p style="text-align: center;"><span style="font-size: 18px; color: ${element.defaultColor};">${chapterTitle}</span></p>`

            console.log(`📝 填充目录项${itemIndex + 1}: ${chapterTitle}`)

          } else {

            element.content = `<p style="text-align: center;"><span style="font-size: 18px; color: ${element.defaultColor};">&nbsp;</span></p>`

          }

        }



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

      // 处理过渡页标题

      else if (element.textType === 'title' && element.content && element.content.includes('章节标题')) {

        const chapterTitle = getChapterTitleForTransition(outlineData, slideIndex)

        element.content = `<p style="text-align: center;"><strong><span style="font-size: 36px; color: ${element.defaultColor};">${chapterTitle}</span></strong></p>`

        console.log(`📝 填充过渡页标题: ${chapterTitle}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

      // 处理过渡页内容（使用AI生成）

      else if (element.textType === 'content' && element.content && element.content.includes('章节介绍')) {

        // 获取当前章节标题用于AI生成

        const chapterTitle = getChapterTitleForTransition(outlineData, slideIndex)



        try {

          console.log(`🤖 正在为过渡页生成章节介绍内容: ${chapterTitle}`)



          // 调用AI API生成章节介绍内容

          const aiResponse = await apis.reProduceSingleQuestion({

            message: `请为PPT章节"${chapterTitle}"生成一个简洁的章节介绍内容。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.控制在20-30字以内 5.不要包含任何编号`

          })



          if (aiResponse && aiResponse.data) {

            const aiContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

            console.log(`✅ 过渡页章节介绍生成完成: ${aiContent}`)



            element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: ${element.defaultColor};">${aiContent}</span></p>`

          } else {

            // AI调用失败，使用备用内容

            const fallbackContent = getChapterContentForTransition(outlineData, slideIndex)

            console.log(`🔄 使用备用内容: ${fallbackContent}`)

            element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: ${element.defaultColor};">${fallbackContent}</span></p>`

          }

        } catch (aiError) {

          console.error(`❌ AI生成过渡页内容失败:`, aiError)

          // AI生成失败，使用备用内容

          const fallbackContent = getChapterContentForTransition(outlineData, slideIndex)

          console.log(`🔄 使用备用内容: ${fallbackContent}`)

          element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: ${element.defaultColor};">${fallbackContent}</span></p>`

        }



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

      // 处理内容页标题

      else if (element.textType === 'title' && element.content && element.content.includes('内容页标题')) {

        const contentTitle = getContentPageTitle(outlineData, slideIndex)

        element.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: ${element.defaultColor};">${contentTitle}</span></strong></p>`

        console.log(`📝 填充内容页标题: ${contentTitle}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

      // 处理需要填充的元素

      else if (element.textType === 'itemContent' && element.content && (element.content.includes('正在生成...') || element.content.includes('待AI填充'))) {

        console.log(`🤖 找到需要AI填充的元素:`, {

          textType: element.textType,

          content: element.content,

          id: element.id

        })

        // 获取对应的标题用于AI生成

        const titleForAI = getTitleForAIContent(outlineData, slideIndex, elementIndex)



        try {

          console.log(`🤖 正在为第${slideIndex + 1}页生成内容: ${titleForAI}`)

          console.log(`🤖 元素信息:`, element)



          // 调用AI API生成内容

          const aiResponse = await apis.reProduceSingleQuestion({

            message: `请为PPT幻灯片生成专业的内容，主题是：${titleForAI}。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.严格控制字数在30-45字以内 5.不要包含任何编号（如"子小节3："、"要点1："等）6.直接返回内容，不要添加前缀 7.要有独特性和创新性，避免与其他内容重复 8.从不同角度阐述主题。这是关于"${title}"的PPT演示文稿，当前章节是"${titleForAI}"`

          })



          console.log(`🤖 AI响应:`, aiResponse)



          if (aiResponse && aiResponse.data) {

            const aiContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data || 'AI生成的内容为空'

            console.log(`🤖 AI生成内容:`, aiContent)



            // 逐字符显示效果

            await animateTextFill(element, aiContent, element.defaultColor)

            console.log(`✅ 第${slideIndex + 1}页内容生成完成`)

          } else {

            // AI调用失败，使用备用内容

            const fallbackContent = generateContentFromOutline(outlineData, slideIndex, elementIndex)

            console.log(`🔄 使用备用内容:`, fallbackContent)

            await animateTextFill(element, fallbackContent, element.defaultColor)

          }

        } catch (aiError) {

          console.error(`❌ AI生成内容失败:`, aiError)

          // AI生成失败，使用备用内容

          const fallbackContent = generateContentFromOutline(outlineData, slideIndex, elementIndex)

          console.log(`🔄 使用备用内容:`, fallbackContent)

          await animateTextFill(element, fallbackContent, element.defaultColor)

        }

      }

      // 处理标题元素

      else if (element.textType === 'itemTitle' && element.content && (element.content.includes('要点标题') || element.content.includes('待AI生成子小节'))) {

        // 获取对应的标题

        const titleForElement = getTitleForElement(outlineData, slideIndex, elementIndex)



        // 检查是否需要AI生成标题 - 统一使用通用PPT的方式

        if (titleForElement.includes('待AI生成子小节')) {

          // 需要AI生成子小节标题

          try {

            console.log(`🤖 正在为子小节生成标题: ${titleForElement}`)



            // 获取小节标题用于AI生成

            const sectionTitle = getSectionTitleForAI(outlineData, slideIndex, elementIndex)



            // 调用AI生成子小节标题

            const aiResponse = await apis.reProduceSingleQuestion({

              message: `请为PPT幻灯片的小节"${sectionTitle}"生成3-4个简洁的子标题。要求：1.每个标题都要不同 2.标题简洁明了，控制在6-12字 3.专业准确 4.适合PPT展示 5.直接返回标题，用换行分隔，不要编号，不要重复，不要解释，只要标题`

            })



            if (aiResponse && aiResponse.data) {

              const aiTitles = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

              const titles = aiTitles.split('\n').filter(title => title.trim()).map(title => title.trim())



              if (titles.length > 0) {

                // 根据元素索引选择不同的标题

                const titleIndex = elementIndex % titles.length

                const finalTitle = titles[titleIndex]

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${finalTitle}</span></strong></p>`

                console.log(`✅ AI生成子小节标题[${titleIndex}]: ${finalTitle}`)

              } else {

                // AI生成失败，使用默认标题

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

                console.log(`⚠️ AI生成标题失败，使用默认标题`)

              }

            } else {

              // AI调用失败，使用默认标题

              element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

              console.log(`⚠️ AI调用失败，使用默认标题`)

            }

          } catch (aiError) {

            console.error(`❌ AI生成标题失败:`, aiError)

            element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

          }

        } else {

          // 使用现有标题

          element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

          console.log(`📝 填充标题: ${titleForElement}`)

        }



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

      // 处理其他需要填充的元素（但不要覆盖已经AI生成的内容）

      else if (element.content && element.content.includes('待AI填充') && !element.content.includes('正在生成...')) {

        // 获取对应的内容

        const contentForElement = getContentForElement(outlineData, slideIndex, elementIndex)

        element.content = `<p style="text-align: center;"><span style="font-size: 16px; color: ${element.defaultColor};">${contentForElement}</span></p>`

        console.log(`📝 填充内容: ${contentForElement}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })



        // 等待一下让用户看到元素出现

        await new Promise(resolve => setTimeout(resolve, 200))

      }

    }

  }



  return filledSlide

}



// 填充单个页面内容

const fillSlideContent = async (slide, outlineData, title, slideIndex) => {

  const filledSlide = JSON.parse(JSON.stringify(slide))



  if (filledSlide.elements) {

    console.log(`🔍 第${slideIndex + 1}页有${filledSlide.elements.length}个元素`)



    // 逐个处理元素，实现逐元素渲染效果

    for (let elementIndex = 0; elementIndex < filledSlide.elements.length; elementIndex++) {

      const element = filledSlide.elements[elementIndex]

      console.log(`🔍 处理元素${elementIndex + 1}:`, {

        textType: element.textType,

        content: element.content,

        id: element.id,

        type: element.type

      })



      // 处理封面页标题

      if (slideIndex === 0 && element.textType === 'title' && element.content && element.content.includes('标题占位符')) {

        element.content = `<p style="text-align: center;"><strong><span style="font-size: 48px; color: #ffffff; text-shadow: 2px 2px 8px rgba(0,0,0,0.5);">${title}</span></strong></p>`

        console.log(`📝 填充封面标题: ${title}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })

      }

      // 处理封面页副标题

      else if (slideIndex === 0 && element.textType === 'content' && element.content && element.content.includes('副标题占位符')) {

        // 使用完整的描述信息作为副标题

        const subtitle = await getFullDescriptionForCover(outlineData, title)

        element.content = `<p style="text-align: center;"><span style="font-size: 24px; color: #f0f0f0;">${subtitle}</span></p>`

        console.log(`📝 填充封面副标题: ${subtitle}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })

      }

      // 处理目录页内容

      else if (slideIndex === 1 && element.textType === 'item' && element.content && element.content.includes('目录项')) {

        // 从元素ID中提取目录项索引

        const itemMatch = element.id.match(/item-(\d+)/)

        if (itemMatch) {

          const itemIndex = parseInt(itemMatch[1]) - 1 // 转换为0-based索引

          if (itemIndex < outlineData.length) {

            const chapter = outlineData[itemIndex]

            const tocItem = `${itemIndex + 1}. ${chapter.title}`

            element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: #4a5568;">${tocItem}</span></p>`

            console.log(`📝 填充目录项${itemIndex + 1}: ${tocItem}`)

          } else {

            // 如果章节数量少于目录项数量，隐藏多余的目录项

            element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: #4a5568;"></span></p>`

            console.log(`📝 隐藏多余的目录项${itemIndex + 1}`)

          }

        } else {

          // 如果无法解析ID，使用默认内容

          element.content = `<p style="text-align: center;"><span style="font-size: 20px; color: #4a5568;">目录项</span></p>`

          console.log(`📝 使用默认目录项内容`)

        }



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })

      }

      // 处理过渡页标题

      else if (slide.type === 'transition' && element.textType === 'title' && element.content && element.content.includes('章节标题')) {

        // 获取当前章节标题

        const chapterTitle = getChapterTitleForTransition(outlineData, slideIndex)

        element.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: #2d3748;">${chapterTitle}</span></strong></p>`

        console.log(`📝 填充过渡页标题: ${chapterTitle}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })

      }

      // 处理过渡页章节介绍内容

      else if (slide.type === 'transition' && element.textType === 'content' && element.content && element.content.includes('章节介绍内容')) {

        // 获取当前章节标题用于AI生成

        const chapterTitle = getChapterTitleForTransition(outlineData, slideIndex)



        try {

          console.log(`🤖 正在为过渡页生成章节介绍内容: ${chapterTitle}`)



          // 调用AI API生成章节介绍内容

          const aiResponse = await apis.reProduceSingleQuestion({

            message: `请为PPT章节"${chapterTitle}"生成一个简洁的章节介绍内容。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.控制在20-30字以内 5.不要包含任何编号`

          })



          if (aiResponse && aiResponse.data) {

            const aiContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

            console.log(`✅ 过渡页章节介绍生成完成: ${aiContent}`)



            // 添加动态填充效果

            await animateTextFill(element, aiContent, element.defaultColor)

          } else {

            // AI调用失败，使用备用内容

            const fallbackContent = getChapterContentForTransition(outlineData, slideIndex)

            console.log(`🔄 使用备用内容: ${fallbackContent}`)

            await animateTextFill(element, fallbackContent, element.defaultColor)

          }

        } catch (aiError) {

          console.error(`❌ AI生成过渡页内容失败:`, aiError)

          // AI生成失败，使用备用内容

          const fallbackContent = getChapterContentForTransition(outlineData, slideIndex)

          console.log(`🔄 使用备用内容: ${fallbackContent}`)

          await animateTextFill(element, fallbackContent, element.defaultColor)

        }

      }

      // 处理内容页标题

      else if (element.textType === 'title' && element.content && element.content.includes('内容页标题')) {

        // 获取当前内容页标题

        const contentTitle = getContentPageTitle(outlineData, slideIndex)

        element.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: #2d3748;">${contentTitle}</span></strong></p>`

        console.log(`📝 填充内容页标题: ${contentTitle}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })

      }

      // 处理需要填充的元素

      else if (element.textType === 'itemContent' && element.content && (element.content.includes('正在生成...') || element.content.includes('待AI填充'))) {

        console.log(`🤖 找到需要AI填充的元素:`, {

          textType: element.textType,

          content: element.content,

          id: element.id

        })

        // 获取对应的标题用于AI生成

        const titleForAI = getTitleForAIContent(outlineData, slideIndex, elementIndex)



        try {

          console.log(`🤖 正在为第${slideIndex + 1}页生成内容: ${titleForAI}`)

          console.log(`🤖 元素信息:`, element)



          // 调用AI API生成内容

          const aiResponse = await apis.reProduceSingleQuestion({

            message: `请为PPT幻灯片生成专业的内容，主题是：${titleForAI}。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.严格控制字数在30-45字以内 5.不要包含任何编号（如"子小节3："、"要点1："等）6.直接返回内容，不要添加前缀 7.要有独特性和创新性，避免与其他内容重复 8.从不同角度阐述主题。这是关于"${title}"的PPT演示文稿，当前章节是"${titleForAI}"`

          })



          console.log(`🤖 AI响应:`, aiResponse)



          if (aiResponse && aiResponse.data) {

            const aiContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data || 'AI生成的内容为空'

            console.log(`🤖 AI生成内容:`, aiContent)



            // 逐字符显示效果

            await animateTextFill(element, aiContent, element.defaultColor)

            console.log(`✅ 第${slideIndex + 1}页内容生成完成`)

          } else {

            // AI调用失败，使用备用内容

            const fallbackContent = generateContentFromOutline(outlineData, slideIndex, elementIndex)

            console.log(`⚠️ AI调用失败，使用备用内容:`, fallbackContent)

            await animateTextFill(element, fallbackContent, element.defaultColor)

          }

        } catch (aiError) {

          console.error(`❌ AI调用失败:`, aiError)

          const fallbackContent = generateContentFromOutline(outlineData, slideIndex, elementIndex)

          console.log(`🔄 使用备用内容:`, fallbackContent)

          await animateTextFill(element, fallbackContent, element.defaultColor)

        }

      }

      // 处理标题元素

      else if (element.textType === 'itemTitle' && element.content && (element.content.includes('要点标题') || element.content.includes('待AI生成子小节'))) {

        // 获取对应的标题

        const titleForElement = getTitleForElement(outlineData, slideIndex, elementIndex)



        // 检查是否需要AI生成标题 - 统一使用通用PPT的方式

        if (titleForElement.includes('待AI生成子小节')) {

          // 需要AI生成子小节标题

          try {

            console.log(`🤖 正在为子小节生成标题: ${titleForElement}`)



            // 获取小节标题用于AI生成

            const sectionTitle = getSectionTitleForAI(outlineData, slideIndex, elementIndex)



            // 调用AI生成子小节标题

            const aiResponse = await apis.reProduceSingleQuestion({

              message: `请为PPT幻灯片的小节"${sectionTitle}"生成3-4个简洁的子标题。要求：1.每个标题都要不同 2.标题简洁明了，控制在6-12字 3.专业准确 4.适合PPT展示 5.直接返回标题，用换行分隔，不要编号，不要重复，不要解释，只要标题`

            })



            if (aiResponse && aiResponse.data) {

              const aiTitles = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

              const titles = aiTitles.split('\n').filter(title => title.trim()).map(title => title.trim())



              if (titles.length > 0) {

                // 根据元素索引选择不同的标题

                const titleIndex = elementIndex % titles.length

                const finalTitle = titles[titleIndex]

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${finalTitle}</span></strong></p>`

                console.log(`✅ AI生成子小节标题[${titleIndex}]: ${finalTitle}`)

              } else {

                // AI生成失败，使用默认标题

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

                console.log(`⚠️ AI生成标题失败，使用默认标题`)

              }

            } else {

              // AI调用失败，使用默认标题

              element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

              console.log(`⚠️ AI调用失败，使用默认标题`)

            }

          } catch (aiError) {

            console.error(`❌ AI生成标题失败:`, aiError)

            element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

          }

        } else {

          // 使用现有标题

          element.content = `<p style="text-align: center;"><strong><span style="font-size: 20px; color: ${element.defaultColor};">${titleForElement}</span></strong></p>`

          console.log(`📝 填充标题: ${titleForElement}`)

        }



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })

      }

      // 处理其他需要填充的元素（但不要覆盖已经AI生成的内容）

      else if (element.content && element.content.includes('待AI填充') && !element.content.includes('正在生成...')) {

        // 获取对应的内容

        const contentForElement = getContentForElement(outlineData, slideIndex, elementIndex)

        element.content = `<p style="text-align: center;"><span style="font-size: 16px; color: ${element.defaultColor};">${contentForElement}</span></p>`

        console.log(`📝 填充内容: ${contentForElement}`)



        // 强制更新PPT预览，触发响应式更新

        nextTick(() => {

          generatedPPT.value = [...generatedPPT.value]

        })

      }



      // 每个元素处理完成后稍作延迟，让用户看到逐元素渲染效果

      await new Promise(resolve => setTimeout(resolve, 100))

    }

  }



  return filledSlide

}



// 动态文本填充效果

const animateTextFill = async (element, content, color) => {

  return new Promise((resolve) => {

    // 先显示加载状态

    element.content = `<p style="text-align: center;"><span style="font-size: 16px; color: ${color};">正在生成内容...</span></p>`



    // 强制更新PPT预览 - 使用nextTick确保DOM更新

    nextTick(() => {

      generatedPPT.value = [...generatedPPT.value]

    })



    // 延迟一下让用户看到加载状态

    setTimeout(() => {

      // 逐步显示内容

      let currentText = ''

      const words = content.split('')

      let index = 0



      const typeWriter = () => {

        if (index < words.length) {

          currentText += words[index]

          element.content = `<p style="text-align: center;"><span style="font-size: 16px; color: ${color};">${currentText}</span></p>`



          // 强制更新PPT预览，触发响应式更新

          nextTick(() => {

            generatedPPT.value = [...generatedPPT.value]

          })



          index++

          setTimeout(typeWriter, 30) // 每30ms显示一个字符，加快速度

        } else {

          resolve()

        }

      }



      typeWriter()

    }, 500) // 500ms后开始打字效果

  })

}



// 获取元素标题

const getTitleForElement = (outlineData, slideIndex, elementIndex) => {

  console.log(`获取元素标题 - 幻灯片索引: ${slideIndex}, 元素索引: ${elementIndex}`)

  console.log(`大纲数据:`, outlineData)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return '默认标题'

  }



  // 计算当前内容页对应的章节和小节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0

  let sectionIndex = 0



  // 找到当前幻灯片对应的章节和小节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    // Each chapter has a transition slide

    currentSlideIndex++ // Account for chapter transition slide

    if (chapter.sections && chapter.sections.length > 0) {

      for (let j = 0; j < chapter.sections.length; j++) {

        if (currentSlideIndex === slideIndex) {

          chapterIndex = i

          sectionIndex = j

          break

        }

        currentSlideIndex++ // Account for section content slide

      }

      if (currentSlideIndex === slideIndex) break

    } else {

      // If a chapter has no sections, it still gets one content slide

      if (currentSlideIndex === slideIndex) {

        chapterIndex = i

        sectionIndex = 0

        break

      }

      currentSlideIndex++

    }

  }



  console.log(`当前内容页对应 - 章节: ${chapterIndex}, 小节: ${sectionIndex}`)



  // 获取对应的章节和小节数据

  const chapter = outlineData[chapterIndex]

  const section = chapter && chapter.sections && chapter.sections[sectionIndex]



  if (section && section.subsections && section.subsections.length > 0) {

    // 根据子小节生成标题

    const subsection = section.subsections[elementIndex % section.subsections.length]

    if (subsection && subsection.title) {

      // 如果是"待AI生成子小节"标记，直接返回

      if (subsection.title.includes('待AI生成子小节')) {

        return subsection.title

      }

      return subsection.title

    }

  }



  // 如果没有子小节，根据小节标题生成标题

  if (section && section.title) {

    return section.title

  }



  // 最后根据章节标题生成标题

  if (chapter && chapter.title) {

    return chapter.title

  }



  // 默认标题

  return '默认标题'

}



// 获取元素内容

const getContentForElement = (outlineData, slideIndex, elementIndex) => {

  console.log(`获取元素内容 - 幻灯片索引: ${slideIndex}, 元素索引: ${elementIndex}`)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return '默认内容'

  }



  // 计算当前内容页对应的章节和小节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0

  let sectionIndex = 0



  // 找到当前幻灯片对应的章节和小节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    // Each chapter has a transition slide

    currentSlideIndex++ // Account for chapter transition slide

    if (chapter.sections && chapter.sections.length > 0) {

      for (let j = 0; j < chapter.sections.length; j++) {

        if (currentSlideIndex === slideIndex) {

          chapterIndex = i

          sectionIndex = j

          break

        }

        currentSlideIndex++ // Account for section content slide

      }

      if (currentSlideIndex === slideIndex) break

    } else {

      // If a chapter has no sections, it still gets one content slide

      if (currentSlideIndex === slideIndex) {

        chapterIndex = i

        sectionIndex = 0

        break

      }

      currentSlideIndex++

    }

  }



  console.log(`当前内容页对应 - 章节: ${chapterIndex}, 小节: ${sectionIndex}`)



  // 获取对应的章节和小节数据

  const chapter = outlineData[chapterIndex]

  const section = chapter && chapter.sections && chapter.sections[sectionIndex]



  if (section && section.subsections && section.subsections.length > 0) {

    // 根据子小节生成内容

    const subsection = section.subsections[elementIndex % section.subsections.length]

    if (subsection && subsection.title) {

      return generateProfessionalContent(subsection.title, elementIndex)

    }

  }



  // 如果没有子小节，根据小节标题生成内容

  if (section && section.title) {

    return generateProfessionalContent(section.title, elementIndex)

  }



  // 最后根据章节标题生成内容

  if (chapter && chapter.title) {

    return generateProfessionalContent(chapter.title, elementIndex)

  }



  // 默认内容

  return generateProfessionalContent('默认内容', elementIndex)

}



// 获取内容页标题

const getContentPageTitle = (outlineData, slideIndex) => {

  console.log(`获取内容页标题 - 幻灯片索引: ${slideIndex}`)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return '默认内容页标题'

  }



  // 计算当前内容页对应的章节和小节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0

  let sectionIndex = 0



  // 找到当前内容页对应的章节和小节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    // Each chapter has a transition slide

    currentSlideIndex++ // Account for chapter transition slide

    if (chapter.sections && chapter.sections.length > 0) {

      for (let j = 0; j < chapter.sections.length; j++) {

        if (currentSlideIndex === slideIndex) {

          chapterIndex = i

          sectionIndex = j

          break

        }

        currentSlideIndex++ // Account for section content slide

      }

      if (currentSlideIndex === slideIndex) break

    } else {

      // If a chapter has no sections, it still gets one content slide

      if (currentSlideIndex === slideIndex) {

        chapterIndex = i

        sectionIndex = 0

        break

      }

      currentSlideIndex++

    }

  }



  console.log(`当前内容页对应 - 章节: ${chapterIndex}, 小节: ${sectionIndex}`)



  // 获取对应的章节和小节数据

  const chapter = outlineData[chapterIndex]

  const section = chapter && chapter.sections && chapter.sections[sectionIndex]



  // 优先使用小节标题

  if (section && section.title) {

    return section.title

  }



  // 如果没有小节，使用章节标题

  if (chapter && chapter.title) {

    return chapter.title

  }



  // 默认标题

  return '默认内容页标题'

}



// 转换用户大纲数据为兼容格式

const convertUserOutlineToCompatibleFormat = async (userOutline) => {

  console.log('转换用户大纲数据为兼容格式...')

  console.log('原始用户大纲数据:', userOutline)



  if (!userOutline || !Array.isArray(userOutline)) {

    console.log('用户大纲数据无效，返回空数组')

    return []

  }



  const convertedOutline = []



  for (let chapterIndex = 0; chapterIndex < userOutline.length; chapterIndex++) {

    const chapter = userOutline[chapterIndex]



    // 生成章节内容

    let chapterContent = chapter.content

    if (!chapterContent) {

      try {

        console.log(`🤖 正在为章节"${chapter.title}"生成内容...`)

        const aiResponse = await apis.reProduceSingleQuestion({

          message: `请为PPT章节"${chapter.title}"生成一个简洁的章节介绍内容。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.控制在20-30字以内 5.不要包含任何编号`

        })



        if (aiResponse && aiResponse.data) {

          chapterContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

          console.log(`✅ 章节内容生成完成: ${chapterContent}`)

        } else {

          chapterContent = `第${chapterIndex + 1}章节的内容`

        }

      } catch (error) {

        console.error(`❌ 章节内容生成失败:`, error)

        chapterContent = `第${chapterIndex + 1}章节的内容`

      }

    }



    const convertedChapter = {

      title: chapter.title || `章节${chapterIndex + 1}`,

      content: chapterContent,

      sections: []

    }



    if (chapter.sections && Array.isArray(chapter.sections)) {

      for (let sectionIndex = 0; sectionIndex < chapter.sections.length; sectionIndex++) {

        const section = chapter.sections[sectionIndex]



        // 生成小节内容

        let sectionContent = section.content

        if (!sectionContent) {

          try {

            console.log(`🤖 正在为小节"${section.title}"生成内容...`)

            const aiResponse = await apis.reProduceSingleQuestion({

              message: `请为PPT小节"${section.title}"生成一个简洁的小节介绍内容。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.控制在20-30字以内 5.不要包含任何编号`

            })



            if (aiResponse && aiResponse.data) {

              sectionContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

              console.log(`✅ 小节内容生成完成: ${sectionContent}`)

            } else {

              sectionContent = `第${sectionIndex + 1}小节的内容`

            }

          } catch (error) {

            console.error(`❌ 小节内容生成失败:`, error)

            sectionContent = `第${sectionIndex + 1}小节的内容`

          }

        }



        const convertedSection = {

          title: section.title || `小节${sectionIndex + 1}`,

          content: sectionContent,

          subsections: []

        }



        if (section.subsections && Array.isArray(section.subsections) && section.subsections.length > 0) {

          // 如果用户大纲有子小节，使用用户的子小节

          convertedSection.subsections = section.subsections.map((subsection, subsectionIndex) => {

            return {

              title: subsection.title || `子小节${subsectionIndex + 1}`

              // 注意：不添加content字段，与mock数据结构保持一致

            }

          })

        } else {

          // 如果用户大纲没有子小节，让AI生成子小节标题

          // 随机生成2-4个子小节

          const subsectionCount = Math.floor(Math.random() * 3) + 2 // 2-4个子小节

          convertedSection.subsections = []



          // 创建占位符，稍后由AI填充

          for (let i = 0; i < subsectionCount; i++) {

            convertedSection.subsections.push({

              title: `待AI生成子小节${i + 1}` // 占位符，稍后由AI填充

            })

          }

          console.log(`为小节"${section.title}"创建了${subsectionCount}个待AI生成的子小节`)

        }



        convertedChapter.sections.push(convertedSection)

      }

    } else {

      // 如果没有小节，创建默认的小节（与mock数据结构一致）

      convertedChapter.sections = [

        {

          title: '主要内容',

          content: '主要内容描述',

          subsections: [

            { title: `${convertedChapter.title} - 要点1` },

            { title: `${convertedChapter.title} - 要点2` },

            { title: `${convertedChapter.title} - 要点3` },

            { title: `${convertedChapter.title} - 要点4` }

          ]

        }

      ]

      console.log(`为章节"${convertedChapter.title}"创建了默认小节和子小节`)

    }



    convertedOutline.push(convertedChapter)

  }



  console.log('转换后的兼容格式数据:', convertedOutline)

  return convertedOutline

}



// 获取封面页的完整描述信息

const getFullDescriptionForCover = async (outlineData, title) => {

  console.log(`获取封面页完整描述 - 标题: ${title}`)



  // 如果是用户大纲数据（检查是否包含用户大纲的特征）

  if (title !== '用户生成的大纲' && outlineData && outlineData.length > 0) {

    if (outlineData && outlineData.length > 0) {

      try {

        // 使用AI生成专业的描述

        console.log(`🤖 正在为用户大纲生成专业描述...`)

        const aiResponse = await apis.reProduceSingleQuestion({

          message: `请为PPT演示文稿生成一个专业的副标题描述。大纲包含${outlineData.length}个章节，每个章节都有多个小节和子小节。要求：1.描述专业准确 2.语言简洁明了 3.适合PPT展示 4.控制在30-45字以内 5.突出培训的专业性和系统性`

        })



        if (aiResponse && aiResponse.data) {

          const aiDescription = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

          console.log(`✅ 用户大纲描述生成完成: ${aiDescription}`)

          return aiDescription

        }

      } catch (error) {

        console.error(`❌ 用户大纲描述生成失败:`, error)

      }



      // AI生成失败，使用默认描述

      const chapterCount = outlineData.length

      let description = `${chapterCount}章节`



      // 添加每个章节的小节信息

      outlineData.forEach((chapter, index) => {

        if (chapter.sections && chapter.sections.length > 0) {

          const sectionCount = chapter.sections.length

          description += `，第${index + 1}章节${sectionCount}小节`



          // 添加子小节信息

          const subsectionCounts = chapter.sections.map(section =>

            section.subsections ? section.subsections.length : 0

          )

          if (subsectionCounts.length > 0 && subsectionCounts.some(count => count > 0)) {

            description += `(${subsectionCounts.join('+')}子小节)`

          }

        }

      })



      console.log(`生成用户大纲描述: ${description}`)

      return description

    }

    return '用户生成的大纲结构'

  }



  // 根据标题找到对应的mock数据选项

  const selectedOption = mockDataOptions.value.find(option => option.title === title)



  if (selectedOption && selectedOption.description) {

    console.log(`找到对应的描述: ${selectedOption.description}`)

    return selectedOption.description

  }



  // 如果没有找到对应的选项，根据大纲数据生成描述

  if (outlineData && outlineData.length > 0) {

    const chapterCount = outlineData.length

    let description = `${chapterCount}章节`



    // 添加每个章节的小节信息

    outlineData.forEach((chapter, index) => {

      if (chapter.sections && chapter.sections.length > 0) {

        const sectionCount = chapter.sections.length

        description += `，第${index + 1}章节${sectionCount}小节`



        // 添加子小节信息

        const subsectionCounts = chapter.sections.map(section =>

          section.subsections ? section.subsections.length : 0

        )

        if (subsectionCounts.length > 0 && subsectionCounts.some(count => count > 0)) {

          description += `(${subsectionCounts.join('+')}子小节)`

        }

      }

    })



    console.log(`生成的描述: ${description}`)

    return description

  }



  // 默认描述

  return '安全培训演示文稿'

}



// 获取过渡页的章节介绍内容

const getChapterContentForTransition = (outlineData, slideIndex) => {

  console.log(`获取过渡页章节介绍内容 - 幻灯片索引: ${slideIndex}`)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return '默认章节介绍'

  }



  // 计算当前过渡页对应的章节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0



  // 找到当前过渡页对应的章节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    // Each chapter has a transition slide

    if (currentSlideIndex === slideIndex) {

      chapterIndex = i

      break

    }

    currentSlideIndex++ // Account for chapter transition slide



    // Then account for all section content slides in this chapter

    if (chapter.sections && chapter.sections.length > 0) {

      currentSlideIndex += chapter.sections.length

    } else {

      // If a chapter has no sections, it still gets one content slide

      currentSlideIndex++

    }

  }



  console.log(`当前过渡页对应 - 章节: ${chapterIndex}`)



  // 获取对应的章节数据

  const chapter = outlineData[chapterIndex]



  // 返回章节的介绍内容

  if (chapter && chapter.content) {

    return chapter.content

  }



  // 如果没有content字段，生成一个基于章节标题的介绍

  if (chapter && chapter.title) {

    return `本章将介绍${chapter.title}的相关内容，包括核心概念、重要知识点和实践应用。`

  }



  // 默认内容

  return '默认章节介绍'

}



// 获取过渡页的章节标题

const getChapterTitleForTransition = (outlineData, slideIndex) => {

  console.log(`获取过渡页章节标题 - 幻灯片索引: ${slideIndex}`)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return '默认章节'

  }



  // 计算当前过渡页对应的章节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0



  // 找到当前过渡页对应的章节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    // Each chapter has a transition slide

    if (currentSlideIndex === slideIndex) {

      chapterIndex = i

      break

    }

    currentSlideIndex++ // Account for chapter transition slide



    // 为每个小节生成内容页

    if (chapter.sections && chapter.sections.length > 0) {

      currentSlideIndex += chapter.sections.length // Account for section content slides

    } else {

      currentSlideIndex++ // Account for default content slide

    }

  }



  console.log(`当前过渡页对应 - 章节: ${chapterIndex}`)



  // 获取对应的章节数据

  const chapter = outlineData[chapterIndex]

  if (chapter && chapter.title) {

    // 使用干净的标题（去掉第X章前缀）

    return getCleanChapterTitle(chapter.title)

  }



  // 默认标题

  return '默认章节'

}



// 获取小节标题用于AI生成子小节标题

const getSectionTitleForAI = (outlineData, slideIndex, elementIndex) => {

  console.log(`获取小节标题用于AI生成 - 幻灯片索引: ${slideIndex}, 元素索引: ${elementIndex}`)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return '默认小节'

  }



  // 计算当前内容页对应的章节和小节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0

  let sectionIndex = 0



  // 找到当前内容页对应的章节和小节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    // Each chapter has a transition slide

    currentSlideIndex++ // Account for chapter transition slide

    if (chapter.sections && chapter.sections.length > 0) {

      for (let j = 0; j < chapter.sections.length; j++) {

        if (currentSlideIndex === slideIndex) {

          chapterIndex = i

          sectionIndex = j

          break

        }

        currentSlideIndex++ // Account for section content slide

      }

      if (currentSlideIndex === slideIndex) break

    } else {

      // If a chapter has no sections, it still gets one content slide

      if (currentSlideIndex === slideIndex) {

        chapterIndex = i

        sectionIndex = 0

        break

      }

      currentSlideIndex++

    }

  }



  console.log(`当前内容页对应 - 章节: ${chapterIndex}, 小节: ${sectionIndex}`)



  // 获取对应的小节数据

  const chapter = outlineData[chapterIndex]

  const section = chapter && chapter.sections && chapter.sections[sectionIndex]



  // 返回小节标题

  if (section && section.title) {

    return section.title

  }



  // 默认标题

  return '默认小节'

}



// 获取用于AI内容生成的标题

const getTitleForAIContent = (outlineData, slideIndex, contentIndex) => {

  console.log(`获取AI标题 - 幻灯片索引: ${slideIndex}, 内容索引: ${contentIndex}`)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return '默认内容'

  }



  // 计算当前内容页对应的章节和小节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0

  let sectionIndex = 0



  // 找到当前幻灯片对应的章节和小节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    // Each chapter has a transition slide

    currentSlideIndex++ // Account for chapter transition slide

    if (chapter.sections && chapter.sections.length > 0) {

      for (let j = 0; j < chapter.sections.length; j++) {

        if (currentSlideIndex === slideIndex) {

          chapterIndex = i

          sectionIndex = j

          break

        }

        currentSlideIndex++ // Account for section content slide

      }

      if (currentSlideIndex === slideIndex) break

    } else {

      // If a chapter has no sections, it still gets one content slide

      if (currentSlideIndex === slideIndex) {

        chapterIndex = i

        sectionIndex = 0

        break

      }

      currentSlideIndex++

    }

  }



  console.log(`当前内容页对应 - 章节: ${chapterIndex}, 小节: ${sectionIndex}`)



  // 获取对应的章节和小节数据

  const chapter = outlineData[chapterIndex]

  const section = chapter && chapter.sections && chapter.sections[sectionIndex]



  if (section && section.subsections && section.subsections.length > 0) {

    // 根据子小节生成标题

    const subsection = section.subsections[contentIndex % section.subsections.length]

    if (subsection && subsection.title) {

      return `${chapter.title} - ${section.title} - ${subsection.title}`

    }

  }



  // 如果没有子小节，根据小节标题生成标题

  if (section && section.title) {

    return `${chapter.title} - ${section.title}`

  }



  // 最后根据章节标题生成标题

  if (chapter && chapter.title) {

    return chapter.title

  }



  // 默认标题

  return '默认内容'

}



// 生成专业内容（使用mock数据测试）

const generateProfessionalContent = (title, index) => {

  // Mock数据：根据大纲结构生成对应的内容

  const mockContentMap = {

    '信息上报与指挥体系建立': [

      '建立完善的信息收集机制，确保各类安全信息及时准确上报',

      '构建统一的指挥调度平台，实现各部门协调联动',

      '制定标准化的上报流程，提高信息处理效率',

      '建立应急响应机制，确保突发事件快速处置'

    ],

    '安全风险评估与控制': [

      '开展全面的安全风险识别，建立风险清单',

      '制定风险等级评估标准，实施分级管控',

      '建立风险监测预警系统，实现动态监控',

      '完善风险控制措施，确保风险可控'

    ],

    '应急预案与处置': [

      '制定完善的应急预案体系，覆盖各类突发事件',

      '建立应急响应队伍，提高应急处置能力',

      '开展应急演练，检验预案有效性',

      '完善应急物资储备，确保应急保障'

    ],

    '培训教育与能力提升': [

      '制定系统化的培训计划，提高全员安全意识',

      '开展专业技能培训，提升操作能力',

      '建立培训考核机制，确保培训效果',

      '持续改进培训方式，提高培训质量'

    ],

    '监督检查与持续改进': [

      '建立监督检查机制，确保制度有效执行',

      '开展定期检查评估，发现问题及时整改',

      '建立问题跟踪机制，确保整改到位',

      '持续改进工作方法，提升管理水平'

    ]

  }



  // 根据标题匹配内容

  for (const [key, contents] of Object.entries(mockContentMap)) {

    if (title.includes(key) || key.includes(title)) {

      return contents[index % contents.length]

    }

  }



  // 默认内容模板

  const defaultTemplates = [

    '建立完善的管理体系，确保各项工作有序开展',

    '制定详细的操作规范，提高工作效率和质量',

    '加强人员培训，提升专业技能和综合素质',

    '建立监督检查机制，确保制度有效执行',

    '完善应急预案，提高应对突发事件的能力',

    '加强沟通协调，促进部门间有效合作',

    '持续改进优化，不断提升管理水平',

    '注重细节管理，确保工作质量稳定可靠',

    '强化责任意识，确保各项工作落实到位',

    '创新工作方法，提高工作效率和效果'

  ]



  return defaultTemplates[index % defaultTemplates.length]

}



// 根据大纲数据生成对应的内容

const generateContentFromOutline = (outlineData, slideIndex, contentIndex) => {

  console.log(`生成内容 - 幻灯片索引: ${slideIndex}, 内容索引: ${contentIndex}`)



  // 跳过封面页和目录页（前2页）

  if (slideIndex < 2) {

    return generateProfessionalContent('默认内容', contentIndex)

  }



  // 计算当前内容页对应的章节和小节

  let currentSlideIndex = 2 // 从第3张幻灯片开始（封面+目录后）

  let chapterIndex = 0

  let sectionIndex = 0



  // 找到当前幻灯片对应的章节和小节

  for (let i = 0; i < outlineData.length; i++) {

    const chapter = outlineData[i]

    if (chapter.sections && chapter.sections.length > 0) {

      for (let j = 0; j < chapter.sections.length; j++) {

        if (currentSlideIndex === slideIndex) {

          chapterIndex = i

          sectionIndex = j

          break

        }

        currentSlideIndex++

      }

      if (currentSlideIndex === slideIndex) break

    } else {

      if (currentSlideIndex === slideIndex) {

        chapterIndex = i

        sectionIndex = 0

        break

      }

      currentSlideIndex++

    }

  }



  console.log(`当前内容页对应 - 章节: ${chapterIndex}, 小节: ${sectionIndex}`)



  // 获取对应的章节和小节数据

  const chapter = outlineData[chapterIndex]

  const section = chapter && chapter.sections && chapter.sections[sectionIndex]



  if (section && section.subsections && section.subsections.length > 0) {

    // 根据子小节生成内容

    const subsection = section.subsections[contentIndex % section.subsections.length]

    if (subsection && subsection.title) {

      return generateProfessionalContent(subsection.title, contentIndex)

    }

  }



  // 如果没有子小节，根据小节标题生成内容

  if (section && section.title) {

    return generateProfessionalContent(section.title, contentIndex)

  }



  // 最后根据章节标题生成内容

  if (chapter && chapter.title) {

    return generateProfessionalContent(chapter.title, contentIndex)

  }



  // 默认内容

  return generateProfessionalContent('默认内容', contentIndex)

}



// Mock数据配置选项

const mockDataOptions = ref([

  {

    id: 'safety-basic',

    title: '基础安全培训',

    description: '2章节，每章节2小节，每小节4子小节',

    data: [

      {

        title: '信息上报与指挥体系建立',

        content: '建立完善的信息上报和指挥体系',

        sections: [

          {

            title: '信息收集机制',

            content: '建立完善的信息收集机制',

            subsections: [

              { title: '信息收集渠道' },

              { title: '信息收集标准' },

              { title: '信息收集流程' },

              { title: '信息质量控制' }

            ]

          },

          {

            title: '指挥调度平台',

            content: '构建统一的指挥调度平台',

            subsections: [

              { title: '平台功能设计' },

              { title: '系统架构规划' },

              { title: '数据集成方案' },

              { title: '用户权限管理' }

            ]

          }

        ]

      },

      {

        title: '安全风险评估与控制',

        content: '开展全面的安全风险评估与控制',

        sections: [

          {

            title: '风险识别与评估',

            content: '建立风险识别与评估体系',

            subsections: [

              { title: '风险识别方法' },

              { title: '风险评估标准' },

              { title: '风险等级划分' },

              { title: '风险评估流程' }

            ]

          },

          {

            title: '风险控制措施',

            content: '制定有效的风险控制措施',

            subsections: [

              { title: '预防控制措施' },

              { title: '监测预警机制' },

              { title: '应急处置方案' },

              { title: '持续改进机制' }

            ]

          }

        ]

      }

    ]

  },

  {

    id: 'comprehensive-training',

    title: '综合培训体系',

    description: '3章节，每章节3小节，每小节3子小节',

    data: [

      {

        title: '培训体系建设',

        content: '建立完善的培训体系',

        sections: [

          {

            title: '培训需求分析',

            content: '分析培训需求',

            subsections: [

              { title: '需求调研方法' },

              { title: '需求分析工具' },

              { title: '需求确认流程' }

            ]

          },

          {

            title: '培训计划制定',

            content: '制定培训计划',

            subsections: [

              { title: '计划制定原则' },

              { title: '计划执行方案' },

              { title: '计划调整机制' }

            ]

          },

          {

            title: '培训效果评估',

            content: '评估培训效果',

            subsections: [

              { title: '评估指标体系' },

              { title: '评估方法选择' },

              { title: '评估结果应用' }

            ]

          }

        ]

      },

      {

        title: '师资队伍建设',

        content: '建设专业师资队伍',

        sections: [

          {

            title: '师资选拔标准',

            content: '制定师资选拔标准',

            subsections: [

              { title: '专业能力要求' },

              { title: '教学经验要求' },

              { title: '综合素质要求' }

            ]

          },

          {

            title: '师资培训体系',

            content: '建立师资培训体系',

            subsections: [

              { title: '培训内容设计' },

              { title: '培训方式选择' },

              { title: '培训效果跟踪' }

            ]

          },

          {

            title: '师资激励机制',

            content: '建立师资激励机制',

            subsections: [

              { title: '激励政策制定' },

              { title: '激励措施实施' },

              { title: '激励效果评估' }

            ]

          }

        ]

      },

      {

        title: '培训资源管理',

        content: '管理培训资源',

        sections: [

          {

            title: '培训设施建设',

            content: '建设培训设施',

            subsections: [

              { title: '设施规划布局' },

              { title: '设施设备配置' },

              { title: '设施维护管理' }

            ]

          },

          {

            title: '培训教材开发',

            content: '开发培训教材',

            subsections: [

              { title: '教材编写标准' },

              { title: '教材审核流程' },

              { title: '教材更新机制' }

            ]

          },

          {

            title: '培训技术支持',

            content: '提供技术支持',

            subsections: [

              { title: '技术平台建设' },

              { title: '技术维护服务' },

              { title: '技术培训支持' }

            ]

          }

        ]

      }

    ]

  },

  {

    id: 'emergency-management',

    title: '应急管理体系',

    description: '4章节，每章节2小节，每小节2子小节',

    data: [

      {

        title: '应急预案制定',

        content: '制定应急预案',

        sections: [

          {

            title: '预案编制流程',

            content: '编制应急预案',

            subsections: [

              { title: '预案编制标准' },

              { title: '预案审核程序' }

            ]

          },

          {

            title: '预案演练实施',

            content: '实施预案演练',

            subsections: [

              { title: '演练计划制定' },

              { title: '演练效果评估' }

            ]

          }

        ]

      },

      {

        title: '应急响应机制',

        content: '建立应急响应机制',

        sections: [

          {

            title: '响应流程设计',

            content: '设计响应流程',

            subsections: [

              { title: '响应级别划分' },

              { title: '响应时间要求' }

            ]

          },

          {

            title: '响应队伍建设',

            content: '建设响应队伍',

            subsections: [

              { title: '队伍组建标准' },

              { title: '队伍培训体系' }

            ]

          }

        ]

      },

      {

        title: '应急物资保障',

        content: '保障应急物资',

        sections: [

          {

            title: '物资储备管理',

            content: '管理物资储备',

            subsections: [

              { title: '储备标准制定' },

              { title: '储备检查制度' }

            ]

          },

          {

            title: '物资调配机制',

            content: '建立调配机制',

            subsections: [

              { title: '调配流程设计' },

              { title: '调配效率优化' }

            ]

          }

        ]

      },

      {

        title: '应急信息管理',

        content: '管理应急信息',

        sections: [

          {

            title: '信息收集系统',

            content: '建设收集系统',

            subsections: [

              { title: '系统功能设计' },

              { title: '系统运行维护' }

            ]

          },

          {

            title: '信息发布机制',

            content: '建立发布机制',

            subsections: [

              { title: '发布渠道建设' },

              { title: '发布效果监控' }

            ]

          }

        ]

      }

    ]

  },

  {

    id: 'quality-management',

    title: '质量管理体系',

    description: '5章节，每章节2小节，每小节3子小节',

    data: [

      {

        title: '质量方针制定',

        content: '制定质量方针',

        sections: [

          {

            title: '方针内容设计',

            content: '设计方针内容',

            subsections: [

              { title: '方针目标设定' },

              { title: '方针实施策略' },

              { title: '方针效果评估' }

            ]

          },

          {

            title: '方针宣传推广',

            content: '推广质量方针',

            subsections: [

              { title: '宣传渠道建设' },

              { title: '推广活动组织' },

              { title: '推广效果跟踪' }

            ]

          }

        ]

      },

      {

        title: '质量目标管理',

        content: '管理质量目标',

        sections: [

          {

            title: '目标设定方法',

            content: '设定质量目标',

            subsections: [

              { title: '目标分解原则' },

              { title: '目标量化标准' },

              { title: '目标调整机制' }

            ]

          },

          {

            title: '目标监控体系',

            content: '监控目标实现',

            subsections: [

              { title: '监控指标设计' },

              { title: '监控频率设定' },

              { title: '监控结果分析' }

            ]

          }

        ]

      },

      {

        title: '质量过程控制',

        content: '控制质量过程',

        sections: [

          {

            title: '过程识别分析',

            content: '识别分析过程',

            subsections: [

              { title: '过程流程图绘制' },

              { title: '过程关键点识别' },

              { title: '过程风险分析' }

            ]

          },

          {

            title: '过程改进优化',

            content: '改进优化过程',

            subsections: [

              { title: '改进机会识别' },

              { title: '改进方案设计' },

              { title: '改进效果验证' }

            ]

          }

        ]

      },

      {

        title: '质量审核评估',

        content: '审核评估质量',

        sections: [

          {

            title: '审核计划制定',

            content: '制定审核计划',

            subsections: [

              { title: '审核范围确定' },

              { title: '审核标准制定' },

              { title: '审核人员安排' }

            ]

          },

          {

            title: '审核实施管理',

            content: '管理审核实施',

            subsections: [

              { title: '审核流程执行' },

              { title: '审核记录管理' },

              { title: '审核结果处理' }

            ]

          }

        ]

      },

      {

        title: '质量持续改进',

        content: '持续改进质量',

        sections: [

          {

            title: '改进机会识别',

            content: '识别改进机会',

            subsections: [

              { title: '问题分析方法' },

              { title: '改进需求评估' },

              { title: '改进优先级排序' }

            ]

          },

          {

            title: '改进措施实施',

            content: '实施改进措施',

            subsections: [

              { title: '改进方案制定' },

              { title: '改进资源保障' },

              { title: '改进效果跟踪' }

            ]

          }

        ]

      }

    ]

  },

  {

    id: 'innovation-system',

    title: '创新管理体系',

    description: '6章节，每章节1小节，每小节4子小节',

    data: [

      {

        title: '创新战略规划',

        content: '规划创新战略',

        sections: [

          {

            title: '战略分析制定',

            content: '制定创新战略',

            subsections: [

              { title: '内外部环境分析' },

              { title: '创新机会识别' },

              { title: '战略目标设定' },

              { title: '战略实施路径' }

            ]

          }

        ]

      },

      {

        title: '创新文化建设',

        content: '建设创新文化',

        sections: [

          {

            title: '文化理念塑造',

            content: '塑造创新文化',

            subsections: [

              { title: '创新价值观建立' },

              { title: '创新氛围营造' },

              { title: '创新激励机制' },

              { title: '创新成果分享' }

            ]

          }

        ]

      },

      {

        title: '创新团队建设',

        content: '建设创新团队',

        sections: [

          {

            title: '团队组建管理',

            content: '管理创新团队',

            subsections: [

              { title: '团队成员选拔' },

              { title: '团队能力建设' },

              { title: '团队协作机制' },

              { title: '团队绩效管理' }

            ]

          }

        ]

      },

      {

        title: '创新项目管理',

        content: '管理创新项目',

        sections: [

          {

            title: '项目全生命周期',

            content: '管理项目全周期',

            subsections: [

              { title: '项目立项评估' },

              { title: '项目执行监控' },

              { title: '项目风险控制' },

              { title: '项目成果转化' }

            ]

          }

        ]

      },

      {

        title: '创新资源保障',

        content: '保障创新资源',

        sections: [

          {

            title: '资源统筹配置',

            content: '配置创新资源',

            subsections: [

              { title: '资金投入保障' },

              { title: '技术平台建设' },

              { title: '人才资源开发' },

              { title: '信息资源整合' }

            ]

          }

        ]

      },

      {

        title: '创新成果转化',

        content: '转化创新成果',

        sections: [

          {

            title: '成果产业化',

            content: '实现成果产业化',

            subsections: [

              { title: '成果评估筛选' },

              { title: '产业化路径设计' },

              { title: '市场推广策略' },

              { title: '经济效益评估' }

            ]

          }

        ]

      }

    ]

  },

  {

    id: 'mixed-structure',

    title: '混合结构演示',

    description: '2章节，第一章节2小节(3+4子小节)，第二章节3小节(2+3+4子小节)',

    data: [

      {

        title: '第一章节：基础管理',

        content: '建立基础管理体系',

        sections: [

          {

            title: '制度建设',

            content: '建立完善的管理制度',

            subsections: [

              { title: '制度框架设计' },

              { title: '制度内容制定' },

              { title: '制度执行监督' }

            ]

          },

          {

            title: '流程优化',

            content: '优化工作流程',

            subsections: [

              { title: '流程梳理分析' },

              { title: '流程改进设计' },

              { title: '流程实施推广' },

              { title: '流程效果评估' }

            ]

          }

        ]

      },

      {

        title: '第二章节：运营管理',

        content: '提升运营管理水平',

        sections: [

          {

            title: '资源配置',

            content: '优化资源配置',

            subsections: [

              { title: '资源需求分析' },

              { title: '资源配置方案' }

            ]

          },

          {

            title: '绩效管理',

            content: '建立绩效管理体系',

            subsections: [

              { title: '绩效指标设定' },

              { title: '绩效评估方法' },

              { title: '绩效改进措施' }

            ]

          },

          {

            title: '风险控制',

            content: '加强风险控制',

            subsections: [

              { title: '风险识别评估' },

              { title: '风险控制措施' },

              { title: '风险监测预警' },

              { title: '风险应急处置' }

            ]

          }

        ]

      }

    ]

  }

])



// 当前选中的mock数据

const selectedMockData = ref(0)



// 是否使用用户大纲数据

const useUserOutline = ref(false)



// 模板风格选择

const selectedTemplateStyle = ref('default')

const availableTemplateStyles = ref(getAvailableTemplateStyles())



// 测试动态模板填充功能

const testDynamicTemplateFill = async () => {

  try {

    console.log('开始测试动态模板填充功能...')



    let testOutline, testTitle, testDescription



    // 根据用户选择决定使用哪个数据源

    if (useUserOutline.value && outlineData.value.length > 0) {

      // 使用用户大纲数据，并转换为兼容格式

      testOutline = await convertUserOutlineToCompatibleFormat(outlineData.value)

      testTitle = outlineTitle.value || '用户生成的大纲' // 使用大纲标题作为PPT标题

      testDescription = `${testOutline.length}章节结构演示`

      console.log('使用用户大纲数据:', testTitle, `共${testOutline.length}个章节`)

      console.log('转换后的用户大纲数据结构:', JSON.stringify(testOutline, null, 2))

    } else {

      // 使用选中的mock数据

      const selectedOption = mockDataOptions.value[selectedMockData.value]

      testOutline = selectedOption.data

      testTitle = selectedOption.title

      testDescription = selectedOption.description

      console.log('使用mock数据:', selectedOption.title, selectedOption.description)

    }



    // 生成动态模板

    const result = await matchOutlineAndGeneratePPT(testOutline, testTitle, selectedTemplateStyle.value)

    console.log('生成的动态模板结果:', result)



    if (!result.success) {

      throw new Error(result.error || '动态模板生成失败')

    }



    // 获取模板数据

    const dynamicTemplate = result.data.template

    console.log('提取的模板数据:', dynamicTemplate)



    // 验证模板数据格式

    if (!Array.isArray(dynamicTemplate)) {

      console.error('模板数据不是数组格式:', typeof dynamicTemplate, dynamicTemplate)

      throw new Error('模板数据格式不正确，期望数组但得到: ' + typeof dynamicTemplate)

    }



    // 启用预览模式

    showDownloadOptions.value = true

    currentPPTSlideIndex.value = 0

    isPPTPreviewMode.value = true



    console.log('已启用PPT预览模式，开始逐页生成效果...')



    // 实现逐页生成效果

    await generatePPTWithAnimation(dynamicTemplate, testOutline, testTitle)



    const successMessage = useUserOutline.value ?

      `动态模板测试完成！使用用户大纲数据 (${testOutline.length}个章节)` :

      `动态模板测试完成！使用数据: ${testTitle} (${testDescription})`

    ElMessage.success(successMessage)



  } catch (error) {

    console.error('测试动态模板填充失败:', error)

    ElMessage.error('测试失败: ' + error.message)

  }

}



// 导入下载选项图标

import pptxIcon from '@/assets/Safety/28.png'

import examIcon from '@/assets/Safety/27.png'

import documentIcon from '@/assets/Safety/26.png'

import wordDocIcon from '@/assets/Chat/26.png'



// 下载选项数据

const downloadOptions = ref([

  {

    icon: pptxIcon,

    title: 'PowerPoint (PPTX)',

    description: '可编辑的演示文稿'

  },

  {

    icon: examIcon,

    title: '考试工坊',

    description: '基于该文档,生成考试题'

  },

  {

    icon: documentIcon,

    title: '培训讲义文档',

    description: '基于PPT内容,提取文档文字'

  }

])



// 计算属性

const currentSlideImage = computed(() => slideImages.value[currentSlideIndex.value])

const currentTime = computed(() => {

  const now = new Date()

  return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`

})



// 历史记录数据

const historyData = ref([])

const historyTotal = ref(0) // 历史记录总数



// 获取历史记录列表

const getHistoryRecordList = async () => {

  try {

    console.log('📋 开始获取安全培训历史记录列表...')

    const startTime = performance.now()



    const response = await apis.getHistoryRecord({

      // ===== 已删除：user_id - 后端从token解析 =====

      ai_conversation_id: 0, // 0表示获取对话列表

      business_type: 1 // 安全培训类型

    })



    const endTime = performance.now()

    console.log(`📋 历史记录API调用耗时: ${(endTime - startTime).toFixed(2)}ms`)

    console.log('📋 安全培训历史记录列表响应:', response)



    if (response.statusCode === 200) {

      // 设置历史记录总数

      historyTotal.value = response.total || 0



      // 转换后端数据为前端格式

      historyData.value = response.data.map(conversation => ({

        id: conversation.id,

        title: generateConversationTitle(conversation.content),

        time: formatTime(conversation.updated_at),

        businessType: conversation.business_type,

        step: conversation.step || 0, // 添加步骤字段

        cover_image: conversation.cover_image || '', // 添加封面图字段

        ppt_json_url: conversation.ppt_json_url || '', // 添加PPT JSON URL字段

        ppt_json_content: conversation.ppt_json_content || '', // 添加PPT JSON内容字段

        isActive: false,

        // 保存原始数据用于后续查询

        rawData: conversation

      }))

      console.log(`✅ 安全培训历史记录列表已设置: ${historyData.value.length}条记录，总数: ${historyTotal.value}`)

    } else {

      console.error('❌ 获取安全培训历史记录列表失败:', response.statusCode)

    }

  } catch (error) {

    console.error('❌ 获取安全培训历史记录列表失败:', error)

  }

}



// 获取指定对话的详细消息

const getConversationMessages = async (conversationId) => {

  try {

    console.log('开始获取安全培训对话消息，conversationId:', conversationId)

    const response = await apis.getHistoryRecord({

      // ===== 已删除：user_id - 后端从token解析 =====

      ai_conversation_id: conversationId,

      business_type: 1 // 安全培训类型

    })

    console.log('安全培训对话消息响应:', response)



    if (response.statusCode === 200) {

      // 转换后端消息数据为前端格式

      const messages = response.data.map(message => {

        const userFeedback = convertUserFeedback(message.user_feedback)

        console.log(`安全培训消息 ${message.id} 的反馈状态:`, {

          raw: message.user_feedback,

          converted: userFeedback

        })



        // 如果是用户消息且包含文件标签，提取文件信息

        let file = null

        let userContent = message.content



        if (message.type === 'user' && message.content.includes('</filesize>')) {

          // 提取文件信息

          const filenameMatch = message.content.match(/<filename>(.*?)<\/filename>/)

          const filesizeMatch = message.content.match(/<filesize>(.*?)<\/filesize>/)

          const wordMatch = message.content.match(/<word>(.*?)<\/word>/s)



          if (filenameMatch && filesizeMatch) {

            const filename = filenameMatch[1]

            const filesize = parseInt(filesizeMatch[1])

            const fileContent = wordMatch ? wordMatch[1].trim() : ''



            // 创建文件对象

            file = {

              name: filename,

              size: filesize,

              type: filename.endsWith('.docx') ? '.docx' : filename.endsWith('.doc') ? '.doc' : '.docx',

              icon: getFileIcon(filename.endsWith('.docx') ? '.docx' : filename.endsWith('.doc') ? '.doc' : '.docx'),

              content: fileContent

            }



            // 提取用户实际说的话（</filesize>标签后的内容）

            const userMessageMatch = message.content.split('</filesize>')[1]

            userContent = userMessageMatch ? userMessageMatch.trim() : ''

          }

        }



        return {

          type: message.type, // 'user' 或 'ai'

          content: userContent, // 使用提取的用户消息

          displayContent: message.type === 'ai' ? markdownToHtml(message.content) : userContent,

          file: file, // 添加文件对象

          isTyping: false,

          id: message.id,

          userFeedback: userFeedback,

          // 保存原始数据

          rawData: message

        }

      })



      // 设置聊天消息

      chatMessages.value = messages

      console.log('安全培训对话消息已设置:', chatMessages.value)



      // 更新对话ID

      ai_conversation_id.value = conversationId



      // 直接使用AI回复内容解析大纲（不再使用ppt_outline字段）

      const aiMessage = messages.find(msg => msg.type === 'ai')

      if (aiMessage && aiMessage.content) {

        console.log('找到AI回复内容，直接解析大纲:', aiMessage.content)



        // 检查是否正在切换历史记录，如果是则强制更新数据

        const isCurrentlySwitching = isSwitchingHistory.value



        if (!outlineData.value || outlineData.value.length === 0 || isCurrentlySwitching) {

          // 解析AI回复中的大纲

          const parsedOutline = parseOutlineFromAI(aiMessage.content)

          if (parsedOutline && parsedOutline.chapters && parsedOutline.chapters.length > 0) {

            outlineData.value = parsedOutline.chapters

            outlineTitle.value = parsedOutline.title || '安全培训大纲'

            outlineStats.value = calculateOutlineStats(parsedOutline.chapters)



            console.log('从AI回复解析大纲数据成功', isCurrentlySwitching ? '(强制更新)' : '')

          } else {

            console.log('AI回复中未找到有效的大纲内容')

          }

        } else {

          console.log('已有大纲数据，跳过设置（避免覆盖用户编辑结果）')

        }

      } else {

        console.log('未找到AI回复内容')

      }



      // 设置大纲反馈状态（从AI消息中获取）

      if (aiMessage && aiMessage.rawData && aiMessage.rawData.user_feedback !== undefined) {

        outlineFeedback.value = aiMessage.rawData.user_feedback

        currentAiMessageId.value = aiMessage.id

        console.log('设置大纲反馈状态:', outlineFeedback.value, 'AI消息ID:', currentAiMessageId.value)

        console.log('AI消息原始数据:', aiMessage.rawData)

      } else {

        outlineFeedback.value = null

        currentAiMessageId.value = null

        console.log('未找到AI消息或反馈状态，重置大纲反馈状态')

      }



      return true

    } else {

      console.error('获取安全培训对话消息失败:', response.statusCode)

      return false

    }

  } catch (error) {

    console.error('获取安全培训对话消息失败:', error)

    return false

  }

}



// 生成对话标题（从内容中提取）

const generateConversationTitle = (content) => {

  if (!content) return '未知对话'



  // 检查是否包含文件标签格式

  if (content.includes('</filesize>')) {

    // 提取</filesize>标签后面的内容

    const userMessage = content.split('</filesize>')[1]

    if (userMessage && userMessage.trim()) {

      // 清理多余的空格和换行

      const cleanMessage = userMessage.replace(/\s+/g, ' ').trim()



      // 提取第一句话作为标题

      const firstSentence = cleanMessage.split(/[。！？\n]/)[0]



      // 限制标题长度

      if (firstSentence.length > 30) {

        return firstSentence.substring(0, 30) + '...'

      }



      return firstSentence || '新对话'

    }

  }



  // 如果没有文件标签，使用原来的方法

  let cleanContent = content.replace(/<[^>]*>/g, '')

  cleanContent = cleanContent.replace(/\s+/g, ' ').trim()



  // 提取第一句话作为标题

  const firstSentence = cleanContent.split(/[。！？\n]/)[0]



  // 限制标题长度

  if (firstSentence.length > 30) {

    return firstSentence.substring(0, 30) + '...'

  }



  return firstSentence || '新对话'

}



// 格式化时间

const formatTime = (timestamp) => {

  if (!timestamp) return '未知时间'



  // 处理时间戳

  let date

  if (typeof timestamp === 'string') {

    // 如果是ISO字符串格式，直接创建Date对象

    date = new Date(timestamp)

  } else {

    // 如果是数字时间戳

    let timestampMs = timestamp

    if (timestamp.toString().length === 10) {

      timestampMs = timestamp * 1000

    } else if (timestamp.toString().length === 11) {

      timestampMs = timestamp * 1000

    } else if (timestamp.toString().length === 13) {

      // 13位时间戳，直接使用

    } else {

      timestampMs = timestamp * 1000

    }

    date = new Date(timestampMs)

  }



  const now = new Date()



  // 获取今天的开始时间（0点0分0秒）

  const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate())

  // 获取昨天的开始时间

  const yesterdayStart = new Date(todayStart.getTime() - 24 * 60 * 60 * 1000)



  // 今天的对话（日期相同）

  if (date >= todayStart) {

    return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })

  }



  // 昨天的对话（日期是昨天）

  if (date >= yesterdayStart && date < todayStart) {

    return '昨天 ' + date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })

  }



  // 更早的对话：显示为 "8月30日 15:30" 格式

  const month = date.getMonth() + 1 // getMonth() 返回 0-11

  const day = date.getDate()

  const time = date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })



  return `${month}月${day}日 ${time}`

}



// 转换用户反馈状态

const convertUserFeedback = (feedback) => {

  console.log('转换用户反馈状态:', feedback, '类型:', typeof feedback)

  switch (parseInt(feedback)) {

    case 2: return 'like'    // 满意(赞)

    case 3: return 'dislike' // 不满意(踩)

    case 0:

    default: return null     // 无反馈

  }

}



// 滚动到底部

const scrollToBottom = () => {

  nextTick(() => {

    const chatContent = document.querySelector('.chat-content')

    console.log('滚动函数执行，找到聊天区域:', chatContent)

    if (chatContent) {

      console.log('滚动前 - scrollTop:', chatContent.scrollTop, 'scrollHeight:', chatContent.scrollHeight, 'clientHeight:', chatContent.clientHeight)



      // 强制滚动到底部

      chatContent.scrollTop = chatContent.scrollHeight



      // 如果上面的方法不工作，尝试其他方法

      setTimeout(() => {

        chatContent.scrollTop = chatContent.scrollHeight

        console.log('延迟滚动后 - scrollTop:', chatContent.scrollTop)

      }, 10)



      // 使用scrollIntoView方法作为备选

      const lastMessage = chatContent.lastElementChild

      if (lastMessage) {

        lastMessage.scrollIntoView({ behavior: 'smooth', block: 'end' })

      }



      console.log('滚动后 - scrollTop:', chatContent.scrollTop)

    } else {

      console.warn('未找到聊天内容区域')

    }

  })

}



// 检查是否在底部

const isAtBottom = () => {

  const chatContent = document.querySelector('.chat-content')

  if (!chatContent) return true



  const threshold = 3.125 // 50px的容差 (50px)

  return chatContent.scrollHeight - chatContent.scrollTop - chatContent.clientHeight < threshold

}



// 删除历史记录

const deleteHistoryItem = (historyItem, index) => {

  console.log('准备删除安全培训历史记录:', historyItem)



  // 设置要删除的项目并显示确认弹窗

  deleteTargetItem.value = { item: historyItem, index: index }

  showDeleteModal.value = true

}



// 确认删除历史记录

const confirmDeleteHistory = async () => {

  if (!deleteTargetItem.value) return



  const { item: historyItem, index } = deleteTargetItem.value



  try {

    // 调用删除接口

    const response = await apis.deleteHistoryRecord({

      ai_conversation_id: historyItem.id

    })



    if (response.statusCode === 200) {

      // 删除成功，从列表中移除

      historyData.value.splice(index, 1)



      // 如果删除的是当前激活的历史记录，需要清空界面并调用新建任务

      if (historyItem.isActive) {

        await createNewChat()

      }



      console.log('安全培训历史记录删除成功')

      ElMessage.success('删除成功')

    } else {

      console.error('删除安全培训历史记录失败:', response.msg)

      ElMessage.error(response.msg || '删除失败')

    }

  } catch (error) {

    console.error('删除安全培训历史记录失败:', error)

    ElMessage.error('删除失败，请稍后重试')

  } finally {

    // 关闭弹窗并清除目标项

    showDeleteModal.value = false

    deleteTargetItem.value = null

  }

}



// 取消删除

const cancelDeleteHistory = () => {

  showDeleteModal.value = false

  deleteTargetItem.value = null

}



// 处理新建任务点击

const handleNewChatClick = () => {

  console.log('点击新建任务按钮，isProcessing:', isProcessing.value)

  if (isProcessing.value) {

    console.log('正在处理中，无法新建任务')

    return

  }

  createNewChat()

}



// 处理历史记录项点击

const handleHistoryItemClick = (item, index) => {

  console.log('点击历史记录项，isProcessing:', isProcessing.value, 'isGeneratingOutline:', isGeneratingOutline.value, 'item.isActive:', item.isActive, 'isSwitchingHistory:', isSwitchingHistory.value)

  if (item.isActive || isProcessing.value || isGeneratingOutline.value || isSwitchingHistory.value) {

    console.log('正在处理中、正在生成大纲、已激活或正在切换历史记录，无法切换')

    return

  }

  handleHistoryItem(item)

}



// 方法

const createNewChat = async () => {

  console.log('创建新安全培训任务')



  // 重置所有状态

  ai_conversation_id.value = 0

  chatMessages.value = []

  messageText.value = ''

  selectedFile.value = null



  // 重置保存状态

  lastSavedOutlineData.value = null

  lastSavedPPTData.value = null

  isSaving.value = false

  showChat.value = false



  // 清除所有历史记录的选中状态

  historyData.value.forEach((item) => {

    item.isActive = false

  })



  // 重置步骤状态，回到第一步

  currentStep.value = 'step1'



  // 清除大纲相关数据

  outlineData.value = null

  outlineStats.value = {}

  outlineTitle.value = ''



  // 清除评价状态

  evaluation.value = ''

  outlineFeedback.value = null

  currentAiMessageId.value = null

  outlineId.value = null



  // 清除编辑状态

  editingItem.value = null

  editingType.value = ''

  editingIndex.value = null

  editingContent.value = ''



  // 清除PPT相关状态

  currentSlideIndex.value = 0

  selectedTemplate.value = 0

  showDownloadOptions.value = false

  selectedDownloadOption.value = 0

  pptSlides.value = []

  currentEditingSlide.value = { title: '', content: '' }



  // 清除PPT预览相关状态

  generatedPPT.value = []

  currentPPTSlideIndex.value = 0

  selectedPPTElementIndex.value = -1

  editingPPTElementIndex.value = -1

  editingPPTHtml.value = ''

  zoom.value = 1

  selectedImageIndex.value = null



  // 重新初始化幻灯片图片数组

  slideImages.value = [

    template5Slide1, // 第1页

    template5Slide2, // 第2页

    template5Slide3, // 第3页

    template5Slide4, // 第4页

    template5Slide5  // 第5页

  ]



  // 刷新历史记录列表

  await getHistoryRecordList()

}



// 获取历史记录图片

const getHistoryImage = (item) => {

  // 如果有封面图且不为空，使用封面图

  if (item.cover_image && item.cover_image.trim()) {

    return item.cover_image

  }

  // 否则使用默认图片

  return defaultHistoryIcon

}



// 从URL加载PPT数据

const loadPPTFromUrl = async (pptJsonUrl) => {

  try {

    console.log('开始从URL加载PPT数据:', pptJsonUrl)



    // 使用后端API代理获取JSON数据

    const response = await apis.getPPTJson({ url: pptJsonUrl })

    console.log('从API获取PPT数据响应:', response)



    if (response.statusCode === 200 && response.data) {

      const pptData = response.data

      console.log('从URL加载的PPT数据:', pptData)



      // 设置PPT数据

      generatedPPT.value = pptData



      // 启用PPT预览模式

      showDownloadOptions.value = true



      console.log('PPT数据加载完成，已启用预览模式')

      return pptData

    } else {

      throw new Error(`API返回错误: ${response.msg || '未知错误'}`)

    }

  } catch (error) {

    console.error('从URL加载PPT数据失败:', error)

    // 如果API也失败，尝试重新生成PPT

    try {

      console.log('API加载失败，尝试重新生成PPT数据...')

      await loadAIPPTData()

      showDownloadOptions.value = true

      console.log('重新生成PPT数据完成')

    } catch (regenerateError) {

      console.error('重新生成PPT也失败:', regenerateError)

      throw error

    }

  }

}



// 保存步骤信息到后端

const saveStepToBackend = async (updateCoverImage = true, forceSave = false) => {

  try {

    if (isSaving.value) {

      console.log('正在保存中，跳过重复保存请求')

      return false

    }



    // 检查PPT数据是否有变化

    if (!forceSave && !hasPPTDataChanged()) {

      console.log('PPT数据未发生变化，跳过保存')

      return false

    }



    isSaving.value = true

    console.log('开始保存步骤信息到后端...')



    // 生成PPT JSON数据

    const pptJsonData = {

      slides: generatedPPT.value,

      title: outlineTitle.value || '安全培训演示文稿',

      generatedAt: new Date().toISOString()

    }



    // 直接使用JSON数据，不再上传到OSS

    const pptJsonContent = JSON.stringify(pptJsonData, null, 2)

    console.log('PPT JSON数据已准备，长度:', pptJsonContent.length)



    // 封面图处理逻辑

    let coverImage = 'http://172.16.17.52:8060/gdsc-ai-aqzs/images/2025/0909_1757403783.png' // 默认封面图



    // 如果使用的是模板7（红色主题），使用指定的封面图

    if (selectedTemplateStyle.value === 'red') {

      coverImage = 'http://172.16.17.52:8060/gdsc-ai-aqzs/images/2025/0913_1757730132.png'

      console.log('使用模板7专用封面图:', coverImage)

    }



    // 如果使用的是模板8（蓝色科技主题），使用指定的封面图

    if (selectedTemplateStyle.value === 'blueTech') {

      coverImage = 'http://172.16.17.52:8060/gdsc-ai-aqzs/images/2025/0913_1757730132.png'

      console.log('使用模板8专用封面图:', coverImage)

    }



    if (updateCoverImage) {

      // 只有在需要更新封面图时才查找新的封面图

      // 遍历所有幻灯片，查找第一张用户上传的图片（排除模板默认图片）

      for (const slide of generatedPPT.value) {

        if (slide.elements && slide.elements.length > 0) {

          for (const element of slide.elements) {

            if (element.type === 'image' && element.src && element.src.startsWith('http')) {

              // 跳过模板中的默认图片（unsplash图片）

              if (element.src.includes('unsplash.com') || element.src.includes('placeholder')) {

                console.log('跳过模板默认图片:', element.src)

                continue

              }

              // 找到第一张用户上传的图片，使用它作为封面图

              coverImage = element.src

              console.log('使用用户上传的图片作为封面图:', coverImage)

              break

            }

          }

          if (coverImage !== 'http://172.16.17.52:8060/gdsc-ai-aqzs/images/2025/0909_1757403783.png' &&

            coverImage !== 'http://172.16.17.52:8060/gdsc-ai-aqzs/images/2025/0913_1757730132.png') {

            break // 已经找到用户上传的图片，跳出外层循环

          }

        }

      }

    } else {

      // 不更新封面图，保持使用现有的封面图

      if (ai_conversation_id.value) {

        const currentHistoryItem = historyData.value.find(item => item.id === ai_conversation_id.value)

        if (currentHistoryItem && currentHistoryItem.cover_image && currentHistoryItem.cover_image.trim()) {

          coverImage = currentHistoryItem.cover_image

          console.log('保持使用现有封面图:', coverImage)

        }

      }

    }



    // 调用保存步骤接口

    const saveStepData = {

      ai_conversation_id: ai_conversation_id.value,

      step: 1, // 设置为步骤1，表示PPT已生成

      ppt_json_url: '', // 不再使用OSS URL

      ppt_json_content: JSON.stringify(generatedPPT.value), // 存储完整的PPT JSON内容

      cover_image: coverImage // 使用检测到的封面图

    }



    console.log('正在保存步骤信息:', saveStepData)

    const saveResponse = await apis.saveStep(saveStepData)



    if (saveResponse.statusCode === 200) {

      console.log('步骤信息保存成功')

      // 更新上次保存的PPT数据

      lastSavedPPTData.value = JSON.parse(JSON.stringify(generatedPPT.value)) // 深拷贝

      ElMessage.success('PPT生成完成并已保存！')

      return true

    } else {

      console.error('保存步骤信息失败:', saveResponse.msg)

      ElMessage.warning('PPT生成完成，但保存步骤信息失败')

      return false

    }

  } catch (error) {

    console.error('保存步骤信息时发生错误:', error)

    ElMessage.warning('PPT生成完成，但保存步骤信息时发生错误')

    return false

  } finally {

    isSaving.value = false

  }

}



// 点击历史记录

const handleHistoryItem = async (historyItem) => {

  console.log('点击安全培训历史记录:', historyItem)



  // 设置切换状态，防止快速点击

  isSwitchingHistory.value = true



  try {

    // 立即更新激活状态，让用户看到反馈

    selectedHistoryItem.value = historyItem



    // 保存当前的历史记录ID，避免在异步保存过程中被覆盖

    const currentConversationId = ai_conversation_id.value

    const currentOutlineId = outlineId.value



    // 保存当前的数据快照，避免在异步保存过程中被覆盖

    const currentOutlineData = outlineData.value ? JSON.parse(JSON.stringify(outlineData.value)) : null

    const currentOutlineTitle = outlineTitle.value

    const currentOutlineStats = outlineStats.value ? JSON.parse(JSON.stringify(outlineStats.value)) : null

    const currentGeneratedPPT = generatedPPT.value ? JSON.parse(JSON.stringify(generatedPPT.value)) : null



    // 更新数据层的active状态

    historyData.value.forEach((item) => {

      item.isActive = item.id === historyItem.id

    })



    // 设置加载状态

    isLoadingHistory.value = true



    // 异步保存当前数据（不阻塞UI更新）

    const saveCurrentData = async () => {

      // 只有在数据真正发生变化时才保存

      let hasChanges = false



      // 检查大纲数据是否有变化（使用数据快照）

      if (currentOutlineData && currentOutlineData.length > 0 && currentOutlineId) {

        // 检查数据快照是否有变化

        const hasChangesInSnapshot = !lastSavedOutlineData.value ||

          JSON.stringify({

            title: currentOutlineTitle,

            stats: currentOutlineStats,

            chapters: currentOutlineData

          }) !== JSON.stringify(lastSavedOutlineData.value)



        if (hasChangesInSnapshot) {

          console.log('检测到当前大纲数据有变化，保存当前修改到历史记录:', currentOutlineId)

          try {

            // 临时设置ai_conversation_id为当前要保存的历史记录ID

            const originalConversationId = ai_conversation_id.value

            ai_conversation_id.value = currentOutlineId



            // 临时设置数据快照

            const originalOutlineData = outlineData.value

            const originalOutlineTitle = outlineTitle.value

            const originalOutlineStats = outlineStats.value



            outlineData.value = currentOutlineData

            outlineTitle.value = currentOutlineTitle

            outlineStats.value = currentOutlineStats



            const saved = await saveOutlineToBackend()



            // 恢复ai_conversation_id

            ai_conversation_id.value = originalConversationId



            // 检查是否需要恢复数据（如果当前数据不是目标历史记录的数据）

            if (ai_conversation_id.value !== historyItem.id) {

              // 如果当前不是目标历史记录，恢复原始数据

              outlineData.value = originalOutlineData

              outlineTitle.value = originalOutlineTitle

              outlineStats.value = originalOutlineStats

            }



            if (saved) {

              hasChanges = true

              console.log('当前大纲修改已保存到历史记录:', currentOutlineId)



              // 更新历史记录列表中的大纲数据

              const currentHistoryItem = historyData.value.find(item => item.id === currentOutlineId)

              if (currentHistoryItem) {

                currentHistoryItem.rawData = {

                  ...currentHistoryItem.rawData,

                  ppt_outline: JSON.stringify({

                    title: currentOutlineTitle,

                    stats: currentOutlineStats,

                    chapters: currentOutlineData,

                    timestamp: Date.now()

                  })

                }

                console.log('已更新历史记录列表中的大纲数据')

              }

            }

          } catch (error) {

            console.error('保存当前大纲修改失败:', error)

          }

        } else {

          console.log('大纲数据快照无变化，跳过保存')

        }

      }



      // 检查PPT数据是否有变化（使用数据快照）

      if (currentGeneratedPPT && currentGeneratedPPT.length > 0 && currentConversationId) {

        // 检查PPT数据快照是否有变化

        const hasChangesInPPTSnapshot = !lastSavedPPTData.value ||

          JSON.stringify(currentGeneratedPPT) !== JSON.stringify(lastSavedPPTData.value)



        if (hasChangesInPPTSnapshot) {

          console.log('检测到当前PPT数据有变化，保存当前修改到历史记录:', currentConversationId)

          try {

            // 临时设置ai_conversation_id为当前要保存的历史记录ID

            const originalConversationId = ai_conversation_id.value

            ai_conversation_id.value = currentConversationId



            // 临时设置PPT数据快照

            const originalGeneratedPPT = generatedPPT.value

            generatedPPT.value = currentGeneratedPPT



            const saved = await saveStepToBackend()



            // 恢复ai_conversation_id

            ai_conversation_id.value = originalConversationId



            // 检查是否需要恢复数据（如果当前数据不是目标历史记录的数据）

            if (currentConversationId !== historyItem.id) {

              // 如果当前不是目标历史记录，恢复原始数据

              generatedPPT.value = originalGeneratedPPT

            }



            if (saved) {

              hasChanges = true

              console.log('当前PPT修改已保存到历史记录:', currentConversationId)



              // 更新历史记录列表中的PPT数据

              const currentHistoryItem = historyData.value.find(item => item.id === currentConversationId)

              if (currentHistoryItem) {

                currentHistoryItem.rawData = {

                  ...currentHistoryItem.rawData,

                  ppt_json_content: JSON.stringify(currentGeneratedPPT),

                  timestamp: Date.now()

                }

                console.log('已更新历史记录列表中的PPT数据')

              }

            }

          } catch (error) {

            console.error('保存当前PPT修改失败:', error)

          }

        } else {

          console.log('PPT数据快照无变化，跳过保存')

        }

      }



      if (!hasChanges) {

        console.log('当前数据无变化，跳过保存')

      }

    }



    // 更新ai_conversation_id（在保存任务启动前）

    ai_conversation_id.value = historyItem.id



    // 启动异步保存任务（不等待完成，避免阻塞UI）

    saveCurrentData()



    // 根据step字段决定跳转到哪个页面

    if (historyItem.step === 0) {

      // step为0，跳转到步骤二（大纲页）

      console.log('历史记录step为0，跳转到步骤二（大纲页）')



      // 清除PPT预览相关状态

      generatedPPT.value = []

      currentPPTSlideIndex.value = 0

      selectedPPTElementIndex.value = -1

      editingPPTElementIndex.value = -1

      editingPPTHtml.value = ''

      zoom.value = 1

      selectedImageIndex.value = null

      showDownloadOptions.value = false



      // 重置为模板预览状态

      currentSlideIndex.value = 0



      // 显示加载状态

      const loadingMessage = {

        type: 'ai',

        content: '正在加载历史对话...',

        displayContent: '正在加载历史对话...',

        isTyping: true,

        id: Date.now() + 1,

        userFeedback: null

      }

      chatMessages.value = [loadingMessage]



      try {

        // 获取该对话的详细消息

        const success = await getConversationMessages(historyItem.id)



        if (success) {

          console.log('安全培训历史对话加载成功', historyItem)

          // 移除加载消息

          chatMessages.value = chatMessages.value.filter(msg => msg.id !== loadingMessage.id)



          // 数据加载完成后再设置步骤

          currentStep.value = 'step2'



          // 清除加载状态

          isLoadingHistory.value = false



          // 检查是否有大纲数据（优先使用outlineData.value，因为getConversationMessages可能已经设置了）

          // 但是要确保这是当前历史记录的大纲数据，不是上一个历史记录的

          if (outlineData.value && outlineData.value.length > 0 && ai_conversation_id.value === historyItem.id) {

            console.log('检测到已有大纲数据，直接使用（来自getConversationMessages）')



            // 设置大纲ID

            outlineId.value = historyItem.id



            // 更新保存状态，避免重复保存

            lastSavedOutlineData.value = {

              title: outlineTitle.value,

              stats: outlineStats.value,

              chapters: JSON.parse(JSON.stringify(outlineData.value)) // 深拷贝

            }

            console.log('设置大纲ID:', outlineId.value)



            // 跳转到步骤二

            currentStep.value = 'step2'

            console.log('已跳转到步骤二：培训大纲界面（使用getConversationMessages数据）')



            // 确保模板预览数据已加载

            loadLocalPPT()

            updateSlideThumbnails()



          } else {

            console.log('未找到大纲数据，显示聊天界面')

            showChat.value = true

          }

        } else {

          // 加载失败，显示错误消息

          chatMessages.value = [{

            type: 'ai',

            content: '抱歉，加载历史对话失败，请稍后重试。',

            displayContent: '抱歉，加载历史对话失败，请稍后重试。',

            isTyping: false,

            id: Date.now() + 1,

            userFeedback: null

          }]

          showChat.value = true

        }

      } catch (error) {

        console.error('加载安全培训历史对话失败:', error)

        chatMessages.value = [{

          type: 'ai',

          content: '抱歉，加载历史对话时发生错误，请稍后重试。',

          displayContent: '抱歉，加载历史对话时发生错误，请稍后重试。',

          isTyping: false,

          id: Date.now() + 1,

          userFeedback: null

        }]

        showChat.value = true



        // 清除加载状态

        isLoadingHistory.value = false

      }

    } else if (historyItem.step === 1) {

      // step为1，直接跳转到PPT预览页面

      console.log('历史记录step为1，直接跳转到PPT预览页面')



      // 清除之前的PPT预览状态，准备加载新的PPT数据

      generatedPPT.value = []

      currentPPTSlideIndex.value = 0

      selectedPPTElementIndex.value = -1

      editingPPTElementIndex.value = -1

      editingPPTHtml.value = ''

      zoom.value = 1

      selectedImageIndex.value = null



      // 显示加载状态

      const loadingMessage = {

        type: 'ai',

        content: '正在加载PPT数据...',

        displayContent: '正在加载PPT数据...',

        isTyping: true,

        id: Date.now() + 1,

        userFeedback: null

      }

      chatMessages.value = [loadingMessage]



      try {

        // 获取该对话的详细消息

        const success = await getConversationMessages(historyItem.id)



        if (success) {

          console.log('安全培训历史对话加载成功')

          // 移除加载消息

          chatMessages.value = chatMessages.value.filter(msg => msg.id !== loadingMessage.id)



          // 数据加载完成后再设置步骤

          currentStep.value = 'step3'



          // 清除加载状态

          isLoadingHistory.value = false



          // 获取最新的AI回复消息并解析大纲

          const aiMessages = chatMessages.value.filter(msg => msg.type === 'ai')

          const aiMessage = aiMessages.length > 1 ? aiMessages[1] : (aiMessages.length > 0 ? aiMessages[0] : null)

          if (aiMessage && aiMessage.content) {

            console.log('找到AI回复，开始解析大纲并直接生成PPT预览')



            // 设置大纲ID

            outlineId.value = historyItem.id



            // 解析AI回复获取大纲数据

            const parsedOutline = parseOutlineFromAI(aiMessage.content)

            if (parsedOutline && parsedOutline.chapters && parsedOutline.chapters.length > 0) {

              outlineData.value = parsedOutline.chapters

              outlineTitle.value = parsedOutline.title || '安全培训大纲'

              // 使用新的统计计算函数

              outlineStats.value = calculateOutlineStats(parsedOutline.chapters)



              // step=1时，直接从数据库读取已保存的PPT数据

              try {

                console.log('step=1，从数据库读取已保存的PPT数据...')



                // 检查是否有保存的PPT JSON内容

                if (historyItem.ppt_json_content && historyItem.ppt_json_content.trim()) {

                  console.log('找到已保存的PPT JSON内容，直接加载...')



                  // 解析JSON内容

                  const savedPPTData = JSON.parse(historyItem.ppt_json_content)

                  console.log('解析的PPT数据:', savedPPTData)



                  // 直接设置PPT数据（后端数据已经是最新的）

                  generatedPPT.value = savedPPTData

                  showDownloadOptions.value = true



                  // 更新保存状态，避免重复保存

                  lastSavedPPTData.value = JSON.parse(JSON.stringify(savedPPTData)) // 深拷贝



                  console.log('PPT数据加载完成，共', savedPPTData.length, '张幻灯片')



                  // 确保历史记录列表中的数据也是最新的

                  const updatedHistoryItem = historyData.value.find(item => item.id === historyItem.id)

                  if (updatedHistoryItem) {

                    updatedHistoryItem.ppt_json_content = historyItem.ppt_json_content

                    console.log('已同步历史记录列表中的PPT数据')

                  }

                } else {

                  console.log('未找到保存的PPT内容，重新生成...')



                  // 如果没有保存的内容，则重新生成

                  const aipptData = convertOutlineToAIPPT(outlineData.value, outlineTitle.value)

                  const filledAIPPTData = await fillAIPPTContent(aipptData, outlineTitle.value)

                  await loadAIPPTData(filledAIPPTData, false)

                  showDownloadOptions.value = true

                  console.log('PPT数据重新生成完成')

                }

              } catch (error) {

                console.error('加载PPT失败:', error)

                ElMessage.error('加载PPT失败: ' + error.message)

              }

            } else {

              console.log('解析大纲失败，显示聊天界面')

              showChat.value = true

            }

          } else {

            console.log('未找到AI回复，显示聊天界面')

            showChat.value = true

          }

        } else {

          // 加载失败，显示错误消息

          chatMessages.value = [{

            type: 'ai',

            content: '抱歉，加载历史对话失败，请稍后重试。',

            displayContent: '抱歉，加载历史对话失败，请稍后重试。',

            isTyping: false,

            id: Date.now() + 1,

            userFeedback: null

          }]

          showChat.value = true

        }

      } catch (error) {

        console.error('加载安全培训历史对话失败:', error)

        chatMessages.value = [{

          type: 'ai',

          content: '抱歉，加载历史对话时发生错误，请稍后重试。',

          displayContent: '抱歉，加载历史对话时发生错误，请稍后重试。',

          isTyping: false,

          id: Date.now() + 1,

          userFeedback: null

        }]

        showChat.value = true



        // 清除加载状态

        isLoadingHistory.value = false

      }

    } else {

      // 其他情况，显示聊天界面

      console.log('历史记录step未知，显示聊天界面')

      showChat.value = true

    }

  } catch (error) {

    console.error('处理历史记录切换失败:', error)

    ElMessage.error('切换历史记录失败，请稍后重试')

  } finally {

    // 重置切换状态

    isSwitchingHistory.value = false

    isLoadingHistory.value = false

  }

}



// 功能卡片图标计数器

let functionCardIconIndex = 0

// 推荐问题图标计数器

let questionIconIndex = 0



// 根据功能卡片标题返回对应的图标

const getFunctionCardIcon = (title) => {

  // 按顺序循环使用4个图标

  const icons = [safetyTrainingIcon, safetyAssessmentIcon, safetyRegulationsIcon, emergencyProceduresIcon]

  const icon = icons[functionCardIconIndex % icons.length]

  functionCardIconIndex++

  return icon

}



// 根据问题标题返回对应的图标

const getQuestionIcon = (question) => {

  // 按顺序循环使用3个图标

  const icons = [questionIcon1, questionIcon2, questionIcon3]

  const icon = icons[questionIconIndex % icons.length]

  questionIconIndex++

  return icon

}



// 点击功能卡片

const handleFunctionCard = (cardType) => {

  console.log('点击功能卡片:', cardType)



  // 显示聊天界面

  showChat.value = true



  // 根据卡片类型设置不同的消息

  let message = ''



  // 如果是后端数据，直接使用卡片标题作为消息

  if (typeof cardType === 'string' && cardType.length > 0) {

    message = `请详细介绍${cardType}的相关内容`

  } else {

    // 兼容原有的硬编码逻辑

    switch (cardType) {

      case 'safety-training':

        message = '请详细介绍安全培训课程的相关内容'

        break

      case 'safety-assessment':

        message = '请介绍安全评估测试的关键要点'

        break

      case 'safety-regulations':

        message = '请查询相关的安全法规和标准'

        break

      case 'emergency-procedures':

        message = '请介绍应急处理程序的关键步骤'

        break

      default:

        message = `请详细介绍${cardType}的相关内容`

    }

  }



  // 自动发送消息

  messageText.value = message

  sendMessage()

}



// 发送消息

const sendMessage = async () => {

  if (messageText.value.trim() && !isSending.value) {

    console.log('开始发送消息:', messageText.value, '文件:', selectedFile.value)



    // 设置发送状态

    isSending.value = true

    isProcessing.value = true



    // 显示聊天界面

    showChat.value = true



    // 如果是新对话（没有历史记录或当前没有激活的历史记录），清除所有历史记录的选中状态

    if (chatMessages.value.length === 0) {

      historyData.value.forEach((item) => {

        item.isActive = false;

      });

      console.log('新对话开始，清除所有历史记录的选中状态');

    }



    // 添加用户消息

    const userMessage = {

      type: 'user',

      content: messageText.value,

      file: selectedFile.value, // 添加文件信息

      id: Date.now() // 添加唯一ID

    }

    chatMessages.value.push(userMessage)



    // 添加AI消息（初始状态为思考中）

    const aiMessage = {

      type: 'ai',

      content: '',

      displayContent: '',

      isTyping: true,

      id: Date.now() + 1, // 添加唯一ID

      userFeedback: null // 用户反馈状态：null, 'like', 'dislike'

    }

    chatMessages.value.push(aiMessage)



    // 清空输入框

    const currentMessage = messageText.value

    messageText.value = ''



    // 立即清理选中的文件

    if (selectedFile.value) {

      removeSelectedFile()

    }



    // 发送消息后滚动到底部

    scrollToBottom()



    console.log('当前聊天消息:', chatMessages.value)



    try {

      // 构建发送给AI的消息

      let messageToAI = currentMessage



      // 如果有文件，使用标签格式

      if (userMessage.file && userMessage.file.content) {

        messageToAI = `<word>${userMessage.file.content}</word><filename>${userMessage.file.name}</filename><filesize>${userMessage.file.size}</filesize>${currentMessage}`

      }



      // 调用后端DeepSeek接口

      const response = await apis.sendDeepseekMessage({

        // ai_conversation_id: ai_conversation_id.value,

        // ===== 已删除：user_id - 后端从token解析 =====

        business_type: 1,

        message: messageToAI

      })



      console.log('DeepSeek API响应:', response)



      if (response.statusCode === 200) {

        const aiReply = response.data.reply

        ai_conversation_id.value = response.data.ai_conversation_id

        // 处理特殊字符和表情符号

        const processedReply = processAIResponse(aiReply)



        // 处理文件标签格式的回显

        const processedReplyWithFileDisplay = processFileDisplay(processedReply, userMessage.file)



        // 将Markdown格式转换为HTML格式

        const htmlReply = markdownToHtml(processedReplyWithFileDisplay)



        // 开始打字效果 - 按完整HTML标签或文本块显示，避免标签分割

        const textBlocks = []

        let currentBlock = ''

        let inTag = false

        let tagContent = ''



        // 将HTML内容分割成完整的块

        for (let i = 0; i < htmlReply.length; i++) {

          const char = htmlReply[i]



          if (char === '<') {

            // 如果之前有文本内容，先保存

            if (currentBlock && !inTag) {

              textBlocks.push({ type: 'text', content: currentBlock })

              currentBlock = ''

            }

            inTag = true

            tagContent = char

          } else if (char === '>') {

            // 标签结束

            tagContent += char

            textBlocks.push({ type: 'tag', content: tagContent })

            inTag = false

            tagContent = ''

          } else if (inTag) {

            // 在标签内

            tagContent += char

          } else {

            // 普通文本

            currentBlock += char

          }

        }



        // 保存最后一个文本块

        if (currentBlock) {

          textBlocks.push({ type: 'text', content: currentBlock })

        }



        console.log('分割后的文本块:', textBlocks)



        let currentBlockIndex = 0

        let currentCharIndex = 0



        const typeInterval = setInterval(() => {

          if (currentBlockIndex < textBlocks.length) {

            const currentBlock = textBlocks[currentBlockIndex]



            if (currentBlock.type === 'tag') {

              // 标签直接显示，不分字符

              aiMessage.displayContent += currentBlock.content

              currentBlockIndex++

              currentCharIndex = 0

            } else {

              // 文本按字符显示

              if (currentCharIndex < currentBlock.content.length) {

                const newContent = aiMessage.displayContent + currentBlock.content[currentCharIndex]

                aiMessage.displayContent = newContent

                currentCharIndex++

              } else {

                // 当前文本块完成，移动到下一个

                currentBlockIndex++

                currentCharIndex = 0

              }

            }



            // 强制触发Vue响应式更新

            chatMessages.value = [...chatMessages.value]



            // 逐步扩展宽度

            const messageElement = document.querySelector(`[data-message-index="${chatMessages.value.length - 1}"] .message-content`)

            if (messageElement) {

              messageElement.style.width = 'fit-content'

            }



            // 每显示一个字符都滚动到底部，确保长回复时滚动条始终跟随

            scrollToBottom()



            // 强制触发DOM更新后再滚动一次

            nextTick(() => {

              scrollToBottom()

            })

          } else {

            // 所有块都显示完成

            aiMessage.isTyping = false

            aiMessage.content = processedReply // 保存完整内容

            clearInterval(typeInterval)

            console.log('打字完成')



            // 打字完成后设置最终宽度

            const messageElement = document.querySelector(`[data-message-index="${chatMessages.value.length - 1}"] .message-content`)

            if (messageElement) {

              messageElement.style.width = '100%'

            }



            // 强制触发Vue响应式更新

            setTimeout(async () => {

              chatMessages.value = [...chatMessages.value]

              console.log('打字完成，AI回复内容已全部显示')



              // AI回复完成后，获取最新的历史记录

              await getHistoryRecordList()



              // 如果是新对话，将最新的历史记录设为激活状态

              if (ai_conversation_id.value > 0) {

                historyData.value.forEach((item) => {

                  item.isActive = item.id === ai_conversation_id.value;

                });

                console.log('设置最新历史记录为激活状态，conversationId:', ai_conversation_id.value);



                // 设置大纲ID（使用对话ID作为大纲ID）

                outlineId.value = ai_conversation_id.value

                console.log('设置新对话的大纲ID:', outlineId.value);

              }



              // 打字完成后强制滚动到底部，确保长回复完全可见

              scrollToBottom()



              // AI回复完全结束后，解禁历史记录和新建任务

              isProcessing.value = false

              console.log('AI回复完成，解禁历史记录和新建任务')



              // 立即检查是否为PPT生成需求，如果是则跳转到步骤二

              console.log('AI回复完成，开始检查是否为PPT需求')

              checkAndNavigateToOutline(processedReply)

            }, 100)

          }

        }, 20) // 每20ms显示一个字符，更频繁地滚动



      } else {

        // API调用失败

        aiMessage.isTyping = false

        aiMessage.content = '抱歉，我暂时无法回答您的问题，请稍后重试。'

        aiMessage.displayContent = aiMessage.content

        console.error('DeepSeek API调用失败:', response)



        // API调用失败时也要解禁历史记录和新建任务

        isProcessing.value = false

      }



    } catch (error) {

      console.error('发送消息失败:', error)

      // 显示错误消息

      aiMessage.isTyping = false

      aiMessage.content = '抱歉，网络连接出现问题，请检查网络后重试。'

      aiMessage.displayContent = aiMessage.content



      // 网络错误时也要解禁历史记录和新建任务

      isProcessing.value = false

    } finally {

      // 重置发送状态

      isSending.value = false

    }

  }

}



// 生成安全培训智能提示词

const generateSafetyTrainingPrompt = () => {

  return `请根据用户的需求进行回复，无论用户说什么都要提取关键词生成安全培训PPT大纲，请严格按照以下格式回复：



以下是为您准备的PPT大纲，包含相关内容：



[在这里生成大纲内容，使用Markdown格式，包含：

1. 章节标题（如：第一章 xxxx）

2. 小节内容（如：1.1 xxxx）

3. 子小节内容（如：1.1.1 xxxx）



大纲统计信息：

- 总章节数：X章

- 总小节数：X小节  

- 预计PPT页数：X-X页

- 预计讲解时长：X-X分钟]



请确保：

1. 每个章节都以"第X章"开头

2. 每个小节都以"X.X"开头

3. 每个子小节都以"X.X.X"开头

4. 统计信息严格按照上述格式

5. 不要添加其他无关内容或占位符

6. 内容要符合安全培训的主题，包括但不限于：安全生产法规、安全操作规程、事故预防、应急处理、安全文化建设等`;

}



// 生成新大纲

const generateNewOutline = async () => {

  try {

    // 设置生成状态

    isGeneratingOutline.value = true

    isProcessing.value = true



    // 锁定当前的ai_conversation_id，防止在生成过程中被切换

    const lockedConversationId = ai_conversation_id.value



    // 获取当前大纲标题作为请求内容

    const currentTitle = outlineTitle.value || '安全培训大纲'

    const requestMessage = `${currentTitle}`



    console.log('开始生成新大纲111:', requestMessage)

    console.log("锁定的ai_conversation_id:", lockedConversationId)

    console.log("outlineTitle.value:", outlineTitle.value)

    // 调用后端DeepSeek接口

    const response = await apis.sendDeepseekMessage({

      business_type: 1,

      message: outlineTitle.value,

      ai_conversation_id: lockedConversationId

    })



    console.log('新大纲生成API响应:', response)



    if (response.statusCode === 200) {

      const aiReply = response.data.reply



      // 处理特殊字符和表情符号

      const processedReply = processAIResponse(aiReply)



      // 解析新大纲

      const newOutline = parseOutlineFromAI(processedReply)



      if (newOutline && newOutline.chapters && newOutline.chapters.length > 0) {

        console.log('新大纲解析成功:', newOutline)



        // 更新大纲数据

        outlineData.value = newOutline.chapters

        outlineStats.value = newOutline.stats

        outlineTitle.value = newOutline.title



        // 重置反馈状态（新大纲没有反馈）

        outlineFeedback.value = null

        evaluation.value = ''

        // currentAiMessageId.value = null

        outlineId.value = null



        // 立即保存新生成的大纲到后端，防止切换历史记录时数据被覆盖

        // 直接调用保存接口，不依赖全局的ai_conversation_id

        await saveOutlineToBackendDirectly(lockedConversationId, newOutline.chapters, newOutline.title, newOutline.stats)



        ElMessage.success('大纲已生成')

      } else {

        console.log('新大纲解析失败')

        ElMessage.error('新大纲生成失败，请重试')

      }



    } else {

      // API调用失败

      console.error('新大纲生成API调用失败:', response)

      ElMessage.error('生成失败，请重试')

    }



  } catch (error) {

    console.error('生成新大纲失败:', error)

    ElMessage.error('生成失败，请重试')

  } finally {

    // 重置生成状态

    isGeneratingOutline.value = false

    isProcessing.value = false

  }

}



// 确保题目初始值正确
const ensureQuestionInitialValues = (examData) => {
  // 单选题
  if (examData.singleChoice && examData.singleChoice.questions) {
    examData.singleChoice.questions.forEach(question => {
      if (!question.selectedAnswer) {
        question.selectedAnswer = ""
      }
      if (!question.options || question.options.length === 0) {
        question.options = [
          { key: "A", text: "选项A" },
          { key: "B", text: "选项B" },
          { key: "C", text: "选项C" },
          { key: "D", text: "选项D" }
        ]
      }
    })
  }

  // 判断题
  if (examData.judge && examData.judge.questions) {
    examData.judge.questions.forEach(question => {
      if (!question.selectedAnswer) {
        question.selectedAnswer = ""
      }
    })
  }

  // 多选题
  if (examData.multiple && examData.multiple.questions) {
    examData.multiple.questions.forEach(question => {
      if (!question.selectedAnswers) {
        question.selectedAnswers = []
      }
      if (!question.options || question.options.length === 0) {
        question.options = [
          { key: "A", text: "选项A" },
          { key: "B", text: "选项B" },
          { key: "C", text: "选项C" },
          { key: "D", text: "选项D" }
        ]
      }
    })
  }

  // 简答题
  if (examData.short && examData.short.questions) {
    examData.short.questions.forEach(question => {
      if (!question.outline) {
        question.outline = { keyFactors: "答题要点、关键因素、示例答案" }
      }
    })
  }
}

// 生成默认考试
const generateDefaultExam = () => {
  return {
    title: "安全培训考试",
    totalScore: 100,
    totalQuestions: 17,
    singleChoice: {
      scorePerQuestion: 5,
      totalScore: 25,
      count: 5,
      questions: []
    },
    judge: {
      scorePerQuestion: 3,
      totalScore: 15,
      count: 5,
      questions: []
    },
    multiple: {
      scorePerQuestion: 8,
      totalScore: 40,
      count: 5,
      questions: []
    },
    short: {
      scorePerQuestion: 10,
      totalScore: 20,
      count: 2,
      questions: []
    }
  }
}

// 导出考试文件为Word格式
const exportExamToFile = (examData) => {
  try {
    // 创建Word文档内容（使用HTML格式，兼容WPS和Word）
    const wordContent = createExamWordContent(examData)

    // 创建Blob对象 - 使用Word兼容的MIME类型
    const blob = new Blob([wordContent], {
      type: 'application/msword'
    })

    // 下载文件
    const url = URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.setAttribute('href', url)
    link.setAttribute('download', `${examData.title}_${new Date().toISOString().split('T')[0]}.doc`)
    link.style.visibility = 'hidden'
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    URL.revokeObjectURL(url)

    ElMessage.success('考试文件已下载！')

  } catch (error) {
    console.error('导出考试文件失败:', error)
    ElMessage.error('导出考试文件失败，请重试')
  }
}

// 创建Word格式的考试文档内容（兼容WPS和Word）
const createExamWordContent = (examData) => {
  const currentTime = new Date().toLocaleString('zh-CN')

  // HTML文档内容，使用Word兼容的格式
  let htmlContent = `<!DOCTYPE html>
<html xmlns:o="urn:schemas-microsoft-com:office:office" 
      xmlns:w="urn:schemas-microsoft-com:office:word" 
      xmlns="http://www.w3.org/TR/REC-html40">
<head>
  <meta charset="utf-8">
  <meta name="ProgId" content="Word.Document">
  <meta name="Generator" content="Microsoft Word 15">
  <meta name="Originator" content="Microsoft Word 15">
  <title>${examData.title || '考试试卷'}</title>
  <!--[if gte mso 9]>
  <xml>
    <w:WordDocument>
      <w:View>Print</w:View>
      <w:Zoom>100</w:Zoom>
      <w:DoNotPromptForConvert/>
      <w:DoNotShowRevisions/>
      <w:DoNotPrintRevisions/>
      <w:DoNotShowComments/>
      <w:DoNotShowInsertionsAndDeletions/>
      <w:DoNotShowPropertyChanges/>
      <w:Compatibility>
        <w:BreakWrappedTables/>
        <w:SnapToGridInCell/>
        <w:WrapTextWithPunct/>
        <w:UseAsianBreakRules/>
        <w:DontGrowAutofit/>
      </w:Compatibility>
    </w:WordDocument>
  </xml>
  <![endif]-->
  <style>
    body {
      font-family: "Microsoft YaHei", "宋体", Arial, sans-serif;
      font-size: 14px;
      line-height: 1.6;
      margin: 24px;
      color: #000;
    }
    .header {
      text-align: center;
      margin-bottom: 14px;
    }
    .exam-title {
      font-size: 24px;
      font-weight: bold;
      margin-bottom: 14px;
      color: #000;
    }
    .exam-info {
      font-size: 14px;
      color: #666;
      margin-bottom: 14px;
    }
    .section {
      margin-bottom: 14px;
    }
    .section-title {
      font-size: 18px;
      font-weight: bold;
      margin-bottom: 14px;
      color: #000;
      border-bottom: 2px solid #3e7bfa;
      padding-bottom: 5px;
    }
    .question {
      margin-bottom: 14px;
      padding: 10px;
      background-color: #f9f9f9;
      border-left: 4px solid #3e7bfa;
    }
    .question-header {
      margin-bottom: 14px;
      line-height: 1.6;
    }
    .question-number {
      font-weight: bold;
      color: #3e7bfa;
    }
    .options {
      margin-left: 12px;
    }
    .option {
      margin-bottom: 5px;
    }
    .answer {
      margin-top: 10px;
      padding: 8px;
      background: #e8f4fd;
      border-radius: 4px;
      font-weight: bold;
      color: #2c5aa0;
    }
  </style>
</head>
<body>
  <div class="header">
    <div class="exam-title">${examData.title || '考试试卷'}</div>
    <div class="exam-info">
      总分：${examData.totalScore || 0}分 | 总题数：${examData.totalQuestions || 0}题 | 生成时间：${currentTime}
    </div>
  </div>`

  // 单选题
  if (examData.singleChoice && examData.singleChoice.questions.length > 0) {
    htmlContent += `
  <div class="section">
    <div class="section-title">一、单选题（${examData.singleChoice.count}题，每题${examData.singleChoice.scorePerQuestion}分，共${examData.singleChoice.totalScore}分）</div>`

    examData.singleChoice.questions.forEach((question, index) => {
      htmlContent += `
    <div class="question">
      <div class="question-header">
        <span class="question-number">${index + 1}.</span> ${question.text}
      </div>
      <div class="options">`

      question.options.forEach(option => {
        htmlContent += `
        <div class="option">${option.key}. ${option.text}</div>`
      })

      htmlContent += `
      </div>
      <div class="answer">正确答案：${question.selectedAnswer}</div>
    </div>`
    })

    htmlContent += `
  </div>`
  }

  // 判断题
  if (examData.judge && examData.judge.questions.length > 0) {
    htmlContent += `
  <div class="section">
    <div class="section-title">二、判断题（${examData.judge.count}题，每题${examData.judge.scorePerQuestion}分，共${examData.judge.totalScore}分）</div>`

    examData.judge.questions.forEach((question, index) => {
      htmlContent += `
    <div class="question">
      <div class="question-header">
        <span class="question-number">${index + 1}.</span> ${question.text}
      </div>
      <div class="answer">正确答案：${question.selectedAnswer}</div>
    </div>`
    })

    htmlContent += `
  </div>`
  }

  // 多选题
  if (examData.multiple && examData.multiple.questions.length > 0) {
    htmlContent += `
  <div class="section">
    <div class="section-title">三、多选题（${examData.multiple.count}题，每题${examData.multiple.scorePerQuestion}分，共${examData.multiple.totalScore}分）</div>`

    examData.multiple.questions.forEach((question, index) => {
      htmlContent += `
    <div class="question">
      <div class="question-header">
        <span class="question-number">${index + 1}.</span> ${question.text}
      </div>
      <div class="options">`

      question.options.forEach(option => {
        htmlContent += `
        <div class="option">${option.key}. ${option.text}</div>`
      })

      htmlContent += `
      </div>
      <div class="answer">正确答案：${question.selectedAnswers.join(', ')}</div>
    </div>`
    })

    htmlContent += `
  </div>`
  }

  // 简答题
  if (examData.short && examData.short.questions.length > 0) {
    htmlContent += `
  <div class="section">
    <div class="section-title">四、简答题（${examData.short.count}题，每题${examData.short.scorePerQuestion}分，共${examData.short.totalScore}分）</div>`

    examData.short.questions.forEach((question, index) => {
      htmlContent += `
    <div class="question">
      <div class="question-header">
        <span class="question-number">${index + 1}.</span> ${question.text}
      </div>
      <div class="answer">答题要点：${question.outline.keyFactors}</div>
    </div>`
    })

    htmlContent += `
  </div>`
  }

  htmlContent += `
</body>
</html>`

  return htmlContent
}



// 检查并跳转到大纲页面

const checkAndNavigateToOutline = (aiReply) => {

  console.log('AI回复完成，直接解析并跳转到步骤二')



  // 直接解析AI回复，提取大纲信息

  const parsedOutline = parseOutlineFromAI(aiReply)



  if (parsedOutline && parsedOutline.chapters && parsedOutline.chapters.length > 0) {

    console.log('解析成功，更新大纲数据:', parsedOutline)



    // 更新大纲数据

    outlineData.value = parsedOutline.chapters

    outlineStats.value = parsedOutline.stats

    outlineTitle.value = parsedOutline.title



    // 只有在没有现有反馈状态时才重置（新生成的大纲）

    if (outlineFeedback.value === null && currentAiMessageId.value === null) {

      console.log('新生成的大纲，重置反馈状态')

      outlineFeedback.value = null

      evaluation.value = ''

      outlineId.value = null



      // 立即跳转到步骤二

      currentStep.value = 'step2'

      console.log('已跳转到步骤二：培训大纲界面')

      console.log('当前大纲数据:', outlineData.value)

      console.log('当前统计信息:', outlineStats.value)

      console.log('当前标题:', outlineTitle.value)



      // 确保模板预览数据已加载

      loadLocalPPT()

      updateSlideThumbnails()



      // 不需要自动保存，后台会自动保存AI回复内容

    } else {

      console.log('从历史记录加载的大纲，保持现有反馈状态:', outlineFeedback.value)



      // 立即跳转到步骤二

      currentStep.value = 'step2'

      console.log('已跳转到步骤二：培训大纲界面')

      console.log('当前大纲数据:', outlineData.value)

      console.log('当前统计信息:', outlineStats.value)

      console.log('当前标题:', outlineTitle.value)



      // 确保模板预览数据已加载

      loadLocalPPT()

      updateSlideThumbnails()



      // 从历史记录加载的大纲不需要保存

    }

  } else {

    console.log('解析失败或没有章节数据')

  }

}







// 从AI回复中解析大纲信息

const parseOutlineFromAI = (aiReply) => {

  try {

    console.log('开始解析AI回复中的大纲信息')

    console.log('AI回复内容:', aiReply)



    // 提取大纲标题 - 从AI回复的第一行或标题行提取

    let title = '安全培训大纲' // 默认标题



    // 尝试从第一行提取标题

    const lines = aiReply.split('\n')

    for (let line of lines) {

      const trimmedLine = line.trim()

      // 查找包含"以下是为您准备的PPT大纲"的行，下一行通常是标题

      if (trimmedLine.includes('以下是为您准备的PPT大纲')) {

        // 查找下一行作为标题

        const nextLineIndex = lines.indexOf(line) + 1

        if (nextLineIndex < lines.length) {

          const nextLine = lines[nextLineIndex].trim()

          if (nextLine && nextLine.length > 0 && !nextLine.includes('以下') && !nextLine.includes('大纲统计信息')) {

            title = nextLine

            break

          }

        }

      }

      // 或者直接查找以#开头的标题行

      if (trimmedLine.startsWith('#') && trimmedLine.length > 1) {

        title = trimmedLine.replace(/^#+\s*/, '').trim()

        break

      }

    }



    // 先解析章节内容，然后自动计算统计信息

    // 不再从AI回复中提取统计信息，而是根据解析的章节内容自动计算



    // 提取章节内容 - 使用基于井号数量的解析逻辑

    const chapters = []

    const contentLines = aiReply.split('\n')

    let currentChapter = null



    console.log('开始解析行数:', lines.length)



    for (let i = 0; i < lines.length; i++) {

      const line = lines[i]

      const trimmedLine = line.trim()



      console.log(`第${i}行: "${trimmedLine}"`)



      // 根据井号数量来解析不同层级的标题

      const hashCount = (trimmedLine.match(/^#+/) || [''])[0].length



      if (hashCount === 1) {

        // # 开头的大标题，作为整个大纲的标题

        console.log('找到大标题:', trimmedLine)

        let mainTitle = trimmedLine.replace(/^#\s*/, '').trim()

        title = mainTitle

        continue

      } else if (hashCount === 2) {

        // ## 开头的标题作为章节

        console.log('找到章节:', trimmedLine)

        let chapterTitle = trimmedLine.replace(/^##\s*/, '').trim()

        currentChapter = {

          title: chapterTitle,

          sections: []

        }

        chapters.push(currentChapter)

        continue

      } else if (hashCount === 3) {

        // ### 开头的标题作为小节

        if (currentChapter) {

          console.log('找到小节:', trimmedLine)

          let sectionTitle = trimmedLine.replace(/^###\s*/, '').trim()

          currentChapter.sections.push({

            title: sectionTitle,

            subsections: []

          })

          continue

        }

      } else if (hashCount === 4) {

        // #### 开头的标题作为子标题

        if (currentChapter && currentChapter.sections.length > 0) {

          console.log('找到子标题:', trimmedLine)

          let subsectionTitle = trimmedLine.replace(/^####\s*/, '').trim()

          const lastSection = currentChapter.sections[currentChapter.sections.length - 1]

          lastSection.subsections.push({

            title: subsectionTitle,

            subsubsections: []

          })

          continue

        }

      } else if (trimmedLine.startsWith('-')) {

        // - 开头的作为具体内容要点

        if (currentChapter && currentChapter.sections.length > 0) {

          console.log('找到具体内容要点:', trimmedLine)

          let contentTitle = trimmedLine.replace(/^-\s*/, '').trim()

          const lastSection = currentChapter.sections[currentChapter.sections.length - 1]

          if (lastSection.subsections.length > 0) {

            const lastSubsection = lastSection.subsections[lastSection.subsections.length - 1]

            if (!lastSubsection.subsubsections) {

              lastSubsection.subsubsections = []

            }

            lastSubsection.subsubsections.push({

              title: contentTitle,

              content: ''

            })

          }

          continue

        }

      } else if (hashCount === 0 && trimmedLine.match(/^\d+\.\d+\.\d+/)) {

        // 没有井号但有三级数字格式的作为子小节

        if (currentChapter && currentChapter.sections.length > 0) {

          console.log('找到子小节(数字格式):', trimmedLine)

          const lastSection = currentChapter.sections[currentChapter.sections.length - 1]

          lastSection.subsections.push({

            title: trimmedLine

          })

          continue

        }

      } else if (hashCount === 0 && trimmedLine.match(/^\d+\.\d+/)) {

        // 没有井号但有二级数字格式的作为小节

        if (currentChapter) {

          console.log('找到小节(数字格式):', trimmedLine)

          currentChapter.sections.push({

            title: trimmedLine,

            subsections: []

          })

          continue

        }

      }



      // 如果没有找到结构化内容，尝试将内容作为章节处理

      if (!currentChapter && trimmedLine && trimmedLine.length > 5 &&

        !trimmedLine.includes('以下') &&

        !trimmedLine.includes('以上') &&

        !trimmedLine.startsWith('#') &&

        !trimmedLine.includes('大纲统计信息')) {

        console.log('将内容作为章节处理:', trimmedLine)

        currentChapter = {

          title: trimmedLine,

          sections: []

        }

        chapters.push(currentChapter)

      }



      // 如果当前行看起来像内容，但不是结构化的，添加到当前章节

      if (currentChapter && trimmedLine && trimmedLine.length > 3 &&

        !trimmedLine.match(/^第[一二三四五六七八九十]+章/) &&

        !trimmedLine.match(/^第\d+章/) &&

        !trimmedLine.match(/^\d+\.\d+/) &&

        !trimmedLine.match(/^[一二三四五六七八九十]+\.\d+/) &&

        !trimmedLine.match(/^\d+\.\d+\.\d+/) &&

        !trimmedLine.match(/^[一二三四五六七八九十]+\.\d+\.\d+/) &&

        !trimmedLine.includes('以下') &&

        !trimmedLine.includes('以上') &&

        !trimmedLine.includes('大纲统计信息') &&

        !trimmedLine.includes('预计PPT页数') &&

        !trimmedLine.includes('预计讲解时长') &&

        !trimmedLine.includes('总章节数') &&

        !trimmedLine.includes('总小节数') &&

        !trimmedLine.startsWith('#')) {



        // 如果当前章节没有小节，创建一个默认小节

        if (currentChapter.sections.length === 0) {

          currentChapter.sections.push({

            title: '内容详情',

            subsections: []

          })

        }



        // 将内容添加到最后一个小节

        const lastSection = currentChapter.sections[currentChapter.sections.length - 1]

        if (!lastSection.subsections) {

          lastSection.subsections = []

        }



        // 过滤掉一些无用的占位符文本和统计信息

        if (trimmedLine &&

          trimmedLine !== '内容要点' &&

          trimmedLine !== '概述' &&

          trimmedLine !== '内容详情' &&

          !trimmedLine.includes('总章节数') &&

          !trimmedLine.includes('总小节数') &&

          !trimmedLine.includes('预计PPT页数') &&

          !trimmedLine.includes('预计讲解时长') &&

          !trimmedLine.startsWith('-') &&

          trimmedLine.length > 2) {



          // 检查是否应该作为具体内容要点的正文内容

          const lastSubsection = lastSection.subsections[lastSection.subsections.length - 1]

          if (lastSubsection && lastSubsection.subsubsections && lastSubsection.subsubsections.length > 0) {

            // 如果最后一个子标题有具体内容要点，将内容添加到最后一个具体内容要点

            const lastSubsubsection = lastSubsection.subsubsections[lastSubsection.subsubsections.length - 1]

            if (lastSubsubsection && !lastSubsubsection.content) {

              lastSubsubsection.content = trimmedLine

              continue

            }

          }



          // 否则作为普通子小节

          lastSection.subsections.push({

            title: trimmedLine,

            subsubsections: []

          })

        }

      }

    }



    // 确保所有数据结构完整

    chapters.forEach(chapter => {

      if (chapter && chapter.sections) {

        chapter.sections.forEach(section => {

          if (section && !section.subsections) {

            section.subsections = []

          }

          // 确保每个子小节都有subsubsections数组

          if (section.subsections) {

            section.subsections.forEach(subsection => {

              if (!subsection.subsubsections) {

                subsection.subsubsections = []

              }

            })

          }

        })

      }

    })



    // 根据解析的章节内容自动计算统计信息

    const stats = calculateOutlineStats(chapters)



    return {

      title,

      stats,

      chapters

    }

  } catch (error) {

    console.error('解析大纲信息失败:', error)

    return null

  }

}



// 测试大纲转换为AIPPT.json格式并应用模板5

const testOutlineToAIPPT = async () => {

  try {

    console.log('开始测试大纲转换为AIPPT.json格式并应用模板5')



    if (outlineData.value && outlineData.value.length > 0) {

      console.log('当前大纲数据:', outlineData.value)

      console.log('当前标题:', outlineTitle.value)



      // 显示loading效果

      const loadingInstance = ElMessage({

        message: '正在填充大纲内容，请稍候...',

        type: 'info',

        duration: 0,

        showClose: false

      })



      try {

        // 第一步：填充大纲内容

        console.log('开始填充大纲内容...')

        const enrichedOutlineData = await enrichOutlineContent(outlineData.value, outlineTitle.value)



        // 检查是否所有内容都填充完成

        let allContentFilled = true

        let missingItems = []



        for (let chapterIndex = 0; chapterIndex < enrichedOutlineData.length; chapterIndex++) {

          const chapter = enrichedOutlineData[chapterIndex]

          if (!chapter.content || chapter.content.trim() === '') {

            allContentFilled = false

            missingItems.push(`章节: ${chapter.title}`)

          }



          if (chapter.sections && chapter.sections.length > 0) {

            for (let sectionIndex = 0; sectionIndex < chapter.sections.length; sectionIndex++) {

              const section = chapter.sections[sectionIndex]

              if (!section.content || section.content.trim() === '') {

                allContentFilled = false

                missingItems.push(`小节: ${section.title}`)

              }



              if (section.subsections && section.subsections.length > 0) {

                for (let subsectionIndex = 0; subsectionIndex < section.subsections.length; subsectionIndex++) {

                  const subsection = section.subsections[subsectionIndex]

                  if (!subsection.content || subsection.content.trim() === '') {

                    allContentFilled = false

                    missingItems.push(`子小节: ${subsection.title}`)

                  }

                }

              }

            }

          }

        }



        if (!allContentFilled) {

          loadingInstance.close()

          console.warn('部分内容填充失败:', missingItems)

          ElMessage.warning(`部分内容填充失败，请重试。失败项目: ${missingItems.slice(0, 3).join(', ')}${missingItems.length > 3 ? '...' : ''}`)

          return

        }



        // 更新loading消息

        loadingInstance.message = '正在转换大纲为PPT格式...'



        // 第二步：转换大纲为AIPPT.json格式

        const aipptData = convertOutlineToAIPPT(enrichedOutlineData, outlineTitle.value)



        console.log('转换结果:')

        console.log(JSON.stringify(aipptData, null, 2))



        // 更新loading消息

        loadingInstance.message = '正在应用模板5...'



        // 第三步：应用模板5

        await loadAIPPTData(aipptData)



        // 确保数据被正确保存到本地存储

        if (generatedPPT.value && generatedPPT.value.length > 0) {

          localStorage.setItem('generatedPPT', JSON.stringify(generatedPPT.value))

          console.log('PPT数据已保存到本地存储:', generatedPPT.value.length, '张幻灯片')

        }



        // 大纲数据现在只存储在后端



        // 关闭loading

        loadingInstance.close()



        // 显示成功消息

        ElMessage.success('转换完成并已应用模板5，正在跳转到PPT预览...')



        // 自动跳转到步骤三（PPT预览）

        setTimeout(() => {

          currentStep.value = 'step3'

        }, 1000)



      } catch (error) {

        // 关闭loading

        loadingInstance.close()

        throw error

      }

    } else {

      ElMessage.warning('没有大纲数据，无法进行转换')

    }

  } catch (error) {

    console.error('测试转换失败:', error)

    ElMessage.error('转换失败: ' + error.message)

  }

}



// 监听语音识别结果

watch(transcript, (newVal) => {

  if (!newVal || isListening.value) return

  messageText.value = newVal

})

// 监听语音识别错误

watch(speechError, (newVal) => {

  if (newVal) {

    console.error('语音识别错误:', newVal)

    ElMessage.error(newVal)

  }

})



// 生命周期钩子

onMounted(async () => {

  console.log('🚀 页面初始化开始，优先加载历史记录...')

  // 检查URL中是否有id参数，如果有，将其设置为当前的 ai_conversation_id
  if (route.query.id) {
    const id = parseInt(route.query.id);
    if (!isNaN(id) && id > 0) {
      ai_conversation_id.value = id;
      console.log('从URL获取到对话ID:', id);
      
      // 等待历史记录加载完成后，自动选中并触发点击事件
      const checkHistory = setInterval(() => {
        if (historyData.value && historyData.value.length > 0) {
          clearInterval(checkHistory);
          const targetItem = historyData.value.find(item => item.id === id);
          if (targetItem) {
            handleHistoryItem(targetItem);
          } else {
            handleHistoryItem({ id }); // 降级处理
          }
        }
      }, 500);
      
      // 5秒后自动清除定时器，避免死循环
      setTimeout(() => clearInterval(checkHistory), 5000);
    }
  }

  // 设置初始加载状态

  isLoadingHistory.value = true



  try {

    // 1. 首先获取历史记录列表（最高优先级）

    await getHistoryRecordList()

    console.log('✅ 历史记录加载完成')



    // 2. 并行获取其他非关键数据（不阻塞历史记录显示）
    // 注释掉功能卡片和热点问题获取，因为版本没有步骤三和四
    const otherDataPromise = Promise.all([
      getFunctionCards(),
      getHotQuestions()
    ])



    // 3. 初始化大纲统计信息

    // if (outlineData.value && outlineData.value.length > 0) {

    //   updateOutlineStats()

    // }



    // 4. 初始化模板选择 - 注释掉，因为版本没有步骤三
    // if (templateStyles.value.length > 0) {
    //   selectTemplate(0) // 选择第一个模板
    //   console.log('初始化模板选择完成，当前模板:', templateStyles.value[0].title)
    // }



    // 5. 等待其他数据加载完成（后台进行） - 注释掉
    // try {
    //   await otherDataPromise
    //   console.log('✅ 其他数据加载完成')
    // } catch (error) {
    //   console.warn('⚠️ 其他数据加载失败，但不影响主要功能:', error)
    // }



    console.log('🎉 页面初始化完成')

  } catch (error) {

    console.error('❌ 页面初始化失败:', error)

  } finally {

    // 清除加载状态

    isLoadingHistory.value = false

  }

})



// 点击推荐问题

const handleRecommendedQuestion = (question) => {

  messageText.value = question

  console.log('选择推荐问题:', question)



  // 显示聊天界面

  showChat.value = true



  // 自动发送问题

  sendMessage()

}



// 获取评价状态（优先使用后端数据，如果没有则使用本地状态）

const getEvaluationStatus = () => {

  console.log('getEvaluationStatus - outlineFeedback:', outlineFeedback.value, 'evaluation:', evaluation.value)

  if (outlineFeedback.value !== null) {

    // 根据后端数据判断状态

    switch (outlineFeedback.value) {

      case 2: return 'satisfied'    // 满意

      case 3: return 'unsatisfied'  // 不满意

      case 0: return ''             // 无反馈（取消评价）

      default: return ''            // 无反馈

    }

  }

  return evaluation.value // 如果没有后端数据，使用本地状态

}



// 设置大纲评价

const setEvaluation = async (value) => {

  try {

    console.log('设置评价:', value)



    // 检查当前状态，如果点击的是当前已激活的状态，则取消评价

    const currentStatus = getEvaluationStatus()

    let feedbackValue



    if (currentStatus === value) {

      // 如果点击的是当前已激活的状态，取消评价

      feedbackValue = 0

      console.log('取消评价，发送0')

    } else {

      // 否则设置新的评价

      feedbackValue = value === 'satisfied' ? 2 : 3

      console.log('设置新评价:', feedbackValue)

    }



    console.log('currentAiMessageId.value', currentAiMessageId.value)

    // 调用后端API保存评价

    const response = await apis.likeAndDislike({

      id: currentAiMessageId.value, // 优先使用大纲ID，如果没有则使用AI消息ID

      user_feedback: feedbackValue

    })



    if (response.statusCode === 200) {

      console.log('评价保存成功')

      // 更新本地状态

      if (feedbackValue === 0) {

        // 取消评价

        evaluation.value = ''

        outlineFeedback.value = 0

        ElMessage.success('评价已取消')

      } else {

        // 设置新评价

        evaluation.value = value

        outlineFeedback.value = feedbackValue

        ElMessage.success('评价已保存')

      }

    } else {

      console.error('评价保存失败:', response)

      ElMessage.error('评价保存失败，请重试')

    }

  } catch (error) {

    console.error('设置评价失败:', error)

    ElMessage.error('评价设置失败，请重试')

  }

}



// 大纲编辑相关方法

const startEditing = (item, type, index, content) => {

  editingItem.value = item

  editingType.value = type

  editingIndex.value = index



  // 提取纯文字内容，去掉编号部分

  let pureContent = content

  if (type === 'chapter') {

    // 章节：去掉"第一章"、"第二章"等前缀

    pureContent = content.replace(/^第[一二三四五六七八九十\d]+章\s*/, '')

  } else if (type === 'section') {

    // 小节：去掉"1.1"、"2.1"等前缀

    pureContent = content.replace(/^\d+\.\d+\s*/, '')

  } else if (type === 'subsection') {

    // 四级标题：去掉"#### "前缀

    pureContent = content.replace(/^####\s*/, '')

  } else if (type === 'subsubsection') {

    // 具体内容要点：去掉"- "前缀

    pureContent = content.replace(/^-\s*/, '')

  }



  editingContent.value = pureContent

  console.log('开始编辑:', { type, index, content: pureContent })

}



const saveEdit = async () => {

  if (!editingContent.value.trim()) {

    console.log('编辑内容为空，取消保存')

    cancelEdit()

    return

  }



  try {

    // 根据编辑类型更新对应的数据

    if (editingType.value === 'title') {

      outlineTitle.value = editingContent.value.trim()

    } else if (editingType.value === 'chapter') {

      const chapterIndex = editingIndex.value

      // 保持原有的章节编号格式，不重新生成

      const originalTitle = outlineData.value[chapterIndex].title

      const match = originalTitle.match(/^(第[一二三四五六七八九十\d]+章)\s*/)

      if (match) {

        // 保持原有的编号格式

        const chapterNumber = match[1]

        const chapterTitle = `${chapterNumber} ${editingContent.value.trim()}`

        outlineData.value[chapterIndex].title = chapterTitle

      } else {

        // 如果没有匹配到编号格式，使用中文数字格式

        const chineseNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']

        const chapterNumber = chapterIndex < chineseNumbers.length ? chineseNumbers[chapterIndex] : (chapterIndex + 1).toString()

        const chapterTitle = `第${chapterNumber}章 ${editingContent.value.trim()}`

        outlineData.value[chapterIndex].title = chapterTitle

      }

    } else if (editingType.value === 'section') {

      const [chapterIndex, sectionIndex] = editingIndex.value.split('-')

      // 直接保存用户编辑的内容，不添加数字前缀

      outlineData.value[chapterIndex].sections[sectionIndex].title = editingContent.value.trim()

    } else if (editingType.value === 'subsection') {

      const [chapterIndex, sectionIndex, subsectionIndex] = editingIndex.value.split('-')

      // 直接保存用户编辑的内容，不添加数字前缀

      outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].title = editingContent.value.trim()

    } else if (editingType.value === 'subsubsection') {

      const [chapterIndex, sectionIndex, subsectionIndex, subsubsectionIndex] = editingIndex.value.split('-')

      // 直接保存用户编辑的内容，不添加数字前缀

      outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].subsubsections[subsubsectionIndex].title = editingContent.value.trim()

    } else if (editingType.value === 'subsubsection-content') {

      const [chapterIndex, sectionIndex, subsectionIndex, subsubsectionIndex] = editingIndex.value.split('-')

      // 保存具体内容要点（-开头）下的正文内容

      outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].subsubsections[subsubsectionIndex].content = editingContent.value.trim()

    }



    console.log('保存成功:', editingContent.value.trim())



    // 更新大纲统计信息

    updateOutlineStats()



    // 自动保存到后端

    await saveOutlineToBackend(true) // 强制保存编辑后的内容



    // 清除编辑状态

    cancelEdit()



    // 显示成功提示

    ElMessage.success('保存成功！')



  } catch (error) {

    console.error('保存失败:', error)

    ElMessage.error('保存失败，请重试')

  }

}



const cancelEdit = () => {

  editingItem.value = null

  editingType.value = ''

  editingIndex.value = null

  editingContent.value = ''

  console.log('取消编辑')

}



const toggleEditOptions = (itemId) => {

  showEditOptions.value = showEditOptions.value === itemId ? null : itemId

}



const deleteItem = async (type, index) => {

  try {

    if (type === 'chapter') {

      // 限制章节数量不能少于2章

      if (outlineData.value.length <= 2) {

        ElMessage.warning('至少需要保留2个章节')

        return

      }

      outlineData.value.splice(index, 1)

    } else if (type === 'section') {

      const [chapterIndex, sectionIndex] = index.split('-')

      // 限制每个章节至少保留1个小节

      if (outlineData.value[chapterIndex].sections.length <= 1) {

        ElMessage.warning('每个章节至少需要保留1个小节')

        return

      }

      outlineData.value[chapterIndex].sections.splice(sectionIndex, 1)

    } else if (type === 'subsection') {

      const [chapterIndex, sectionIndex, subsectionIndex] = index.split('-')

      outlineData.value[chapterIndex].sections[sectionIndex].subsections.splice(subsectionIndex, 1)

    } else if (type === 'subsubsection') {

      const [chapterIndex, sectionIndex, subsectionIndex, subsubsectionIndex] = index.split('-')

      outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].subsubsections.splice(subsubsectionIndex, 1)

    }



    // 更新统计信息

    updateOutlineStats()



    // 自动保存到后端

    await saveOutlineToBackend(true) // 强制保存删除后的内容



    ElMessage.success('删除成功！')



  } catch (error) {

    console.error('删除失败:', error)

    ElMessage.error('删除失败，请重试')

  }

}



// 智能编号生成函数

const generateNextNumber = (type, parentIndex) => {

  try {

    if (type === 'chapter') {

      // 生成下一个章节编号

      const existingChapters = outlineData.value || []

      let maxChapterNum = 0



      // 查找现有的最大章节编号

      const chineseNumbers = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']



      existingChapters.forEach(chapter => {

        const match = chapter.title.match(/^第([一二三四五六七八九十\d]+)章/)

        if (match) {

          let num = 0

          const matchedText = match[1]



          // 如果是阿拉伯数字

          if (/^\d+$/.test(matchedText)) {

            num = parseInt(matchedText)

          } else {

            // 如果是中文数字，转换为阿拉伯数字

            const index = chineseNumbers.indexOf(matchedText)

            if (index > 0) {

              num = index

            }

          }



          maxChapterNum = Math.max(maxChapterNum, num)

        }

      })



      const nextChapterNum = maxChapterNum + 1

      // 最多6章，直接使用中文数字

      return `第${chineseNumbers[nextChapterNum]}章`

    } else if (type === 'section') {

      // 生成下一个小节编号

      const chapterIndex = parentIndex

      const existingSections = outlineData.value[chapterIndex]?.sections || []

      let maxSectionNum = 0



      // 查找现有的最大小节编号

      existingSections.forEach(section => {

        const match = section.title.match(/^\d+\.(\d+)/)

        if (match) {

          const num = parseInt(match[1]) || 0

          maxSectionNum = Math.max(maxSectionNum, num)

        }

      })



      const nextSectionNum = maxSectionNum + 1

      return `${chapterIndex + 1}.${nextSectionNum}`

    } else if (type === 'subsection') {

      // 生成下一个子小节编号

      const [chapterIndex, sectionIndex] = parentIndex.split('-')

      const existingSubsections = outlineData.value[chapterIndex]?.sections[sectionIndex]?.subsections || []

      let maxSubsectionNum = 0



      // 查找现有的最大子小节编号

      existingSubsections.forEach(subsection => {

        const match = subsection.title.match(/^\d+\.\d+\.(\d+)/)

        if (match) {

          const num = parseInt(match[1]) || 0

          maxSubsectionNum = Math.max(maxSubsectionNum, num)

        }

      })



      const nextSubsectionNum = maxSubsectionNum + 1

      return `${parseInt(chapterIndex) + 1}.${parseInt(sectionIndex) + 1}.${nextSubsectionNum}`

    } else if (type === 'subsubsection') {

      // 生成下一个具体内容要点（-开头）编号

      const [chapterIndex, sectionIndex, subsectionIndex] = parentIndex.split('-')

      const existingSubsubsections = outlineData.value[chapterIndex]?.sections[sectionIndex]?.subsections[subsectionIndex]?.subsubsections || []

      let maxSubsubsectionNum = 0



      // 查找现有的最大具体内容要点（-开头）编号

      existingSubsubsections.forEach(subsubsection => {

        const match = subsubsection.title.match(/^\d+\.\d+\.\d+\.(\d+)/)

        if (match) {

          const num = parseInt(match[1]) || 0

          maxSubsubsectionNum = Math.max(maxSubsubsectionNum, num)

        }

      })



      const nextSubsubsectionNum = maxSubsubsectionNum + 1

      return `${parseInt(chapterIndex) + 1}.${parseInt(sectionIndex) + 1}.${parseInt(subsectionIndex) + 1}.${nextSubsubsectionNum}`

    }

    return ''

  } catch (error) {

    console.error('生成编号失败:', error)

    return ''

  }

}



// 拖拽相关函数

const handleDragStart = (event, chapterIndex) => {

  // 如果事件来自编辑框内部，不启动拖拽
  if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT' || 
      event.target.closest('.edit-input-container') || event.target.closest('.edit-input-wrapper')) {
    event.preventDefault()
    return
  }

  // 如果正在编辑状态，不启动拖拽
  if (editingType.value !== '') {
    event.preventDefault()
    return
  }

  draggedChapterIndex.value = chapterIndex

  event.dataTransfer.effectAllowed = 'move'

  event.dataTransfer.setData('text/html', event.target.outerHTML)

  event.target.style.opacity = '0.5'

}



const handleDragEnd = (event) => {

  event.target.style.opacity = '1'

  draggedChapterIndex.value = null

  dragOverChapterIndex.value = null

}



const handleDragOver = (event, chapterIndex) => {

  event.preventDefault()

  event.dataTransfer.dropEffect = 'move'

  dragOverChapterIndex.value = chapterIndex

}



const handleDragLeave = (event) => {

  // 只有当鼠标真正离开元素时才清除悬停状态

  if (!event.currentTarget.contains(event.relatedTarget)) {

    dragOverChapterIndex.value = null

  }

}



const handleDrop = async (event, targetChapterIndex) => {

  event.preventDefault()



  const sourceIndex = draggedChapterIndex.value

  const targetIndex = targetChapterIndex



  if (sourceIndex === null || sourceIndex === targetIndex) {

    return

  }



  try {

    // 移动章节

    const chapters = [...outlineData.value]

    const [movedChapter] = chapters.splice(sourceIndex, 1)

    chapters.splice(targetIndex, 0, movedChapter)



    // 重新生成所有章节的编号

    const chineseNumbers = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']

    chapters.forEach((chapter, index) => {

      // 提取章节标题中的内容部分（去掉"第X章"前缀）

      const contentMatch = chapter.title.match(/^第[一二三四五六七八九十\d]+章\s*(.+)$/)

      if (contentMatch) {

        const content = contentMatch[1]

        chapter.title = `第${chineseNumbers[index + 1]}章 ${content}`

      } else {

        // 如果没有匹配到标准格式，直接使用新编号

        chapter.title = `第${chineseNumbers[index + 1]}章 ${chapter.title}`

      }



      // 保持小节和子小节无序号格式

      if (chapter.sections && chapter.sections.length > 0) {

        chapter.sections.forEach((section, sectionIndex) => {

          // 移除小节标题中的序号，只保留内容

          const sectionContentMatch = section.title.match(/^\d+\.\d+\s*(.+)$/)

          if (sectionContentMatch) {

            section.title = sectionContentMatch[1]

          }

          // 如果已经是无序号格式，保持不变



          // 保持子小节无序号格式

          if (section.subsections && section.subsections.length > 0) {

            section.subsections.forEach((subsection, subsectionIndex) => {

              // 移除子小节标题中的序号，只保留内容

              const subsectionContentMatch = subsection.title.match(/^\d+\.\d+\.\d+\s*(.+)$/)

              if (subsectionContentMatch) {

                subsection.title = subsectionContentMatch[1]

              }

              // 如果已经是无序号格式，保持不变

            })

          }

        })

      }

    })



    // 更新数据

    outlineData.value = chapters



    // 更新统计信息

    updateOutlineStats()



    // 保存到后端

    await saveOutlineToBackend(true) // 强制保存调整后的内容



    ElMessage.success('章节顺序已调整，编号已重新排序')



  } catch (error) {

    console.error('调整章节顺序失败:', error)

    ElMessage.error('调整章节顺序失败，请重试')

  }



  // 清除拖拽状态

  draggedChapterIndex.value = null

  dragOverChapterIndex.value = null

}



const addNewItem = async (type, parentIndex) => {

  try {

    if (type === 'chapter') {

      // 限制章节数量不能超过6章

      if (outlineData.value.length >= 6) {

        ElMessage.warning('最多只能添加6个章节')

        return

      }



      const nextNumber = generateNextNumber('chapter', null)

      const newChapter = {

        title: `${nextNumber} 新章节`,

        sections: [{

          title: `新小节`,

          subsections: []

        }]

      }



      outlineData.value.push(newChapter)



      // 自动开始编辑新章节

      startEditing(newChapter, 'chapter', outlineData.value.length - 1, `${nextNumber} 新章节`)

    } else if (type === 'section') {

      const nextNumber = generateNextNumber('section', parentIndex)

      const newSection = {

        title: `${nextNumber} 新小节`,

        subsections: []

      }

      outlineData.value[parentIndex].sections.push(newSection)

      // 自动开始编辑新小节

      startEditing(newSection, 'section', `${parentIndex}-${outlineData.value[parentIndex].sections.length - 1}`, `新小节`)

    } else if (type === 'subsection') {

      const [chapterIndex, sectionIndex] = parentIndex.split('-')

      const nextNumber = generateNextNumber('subsection', parentIndex)

      const newSubsection = {

        title: `新子标题`,

        subsubsections: []

      }

      outlineData.value[chapterIndex].sections[sectionIndex].subsections.push(newSubsection)

      // 自动开始编辑新子标题

      startEditing(newSubsection, 'subsection', `${chapterIndex}-${sectionIndex}-${outlineData.value[chapterIndex].sections[sectionIndex].subsections.length - 1}`, `新子标题`)

    } else if (type === 'subsubsection') {

      const [chapterIndex, sectionIndex, subsectionIndex] = parentIndex.split('-')

      const nextNumber = generateNextNumber('subsubsection', parentIndex)

      const newSubsubsection = {

        title: `${nextNumber} 新具体内容要点`,

        content: ''

      }

      // 确保subsection有subsubsections数组

      if (!outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].subsubsections) {

        outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].subsubsections = []

      }

      outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].subsubsections.push(newSubsubsection)

      // 自动开始编辑新具体内容要点（-开头）

      startEditing(newSubsubsection, 'subsubsection', `${chapterIndex}-${sectionIndex}-${subsectionIndex}-${outlineData.value[chapterIndex].sections[sectionIndex].subsections[subsectionIndex].subsubsections.length - 1}`, `${nextNumber} 新具体内容要点`)

    }



    // 更新统计信息

    updateOutlineStats()



    // 自动保存到后端

    await saveOutlineToBackend(true) // 强制保存添加后的内容



  } catch (error) {

    console.error('添加失败:', error)

    ElMessage.error('添加失败，请重试')

  }

}



// 填充大纲内容 - 整体处理方案

const enrichOutlineContent = async (outlineData, outlineTitle) => {

  console.log('开始填充大纲内容...')



  // 深拷贝大纲数据，避免修改原始数据

  const enrichedData = JSON.parse(JSON.stringify(outlineData))



  // 最大重试次数

  const maxRetries = 3

  let retryCount = 0



  while (retryCount < maxRetries) {

    try {

      console.log(`第 ${retryCount + 1} 次尝试填充内容...`)



      // 构建整体提示词

      const overallPrompt = `请为以下大纲数据填充内容，要求：

1. title字段：15字以内

2. text字段：20-50字以内

3. 内容要专业、实用、简洁

4. 保持JSON格式不变，填充空的内容

5. 请自由发挥，生成丰富多样的标题和内容

6. 重要：请确保所有空的内容都被填充，不要遗漏任何项目

7. 如果数据量较大，请耐心处理，确保完整性



大纲主题：${outlineTitle}

大纲数据：${JSON.stringify(enrichedData, null, 2)}



请直接返回完整的JSON数据，不要添加任何说明文字。确保所有content字段都有内容。`



      console.log('发送整体填充请求...')

      const response = await apis.reProduceSingleQuestion({ message: overallPrompt })

      console.log('API响应:', response)



      // 解析AI返回的JSON数据

      let aiResponse = null

      if (response && response.data) {

        if (response.data && typeof response.data === 'object' && response.data.reply) {

          aiResponse = response.data.reply

        } else if (response.data && typeof response.data === 'string') {

          aiResponse = response.data

        } else {

          aiResponse = JSON.stringify(response.data)

        }

      } else if (response && response.message) {

        aiResponse = response.message

      } else if (response && response.content) {

        aiResponse = response.content

      } else if (response && response.reply) {

        aiResponse = response.reply

      } else if (response && typeof response === 'string') {

        aiResponse = response

      } else if (response && typeof response === 'object') {

        aiResponse = JSON.stringify(response)

      }



      if (aiResponse && typeof aiResponse === 'string' && aiResponse.trim() !== '') {

        try {

          // 尝试解析JSON

          const parsedData = JSON.parse(aiResponse.trim())

          console.log('AI返回的JSON数据解析成功:', parsedData)



          // 验证数据结构

          if (Array.isArray(parsedData)) {

            // 验证内容完整性

            let totalItems = 0

            let filledItems = 0



            parsedData.forEach(chapter => {

              if (chapter.content && chapter.content.trim() !== '') {

                filledItems++

              }

              totalItems++



              if (chapter.sections && chapter.sections.length > 0) {

                chapter.sections.forEach(section => {

                  if (section.content && section.content.trim() !== '') {

                    filledItems++

                  }

                  totalItems++



                  if (section.subsections && section.subsections.length > 0) {

                    section.subsections.forEach(subsection => {

                      if (subsection.content && subsection.content.trim() !== '') {

                        filledItems++

                      }

                      totalItems++

                    })

                  }

                })

              }

            })



            console.log(`内容填充统计: ${filledItems}/${totalItems} 项已填充`)



            if (filledItems < totalItems * 0.8) {

              console.warn('内容填充不完整，尝试重新填充...')

              retryCount++

              if (retryCount >= maxRetries) {

                throw new Error('多次尝试后内容填充仍不完整')

              }

              continue

            }



            console.log('大纲内容填充完成')

            return parsedData

          } else {

            throw new Error('AI返回的数据不是数组格式')

          }

        } catch (parseError) {

          console.error('解析AI返回的JSON失败:', parseError)

          console.log('AI返回的原始内容:', aiResponse)

          retryCount++

          if (retryCount >= maxRetries) {

            throw new Error('AI返回的数据格式不正确')

          }

          continue

        }

      } else {

        retryCount++

        if (retryCount >= maxRetries) {

          throw new Error('AI返回内容为空或格式不正确')

        }

        continue

      }

    } catch (error) {

      retryCount++

      if (retryCount >= maxRetries) {

        throw error

      }

      console.warn(`第 ${retryCount} 次尝试失败，准备重试...`)

      // 等待一段时间后重试

      await new Promise(resolve => setTimeout(resolve, 2000))

    }

  }





}



// 直接填充AIPPT格式的内容

const fillAIPPTContent = async (aipptData, outlineTitle) => {

  console.log('开始填充AIPPT内容...')



  try {

    // 构建提示词

    const prompt = `请为以下PPT数据填充内容，要求：

1. title字段：15字以内

2. text字段：20-50字以内

3. 内容要专业、实用、简洁

4. 保持JSON格式不变，只填充空的内容

5. 请自由发挥，生成丰富多样的标题和内容

6. 重要：必须保持原有的幻灯片数量和结构，不能删除或合并任何幻灯片

7. 对于每个content类型的幻灯片，确保items数组有4个元素



PPT主题：${outlineTitle}

PPT数据：${JSON.stringify(aipptData, null, 2)}



请直接返回完整的JSON数据，不要添加任何说明文字。确保所有空的内容都被填充，并且保持原有的幻灯片数量。`



    console.log('发送AIPPT填充请求...')

    const response = await apis.reProduceSingleQuestion({ message: prompt })

    console.log('API响应:', response)



    // 解析AI返回的JSON数据

    let aiResponse = null

    if (response && response.data) {

      if (response.data && typeof response.data === 'object' && response.data.reply) {

        aiResponse = response.data.reply

      } else if (response.data && typeof response.data === 'string') {

        aiResponse = response.data

      } else {

        aiResponse = JSON.stringify(response.data)

      }

    } else if (response && response.message) {

      aiResponse = response.message

    } else if (response && response.content) {

      aiResponse = response.content

    } else if (response && response.reply) {

      aiResponse = response.reply

    } else if (response && typeof response === 'string') {

      aiResponse = response

    } else if (response && typeof response === 'object') {

      aiResponse = JSON.stringify(response)

    }



    if (aiResponse && typeof aiResponse === 'string' && aiResponse.trim() !== '') {

      try {

        // 尝试解析JSON

        const parsedData = JSON.parse(aiResponse.trim())

        console.log('AI返回的AIPPT数据解析成功:', parsedData)



        // 验证数据结构

        if (Array.isArray(parsedData)) {

          console.log('AIPPT内容填充完成，原始数量:', aipptData.length, '填充后数量:', parsedData.length)



          // 检查数量是否一致

          if (parsedData.length !== aipptData.length) {

            console.warn('警告：AI返回的幻灯片数量与原始数量不一致！')

            console.warn('原始数量:', aipptData.length, 'AI返回数量:', parsedData.length)

          }



          return parsedData

        } else {

          throw new Error('AI返回的数据不是数组格式')

        }

      } catch (parseError) {

        console.error('解析AI返回的JSON失败:', parseError)

        console.log('AI返回的原始内容:', aiResponse)

        throw new Error('AI返回的数据格式不正确')

      }

    } else {

      throw new Error('AI返回内容为空或格式不正确')

    }



  } catch (error) {

    console.error('填充AIPPT内容失败:', error)

    throw new Error('填充AIPPT内容失败: ' + error.message)

  }

}



// 转换字符串数组为大纲对象格式

const convertStringArrayToOutline = (stringArray) => {

  const outline = []

  let currentChapter = null

  let currentSection = null



  stringArray.forEach((item, index) => {

    if (item.startsWith('章节:')) {

      // 创建新章节

      currentChapter = {

        title: item.replace('章节: ', ''),

        content: '',

        sections: []

      }

      outline.push(currentChapter)

      currentSection = null

    } else if (item.startsWith('小节:')) {

      // 创建新小节

      if (currentChapter) {

        currentSection = {

          title: item.replace('小节: ', ''),

          content: '',

          subsections: []

        }

        currentChapter.sections.push(currentSection)

      }

    } else if (item.startsWith('子小节:')) {

      // 创建新子小节

      if (currentSection) {

        const subsection = {

          title: item.replace('子小节: ', ''),

          content: ''

        }

        currentSection.subsections.push(subsection)

      }

    }

  })



  // 确保每个小节至少有4个子小节

  outline.forEach(chapter => {

    chapter.sections.forEach(section => {

      while (section.subsections.length < 4) {

        // 让AI自由发挥，不预设标题模式

        const additionalSubsections = [

          {

            title: '',

            content: ''

          },

          {

            title: '',

            content: ''

          },

          {

            title: '',

            content: ''

          },

          {

            title: '',

            content: ''

          }

        ]



        const index = section.subsections.length

        if (index < additionalSubsections.length) {

          section.subsections.push(additionalSubsections[index])

        }

      }

    })

  })



  return outline

}



// 检查大纲数据是否有变化

const hasOutlineDataChanged = () => {

  if (!lastSavedOutlineData.value) return true



  const currentData = {

    title: outlineTitle.value,

    stats: outlineStats.value,

    chapters: outlineData.value

  }



  return JSON.stringify(currentData) !== JSON.stringify(lastSavedOutlineData.value)

}



// 检查PPT数据是否有变化

const hasPPTDataChanged = () => {

  if (!lastSavedPPTData.value) return true



  return JSON.stringify(generatedPPT.value) !== JSON.stringify(lastSavedPPTData.value)

}



// 获取章节的显示标题（加上第X章前缀）

const getDisplayChapterTitle = (chapterTitle, chapterIndex) => {

  // 如果标题已经包含"第X章"，直接返回

  if (chapterTitle.match(/^第[一二三四五六七八九十\d]+章/)) {

    return chapterTitle

  }

  // 否则加上"第X章"前缀，使用中文数字

  const chineseNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']

  const chapterNumber = chapterIndex < chineseNumbers.length ? chineseNumbers[chapterIndex] : (chapterIndex + 1).toString()

  return `第${chapterNumber}章 ${chapterTitle}`

}



// 获取章节的干净标题（去掉第X章前缀，用于编辑）

const getCleanChapterTitle = (chapterTitle) => {

  // 去掉章节标题中的"第X章"前缀

  return chapterTitle.replace(/^第[一二三四五六七八九十\d]+章\s*/, '')

}



// 将大纲数据转换为markdown格式

const convertOutlineToMarkdown = (chapters, title) => {

  let markdown = `# ${title}\n\n`



  chapters.forEach((chapter, index) => {

    // 直接使用原始标题，不要修改

    markdown += `## ${chapter.title}\n\n`



    if (chapter.sections && chapter.sections.length > 0) {

      chapter.sections.forEach((section, sectionIndex) => {

        markdown += `### ${section.title}\n\n`



        // 处理子小节

        if (section.subsections && section.subsections.length > 0) {

          section.subsections.forEach((subsection, subsectionIndex) => {

            // 四级标题添加 #### 前缀

            markdown += `#### ${subsection.title}\n\n`



            // 处理具体内容要点（-开头）

            if (subsection.subsubsections && subsection.subsubsections.length > 0) {

              subsection.subsubsections.forEach((subsubsection, subsubsectionIndex) => {

                // 具体内容要点添加 - 前缀

                markdown += `- ${subsubsection.title}\n\n`



                // 添加具体内容要点（-开头）下的正文内容

                if (subsubsection.content && subsubsection.content.trim()) {

                  markdown += `${subsubsection.content}\n\n`

                }

              })

            }

          })

        }

      })

    }



    markdown += '\n'

  })



  return markdown

}



// 直接保存大纲到后端（不依赖全局状态，用于生成大纲时）

const saveOutlineToBackendDirectly = async (conversationId, chapters, title, stats) => {

  try {

    if (isSaving.value) {

      console.log('正在保存中，跳过重复保存请求')

      return false

    }



    if (!conversationId || !chapters) {

      console.log('缺少conversationId或大纲数据，跳过后端保存')

      return false

    }



    isSaving.value = true



    // 将大纲数据转换为markdown格式

    const markdownContent = convertOutlineToMarkdown(chapters, title)



    console.log('直接保存大纲到后端:', {

      ai_conversation_id: conversationId,

      markdownContent: markdownContent

    })



    const response = await apis.savePPTOutline({

      ai_conversation_id: conversationId,

      ppt_content: markdownContent

    })



    if (response.statusCode === 200) {

      console.log('大纲已直接保存到后端服务器，conversation_id:', conversationId)

      return true

    } else {

      console.error('直接保存到后端服务器失败:', response)

      return false

    }

  } catch (error) {

    console.error('直接保存大纲失败:', error)

    return false

  } finally {

    isSaving.value = false

  }

}



// 保存大纲到后端（用户编辑时才调用）

const saveOutlineToBackend = async (forceSave = false) => {

  try {

    if (isSaving.value) {

      console.log('正在保存中，跳过重复保存请求')

      return false

    }



    if (!ai_conversation_id.value || !outlineData.value) {

      console.log('缺少ai_conversation_id或大纲数据，跳过后端保存')

      return false

    }



    // 检查数据是否有变化

    if (!forceSave && !hasOutlineDataChanged()) {

      console.log('大纲数据未发生变化，跳过保存')

      return false

    }



    isSaving.value = true



    // 将大纲数据转换为markdown格式

    const markdownContent = convertOutlineToMarkdown(outlineData.value, outlineTitle.value)



    console.log('准备保存大纲到后端:', {

      ai_conversation_id: ai_conversation_id.value,

      markdownContent: markdownContent

    })



    const response = await apis.savePPTOutline({

      ai_conversation_id: ai_conversation_id.value,

      ppt_content: markdownContent

    })



    if (response.statusCode === 200) {

      console.log('大纲已保存到后端服务器，conversation_id:', ai_conversation_id.value)

      // 更新上次保存的数据

      lastSavedOutlineData.value = {

        title: outlineTitle.value,

        stats: outlineStats.value,

        chapters: JSON.parse(JSON.stringify(outlineData.value)) // 深拷贝

      }

      return true

    } else {

      console.error('保存到后端服务器失败:', response)

      return false

    }

  } catch (error) {

    console.error('保存大纲失败:', error)

    return false

  } finally {

    isSaving.value = false

  }

}





// 步骤三相关方法

const goToStep3 = () => {

  currentStep.value = 'step3'

  console.log('进入步骤三：PPT模板选择')



  // 确保处于模板预览模式

  showDownloadOptions.value = false

  selectedDownloadOption.value = 0



  // 清理PPT预览相关状态（保留generatedPPT数据）

  currentPPTSlideIndex.value = 0

  selectedPPTElementIndex.value = -1

  editingPPTElementIndex.value = -1

  editingPPTHtml.value = ''

  zoom.value = 1

  selectedImageIndex.value = null



  // 加载模板预览数据

  loadLocalPPT()

  updateSlideThumbnails()



  // 重置当前幻灯片索引

  currentSlideIndex.value = 0



  console.log('模板预览已加载，共', slideImages.value.length, '张图片')

}



// 显示复制大纲提示
const showCopyOutlineToast = () => {
  showCopyToast.value = true
  setTimeout(() => {
    showCopyToast.value = false
  }, 1000)
}

// WPS AI PPT集成方法

const openWPS = () => {

  try {

    // 检查是否有大纲数据

    if (!outlineData.value || outlineData.value.length === 0) {

      ElMessage.warning('请先生成大纲后再使用WPS AI PPT')

      return

    }

    // 默认调用复制大纲功能
    copyEntireOutline()
    
    // 显示复制大纲提示
    showCopyOutlineToast()

    // 重置到WPS AI PPT页面

    selectedWPSUrl.value = 'https://aippt.wps.cn/aippt/'



    // 显示WPS AI PPT集成页面

    showWPSModal.value = true



    ElMessage.success('正在加载WPS AI PPT...')

    console.log('WPS AI PPT集成页面已打开')

  } catch (error) {

    console.error('WPS AI PPT加载失败:', error)

    ElMessage.error('WPS AI PPT加载失败，请稍后重试')

  }

}



// 将大纲转换为文本格式

const convertOutlineToText = () => {

  if (!outlineData.value || outlineData.value.length === 0) {

    return '暂无大纲内容'

  }



  let text = ''

  outlineData.value.forEach((chapter, chapterIndex) => {

    if (chapter && chapter.title) {

      text += `${chapterIndex + 1}. ${chapter.title}\n`



      if (chapter.sections && chapter.sections.length > 0) {

        chapter.sections.forEach((section, sectionIndex) => {

          if (section && section.title &&

            section.title !== '内容要点' &&

            section.title !== '概述') {

            text += `  ${chapterIndex + 1}.${sectionIndex + 1} ${section.title}\n`



            if (section.subsections && section.subsections.length > 0) {

              section.subsections.forEach((subsection, subsectionIndex) => {

                if (subsection && subsection.title &&

                  subsection.title !== '内容要点' &&

                  subsection.title !== '概述' &&

                  subsection.title !== '内容详情') {

                  text += `    ${chapterIndex + 1}.${sectionIndex + 1}.${subsectionIndex + 1} ${subsection.title}\n`

                }

              })

            }

          }

        })

      }

      text += '\n'

    }

  })



  return text

}



// 复制大纲到剪贴板

const copyOutlineToClipboard = () => {

  const outlineText = convertOutlineToText()

  navigator.clipboard.writeText(outlineText).then(() => {

    ElMessage.success('大纲已复制到剪贴板！')

  }).catch(() => {

    // 降级方案

    const textArea = document.createElement('textarea')

    textArea.value = outlineText

    document.body.appendChild(textArea)

    textArea.select()

    document.execCommand('copy')

    document.body.removeChild(textArea)

    ElMessage.success('大纲已复制到剪贴板！')

  })

}



// 更新iframe源地址

const updateIframeSrc = () => {

  console.log('切换到WPS页面:', selectedWPSUrl.value)

  ElMessage.info(`正在切换到${selectedWPSUrl.value}`)

}



// 刷新iframe

const refreshIframe = () => {

  console.log('刷新WPS页面')

  ElMessage.info('正在刷新页面...')

  // 通过改变src来刷新iframe

  const currentSrc = selectedWPSUrl.value

  selectedWPSUrl.value = ''

  setTimeout(() => {

    selectedWPSUrl.value = currentSrc

  }, 100)

}



// 处理iframe错误

const handleIframeError = () => {

  console.log('iframe加载失败')

  ElMessage.warning('页面加载失败，请尝试其他选项或检查网络连接')

}



// ==================== 下载监听插件功能 ====================



// 切换下载监听状态

const toggleDownloadListener = () => {

  if (isDownloadListenerActive.value) {

    startDownloadListener()

    ElMessage.success('下载监听插件已启用')

  } else {

    stopDownloadListener()

    ElMessage.info('下载监听插件已关闭')

  }

}



// 启动下载监听

const startDownloadListener = () => {

  console.log('启动下载监听插件...')



  // 方法1: 监听浏览器的下载事件

  setupBrowserDownloadListener()



  // 方法2: 监听iframe的postMessage事件

  setupIframeMessageListener()



  // 方法3: 定期检查下载文件夹变化（如果可能）

  setupDownloadFolderMonitor()



  // 方法4: 监听网络请求中的下载链接

  setupNetworkRequestListener()

}



// 停止下载监听

const stopDownloadListener = () => {

  console.log('停止下载监听插件...')



  // 移除所有事件监听器

  window.removeEventListener('beforeunload', handleBeforeUnload)

  window.removeEventListener('message', handleIframeMessage)



  // 清除定时器

  if (downloadMonitorTimer.value) {

    clearInterval(downloadMonitorTimer.value)

    downloadMonitorTimer.value = null

  }

}



// 方法1: 监听浏览器下载事件

const setupBrowserDownloadListener = () => {

  // 监听页面卸载前的下载事件

  window.addEventListener('beforeunload', handleBeforeUnload)



  // 监听点击事件，检测可能的下载链接

  document.addEventListener('click', handleClickEvent, true)

}



// 方法2: 监听iframe的postMessage事件

const setupIframeMessageListener = () => {

  window.addEventListener('message', handleIframeMessage)

}



// 方法3: 定期检查下载状态

const downloadMonitorTimer = ref(null)

const setupDownloadFolderMonitor = () => {

  // 由于浏览器安全限制，无法直接访问下载文件夹

  // 这里使用模拟的方式，定期检查是否有新的下载

  // downloadMonitorTimer.value = setInterval(() => {

  //   checkForNewDownloads()

  // }, 2000) // 每2秒检查一次

}



// 方法4: 监听网络请求

const setupNetworkRequestListener = () => {

  // 拦截fetch请求

  const originalFetch = window.fetch

  window.fetch = async (...args) => {

    const response = await originalFetch(...args)



    // 检查响应头中是否包含下载相关的信息

    const contentType = response.headers.get('content-type')

    const contentDisposition = response.headers.get('content-disposition')



    if (contentType && (

      contentType.includes('application/vnd.openxmlformats-officedocument.presentationml.presentation') ||

      contentType.includes('application/vnd.ms-powerpoint') ||

      contentType.includes('application/pdf')

    )) {

      console.log('检测到可能的PPT下载请求:', args[0])

      handleDetectedDownload(args[0], contentType, contentDisposition)

    }



    return response

  }

}



// 处理页面卸载前的下载检测

const handleBeforeUnload = (event) => {

  console.log('页面即将卸载，检查是否有下载活动')

  // 这里可以记录一些状态信息

}





// 处理iframe消息

const handleIframeMessage = (event) => {

  console.log('收到iframe消息 - 来源:', event.origin, '数据:', event.data)



  // 检查消息来源 - 放宽限制，支持多个WPS域名

  const allowedOrigins = [

    'https://aippt.wps.cn',

    'https://web.wps.cn',

    'https://www.kdocs.cn',

    'https://docs.wps.cn'

  ]



  if (!allowedOrigins.includes(event.origin)) {

    console.log('消息来源不在允许列表中:', event.origin)

    return

  }



  console.log('消息来源验证通过，处理消息:', event.data)

  console.log('消息类型:', JSON.parse(event.data))

  // 处理WPS AI PPT创建完成的消息

  let messageData = JSON.parse(event.data)



  if (messageData.type === 'AIPPT_BEFORE_CREATEPPT' && messageData.data && messageData.data.fileInfo) {

    const fileInfo = messageData.data.fileInfo

    console.log('文件信息:', fileInfo)

    currentPPTInfo.value = fileInfo

    showOpenPPTButton.value = true



    // 延迟3秒显示第二个遮罩

    setTimeout(() => {

      showSecondMask.value = true

      console.log('延迟3秒后显示第二个遮罩')

    }, 3000)

  }

}



// 处理点击事件，检测下载链接

const handleClickEvent = (event) => {

  const target = event.target

  const link = target.closest('a')



  if (link && link.href) {

    const href = link.href.toLowerCase()

    if (href.includes('.pptx') || href.includes('.ppt') || href.includes('download')) {

      console.log('检测到可能的下载链接:', link.href)

      handleDetectedDownload(link.href, 'application/vnd.openxmlformats-officedocument.presentationml.presentation')

    }

  }

}



// 检查新下载

const checkForNewDownloads = () => {

  // 由于浏览器安全限制，这里使用模拟的方式

  // 在实际应用中，可能需要通过其他方式来实现

  console.log('检查新下载...')

}



// 处理检测到的下载

const handleDetectedDownload = (url, contentType, filename) => {

  const now = new Date()

  const downloadInfo = {

    id: Date.now(),

    url: url,

    contentType: contentType,

    filename: filename || `PPT_${now.getTime()}.pptx`,

    timestamp: now.toLocaleString(),

    source: 'WPS AI PPT'

  }



  console.log('检测到下载:', downloadInfo)



  // 添加到下载历史

  downloadHistory.value.unshift(downloadInfo)



  // 限制历史记录数量

  if (downloadHistory.value.length > 10) {

    downloadHistory.value = downloadHistory.value.slice(0, 10)

  }



  // 更新最后下载时间

  lastDownloadTime.value = now



  // 显示通知

  ElMessage.success(`检测到PPT下载: ${downloadInfo.filename}`)



  // 自动下载到本地

  autoDownloadToLocal(downloadInfo)

}



// 自动下载到本地

const autoDownloadToLocal = async (downloadInfo) => {

  try {

    console.log('开始自动下载到本地:', downloadInfo)



    // 创建下载链接

    const link = document.createElement('a')

    link.href = downloadInfo.url

    link.download = downloadInfo.filename

    link.style.display = 'none'



    // 添加到页面并触发下载

    document.body.appendChild(link)

    link.click()

    document.body.removeChild(link)



    ElMessage.success(`已自动下载: ${downloadInfo.filename}`)



  } catch (error) {

    console.error('自动下载失败:', error)

    ElMessage.error('自动下载失败，请手动下载')

  }

}



// 下载WPS文件

const downloadWPSFile = async (fileInfo) => {

  try {

    console.log('开始下载WPS文件:', fileInfo)



    // 构建下载URL - 使用link_url作为下载地址

    const downloadUrl = fileInfo.link_url

    const fileName = fileInfo.file_name || `WPS_PPT_${Date.now()}.pptx`



    // 创建下载信息记录

    const downloadInfo = {

      id: Date.now(),

      url: downloadUrl,

      contentType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',

      filename: fileName,

      timestamp: new Date().toLocaleString(),

      source: 'WPS AI PPT',

      fileId: fileInfo.id,

      linkId: fileInfo.link_id

    }



    // 添加到下载历史

    downloadHistory.value.unshift(downloadInfo)



    // 限制历史记录数量

    if (downloadHistory.value.length > 10) {

      downloadHistory.value = downloadHistory.value.slice(0, 10)

    }



    // 更新最后下载时间

    lastDownloadTime.value = new Date()



    // 显示通知

    ElMessage.success(`检测到WPS PPT创建完成，正在下载: ${fileName}`)



    // 尝试多种下载方式

    await tryMultipleDownloadMethods(downloadUrl, fileName, downloadInfo)



  } catch (error) {

    console.error('WPS文件下载失败:', error)

    ElMessage.error('WPS文件下载失败，请手动下载')

  }

}



// 尝试多种下载方式

const tryMultipleDownloadMethods = async (downloadUrl, fileName, downloadInfo) => {

  const methods = [

    () => downloadViaDirectLink(downloadUrl, fileName),

    () => downloadViaProxy(downloadUrl, fileName),

    () => downloadViaNewWindow(downloadUrl, fileName),

    () => downloadViaIframe(downloadUrl, fileName)

  ]



  for (let i = 0; i < methods.length; i++) {

    try {

      console.log(`尝试下载方式 ${i + 1}:`, methods[i].name)

      await methods[i]()

      ElMessage.success(`已自动下载WPS文件: ${fileName}`)

      console.log('WPS文件下载完成:', downloadInfo)

      return // 成功则退出

    } catch (error) {

      console.warn(`下载方式 ${i + 1} 失败:`, error)

      if (i === methods.length - 1) {

        throw error // 所有方式都失败

      }

    }

  }

}



// 方式1: 直接链接下载

const downloadViaDirectLink = (downloadUrl, fileName) => {

  return new Promise((resolve, reject) => {

    const link = document.createElement('a')

    link.href = downloadUrl

    link.download = fileName

    link.style.display = 'none'



    link.onload = () => resolve()

    link.onerror = () => reject(new Error('直接链接下载失败'))



    document.body.appendChild(link)

    link.click()

    document.body.removeChild(link)



    // 给一点时间让下载开始

    setTimeout(resolve, 1000)

  })

}



// 方式2: 通过代理下载

const downloadViaProxy = async (downloadUrl, fileName) => {

  try {

    // 尝试通过后端代理下载

    const response = await fetch('/api/proxy-download', {

      method: 'POST',

      headers: {

        'Content-Type': 'application/json',

      },

      body: JSON.stringify({

        url: downloadUrl,

        filename: fileName

      })

    })



    if (!response.ok) {

      throw new Error('代理下载失败')

    }



    const blob = await response.blob()

    const url = URL.createObjectURL(blob)



    const link = document.createElement('a')

    link.href = url

    link.download = fileName

    link.style.display = 'none'



    document.body.appendChild(link)

    link.click()

    document.body.removeChild(link)



    URL.revokeObjectURL(url)

  } catch (error) {

    throw new Error('代理下载失败: ' + error.message)

  }

}



// 方式3: 新窗口下载

const downloadViaNewWindow = (downloadUrl, fileName) => {

  return new Promise((resolve, reject) => {

    const newWindow = window.open(downloadUrl, '_blank')



    if (!newWindow) {

      reject(new Error('无法打开新窗口'))

      return

    }



    // 监听窗口关闭

    const checkClosed = setInterval(() => {

      if (newWindow.closed) {

        clearInterval(checkClosed)

        resolve()

      }

    }, 1000)



    // 5秒后超时

    setTimeout(() => {

      clearInterval(checkClosed)

      resolve()

    }, 5000)

  })

}



// 方式4: iframe下载

const downloadViaIframe = (downloadUrl, fileName) => {

  return new Promise((resolve, reject) => {

    const iframe = document.createElement('iframe')

    iframe.style.display = 'none'

    iframe.src = downloadUrl



    iframe.onload = () => {

      setTimeout(() => {

        document.body.removeChild(iframe)

        resolve()

      }, 2000)

    }



    iframe.onerror = () => {

      document.body.removeChild(iframe)

      reject(new Error('iframe下载失败'))

    }



    document.body.appendChild(iframe)

  })

}



// ==================== PPT查看器功能 ====================



// 打开PPT查看器

const openPPTViewer = () => {

  if (!currentPPTInfo.value) {

    ElMessage.error('没有可查看的PPT信息')

    return

  }



  try {

    console.log('打开PPT查看器:', currentPPTInfo.value)



    // 构建PPT查看器URL

    // 使用WPS的在线查看器

    const viewerUrl = `https://www.kdocs.cn/l/${currentPPTInfo.value.linkId}`



    // 直接在当前WPS弹窗中切换iframe内容

    selectedWPSUrl.value = viewerUrl



    ElMessage.success(`正在打开PPT: ${currentPPTInfo.value.file_name}`)



  } catch (error) {

    console.error('打开PPT查看器失败:', error)

    ElMessage.error('打开PPT查看器失败，请重试')

  }

}



// 下载当前PPT

const downloadCurrentPPT = () => {

  if (!currentPPTInfo.value) {

    ElMessage.error('没有可下载的PPT信息')

    return

  }



  try {

    console.log('下载当前PPT:', currentPPTInfo.value)



    // 创建下载信息

    const downloadInfo = {

      id: Date.now(),

      url: currentPPTInfo.value.link_url,

      contentType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',

      filename: currentPPTInfo.value.file_name,

      timestamp: new Date().toLocaleString(),

      source: 'PPT查看器',

      fileId: currentPPTInfo.value.fileId,

      linkId: currentPPTInfo.value.linkId

    }



    // 添加到下载历史

    downloadHistory.value.unshift(downloadInfo)



    // 限制历史记录数量

    if (downloadHistory.value.length > 10) {

      downloadHistory.value = downloadHistory.value.slice(0, 10)

    }



    // 尝试下载

    downloadViaDirectLink(currentPPTInfo.value.link_url, currentPPTInfo.value.file_name)

      .then(() => {

        ElMessage.success(`已下载PPT: ${currentPPTInfo.value.file_name}`)

      })

      .catch((error) => {

        console.error('下载失败:', error)

        ElMessage.error('下载失败，请手动下载')

      })



  } catch (error) {

    console.error('下载当前PPT失败:', error)

    ElMessage.error('下载失败，请重试')

  }

}



// 处理PPT iframe错误

const handlePPTIframeError = () => {

  console.log('PPT iframe加载失败')

  ElMessage.warning('PPT加载失败，请检查网络连接或尝试刷新')

}



// 测试PPT检测功能

const testPPTDetection = () => {

  console.log('开始测试PPT检测功能...')



  // 模拟您提供的消息数据

  const testMessage = {

    type: "AIPPT_BEFORE_CREATEPPT",

    data: {

      infoId: "0d51bbc5f7cde18652340593cccc1305",

      fileInfo: {

        id: 450721083297,

        link_id: "ckNZFi6F3Gtv",

        link_url: "https://www.kdocs.cn/l/ckNZFi6F3Gtv",

        cache: 1,

        branch_id: 0,

        file_name: "课堂互动小游戏设计PPT.pptx"

      }

    }

  }



  console.log('模拟消息数据:', testMessage)



  // 直接调用消息处理函数

  const mockEvent = {

    origin: 'https://aippt.wps.cn',

    data: testMessage

  }



  handleIframeMessage(mockEvent)



  ElMessage.success('测试消息已发送，请检查是否出现"打开PPT"按钮')

}



// 返回WPS AI PPT

const backToWPSAI = () => {

  try {

    console.log('返回WPS AI PPT')



    // 切换回WPS AI PPT页面

    selectedWPSUrl.value = 'https://aippt.wps.cn/aippt/'



    ElMessage.success('已返回WPS AI PPT页面')



  } catch (error) {

    console.error('返回WPS AI PPT失败:', error)

    ElMessage.error('返回失败，请重试')

  }

}



// 打开测试PPT

const openTestPPT = () => {

  try {

    console.log('打开测试PPT')







    // 直接打开WPS弹窗并显示测试PPT

    showWPSModal.value = true

    selectedWPSUrl.value = currentPPTInfo.value.link_url



    ElMessage.success('正在打开测试PPT...')



  } catch (error) {

    console.error('打开测试PPT失败:', error)

    ElMessage.error('打开测试PPT失败，请重试')

  }

}



const goToStep2 = () => {

  console.log('返回步骤二：编辑大纲')



  // 检查大纲数据是否已加载，如果没有则重新加载

  if (!outlineData.value || outlineData.value.length === 0) {

    console.log('大纲数据未加载，重新加载历史记录数据')



    // 获取当前历史记录

    const currentHistoryItem = historyData.value.find(item => item.id === ai_conversation_id.value)

    if (currentHistoryItem) {

      // 重新加载大纲数据

      if (currentHistoryItem.rawData && currentHistoryItem.rawData.ppt_outline && currentHistoryItem.rawData.ppt_outline.trim()) {

        try {

          const savedOutlineData = JSON.parse(currentHistoryItem.rawData.ppt_outline)

          outlineData.value = savedOutlineData.chapters

          outlineTitle.value = savedOutlineData.title || '安全培训大纲'

          outlineStats.value = calculateOutlineStats(savedOutlineData.chapters)

          outlineId.value = currentHistoryItem.id



          // 更新保存状态

          lastSavedOutlineData.value = {

            title: outlineTitle.value,

            stats: outlineStats.value,

            chapters: JSON.parse(JSON.stringify(savedOutlineData.chapters))

          }



          console.log('重新加载大纲数据成功:', outlineTitle.value)

        } catch (error) {

          console.error('重新加载大纲数据失败:', error)

        }

      } else {

        console.log('当前历史记录没有大纲数据')

      }

    }

  } else {

    console.log('大纲数据已存在，直接跳转')

  }



  currentStep.value = 'step2'

}



const prevSlide = () => {

  if (currentSlideIndex.value > 0) {

    currentSlideIndex.value--

  } else {

    currentSlideIndex.value = slideImages.value.length - 1

  }

}



const nextSlide = () => {

  if (currentSlideIndex.value < slideImages.value.length - 1) {

    currentSlideIndex.value++

  } else {

    currentSlideIndex.value = 0

  }

}



const goToSlide = (index) => {

  currentSlideIndex.value = index



  // 如果在编辑模式下，更新当前编辑的幻灯片

  if (showDownloadOptions.value && pptSlides.value[index]) {

    currentEditingSlide.value = { ...pptSlides.value[index] }

  }

}



const goToPPTSlide = (index) => {

  currentPPTSlideIndex.value = index

  console.log('切换到PPT幻灯片:', index)



  // 确保缩略图条滚动到正确位置

  nextTick(() => {

    const thumbnailStripEl = thumbnailStrip.value

    if (thumbnailStripEl) {

      // 计算需要滚动的位置，确保当前缩略图可见

      const thumbnailWidth = 135 + 11 // 缩略图宽度 + 间距

      const scrollPosition = index * thumbnailWidth - thumbnailStripEl.clientWidth / 2 + thumbnailWidth / 2

      thumbnailStripEl.scrollLeft = Math.max(0, scrollPosition)

      console.log(`缩略图条已滚动到第${index + 1}页，位置：${scrollPosition}`)

    }

  })

}



// 缩略图滚轮滑动处理

const handleThumbnailWheel = (event) => {

  event.preventDefault() // 阻止默认滚动行为



  const thumbnailStrip = event.currentTarget

  const scrollAmount = event.deltaY > 0 ? 200 : -200 // 向下滚动时向右，向上滚动时向左



  thumbnailStrip.scrollLeft += scrollAmount

}



// PPT编辑相关方法

const initializePPTEditor = () => {

  // 使用你的本地1.pptx文件内容（2页）

  pptSlides.value = [

    {

      title: '第1页标题',

      content: '第1页内容',

      type: 'cover'

    },

    {

      title: '第2页标题',

      content: '第2页内容',

      type: 'content'

    }

  ]



  // 设置当前编辑的幻灯片

  if (pptSlides.value.length > 0) {

    currentSlideIndex.value = 0

    currentEditingSlide.value = { ...pptSlides.value[0] }

  }



  // 更新缩略图

  updateSlideThumbnails()



  console.log('PPT编辑器初始化完成，页数:', pptSlides.value.length)

}



const loadLocalPPT = () => {

  console.log('开始加载本地PPT数据')



  // 优先检查是否有生成的PPT数据

  if (generatedPPT.value && generatedPPT.value.length > 0) {

    console.log('使用已生成的PPT数据:', generatedPPT.value.length, '张幻灯片')

    // 将generatedPPT数据转换为pptSlides格式

    pptSlides.value = generatedPPT.value.map((slide, index) => ({

      title: slide.data?.title || `第${index + 1}页`,

      content: slide.data?.text || slide.data?.items?.map(item => item.title).join('\n') || '内容',

      type: slide.type

    }))



    // 更新缩略图

    updateSlideThumbnails()



    // 重置当前幻灯片索引

    currentSlideIndex.value = 0

    currentEditingSlide.value = { ...pptSlides.value[0] }



    ElMessage.success('生成的PPT数据加载成功')

    console.log('加载的PPT页数:', pptSlides.value.length)

    return

  }



  // 如果没有生成的PPT数据，加载默认的template5内容

  console.log('加载默认template5内容')

  const localPPTData = [

    {

      title: '第1页标题',

      content: '第1页内容',

      type: 'cover'

    },

    {

      title: '第2页标题',

      content: '第2页内容',

      type: 'content'

    },

    {

      title: '第3页标题',

      content: '第3页内容',

      type: 'content'

    },

    {

      title: '第4页标题',

      content: '第4页内容',

      type: 'content'

    },

    {

      title: '第5页标题',

      content: '第5页内容',

      type: 'content'

    }

  ]



  // 更新PPT数据

  pptSlides.value = localPPTData



  // 更新缩略图，显示5页

  updateSlideThumbnails()



  // 重置当前幻灯片索引

  currentSlideIndex.value = 0

  currentEditingSlide.value = { ...localPPTData[0] }



  // ElMessage.success('默认PPT文件加载成功')

  console.log('加载的PPT页数:', localPPTData.length)

}



// 从大纲数据生成PPT幻灯片

const generateSlidesFromOutline = () => {

  const slides = []



  if (outlineData.value) {

    // 封面幻灯片

    slides.push({

      title: outlineTitle.value || '安全培训演示文稿',

      content: '基于AI生成的培训大纲',

      type: 'cover'

    })



    // 目录幻灯片

    const tocContent = outlineData.value.map((chapter, index) =>

      `${index + 1}. ${chapter.title}`

    ).join('\n')

    slides.push({

      title: '目录',

      content: tocContent,

      type: 'toc'

    })



    // 章节内容幻灯片

    outlineData.value.forEach((chapter, chapterIndex) => {

      // 章节标题幻灯片

      slides.push({

        title: chapter.title,

        content: `第${chapterIndex + 1}章`,

        type: 'chapter'

      })



      // 小节内容幻灯片

      chapter.sections.forEach((section, sectionIndex) => {

        if (section.subsections && section.subsections.length > 0) {

          const content = section.subsections.map(sub => sub.title).join('\n')

          slides.push({

            title: section.title,

            content: content,

            type: 'content'

          })

        } else {

          slides.push({

            title: section.title,

            content: '详细内容待补充',

            type: 'content'

          })

        }

      })

    })



    // 总结幻灯片

    slides.push({

      title: '总结与展望',

      content: '培训要点回顾\n安全知识巩固\n持续改进建议',

      type: 'summary'

    })

  }



  return slides

}



const updateSlideContent = (event) => {

  const target = event.target

  if (target.classList.contains('slide-title')) {

    currentEditingSlide.value.title = target.textContent

  } else if (target.classList.contains('slide-body')) {

    currentEditingSlide.value.content = target.textContent

  }



  // 更新PPT数据

  if (pptSlides.value[currentSlideIndex.value]) {

    pptSlides.value[currentSlideIndex.value] = { ...currentEditingSlide.value }

  }



  // 实时更新缩略图标题

  updateSlideThumbnails()

}



const addNewSlide = () => {

  // 限制最大页数为5页

  if (pptSlides.value.length >= 5) {

    ElMessage.warning('最多只能添加5页')

    return

  }



  const newSlide = {

    title: '新页面',

    content: '点击编辑内容',

    type: 'content'

  }



  pptSlides.value.push(newSlide)

  currentSlideIndex.value = pptSlides.value.length - 1

  currentEditingSlide.value = { ...newSlide }



  updateSlideThumbnails()

  ElMessage.success('新增页面成功')

}



const deleteCurrentSlide = async () => {

  if (pptSlides.value.length <= 1) {

    ElMessage.warning('至少保留一个页面')

    return

  }



  try {

    await ElMessageBox.confirm('确定要删除当前页面吗？', '确认删除', {

      confirmButtonText: '确定',

      cancelButtonText: '取消',

      type: 'warning'

    })

  } catch {

    return // 用户取消删除

  }



  pptSlides.value.splice(currentSlideIndex.value, 1)



  if (currentSlideIndex.value >= pptSlides.value.length) {

    currentSlideIndex.value = pptSlides.value.length - 1

  }



  currentEditingSlide.value = { ...pptSlides.value[currentSlideIndex.value] }

  updateSlideThumbnails()

  ElMessage.success('删除页面成功')

}



const savePPT = () => {

  // 保存PPT数据

  localStorage.setItem('safety-training-ppt', JSON.stringify(pptSlides.value))

  ElMessage.success('PPT保存成功')

  console.log('PPT数据已保存:', pptSlides.value)

}



// 下载状态

const isDownloading = ref(false)



const exportPPTX = async () => {

  if (isDownloading.value) {

    return // 防止重复点击

  }



  try {

    isDownloading.value = true

    isProcessing.value = true

    console.log('exportPPTX: 设置 isProcessing = true')



    // 根据选中的下载选项执行不同的操作

    switch (selectedDownloadOption.value) {

      case 0: // PowerPoint (PPTX)

        await exportAsPPTX()

        break

      case 1: // 考试工坊

        await generateExamQuestions()

        break

      case 2: // 培训讲义文档

        await generateTrainingDocument()

        break

      default:

        ElMessage.warning('未知的下载选项')

    }

  } catch (error) {

    console.error('导出失败:', error)

    ElMessage.error('导出失败，请重试')

  } finally {

    isDownloading.value = false

    isProcessing.value = false

    console.log('exportPPTX: 重置 isProcessing = false')

  }

}



// 生成考试题目

const generateExamQuestions = async () => {

  try {

    console.log('开始生成考试题目...')



    // 设置loading状态

    isGeneratingExam.value = true

    isProcessing.value = true



    // 构建提示词

    const prompt = `请基于以下安全培训内容生成完整的考试题目：



培训主题：${outlineTitle.value || '安全培训'}



培训内容：

${generatedPPT.value.map((slide, index) => {

      const content = slide.elements?.map(element => {

        if (element.type === 'text') {

          return element.content.replace(/<[^>]*>/g, '') // 移除HTML标签

        }

        return ''

      }).filter(text => text.trim()).join(' ')



      return `第${index + 1}页：${content}`

    }).join('\n')}



请严格按照以下格式生成考试题目：



一、单选题（每题4分，共60分）

1. 题目内容

A. 选项A

B. 选项B

C. 选项C

D. 选项D

正确答案：X

解析：详细解析内容



2. 题目内容

A. 选项A

B. 选项B

C. 选项C

D. 选项D

正确答案：X

解析：详细解析内容



二、多选题（每题4分，共20分）

1. 题目内容

A. 选项A

B. 选项B

C. 选项C

D. 选项D

正确答案：AB

解析：详细解析内容



三、判断题（每题2分，共20分）

1. 题目内容

正确答案：正确/错误

解析：详细解析内容



四、简答题（每题10分，共20分）

1. 题目内容

答题要点：详细答案内容和评分标准



2. 题目内容

答题要点：详细答案内容和评分标准



3. 题目内容

答案：详细答案内容和评分标准



重要要求：

1. 必须严格按照上述格式输出，不能省略任何内容

2. 单选题15道（每题2分，共30分），多选题10道（每题3分，共30分），判断题10道（每题2分，共20分），简答题2道（每题10分，共20分）

3. 总分控制在100分，不包含填空题

4. 题目要全面覆盖培训内容的主要知识点

5. 每道题都要包含正确答案和详细解析

6. 简答题的答案必须详细具体，不能写"未设置"或"待补充"

7. 所有答案都要具体详细，不能省略或留空

8. 严格按照示例格式，每道简答题后面必须跟"答题要点："开头的详细内容

9. 简答题答案必须基于题目内容提供具体的知识点和实际应用示例

10. 答案内容要丰富详实，至少包含3-5个要点，每个要点都要有具体说明

11. 每道简答题的答题要点必须包含：核心概念解释、关键步骤分析、实际应用举例、注意事项说明

12. 答题要点内容要具体可操作，不能是空泛的指导性语言

13. 必须为每道简答题提供完整的答题要点，不能留空或写"未设置"`



    // 调用AI接口

    const response = await apis.reProduceSingleQuestion({

      message: prompt

    })



    if (response && response.data) {

      console.log('AI返回的数据:', response.data)

      // 提取AI回复的内容

      const aiContent = response.data.reply || response.data.content || response.data.message || response.data || 'AI生成的内容为空'

      console.log('AI生成的内容:', aiContent)

      // 将AI回复转换为Word文档并下载

      await downloadAsWord(aiContent, `考试题目-${outlineTitle.value || '安全培训'}`)

      ElMessage.success('考试题目生成成功！')

    } else {

      throw new Error('AI生成考试题目失败')

    }



  } catch (error) {

    console.error('生成考试题目失败:', error)

    ElMessage.error('生成考试题目失败: ' + error.message)

  } finally {

    // 重置loading状态

    isGeneratingExam.value = false

    isProcessing.value = false

  }

}



// 生成培训讲义文档

const generateTrainingDocument = async () => {

  try {

    console.log('开始生成培训讲义文档...')



    // 设置loading状态

    isGeneratingTrainingMaterial.value = true

    isProcessing.value = true



    // 构建提示词

    const prompt = `请基于以下安全培训内容生成培训讲义：



培训主题：${outlineTitle.value || '安全培训'}



培训内容：

${generatedPPT.value.map((slide, index) => {

      const content = slide.elements?.map(element => {

        if (element.type === 'text') {

          return element.content.replace(/<[^>]*>/g, '') // 移除HTML标签

        }

        return ''

      }).filter(text => text.trim()).join(' ')



      return `第${index + 1}页：${content}`

    }).join('\n')}



要求：

1. 生成完整的培训讲义，使用Markdown格式

2. 包含以下结构：

   - 封面页（标题、副标题、日期）

   - 目录页

   - 各章节内容（使用# ## ###等标题层级）

   - 要点列表（使用- 或 1. 2. 等）

   - 重要概念（使用**粗体**标记）

   - 注意事项（使用> 引用格式）

   - 总结页

3. 内容要详细、专业、易懂

4. 适合作为培训教材使用

5. 使用标准的Markdown语法



请生成完整的培训讲义文档，使用规范的Markdown格式。`



    // 调用AI接口

    const response = await apis.reProduceSingleQuestion({

      message: prompt

    })



    if (response && response.data) {

      console.log('AI返回的数据:', response.data)

      // 提取AI回复的内容

      const aiContent = response.data.reply || response.data.content || response.data.message || response.data || 'AI生成的内容为空'

      // 将AI回复转换为Word文档并下载

      await downloadAsWord(aiContent, `培训讲义-${outlineTitle.value || '安全培训'}`)

      ElMessage.success('培训讲义生成成功！')

    } else {

      throw new Error('AI生成培训讲义失败')

    }



  } catch (error) {

    console.error('生成培训讲义失败:', error)

    ElMessage.error('生成培训讲义失败: ' + error.message)

  } finally {

    // 重置loading状态

    isGeneratingTrainingMaterial.value = false

    isProcessing.value = false

  }

}



// 下载为Word文档 - 培训讲义专用（简洁格式）

const downloadAsWord = async (content, fileName) => {

  try {

    console.log('开始生成Word文档，内容:', content)



    // 确保content是字符串

    const contentStr = String(content)

    console.log('转换后的内容字符串:', contentStr)



    // 清理内容，移除复杂的格式

    let cleanedContent = contentStr

      // 移除HTML标签

      .replace(/<[^>]*>/g, '')

      // 移除颜色相关的文本

      .replace(/颜色[：:].*?[，,。]/g, '')

      .replace(/红色|蓝色|绿色|黄色|紫色|橙色|灰色|黑色|白色/g, '')

      .replace(/#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}/g, '')

      .replace(/rgb\([^)]*\)|rgba\([^)]*\)/g, '')

      // 移除代码块标记

      .replace(/```[\s\S]*?```/g, '')

      .replace(/`[^`]*`/g, '')

      // 移除引用标记

      .replace(/^>\s*/gm, '')

      // 移除表格标记

      .replace(/\|.*\|/g, '')

      .replace(/^[-:\s|]+$/gm, '')

      // 清理多余的空行

      .replace(/\n\s*\n\s*\n/g, '\n\n')



    // 创建简洁的HTML文档内容

    const wordContent = createSimpleWordContent(cleanedContent, fileName)



    // 创建Blob对象 - 使用Word兼容的MIME类型

    const blob = new Blob([wordContent], {

      type: 'application/msword'

    })



    // 下载文件

    const url = URL.createObjectURL(blob)

    const link = document.createElement('a')

    link.setAttribute('href', url)

    link.setAttribute('download', `${fileName}-${new Date().toISOString().split('T')[0]}.doc`)

    link.style.visibility = 'hidden'

    document.body.appendChild(link)

    link.click()

    document.body.removeChild(link)

    URL.revokeObjectURL(url)



    console.log('Word文档已下载')



  } catch (error) {

    console.error('下载Word文档失败:', error)

    // 备用方案：使用简单的文本下载

    const contentStr = String(content)

    const blob = new Blob([contentStr], { type: 'text/plain;charset=utf-8' })

    const url = window.URL.createObjectURL(blob)

    const link = document.createElement('a')

    link.href = url

    link.download = `${fileName}-${new Date().toISOString().split('T')[0]}.txt`

    document.body.appendChild(link)

    link.click()

    document.body.removeChild(link)

    window.URL.revokeObjectURL(url)

    console.log('已降级为文本文件下载')

  }

}



// 创建简洁的Word文档内容（参考AI写作的格式）

const createSimpleWordContent = (content, fileName) => {

  // 处理内容，转换为简洁格式

  let processedContent = content

    // 处理Markdown标题

    .replace(/^# (.*?)$/gm, '<h1>$1</h1>')

    .replace(/^## (.*?)$/gm, '<h2>$1</h2>')

    .replace(/^### (.*?)$/gm, '<h3>$1</h3>')

    .replace(/^#### (.*?)$/gm, '<h4>$1</h4>')

    // 处理Markdown加粗

    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')

    // 处理Markdown斜体

    .replace(/\*(.*?)\*/g, '<em>$1</em>')

    // 处理Markdown列表

    .replace(/^\- (.*$)/gim, '<div class="list-item">- $1</div>')

    .replace(/^(\d+)\. (.*$)/gim, '<div class="list-item">$1. $2</div>')

    // 处理换行符

    .replace(/\n/g, '<br>')



  // 创建HTML格式的文档内容（兼容Microsoft Office Word）

  return `<!DOCTYPE html>

<html xmlns:o="urn:schemas-microsoft-com:office:office" 

      xmlns:w="urn:schemas-microsoft-com:office:word" 

      xmlns="http://www.w3.org/TR/REC-html40">

<head>

  <meta charset="utf-8">

  <meta name="ProgId" content="Word.Document">

  <meta name="Generator" content="Microsoft Word 15">

  <meta name="Originator" content="Microsoft Word 15">

  <title>${fileName || '培训讲义'}</title>

  <style>

    body {

      font-family: "Microsoft YaHei", "宋体", Arial, sans-serif;

      font-size: 14px;

      line-height: 1.6;

      margin: 24px;

      color: #000;

    }

    .header {

      text-align: center;

      margin-bottom: 14px;

    }

    .doc-title {

      font-size: 24px;

      font-weight: bold;

      margin-bottom: 14px;

      color: #000;

    }

    .content { 

          font-family: "Microsoft YaHei", "宋体", Arial, sans-serif; 

          font-size: 14px; 

          line-height: 1.6; 

          color: #000;

          margin: 0;

          padding: 0;

        }

        .header { 

          text-align: center; 

          margin-bottom: 30px; 

        }

        .doc-title { 

          font-size: 24px; 

          font-weight: bold; 

          margin-bottom: 20px; 

          color: #333;

        }

        h1 { 

          font-size: 20px; 

          font-weight: bold; 

          margin: 20px 0 15px 0; 

          color: #333;

        }

        h2 { 

          font-size: 18px; 

          font-weight: bold; 

          margin: 18px 0 12px 0; 

          color: #333;

        }

        h3 { 

          font-size: 16px; 

          font-weight: bold; 

          margin: 15px 0 10px 0; 

          color: #333;

        }

        h4 { 

          font-size: 14px; 

          font-weight: bold; 

          margin: 12px 0 8px 0; 

          color: #333;

        }

        p { 

          margin: 10px 0; 

          text-align: justify;

        }

        .list-item { 

          margin: 5px 0; 

          padding-left: 20px;

        }

        strong { 

          font-weight: bold; 

        }

        em { 

          font-style: italic; 

        }

      </style>

    </head>

    <body>

      <div class='header'>

        <div class='doc-title'>${fileName || '培训讲义'}</div>

      </div>

      <div class='content'>

        ${processedContent}

      </div>

    </body>

    </html>

  `

}



// 解析AI生成的内容，提取题目信息

const parseExamContent = (content, fileName) => {

  const contentStr = String(content)

  console.log('开始解析内容，原始内容长度:', contentStr.length)

  const lines = contentStr.split('\n').filter(line => line.trim())

  console.log('分割后的行数:', lines.length)

  console.log('前10行内容:', lines.slice(0, 10))



  const examData = {

    title: fileName,

    totalScore: 0,

    totalQuestions: 0,

    singleChoice: {

      questions: [],

      scorePerQuestion: 2,

      totalScore: 0

    },

    multiple: {

      questions: [],

      scorePerQuestion: 3,

      totalScore: 0

    },

    judge: {

      questions: [],

      scorePerQuestion: 2,

      totalScore: 0

    },

    short: {

      questions: [],

      scorePerQuestion: 10,

      totalScore: 0

    }

  }



  let currentSection = ''

  let questionIndex = 0



  for (let i = 0; i < lines.length; i++) {

    const line = lines[i].trim()

    if (!line) continue



    // 检测章节

    if (line.includes('一、单选题') || line.includes('单选题') || line.includes('选择题') || line.includes('一、')) {

      currentSection = 'singleChoice'

      continue

    } else if (line.includes('二、多选题') || line.includes('多选题') || line.includes('多项选择题') || line.includes('二、')) {

      currentSection = 'multiple'

      continue

    } else if (line.includes('三、判断题') || line.includes('判断题') || line.includes('三、')) {

      currentSection = 'judge'

      continue

    } else if (line.includes('四、简答题') || line.includes('简答题') || line.includes('问答题') || line.includes('四、')) {

      console.log('检测到简答题章节:', line)

      currentSection = 'short'

      continue

    }



    // 检测题目

    if (line.match(/^\d+[.、]/) || line.match(/^第\d+题/)) {

      questionIndex++



      // 检查是否是填空题（包含空白处）

      const isFillQuestion = line.includes('_______') || line.includes('____') || line.includes('空白')



      // 如果当前是判断题章节但题目是填空题，则归类为填空题

      if (currentSection === 'judge' && isFillQuestion) {

        currentSection = 'fill'

      }



      const question = {

        text: line.replace(/^\d+[.、]/, '').replace(/^第\d+题/, '').trim()

          .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // 转换粗体

          .replace(/\*(.*?)\*/g, '<em>$1</em>'), // 转换斜体

        options: [],

        selectedAnswer: '',

        selectedAnswers: [],

        outline: { keyFactors: '' }

      }



      // 收集选项和答案

      let j = i + 1

      while (j < lines.length) {

        const nextLine = lines[j].trim()

        if (!nextLine) {

          j++

          continue

        }



        // 检测选项

        if (nextLine.match(/^[A-D][.、]/)) {

          question.options.push({

            key: nextLine.charAt(0),

            text: nextLine.substring(2).trim()

          })

        }

        // 检测答案

        else if (nextLine.includes('正确答案：') || nextLine.includes('答案：')) {

          const answerText = nextLine.replace(/正确答案[:：]?/, '').replace(/答案[:：]?/, '').trim()

          if (currentSection === 'multiple') {

            question.selectedAnswers = answerText.split('').filter(char => /[A-D]/.test(char))

          } else if (currentSection === 'judge') {

            question.selectedAnswer = answerText.includes('正确') ? '正确' : '错误'

          } else if (currentSection === 'fill') {

            question.selectedAnswer = answerText

          } else {

            question.selectedAnswer = answerText

          }

        }

        // 检测解析

        else if (nextLine.includes('解析：') || nextLine.includes('说明：')) {

          let analysisText = nextLine.replace(/解析[:：]?/, '').replace(/说明[:：]?/, '').trim()



          // 收集多行解析内容

          let k = j + 1

          while (k < lines.length) {

            const nextAnalysisLine = lines[k]?.trim()

            // 如果遇到下一题或新章节，停止收集

            if (nextAnalysisLine.match(/^\d+[.、]/) || nextAnalysisLine.match(/^第\d+题/) ||

              nextAnalysisLine.includes('一、单选题') || nextAnalysisLine.includes('二、多选题') ||

              nextAnalysisLine.includes('三、判断题') || nextAnalysisLine.includes('四、简答题') ||

              nextAnalysisLine.includes('单选题') || nextAnalysisLine.includes('多选题') ||

              nextAnalysisLine.includes('判断题') || nextAnalysisLine.includes('简答题') ||

              nextAnalysisLine.includes('正确答案：') || nextAnalysisLine.includes('答题要点：')) {

              break

            }

            // 如果遇到空行，跳过

            if (nextAnalysisLine === '') {

              k++

              continue

            }

            // 收集解析内容

            if (analysisText) {

              analysisText += '\n' + nextAnalysisLine

            } else {

              analysisText = nextAnalysisLine

            }

            k++

          }



          question.outline.keyFactors = analysisText

          console.log('收集到解析内容:', analysisText)

          j = k - 1 // 更新j的位置

        }

        // 检测答题要点或答案

        else if (nextLine.includes('答题要点：') || nextLine.includes('答案：') || nextLine.includes('答案')) {

          console.log('找到简答题答案:', nextLine)

          let answerText = nextLine.replace(/答题要点[:：]?/, '').replace(/答案[:：]?/, '').trim()



          // 无论是否包含"未设置"，都尝试收集多行答案内容

          let k = j + 1

          while (k < lines.length) {

            const nextAnswerLine = lines[k]?.trim()

            // 如果遇到下一题或新章节，停止收集

            if (nextAnswerLine.match(/^\d+[.、]/) || nextAnswerLine.match(/^第\d+题/) ||

              nextAnswerLine.includes('一、单选题') || nextAnswerLine.includes('二、多选题') ||

              nextAnswerLine.includes('三、判断题') || nextAnswerLine.includes('四、简答题') ||

              nextAnswerLine.includes('单选题') || nextAnswerLine.includes('多选题') ||

              nextAnswerLine.includes('判断题') || nextAnswerLine.includes('简答题') ||

              nextAnswerLine.includes('正确答案：') || nextAnswerLine.includes('解析：')) {

              break

            }

            // 如果遇到空行，跳过

            if (nextAnswerLine === '') {

              k++

              continue

            }

            // 收集答案内容

            if (answerText) {

              answerText += '\n' + nextAnswerLine

            } else {

              answerText = nextAnswerLine

            }

            k++

          }



          // 如果答案包含"未设置"，则清空

          if (answerText.includes('未设置')) {

            answerText = ''

          }



          question.outline.keyFactors = answerText

          console.log('收集到完整答题要点:', answerText)

          j = k - 1 // 更新j的位置

        }

        // 如果没有找到答题要点，尝试从题目后面直接收集答案内容

        else if (currentSection === 'short' && nextLine.trim() !== '' && !nextLine.match(/^\d+[.、]/) &&

          !nextLine.includes('一、') && !nextLine.includes('二、') && !nextLine.includes('三、') &&

          !nextLine.includes('四、') && !nextLine.includes('五、') &&

          !nextLine.includes('单选题') && !nextLine.includes('多选题') &&

          !nextLine.includes('判断题') && !nextLine.includes('简答题')) {

          // 如果当前题目还没有答题要点，则收集这行作为答案

          if (!question.outline.keyFactors || question.outline.keyFactors === '') {

            question.outline.keyFactors = nextLine.trim()

            console.log('收集到简答题答案内容:', nextLine.trim())

          }

        }

        // 如果遇到下一题或新章节，停止收集

        else if (nextLine.match(/^\d+[.、]/) || nextLine.match(/^第\d+题/) ||

          nextLine.includes('一、单选题') || nextLine.includes('二、多选题') ||

          nextLine.includes('三、判断题') || nextLine.includes('四、简答题') ||

          nextLine.includes('单选题') || nextLine.includes('多选题') ||

          nextLine.includes('判断题') || nextLine.includes('简答题')) {

          break

        }



        j++

      }



      if (currentSection && examData[currentSection]) {

        examData[currentSection].questions.push(question)

        examData.totalQuestions++

      }



      i = j - 1 // 跳过已处理的行

    }

  }



  // 计算分数

  examData.singleChoice.totalScore = examData.singleChoice.questions.length * examData.singleChoice.scorePerQuestion

  examData.multiple.totalScore = examData.multiple.questions.length * examData.multiple.scorePerQuestion

  examData.judge.totalScore = examData.judge.questions.length * examData.judge.scorePerQuestion

  examData.short.totalScore = examData.short.questions.length * examData.short.scorePerQuestion

  examData.totalScore = examData.singleChoice.totalScore + examData.multiple.totalScore +

    examData.judge.totalScore + examData.short.totalScore



  return examData

}

// 自动保存修改后的PPT数据

const saveModifiedPPTData = () => {

  try {

    // 将修改后的PPT数据保存到localStorage

    const modifiedPPTData = {

      slides: generatedPPT.value,

      timestamp: Date.now(),

      title: outlineTitle.value || '安全培训演示文稿'

    }

    localStorage.setItem('safetyHazardModifiedPPT', JSON.stringify(modifiedPPTData))

    console.log('PPT修改已自动保存')

  } catch (error) {

    console.error('保存PPT数据失败:', error)

  }

}

// 检查并跳转到指定步骤



// 加载修改后的PPT数据

const loadModifiedPPTData = () => {

  try {

    const savedData = localStorage.getItem('safetyHazardModifiedPPT')

    if (savedData) {

      const data = JSON.parse(savedData)

      // 检查数据是否过期（24小时）

      if (Date.now() - data.timestamp < 24 * 60 * 60 * 1000) {

        generatedPPT.value = data.slides || []

        console.log('已加载修改后的PPT数据')

        return true

      } else {

        localStorage.removeItem('safetyHazardModifiedPPT')

        console.log('修改后的PPT数据已过期，已清除')

      }

    }

  } catch (error) {

    console.error('加载修改后的PPT数据失败:', error)

  }

  return false

}







// 导出为真正的PPTX文件（使用修改后的数据）

const exportAsPPTX = async () => {

  if (generatedPPT.value.length === 0) {

    ElMessage.warning('没有可导出的PPT内容，请先生成PPT')

    return

  }



  try {

    // 动态导入PptxGenJS

    const PptxGenJS = (await import('pptxgenjs')).default



    // 创建新的PPT实例

    const pptx = new PptxGenJS()



    // 设置PPT页面尺寸（16:9比例，无黑边）

    pptx.defineLayout({

      name: 'CUSTOM_16_9',

      width: 10,

      height: 5.625

    })

    pptx.layout = 'CUSTOM_16_9'



    // 设置PPT属性

    pptx.author = '安全培训系统'

    pptx.company = '蜀道科技'

    pptx.subject = '安全培训演示文稿'

    pptx.title = outlineTitle.value || '安全培训演示文稿'



    // 遍历生成的所有幻灯片（使用修改后的数据）

    for (let i = 0; i < generatedPPT.value.length; i++) {

      const slideData = generatedPPT.value[i]

      console.log(`正在转换第 ${i + 1} 页:`, slideData.type)



      await convertSlideToPptx(pptx, slideData)

    }



    // 生成并下载PPTX文件

    const fileName = `安全培训-${outlineTitle.value || '演示文稿'}-${new Date().toISOString().split('T')[0]}.pptx`

    await pptx.writeFile({ fileName })



    console.log('PPTX文件已生成并下载')

    ElMessage.success(`成功导出PPTX文件！\n文件名: ${fileName}`)



  } catch (error) {

    console.error('导出PPTX失败:', error)

    ElMessage.error('导出PPTX失败: ' + error.message)

  }

}



// 将单个幻灯片转换为PPTX格式

const convertSlideToPptx = async (pptx, slideData) => {

  // 添加新幻灯片

  const slide = pptx.addSlide()



  // 设置背景

  if (slideData.background) {

    if (slideData.background.type === 'solid') {

      const bgColor = convertColorForPptx(slideData.background.color || '#FFFFFF')

      slide.background = { color: bgColor }

    } else if (slideData.background.type === 'gradient' && slideData.background.gradient) {

      // PptxGenJS对渐变支持有限，使用第一个颜色作为背景

      const firstColor = slideData.background.gradient.colors[0]?.color || '#FFFFFF'

      const bgColor = convertColorForPptx(firstColor)

      slide.background = { color: bgColor }

    }

  }



  // 处理每个元素

  for (const element of slideData.elements) {

    await addElementToPptxSlide(slide, element)

  }

}



// 将元素添加到PPTX幻灯片

const addElementToPptxSlide = async (slide, element) => {

  try {

    // 转换坐标和尺寸 (从960x540到标准PPT尺寸)

    const scaleX = 10 // PPTX使用英寸，960px ≈ 10英寸

    const scaleY = 5.625 // 540px ≈ 5.625英寸



    const x = (element.left / 960) * scaleX

    const y = (element.top / 540) * scaleY

    const w = (element.width / 960) * scaleX

    const h = (element.height / 540) * scaleY



    switch (element.type) {

      case 'text':

        addTextToPptx(slide, element, x, y, w, h)

        break



      case 'image':

        await addImageToPptx(slide, element, x, y, w, h)

        break



      case 'shape':

        addShapeToPptx(slide, element, x, y, w, h)

        break

    }

  } catch (error) {

    console.warn(`添加元素失败 ${element.type}:`, error)

  }

}



// 添加文本到PPTX

const addTextToPptx = (slide, element, x, y, w, h) => {

  // 提取纯文本内容

  const textContent = extractTextFromHtml(element.content)



  // 提取样式信息

  const style = extractStyleFromHtml(element.content)



  // 优先使用HTML中的颜色，其次使用元素默认颜色

  const textColor = style.color || element.defaultColor || '#000000'



  // 计算透明度（PPTX使用0-100的百分比）

  const opacity = element.opacity !== undefined ? Math.round(element.opacity * 100) : 100



  // 调试文本对齐

  if (element.content && element.content.includes('text-align: center')) {

    console.log(`文本对齐调试 ${element.id || 'unknown'}:`, {

      content: element.content,

      extractedAlign: style.align,

      finalAlign: style.align || 'left'

    })

  }



  slide.addText(textContent, {

    x: x,

    y: y,

    w: w,

    h: h,

    fontSize: style.fontSize || 16,

    color: convertColorForPptx(textColor),

    fontFace: element.defaultFontName || '微软雅黑',

    align: style.align || 'left',

    valign: 'middle',

    bold: style.bold || false,

    wrap: true,

    transparency: 100 - opacity,  // PPTX使用transparency（0=不透明，100=完全透明）

    line: null  // 禁用文本边框

  })

}



// 添加图片到PPTX

const addImageToPptx = async (slide, element, x, y, w, h) => {

  if (!element.src) {

    console.warn('图片元素没有src属性:', element)

    return

  }



  try {

    // 计算透明度（PPTX使用0-100的百分比）

    const opacity = element.opacity !== undefined ? Math.round(element.opacity * 100) : 100

    const transparency = 100 - opacity



    console.log('处理图片元素:', {

      id: element.id,

      srcType: element.src.startsWith('data:image') ? 'base64' : 'url',

      srcLength: element.src.length,

      opacity: opacity,

      transparency: transparency

    })



    // 如果是base64图片，需要检查是否为SVG格式

    if (element.src.startsWith('data:image')) {

      console.log('使用base64图片数据')



      // 验证base64数据格式

      if (!element.src.includes(',')) {

        throw new Error('Base64数据格式错误：缺少逗号分隔符')

      }



      const [header, data] = element.src.split(',')

      if (!header || !data) {

        throw new Error('Base64数据格式错误：header或data为空')

      }



      // 检查数据长度（base64数据不能为空）

      if (data.length < 100) {

        throw new Error('Base64数据过短，可能已损坏')

      }



      let imageData = element.src



      // 检查是否为SVG格式，如果是则转换为PNG

      if (element.src.includes('data:image/svg+xml')) {

        console.log('检测到SVG图像，开始转换为PNG格式')

        try {

          imageData = await convertSvgToPng(element.src)

          console.log('SVG转PNG成功，使用转换后的PNG数据')

        } catch (svgError) {

          console.error('SVG转PNG失败:', svgError)

          throw new Error('SVG图像转换失败: ' + svgError.message)

        }

      }



      try {

        slide.addImage({

          data: imageData,

          x: x,

          y: y,

          w: w,

          h: h,

          transparency: transparency,

          sizing: {

            type: 'cover', // 填满目标区域，超出部分裁剪

            w: w,

            h: h

          },

          line: null  // 禁用图片边框

        })



        console.log('Base64图片添加成功')

      } catch (addImageError) {

        console.error('PptxGenJS添加图片失败:', addImageError)



        // 尝试使用不同的方式添加图片

        try {

          // 移除data:image/xxx;base64,前缀，只保留纯base64数据

          const pureBase64 = imageData.split(',')[1]

          slide.addImage({

            data: pureBase64,

            x: x,

            y: y,

            w: w,

            h: h,

            transparency: transparency,

            sizing: {

              type: 'cover', // 填满目标区域，超出部分裁剪

              w: w,

              h: h

            },

            line: null  // 禁用图片边框

          })

          console.log('使用纯base64数据添加成功')

        } catch (retryError) {

          console.error('重试添加图片也失败:', retryError)

          throw addImageError // 抛出原始错误

        }

      }

    } else {

      // 如果是URL，需要转换为base64

      console.log('转换URL图片为base64:', element.src)

      const base64Data = await convertImageToBase64(element.src)



      slide.addImage({

        data: base64Data,

        x: x,

        y: y,

        w: w,

        h: h,

        transparency: transparency,

        sizing: {

          type: 'cover', // 填满目标区域，超出部分裁剪

          w: w,

          h: h

        },

        line: null  // 禁用图片边框

      })



      console.log('URL图片转换并添加成功')

    }

  } catch (error) {

    console.error('添加图片失败:', error)

    console.error('图片元素详情:', element)



    // 图片加载失败时，添加一个占位文本

    slide.addText(`图片加载失败: ${error.message}`, {

      x: x,

      y: y,

      w: w,

      h: h,

      fontSize: 10,

      color: 'FF0000',

      align: 'center',

      valign: 'middle',

      bold: true

    })

  }

}



// 添加形状到PPTX

const addShapeToPptx = (slide, element, x, y, w, h) => {

  // 获取颜色值，优先使用fill属性

  const fillColor = element.fill || element.color || '#007bff'



  // 计算透明度

  let transparency = 0

  if (fillColor.startsWith('rgba(')) {

    // 如果颜色是rgba格式，优先使用其alpha通道

    const rgbaMatch = fillColor.match(/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/)

    if (rgbaMatch) {

      const alpha = parseFloat(rgbaMatch[4])

      transparency = Math.round((1 - alpha) * 100)

    }

  } else if (element.opacity !== undefined) {

    // 如果颜色不是rgba但元素有opacity属性，使用它

    transparency = 100 - Math.round(element.opacity * 100)

  }



  const convertedColor = convertColorForPptx(fillColor)



  // 调试overlay元素

  if (element.id && element.id.includes('overlay')) {

    console.log(`Overlay元素调试 ${element.id}:`, {

      originalFill: element.fill,

      fillColor: fillColor,

      convertedColor: convertedColor,

      opacity: element.opacity,

      transparency: transparency,

      alphaFromRgba: fillColor.startsWith('rgba(') ? parseFloat(fillColor.match(/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/)?.[4] || '1') : null

    })

  }



  slide.addShape('rect', {

    x: x,

    y: y,

    w: w,

    h: h,

    fill: {

      type: 'solid',

      color: convertedColor,

      transparency: transparency

    },

    line: null  // 完全禁用边框

  })

}



// 从HTML提取纯文本

const extractTextFromHtml = (html) => {

  if (!html) return ''

  // 创建临时DOM元素来解析HTML

  const tempDiv = document.createElement('div')

  tempDiv.innerHTML = html

  return tempDiv.textContent || tempDiv.innerText || ''

}



// 从HTML提取样式

const extractStyleFromHtml = (html) => {

  if (!html) return {}



  const style = {}



  // 检查字体大小 - 让导出字号和预览保持一致

  const fontSizeMatch = html.match(/font-size:\s*(\d+)px/)

  if (fontSizeMatch) {

    const previewFontSize = parseInt(fontSizeMatch[1])

    // 为了让导出字号和预览视觉效果一致，我们进一步缩小导出字号

    // 预览字体大小 = 原始字体大小 * (1105/960)

    // 导出时再缩小一点，让效果和预览一致

    const scaleFactor = 1105 / 960

    const additionalScale = 0.7 // 额外缩小30%，让导出和预览视觉效果一致

    style.fontSize = Math.round(previewFontSize / scaleFactor * additionalScale)

    console.log(`字体大小转换: 预览${previewFontSize}px -> 导出${style.fontSize}px (缩放因子: ${scaleFactor.toFixed(3)}, 额外缩放: ${additionalScale})`)

  }



  // 检查颜色

  const colorMatch = html.match(/color:\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|rgb\([^)]+\)|rgba\([^)]+\))/)

  if (colorMatch) {

    style.color = colorMatch[1]

  }



  // 检查对齐方式

  if (html.includes('text-align: center')) {

    style.align = 'center'

  } else if (html.includes('text-align: right')) {

    style.align = 'right'

  } else {

    style.align = 'left'  // 默认左对齐

  }



  // 检查粗体

  if (html.includes('<strong>') || html.includes('<b>')) {

    style.bold = true

  }



  return style

}



// 将SVG转换为PNG格式的base64

const convertSvgToPng = (svgDataUrl) => {

  return new Promise((resolve, reject) => {

    try {

      // 创建Image对象来加载SVG

      const img = new Image()



      img.onload = () => {

        try {

          // 创建Canvas

          const canvas = document.createElement('canvas')

          const ctx = canvas.getContext('2d')



          // 设置Canvas尺寸

          canvas.width = img.width || 960

          canvas.height = img.height || 540



          // 设置高质量渲染

          ctx.imageSmoothingEnabled = true

          ctx.imageSmoothingQuality = 'high'



          // 绘制SVG到Canvas

          ctx.drawImage(img, 0, 0, canvas.width, canvas.height)



          // 转换为PNG格式的base64

          const pngDataUrl = canvas.toDataURL('image/png', 1.0)



          console.log('SVG转PNG成功:', {

            originalSvgLength: svgDataUrl.length,

            pngDataUrlLength: pngDataUrl.length,

            canvasSize: `${canvas.width}x${canvas.height}`

          })



          resolve(pngDataUrl)

        } catch (canvasError) {

          console.error('Canvas转换失败:', canvasError)

          reject(new Error('SVG转PNG失败: ' + canvasError.message))

        }

      }



      img.onerror = (error) => {

        console.error('SVG图像加载失败:', error)

        reject(new Error('SVG图像加载失败'))

      }



      // 加载SVG图像

      img.src = svgDataUrl

    } catch (error) {

      console.error('SVG转换初始化失败:', error)

      reject(new Error('SVG转换初始化失败: ' + error.message))

    }

  })

}



// 将图片URL转换为base64

const convertImageToBase64 = (url) => {

  return new Promise((resolve, reject) => {

    const img = new Image()

    img.crossOrigin = 'anonymous'



    img.onload = () => {

      const canvas = document.createElement('canvas')

      const ctx = canvas.getContext('2d')

      canvas.width = img.width

      canvas.height = img.height

      ctx.drawImage(img, 0, 0)



      try {

        const dataURL = canvas.toDataURL('image/png')

        resolve(dataURL)

      } catch (error) {

        reject(error)

      }

    }



    img.onerror = () => {

      reject(new Error('图片加载失败'))

    }



    img.src = url

  })

}



// 颜色转换函数 - 将各种颜色格式转换为PPTX需要的格式

const convertColorForPptx = (color) => {

  if (!color) return 'FFFFFF'



  // 如果已经是6位十六进制格式（无#），直接返回

  if (/^[0-9A-Fa-f]{6}$/.test(color)) {

    return color.toUpperCase()

  }



  // 如果是#开头的十六进制格式

  if (color.startsWith('#')) {

    const hex = color.substring(1)

    // 处理3位十六进制颜色（如#f0f）

    if (hex.length === 3) {

      return (hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]).toUpperCase()

    }

    // 处理6位十六进制颜色（如#ff00ff）

    if (hex.length === 6) {

      return hex.toUpperCase()

    }

  }



  // 处理rgb()格式

  const rgbMatch = color.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/)

  if (rgbMatch) {

    const r = parseInt(rgbMatch[1]).toString(16).padStart(2, '0')

    const g = parseInt(rgbMatch[2]).toString(16).padStart(2, '0')

    const b = parseInt(rgbMatch[3]).toString(16).padStart(2, '0')

    return (r + g + b).toUpperCase()

  }



  // 处理rgba()格式（忽略透明度）

  const rgbaMatch = color.match(/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)/)

  if (rgbaMatch) {

    const r = parseInt(rgbaMatch[1]).toString(16).padStart(2, '0')

    const g = parseInt(rgbaMatch[2]).toString(16).padStart(2, '0')

    const b = parseInt(rgbaMatch[3]).toString(16).padStart(2, '0')

    return (r + g + b).toUpperCase()

  }



  // 处理命名颜色

  const namedColors = {

    'white': 'FFFFFF',

    'black': '000000',

    'red': 'FF0000',

    'green': '008000',

    'blue': '0000FF',

    'yellow': 'FFFF00',

    'orange': 'FFA500',

    'purple': '800080',

    'pink': 'FFC0CB',

    'gray': '808080',

    'grey': '808080'

  }



  const normalizedColor = color.toLowerCase()

  if (namedColors[normalizedColor]) {

    return namedColors[normalizedColor]

  }



  // 如果无法识别，默认返回黑色

  console.warn(`无法识别的颜色格式: ${color}，使用默认黑色`)

  return '000000'

}



const updateSlideThumbnails = () => {

  // 优先使用generatedPPT（红色主题模板），如果没有则使用pptSlides（通用类PPT）

  const pptData = generatedPPT.value && generatedPPT.value.length > 0 ? generatedPPT.value : pptSlides.value



  // 如果都没有数据，直接使用template5的5张图片

  if (!pptData || pptData.length === 0) {

    slideImages.value = [

      template5Slide1,

      template5Slide2,

      template5Slide3,

      template5Slide4,

      template5Slide5

    ]

    console.log('PPT内容为空，使用默认template5图片，共', slideImages.value.length, '张')

    return

  }



  // 根据PPT内容生成缩略图

  slideImages.value = pptData.map((slide, index) => {

    // 如果是红色主题模板，使用模板7的图片

    if (selectedTemplateStyle.value === 'red') {



      const template7Images = [

        template7Slide1, // 第1页 - 封面

        template7Slide2, // 第2页 - 目录

        template7Slide3, // 第3页 - 过渡

        template7Slide4, // 第4页 - 内容

        template7Slide5  // 第5页 - 结束

      ]



      // 直接根据索引选择模板7图片，确保每张都不同

      console.log(`幻灯片 ${index + 1} 类型: ${slide.type || 'content'}, ID: ${slide.id}`)



      // 直接使用索引来选择图片，确保每张都不同

      return template7Images[index % 5]

    }



    // 如果是蓝色科技主题模板，使用模板8的图片

    if (selectedTemplateStyle.value === 'blueTech') {

      const template8Images = [

        template8Slide1, // 第1页 - 封面

        template8Slide2, // 第2页 - 目录

        template8Slide3, // 第3页 - 过渡

        template8Slide4, // 第4页 - 内容

        template8Slide5  // 第5页 - 结束

      ]



      // 直接根据索引选择模板8图片，确保每张都不同

      console.log(`幻灯片 ${index + 1} 类型: ${slide.type || 'content'}, ID: ${slide.id}`)



      // 直接使用索引来选择图片，确保每张都不同

      return template8Images[index % 5]

    }



    // 否则使用template5文件夹中的图片，循环使用5张图片

    const template5Images = [

      template5Slide1,

      template5Slide2,

      template5Slide3,

      template5Slide4,

      template5Slide5

    ]

    return template5Images[index % 5] // 循环使用5张图片

  })



  console.log('缩略图更新完成，共', slideImages.value.length, '页')

}



// 根据模板风格和幻灯片类型生成缩略图

const generateThumbnailForStyle = (slideType, style) => {

  // 根据风格选择背景图片

  let backgroundImage = ''

  if (style === 'redElegant' || style === 'red') {

    backgroundImage = 'http://172.16.17.52:8060/gdsc-ai-aqzs/images/2025/0911_1757633045.png'

  } else if (style === 'blueTech') {

    backgroundImage = 'http://172.16.17.52:8060/gdsc-ai-aqzs/images/2025/0911_1757633045.png'

  } else {

    // 默认风格使用template5图片

    const template5Images = [

      template5Slide1, // cover

      template5Slide2, // contents

      template5Slide3, // transition

      template5Slide4, // content

      template5Slide5  // end

    ]



    switch (slideType) {

      case 'cover':

        return template5Slide1

      case 'contents':

        return template5Slide2

      case 'transition':

        return template5Slide3

      case 'content':

        return template5Slide4

      case 'end':

        return template5Slide5

      default:

        return template5Images[0]

    }

  }



  // 对于红色和蓝色风格，所有页面都使用对应的背景图片

  return backgroundImage

}



// 更新动态模板缩略图

const updateDynamicTemplateThumbnails = () => {

  if (!generatedPPT.value || generatedPPT.value.length === 0) {

    // 即使没有PPT数据，也要根据风格更新缩略图

    if (selectedTemplateStyle.value === 'redElegant' || selectedTemplateStyle.value === 'red') {

      // 使用模板7的图片

      slideImages.value = [

        template7Slide1,

        template7Slide2,

        template7Slide3,

        template7Slide4,

        template7Slide5

      ]

    } else if (selectedTemplateStyle.value === 'blueTech') {

      // 使用模板8的图片

      slideImages.value = [

        template8Slide1,

        template8Slide2,

        template8Slide3,

        template8Slide4,

        template8Slide5

      ]

    } else {

      slideImages.value = [

        template5Slide1,

        template5Slide2,

        template5Slide3,

        template5Slide4,

        template5Slide5

      ]

    }

    // 强制Vue更新 - 使用数组重新赋值触发响应式

    slideImages.value = [...slideImages.value]

    console.log('动态模板内容为空，使用风格化图片，共', slideImages.value.length, '张')

    console.log('当前风格:', selectedTemplateStyle.value)

    console.log('更新后的缩略图数组:', slideImages.value)

    return

  }



  // 根据动态模板内容生成缩略图

  slideImages.value = generatedPPT.value.map((slide, index) => {

    // 如果是红色主题，使用模板7图片

    if (selectedTemplateStyle.value === 'redElegant' || selectedTemplateStyle.value === 'red') {

      const template7Images = [

        template7Slide1, // 第1页 - 封面

        template7Slide2, // 第2页 - 目录

        template7Slide3, // 第3页 - 过渡

        template7Slide4, // 第4页 - 内容

        template7Slide5  // 第5页 - 结束

      ]



      // 直接根据索引选择模板7图片，确保每张都不同

      console.log(`动态模板幻灯片 ${index + 1} 类型: ${slide.type || 'content'}, ID: ${slide.id}`)



      // 直接使用索引来选择图片，确保每张都不同

      return template7Images[index % 5]

    }



    // 如果是蓝色科技主题，使用模板8图片

    if (selectedTemplateStyle.value === 'blueTech') {

      const template8Images = [

        template8Slide1, // 第1页 - 封面

        template8Slide2, // 第2页 - 目录

        template8Slide3, // 第3页 - 过渡

        template8Slide4, // 第4页 - 内容

        template8Slide5  // 第5页 - 结束

      ]



      // 直接根据索引选择模板8图片，确保每张都不同

      console.log(`动态模板幻灯片 ${index + 1} 类型: ${slide.type || 'content'}, ID: ${slide.id}`)



      // 直接使用索引来选择图片，确保每张都不同

      return template8Images[index % 5]

    }



    return generateThumbnailForStyle(slide.type, selectedTemplateStyle.value)

  })



  // 强制Vue更新 - 使用数组重新赋值触发响应式

  slideImages.value = [...slideImages.value]

  console.log('动态模板缩略图更新完成，共', slideImages.value.length, '页')

}



const selectTemplate = async (index) => {

  selectedTemplate.value = index

  const selectedTemplateData = templateStyles.value[index]

  console.log('选择模板:', selectedTemplateData.title)



  // 设置模板风格

  if (selectedTemplateData.style) {

    selectedTemplateStyle.value = selectedTemplateData.style

    console.log('设置模板风格:', selectedTemplateData.style)

  }



  // 检查是否为动态模板

  if (selectedTemplateData.type === 'dynamic') {

    isDynamicTemplate.value = true



    // 立即更新缩略图以显示风格变化

    if (generatedPPT.value && generatedPPT.value.length > 0) {

      updateDynamicTemplateThumbnails()

      console.log('缩略图已更新为', selectedTemplateData.style, '风格')

    } else {

      // 即使没有生成的PPT，也要更新缩略图显示风格

      console.log('准备更新缩略图，当前风格:', selectedTemplateStyle.value)

      updateDynamicTemplateThumbnails()

      console.log('缩略图已更新为', selectedTemplateData.style, '风格（无PPT数据）')

      console.log('最终缩略图数组:', slideImages.value)

    }



    // 强制触发Vue响应式更新

    nextTick(() => {

      slideImages.value = [...slideImages.value]

      console.log('强制更新缩略图完成')

    })



    // 如果有大纲数据，生成预览

    if (outlineData.value && outlineData.value.length > 0) {

      try {

        const previewResult = previewOutlineMatch(outlineData.value, outlineTitle.value)

        if (previewResult.success) {

          templatePreview.value = previewResult.preview

          templateAnalysis.value = previewResult.preview

          console.log('动态模板预览生成成功:', templatePreview.value)

        } else {

          console.error('动态模板预览生成失败:', previewResult.error)

          ElMessage.error('动态模板预览生成失败: ' + previewResult.error)

        }

      } catch (error) {

        console.error('动态模板预览生成异常:', error)

        ElMessage.error('动态模板预览生成异常: ' + error.message)

      }

    }

  } else {

    isDynamicTemplate.value = false

    templatePreview.value = null

    templateAnalysis.value = null



    // 处理静态模板（如Template7）

    if (selectedTemplateData.type === 'static' && selectedTemplateData.templateData) {

      console.log('选择静态模板:', selectedTemplateData.title, '等待用户点击应用模板')



      // 立即更新缩略图以显示风格变化（与动态模板保持一致）

      console.log('准备更新静态模板缩略图，当前风格:', selectedTemplateStyle.value)

      updateSlideThumbnails()

      console.log('静态模板缩略图已更新为', selectedTemplateData.style, '风格')



      // 为Template7生成预览信息，就像动态模板一样

      if (outlineData.value && outlineData.value.length > 0) {

        try {

          const previewResult = previewOutlineMatch(outlineData.value, outlineTitle.value)

          if (previewResult.success) {

            templatePreview.value = previewResult.preview

            templateAnalysis.value = previewResult.preview

            console.log('Template7预览生成成功:', templatePreview.value)

          } else {

            console.error('Template7预览生成失败:', previewResult.error)

          }

        } catch (error) {

          console.error('Template7预览生成异常:', error)

        }

      }

    }

  }

}



const applyTemplate = async () => {

  console.log('应用模板:', templateStyles.value[selectedTemplate.value].title)



  // 设置应用模板状态

  isApplyingTemplate.value = true

  isProcessing.value = true



  try {

    // 检查是否为动态模板

    if (isDynamicTemplate.value) {

      console.log('使用动态模板生成PPT...')



      let testOutline, testTitle, testDescription



      // 根据用户选择决定使用哪个数据源

      if (outlineData.value && outlineData.value.length > 0) {

        // 使用用户大纲数据，并转换为兼容格式

        testOutline = await convertUserOutlineToCompatibleFormat(outlineData.value)

        testTitle = outlineTitle.value || '用户生成的大纲' // 使用大纲标题作为PPT标题

        testDescription = `${testOutline.length}章节结构演示`

        console.log('使用用户大纲数据:', testTitle, `共${testOutline.length}个章节`)

        console.log('转换后的用户大纲数据结构:', JSON.stringify(testOutline, null, 2))

      } else if (selectedMockData.value !== null && selectedMockData.value !== undefined) {

        // 使用选中的mock数据

        const selectedOption = mockDataOptions.value[selectedMockData.value]

        testOutline = selectedOption.data

        testTitle = selectedOption.title

        testDescription = selectedOption.description

        console.log('使用mock数据:', selectedOption.title, selectedOption.description)

      } else {

        // 使用默认测试数据

        testOutline = mockDataOptions.value[0].data

        testTitle = mockDataOptions.value[0].title

        testDescription = mockDataOptions.value[0].description

        console.log('使用默认测试数据:', mockDataOptions.value[0].title)

      }



      // 生成动态模板

      const result = await matchOutlineAndGeneratePPT(testOutline, testTitle, selectedTemplateStyle.value)

      console.log('生成的动态模板结果:', result)



      if (!result.success) {

        throw new Error(result.error || '动态模板生成失败')

      }



      // 获取模板数据

      const dynamicTemplate = result.data.template

      console.log('提取的模板数据:', dynamicTemplate)



      // 验证模板数据格式

      if (!Array.isArray(dynamicTemplate)) {

        console.error('模板数据不是数组格式:', typeof dynamicTemplate, dynamicTemplate)

        throw new Error('模板数据格式不正确，期望数组但得到: ' + typeof dynamicTemplate)

      }



      // 启用预览模式

      showDownloadOptions.value = true

      currentPPTSlideIndex.value = 0

      isPPTPreviewMode.value = true



      console.log('已启用PPT预览模式，开始逐页生成效果...')



      // 实现逐页生成效果

      await generatePPTWithAnimation(dynamicTemplate, testOutline, testTitle)



      console.log('动态模板应用完成，共', generatedPPT.value.length, '张幻灯片')



      // 显示警告和建议

      if (result.warnings && result.warnings.length > 0) {

        ElMessage.warning('模板应用成功，但有以下建议: ' + result.warnings.join(', '))

      }



      if (result.recommendations && result.recommendations.length > 0) {

        console.log('优化建议:', result.recommendations)

      }



      // 更新缩略图

      updateDynamicTemplateThumbnails()



      // 显示成功消息

      const successMessage = outlineData.value && outlineData.value.length > 0 ?

        `动态模板应用完成！使用用户大纲数据 (${testOutline.length}个章节)` :

        `动态模板应用完成！使用数据: ${testTitle} (${testDescription})`

      ElMessage.success(successMessage)



      // 保存PPT数据到本地存储

      if (generatedPPT.value && generatedPPT.value.length > 0) {

        localStorage.setItem('generatedPPT', JSON.stringify(generatedPPT.value))

        console.log('动态模板PPT数据已保存到本地存储:', generatedPPT.value.length, '张幻灯片')

      }



      // 显示下载选项

      showDownloadOptions.value = true



      // 保存步骤信息到后端

      await saveStepToBackend(true, true) // 强制保存新生成的PPT



      // 更新历史记录状态

      await getHistoryRecordList()



      // 重新设置当前历史记录的选中状态

      historyData.value.forEach((item) => {

        item.isActive = item.id === ai_conversation_id.value

      })



      // 确保缩略图条滚动到第一页

      nextTick(() => {

        const thumbnailStripEl = thumbnailStrip.value

        if (thumbnailStripEl) {

          thumbnailStripEl.scrollLeft = 0

        }

      })



    } else {

      console.log('使用静态模板生成PPT...')



      // 检查是否为Template7静态模板

      const selectedTemplateData = templateStyles.value[selectedTemplate.value]

      console.log('检查模板数据:', {

        title: selectedTemplateData.title,

        type: selectedTemplateData.type,

        hasTemplateData: !!selectedTemplateData.templateData,

        templateDataLength: selectedTemplateData.templateData?.length

      })



      if (selectedTemplateData.type === 'static' && selectedTemplateData.templateData) {

        // 检查是否为红色主题模板

        if (selectedTemplateData.title === '红色主题PPT') {

          console.log('应用Template7红色主题模板:', selectedTemplateData.title)



          try {

            // 使用我们精心设计的template_7.json结构，但支持动态多页生成和动画效果

            if (outlineData.value && outlineData.value.length > 0) {

              console.log('开始基于template_7.json生成动态多页结构...')



              // 显示生成进度

              isGeneratingTrainingMaterial.value = true



              // 生成红色主题模板数据

              const redThemeTemplate = generateDynamicTemplate7FromStatic(outlineData.value, outlineTitle.value)



              // 启用PPT预览模式 - 与通用类PPT保持一致

              showDownloadOptions.value = true

              currentPPTSlideIndex.value = 0

              isPPTPreviewMode.value = true



              console.log('已启用PPT预览模式，开始逐页生成效果...')



              // 实现逐页生成效果（与通用类PPT相同的动画逻辑）

              await generatePPTWithAnimation(redThemeTemplate, outlineData.value, outlineTitle.value)



              isGeneratingTrainingMaterial.value = false

              console.log('Template7动态多页结构生成完成，共', generatedPPT.value.length, '页')

            } else {

              // 如果没有大纲数据，使用默认的5页结构

              const defaultTemplate = JSON.parse(JSON.stringify(selectedTemplateData.templateData))



              // 启用PPT预览模式 - 与通用类PPT保持一致

              showDownloadOptions.value = true

              currentPPTSlideIndex.value = 0

              isPPTPreviewMode.value = true



              console.log('已启用PPT预览模式，开始逐页生成效果...')



              // 实现逐页生成效果

              await generatePPTWithAnimation(defaultTemplate, [], outlineTitle.value || '默认标题')



              console.log('Template7默认模板加载成功，共', generatedPPT.value.length, '页')

            }



            // 更新缩略图 - 与通用类PPT保持一致的处理流程

            updateDynamicTemplateThumbnails()



            // 显示成功消息

            const successMessage = outlineData.value && outlineData.value.length > 0 ?

              `红色主题模板应用完成！使用用户大纲数据 (${outlineData.value.length}个章节)` :

              `红色主题模板应用完成！使用默认内容`

            ElMessage.success(successMessage)



            // 保存PPT数据到本地存储

            if (generatedPPT.value && generatedPPT.value.length > 0) {

              localStorage.setItem('generatedPPT', JSON.stringify(generatedPPT.value))

              console.log('Template7静态模板PPT数据已保存到本地存储:', generatedPPT.value.length, '张幻灯片')

            }



            // 显示下载选项

            showDownloadOptions.value = true

            currentPPTSlideIndex.value = 0



            // 保存步骤信息到后端

            await saveStepToBackend(true, true) // 强制保存新生成的PPT



            // 更新历史记录状态

            await getHistoryRecordList()



            // 重新设置当前历史记录的选中状态

            historyData.value.forEach((item) => {

              item.isActive = item.id === ai_conversation_id.value

            })



            // 确保缩略图条滚动到第一页

            nextTick(() => {

              const thumbnailStripEl = thumbnailStrip.value

              if (thumbnailStripEl) {

                thumbnailStripEl.scrollLeft = 0

              }

            })



          } catch (error) {

            console.error('应用Template7静态模板失败:', error)

            ElMessage.error('应用Template7静态模板失败: ' + error.message)

          }

        }



        // 检查是否为蓝色科技主题模板

        if (selectedTemplateData.title === '蓝色科技主题PPT') {

          console.log('应用Template8蓝色科技主题模板:', selectedTemplateData.title)



          try {

            // 使用我们精心设计的template_8.json结构，但支持动态多页生成和动画效果

            if (outlineData.value && outlineData.value.length > 0) {

              console.log('开始基于template_8.json生成动态多页结构...')



              // 显示生成进度

              isGeneratingTrainingMaterial.value = true



              // 生成蓝色科技主题模板数据

              const blueTechThemeTemplate = generateDynamicTemplate8FromStatic(outlineData.value, outlineTitle.value)



              // 启用PPT预览模式 - 与通用类PPT保持一致

              showDownloadOptions.value = true

              currentPPTSlideIndex.value = 0

              isPPTPreviewMode.value = true



              console.log('已启用PPT预览模式，开始逐页生成效果...')



              // 实现逐页生成效果（与通用类PPT相同的动画逻辑）

              await generatePPTWithAnimation(blueTechThemeTemplate, outlineData.value, outlineTitle.value)



              isGeneratingTrainingMaterial.value = false

              console.log('Template8动态多页结构生成完成，共', generatedPPT.value.length, '页')

            } else {

              // 如果没有大纲数据，使用默认的5页结构

              const defaultTemplate = JSON.parse(JSON.stringify(selectedTemplateData.templateData))



              // 启用PPT预览模式 - 与通用类PPT保持一致

              showDownloadOptions.value = true

              currentPPTSlideIndex.value = 0

              isPPTPreviewMode.value = true



              console.log('已启用PPT预览模式，开始逐页生成效果...')



              // 实现逐页生成效果

              await generatePPTWithAnimation(defaultTemplate, [], outlineTitle.value || '默认标题')



              console.log('Template8默认模板加载成功，共', generatedPPT.value.length, '页')

            }



            // 更新缩略图 - 与通用类PPT保持一致的处理流程

            updateDynamicTemplateThumbnails()



            // 显示成功消息

            const successMessage = outlineData.value && outlineData.value.length > 0 ?

              `蓝色科技主题模板应用完成！使用用户大纲数据 (${outlineData.value.length}个章节)` :

              `蓝色科技主题模板应用完成！使用默认内容`

            ElMessage.success(successMessage)



            // 保存PPT数据到本地存储

            if (generatedPPT.value && generatedPPT.value.length > 0) {

              localStorage.setItem('generatedPPT', JSON.stringify(generatedPPT.value))

              console.log('Template8静态模板PPT数据已保存到本地存储:', generatedPPT.value.length, '张幻灯片')

            }



          } catch (error) {

            isGeneratingTrainingMaterial.value = false

            console.error('应用Template8静态模板失败:', error)

            ElMessage.error('应用Template8静态模板失败: ' + error.message)

          }

        }



      } else if (outlineData.value && outlineData.value.length > 0) {

        // 原有的静态模板逻辑

        console.log('开始生成PPT数据...')



        // 第一步：转换大纲为AIPPT格式（包含空内容）

        const aipptData = convertOutlineToAIPPT(outlineData.value, outlineTitle.value)

        console.log('生成的AIPPT数据:', aipptData)



        // 第二步：直接让AI填充AIPPT格式的数据

        const filledAIPPTData = await fillAIPPTContent(aipptData, outlineTitle.value)

        console.log('AI填充后的AIPPT数据:', filledAIPPTData)



        // 第三步：直接使用填充后的数据（需要保存步骤）

        await loadAIPPTData(filledAIPPTData, true)

        console.log('模板应用完成，PPT数据已加载')



        // 等待数据完全处理完成

        await nextTick()



        // 确保数据被正确保存到本地存储

        if (generatedPPT.value && generatedPPT.value.length > 0) {

          localStorage.setItem('generatedPPT', JSON.stringify(generatedPPT.value))

          console.log('PPT数据已保存到本地存储:', generatedPPT.value.length, '张幻灯片')

        }



        // 显示成功消息

        ElMessage.success('模板应用完成，内容已填充！')



        // 更新历史记录状态

        await getHistoryRecordList()



        // 重新设置当前历史记录的选中状态

        historyData.value.forEach((item) => {

          item.isActive = item.id === ai_conversation_id.value

        })



        // 显示下载选项（模板和大纲结合页面）

        showDownloadOptions.value = true



        // 确保缩略图条滚动到第一页

        nextTick(() => {

          const thumbnailStripEl = thumbnailStrip.value

          if (thumbnailStripEl) {

            thumbnailStripEl.scrollLeft = 0

            console.log('缩略图条已滚动到第一页')

          }

        })

      } else {

        // 如果没有大纲数据，直接加载默认数据

        await loadAIPPTData()

        ElMessage.success('模板应用完成！')

        showDownloadOptions.value = true

      }

    }

  } catch (error) {

    console.error('应用模板失败:', error)

    ElMessage.error('应用模板失败: ' + error.message)

  } finally {

    // 重置应用模板状态

    isApplyingTemplate.value = false

    isProcessing.value = false

  }

}



// 填充静态模板内容函数

const fillStaticTemplateContent = async (pptData, outlineData, title) => {

  console.log('开始填充静态模板内容:', { title, chapters: outlineData.length })



  try {

    // 处理封面页

    const coverSlide = pptData.find(slide => slide.type === 'cover')

    if (coverSlide) {

      // 填充标题

      const titleElement = coverSlide.elements.find(el => el.id === 'title-text')

      if (titleElement) {

        titleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: #ffffff; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">${title}</span></strong></p>`

        console.log('封面标题已填充:', title)

      }



      // 填充副标题

      const subtitleElement = coverSlide.elements.find(el => el.id === 'subtitle-text')

      if (subtitleElement) {

        const subtitle = outlineData.length > 0 ? `${outlineData.length}个章节的详细内容` : '详细内容介绍'

        subtitleElement.content = `<p style="text-align: center;"><span style="font-size: 18px; color: rgba(255,255,255,0.9);">${subtitle}</span></p>`

        console.log('封面副标题已填充:', subtitle)

      }

    }



    // 处理目录页

    const contentsSlide = pptData.find(slide => slide.type === 'contents')

    if (contentsSlide && outlineData.length > 0) {

      // 填充目录项

      for (let i = 0; i < Math.min(outlineData.length, 6); i++) {

        const chapter = outlineData[i]

        const itemElement = contentsSlide.elements.find(el => el.id === `item-${i + 1}`)

        if (itemElement) {

          itemElement.content = `<p><span style="font-size: 18px; color: #333333;">${chapter.title}</span></p>`

          console.log(`目录项${i + 1}已填充:`, chapter.title)

        }

      }



      // 隐藏多余的目录项

      for (let i = outlineData.length; i < 6; i++) {

        const itemElement = contentsSlide.elements.find(el => el.id === `item-${i + 1}`)

        if (itemElement) {

          itemElement.content = `<p><span style="font-size: 18px; color: #333333;"></span></p>`

        }

      }

    }



    // 处理过渡页和内容页

    let slideIndex = 0

    for (let chapterIndex = 0; chapterIndex < outlineData.length; chapterIndex++) {

      const chapter = outlineData[chapterIndex]



      // 查找过渡页

      const transitionSlide = pptData.find(slide => slide.type === 'transition' && slideIndex === 0)

      if (transitionSlide) {

        // 填充过渡页标题

        const transitionTitleElement = transitionSlide.elements.find(el => el.id === 'transition-title')

        if (transitionTitleElement) {

          transitionTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: #e74c3c;">${chapter.title}</span></strong></p>`

          console.log('过渡页标题已填充:', chapter.title)

        }



        // 填充过渡页内容（使用AI生成）

        const transitionContentElement = transitionSlide.elements.find(el => el.id === 'transition-content')

        if (transitionContentElement) {

          try {

            console.log(`🤖 正在为红色PPT过渡页生成章节介绍内容: ${chapter.title}`)



            // 调用AI API生成章节介绍内容

            const aiResponse = await apis.reProduceSingleQuestion({

              message: `请为PPT章节"${chapter.title}"生成一个简洁的章节介绍内容。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.控制在20-30字以内 5.不要包含任何编号`

            })



            if (aiResponse && aiResponse.data) {

              const aiContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

              console.log(`✅ 红色PPT过渡页章节介绍生成完成: ${aiContent}`)



              transitionContentElement.content = `<p style="text-align: center;"><span style="font-size: 16px; color: #666666;">${aiContent}</span></p>`

            } else {

              // AI调用失败，使用备用内容

              const fallbackContent = chapter.sections && chapter.sections.length > 0

                ? `本章将介绍${chapter.title}的相关内容`

                : `${chapter.title}的详细说明`

              transitionContentElement.content = `<p style="text-align: center;"><span style="font-size: 16px; color: #666666;">${fallbackContent}</span></p>`

              console.log(`🔄 使用备用内容: ${fallbackContent}`)

            }

          } catch (aiError) {

            console.error(`❌ AI生成红色PPT过渡页内容失败:`, aiError)

            // AI生成失败，使用备用内容

            const fallbackContent = chapter.sections && chapter.sections.length > 0

              ? `本章将介绍${chapter.title}的相关内容`

              : `${chapter.title}的详细说明`

            transitionContentElement.content = `<p style="text-align: center;"><span style="font-size: 16px; color: #666666;">${fallbackContent}</span></p>`

            console.log(`🔄 使用备用内容: ${fallbackContent}`)

          }

        }

        slideIndex++

      }



      // 查找内容页

      const contentSlide = pptData.find(slide => slide.type === 'content' && slideIndex === 0)

      if (contentSlide && chapter.sections && chapter.sections.length > 0) {

        // 填充内容页标题

        const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

        if (contentTitleElement) {

          contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #e74c3c;">${chapter.title}</span></strong></p>`

          console.log('内容页标题已填充:', chapter.title)

        }



        // 填充内容项

        const sections = chapter.sections.slice(0, 3) // 最多显示3个要点

        for (let sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {

          const section = sections[sectionIndex]



          // 填充要点标题（使用AI生成）

          const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${sectionIndex + 1}`)

          if (itemTitleElement) {

            try {

              console.log(`🤖 正在为红色PPT生成要点标题: ${section.title}`)



              // 使用getTitleForElement函数获取正确的标题，与通用类PPT保持一致

              const titleForElement = getTitleForElement(outlineData, slideIndex, sectionIndex)



              // 检查是否需要AI生成标题

              if (titleForElement.includes('待AI生成子小节')) {

                // 需要AI生成子小节标题

                console.log(`🤖 正在为子小节生成标题: ${titleForElement}`)



                // 获取小节标题用于AI生成

                const sectionTitle = getSectionTitleForAI(outlineData, slideIndex, sectionIndex)



                // 根据不同的标记生成不同的提示词

                let prompt = ''

                if (titleForElement.includes('待AI生成子小节1')) {

                  prompt = `请为PPT幻灯片的小节"${sectionTitle}"生成第一个专业的子小节标题。要求：1.标题简洁明了 2.专业准确 3.适合PPT展示 4.控制在8-15字以内 5.不要包含任何编号 6.直接返回标题`

                } else if (titleForElement.includes('待AI生成子小节2')) {

                  prompt = `请为PPT幻灯片的小节"${sectionTitle}"生成第二个专业的子小节标题。要求：1.标题简洁明了 2.专业准确 3.适合PPT展示 4.控制在8-15字以内 5.不要包含任何编号 6.直接返回标题`

                } else if (titleForElement.includes('待AI生成子小节3')) {

                  prompt = `请为PPT幻灯片的小节"${sectionTitle}"生成第三个专业的子小节标题。要求：1.标题简洁明了 2.专业准确 3.适合PPT展示 4.控制在8-15字以内 5.不要包含任何编号 6.直接返回标题`

                } else if (titleForElement.includes('待AI生成子小节4')) {

                  prompt = `请为PPT幻灯片的小节"${sectionTitle}"生成第四个专业的子小节标题。要求：1.标题简洁明了 2.专业准确 3.适合PPT展示 4.控制在8-15字以内 5.不要包含任何编号 6.直接返回标题`

                } else {

                  prompt = `请为PPT幻灯片的小节"${sectionTitle}"生成一个专业的子小节标题。要求：1.标题简洁明了 2.专业准确 3.适合PPT展示 4.控制在8-15字以内 5.不要包含任何编号 6.直接返回标题`

                }



                // 调用AI生成单个子小节标题（为当前元素生成）

                const aiResponse = await apis.reProduceSingleQuestion({

                  message: prompt

                })



                if (aiResponse && aiResponse.data) {

                  const aiTitle = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

                  console.log(`✅ 红色PPT子小节标题生成完成: ${aiTitle}`)



                  itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${aiTitle}</span></strong></p>`

                } else {

                  // AI调用失败，使用备用标题

                  itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${section.title}</span></strong></p>`

                  console.log(`🔄 使用备用标题: ${section.title}`)

                }

              } else {

                // 直接使用获取到的标题

                itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${titleForElement}</span></strong></p>`

                console.log(`✅ 使用现有标题: ${titleForElement}`)

              }

            } catch (aiError) {

              console.error(`❌ AI生成红色PPT要点标题失败:`, aiError)

              // AI生成失败，使用备用标题

              itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${section.title}</span></strong></p>`

              console.log(`🔄 使用备用标题: ${section.title}`)

            }

          }



          // 填充要点内容（使用AI生成）

          const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${sectionIndex + 1}`)

          if (itemContentElement) {

            try {

              console.log(`🤖 正在为红色PPT生成要点内容: ${section.title}`)



              // 使用getTitleForAIContent函数获取正确的内容标题，与通用类PPT保持一致

              const titleForAI = getTitleForAIContent(outlineData, slideIndex, sectionIndex)



              // 调用AI API生成要点内容

              const aiResponse = await apis.reProduceSingleQuestion({

                message: `请为PPT幻灯片生成专业的内容，主题是：${titleForAI}。要求：1.内容专业准确 2.语言简洁明了 3.适合PPT展示 4.严格控制字数在30-45字以内 5.不要包含任何编号（如"子小节3："、"要点1："等）6.直接返回内容，不要添加前缀 7.要有独特性和创新性，避免与其他内容重复 8.从不同角度阐述主题。这是关于"${outlineTitle.value}"的PPT演示文稿，当前章节是"${titleForAI}"`

              })



              if (aiResponse && aiResponse.data) {

                const aiContent = aiResponse.data.reply || aiResponse.data.content || aiResponse.data.message || aiResponse.data

                console.log(`✅ 红色PPT要点内容生成完成: ${aiContent}`)



                itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${aiContent}</span></p>`

              } else {

                // AI调用失败，使用备用内容

                const fallbackContent = section.subsections && section.subsections.length > 0

                  ? section.subsections.map(sub => sub.title).join('、')

                  : section.content || '详细说明内容'

                itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${fallbackContent}</span></p>`

                console.log(`🔄 使用备用内容: ${fallbackContent}`)

              }

            } catch (aiError) {

              console.error(`❌ AI生成红色PPT要点内容失败:`, aiError)

              // AI生成失败，使用备用内容

              const fallbackContent = section.subsections && section.subsections.length > 0

                ? section.subsections.map(sub => sub.title).join('、')

                : section.content || '详细说明内容'

              itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${fallbackContent}</span></p>`

              console.log(`🔄 使用备用内容: ${fallbackContent}`)

            }

          }

        }



        // 隐藏多余的要点

        for (let sectionIndex = sections.length; sectionIndex < 3; sectionIndex++) {

          const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${sectionIndex + 1}`)

          const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${sectionIndex + 1}`)

          if (itemTitleElement) {

            itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;"></span></strong></p>`

          }

          if (itemContentElement) {

            itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;"></span></p>`

          }

        }

        slideIndex++

      }

    }



    // 处理结束页

    const endSlide = pptData.find(slide => slide.type === 'end')

    if (endSlide) {

      const endTitleElement = endSlide.elements.find(el => el.id === 'end-title')

      if (endTitleElement) {

        endTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: #ffffff; text-shadow: 2px 2px 6px rgba(0,0,0,0.3);">谢谢聆听</span></strong></p>`

        console.log('结束页标题已填充')

      }



      const endSubtitleElement = endSlide.elements.find(el => el.id === 'end-subtitle')

      if (endSubtitleElement) {

        endSubtitleElement.content = `<p style="text-align: center;"><span style="font-size: 18px; color: rgba(255,255,255,0.9);">感谢您的时间与关注</span></p>`

        console.log('结束页副标题已填充')

      }

    }



    console.log('静态模板内容填充完成')



  } catch (error) {

    console.error('填充静态模板内容失败:', error)

    throw error

  }

}



// 切换预览模式

const togglePreviewMode = () => {

  previewMode.value = previewMode.value === 'edit' ? 'preview' : 'edit'

  console.log('切换预览模式:', previewMode.value)



  if (previewMode.value === 'preview') {

    ElMessage.success('已切换到预览模式')

  } else {

    ElMessage.success('已切换到编辑模式')

  }

}



// PPT预览相关方法

const getCurrentPPTSlide = () => {

  const slide = generatedPPT.value[currentPPTSlideIndex.value] || null

  console.log('获取当前PPT幻灯片:', slide)

  console.log('当前索引:', currentPPTSlideIndex.value)

  console.log('总幻灯片数:', generatedPPT.value.length)

  return slide

}



// 获取大图显示的内容（在动画过程中显示"正在生成..."）

const getCurrentPPTSlideForMainView = () => {

  const slide = generatedPPT.value[currentPPTSlideIndex.value] || null

  if (!slide) return null



  // 如果正在生成中，显示"正在生成..."的内容

  if (isGeneratingTrainingMaterial.value && slide.elements) {

    const generatingSlide = JSON.parse(JSON.stringify(slide))

    generatingSlide.elements = generatingSlide.elements.map(el => ({

      ...el,

      content: (el.textType === 'itemContent' && el.content && el.content.includes('待AI填充')) ?

        `<p style="text-align: center;"><span style="font-size: 16px; color: ${el.defaultColor};">正在生成...</span></p>` :

        el.content

    }))

    return generatingSlide

  }



  return slide

}



const getCurrentPPTSlideBackground = () => {

  const slide = getCurrentPPTSlideForMainView()

  if (!slide) return '#ffffff'



  if (slide.background?.type === 'gradient') {

    const gradient = slide.background.gradient

    if (gradient.type === 'linear') {

      const colors = gradient.colors.map(c => `${c.color} ${c.pos}%`).join(', ')

      return `linear-gradient(135deg, ${colors})`

    } else if (gradient.type === 'radial') {

      const colors = gradient.colors.map(c => `${c.color} ${c.pos}%`).join(', ')

      return `radial-gradient(circle, ${colors})`

    }

  }



  return slide.background?.color || '#667eea'

}



// 获取幻灯片背景（用于缩略图）

const getSlideBackground = (slide) => {

  if (!slide) return '#667eea'



  // 检查是否有背景设置

  if (slide.background) {

    if (slide.background.type === 'gradient') {

      const gradient = slide.background.gradient

      if (gradient.type === 'linear') {

        const colors = gradient.colors.map(c => `${c.color} ${c.pos}%`).join(', ')

        return `linear-gradient(135deg, ${colors})`

      } else if (gradient.type === 'radial') {

        const colors = gradient.colors.map(c => `${c.color} ${c.pos}%`).join(', ')

        return `radial-gradient(circle, ${colors})`

      }

    } else if (slide.background.color) {

      // 确保白色背景能正确显示

      return slide.background.color

    }

  }



  // 如果没有背景设置，检查是否有白色背景的元素

  if (slide.elements && slide.elements.length > 0) {

    const whiteElement = slide.elements.find(el =>

      el.type === 'shape' && el.fill === '#ffffff' ||

      el.type === 'shape' && el.fill === 'white'

    )

    if (whiteElement) {

      return '#ffffff'

    }

  }



  return '#667eea'

}



// 获取缩略图元素样式

const getThumbnailElementStyle = (element) => {

  // 获取当前根字体大小，用于响应amfe-flexible缩放

  const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)

  const baseFontSize = 192 // amfe-flexible的基础字体大小 (1920px设计稿 / 10)

  const scaleFactor = rootFontSize / baseFontSize



  // 使用响应式的缩放比例计算，与主图保持一致

  const scaleX = (1105 * scaleFactor) / 960

  const scaleY = (603 * scaleFactor) / 540



  const style = {

    position: 'absolute',

    left: ((element.left || element.x || 0) * scaleX) + 'px',

    top: ((element.top || element.y || 0) * scaleY) + 'px',

    width: ((element.width || 100) * scaleX) + 'px',

    height: ((element.height || 50) * scaleY) + 'px',

    zIndex: element.zIndex || 1

  }



  // 添加与主图相同的样式属性

  if (element.defaultColor) {

    style.color = element.defaultColor

  }



  if (element.defaultFontName) {

    style.fontFamily = element.defaultFontName

  }



  // 添加透明度支持

  if (element.opacity !== undefined) {

    style.opacity = element.opacity

  }



  // 处理形状元素的背景色

  if (element.type === 'shape' && element.fill) {

    style.backgroundColor = element.fill

    style.borderRadius = element.viewBox ? '8px' : '0'

  }



  // 处理文本元素的对齐方式

  if (element.type === 'text' && element.content) {

    // 从HTML内容中提取text-align样式

    const textAlignMatch = element.content.match(/text-align:\s*([^;]+)/)

    if (textAlignMatch) {

      style.textAlign = textAlignMatch[1].trim()

    }



    // 特殊处理目录项：确保居中显示

    if (element.textType === 'item' && element.content.includes('目录项')) {

      style.textAlign = 'center'

    }

  }



  return style

}



// 获取响应式字体大小

const getResponsiveFontSize = (baseFontSize) => {

  const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)

  const baseRootFontSize = 192 // amfe-flexible的基础字体大小 (1920px设计稿 / 10)

  const scaleFactor = rootFontSize / baseRootFontSize

  return Math.round(baseFontSize * scaleFactor)

}



// 监听窗口大小变化，重新计算PPT元素样式

const handleWindowResize = () => {

  // 强制重新渲染PPT预览

  if (generatedPPT.value.length > 0) {

    // 触发响应式更新

    generatedPPT.value = [...generatedPPT.value]

  }

}



// 添加窗口大小变化监听器

onMounted(() => {

  window.addEventListener('resize', handleWindowResize)



  // 自动启动下载监听（因为默认选中） - 注释掉，因为版本没有步骤四
  // if (isDownloadListenerActive.value) {
  //   startDownloadListener()
  // }

})



onUnmounted(() => {

  window.removeEventListener('resize', handleWindowResize)



  // 清理下载监听插件 - 注释掉，因为版本没有步骤四
  // if (isDownloadListenerActive.value) {
  //   stopDownloadListener()
  // }

})



const getElementStyle = (element) => {

  // 获取当前根字体大小，用于响应amfe-flexible缩放

  const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)

  const baseFontSize = 192 // amfe-flexible的基础字体大小 (1920px设计稿 / 10)

  const scaleFactor = rootFontSize / baseFontSize



  // 使用响应式的目标尺寸，考虑amfe-flexible缩放

  const targetWidth = 1105 * scaleFactor  // slide-preview 的 max-width

  const targetHeight = 603 * scaleFactor  // slide-preview 的 height



  // 原始PPT尺寸 (960x540)

  const originalWidth = 960

  const originalHeight = 540



  // 计算响应式的缩放比例

  const scaleX = targetWidth / originalWidth

  const scaleY = targetHeight / originalHeight



  return {

    position: 'absolute',

    left: ((element.left || element.x) * scaleX) + 'px',

    top: ((element.top || element.y) * scaleY) + 'px',

    width: (element.width * scaleX) + 'px',

    height: (element.height * scaleY) + 'px',

    zIndex: element.zIndex || 1

  }

}



const getElementTypeName = (type) => {

  const typeNames = {

    'text': '文本',

    'image': '图片',

    'shape': '形状'

  }

  return typeNames[type] || type

}



const getShapeStyle = (element) => {

  return {

    width: '100%',

    height: '100%',

    backgroundColor: element.fill,

    borderRadius: element.viewBox ? '8px' : '0',

    opacity: element.opacity || 1

  }

}



const selectPPTElement = (index) => {

  selectedPPTElementIndex.value = index

  console.log('选中PPT元素:', index)

}



const handleDoubleClick = (index) => {

  editingPPTElementIndex.value = index

  const element = getCurrentPPTSlide().elements[index]

  editingPPTHtml.value = element.content

  console.log('开始编辑PPT元素:', index)

}



// 禁用的双击处理函数（PPT预览模式下不允许文字编辑）

const handleDoubleClickDisabled = (index) => {

  console.log('PPT预览模式下不允许文字编辑')

  ElMessage.info('PPT预览模式下不允许编辑文字，只能更换图片')

}



const onPPTInlineInput = (event) => {

  editingPPTHtml.value = event.target.innerHTML

}



const savePPTInlineEdit = (index) => {

  const slide = getCurrentPPTSlide()

  if (slide.elements[index]) {

    slide.elements[index].content = editingPPTHtml.value

    console.log('文本编辑保存成功')

    ElMessage.success('文本编辑已保存')



    // 自动保存修改后的PPT数据

    saveModifiedPPTData()

  }

  editingPPTElementIndex.value = -1

  editingPPTHtml.value = ''

  console.log('保存PPT元素编辑:', index)

}



const startPPTDrag = (event, index) => {

  // 如果是双击事件，不启动拖拽

  if (event.detail >= 2) {

    return

  }



  // 获取当前元素

  const slide = getCurrentPPTSlide()

  const element = slide.elements[index]



  // PPT预览模式下，文字元素不允许拖拽

  if (element.type === 'text') {

    console.log('PPT预览模式下，文字元素不允许拖拽')

    return

  }



  // 如果当前正在编辑文字元素，不启动拖拽

  if (editingPPTElementIndex.value === index && element.type === 'text' && editingPPTHtml.value !== '') {

    return

  }



  if (selectedPPTElementIndex.value !== index) {

    selectPPTElement(index)

  }



  // 开始拖拽

  const startX = event.clientX

  const startY = event.clientY

  const startLeft = element.left

  const startTop = element.top



  const onMouseMove = (moveEvent) => {

    const deltaX = moveEvent.clientX - startX

    const deltaY = moveEvent.clientY - startY



    element.left = startLeft + deltaX

    element.top = startTop + deltaY

  }



  const onMouseUp = () => {

    document.removeEventListener('mousemove', onMouseMove)

    document.removeEventListener('mouseup', onMouseUp)



    // 自动保存修改

    saveModifiedPPTData()

    console.log('拖拽完成，已自动保存')

  }



  document.addEventListener('mousemove', onMouseMove)

  document.addEventListener('mouseup', onMouseUp)

  event.preventDefault()

}



const startPPTResize = (event, index, direction) => {

  if (selectedPPTElementIndex.value !== index) {

    selectPPTElement(index)

  }



  const slide = getCurrentPPTSlide()

  const element = slide.elements[index]



  // PPT预览模式下，文字元素不允许调整大小

  if (element.type === 'text') {

    console.log('PPT预览模式下，文字元素不允许调整大小')

    return

  }



  const startX = event.clientX

  const startY = event.clientY

  const startWidth = element.width

  const startHeight = element.height

  const startLeft = element.left

  const startTop = element.top



  const onMouseMove = (moveEvent) => {

    const deltaX = moveEvent.clientX - startX

    const deltaY = moveEvent.clientY - startY



    switch (direction) {

      case 'se': // 右下角

        element.width = Math.max(20, startWidth + deltaX)

        element.height = Math.max(20, startHeight + deltaY)

        break

      case 'sw': // 左下角

        element.width = Math.max(20, startWidth - deltaX)

        element.height = Math.max(20, startHeight + deltaY)

        element.left = startLeft + (startWidth - element.width)

        break

      case 'ne': // 右上角

        element.width = Math.max(20, startWidth + deltaX)

        element.height = Math.max(20, startHeight - deltaY)

        element.top = startTop + (startHeight - element.height)

        break

      case 'nw': // 左上角

        element.width = Math.max(20, startWidth - deltaX)

        element.height = Math.max(20, startHeight - deltaY)

        element.left = startLeft + (startWidth - element.width)

        element.top = startTop + (startHeight - element.height)

        break

    }

  }



  const onMouseUp = () => {

    document.removeEventListener('mousemove', onMouseMove)

    document.removeEventListener('mouseup', onMouseUp)



    // 自动保存修改

    saveModifiedPPTData()

    console.log('缩放完成，已自动保存')

  }



  document.addEventListener('mousemove', onMouseMove)

  document.addEventListener('mouseup', onMouseUp)

  event.preventDefault()

}



// 将图片文件转换为base64

const convertFileToBase64 = (file) => {

  return new Promise((resolve, reject) => {

    const reader = new FileReader()

    reader.onload = () => {

      const result = reader.result



      // 验证base64数据

      if (!result || typeof result !== 'string') {

        reject(new Error('图片读取结果无效'))

        return

      }



      if (!result.startsWith('data:image/')) {

        reject(new Error('不是有效的图片数据'))

        return

      }



      if (!result.includes(',')) {

        reject(new Error('Base64数据格式错误'))

        return

      }



      const [, data] = result.split(',')

      if (!data || data.length < 100) {

        reject(new Error('Base64数据过短'))

        return

      }



      console.log('图片转换成功:', {

        fileName: file.name,

        fileSize: file.size,

        base64Length: result.length,

        dataLength: data.length

      })



      resolve(result)

    }

    reader.onerror = () => {

      reject(new Error('图片读取失败: ' + reader.error?.message))

    }

    reader.readAsDataURL(file)

  })

}



// 高质量图片转换函数（使用Canvas保持原始质量）

const convertFileToHighQualityBase64 = (file, targetWidth = null, targetHeight = null) => {

  return new Promise((resolve, reject) => {

    const reader = new FileReader()

    reader.onload = () => {

      const img = new Image()

      img.onload = () => {

        try {

          // 创建Canvas

          const canvas = document.createElement('canvas')

          const ctx = canvas.getContext('2d')



          let canvasWidth, canvasHeight



          if (targetWidth && targetHeight) {

            // 如果有目标尺寸，使用cover模式（填满目标区域，超出部分裁剪）

            canvasWidth = targetWidth

            canvasHeight = targetHeight

          } else {

            // 没有目标尺寸，保持原始尺寸

            canvasWidth = img.width

            canvasHeight = img.height

          }



          // 设置Canvas尺寸

          canvas.width = canvasWidth

          canvas.height = canvasHeight



          // 设置高质量渲染

          ctx.imageSmoothingEnabled = true

          ctx.imageSmoothingQuality = 'high'



          // 绘制图片到Canvas，使用cover模式（填满目标区域，超出部分裁剪）

          if (targetWidth && targetHeight) {

            // 计算cover模式的缩放比例和偏移

            const aspectRatio = img.width / img.height

            const targetAspectRatio = targetWidth / targetHeight



            let sourceWidth, sourceHeight, sourceX, sourceY



            if (aspectRatio > targetAspectRatio) {

              // 图片更宽，以高度为准，裁剪左右

              sourceHeight = img.height

              sourceWidth = img.height * targetAspectRatio

              sourceX = (img.width - sourceWidth) / 2

              sourceY = 0

            } else {

              // 图片更高，以宽度为准，裁剪上下

              sourceWidth = img.width

              sourceHeight = img.width / targetAspectRatio

              sourceX = 0

              sourceY = (img.height - sourceHeight) / 2

            }



            // 绘制裁剪后的图片到目标尺寸

            ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, canvasWidth, canvasHeight)

          } else {

            // 没有目标尺寸，直接绘制原始图片

            ctx.drawImage(img, 0, 0)

          }



          // 转换为base64，使用最高质量

          const base64 = canvas.toDataURL('image/png', 1.0) // 使用PNG格式，质量1.0



          console.log('高质量图片转换成功:', {

            fileName: file.name,

            fileSize: file.size,

            originalSize: `${img.width}x${img.height}`,

            canvasSize: `${canvasWidth}x${canvasHeight}`,

            targetSize: targetWidth && targetHeight ? `${targetWidth}x${targetHeight}` : 'none',

            base64Length: base64.length,

            format: 'PNG'

          })



          resolve(base64)

        } catch (error) {

          console.error('Canvas转换失败，使用原始方法:', error)

          // 如果Canvas转换失败，回退到原始方法

          resolve(reader.result)

        }

      }

      img.onerror = () => {

        console.error('图片加载失败，使用原始方法')

        // 如果图片加载失败，回退到原始方法

        resolve(reader.result)

      }

      img.src = reader.result

    }

    reader.onerror = () => {

      reject(new Error('图片读取失败: ' + reader.error?.message))

    }

    reader.readAsDataURL(file)

  })

}



const changePPTImage = async (index) => {

  console.log('更换PPT图片被触发，索引:', index)

  const input = document.createElement('input')

  input.type = 'file'

  input.accept = 'image/*'

  input.onchange = async (event) => {

    const file = event.target.files[0]

    if (file) {

      try {

        // 检查文件大小（限制为5MB）

        const maxSize = 5 * 1024 * 1024 // 5MB

        if (file.size > maxSize) {

          ElMessage.error('图片大小不能超过5MB')

          return

        }



        // 显示处理进度

        ElMessage.info('正在处理图片...')



        // 获取当前图片元素的目标尺寸

        const slide = getCurrentPPTSlide()

        const element = slide.elements[index]

        const targetWidth = element.width

        const targetHeight = element.height



        console.log('图片目标尺寸:', { targetWidth, targetHeight })



        // 将图片转换为高质量base64，保持宽高比

        const base64Data = await convertFileToHighQualityBase64(file, targetWidth, targetHeight)

        console.log('图片转换为高质量base64成功，长度:', base64Data.length)



        // 调试信息：显示base64数据的前100个字符

        console.log('Base64数据预览:', base64Data.substring(0, 100) + '...')



        // 更新PPT元素

        if (element && element.type === 'image') {

          element.src = base64Data

          console.log('图片更换成功，使用base64数据')

          ElMessage.success('图片更换成功')



          // 自动保存修改后的PPT数据

          saveModifiedPPTData()



          // 立即保存到后端（不更新封面图）

          try {

            console.log('开始保存图片更换后的PPT数据到后端...')

            await saveStepToBackend(false) // 传入false，不更新封面图

            console.log('图片更换后的PPT数据已保存到后端')



            // 立即更新历史记录列表中的数据

            const currentHistoryItem = historyData.value.find(item => item.id === ai_conversation_id.value)

            if (currentHistoryItem) {

              currentHistoryItem.ppt_json_content = JSON.stringify(generatedPPT.value)

              console.log('已更新历史记录列表中的PPT数据')

            }

          } catch (error) {

            console.error('保存图片更换后的PPT数据失败:', error)

            ElMessage.warning('图片更换成功，但保存到后端失败')

          }

        }

      } catch (error) {

        console.error('图片处理过程中发生错误:', error)

        ElMessage.error('图片处理失败: ' + error.message)

      }

    }

  }

  input.click()

}



// 上传图片到服务器

const uploadImageToServer = async (file) => {

  try {

    console.log('开始上传图片到服务器...')



    // 创建FormData，后端需要的是 'image' 字段

    const formData = new FormData()

    formData.append('image', file)



    // 使用正确的上传接口

    const response = await apis.uploadImage(formData)

    console.log('图片上传响应:', response)



    if (response.statusCode === 200) {

      console.log('图片上传成功:', response.fileUrl)

      return {

        statusCode: 200,

        fileUrl: response.fileUrl,

        message: '上传成功'

      }

    } else {

      console.error('图片上传失败:', response)

      return {

        statusCode: response.statusCode || 500,

        message: response.message || '上传失败'

      }

    }

  } catch (error) {

    console.error('图片上传过程中发生错误:', error)

    return {

      statusCode: 500,

      message: error.message

    }

  }

}



// 处理图片选择

const handleImageSelect = (event) => {

  const file = event.target.files[0]

  if (!file) return



  // 检查文件类型

  if (!file.type.startsWith('image/')) {

    ElMessage.warning('请选择图片文件')

    event.target.value = ''

    return

  }



  // 检查文件大小（5MB限制）

  const maxSize = 5 * 1024 * 1024

  if (file.size > maxSize) {

    ElMessage.warning('图片大小不能超过5MB')

    event.target.value = ''

    return

  }



  try {

    // 创建本地URL

    const imageUrl = URL.createObjectURL(file)



    // 更新选中的图片元素

    if (selectedImageIndex.value !== null && generatedPPT.value.length > 0) {

      const currentSlide = getCurrentPPTSlide()

      if (currentSlide && currentSlide.elements[selectedImageIndex.value]) {

        currentSlide.elements[selectedImageIndex.value].src = imageUrl

        console.log('图片已更换:', imageUrl)

        ElMessage.success('图片更换成功')

      }

    }



    // 清理选中的图片索引

    selectedImageIndex.value = null



  } catch (error) {

    console.error('图片更换失败:', error)

    ElMessage.error('图片更换失败，请重试')

  } finally {

    event.target.value = ''

  }

}







// 将大纲数据转换为AIPPT.json格式

const convertOutlineToAIPPT = (outlineData, outlineTitle) => {

  try {

    console.log('开始转换大纲为AIPPT.json格式')

    console.log('输入大纲数据:', outlineData)

    console.log('标题:', outlineTitle)



    const aipptSlides = []



    // 1. 添加封面页

    aipptSlides.push({

      type: 'cover',

      data: {

        title: outlineTitle || '安全培训大纲',

        text: '基于AI生成的培训大纲，包含相关内容'

      }

    })

    console.log('已添加封面页')



    // 2. 生成目录项

    const tocItems = outlineData.map((chapter, index) => chapter.title)

    console.log('目录项:', tocItems)



    // 添加目录页

    aipptSlides.push({

      type: 'contents',

      data: {

        items: tocItems

      }

    })

    console.log('已添加目录页')



    // 3. 遍历章节生成幻灯片

    outlineData.forEach((chapter, chapterIndex) => {

      console.log(`处理章节 ${chapterIndex + 1}:`, chapter.title)

      console.log(`章节内容:`, chapter)



      // 添加章节过渡页

      aipptSlides.push({

        type: 'transition',

        data: {

          title: chapter.title,

          text: chapter.content || `本章将介绍${chapter.title}的相关内容`

        }

      })

      console.log(`已添加章节 ${chapterIndex + 1} 的过渡页`)



      // 遍历小节，每个小节生成一个内容页（恢复昨天的逻辑）

      if (chapter.sections && chapter.sections.length > 0) {

        chapter.sections.forEach((section, sectionIndex) => {

          console.log(`处理小节 ${sectionIndex + 1}:`, section.title)

          console.log(`小节内容:`, section)



          // 添加内容页

          let contentItems = []



          // 添加小节内容（保持昨天的逻辑，让AI填充）

          if (section.subsections && section.subsections.length > 0) {

            section.subsections.forEach((subsection, subsectionIndex) => {

              contentItems.push({

                title: '',

                text: ''

              })

            })

          } else {

            // 如果没有子小节，使用小节的内容

            contentItems.push({

              title: '',

              text: ''

            })

          }



          // 确保每个content页面至少有4个items（参考AIPPT.json格式）

          while (contentItems.length < 4) {

            // 添加空的items，让AI填充

            contentItems.push({

              title: '',

              text: ''

            })

          }



          // 限制最多4个items（与AIPPT.json保持一致）

          contentItems = contentItems.slice(0, 4)



          aipptSlides.push({

            type: 'content',

            data: {

              title: section.title,

              items: contentItems

            }

          })

          console.log(`已添加小节 ${sectionIndex + 1} 的内容页`)

        })

      } else {

        // 如果章节没有小节，添加空的内容页

        const emptyContentItems = [

          {

            title: '',

            text: ''

          },

          {

            title: '',

            text: ''

          },

          {

            title: '',

            text: ''

          },

          {

            title: '',

            text: ''

          }

        ]



        aipptSlides.push({

          type: 'content',

          data: {

            title: chapter.title,

            items: emptyContentItems

          }

        })

        console.log(`已添加章节 ${chapterIndex + 1} 的空内容页`)

      }

    })



    // 添加结束页

    aipptSlides.push({

      type: 'end'

    })



    console.log('转换完成，生成的AIPPT.json格式数据:')

    console.log(JSON.stringify(aipptSlides, null, 2))



    // 统计各类型幻灯片数量

    const slideTypes = {}

    aipptSlides.forEach(slide => {

      slideTypes[slide.type] = (slideTypes[slide.type] || 0) + 1

    })

    console.log('幻灯片类型统计:', slideTypes)

    console.log('总幻灯片数量:', aipptSlides.length)



    return aipptSlides



  } catch (error) {

    console.error('转换大纲为AIPPT.json格式失败:', error)

    return []

  }

}



// 加载AIPPT.json数据

const loadAIPPTData = async (convertedAIPPTData = null, shouldSaveStep = false) => {

  try {

    console.log('开始加载template5数据...')



    // 使用导入的template5.json数据

    console.log('template5.json数据加载成功:', template5JsonData)



    // 根据当前大纲数据生成AIPPT格式的数据

    let aipptData

    if (convertedAIPPTData) {

      // 使用传入的转换后的AIPPT数据

      aipptData = convertedAIPPTData

      console.log('使用传入的AIPPT数据:', aipptData)

    } else if (outlineData.value && outlineData.value.length > 0) {

      // 使用当前大纲数据生成AIPPT格式

      aipptData = convertOutlineToAIPPT(outlineData.value, outlineTitle.value)

      console.log('根据大纲生成的AIPPT数据:', aipptData)

    } else {

      // 如果没有大纲数据，使用导入的默认AIPPT.json数据

      aipptData = aipptJsonData

      console.log('加载默认AIPPT.json数据:', aipptData)

    }



    // 将AIPPT.json数据与template5模板结合

    const convertedSlides = aipptData.map((slide, slideIndex) => {

      // 根据slide.type找到对应的template5模板

      const templateSlide = template5JsonData.find(t => t.type === slide.type)



      if (templateSlide) {

        // 复制模板结构，包括背景、图片、形状等

        const newSlide = JSON.parse(JSON.stringify(templateSlide))



        // 更新幻灯片ID

        newSlide.id = `generated-slide-${slideIndex}`



        // 根据不同类型填充内容

        switch (slide.type) {

          case 'cover':

            newSlide.elements.forEach(element => {

              if (element.textType === 'title') {

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 48px; color: ${element.defaultColor}; text-shadow: 2px 2px 8px rgba(0,0,0,0.5);">${slide.data.title}</span></strong></p>`

              } else if (element.textType === 'content') {

                element.content = `<p style="text-align: center;"><span style="font-size: 24px; color: ${element.defaultColor};">${slide.data.text}</span></p>`

              }

            })

            break



          case 'contents':

            if (slide.data.items) {

              let itemIndex = 0

              newSlide.elements.forEach(element => {

                if (element.textType === 'item' && itemIndex < slide.data.items.length) {

                  const itemText = slide.data.items[itemIndex]

                  element.content = `<p style="text-align: center;"><span style="font-size: 18px; color: ${element.defaultColor};">${itemIndex + 1}. ${itemText}</span></p>`

                  itemIndex++

                }

              })

            }

            break



          case 'content':

            newSlide.elements.forEach(element => {

              if (element.textType === 'title') {

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: ${element.defaultColor};">${slide.data.title}</span></strong></p>`

              }

            })



            if (slide.data.items && Array.isArray(slide.data.items)) {

              let itemIndex = 0

              newSlide.elements.forEach(element => {

                if (element.textType === 'itemTitle' && itemIndex < slide.data.items.length) {

                  const item = slide.data.items[itemIndex]

                  element.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: ${element.defaultColor};">${item.title}</span></strong></p>`

                } else if (element.textType === 'itemContent' && itemIndex < slide.data.items.length) {

                  const item = slide.data.items[itemIndex]

                  element.content = `<p style="text-align: center;"><span style="font-size: 14px; color: ${element.defaultColor};">${item.text}</span></p>`

                  itemIndex++

                }

              })

            }

            break



          case 'transition':

            newSlide.elements.forEach(element => {

              if (element.textType === 'title') {

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: ${element.defaultColor};">${slide.data.title}</span></strong></p>`

              } else if (element.textType === 'content') {

                element.content = `<p style="text-align: center;"><span style="font-size: 16px; color: ${element.defaultColor};">${slide.data.text}</span></p>`

              }

            })

            break



          case 'end':

            newSlide.elements.forEach(element => {

              if (element.textType === 'title') {

                element.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: ${element.defaultColor};">谢谢聆听</span></strong></p>`

              } else if (element.textType === 'content') {

                element.content = `<p style="text-align: center;"><span style="font-size: 18px; color: ${element.defaultColor};">感谢您的时间与关注</span></p>`

              }

            })

            break

        }



        return newSlide

      } else {

        // 如果没有找到对应的模板，使用默认模板

        console.warn(`未找到类型为 ${slide.type} 的模板，使用默认模板`)

        return {

          id: `generated-slide-${slideIndex}`,

          type: slide.type,

          elements: [{

            id: `title-${slideIndex}`,

            type: 'text',

            content: `<h1 style="text-align: center; font-size: 48px; color: #1F2937;">${slide.data.title || '标题'}</h1>`,

            left: 100,

            top: 150,

            width: 760,

            height: 100,

            defaultColor: '#1F2937',

            defaultFontName: 'Arial',

            zIndex: 1

          }],

          background: { color: '#ffffff' }

        }

      }

    })



    // 直接设置生成的PPT数据

    generatedPPT.value = convertedSlides

    console.log('已设置生成的PPT数据:', generatedPPT.value.length, '张幻灯片')



    isPPTPreviewMode.value = true

    currentPPTSlideIndex.value = 0

    console.log('AIPPT数据与template5模板结合完成:', generatedPPT.value)

    console.log('PPT预览模式已启用:', isPPTPreviewMode.value)

    console.log('当前PPT幻灯片索引:', currentPPTSlideIndex.value)

    console.log('生成的PPT数据长度:', generatedPPT.value.length)



    // 强制触发Vue响应式更新

    await nextTick()

    console.log('Vue响应式更新完成')



    // 只有在需要保存步骤时才调用保存API

    if (shouldSaveStep) {

      console.log('需要保存步骤信息到后端')

      await saveStepToBackend(true, true) // 强制保存新生成的PPT

    } else {

      console.log('跳过保存步骤信息，仅加载PPT数据')

    }



    // 确保所有数据处理完成

    return Promise.resolve()



  } catch (error) {

    console.error('加载AIPPT.json或template5.json失败:', error)

    // 如果加载失败，使用默认数据

    generateDefaultPPTData()

    return Promise.reject(error)

  }

}



// 生成默认PPT数据（备用方案）

const generateDefaultPPTData = () => {

  generatedPPT.value = [

    {

      id: 'slide-1',

      background: '#ffffff',

      elements: [

        {

          id: 'title-1',

          type: 'text',

          textType: 'title',

          content: '<p style="text-align: center;"><strong><span style="font-size: 48px; color: #1F2937;">犯罪心理学研究</span></strong></p>',

          x: 100,

          y: 100,

          width: 600,

          height: 100,

          defaultColor: '#1F2937',

          defaultFontName: 'Arial',

          zIndex: 1

        },

        {

          id: 'content-1',

          type: 'text',

          textType: 'content',

          content: '<p style="text-align: center;"><span style="font-size: 20px; color: #6B7280;">探索犯罪心理的成因、特征及干预策略</span></p>',

          x: 100,

          y: 250,

          width: 600,

          height: 50,

          defaultColor: '#6B7280',

          defaultFontName: 'Arial',

          zIndex: 1

        }

      ]

    }

  ]



  isPPTPreviewMode.value = true

  currentPPTSlideIndex.value = 0

  console.log('默认PPT数据生成完成:', generatedPPT.value)

}











// 步骤四相关方法

const selectDownloadOption = (index) => {

  selectedDownloadOption.value = index

  console.log('选择下载选项:', downloadOptions.value[index].title)

}



const downloadNow = () => {

  console.log('立即下载:', downloadOptions.value[selectedDownloadOption.value].title)

  // 这里可以添加下载逻辑

}



const goBackToTemplate = () => {

  showDownloadOptions.value = false

  selectedDownloadOption.value = 0



  // 清理PPT预览相关状态

  generatedPPT.value = []

  currentPPTSlideIndex.value = 0

  selectedPPTElementIndex.value = -1

  editingPPTElementIndex.value = -1

  editingPPTHtml.value = ''

  zoom.value = 1

  selectedImageIndex.value = null



  // 重置为模板预览状态

  currentSlideIndex.value = 0



  // 重新加载模板预览数据

  loadLocalPPT()

  updateSlideThumbnails()



  console.log('已回到模板选择页面，PPT预览状态已清理，模板预览已重新加载')

}



// 处理AI回复中的特殊字符和表情符号

const processAIResponse = (text) => {

  if (!text) return text



  console.log('原始AI回复:', text)

  console.log('原始文本长度:', text.length)

  console.log('原始文本字符码:', Array.from(text).map(char => char.charCodeAt(0)))



  try {

    // 方法1：尝试直接解码

    if (text.includes('%')) {

      const decoded = decodeURIComponent(text)

      console.log('URL解码后:', decoded)

      return decoded

    }



    // 方法2：处理可能的编码问题

    if (text.includes('??')) {

      // 如果包含问号，尝试修复编码

      const cleaned = text.replace(/\?\?/g, '')

      console.log('清理问号后:', cleaned)

      return cleaned

    }



    // 方法3：处理Unicode转义序列

    if (text.includes('\\u')) {

      const unicodeDecoded = text.replace(/\\u[\dA-F]{4}/gi, (match) => {

        return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16))

      })

      console.log('Unicode解码后:', unicodeDecoded)

      return unicodeDecoded

    }



    // 方法4：处理HTML实体

    if (text.includes('&')) {

      const textarea = document.createElement('textarea')

      textarea.innerHTML = text

      const htmlDecoded = textarea.value

      console.log('HTML解码后:', htmlDecoded)

      return htmlDecoded

    }



    console.log('无需特殊处理，直接返回')

    return text

  } catch (error) {

    console.warn('字符编码处理失败:', error)

    return text

  }

}



// 安全的HTML清理函数

const sanitizeHtml = (html) => {

  if (!html) return html



  // 只允许安全的HTML标签

  const allowedTags = {

    'br': {},

    'strong': {},

    'em': {},

    'h1': {},

    'h2': {},

    'h3': {},

    'h4': {},

    'h5': {},

    'h6': {},

    'ul': {},

    'li': {},

    'code': {}

  }



  // 创建临时DOM元素来清理HTML

  const tempDiv = document.createElement('div')

  tempDiv.innerHTML = html



  // 递归清理节点

  const cleanNode = (node) => {

    if (node.nodeType === Node.TEXT_NODE) {

      return node.textContent

    }



    if (node.nodeType === Node.ELEMENT_NODE) {

      const tagName = node.tagName.toLowerCase()



      // 只保留允许的标签

      if (allowedTags[tagName]) {

        const cleanElement = document.createElement(tagName)



        // 递归处理子节点

        for (let child of node.childNodes) {

          const cleanChild = cleanNode(child)

          if (cleanChild) {

            if (typeof cleanChild === 'string') {

              cleanElement.appendChild(document.createTextNode(cleanChild))

            } else {

              cleanElement.appendChild(cleanChild)

            }

          }

        }



        return cleanElement.outerHTML

      } else {

        // 不允许的标签，只保留文本内容

        let textContent = ''

        for (let child of node.childNodes) {

          textContent += cleanNode(child) || ''

        }

        return textContent

      }

    }



    return ''

  }



  // 清理整个HTML

  let cleanHtml = ''

  for (let child of tempDiv.childNodes) {

    cleanHtml += cleanNode(child) || ''

  }



  return cleanHtml

}



// 将Markdown格式转换为HTML格式

const markdownToHtml = (text) => {

  if (!text) return text



  console.log('开始转换Markdown:', text)



  let html = text



  // 清理可能的HTML标签残留

  html = html.replace(/<\/?[^>]*>/g, '')

  console.log('清理HTML标签后:', html)



  // 处理加粗文本 **text** -> <strong>text</strong>

  html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')



  // 处理斜体文本 *text* -> <em>text</em>

  html = html.replace(/\*(.*?)\*/g, '<em>$1</em>')



  // 处理标题 - 支持行首的标题（井号后可以有空格也可以没有空格）

  html = html.replace(/^#{1,6}\s*(.+?)$/gm, (match, content) => {

    console.log('标题匹配:', match, '内容:', content)

    // 检查井号的数量来确定标题级别

    const hashCount = (match.match(/#/g) || []).length

    const level = Math.min(hashCount, 6)

    const result = `<h${level}>${content.trim()}</h${level}>`

    console.log('标题转换结果:', result)

    return result

  })



  // 处理列表项 - 主列表项用2空格缩进，嵌套列表项用8空格缩进

  html = html.replace(/^- (.*?)$/gm, (match, content) => {

    // 检查是否已经有缩进（嵌套列表）

    if (match.startsWith('  - ') || match.startsWith('    - ')) {

      return '        ' + content // 8空格缩进

    }

    return '  ' + content // 2空格缩进

  })



  // 处理数字列表 1. text -> 主列表项用2空格缩进，嵌套列表项用8空格缩进

  html = html.replace(/^\d+\. (.*?)$/gm, (match, content) => {

    // 检查是否已经有缩进（嵌套列表）

    if (match.startsWith('  ') || match.startsWith('    ')) {

      return '        ' + content // 8空格缩进

    }

    return '  ' + content // 2空格缩进

  })



  // 处理换行符 - 在所有Markdown转换完成后进行

  html = html.replace(/\n/g, '<br>')



  // 处理<br>标签后的标题（换行符转换后）

  html = html.replace(/<br>#{1,6}\s*(.+?)(?=<br>|$)/g, (match, content) => {

    console.log('<br>标签后标题匹配:', match, '内容:', content)

    // 检查井号的数量来确定标题级别

    const hashCount = (match.match(/#/g) || []).length

    const level = Math.min(hashCount, 6)

    const result = `<br><h${level}>${content.trim()}</h${level}>`

    console.log('<br>标签后标题转换结果:', result)

    return result

  })



  // 处理代码块 ```code``` -> <code>code</code>

  html = html.replace(/```(.*?)```/gs, '<code>$1</code>')



  // 处理行内代码 `code` -> <code>code</code>

  html = html.replace(/`(.*?)`/g, '<code>$1</code>')



  // 最终清理：确保没有不完整的HTML标签

  html = html.replace(/<\/?[^>]*$/g, '')



  console.log('Markdown转换后:', html)



  // 使用安全的HTML清理函数

  const finalHtml = sanitizeHtml(html)

  console.log('最终清理后:', finalHtml)



  return finalHtml

}



// 复制功能

const copyToClipboard = async (text) => {

  try {

    // 检查 Clipboard API 是否可用

    if (navigator.clipboard && navigator.clipboard.writeText && window.isSecureContext) {

      try {

        await navigator.clipboard.writeText(text)

        ElMessage.success('复制成功！')

        return

      } catch (clipboardError) {

        console.warn('Clipboard API 失败，使用降级方案:', clipboardError)

      }

    }



    // 降级方案：使用传统复制方法

    const textArea = document.createElement('textarea')

    textArea.value = text

    textArea.style.position = 'fixed'

    textArea.style.left = '-999999px'

    textArea.style.top = '-999999px'

    document.body.appendChild(textArea)

    textArea.focus()

    textArea.select()



    try {

      const successful = document.execCommand('copy')

      if (successful) {

        ElMessage.success('复制成功！')

      } else {

        throw new Error('execCommand 复制失败')

      }

    } catch (execError) {

      console.error('传统复制方法也失败:', execError)

      ElMessage.error('复制失败，请手动选择文本复制')

    } finally {

      document.body.removeChild(textArea)

    }

  } catch (err) {

    console.error('复制失败:', err)

    ElMessage.error('复制失败，请手动选择文本复制')

  }

}



// 复制用户消息

const copyUserMessage = (message) => {

  copyToClipboard(message.content)

}



// 复制AI消息

const copyAIMessage = (message) => {

  // 优先使用displayContent，如果没有则使用content

  const textToCopy = message.displayContent || message.content

  copyToClipboard(textToCopy)

}



// 点赞和点踩功能

const handleThumbsUp = (message) => {

  console.log('点赞消息:', message.id)



  // 如果已经点赞，则取消点赞

  if (message.userFeedback === 'like') {

    message.userFeedback = null

  } else {

    // 设置点赞，取消点踩

    message.userFeedback = 'like'

  }



  // 可以在这里发送反馈给后端

  // sendFeedbackToBackend(message.id, message.userFeedback)

}



const handleThumbsDown = (message) => {

  console.log('点踩消息:', message.id)



  // 如果已经点踩，则取消点踩

  if (message.userFeedback === 'dislike') {

    message.userFeedback = null

  } else {

    // 设置点踩，取消点赞

    message.userFeedback = 'dislike'

  }



  // 可以在这里发送反馈给后端

  // sendFeedbackToBackend(message.id, message.userFeedback)

}



// 删除弹窗相关方法

const handleDeleteClick = async (messageIndex) => {

  console.log('点击删除按钮，消息索引:', messageIndex)

  // 这里可以添加删除确认弹窗逻辑

  try {

    await ElMessageBox.confirm('确定要删除这条消息吗？', '确认删除', {

      confirmButtonText: '确定',

      cancelButtonText: '取消',

      type: 'warning'

    })

    chatMessages.value.splice(messageIndex, 1)

  } catch {

    // 用户取消删除

  }

}



// 文件输入框引用

const fileInput = ref(null)



// 图片输入框引用

const imageInput = ref(null)



// 缩略图区域引用

const thumbnailStrip = ref(null)



// 语音朗读相关方法

const handleVoiceRead = (message) => {

  if (speakingMessageId.value === message.id) {

    // 如果正在朗读这条消息，则停止

    stopSpeaking()

    speakingMessageId.value = null

  } else {

    // 如果朗读其他消息，先停止当前朗读

    if (speakingMessageId.value) {

      stopSpeaking()

    }



    // 开始朗读新消息

    const textToRead = message.displayContent || message.content

    if (textToRead && textToRead.trim()) {

      // 清理HTML标签，只朗读纯文本

      const cleanText = textToRead.replace(/<[^>]*>/g, '')



      speakText(cleanText, {

        lang: 'zh-CN',

        rate: 0.9,

        pitch: 1,

        volume: 1

      })

      speakingMessageId.value = message.id

    }

  }

}



// Template8蓝色科技主题缩略图生成函数

const generateTemplate8Thumbnails = () => {

  console.log('开始生成Template8蓝色科技主题缩略图')



  if (!generatedPPT.value || generatedPPT.value.length === 0) {

    console.error('无有效的PPT数据用于生成缩略图')

    return

  }



  // 为Template8使用实际的图片文件作为缩略图

  const template8Images = [

    template8Slide1, // 第1页 - 封面

    template8Slide2, // 第2页 - 目录

    template8Slide3, // 第3页 - 过渡

    template8Slide4, // 第4页 - 内容

    template8Slide5  // 第5页 - 结束

  ]



  const newThumbnails = []



  generatedPPT.value.forEach((slide, index) => {

    // 直接根据索引选择模板8图片，确保每张都不同

    console.log(`Template8缩略图幻灯片 ${index + 1} 类型: ${slide.type || 'content'}, ID: ${slide.id}`)



    // 直接使用索引来选择图片，确保每张都不同

    const thumbnailImage = template8Images[index % 5]



    newThumbnails.push(thumbnailImage)

  })



  // 更新slideImages

  slideImages.value = newThumbnails

  console.log('Template8蓝色科技主题缩略图生成完成，共', newThumbnails.length, '张')

}



// Template7红色主题缩略图生成函数

const generateTemplate7Thumbnails = () => {

  console.log('开始生成Template7红色主题缩略图')



  if (!generatedPPT.value || generatedPPT.value.length === 0) {

    console.error('无有效的PPT数据用于生成缩略图')

    return

  }



  // 为Template7使用实际的图片文件作为缩略图

  const template7Images = [

    template7Slide1, // 第1页 - 封面

    template7Slide2, // 第2页 - 目录

    template7Slide3, // 第3页 - 过渡

    template7Slide4, // 第4页 - 内容

    template7Slide5  // 第5页 - 结束

  ]



  const newThumbnails = []



  generatedPPT.value.forEach((slide, index) => {

    // 直接根据索引选择模板7图片，确保每张都不同

    console.log(`Template7缩略图幻灯片 ${index + 1} 类型: ${slide.type || 'content'}, ID: ${slide.id}`)



    // 直接使用索引来选择图片，确保每张都不同

    const thumbnailImage = template7Images[index % 5]



    newThumbnails.push(thumbnailImage)

  })



  // 更新slideImages

  slideImages.value = newThumbnails

  console.log('Template7红色主题缩略图生成完成，共', newThumbnails.length, '张')

}



// 生成红色主题缩略图的具体实现

const generateRedThemeThumbnail = (slide, index) => {

  // 根据幻灯片类型和索引生成红色主题的缩略图

  const slideType = slide.type || 'content'



  // 使用Canvas生成红色主题缩略图

  const canvas = document.createElement('canvas')

  const ctx = canvas.getContext('2d')

  canvas.width = 160

  canvas.height = 90



  // 设置红色主题背景 - 使用更丰富的红色渐变

  const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)

  gradient.addColorStop(0, '#E74C3C') // 红色主题的主色调

  gradient.addColorStop(0.5, '#C0392B') // 中等红色

  gradient.addColorStop(1, '#A93226') // 深红色



  ctx.fillStyle = gradient

  ctx.fillRect(0, 0, canvas.width, canvas.height)



  // 添加红色主题装饰元素

  ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'

  ctx.beginPath()

  ctx.arc(canvas.width - 20, 20, 15, 0, Math.PI * 2)

  ctx.fill()



  // 添加文本标识

  ctx.fillStyle = '#ffffff'

  ctx.font = 'bold 14px Arial'

  ctx.textAlign = 'center'

  ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'

  ctx.shadowBlur = 2

  ctx.shadowOffsetX = 1

  ctx.shadowOffsetY = 1



  switch (slideType) {

    case 'cover':

      ctx.fillText('封面', canvas.width / 2, canvas.height / 2 - 5)

      ctx.font = '10px Arial'

      ctx.fillText('红色主题', canvas.width / 2, canvas.height / 2 + 10)

      break

    case 'contents':

      ctx.fillText('目录', canvas.width / 2, canvas.height / 2 - 5)

      ctx.font = '10px Arial'

      ctx.fillText('红色主题', canvas.width / 2, canvas.height / 2 + 10)

      break

    case 'transition':

      ctx.fillText('过渡', canvas.width / 2, canvas.height / 2 - 5)

      ctx.font = '10px Arial'

      ctx.fillText('红色主题', canvas.width / 2, canvas.height / 2 + 10)

      break

    case 'content':

      ctx.fillText(`内容${index}`, canvas.width / 2, canvas.height / 2 - 5)

      ctx.font = '10px Arial'

      ctx.fillText('红色主题', canvas.width / 2, canvas.height / 2 + 10)

      break

    case 'end':

      ctx.fillText('结束', canvas.width / 2, canvas.height / 2 - 5)

      ctx.font = '10px Arial'

      ctx.fillText('红色主题', canvas.width / 2, canvas.height / 2 + 10)

      break

    default:

      ctx.fillText(`页面${index + 1}`, canvas.width / 2, canvas.height / 2 - 5)

      ctx.font = '10px Arial'

      ctx.fillText('红色主题', canvas.width / 2, canvas.height / 2 + 10)

  }



  // 添加装饰元素

  ctx.strokeStyle = 'rgba(255,255,255,0.3)'

  ctx.lineWidth = 1

  ctx.strokeRect(10, 10, canvas.width - 20, canvas.height - 20)



  return canvas.toDataURL()

}



// 重置幻灯片位置到首页

const resetSlidePosition = () => {

  console.log('重置幻灯片位置到首页')



  // 重置所有相关的页面索引

  currentSlideIndex.value = 0

  currentPPTSlideIndex.value = 0

  selectedImageIndex.value = null

  selectedPPTElementIndex.value = -1

  editingPPTElementIndex.value = -1



  // 重置缩略图滚动位置

  nextTick(() => {

    const thumbnailStripEl = document.querySelector('.thumbnail-strip')

    if (thumbnailStripEl) {

      thumbnailStripEl.scrollLeft = 0

    }

  })



  console.log('页面位置已重置到首页')

}



// 带进度的Template7生成函数

const generateTemplate7WithProgress = async (outlineData, title) => {

  console.log('开始带进度的Template7生成:', outlineData.length, '个章节')



  const slides = []



  // 1. 封面页

  await new Promise(resolve => setTimeout(resolve, 300))



  const coverSlide = JSON.parse(JSON.stringify(template7JsonData[0]))

  coverSlide.id = 'template7-dynamic-cover'



  const titleElement = coverSlide.elements.find(el => el.id === 'title-text')

  if (titleElement) {

    titleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: #ffffff; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">${title}</span></strong></p>`

  }



  slides.push(coverSlide)

  generatedPPT.value = [...slides]



  // 2. 目录页

  await new Promise(resolve => setTimeout(resolve, 300))



  const contentsSlide = JSON.parse(JSON.stringify(template7JsonData[1]))

  contentsSlide.id = 'template7-dynamic-contents'



  // 填充目录项

  outlineData.forEach((chapter, index) => {

    const itemElement = contentsSlide.elements.find(el => el.id === `item-${index + 1}`)

    if (itemElement) {

      itemElement.content = `<p><span style="font-size: 18px; color: #333333;">${chapter.title}</span></p>`

    }

  })



  // 隐藏多余的目录项

  for (let i = outlineData.length; i < 6; i++) {

    const itemElement = contentsSlide.elements.find(el => el.id === `item-${i + 1}`)

    if (itemElement) {

      itemElement.content = `<p><span style="font-size: 18px; color: #333333;"></span></p>`

    }

  }



  slides.push(contentsSlide)

  generatedPPT.value = [...slides]



  // 3. 为每个章节生成过渡页和内容页

  for (let chapterIndex = 0; chapterIndex < outlineData.length; chapterIndex++) {

    const chapter = outlineData[chapterIndex]



    // 章节过渡页

    await new Promise(resolve => setTimeout(resolve, 300))



    const transitionSlide = JSON.parse(JSON.stringify(template7JsonData[2]))

    transitionSlide.id = `template7-dynamic-transition-${chapterIndex}`



    const transitionTitleElement = transitionSlide.elements.find(el => el.id === 'transition-title')

    if (transitionTitleElement) {

      transitionTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: #e74c3c;">${chapter.title}</span></strong></p>`

    }



    slides.push(transitionSlide)

    generatedPPT.value = [...slides]



    // 为每个小节生成内容页

    if (chapter.sections && chapter.sections.length > 0) {

      for (let sectionIndex = 0; sectionIndex < chapter.sections.length; sectionIndex++) {

        const section = chapter.sections[sectionIndex]



        await new Promise(resolve => setTimeout(resolve, 300))



        const contentSlide = JSON.parse(JSON.stringify(template7JsonData[3]))

        contentSlide.id = `template7-dynamic-content-${chapterIndex}-${sectionIndex}`



        // 填充内容页标题

        const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

        if (contentTitleElement) {

          contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #e74c3c;">${section.title}</span></strong></p>`

        }



        // 动态生成子小节内容

        const subsections = section.subsections || []

        const actualSubsectionCount = Math.min(subsections.length, 4) // 最多4个子小节



        // 填充实际存在的子小节

        for (let subIndex = 0; subIndex < actualSubsectionCount; subIndex++) {

          const subsection = subsections[subIndex]



          const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${subIndex + 1}`)

          if (itemTitleElement) {

            itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${subsection.title}</span></strong></p>`

          }



          const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${subIndex + 1}`)

          if (itemContentElement) {

            itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${subsection.content || '待AI填充'}</span></p>`

          }

        }



        // 隐藏多余的子小节元素

        for (let subIndex = actualSubsectionCount; subIndex < 4; subIndex++) {

          const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${subIndex + 1}`)

          if (itemTitleElement) {

            itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;"></span></strong></p>`

          }



          const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${subIndex + 1}`)

          if (itemContentElement) {

            itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;"></span></p>`

          }

        }



        slides.push(contentSlide)

        generatedPPT.value = [...slides]

      }

    } else {

      // 如果章节没有小节，生成一个默认内容页

      await new Promise(resolve => setTimeout(resolve, 300))



      const contentSlide = JSON.parse(JSON.stringify(template7JsonData[3]))

      contentSlide.id = `template7-dynamic-content-${chapterIndex}-default`



      const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

      if (contentTitleElement) {

        contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #e74c3c;">${chapter.title}</span></strong></p>`

      }



      slides.push(contentSlide)

      generatedPPT.value = [...slides]

    }

  }



  // 4. 结束页

  await new Promise(resolve => setTimeout(resolve, 300))



  const endSlide = JSON.parse(JSON.stringify(template7JsonData[4]))

  endSlide.id = 'template7-dynamic-end'

  slides.push(endSlide)

  generatedPPT.value = [...slides]



  console.log('带进度的Template7生成完成，共', slides.length, '页')

}



// 基于template_8.json生成动态多页结构（保持我们精心设计的蓝色科技主题）

const generateDynamicTemplate8FromStatic = (outlineData, title) => {

  console.log('开始基于template_8.json生成动态多页结构:', outlineData.length, '个章节')



  const slides = []



  // 1. 封面页 - 使用template_8.json的封面设计

  const coverSlide = JSON.parse(JSON.stringify(template8JsonData[0]))

  coverSlide.id = 'template8-dynamic-cover'



  // 调试信息：检查封面页的背景

  console.log('封面页背景信息:', coverSlide.background)

  console.log('封面页元素数量:', coverSlide.elements?.length)



  // 填充封面标题

  const titleElement = coverSlide.elements.find(el => el.id === 'title-text')

  if (titleElement) {

    titleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: #ffffff; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">${title}</span></strong></p>`

  }



  slides.push(coverSlide)



  // 2. 目录页 - 使用template_8.json的目录设计

  const contentsSlide = JSON.parse(JSON.stringify(template8JsonData[1]))

  contentsSlide.id = 'template8-dynamic-contents'



  // 填充目录项

  outlineData.forEach((chapter, index) => {

    const itemElement = contentsSlide.elements.find(el => el.id === `item-${index + 1}`)

    if (itemElement) {

      itemElement.content = `<p style="text-align: center;"><span style="font-size: 18px; color: #333333;">${chapter.title}</span></p>`

    }

  })



  slides.push(contentsSlide)



  // 3. 生成章节内容

  outlineData.forEach((chapter, chapterIndex) => {

    // 章节过渡页 - 使用template_8.json的过渡页设计

    const transitionSlide = JSON.parse(JSON.stringify(template8JsonData[2]))

    transitionSlide.id = `template8-dynamic-transition-${chapterIndex}`



    // 填充过渡页标题

    const transitionTitleElement = transitionSlide.elements.find(el => el.id === 'transition-title')

    if (transitionTitleElement) {

      transitionTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: #47acc5;">${chapter.title}</span></strong></p>`

    }



    // 填充过渡页内容

    const transitionContentElement = transitionSlide.elements.find(el => el.id === 'transition-content')

    if (transitionContentElement) {

      transitionContentElement.content = `<p style="text-align: center;"><span style="font-size: 16px; color: #666666;">${chapter.content || `本章将介绍${chapter.title}的相关内容`}</span></p>`

    }



    slides.push(transitionSlide)



    // 生成小节内容页

    if (chapter.sections && chapter.sections.length > 0) {

      chapter.sections.forEach((section, sectionIndex) => {

        // 内容页 - 使用template_8.json的内容页设计

        const contentSlide = JSON.parse(JSON.stringify(template8JsonData[3]))

        contentSlide.id = `template8-dynamic-content-${chapterIndex}-${sectionIndex}`



        // 填充内容页标题

        const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

        if (contentTitleElement) {

          contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #47acc5;">${section.title}</span></strong></p>`

        }



        // 填充内容项

        if (section.content && Array.isArray(section.content)) {

          section.content.forEach((item, itemIndex) => {

            const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${itemIndex + 1}`)

            const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${itemIndex + 1}`)



            if (itemTitleElement && item.title) {

              itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${item.title}</span></strong></p>`

            }



            if (itemContentElement && item.content) {

              itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${item.content}</span></p>`

            }

          })

        }



        slides.push(contentSlide)

      })

    } else {

      // 如果章节没有小节，创建默认内容页

      const contentSlide = JSON.parse(JSON.stringify(template8JsonData[3]))

      contentSlide.id = `template8-dynamic-content-${chapterIndex}-default`



      // 填充内容页标题

      const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

      if (contentTitleElement) {

        contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #47acc5;">${chapter.title}</span></strong></p>`

      }



      slides.push(contentSlide)

    }

  })



  // 4. 结束页 - 使用template_8.json的结束页设计

  const endSlide = JSON.parse(JSON.stringify(template8JsonData[4]))

  endSlide.id = 'template8-dynamic-end'



  slides.push(endSlide)



  console.log('基于template_8.json的动态多页结构生成完成，共', slides.length, '页')

  return slides

}



// 基于template_7.json生成动态多页结构（保持我们精心设计的红色主题）

const generateDynamicTemplate7FromStatic = (outlineData, title) => {

  console.log('开始基于template_7.json生成动态多页结构:', outlineData.length, '个章节')



  const slides = []



  // 1. 封面页 - 使用template_7.json的封面设计

  const coverSlide = JSON.parse(JSON.stringify(template7JsonData[0]))

  coverSlide.id = 'template7-dynamic-cover'



  // 调试信息：检查封面页的背景

  console.log('封面页背景信息:', coverSlide.background)

  console.log('封面页元素数量:', coverSlide.elements?.length)



  // 填充封面标题

  const titleElement = coverSlide.elements.find(el => el.id === 'title-text')

  if (titleElement) {

    titleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: #ffffff; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">${title}</span></strong></p>`

  }



  slides.push(coverSlide)



  // 2. 目录页 - 使用template_7.json的目录设计

  const contentsSlide = JSON.parse(JSON.stringify(template7JsonData[1]))

  contentsSlide.id = 'template7-dynamic-contents'



  // 填充目录项 - 只填充实际存在的章节

  outlineData.forEach((chapter, index) => {

    const itemElement = contentsSlide.elements.find(el => el.id === `item-${index + 1}`)

    if (itemElement) {

      itemElement.content = `<p><span style="font-size: 18px; color: #333333;">${chapter.title}</span></p>`

    }

  })



  // 隐藏多余的目录项

  for (let i = outlineData.length; i < 6; i++) {

    const itemElement = contentsSlide.elements.find(el => el.id === `item-${i + 1}`)

    if (itemElement) {

      itemElement.content = `<p><span style="font-size: 18px; color: #333333;"></span></p>`

      itemElement.opacity = 0 // 设置为不可见

    }

  }



  // 根据实际目录项数量调整布局

  const actualItemCount = Math.min(outlineData.length, 6)

  console.log(`目录页实际显示${actualItemCount}个目录项`)



  if (actualItemCount === 2) {

    // 2个目录项：调整位置让它们居中显示

    const item1 = contentsSlide.elements.find(el => el.id === 'item-1')

    const item2 = contentsSlide.elements.find(el => el.id === 'item-2')



    if (item1) item1.top = 250

    if (item2) item2.top = 300



  } else if (actualItemCount === 3) {

    // 3个目录项：调整位置让它们均匀分布

    const item1 = contentsSlide.elements.find(el => el.id === 'item-1')

    const item2 = contentsSlide.elements.find(el => el.id === 'item-2')

    const item3 = contentsSlide.elements.find(el => el.id === 'item-3')



    if (item1) item1.top = 220

    if (item2) item2.top = 270

    if (item3) item3.top = 320



  } else if (actualItemCount === 4) {

    // 4个目录项：保持默认位置

    // 默认位置：item-1(200), item-2(250), item-3(300), item-4(350)



  } else if (actualItemCount === 5) {

    // 5个目录项：调整位置让它们均匀分布

    const item1 = contentsSlide.elements.find(el => el.id === 'item-1')

    const item2 = contentsSlide.elements.find(el => el.id === 'item-2')

    const item3 = contentsSlide.elements.find(el => el.id === 'item-3')

    const item4 = contentsSlide.elements.find(el => el.id === 'item-4')

    const item5 = contentsSlide.elements.find(el => el.id === 'item-5')



    if (item1) item1.top = 180

    if (item2) item2.top = 230

    if (item3) item3.top = 280

    if (item4) item4.top = 330

    if (item5) item5.top = 380



  } else if (actualItemCount === 6) {

    // 6个目录项：调整位置让它们均匀分布

    const item1 = contentsSlide.elements.find(el => el.id === 'item-1')

    const item2 = contentsSlide.elements.find(el => el.id === 'item-2')

    const item3 = contentsSlide.elements.find(el => el.id === 'item-3')

    const item4 = contentsSlide.elements.find(el => el.id === 'item-4')

    const item5 = contentsSlide.elements.find(el => el.id === 'item-5')

    const item6 = contentsSlide.elements.find(el => el.id === 'item-6')



    if (item1) item1.top = 160

    if (item2) item2.top = 210

    if (item3) item3.top = 260

    if (item4) item4.top = 310

    if (item5) item5.top = 360

    if (item6) item6.top = 410

  }



  slides.push(contentsSlide)



  // 3. 为每个章节生成过渡页和内容页

  outlineData.forEach((chapter, chapterIndex) => {

    // 章节过渡页 - 使用template_7.json的过渡页设计

    const transitionSlide = JSON.parse(JSON.stringify(template7JsonData[2]))

    transitionSlide.id = `template7-dynamic-transition-${chapterIndex}`



    // 填充过渡页标题

    const transitionTitleElement = transitionSlide.elements.find(el => el.id === 'transition-title')

    if (transitionTitleElement) {

      transitionTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: #e74c3c;">${chapter.title}</span></strong></p>`

    }



    slides.push(transitionSlide)



    // 为每个小节生成内容页

    if (chapter.sections && chapter.sections.length > 0) {

      chapter.sections.forEach((section, sectionIndex) => {

        // 内容页 - 使用template_7.json的内容页设计

        const contentSlide = JSON.parse(JSON.stringify(template7JsonData[3]))

        contentSlide.id = `template7-dynamic-content-${chapterIndex}-${sectionIndex}`



        // 填充内容页标题

        const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

        if (contentTitleElement) {

          contentTitleElement.textType = 'title'

          contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #e74c3c;">${section.title}</span></strong></p>`

        }



        // 确保子小节数据同步到大纲数据中，与通用PPT保持一致

        let actualSubsections = section.subsections || []



        // 如果没有子小节，生成默认子小节并同步到大纲数据

        if (actualSubsections.length === 0) {

          const randomCount = Math.floor(Math.random() * 3) + 2 // 2-4个

          const defaultTitles = ['待AI生成子小节1', '待AI生成子小节2', '待AI生成子小节3', '待AI生成子小节4']



          actualSubsections = []

          for (let i = 0; i < randomCount; i++) {

            actualSubsections.push({

              title: defaultTitles[i] || `要点${i + 1}`,

              content: '待AI填充'

            })

          }



          // 重要：同步到大纲数据中，确保getTitleForElement能正确访问

          section.subsections = actualSubsections

        }



        const actualSubsectionCount = Math.min(actualSubsections.length, 4) // 最多4个子小节



        console.log(`章节${chapterIndex + 1}小节${sectionIndex + 1}有${actualSubsections.length}个子小节，实际显示${actualSubsectionCount}个`)



        // 填充实际存在的子小节

        for (let subIndex = 0; subIndex < actualSubsectionCount; subIndex++) {

          const subsection = actualSubsections[subIndex]



          const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${subIndex + 1}`)

          if (itemTitleElement) {

            itemTitleElement.textType = 'itemTitle'

            itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${subsection.title}</span></strong></p>`

            console.log(`填充子小节标题${subIndex + 1}: ${subsection.title}`)

          }



          const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${subIndex + 1}`)

          if (itemContentElement) {

            // 确保元素有正确的textType属性，以便动画函数能识别

            itemContentElement.textType = 'itemContent'

            itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${subsection.content || '待AI填充'}</span></p>`

            console.log(`填充子小节内容${subIndex + 1}: ${subsection.content || '待AI填充'}`)

          }

        }



        // 隐藏多余的子小节元素（完全移除）

        for (let subIndex = actualSubsectionCount; subIndex < 4; subIndex++) {

          // 移除多余的元素

          contentSlide.elements = contentSlide.elements.filter(el =>

            el.id !== `itemTitle-${subIndex + 1}` &&

            el.id !== `itemContent-${subIndex + 1}` &&

            el.id !== `item-bg-${subIndex + 1}` &&

            el.id !== `item-icon-${subIndex + 1}`

          )

        }



        // 根据实际子小节数量调整布局

        if (actualSubsectionCount === 2) {

          // 2个子小节：调整位置，让它们居中显示

          // 默认位置：item-bg-1(140), item-bg-2(240) -> 调整为：item-bg-1(190), item-bg-2(290)

          const item1 = contentSlide.elements.find(el => el.id === 'item-bg-1')

          const item2 = contentSlide.elements.find(el => el.id === 'item-bg-2')

          if (item1) item1.top = 190 // 向下移动50px

          if (item2) item2.top = 290 // 向下移动50px



          // 调整对应的文字和图标位置

          const title1 = contentSlide.elements.find(el => el.id === 'itemTitle-1')

          const content1 = contentSlide.elements.find(el => el.id === 'itemContent-1')

          const icon1 = contentSlide.elements.find(el => el.id === 'item-icon-1')

          if (title1) title1.top = 200

          if (content1) content1.top = 230

          if (icon1) icon1.top = 205



          const title2 = contentSlide.elements.find(el => el.id === 'itemTitle-2')

          const content2 = contentSlide.elements.find(el => el.id === 'itemContent-2')

          const icon2 = contentSlide.elements.find(el => el.id === 'item-icon-2')

          if (title2) title2.top = 300

          if (content2) content2.top = 330

          if (icon2) icon2.top = 305



          // 调整三角形装饰图像位置，让它居中

          const decorationElement = contentSlide.elements.find(el => el.id === 'content-side-decoration')

          if (decorationElement) {

            // 计算2个子小节的总高度：从190到290，总高度100px

            // 装饰图像应该居中，所以top = 190 + (100/2) - (400/2) = 190 + 50 - 200 = 40

            decorationElement.top = 40

          }



        } else if (actualSubsectionCount === 3) {

          // 3个子小节：明确设置位置，确保间距一致

          // 默认位置：item-bg-1(140), item-bg-2(240), item-bg-3(340) - 间距100px

          const item1 = contentSlide.elements.find(el => el.id === 'item-bg-1')

          const item2 = contentSlide.elements.find(el => el.id === 'item-bg-2')

          const item3 = contentSlide.elements.find(el => el.id === 'item-bg-3')

          if (item1) item1.top = 140

          if (item2) item2.top = 240

          if (item3) item3.top = 340



          // 调整对应的文字和图标位置

          const title1 = contentSlide.elements.find(el => el.id === 'itemTitle-1')

          const content1 = contentSlide.elements.find(el => el.id === 'itemContent-1')

          const icon1 = contentSlide.elements.find(el => el.id === 'item-icon-1')

          if (title1) title1.top = 150

          if (content1) content1.top = 180

          if (icon1) icon1.top = 155



          const title2 = contentSlide.elements.find(el => el.id === 'itemTitle-2')

          const content2 = contentSlide.elements.find(el => el.id === 'itemContent-2')

          const icon2 = contentSlide.elements.find(el => el.id === 'item-icon-2')

          if (title2) title2.top = 250

          if (content2) content2.top = 280

          if (icon2) icon2.top = 255



          const title3 = contentSlide.elements.find(el => el.id === 'itemTitle-3')

          const content3 = contentSlide.elements.find(el => el.id === 'itemContent-3')

          const icon3 = contentSlide.elements.find(el => el.id === 'item-icon-3')

          if (title3) title3.top = 350

          if (content3) content3.top = 380

          if (icon3) icon3.top = 355



          // 调整三角形装饰图像位置，让它居中

          const decorationElement = contentSlide.elements.find(el => el.id === 'content-side-decoration')

          if (decorationElement) {

            // 计算3个子小节的总高度：从140到340，总高度200px

            // 装饰图像应该居中，所以top = 140 + (200/2) - (400/2) = 140 + 100 - 200 = 40

            decorationElement.top = 40

          }



        } else if (actualSubsectionCount === 4) {

          // 4个子小节：调整位置，让它们均匀分布，增加间距

          // 默认位置：item-bg-1(140), item-bg-2(240), item-bg-3(340), item-bg-4(440) -> 调整为：item-bg-1(120), item-bg-2(220), item-bg-3(320), item-bg-4(420)

          const item1 = contentSlide.elements.find(el => el.id === 'item-bg-1')

          const item2 = contentSlide.elements.find(el => el.id === 'item-bg-2')

          const item3 = contentSlide.elements.find(el => el.id === 'item-bg-3')

          const item4 = contentSlide.elements.find(el => el.id === 'item-bg-4')

          if (item1) item1.top = 120

          if (item2) item2.top = 220

          if (item3) item3.top = 320

          if (item4) item4.top = 420



          // 调整对应的文字和图标位置

          const elements = [

            { id: 'itemTitle-1', top: 130 }, { id: 'itemContent-1', top: 160 }, { id: 'item-icon-1', top: 135 },

            { id: 'itemTitle-2', top: 230 }, { id: 'itemContent-2', top: 260 }, { id: 'item-icon-2', top: 235 },

            { id: 'itemTitle-3', top: 330 }, { id: 'itemContent-3', top: 360 }, { id: 'item-icon-3', top: 335 },

            { id: 'itemTitle-4', top: 430 }, { id: 'itemContent-4', top: 460 }, { id: 'item-icon-4', top: 435 }

          ]



          elements.forEach(({ id, top }) => {

            const element = contentSlide.elements.find(el => el.id === id)

            if (element) element.top = top

          })



          // 调整三角形装饰图像位置，让它再下来一点

          const decorationElement = contentSlide.elements.find(el => el.id === 'content-side-decoration')

          if (decorationElement) {

            // 计算4个子小节的总高度：从120到420，总高度300px

            // 装饰图像应该再下来一点，所以top = 120 + (300/2) - (400/2) + 30 = 120 + 150 - 200 + 30 = 100

            decorationElement.top = 100

          }

        }



        slides.push(contentSlide)

      })

    } else {

      // 如果章节没有小节，生成一个默认内容页

      const contentSlide = JSON.parse(JSON.stringify(template7JsonData[3]))

      contentSlide.id = `template7-dynamic-content-${chapterIndex}-default`



      // 填充内容页标题

      const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

      if (contentTitleElement) {

        contentTitleElement.textType = 'title'

        contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #e74c3c;">${chapter.title}</span></strong></p>`

      }



      // 为默认内容页随机生成2-4个子小节（模板7现在支持4个子小节）

      const randomCount = Math.floor(Math.random() * 3) + 2 // 2-4个

      const defaultTitles = ['主要内容', '详细说明', '补充内容', '总结要点']

      const defaultContents = ['待AI填充', '待AI填充', '待AI填充', '待AI填充']



      const defaultSubsections = []

      for (let i = 0; i < randomCount; i++) {

        defaultSubsections.push({

          title: defaultTitles[i] || `要点${i + 1}`,

          content: defaultContents[i] || '待AI填充'

        })

      }



      // 填充子小节

      for (let subIndex = 0; subIndex < randomCount; subIndex++) {

        const subsection = defaultSubsections[subIndex]



        const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${subIndex + 1}`)

        if (itemTitleElement) {

          itemTitleElement.textType = 'itemTitle'

          itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${subsection.title}</span></strong></p>`

        }



        const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${subIndex + 1}`)

        if (itemContentElement) {

          itemContentElement.textType = 'itemContent'

          itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${subsection.content}</span></p>`

        }

      }



      // 移除多余的子小节

      for (let subIndex = randomCount; subIndex < 4; subIndex++) {

        contentSlide.elements = contentSlide.elements.filter(el =>

          el.id !== `itemTitle-${subIndex + 1}` &&

          el.id !== `itemContent-${subIndex + 1}` &&

          el.id !== `item-bg-${subIndex + 1}` &&

          el.id !== `item-icon-${subIndex + 1}`

        )

      }



      // 根据实际子小节数量调整布局

      if (randomCount === 2) {

        // 2个子小节：调整位置，让它们居中显示

        // 默认位置：item-bg-1(140), item-bg-2(240) -> 调整为：item-bg-1(190), item-bg-2(290)

        const item1 = contentSlide.elements.find(el => el.id === 'item-bg-1')

        const item2 = contentSlide.elements.find(el => el.id === 'item-bg-2')

        if (item1) item1.top = 190 // 向下移动50px

        if (item2) item2.top = 290 // 向下移动50px



        // 调整对应的文字和图标位置

        const title1 = contentSlide.elements.find(el => el.id === 'itemTitle-1')

        const content1 = contentSlide.elements.find(el => el.id === 'itemContent-1')

        const icon1 = contentSlide.elements.find(el => el.id === 'item-icon-1')

        if (title1) title1.top = 200

        if (content1) content1.top = 230

        if (icon1) icon1.top = 205



        const title2 = contentSlide.elements.find(el => el.id === 'itemTitle-2')

        const content2 = contentSlide.elements.find(el => el.id === 'itemContent-2')

        const icon2 = contentSlide.elements.find(el => el.id === 'item-icon-2')

        if (title2) title2.top = 300

        if (content2) content2.top = 330

        if (icon2) icon2.top = 305



        // 调整三角形装饰图像位置，让它居中

        const decorationElement = contentSlide.elements.find(el => el.id === 'content-side-decoration')

        if (decorationElement) {

          // 计算2个子小节的总高度：从190到290，总高度100px

          // 装饰图像应该居中，所以top = 190 + (100/2) - (400/2) = 190 + 50 - 200 = 40

          decorationElement.top = 40

        }



      } else if (randomCount === 3) {

        // 3个子小节：明确设置位置，确保间距一致

        // 默认位置：item-bg-1(140), item-bg-2(240), item-bg-3(340) - 间距100px

        const item1 = contentSlide.elements.find(el => el.id === 'item-bg-1')

        const item2 = contentSlide.elements.find(el => el.id === 'item-bg-2')

        const item3 = contentSlide.elements.find(el => el.id === 'item-bg-3')

        if (item1) item1.top = 140

        if (item2) item2.top = 240

        if (item3) item3.top = 340



        // 调整对应的文字和图标位置

        const title1 = contentSlide.elements.find(el => el.id === 'itemTitle-1')

        const content1 = contentSlide.elements.find(el => el.id === 'itemContent-1')

        const icon1 = contentSlide.elements.find(el => el.id === 'item-icon-1')

        if (title1) title1.top = 150

        if (content1) content1.top = 180

        if (icon1) icon1.top = 155



        const title2 = contentSlide.elements.find(el => el.id === 'itemTitle-2')

        const content2 = contentSlide.elements.find(el => el.id === 'itemContent-2')

        const icon2 = contentSlide.elements.find(el => el.id === 'item-icon-2')

        if (title2) title2.top = 250

        if (content2) content2.top = 280

        if (icon2) icon2.top = 255



        const title3 = contentSlide.elements.find(el => el.id === 'itemTitle-3')

        const content3 = contentSlide.elements.find(el => el.id === 'itemContent-3')

        const icon3 = contentSlide.elements.find(el => el.id === 'item-icon-3')

        if (title3) title3.top = 350

        if (content3) content3.top = 380

        if (icon3) icon3.top = 355



        // 调整三角形装饰图像位置，让它居中

        const decorationElement = contentSlide.elements.find(el => el.id === 'content-side-decoration')

        if (decorationElement) {

          // 计算3个子小节的总高度：从140到340，总高度200px

          // 装饰图像应该居中，所以top = 140 + (200/2) - (400/2) = 140 + 100 - 200 = 40

          decorationElement.top = 40

        }



      } else if (randomCount === 4) {

        // 4个子小节：调整位置，让它们均匀分布，增加间距

        // 默认位置：item-bg-1(140), item-bg-2(240), item-bg-3(340), item-bg-4(440) -> 调整为：item-bg-1(120), item-bg-2(220), item-bg-3(320), item-bg-4(420)

        const item1 = contentSlide.elements.find(el => el.id === 'item-bg-1')

        const item2 = contentSlide.elements.find(el => el.id === 'item-bg-2')

        const item3 = contentSlide.elements.find(el => el.id === 'item-bg-3')

        const item4 = contentSlide.elements.find(el => el.id === 'item-bg-4')

        if (item1) item1.top = 120

        if (item2) item2.top = 220

        if (item3) item3.top = 320

        if (item4) item4.top = 420



        // 调整对应的文字和图标位置

        const elements = [

          { id: 'itemTitle-1', top: 130 }, { id: 'itemContent-1', top: 160 }, { id: 'item-icon-1', top: 135 },

          { id: 'itemTitle-2', top: 230 }, { id: 'itemContent-2', top: 260 }, { id: 'item-icon-2', top: 235 },

          { id: 'itemTitle-3', top: 330 }, { id: 'itemContent-3', top: 360 }, { id: 'item-icon-3', top: 335 },

          { id: 'itemTitle-4', top: 430 }, { id: 'itemContent-4', top: 460 }, { id: 'item-icon-4', top: 435 }

        ]



        elements.forEach(({ id, top }) => {

          const element = contentSlide.elements.find(el => el.id === id)

          if (element) element.top = top

        })



        // 调整三角形装饰图像位置，让它再下来一点

        const decorationElement = contentSlide.elements.find(el => el.id === 'content-side-decoration')

        if (decorationElement) {

          // 计算4个子小节的总高度：从120到420，总高度300px

          // 装饰图像应该再下来一点，所以top = 120 + (300/2) - (400/2) + 30 = 120 + 150 - 200 + 30 = 100

          decorationElement.top = 100

        }

      }



      slides.push(contentSlide)

    }

  })



  // 4. 结束页 - 使用template_7.json的结束页设计

  const endSlide = JSON.parse(JSON.stringify(template7JsonData[4]))

  endSlide.id = 'template7-dynamic-end'

  slides.push(endSlide)



  console.log('基于template_7.json的动态多页结构生成完成，共', slides.length, '页')

  return slides

}



// 动态生成Template7多页结构（基于红色主题）

const generateDynamicTemplate7 = (outlineData, title) => {

  console.log('开始动态生成Template7多页结构:', outlineData.length, '个章节')



  const slides = []



  // 1. 封面页 - 使用template_7.json的封面设计

  const coverSlide = JSON.parse(JSON.stringify(template7JsonData[0]))

  coverSlide.id = 'template7-dynamic-cover'



  // 填充封面标题

  const titleElement = coverSlide.elements.find(el => el.id === 'title-text')

  if (titleElement) {

    titleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 40px; color: #ffffff; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">${title}</span></strong></p>`

  }



  slides.push(coverSlide)



  // 2. 目录页 - 使用template_7.json的目录设计

  const contentsSlide = JSON.parse(JSON.stringify(template7JsonData[1]))

  contentsSlide.id = 'template7-dynamic-contents'



  // 填充目录项

  outlineData.forEach((chapter, index) => {

    const itemElement = contentsSlide.elements.find(el => el.id === `item-${index + 1}`)

    if (itemElement) {

      itemElement.content = `<p><span style="font-size: 18px; color: #333333;">${chapter.title}</span></p>`

    }

  })



  // 隐藏多余的目录项

  for (let i = outlineData.length; i < 6; i++) {

    const itemElement = contentsSlide.elements.find(el => el.id === `item-${i + 1}`)

    if (itemElement) {

      itemElement.content = `<p><span style="font-size: 18px; color: #333333;"></span></p>`

    }

  }



  slides.push(contentsSlide)



  // 3. 为每个章节生成过渡页和内容页

  outlineData.forEach((chapter, chapterIndex) => {

    // 章节过渡页 - 使用template_7.json的过渡页设计

    const transitionSlide = JSON.parse(JSON.stringify(template7JsonData[2]))

    transitionSlide.id = `template7-dynamic-transition-${chapterIndex}`



    // 填充过渡页标题

    const transitionTitleElement = transitionSlide.elements.find(el => el.id === 'transition-title')

    if (transitionTitleElement) {

      transitionTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 32px; color: #e74c3c;">${chapter.title}</span></strong></p>`

    }



    slides.push(transitionSlide)



    // 为每个小节生成内容页

    if (chapter.sections && chapter.sections.length > 0) {

      chapter.sections.forEach((section, sectionIndex) => {

        // 内容页 - 使用template_7.json的内容页设计

        const contentSlide = JSON.parse(JSON.stringify(template7JsonData[3]))

        contentSlide.id = `template7-dynamic-content-${chapterIndex}-${sectionIndex}`



        // 填充内容页标题

        const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

        if (contentTitleElement) {

          contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #e74c3c;">${section.title}</span></strong></p>`

        }



        // 填充小节内容

        const subsections = section.subsections || []

        subsections.forEach((subsection, subIndex) => {

          const itemTitleElement = contentSlide.elements.find(el => el.id === `itemTitle-${subIndex + 1}`)

          if (itemTitleElement) {

            itemTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 18px; color: #333333;">${subsection.title}</span></strong></p>`

          }



          const itemContentElement = contentSlide.elements.find(el => el.id === `itemContent-${subIndex + 1}`)

          if (itemContentElement) {

            itemContentElement.content = `<p style="text-align: center;"><span style="font-size: 14px; color: #666666;">${subsection.content || '待AI填充'}</span></p>`

          }

        })



        slides.push(contentSlide)

      })

    } else {

      // 如果章节没有小节，生成一个默认内容页

      const contentSlide = JSON.parse(JSON.stringify(template7JsonData[3]))

      contentSlide.id = `template7-dynamic-content-${chapterIndex}-default`



      // 填充内容页标题

      const contentTitleElement = contentSlide.elements.find(el => el.id === 'content-title')

      if (contentTitleElement) {

        contentTitleElement.content = `<p style="text-align: center;"><strong><span style="font-size: 28px; color: #e74c3c;">${chapter.title}</span></strong></p>`

      }



      slides.push(contentSlide)

    }

  })



  // 4. 结束页 - 使用template_7.json的结束页设计

  const endSlide = JSON.parse(JSON.stringify(template7JsonData[4]))

  endSlide.id = 'template7-dynamic-end'

  slides.push(endSlide)



  console.log('Template7动态多页结构生成完成，共', slides.length, '页')

  return slides

}



// 检查消息是否正在朗读

const isSpeaking = (messageId) => {

  return speakingMessageId.value === messageId

}







// 处理输入框输入，限制2000字

const handleInput = () => {

  if (messageText.value.length > 2000) {

    messageText.value = messageText.value.substring(0, 2000)

    ElMessage.warning('消息长度不能超过2000字')

  }

}



// 获取功能卡片数据

const getFunctionCards = async () => {

  try {

    console.log('开始获取安全培训功能卡片...')

    const response = await apis.getFunctionCard({ function_type: 1 }) // 1为安全培训类型

    console.log('功能卡片响应:', response)



    if (response.statusCode === 200) {

      functionCards.value = response.data

      console.log('功能卡片数据已设置:', functionCards.value)

    } else {

      console.error('获取功能卡片失败:', response.statusCode)

    }

  } catch (error) {

    console.error('获取功能卡片失败:', error)

  }

}



// 获取热点问题数据

const getHotQuestions = async () => {

  try {

    console.log('开始获取安全培训热点问题...')

    const response = await apis.getHotQuestion({ question_type: 1 }) // 1为安全培训类型

    console.log('热点问题响应:', response)



    if (response.statusCode === 200) {

      hotQuestions.value = response.data

      console.log('热点问题数据已设置:', hotQuestions.value)

    } else {

      console.error('获取热点问题失败:', response.statusCode)

    }

  } catch (error) {

    console.error('获取热点问题失败:', error)

  }

}



// 获取OSS上传配置

const getOSSUploadConfig = async () => {

  try {

    console.log('开始获取OSS配置...')

    const response = await apis.uploadOss({})

    console.log('OSS配置响应:', response)



    if (response.statusCode === 200) {

      const res = response

      console.log('OSS配置数据:', res)



      uploadData.OssAccessKeyId = res.accessid

      uploadData.policy = res.policy

      uploadData.Signature = res.signature

      uploadData.host = res.host

      uploadData.dir = res.dir || 'uploads/safety/'

      uploadData.key = uploadData.dir + '${filename}'



      console.log('OSS上传配置已设置:', uploadData)

      return true

    } else {

      console.error('接口返回状态码不是200:', response.statusCode)

      throw new Error('获取OSS配置失败')

    }

  } catch (error) {

    console.error('获取OSS配置失败:', error)

    ElMessage.error('获取上传配置失败，请重试')

    return false

  }

}



// 上传文件到阿里云OSS（web直传）

const uploadFileToOSS = async (file) => {

  try {

    console.log('开始上传文件:', file.name)

    console.log('当前OSS配置状态:', uploadData)



    // 如果还没有OSS配置，先获取

    if (!uploadData.host) {

      console.log('OSS配置为空，开始获取配置...')

      const configResult = await getOSSUploadConfig()

      if (!configResult) {

        throw new Error('获取OSS配置失败')

      }

    }



    // 验证OSS配置是否完整

    if (!uploadData.host || !uploadData.policy || !uploadData.OssAccessKeyId || !uploadData.Signature) {

      console.error('OSS配置不完整:', uploadData)

      throw new Error('OSS配置不完整，请重试')

    }



    // 构建OSS上传参数

    const formData = new FormData()

    formData.append('key', uploadData.dir + file.name)

    formData.append('policy', uploadData.policy)

    formData.append('OSSAccessKeyId', uploadData.OssAccessKeyId)

    formData.append('Signature', uploadData.Signature)

    formData.append('success_action_status', '200')

    formData.append('file', file)



    console.log('开始上传到OSS:', uploadData.host)



    // 直接上传到OSS

    const uploadResponse = await fetch(uploadData.host, {

      method: 'POST',

      body: formData

    })



    console.log('OSS上传响应:', uploadResponse)



    if (uploadResponse.ok) {

      // 上传成功，创建文件信息对象

      const fileExtension = '.' + file.name.split('.').pop().toLowerCase()

      const ossUrl = uploadData.host + '/' + uploadData.dir + file.name



      selectedFile.value = {

        file,

        name: file.name,

        size: file.size,

        type: fileExtension,

        icon: getFileIcon(fileExtension),

        ossUrl: ossUrl, // OSS访问链接

        ossKey: uploadData.dir + file.name

      }



      ElMessage.success('文件上传成功')

    } else {

      const errorText = await uploadResponse.text()

      console.error('OSS上传失败响应:', errorText)

      throw new Error(`上传失败: ${uploadResponse.status} - ${errorText}`)

    }



  } catch (error) {

    console.error('文件上传失败:', error)

    throw error

  }

}



// 获取文件图标

const getFileIcon = (fileType) => {

  switch (fileType) {

    case '.doc':

    case '.docx':

      return wordDocIcon

    default:

      return '📎'

  }

}



// 格式化文件大小

const formatFileSize = (bytes) => {

  if (bytes === 0) return '0 B'

  const k = 1024

  const sizes = ['B', 'KB', 'MB', 'GB']

  const i = Math.floor(Math.log(bytes) / Math.log(k))

  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]

}



// 删除选中的文件

const removeSelectedFile = () => {

  if (selectedFile.value) {

    selectedFile.value = null

  }

}



// 触发文件上传

const triggerFileUpload = () => {

  if (selectedFile.value) {

    ElMessage.warning('只能上传一个文件，请先删除当前文件')

    return

  }

  fileInput.value?.click()

}



// 文件处理工具函数

const validateFile = (file) => {

  // 检查文件大小

  if (file.size > fileConfig.maxSize) {

    throw new Error('文件大小不能超过20MB')

  }



  // 检查文件类型

  const fileExtension = '.' + file.name.split('.').pop().toLowerCase()

  if (!fileConfig.allowedTypes.includes(fileExtension)) {

    throw new Error('只支持.docx格式的Word文档。如果是.doc格式，请先另存为.docx格式。')

  }



  return fileExtension

}



// 读取Word文件内容

const readWordFile = async (file) => {

  try {

    console.log('开始读取Word文件:', file.name, '文件大小:', file.size)



    // 检查文件是否为空

    if (file.size === 0) {

      throw new Error('Word文件为空')

    }



    // 使用mammoth.js库读取Word文档

    console.log('正在导入mammoth库...')

    const mammoth = await import('mammoth')

    console.log('mammoth库导入成功')



    // 将文件转换为ArrayBuffer

    const arrayBuffer = await file.arrayBuffer()

    console.log('文件转换为ArrayBuffer成功，大小:', arrayBuffer.byteLength)



    // 提取文本内容

    console.log('开始提取文本内容...')

    const result = await mammoth.extractRawText({ arrayBuffer })



    console.log('Word文件读取完成，内容长度:', result.value.length)

    return result.value



  } catch (error) {

    console.error('Word文件读取失败，详细错误:', error)

    console.error('错误堆栈:', error.stack)



    // 提供更具体的错误信息

    if (error.message.includes('Invalid file format')) {

      throw new Error('Word文件格式无效或已损坏')

    } else if (error.message.includes('File is empty')) {

      throw new Error('Word文件为空')

    } else {

      throw new Error(`Word文件读取失败: ${error.message}`)

    }

  }

}



// 处理文件标签格式的回显

const processFileDisplay = (text, file) => {

  if (!file) {

    // 如果没有文件对象，尝试从文本中提取文件信息

    return processFileDisplayFromText(text)

  }



  // 查找文件标签并替换为显示格式

  const fileDisplay = `

📄 文件信息：

文件名：${file.name}

文件大小：${formatFileSize(file.size)}

文件类型：${file.type}



📝 文件内容：

${file.content}



---

`



  // 替换文件标签为显示格式

  let processedText = text

    .replace(/<word>.*?<\/word>/gs, fileDisplay)

    .replace(/<filename>.*?<\/filename>/g, '')

    .replace(/<filesize>.*?<\/filesize>/g, '')



  return processedText

}



// 从文本中提取文件信息并转换为显示格式

const processFileDisplayFromText = (text) => {

  // 提取文件名

  const filenameMatch = text.match(/<filename>(.*?)<\/filename>/)

  const filename = filenameMatch ? filenameMatch[1] : '未知文件'



  // 提取文件大小

  const filesizeMatch = text.match(/<filesize>(.*?)<\/filesize>/)

  const filesize = filesizeMatch ? parseInt(filesizeMatch[1]) : 0



  // 提取文件内容

  const wordMatch = text.match(/<word>(.*?)<\/word>/s)

  const fileContent = wordMatch ? wordMatch[1].trim() : '无内容'



  // 创建文件显示格式

  const fileDisplay = `

📄 文件信息：

文件名：${filename}

文件大小：${formatFileSize(filesize)}

文件类型：${filename.endsWith('.docx') ? '.docx' : filename.endsWith('.doc') ? '.doc' : '未知'}



📝 文件内容：

${fileContent}



---

`



  // 替换文件标签为显示格式

  let processedText = text

    .replace(/<word>.*?<\/word>/gs, fileDisplay)

    .replace(/<filename>.*?<\/filename>/g, '')

    .replace(/<filesize>.*?<\/filesize>/g, '')



  return processedText

}









// 处理文件选择

const handleFileSelect = async (event) => {

  const file = event.target.files[0]

  if (!file) return



  try {

    // 验证文件

    const fileExtension = validateFile(file)



    isUploadingFile.value = true

    console.log('开始读取文件内容:', file.name)



    // 只处理Word文档

    const extractedContent = await readWordFile(file)



    // 创建文件信息对象

    selectedFile.value = {

      file,

      name: file.name,

      size: file.size,

      type: fileExtension,

      icon: getFileIcon(fileExtension),

      content: extractedContent // 存储提取的内容

    }



    // 显示提取的内容长度

    const contentLength = extractedContent.length

    console.log('文件内容提取完成，字符数:', contentLength)

    ElMessage.success(`文件读取成功，提取了${contentLength}个字符的内容`)



  } catch (error) {

    console.error('文件读取失败:', error)

    ElMessage.error(error.message || '文件读取失败，请重试')

  } finally {

    isUploadingFile.value = false

    event.target.value = ''

  }

}



// 语音输入相关方法

const handleVoiceClick = () => {

  console.log('点击语音按钮')

  if (isListening.value) {

    // 如果正在录音，则停止

    stopVoiceInput()

  } else {

    // 开始语音输入

    startVoiceInput()

  }

}



const startVoiceInput = () => {

  console.log('开始语音输入')



  // 开始语音识别

  const success = startListening()

  if (!success) {

    ElMessage.error('语音识别启动失败，请检查麦克风权限')

  }

}



const stopVoiceInput = () => {

  console.log('停止语音输入')

  stopListening()



  // 语音识别完成后，将结果填入输入框

  if (transcript.value.trim()) {

    messageText.value = transcript.value

  }

}







// 复制整个大纲

const copyEntireOutline = async () => {

  try {

    if (!outlineData.value || outlineData.value.length === 0) {

      ElMessage.warning('暂无大纲内容可复制')

      return

    }



    // 构建大纲文本（纯文本格式，无井号）

    let outlineText = `${outlineTitle.value || '安全培训大纲'}\n\n`



    outlineData.value.forEach((chapter, chapterIndex) => {

      outlineText += `${chapter.title}\n\n`



      if (chapter.sections && chapter.sections.length > 0) {

        chapter.sections.forEach((section, sectionIndex) => {

          outlineText += `  ${section.title}\n\n`



          if (section.subsections && section.subsections.length > 0) {

            section.subsections.forEach((subsection, subsectionIndex) => {

              outlineText += `    ${subsection.title}\n\n`



              if (subsection.subsubsections && subsection.subsubsections.length > 0) {

                subsection.subsubsections.forEach((subsubsection, subsubsectionIndex) => {

                  outlineText += `      - ${subsubsection.title}\n`

                  if (subsubsection.content) {

                    outlineText += `        ${subsubsection.content}\n`

                  }

                })

                outlineText += '\n'

              }

            })

          }

        })

      }

    })



    // 不添加统计信息，只复制纯大纲内容



    // 检查 Clipboard API 是否可用

    if (navigator.clipboard && navigator.clipboard.writeText && window.isSecureContext) {

      try {

        await navigator.clipboard.writeText(outlineText)

        ElMessage.success('复制成功')

        return

      } catch (clipboardError) {

        console.warn('Clipboard API 失败，使用降级方案:', clipboardError)

      }

    }



    // 降级方案：使用传统复制方法

    const textArea = document.createElement('textarea')

    textArea.value = outlineText

    textArea.style.position = 'fixed'

    textArea.style.left = '-999999px'

    textArea.style.top = '-999999px'

    document.body.appendChild(textArea)

    textArea.focus()

    textArea.select()



    try {

      const successful = document.execCommand('copy')

      if (successful) {

        ElMessage.success('大纲已复制到剪贴板')

      } else {

        throw new Error('execCommand 复制失败')

      }

    } catch (execError) {

      console.error('传统复制方法也失败:', execError)

      ElMessage.error('复制失败，请手动选择文本复制')

    } finally {

      document.body.removeChild(textArea)

    }



  } catch (error) {

    console.error('复制大纲失败:', error)

    ElMessage.error('复制失败，请手动选择文本复制')

  }

}



// 下载大纲为Word文档

const downloadOutlineAsWord = async () => {

  try {

    if (!outlineData.value || outlineData.value.length === 0) {

      ElMessage.warning('暂无大纲内容可下载')

      return

    }



    // 构建Word文档内容（HTML格式，兼容Microsoft Office Word）

    let htmlContent = `<!DOCTYPE html>

<html xmlns:o="urn:schemas-microsoft-com:office:office" 

      xmlns:w="urn:schemas-microsoft-com:office:word" 

      xmlns="http://www.w3.org/TR/REC-html40">

<head>

  <meta charset="utf-8">

  <meta name="ProgId" content="Word.Document">

  <meta name="Generator" content="Microsoft Word 15">

  <meta name="Originator" content="Microsoft Word 15">

  <title>${outlineTitle.value || '安全培训大纲'}</title>
  <!--[if gte mso 9]>
  <xml>
    <w:WordDocument>
      <w:View>Print</w:View>
      <w:Zoom>100</w:Zoom>
      <w:DoNotPromptForConvert/>
      <w:DoNotShowRevisions/>
      <w:DoNotPrintRevisions/>
      <w:DoNotShowComments/>
      <w:DoNotShowInsertionsAndDeletions/>
      <w:DoNotShowPropertyChanges/>
      <w:Compatibility>
        <w:BreakWrappedTables/>
        <w:SnapToGridInCell/>
        <w:WrapTextWithPunct/>
        <w:UseAsianBreakRules/>
        <w:DontGrowAutofit/>
      </w:Compatibility>
    </w:WordDocument>
  </xml>
  <![endif]-->
  <style>

    body {

      font-family: "Microsoft YaHei", Arial, sans-serif;

      font-size: 14px;

      line-height: 1.6;

      margin: 24px;

      color: #000;

    }

    .header {

      text-align: center;

      margin-bottom: 14px;

    }

    .outline-title {

      font-size: 24px;

      font-weight: bold;

      margin-bottom: 14px;

      color: #000;

    }

    h1, h2, h3, h4, h5, h6 { 

      color: #000; 

      font-weight: bold;

      font-family: "Microsoft YaHei", Arial, sans-serif;

      margin-top: 20px;

      margin-bottom: 15px;

    }

    h1 { 

      font-size: 20px;

      border-bottom: 2px solid #000; 

      padding-bottom: 10px; 

    }

    h2 { 

      font-size: 18px; 

      margin-top: 20px; 

      margin-bottom: 12px; 

    }

    h3 { 

      font-size: 16px; 

      margin-top: 15px; 

      margin-bottom: 8px; 

    }

    h4 { 

      font-size: 14px; 

      margin-top: 12px; 

      margin-bottom: 6px; 

    }

    ul, li {

      color: #000;

      font-family: "Microsoft YaHei", Arial, sans-serif;

    }

    li {

      margin-bottom: 4px;

    }

    .stats { 

      background: #f8f9fa; 

      padding: 20px; 

      border-radius: 8px; 

      margin-top: 30px; 

      border: 1px solid #ddd;

    }

    .stats h3 { 

      color: #2c3e50; 

      margin-top: 0; 

      font-size: 16px;

    }

    .stats p { 

      margin: 8px 0; 

      color: #555; 

      font-size: 14px;

    }

  </style>

</head>

<body>

  <div class="header">

    <div class="outline-title">${outlineTitle.value || '安全培训大纲'}</div>

  </div>

    `



    // 添加章节内容（使用新的结构格式）

    outlineData.value.forEach((chapter, chapterIndex) => {

      htmlContent += `<h2>${chapter.title}</h2>`



      if (chapter.sections && chapter.sections.length > 0) {

        chapter.sections.forEach((section, sectionIndex) => {

          htmlContent += `<h3>${section.title}</h3>`



          if (section.subsections && section.subsections.length > 0) {

            section.subsections.forEach((subsection, subsectionIndex) => {

              htmlContent += `<h4>${subsection.title}</h4>`



              if (subsection.subsubsections && subsection.subsubsections.length > 0) {

                htmlContent += `<ul>`

                subsection.subsubsections.forEach((subsubsection, subsubsectionIndex) => {

                  htmlContent += `<li><strong>${subsubsection.title}</strong>`

                  if (subsubsection.content) {

                    htmlContent += `<br>${subsubsection.content}`

                  }

                  htmlContent += `</li>`

                })

                htmlContent += `</ul>`

              }

            })

          }

        })

      }

    })



    // 添加统计信息

    if (outlineStats.value) {

      htmlContent += `

        <div class="stats">

          <h3>大纲统计信息</h3>

          <p><strong>总章节数：</strong>${outlineStats.value.totalChapters || '未知'}章</p>

          <p><strong>总小节数：</strong>${outlineStats.value.totalSections || '未知'}小节</p>

          <p><strong>预计PPT页数：</strong>${outlineStats.value.estimatedPages || '未知'}</p>

          <p><strong>预计讲解时长：</strong>${outlineStats.value.estimatedTime || '未知'}</p>

        </div>

      `

    }



    htmlContent += `

      </body>

      </html>

    `



    // 创建Blob对象 - 使用Word兼容的MIME类型

    const blob = new Blob([htmlContent], { type: 'application/msword' })



    // 创建下载链接

    const url = URL.createObjectURL(blob)

    const link = document.createElement('a')

    link.href = url

    link.download = `${outlineTitle.value || '安全培训大纲'}.doc`

    document.body.appendChild(link)

    link.click()

    document.body.removeChild(link)



    // 清理URL对象

    URL.revokeObjectURL(url)



    ElMessage.success('下载成功')



  } catch (error) {

    console.error('下载大纲失败:', error)

    ElMessage.error('下载失败，请重试')

  }

}




</script>



<style lang="less" scoped>
// 全局图片无边框样式

img {

  border: none !important;

  outline: none !important;

}



// 删除图标样式

.delete-icon {

  width: 16px;

  height: 16px;

}



body,

html {

  background-color: #E8F4FD;

  margin: 0;

  padding: 0;

}



.chat-container {

  display: flex;

  height: 100vh;

  font-family: 'Alibaba PuHuiTi 3.0', sans-serif;

}



/* 中间历史记录栏 */

.history-sidebar {

  width: 280px;

  min-width: 280px;

  flex-shrink: 0;

  background: #E8F0FF;

  display: flex;

  flex-direction: column;

}



/* 中间历史记录栏样式 */

.history-sidebar {

  padding: 24px 16px 0 16px;



  .history-header {

    background: transparent;



    .section-title {

      font-size: 16px;

      font-weight: 600;

      color: #2C3E50;

    }



    .new-chat-btn {

      width: 248px;

      height: 40px;

      cursor: pointer;

      transition: opacity 0.3s ease;

      object-fit: contain;

      display: block;

      margin-top: 16px;

      margin-bottom: 16px;



      &:hover {

        opacity: 0.8;

      }

    }

  }



  .history-list {

    flex: 1;

    overflow-y: auto;

    width: 248px;

    height: 84px;



    /* 隐藏滚动条 */

    &::-webkit-scrollbar {

      display: none;

    }



    -ms-overflow-style: none;

    /* IE and Edge */

    scrollbar-width: none;

    /* Firefox */



    .history-item {

      background: white;

      border-radius: 8px;

      padding: 12px 15px 12px 15px;

      margin-bottom: 8px;

      cursor: pointer;

      transition: all 0.3s ease;

      border-left: 3px solid transparent;

      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);

      display: flex;

      align-items: flex-start;

      gap: 12px;

      position: relative;



      &:hover {

        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);



        .delete-btn {

          opacity: 1;

          visibility: visible;

        }

      }



      &.active {

        border-left-color: #3E7BFA;

        box-shadow: 0 2px 8px rgba(62, 123, 250, 0.2);

        cursor: default;

        opacity: 0.8;



        &:hover {

          transform: none;

          box-shadow: 0 2px 8px rgba(62, 123, 250, 0.2);

        }

      }



      .history-icon {

        width: 80px;

        height: 60px;

        flex-shrink: 0;



        .history-icon-img {

          width: 100%;

          height: 100%;

          object-fit: contain;

          border-radius: 4px;

        }

      }



      .history-content {

        flex: 1;

        min-width: 0;

        display: flex;

        flex-direction: column;

        justify-content: space-between;

        height: 60px;

        // margin-right: 12px;



        .history-title {

          font-size: 14px;

          line-height: 1.4;

          color: #1F2937;

          overflow: hidden;

          display: -webkit-box;

          -webkit-line-clamp: 2;

          -webkit-box-orient: vertical;

          text-overflow: ellipsis;

          flex: 1;

          max-height: calc(14px * 1.4 * 2);

          /* 确保最大高度正好是两行 */

          word-break: break-word;

        }



        .history-meta {

          display: flex;

          justify-content: space-between;

          align-items: center;

          margin-top: auto;



          .history-time {

            font-size: 12px;

            color: #7C8DB5;

          }



          .history-pages {

            font-size: 12px;

            color: #6B7280;

          }

        }

      }



      .delete-btn {

        opacity: 0;

        visibility: hidden;

        transition: all 0.2s ease;

        cursor: pointer;

        padding: 2px;

        border-radius: 3px;

        color: #7c8db5;

        display: flex;

        align-items: center;

        justify-content: center;

        margin-left: 8px;

        flex-shrink: 0;

        position: absolute;

        top: 50%;

        right: 2px;

        transform: translateY(-50%);



        &:hover {

          color: #ff4757;

          background-color: rgba(255, 71, 87, 0.05);

        }



        &.always-visible {

          opacity: 1;

          visibility: visible;

        }

      }

    }



    .empty-history {

      display: flex;

      flex-direction: column;

      align-items: center;

      justify-content: center;

      margin-top: 236px;



      .empty-icon {

        width: 147px;

        height: 148px;

        object-fit: contain;

        margin-bottom: 16px;

      }



      .empty-text {

        font-size: 16px;

        color: #9C9FA7;

        text-align: center;

      }

    }

  }

}



/* 主工作区域 */

.main-work {

  flex: 1;

  background: #EBF3FF;

  display: flex;

  flex-direction: column;

}



/* 工作头部 */

.work-header {

  background: transparent;

  padding: 30px 0px 0px 18px;



  h2 {

    margin: 0;

    font-size: 20px;

    font-weight: 600;

    color: #2C3E50;

  }

}



/* 工作内容区域 */

.work-content {

  flex: 1;

  overflow-y: auto;

  display: flex;

  flex-direction: column;

  align-items: center;

  position: relative;

  margin-top: 24px;

  padding: 0px 30px;



  /* 隐藏滚动条样式 */

  &::-webkit-scrollbar {

    width: 0;

    background: transparent;

  }



  &::-webkit-scrollbar-track {

    background: transparent;

  }



  &::-webkit-scrollbar-thumb {

    background: transparent;

  }

}



/* 聊天内容区域 */

.chat-content {

  flex: 1;

  overflow-y: auto;

  display: flex;

  flex-direction: column;

  align-items: center;

  margin-top: 24px;

  width: 1060px;

}



/* 旋转动画 */

.rotating {

  animation: rotate 1s linear infinite;

}



@keyframes rotate {

  from {

    transform: rotate(0deg);

  }



  to {

    transform: rotate(360deg);

  }

}



/* 大纲内容禁用状态 */

.outline-content.disabled {

  position: relative;

  pointer-events: none;

  opacity: 0.8;

}



/* 生成中遮罩层 */

.generating-overlay {

  position: absolute;

  top: 0;

  left: 0;

  right: 0;

  bottom: 0;

  background: rgba(255, 255, 255, 0.9);

  display: flex;

  align-items: center;

  justify-content: center;

  z-index: 100;

  border-radius: 12px;

}



.generating-content {

  text-align: center;

  padding: 40px;

}







.generating-content p {

  font-size: 18px;

  color: #6B7280;

  margin: 0;

  font-weight: 500;

}



/* 应用模板中遮罩层 */

.applying-overlay {

  position: absolute;

  top: 0;

  left: 0;

  right: 0;

  bottom: 0;

  background: rgba(255, 255, 255, 0.7);

  display: flex;

  align-items: center;

  justify-content: center;

  z-index: 1000;

  border-radius: 12px;

  overflow: hidden;

}



/* 禁用状态样式 */

.history-sidebar.disabled {

  pointer-events: none;

  opacity: 0.8;

}



.history-sidebar.disabled .new-chat-btn {

  cursor: not-allowed;

  opacity: 0.8;

}



.history-item.disabled {

  cursor: not-allowed !important;

  opacity: 0.8;

}



.applying-content {

  text-align: center;

  padding: 40px;

}



.applying-content p {

  font-size: 18px;

  color: #6B7280;

  margin: 0;

  font-weight: 500;

}



/* 模板内容禁用状态 */

.template-content.disabled {

  position: relative;

  pointer-events: none;

  opacity: 0.8;

}



/* step3内容禁用状态 */

.step3-content.disabled {

  position: relative;

  pointer-events: none;

  opacity: 0.8;

}



/* 下载内容禁用状态 */

.download-content.disabled {

  position: relative;

  pointer-events: none;

  opacity: 0.8;

}



/* 加载状态样式 */

.loading-overlay {

  position: absolute;

  top: 0;

  left: 0;

  right: 0;

  bottom: 0;

  background: rgba(255, 255, 255, 0.95);

  display: flex;

  flex-direction: column;

  justify-content: center;

  align-items: center;

  z-index: 1000;

  border-radius: 12px;

  backdrop-filter: blur(2px);

  transition: all 0.3s ease;

}



.loading-content {

  text-align: center;

  padding: 40px;

  background: rgba(255, 255, 255, 0.9);

  border-radius: 16px;

  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);

  border: 1px solid rgba(255, 255, 255, 0.2);

}



.loading-spinner {

  width: 48px;

  height: 48px;

  border: 4px solid #f3f3f3;

  border-top: 4px solid #409eff;

  border-radius: 50%;

  animation: spin 1s linear infinite;

  margin: 0 auto 24px auto;

}



.loading-text {

  color: #6B7280;

  font-size: 18px;

  margin: 0;

  font-weight: 500;

}



/* 历史记录加载状态样式 */

.history-loading {

  display: flex;

  flex-direction: column;

  align-items: center;

  justify-content: center;

  padding: 40px 20px;

  min-height: 200px;

}



.history-loading .loading-spinner {

  width: 32px;

  height: 32px;

  border: 3px solid #f3f3f3;

  border-top: 3px solid #409eff;

  border-radius: 50%;

  animation: spin 1s linear infinite;

  margin: 0 auto 16px auto;

}



.history-loading .loading-text {

  color: #6B7280;

  font-size: 14px;

  margin: 0;

  font-weight: 400;

}



.loading-subtitle {

  color: #9CA3AF;

  font-size: 14px;

  margin-top: 8px;

  font-weight: 400;

}



@keyframes spin {

  0% {

    transform: rotate(0deg);

  }



  100% {

    transform: rotate(360deg);

  }

}



@keyframes pulse {

  0%,
  100% {

    transform: scale(1);

    opacity: 1;

  }

  50% {

    transform: scale(1.05);

    opacity: 0.8;

  }

}



@keyframes blink {

  0%,
  50% {

    opacity: 1;

  }

  51%,
  100% {

    opacity: 0.3;

  }

}



/* AI生成的幻灯片样式 */

.ai-generated-slide {

  width: 100%;

  height: 100%;

  display: flex;

  align-items: center;

  justify-content: center;

}



.slide-content {

  width: 960px;

  height: 540px;

  position: relative;

  border-radius: 8px;

  overflow: hidden;

  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);

}



.slide-element {

  position: absolute;

}



/* 按钮禁用状态样式 */

.action-btn:disabled,

.eval-btn:disabled {

  cursor: not-allowed;

}



.action-btn:disabled:hover,

.eval-btn:disabled:hover {

  background: inherit;

  border-color: inherit;

  transform: none;

}



/* AI助手介绍 */

.ai-intro {

  display: flex;

  flex-direction: column;

  align-items: center;

  margin-bottom: 96px;



  .ai-avatar {

    width: 80px;

    height: 80px;

    border-radius: 20px;

    display: flex;

    align-items: center;

    justify-content: center;

    margin-bottom: 20px;



    .ai-avatar-img {

      width: 100%;

      height: 100%;

      object-fit: contain;

    }

  }



  .ai-greeting {

    text-align: center;



    h3 {

      font-size: 24px;

      font-weight: 600;

      color: #2C3E50;

      margin: 0 0 8px 0;

    }



    p {

      font-size: 16px;

      color: #7C8DB5;

      margin: 0;

    }

  }

}



/* 聊天消息区域 */

.chat-messages {

  width: 100%;

  max-width: 1060px;

  padding: 0 75px 0 35px;

  display: flex;

  flex-direction: column;

  gap: 24px;

  padding-bottom: 40px;

  /* 添加底部间距 */

}





/* 消息项样式 */

.message-item {

  display: flex;

  flex-direction: column;

  gap: 12px;

}



/* 用户消息样式 */

.user-message {

  display: flex;

  flex-direction: column;

  align-items: flex-end;

  gap: 8px;



  .message-content {

    background: #004BFF20;

    color: #000000;

    padding: 16px 20px;

    border-radius: 18px 0px 18px 18px;

    max-width: 900px;

    word-wrap: break-word;

    line-height: 1.5;

    font-size: 16px;



    .message-file {

      margin-bottom: 12px;



      .file-display {

        display: flex;

        align-items: center;

        background: rgba(255, 255, 255, 0.8);

        border: 1px solid #E5E7EB;

        border-radius: 12px;

        padding: 16px;

        max-width: 400px;



        .file-icon {

          font-size: 32px;

          margin-right: 16px;

          width: 48px;

          text-align: center;
          
          .file-icon-img {
            width: 32px;
            height: 32px;
            object-fit: contain;
          }

        }



        .file-details {

          flex: 1;



          .file-name {

            font-size: 14px;

            font-weight: 500;

            color: #374151;

            margin-bottom: 4px;

            overflow: hidden;

            text-overflow: ellipsis;

            white-space: nowrap;

            max-width: 200px;

          }



          .file-size {

            font-size: 12px;

            color: #6B7280;

          }

        }

      }

    }



    .message-text {

      // margin-top: 8px;

    }

  }



  .message-actions {

    display: flex;

    // gap: 8px;



    .action-btn {

      background: none;

      border: none;

      padding: 6px 4px;

      border-radius: 4px;

      font-size: 12px;

      color: #6b7280;

      cursor: pointer;

      transition: all 0.2s ease;

      display: flex;

      align-items: center;

      gap: 4px;



      &:hover {

        color: #374151;

      }



      .action-icon {

        width: 16px;

        height: 16px;

        object-fit: contain;

      }

    }

  }

}



/* AI消息样式 */

.ai-message {

  display: flex;

  gap: 12px;



  .ai-avatar-small {

    width: 40px;

    height: 40px;

    flex-shrink: 0;



    .ai-icon {

      width: 100%;

      height: 100%;

      object-fit: contain;

    }

  }



  .message-content {

    background: white;

    color: #374151;

    padding: 16px 20px;

    border-radius: 0px 18px 18px 18px;

    max-width: 900px;

    width: fit-content;

    min-width: 200px;

    word-wrap: break-word;

    line-height: 1.5;

    font-size: 16px;

    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);

    white-space: pre-line;

    transition: width 0.3s ease;





    .ai-text {

      min-height: 20px;

      line-height: 1.5;

      margin-bottom: 16px;



      .typing-indicator {

        color: #9CA3AF;

        font-style: italic;

        font-size: 14px;

        display: flex;

        align-items: center;

        gap: 8px;

        white-space: nowrap;



        .thinking-animation {

          display: flex;

          gap: 4px;



          .dot {

            width: 6px;

            height: 6px;

            background: #9CA3AF;

            border-radius: 50%;

            animation: thinking 1.4s infinite ease-in-out;



            &:nth-child(1) {

              animation-delay: -0.32s;

            }



            &:nth-child(2) {

              animation-delay: -0.16s;

            }



            &:nth-child(3) {

              animation-delay: 0s;

            }

          }

        }

      }



      .ai-content {

        line-height: 1.6;



        // 加粗文本样式

        strong {

          font-weight: 600;

          color: #1f2937;

        }



        // 斜体文本样式

        em {

          font-style: italic;

          color: #4b5563;

        }



        // 标题样式

        h3 {

          font-size: 18px;

          font-weight: 600;

          color: #1f2937;

          margin: 16px 0 8px 0;

          border-bottom: 2px solid #e5e7eb;

          padding-bottom: 4px;

        }



        h4 {

          font-size: 16px;

          font-weight: 600;

          color: #1f2937;

          margin: 12px 0 6px 0;

        }



        // 列表样式

        ul {

          margin: 8px 0;

          padding-left: 20px;



          li {

            margin: 4px 0;

            line-height: 1.5;

          }

        }



        // 代码样式

        code {

          background: #f3f4f6;

          padding: 2px 6px;

          border-radius: 4px;

          font-family: 'Courier New', monospace;

          font-size: 14px;

          color: #dc2626;

        }



        // 换行样式

        br {

          margin: 4px 0;

        }

      }

    }



    .divider {

      height: 1px;

      background: #e5e7eb;

      margin: 16px 0 12px 0;

    }



    .message-actions {

      display: flex;

      justify-content: space-between;

      align-items: center;



      .left-actions {

        display: flex;

        gap: 8px;

        flex-wrap: wrap;

      }



      .right-actions {

        display: flex;

      }



      .action-btn {

        background: none;

        border: none;

        padding: 6px 5px;

        border-radius: 4px;

        cursor: pointer;

        transition: all 0.2s ease;

        display: flex;

        align-items: center;

        gap: 4px;

        font-size: 12px;

        color: #6b7280;



        &:hover {

          color: #374151;

          background: rgba(59, 130, 246, 0.1);

        }



        .action-icon {

          width: 16px;

          height: 16px;

          object-fit: contain;

        }



        // 点赞和点踩按钮的特殊样式

        &.thumbs-up-btn {

          transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);



          &:hover {

            color: #059669;

            transform: scale(1.1);

          }



          &.active {

            color: #059669;

            transform: scale(1.2);



            .action-icon {

              filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(142deg) brightness(104%) contrast(97%);

            }

          }



          &:active {

            transform: scale(0.9);

            transition: all 0.1s ease;

          }

        }



        &.thumbs-down-btn {

          transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);



          &:hover {

            color: #dc2626;

            transform: scale(1.1);

          }



          &.active {

            color: #dc2626;

            transform: scale(1.2);



            .action-icon {

              filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(0deg) brightness(104%) contrast(97%) !important;

            }

          }



          &:active {

            transform: scale(0.9);

            transition: all 0.1s ease;

          }

        }

      }

    }

  }

}



/* 打字动画 */

@keyframes thinking {



  0%,

  80%,

  100% {

    transform: scale(0.8);

    opacity: 0.5;

  }



  40% {

    transform: scale(1);

    opacity: 1;

  }

}



/* 功能卡片 */

.function-cards {

  display: grid;

  grid-template-columns: repeat(2, 1fr);

  gap: 16px;

  margin-top: 56px;

  max-width: 592px;





  .function-card {

    background: white;

    width: 288px;

    height: 112px;

    padding: 20px 0px;

    padding-left: 20px;

    border-radius: 12px;

    border: 1px solid #E5E8EB;

    cursor: pointer;

    transition: all 0.3s ease;

    display: flex;

    flex-direction: column;

    box-sizing: border-box;





    &:hover {

      border-color: #667eea;

      transform: translateY(-2px);

      box-shadow: 0 4px 20px rgba(102, 126, 234, 0.1);

    }



    .card-header {

      display: flex;

      align-items: center;

      gap: 16px;

      margin-bottom: 8px;



      .card-icon {

        width: 40px;

        height: 40px;

        display: flex;

        align-items: center;

        justify-content: center;

        background: #F3F5FF;

        border-radius: 8px;

        flex-shrink: 0;



        .card-icon-img {

          width: 40px;

          height: 40px;

          object-fit: contain;

        }

      }



      h4 {

        font-size: 16px;

        font-weight: 600;

        color: #2C3E50;

        margin: 0;

        line-height: 1.5;

        flex: 1;

      }

    }



    .card-description {

      margin-left: 55px;



      p {

        font-size: 14px;

        color: #7C8DB5;

        margin: 0;

        line-height: 1.4;

      }

    }

  }

}



/* 步骤二：培训大纲界面 */

.step2-content {

  width: 100%;

  // max-width: 1200px;

  margin: 0 auto;



  .outline-container {

    display: flex;

    gap: 32px;

    height: 100%;

  }



  .outline-main {

    flex: 1;

    background: white;

    border-radius: 12px;

    width: 100px;

    min-height: 943px;

    height: calc(100vh - 80px);

    /* 自适应浏览器高度，减去头部和边距 */

    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

    overflow: hidden;

    margin-bottom: 20px;



    .outline-header {

      display: flex;

      align-items: center;

      justify-content: space-between;

      padding: 24px;

      border-bottom: 1px solid #E5E7EB;



      .outline-title-container {

        flex: 1;

        min-width: 0;

        margin-right: 16px;
      }



      .outline-title {

        font-size: 22px;

        font-weight: 600;

        color: #1F2937;

        margin: 0;

        cursor: pointer;

        transition: all 0.2s ease;

        word-break: break-word;





        &:hover:not(.disabled) {

          color: #3E7BFA;

          background: rgba(62, 123, 250, 0.05);

          border-radius: 4px;

          padding: 4px 8px;

          margin: -4px -8px;

        }



        &.disabled {

          cursor: not-allowed;

        }

      }



      .outline-actions {

        display: flex;

        gap: 6px;

        // margin-right: 24px;



        .action-btn {

          display: flex;

          align-items: center;

          gap: 6px;

          color: #374151;

          font-size: 14px;

          cursor: pointer;

          transition: all 0.3s ease;

          background: none;

          border: none;



          &:hover {

            background: none;

            border-color: transparent;

          }



          .action-icon {

            width: 16px;

            height: 16px;

          }

        }



        .exam-btn {

          color: #22B850;

          font-weight: 500;



          &:hover {

            background: none;

            color: #22B850;

          }



          .action-icon {

            width: 16px;

            height: 16px;

          }

        }

      }



      .edit-input-container {

        flex: 1;

        min-width: 0;

        margin-right: 16px;
      }



      .edit-textarea {

        width: 100%;

        border: 2px solid #3E7BFA;

        border-radius: 6px;

        padding: 8px 12px;

        font-size: 12px;

        color: #6B7280;

        background: white;

        outline: none;

        transition: all 0.2s ease;

        resize: vertical;

        min-height: 60px;

        font-family: inherit;

        line-height: 1.6;



        &:focus {

          border-color: #3E7BFA;

          box-shadow: 0 0 0 3px rgba(62, 123, 250, 0.1);

        }



        &.title-edit-textarea {

          font-size: 22px;

          font-weight: 600;

          color: #1F2937;

          min-height: 60px;

        }

      }



      .edit-input {

        width: 100%;

        border: 2px solid #3E7BFA;

        border-radius: 6px;

        padding: 8px 12px;

        font-size: inherit;

        font-weight: inherit;

        color: inherit;

        background: white;

        outline: none;

        transition: all 0.2s ease;



        &:focus {

          border-color: #2563EB;

          box-shadow: 0 0 0 3px rgba(62, 123, 250, 0.1);

        }



        &.title-edit-input {

          font-size: 22px;

          font-weight: 600;

          color: #1F2937;

        }

      }





    }



    .outline-content {

      padding: 32px;

      height: calc(100% - 80px);

      /* 自适应高度，减去头部高度 */

      overflow-y: auto;

      position: relative;



      .outline-content-scrollable {

        height: 100%;

        /* 占满父容器高度 */

        overflow-y: auto;

        padding-right: 16px;



        /* 自定义滚动条样式 */

        &::-webkit-scrollbar {

          width: 8px;

        }



        &::-webkit-scrollbar-track {

          background: #f1f1f1;

          border-radius: 4px;

        }



        &::-webkit-scrollbar-thumb {

          background: #c1c1c1;

          border-radius: 4px;



          &:hover {

            background: #a8a8a8;

          }

        }

      }



      .zoom-controls {

        position: absolute;

        top: 16px;

        right: 16px;

        display: flex;

        gap: 4px;

        z-index: 10;

        margin-right: 12px;



        .zoom-btn {

          border: none;

          background: none;

          cursor: pointer;

          display: flex;

          align-items: center;

          justify-content: center;





          .zoom-icon {

            width: 16px;

            height: 16px;

          }

        }

      }



      .edit-input-container {

        flex: 1;

        margin-right: 8px;

      }



      .edit-input-wrapper {

        width: 100%;

        position: relative;

      }



      .edit-options-inline {

        position: absolute;

        right: 8px;

        top: 50%;

        transform: translateY(-50%);

        display: flex;

        gap: 4px;

        z-index: 10;



        .edit-option-btn {

          background: none;

          border: none;

          padding: 4px;

          border-radius: 3px;

          cursor: pointer;

          transition: all 0.2s ease;

          display: flex;

          align-items: center;

          justify-content: center;

          width: 28px;

          height: 28px;



          &:hover {

            background: rgba(62, 123, 250, 0.1);

            transform: scale(1.1);

          }



          &.delete-btn:hover {

            background: rgba(239, 68, 68, 0.05);

          }



          .edit-icon {

            width: 16px;

            height: 16px;

            object-fit: contain;

          }

        }

      }



      .add-chapter-container {

        margin-top: 16px;

        padding: 16px;

        text-align: center;



        .add-chapter-btn {

          display: inline-flex;

          align-items: center;

          gap: 8px;

          padding: 12px 24px;

          background: linear-gradient(135deg, #3E7BFA 0%, #5A9BFF 100%);

          color: white;

          border: none;

          border-radius: 8px;

          font-size: 14px;

          font-weight: 500;

          cursor: pointer;

          transition: all 0.3s ease;

          box-shadow: 0 2px 8px rgba(62, 123, 250, 0.3);



          &:hover {

            transform: translateY(-2px);

            box-shadow: 0 4px 12px rgba(62, 123, 250, 0.4);

            background: linear-gradient(135deg, #2D6BFA 0%, #4A8BFF 100%);

          }



          &:active {

            transform: translateY(0);

            box-shadow: 0 2px 8px rgba(62, 123, 250, 0.3);

          }



          .add-icon {

            width: 16px;

            height: 16px;

            object-fit: contain;

            filter: brightness(0) invert(1);

          }

        }

      }



      .edit-input {

        width: 100%;

        border: 2px solid #3E7BFA;

        border-radius: 6px;

        padding: 8px 12px;

        font-size: inherit;

        font-weight: inherit;

        color: inherit;

        background: white;

        outline: none;

        transition: all 0.2s ease;



        &:focus {

          border-color: #2563EB;

          box-shadow: 0 0 0 3px rgba(62, 123, 250, 0.1);

        }



        &.title-edit-input {

          font-size: 22px;

          font-weight: 600;

          color: #1F2937;

        }



        &.chapter-edit-input {

          font-size: 20px;

          font-weight: 700;

          color: #111827;

          padding-right: 80px;

          /* 为图标留出空间 */

        }



        &.section-edit-input {

          font-size: 16px;

          font-weight: 600;

          color: #374151;

          padding-left: 24px;

          padding-right: 80px;

          /* 为图标留出空间 */

        }



        &.subsection-edit-input {

          font-size: 14px;

          font-weight: 400;

          color: #6B7280;

          padding-left: 48px;

          padding-right: 50px;

          /* 为图标留出空间，子小节只有删除按钮 */

        }



        &.subsubsection-edit-input {

          font-size: 13px;

          font-weight: 500;

          color: #9CA3AF;

          padding-right: 50px;

        }

      }



      .edit-textarea {

        width: 100%;

        border: 2px solid #3E7BFA;

        border-radius: 6px;

        padding: 8px 12px;

        font-size: 12px;

        color: #6B7280;

        background: white;

        outline: none;

        transition: all 0.2s ease;

        resize: vertical;

        min-height: 60px;

        font-family: inherit;

        line-height: 1.6;



        &:focus {

          border-color: #3E7BFA;

          box-shadow: 0 0 0 3px rgba(62, 123, 250, 0.1);

        }



        &.title-edit-textarea {

          font-size: 22px;

          font-weight: 600;

          color: #1F2937;

          min-height: 50px;

        }



        &.chapter-edit-textarea {

          font-size: 18px;

          font-weight: 500;

          color: #374151;

          min-height: 60px;

        }



        &.section-edit-textarea {

          font-size: 16px;

          font-weight: 500;

          color: #4B5563;

          min-height: 50px;

        }



        &.subsection-edit-textarea {

          font-size: 14px;

          font-weight: 400;

          color: #6B7280;

          min-height: 45px;

        }



        &.subsubsection-edit-textarea {

          font-size: 12px;

          font-weight: 400;

          color: #6B7280;

          min-height: 40px;

        }

      }



      .outline-chapter {

        // margin-bottom: 32px;

        position: relative;

        cursor: move;

        transition: all 0.3s ease;

        border: 2px solid transparent;

        border-radius: 8px;

        background: white;

        padding: 8px 0px 0px 8px;



        &:hover {

          border-color: #3E7BFA;

          box-shadow: 0 4px 12px rgba(62, 123, 250, 0.15);

        }



        &.dragging {

          opacity: 0.5;

          transform: rotate(2deg);

          box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);

          z-index: 1000;

        }



        &.drag-over {

          border-color: #10B981;

          background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(16, 185, 129, 0.1) 100%);

          box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);

          transform: scale(1.02);

        }



        // 拖拽提示点

        &::before {

          content: '';

          position: absolute;

          top: 12px;

          left: 12px;

          width: 4px;

          height: 4px;

          background: #9CA3AF;

          border-radius: 50%;

          opacity: 0;

          transition: opacity 0.3s ease;

        }



        &:hover::before {

          opacity: 1;

        }



        &.dragging::before {

          opacity: 0;

        }



        .chapter-header {

          display: flex;

          align-items: center;

          justify-content: space-between;

          margin-bottom: 20px;

          position: relative;

        }



        .chapter-title {

          font-size: 20px;

          font-weight: 700;

          color: #111827;

          margin: 0;

          cursor: pointer;

          transition: all 0.2s ease;

          flex: 1;



          &:hover {

            color: #3E7BFA;

            background: rgba(62, 123, 250, 0.05);

            border-radius: 4px;

          }

        }



        .outline-section {

          .section-container {

            margin-bottom: 16px;

            position: relative;

          }



          .section-header {

            display: flex;

            align-items: center;

            justify-content: space-between;

            margin-bottom: 12px;

            position: relative;

          }



          .section-title {

            font-size: 16px;

            color: #374151;

            margin: 0;

            line-height: 1.5;

            font-weight: 600;

            padding-left: 24px;

            position: relative;

            cursor: pointer;

            transition: all 0.2s ease;

            flex: 1;



            &:hover {

              color: #3E7BFA;

              background: rgba(62, 123, 250, 0.05);

              border-radius: 4px;

            }

          }



          .section-subsection {

            .subsection-container {

              margin-bottom: 8px;

              position: relative;

            }



            .subsection-header {

              display: flex;

              align-items: center;

              justify-content: space-between;

              position: relative;

            }



            .subsection-title {

              font-size: 14px;

              color: #6B7280;

              margin: 0;

              line-height: 1.5;

              font-weight: 700;

              padding-left: 48px;

              position: relative;

              cursor: pointer;

              transition: all 0.2s ease;

              flex: 1;



              &:hover {

                color: #3E7BFA;

                background: rgba(62, 123, 250, 0.05);

                border-radius: 4px;

              }

            }



            // 具体内容要点样式（-开头）

            .subsubsection-container {

              margin-left: 20px;

              margin-top: 8px;



              .subsubsection-item {

                margin-bottom: 12px;

                border-left: 2px solid #E5E7EB;

                padding-left: 16px;

              }



              .subsubsection-header {

                display: flex;

                align-items: center;

                justify-content: space-between;

                position: relative;

                margin-bottom: 8px;

              }



              .subsubsection-title {

                font-size: 13px;

                color: #6B7280;

                margin: 0;

                line-height: 1.4;

                font-weight: 400;

                cursor: pointer;

                transition: all 0.2s ease;

                flex: 1;



                &:hover {

                  color: #3E7BFA;

                  background: rgba(62, 123, 250, 0.05);

                  border-radius: 4px;

                }

              }



              .subsubsection-content {

                margin-top: 0;



                .subsubsection-text {

                  font-size: 12px;

                  color: #6B7280;

                  line-height: 1.6;

                  cursor: pointer;

                  padding: 0;

                  background: transparent;

                  border-radius: 0;

                  border: none;

                  transition: all 0.2s ease;

                  min-height: 0;



                  &:hover {

                    background: transparent;

                    border-color: transparent;

                  }



                  &:empty::before {

                    content: "点击添加正文内容...";

                    color: #9CA3AF;

                    font-style: italic;

                  }

                }

              }

            }

          }

        }



        // 编辑相关样式

        .edit-options {

          display: flex;

          gap: 4px;

          opacity: 0;

          transition: opacity 0.2s ease;

          position: absolute;

          right: 0;

          top: 50%;

          transform: translateY(-50%);

          background: white;

          border-radius: 6px;

          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);

          padding: 4px;

          z-index: 10;

        }



        .outline-chapter:hover .edit-options,

        .section-container:hover .edit-options,

        .subsection-container:hover .edit-options {

          opacity: 1;

        }







        .edit-input-container {

          flex: 1;

          margin-right: 8px;

        }

      }

    }

  }

}



.outline-sidebar {

  width: 400px;

  display: flex;

  flex-direction: column;

  gap: 24px;



  .sidebar-section {

    background: white;

    border-radius: 12px;

    overflow: hidden;

    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);



    .section-header {

      display: flex;

      align-items: center;

      gap: 8px;

      padding: 20px 20px 0px 20px;

      // background: #E8F0FF;



      .section-icon {

        width: 20px;

        height: 20px;

      }



      h5 {

        font-size: 16px;

        font-weight: 600;

        color: #1F2937;

        margin: 0;

      }

    }



    .section-content {

      padding: 20px 20px 12px 20px;



      .stat-item {

        display: flex;

        justify-content: space-between;

        align-items: center;

        margin-bottom: 12px;



        .stat-label {

          font-size: 14px;

          color: #6B7280;

        }



        .stat-value {

          font-size: 14px;

          font-weight: 600;

          color: #1F2937;

        }

      }



      .tip-list {

        list-style: none;

        padding: 0;

        margin: 0;



        li {

          font-size: 14px;

          color: #6B7280;

          line-height: 1.6;

          margin-bottom: 8px;

          padding-left: 16px;

          position: relative;



          &:before {

            content: "•";

            position: absolute;

            left: 0;

            color: #3E7BFA;

          }

        }

      }



      .evaluation-question {

        font-size: 14px;

        color: #374151;

        margin: 0 0 16px 0;

        line-height: 1.5;

      }



      .evaluation-buttons {

        display: flex;

        gap: 12px;



        .eval-btn {

          flex: 1;

          display: flex;

          align-items: center;

          justify-content: center;

          gap: 6px;

          padding: 10px 16px;

          border: 1px solid #D1D5DB;

          border-radius: 8px;

          background: #F3F4F6;

          color: #4B5563;

          font-size: 14px;

          cursor: pointer;

          transition: all 0.3s ease;



          &.satisfied {

            &.active {

              background: rgba(34, 184, 80, 0.1);

              color: #22B850;

              border-color: #22B850;

            }



            &:hover {

              background: rgba(34, 184, 80, 0.15);

              border-color: #22B850;

            }

          }



          &.unsatisfied {

            &.active {

              background: rgba(255, 77, 79, 0.1);

              color: #FF4D4F;

              border-color: #FF4D4F;

            }



            &:hover {

              background: rgba(255, 77, 79, 0.15);

              border-color: #FF4D4F;

            }

          }



          .eval-icon {

            width: 16px;

            height: 16px;

            filter: brightness(0) saturate(100%) invert(45%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(0%) contrast(0%);

            transition: filter 0.3s ease;

          }



          &.satisfied.active .eval-icon {

            filter: brightness(0) saturate(100%) invert(67%) sepia(61%) saturate(1237%) hue-rotate(86deg) brightness(95%) contrast(89%);

          }



          &.unsatisfied.active .eval-icon {

            filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2875%) hue-rotate(340deg) brightness(104%) contrast(107%);

          }

        }

      }

    }

  }



  .sidebar-actions {

    display: flex;

    gap: 12px;

    justify-content: center;

    margin-top: 12px;



    .action-btn {

      display: flex;

      align-items: center;

      justify-content: center;

      gap: 8px;

      padding: 16px 24px;

      border: none;

      border-radius: 8px;

      font-size: 15px;

      font-weight: 500;

      cursor: pointer;

      transition: all 0.3s ease;



      &.secondary {

        background: white;

        color: #3E7BFA;

        border: 1px solid #3E7BFA;



        &:hover {

          background: none;

          border-color: transparent;

        }



        .action-icon {

          width: 16px;

          height: 16px;

        }

      }



      &.primary {

        background: #3E7BFA;

        color: white;



        &:hover {

          background: #2563EB;

        }



        .action-icon {

          width: 16px;

          height: 16px;

        }

      }



      &.exam {

        background: #10B981;

        color: white;



        &:hover {

          background: #059669;

        }



        .action-icon {

          width: 16px;

          height: 16px;

        }

      }



      &.wps {

        background: #FF6B35;

        color: white;



        &:hover {

          background: #E55A2B;

        }



        .action-icon {

          width: 16px;

          height: 16px;

        }

      }

    }

  }

}





/* 推荐问题 */

.recommended-questions {

  display: flex;

  flex-wrap: wrap;

  gap: 5px;

  justify-content: center;

  max-width: 1000px;

  margin: 0 auto 0 auto;

  padding: 0 30px;



  .question-tag {

    background: white;

    padding: 9px 16px;

    border-radius: 20px;

    border: 1px solid #E5E8EB;

    font-size: 14px;

    color: #5A6C7D;

    cursor: pointer;

    transition: all 0.3s ease;

    display: flex;

    align-items: center;

    gap: 8px;

    white-space: nowrap;



    &:hover {

      border-color: #667eea;

      color: #667eea;

      transform: translateY(-1px);

    }



    .question-icon {

      width: 16px;

      height: 16px;

      object-fit: contain;

      flex-shrink: 0;

    }

  }

}



/* 文件预览区域 */

.file-preview-section {

  margin-bottom: 12px;



  .file-preview {

    position: relative;

    display: flex;

    align-items: center;

    background: rgba(255, 255, 255, 0.9);

    border: 1px solid #E5E7EB;

    border-radius: 12px;

    padding: 12px;

    max-width: 400px;



    .file-icon {

      font-size: 32px;

      margin-right: 12px;

      width: 48px;

      text-align: center;
      
      .file-icon-img {
        width: 32px;
        height: 32px;
        object-fit: contain;
      }

    }



    .file-info {

      flex: 1;



      .file-name {

        font-size: 14px;

        font-weight: 500;

        color: #374151;

        margin-bottom: 4px;

        overflow: hidden;

        text-overflow: ellipsis;

        white-space: nowrap;

        max-width: 200px;

      }



      .file-size {

        font-size: 12px;

        color: #6B7280;

      }

    }



    .remove-file-btn {

      width: 24px;

      height: 24px;

      border: none;

      background: rgba(239, 68, 68, 0.1);

      color: #DC2626;

      border-radius: 50%;

      cursor: pointer;

      display: flex;

      align-items: center;

      justify-content: center;

      transition: all 0.2s ease;



      &:hover {

        background: rgba(239, 68, 68, 0.2);

        transform: scale(1.1);

      }



      .remove-icon {

        font-size: 16px;

        font-weight: bold;

      }

    }

  }

}



/* 底部输入区域 */

.chat-input-section {

  background: transparent;

  padding: 9px 30px;



  .input-container {

    max-width: 900px;

    margin: 0 auto;



    .input-box {

      display: flex;

      align-items: center;

      // gap: 12px;

      background: white;

      border-radius: 16px;

      padding: 8px 20px;

      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

      transition: box-shadow 0.3s ease;

      border: 1px solid #3E7BFA;

      height: 57px;



      &:focus-within {

        box-shadow: 0 2px 12px rgba(62, 123, 250, 0.2);

      }



      .attach-btn,

      .voice-btn {

        background: none;

        border: none;

        cursor: pointer;

        padding: 8px;

        border-radius: 6px;

        transition: all 0.3s ease;

        display: flex;

        align-items: center;

        justify-content: center;

        position: relative;



        &:hover:not(:disabled) {

          background: rgba(102, 126, 234, 0.1);

        }



        &:disabled {

          cursor: not-allowed;

        }



        &.recording {

          background: rgba(239, 68, 68, 0.1);

          animation: pulse 1.5s ease-in-out infinite;

        }



        .icon-container {

          width: 20px;

          height: 20px;

          display: flex;

          align-items: center;

          justify-content: center;

          flex-shrink: 0;

          position: relative;

        }



        .recording-indicator {

          position: absolute;

          top: -2px;

          right: -2px;

          width: 8px;

          height: 8px;

          background: #ef4444;

          border-radius: 50%;

          animation: blink 1s ease-in-out infinite;

        }



        .action-icon {

          width: 20px;

          height: 20px;

          height: 20px !important;

          max-width: 20px !important;

          max-height: 20px !important;

          object-fit: contain;

          flex-shrink: 0;

        }

      }



      .message-input {

        flex: 1;

        border: none;

        background: transparent;

        font-size: 16px;

        color: #2C3E50;

        outline: none;

        transition: opacity 0.3s ease;



        &::placeholder {

          color: #A0A6B8;

        }



        &:disabled {

          cursor: not-allowed;

        }

      }



      .divider {

        width: 1px;

        height: 31px;

        background-color: #D6D5DE;

        margin: 0 4px;

      }



      .send-btn {

        background: none;

        border: none;

        cursor: pointer;

        border-radius: 6px;
        margin-left: 12px;

        transition: background 0.3s ease;

        display: flex;

        align-items: center;

        justify-content: center;



        &:hover:not(:disabled) {

          background: rgba(102, 126, 234, 0.1);

        }



        &:disabled {

          cursor: not-allowed;

        }



        .send-icon {

          width: 90px;

          height: 40px;

          object-fit: contain;

        }

      }

    }

  }

}



/* 步骤三：PPT模板选择界面 */

.step3-content {

  width: 100%;

  min-height: 888px;

  height: calc(100vh - 160px);

  /* 自适应浏览器高度，减去头部和边距 */

  margin: 0 auto;





  .preview-actions {

    display: flex;

    justify-content: flex-end;

    gap: 12px;

    margin-top: 24px;

    padding-right: 347px;



    .action-btn {

      display: flex;

      align-items: center;

      justify-content: center;

      gap: 8px;

      padding: 12px 20px;

      border: none;

      border-radius: 8px;

      font-size: 14px;

      font-weight: 500;

      cursor: pointer;

      transition: all 0.3s ease;



      &.secondary {

        background: white;

        color: #3E7BFA;

        border: 1px solid #3E7BFA;



        &:hover {

          background: rgba(62, 123, 250, 0.05);

        }

      }



      &.primary {

        background: #3E7BFA;

        color: white;



        &:hover {

          background: #2563EB;

        }



        &:disabled {

          background: #9CA3AF;

          cursor: not-allowed;

          opacity: 0.6;

        }

      }

    }

  }



  .template-container {

    display: flex;

    gap: 18px;

    height: 100%;

  }



  .template-preview {

    flex: 1;

    background: white;

    border-radius: 12px;

    padding: 21px 41px 0px 25px;

    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);



    .preview-header {

      display: flex;

      justify-content: space-between;

      align-items: center;

      margin-bottom: 24px;



      .preview-title {

        font-size: 16px;

        font-weight: 600;

        color: #1F2937;

        margin: 0;

      }



      .save-status {

        font-size: 14px;

        color: #6B7280;

      }

    }







    .main-carousel {

      position: relative;

      display: flex;

      align-items: center;

      justify-content: center;

      margin-bottom: 27px;

    }



    // PPT编辑工作台样式

    .ppt-editor-workspace {

      width: 100%;



      .editor-canvas {

        // background: white;

        // border: 2px solid #E5E7EB;

        // border-radius: 12px;

        // padding: 40px;

        // min-height: 603px;

        box-shadow: none;

        display: flex;

        justify-content: center;

        align-items: center;

        min-height: 603px;



        // PPT预览模式样式

        .slide-preview {

          width: 100%;

          max-width: 1105px;

          height: 603px;

          position: relative;

          border-radius: 12px;

          overflow: hidden;

          box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);

          background: transparent;





          .ppt-preview-tip {

            position: absolute;

            top: 10px;

            left: 10px;

            background: rgba(0, 0, 0, 0.7);

            color: white;

            padding: 8px 12px;

            border-radius: 6px;

            font-size: 12px;

            z-index: 1000;



            p {

              margin: 0;

            }

          }



          .preview-element {

            position: absolute;

            cursor: pointer;

            // border: 2px solid transparent;

            transition: border-color 0.2s ease;

            user-select: none;



            &.selected {

              border: 2px solid transparent;

              box-shadow: none;

            }



            &:hover {

              border: 2px solid transparent;

            }



            .resize-handle {

              display: none;

            }



            .drag-handle {

              display: none;

            }



            img {

              width: 100%;

              height: 100%;

              object-fit: cover;

            }



            div {

              width: 100%;

              height: 100%;

              display: flex;

              align-items: center;

              justify-content: center;

              text-align: center;

              padding: 10px;

              box-sizing: border-box;

            }



            .inline-editor {

              outline: 2px dashed #4c9ffe;

              background: rgba(255, 255, 255, 0.2);

              direction: ltr !important;

              text-align: center !important;

              unicode-bidi: normal !important;

            }



            .shape {

              width: 100%;

              height: 100%;

            }

          }



          .preview-element {

            position: absolute;

            cursor: pointer;

            transition: all 0.2s ease;



            &.selected {

              outline: none;

              outline-offset: 0;

            }



            .resize-handle {

              display: none;

            }



            .drag-handle {

              display: none;

            }



            .inline-editor {

              width: 100%;

              height: 100%;

              outline: none;

              border: 1px solid #3E7BFA;

              background: rgba(255, 255, 255, 0.9);

              padding: 4px;

              border-radius: 4px;

            }



            .shape {

              width: 100%;

              height: 100%;

            }



            img {

              width: 100%;

              height: 100%;

              object-fit: cover;

              border-radius: 0;

            }

          }

        }



        // 编辑模式样式

        .edit-mode {

          width: 100%;

          height: 100%;



          .slide-editor {

            width: 100%;

            height: 100%;



            .slide-content {

              outline: none;



              .slide-title {

                font-size: 32px;

                font-weight: 700;

                color: #1F2937;

                margin: 0 0 24px 0;

                text-align: center;

                border-bottom: 3px solid #3E7BFA;

                padding-bottom: 16px;



                &:focus {

                  background: rgba(62, 123, 250, 0.05);

                  border-radius: 8px;

                  padding: 8px;

                  margin: -8px -8px 16px -8px;

                }

              }



              .slide-body {

                font-size: 18px;

                color: #374151;

                line-height: 1.6;



                p {

                  margin: 16px 0;



                  &:focus {

                    background: rgba(62, 123, 250, 0.05);

                    border-radius: 6px;

                    padding: 8px;

                    margin: 8px -8px;

                  }

                }

              }

            }

          }

        }

      }

    }



    .carousel-btn {

      position: absolute;

      top: 50%;

      transform: translateY(-50%);

      background: #FFFFFF80;

      border: 1px solid #D1D5DB;

      border-radius: 50%;

      width: 40px;

      height: 40px;

      display: flex;

      align-items: center;

      justify-content: center;

      cursor: pointer;

      transition: all 0.3s ease;

      z-index: 10;



      &:hover {

        background: none;

        border-color: transparent;

      }



      &.prev {

        left: 18px;

      }



      &.next {

        right: 18px;

      }



      .carousel-icon {

        width: 6px;

        height: 10px;

      }

    }



    .main-slide {

      width: 100%;

      max-width: 1105px;

      height: 603px;

      border-radius: 8px;

      overflow: hidden;

      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);



      .slide-image {

        width: 100%;

        height: 100%;

        object-fit: cover;

      }

    }

  }



  .thumbnail-nav {

    display: flex;

    flex-direction: column;

    align-items: center;



    .slide-counter {

      font-size: 14px;

      color: #6B7280;

      text-align: center;

      margin-bottom: 36px;

      display: flex;

      align-items: center;

      justify-content: center;

      gap: 16px;



      .progress-dots {

        display: flex;

        gap: 4px;

        align-items: center;



        .progress-dot {

          width: 8px;

          height: 8px;

          border-radius: 50%;

          background: #D1D5DB;

          cursor: pointer;

          transition: all 0.3s ease;



          &:hover {

            background: #D1D5DB;

            transform: scale(1.2);

          }



          &.active {

            background: #3E7BFA;

            width: 16px;

            border-radius: 8px;

            transform: scale(1);

            box-shadow: 0 0 6px rgba(62, 123, 250, 0.4);

          }

        }

      }

    }



    .thumbnail-strip {

      display: flex;

      gap: 11px;

      max-width: 1050px;

      /* 限制最大宽度 */

      margin: 0 auto;

      /* 居中显示 */

      overflow-x: auto;

      /* 横向滚动 */

      justify-content: flex-start;

      /* 从左侧开始排列 */

      // padding: 0 10px;



      /* 自定义滚动条样式 */

      &::-webkit-scrollbar {

        height: 6px;

      }



      &::-webkit-scrollbar-track {

        background: #f1f1f1;

        border-radius: 3px;

      }



      &::-webkit-scrollbar-thumb {

        background: #c1c1c1;

        border-radius: 3px;



        &:hover {

          background: #a8a8a8;

        }

      }



      .thumbnail-item {

        width: 135px;

        height: 79px;

        border-radius: 6px;

        overflow: hidden;

        cursor: pointer;

        border: 2px solid transparent;

        transition: all 0.3s ease;

        position: relative;

        flex-shrink: 0;

        /* 防止缩略图被压缩 */

        margin: 0 auto;



        /* 确保单个缩略图也能居中 */

        &:hover {

          border-color: #3E7BFA;

          transform: translateY(-2px);

        }



        &.active {

          border-color: #3E7BFA;

          box-shadow: 0 2px 8px rgba(62, 123, 250, 0.3);

        }



        .thumbnail-preview {

          width: 100%;

          height: 100%;

          position: relative;

          overflow: hidden;

          border-radius: 4px;

        }



        .thumbnail-content {

          width: 100%;

          height: 100%;

          position: relative;

          transform: scale(0.22);

          transform-origin: -70px -20px;

        }



        .thumbnail-element {

          position: absolute;

          transform: scale(1);

        }



        .thumbnail-element img {

          max-width: 100%;

          max-height: 100%;

          object-fit: contain;

        }



        .thumbnail-element div {

          font-size: 6px;

          line-height: 1.1;

          overflow: hidden;

          text-overflow: ellipsis;

          white-space: nowrap;

          word-break: break-word;

          /* 确保文本样式与主图一致 */

          font-weight: inherit;

          text-align: inherit;

          text-shadow: inherit;

        }



        .thumbnail-element div p {

          margin: 0;

          padding: 0;

          font-size: inherit;

          line-height: inherit;

          color: inherit;

          font-weight: inherit;

          text-align: inherit;

          text-shadow: inherit;

        }



        .thumbnail-element div strong {

          font-weight: bold;

        }



        .thumbnail-element div span {

          font-size: inherit;

          color: inherit;

          font-weight: inherit;

        }



        .thumbnail-image {

          width: 100%;

          height: 100%;

          object-fit: cover;

        }



        .thumbnail-number {

          position: absolute;

          bottom: 4px;

          right: 4px;

          background: rgba(0, 0, 0, 0.5);

          color: #FFFFFF;

          font-size: 12px;

          font-weight: 500;

          padding: 2px 6px;

          border-radius: 3.81px;

          line-height: 1;

          min-width: 16px;

          text-align: center;

        }

      }

    }

  }

}



.template-sidebar {

  background: #FFFFFF;

  border-radius: 8px;

  // width: 320px;

  display: flex;

  flex-direction: column;

  gap: 16px;

  padding: 20px 19px 16px 19px;



  .sidebar-title {

    font-size: 15px;

    font-weight: 600;

    color: #1F2937;

    margin: 0 0 16px 0;

  }



  .template-list {

    display: flex;

    flex-direction: column;

    gap: 20px;



    .template-item {

      background: white;

      border-radius: 12px;

      padding: 16px;

      cursor: pointer;

      transition: all 0.3s ease;

      border: 2px solid #E4E4E4;



      &:hover {

        border-color: #E5E7EB;

        transform: translateY(-2px);

        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);

      }



      &.active {

        border-color: #3E7BFA;

        background: rgba(62, 123, 250, 0.05);

      }



      .template-thumbnail {

        width: 100%;

        height: 144px;

        width: 256px;

        border-radius: 8px;

        overflow: hidden;

        margin-bottom: 12px;

        border: none;

        position: relative;



        .template-img {

          width: 100%;

          height: 100%;

          object-fit: cover;

          border: none !important;

          outline: none !important;

        }



        .dynamic-badge {

          position: absolute;

          top: 8px;

          right: 8px;

          background: #10b981;

          color: white;

          font-size: 12px;

          padding: 4px 8px;

          border-radius: 4px;

          font-weight: 600;

          z-index: 10;

        }

      }



      .template-info {

        .template-title {

          font-size: 15px;

          font-weight: 600;

          color: #1F2937;

          margin: 0 0 8px 0;

          line-height: 1.4;

        }



        .template-meta {

          display: flex;

          justify-content: space-between;

          align-items: center;



          .update-time {

            font-size: 13px;

            color: #6B7280;

          }



          .page-count {

            font-size: 13px;

            color: #6B7280;

            font-weight: 500;

          }

        }



        .template-description {

          font-size: 13px;

          color: #4a5568;

          margin-top: 8px;

          line-height: 1.4;

        }

      }

    }

  }



  .dynamic-preview {

    margin-top: 16px;

    padding: 16px;

    background: #f7fafc;

    border-radius: 8px;

    border: 1px solid #e2e8f0;



    .preview-title {

      font-size: 14px;

      font-weight: 600;

      color: #2d3748;

      margin: 0 0 12px 0;

    }



    .preview-stats {

      display: grid;

      grid-template-columns: 1fr 1fr;

      gap: 8px;

      margin-bottom: 12px;



      .stat-item {

        display: flex;

        justify-content: space-between;

        align-items: center;

        padding: 6px 8px;

        background: white;

        border-radius: 4px;

        border: 1px solid #e2e8f0;



        .stat-label {

          font-size: 12px;

          color: #718096;

        }



        .stat-value {

          font-size: 12px;

          font-weight: 600;

          color: #2d3748;



          &.complexity-simple {

            color: #10b981;

          }



          &.complexity-medium {

            color: #f59e0b;

          }



          &.complexity-complex {

            color: #ef4444;

          }

        }

      }

    }



    .recommendations,

    .warnings {

      margin-top: 12px;



      .recommendations-title,

      .warnings-title {

        font-size: 12px;

        font-weight: 600;

        color: #2d3748;

        margin: 0 0 8px 0;

      }



      .recommendations-list,

      .warnings-list {

        margin: 0;

        padding-left: 16px;



        li {

          font-size: 12px;

          color: #4a5568;

          margin-bottom: 4px;

          line-height: 1.3;

        }

      }



      &.warnings {

        .warnings-title {

          color: #ef4444;

        }



        .warnings-list li {

          color: #dc2626;

        }

      }

    }

  }

}





.template-sidebar {

  background: #FFFFFF;

  border-radius: 8px;

  // width: 320px;

  display: flex;

  flex-direction: column;

  gap: 16px;

  padding: 20px 19px 16px 19px;



  .sidebar-title {

    font-size: 15px;

    font-weight: 600;

    color: #1F2937;

    // margin: 0 0 16px 0;

  }



  .template-list {

    display: flex;

    flex-direction: column;

    gap: 20px;



    .template-item {

      background: white;

      border-radius: 12px;

      padding: 16px;

      cursor: pointer;

      transition: all 0.3s ease;

      border: 2px solid #E4E4E4;



      &:hover {

        border-color: #E5E7EB;

        transform: translateY(-2px);

        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);

      }



      &.active {

        border-color: #3E7BFA;

        background: rgba(62, 123, 250, 0.05);

      }



      .template-thumbnail {

        width: 100%;

        height: 144px;

        width: 256px;

        border-radius: 8px;

        overflow: hidden;

        margin-bottom: 12px;



        .template-img {

          width: 100%;

          height: 100%;

          object-fit: cover;

        }

      }



      .template-info {

        .template-title {

          font-size: 15px;

          font-weight: 600;

          color: #1F2937;

          margin: 0 0 8px 0;

          line-height: 1.4;

        }



        .template-meta {

          display: flex;

          justify-content: space-between;

          align-items: center;



          .update-time {

            font-size: 13px;

            color: #6B7280;

          }



          .page-count {

            font-size: 13px;

            color: #6B7280;

            font-weight: 500;

          }

        }

      }

    }

  }



  // 下载选项样式

  .download-content {







    .download-options {

      display: flex;

      flex-direction: column;

      gap: 16px;



      .download-option {

        background: #F9FAFB;

        border-radius: 8px;

        padding: 13px;

        width: 292px;

        height: 66px;

        cursor: pointer;

        transition: all 0.3s ease;

        border: 2px solid transparent;

        display: flex;

        align-items: center;

        gap: 12px;

        position: relative;



        &:hover {

          background: none;

          border-color: transparent;

        }



        &.active {

          background: white;

          border-color: #3E7BFA;

          box-shadow: 0 2px 8px rgba(62, 123, 250, 0.1);

        }



        .option-icon {

          width: 40px;

          height: 40px;

          flex-shrink: 0;



          .option-img {

            width: 100%;

            height: 100%;

            object-fit: contain;

          }

        }



        .option-info {

          flex: 1;



          .option-title {

            font-size: 16px;

            font-weight: 600;

            color: #111827;

            margin: 0 0 4px 0;

          }



          .option-description {

            font-size: 12px;

            color: #6B7280;

            margin: 0;

            line-height: 1.4;

          }

        }



        .option-check {

          width: 24px;

          height: 24px;

          flex-shrink: 0;



          .check-icon {

            width: 100%;

            height: 100%;

            object-fit: contain;

          }

        }

      }

    }



    .download-actions {

      display: flex;

      flex-direction: column;

      gap: 12px;

      margin-top: 485px;



      .action-btn {

        display: flex;

        align-items: center;

        justify-content: center;

        gap: 8px;

        padding: 12px 20px;

        border: none;

        border-radius: 8px;

        font-size: 14px;

        font-weight: 500;

        cursor: pointer;

        transition: all 0.3s ease;



        &.secondary {

          background: white;

          color: #3E7BFA;

          border: 1px solid #3E7BFA;



          &:hover {

            background: rgba(62, 123, 250, 0.05);

          }

        }



        &.primary {

          background: #3E7BFA;

          color: white;



          &:hover {

            background: #2563EB;

          }



          .download-icon {

            width: 16px;

            height: 16px;

          }

        }





      }

    }

  }

}
</style>
