﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿<template>
  <div class="read-page">
    <!-- 功能标签栏 -->
    <TabsBar
      :tabs="readModuleTabs"
      v-model="activeTab"
    />
    
    <!-- 上传任务面板 -->
    <UploadTaskPanel
      v-if="showTaskPanel"
      :stats="taskStats"
      v-model:status-filter="taskStatusFilter"
      v-model:search-query="taskSearchQuery"
      v-model:current-page="taskCurrentPage"
      :paginated-tasks="taskPaginatedTasks"
      :total-filtered-pages="taskTotalPages"
      :loading="taskLoading"
      :polling="taskPolling"
      :parse-vector-status="parseVectorStatus"
      :parse-exam-status="parseExamStatus"
      @refresh="refreshUploadTasks"
    />
    
    <!-- 文档阅读工具栏（仅在文档阅读tab时显示）—— 精简版：仅保留返回按钮和文件名 -->
    <div v-show="activeTab === 'reader'" class="reader-toolbar-wrapper">
      <div class="reader-toolbar">
        <a-button class="btn btn-small" @click="handleBackToList">
          <template #icon><MenuUnfoldOutlined /></template>
          返回列表
        </a-button>

        <div v-if="selectedDocument" class="doc-info">
          <FileTextOutlined />
          <span>{{ selectedDocument.title }}</span>
          <a-tag size="small">{{ selectedDocument.version }}</a-tag>
        </div>
      </div>
    </div>

    <!-- ========== 文件列表面板 ========== -->
    <div v-show="activeTab === 'list'" class="exam-container">
      <div class="read-content">
        <PageHeader
            :title="currentFileType === 'system' ? '制度文件列表' : '个人文件列表'"
            description="点击文件查看详情并进行学习"
          />

          <!-- 文件类型切换条 -->
          <div class="file-type-toggle-bar">
            <button 
              :class="['toggle-btn', { active: currentFileType === 'system' }]"
              @click="currentFileType = 'system'"
            >
              <span class="toggle-icon"><FileTextOutlined /></span>
              <span class="toggle-text">制度文件</span>
            </button>
            <button 
              :class="['toggle-btn', { active: currentFileType === 'personal' }]"
              @click="currentFileType = 'personal'"
            >
              <span class="toggle-icon"><UserOutlined /></span>
              <span class="toggle-text">个人文件</span>
            </button>
          </div>

          <StatsRow :stats="fileStats" />

          <div class="filter-bar">
            <a-input
              placeholder="搜索文件名称、描述或标签..."
              v-model:value="searchQuery"
              class="form-input"
              allow-clear
            >
              <template #prefix><SearchOutlined /></template>
            </a-input>
            
            <select
              v-model="selectedCategory"
              class="form-select"
            >
              <option v-for="cat in categories" :key="cat" :value="cat">
                {{ cat === 'all' ? '全部' : cat }}
              </option>
            </select>
            
            <a-button type="primary" class="btn btn-primary" @click="handleFileUploadClick" v-if="canUploadFile">
              <template #icon><UploadOutlined /></template>
              上传文件
            </a-button>
            <button
              v-if="uploadTasks.length > 0"
              class="btn btn-secondary btn-sm"
              style="margin-left: auto;"
              @click="showTaskPanel = !showTaskPanel"
            >
📋 上传任务 ({{ uploadTasks.length }})
</button>
            <input
              ref="fileUploadRef"
              type="file"
              accept=".pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.txt,.md,.csv,.json,.xml,.html,.htm,.jpg,.jpeg,.png,.gif,.webp,.bmp,.svg,.epub,.rtf"
              style="display: none;"
              @change="handleFileUpload"
            />
          </div>

          <div v-if="!showVersionList" class="exam-card">
            <div class="card-title">
              <span>文件列表</span>
              <span class="card-subtitle">共 {{ filteredDocuments.length }} 个文件</span>
              <a-spin v-if="loading" size="small" style="margin-left: 12px;" />
            </div>
            
            <!-- 条形列表 -->
            <div class="file-list-bar" style="overflow: hidden;">
              <div
                v-for="(item, index) in paginatedDocuments"
                :key="item.id"
                :class="['file-bar', { 'file-bar-empty': item.isEmpty }]"
                @click="!item.isEmpty && handleDocumentClick(item)"
              >
                <template v-if="!item.isEmpty">
                  <div class="file-bar-icon">
                    <FileTextOutlined />
                    <span class="file-bar-ext">{{ item.extension?.toUpperCase() || '-' }}</span>
                  </div>
                  <div class="file-bar-body">
                    <div class="file-bar-title">{{ item.title }}</div>
                    <div class="file-bar-tags">
                      <a-tag color="blue" size="small">{{ item.category }}</a-tag>
                      <a-tag v-if="item.versions && item.versions.length > 1" color="purple" size="small">
                        {{ item.versions.length }}个版本
                      </a-tag>
                    </div>
                  </div>
                  <div class="file-bar-meta">
                    <span><CalendarOutlined /> {{ item.updateDate }}</span>
                    <span><EyeOutlined /> {{ item.views }}</span>
                  </div>
                  <div v-if="currentFileType === 'personal' && item.uploadUserName" class="file-bar-uploader">
                    <UserOutlined />
                    <span>{{ item.uploadUserName }}</span>
                  </div>
                </template>
              </div>
            </div>

            <!-- 分页器 - 使用后端分页参数 -->
            <div v-if="fileTotal > 0" class="file-pagination">
              <a-pagination
                v-model:current="fileCurrentPage"
                :total="fileTotal"
                :page-size="filePageSize"
                :show-total="(total) => `共 ${total} 个文件`"
                :show-quick-jumper="true"
                :show-size-changer="false"
                size="small"
                @change="handleFilePageChange"
              />
            </div>

            <div v-if="!loading && filteredDocuments.length === 0" class="empty-state">
              <SearchOutlined />
              <h3>未找到匹配的文件</h3>
              <p>请尝试其他搜索词或筛选条件</p>
            </div>
          </div>

          <!-- 版本详情面板 -->
          <div v-else class="exam-card">
            <div class="card-title version-detail-header">
              <div class="version-detail-left">
                <a-button class="btn btn-small" @click="handleBackFromVersions">
                  <template #icon><MenuUnfoldOutlined /></template>
                  返回文件列表
                </a-button>
                <span class="version-detail-title">{{ selectedFileForVersions?.title }}</span>
                <span class="card-subtitle">共 {{ selectedFileForVersions?.versions?.length || 0 }} 个版本</span>
              </div>
              <div class="version-detail-actions">
                <a-button 
                  v-if="canReadDocument && selectedFileForVersions?.versions?.length >= 2"
                  class="btn btn-small"
                  @click="openCompareModal"
                >
                  <template #icon><DiffOutlined /></template>
                  版本对比
                </a-button>
              </div>
            </div>

            <div class="version-meta-bar" v-if="selectedFileForVersions">
              <div class="meta-item">
                <span class="meta-label">当前版本</span>
                <span class="meta-value">v{{ selectedFileForVersions.currentVersion || '-' }}</span>
              </div>
              <div class="meta-item">
                <span class="meta-label">版本数量</span>
                <span class="meta-value">{{ selectedFileForVersions.versions?.length || 0 }}</span>
              </div>
              <div class="meta-item">
                <span class="meta-label">最近更新</span>
                <span class="meta-value">{{ selectedFileForVersions.updateDate || '-' }}</span>
              </div>
            </div>
            
            <div class="version-timeline">
              <div
                v-for="(ver, idx) in selectedFileForVersions?.versions || []"
                :key="ver.id"
                class="version-timeline-item"
                :class="{
                  'is-latest': idx === 0,
                  'compare-pending': comparePendingVersion?.id === ver.id
                }"
              >
                <div class="timeline-dot"></div>
                <div class="timeline-connector" v-if="idx < (selectedFileForVersions?.versions?.length || 0) - 1"></div>
                <div class="version-timeline-card">
                  <div class="timeline-card-header">
                    <div class="timeline-card-left">
                      <span class="version-tag">v{{ ver.version }}</span>
                      <span v-if="idx === 0" class="latest-badge">最新</span>
                      <a-badge :status="ver.status === 'active' ? 'success' : 'warning'" />
                    </div>
                    <div class="timeline-card-actions">
                      <a-button
                        v-if="canReadDocument"
                        class="btn btn-small"
                        @click.stop="handleVersionSelect(ver)"
                      >
                        <template #icon><EyeOutlined /></template>
                        查看
                      </a-button>
                      <a-button
                        v-if="canManageVersion"
                        class="btn btn-small"
                        :class="{ 'btn-primary-outline': !isVersionVectorized(ver.id) }"
                        @click.stop="openVectorizeModal(ver)"
                      >
                        <template #icon><DatabaseOutlined /></template>
                        向量化
                      </a-button>
                      <a-button
                        v-if="isVersionVectorized(ver.id) && canReadDocument"
                        class="btn btn-small btn-success-outline"
                        :loading="generatingExamId === ver.id"
                        :disabled="generatingExamId === ver.id"
                        @click.stop="handleGenerateExamForFile(ver)"
                      >
                        <template #icon><EditOutlined /></template>
                        {{ generatingExamId === ver.id ? '出题中...' : '出题' }}
                      </a-button>
                      <a-button
                        v-if="canManageVersion && isVersionVectorized(ver.id)"
                        class="btn btn-small btn-warning-outline"
                        :loading="devectorizingId === ver.id"
                        :disabled="devectorizingId === ver.id"
                        @click.stop="handleDevectorize(ver)"
                      >
                        <template #icon><DislikeOutlined /></template>
                        {{ devectorizingId === ver.id ? '处理中...' : '去向量化' }}
                      </a-button>
                      <a-button 
                        v-if="canManageVersion && idx !== 0"
                        class="btn btn-small btn-danger-outline"
                        @click.stop="openRollbackModal(ver)"
                      >
                        <template #icon><RollbackOutlined /></template>
                        回退
                      </a-button>
                      <a-button 
                        v-if="canManageVersion"
                        class="btn btn-small btn-danger-outline"
                        @click.stop="handleDeleteFile(ver)"
                      >
                        <template #icon><DeleteOutlined /></template>
                        删除
                      </a-button>
                    </div>
                  </div>
                  <div class="timeline-card-body">
                    <div class="timeline-card-meta">
                      <span><CalendarOutlined /> {{ ver.updateDate }}</span>
                      <span><EyeOutlined /> {{ ver.views }} 次浏览</span>
                      <span v-if="ver.uploadUserName"><UserOutlined /> {{ ver.uploadUserName }}</span>
                      <span>{{ formatFileSize(ver.fileSize) }}</span>
                    </div>
                    <!-- 文件标签：按文档5.1.3规范，支持tags字段读写 -->
                    <div v-if="ver.tags && ver.tags.length > 0 || canManageVersion" class="doc-tags-row">
                      <template v-if="editingTagsId !== ver.id">
                        <span class="doc-tags-label">标签:</span>
                        <div class="doc-tags">
                          <a-tag v-for="(tag, tagIdx) in (ver.tags || [])" :key="tagIdx" color="blue" size="small">{{ tag }}</a-tag>
                          <span v-if="!ver.tags || ver.tags.length === 0" class="doc-tags-empty">暂无标签</span>
                        </div>
                        <a-button
                          v-if="canManageVersion"
                          class="btn btn-small btn-text"
                          size="small"
                          @click.stop="startEditTags(ver)"
                        >
                          <template #icon><EditOutlined /></template>
                          编辑
                        </a-button>
                      </template>
                      <template v-else>
                        <span class="doc-tags-label">标签:</span>
                        <a-input
                          v-model:value="editingTagsText"
                          size="small"
                          placeholder="输入标签，用逗号分隔"
                          style="flex: 1; max-width: 240px;"
                          @click.stop
                          @keyup.enter="saveFileTags(ver)"
                        />
                        <a-button class="btn btn-small btn-primary-outline" size="small" @click.stop="saveFileTags(ver)">
                          保存
                        </a-button>
                        <a-button class="btn btn-small btn-text" size="small" @click.stop="cancelEditTags">
                          取消
                        </a-button>
                      </template>
                    </div>
                    <div class="timeline-card-toggle" @click.stop="handleToggleVersionExpand(ver)">
                      <a-button class="btn btn-small btn-text" size="small">
                        <template #icon>
                          <component :is="expandedVersionId === ver.id ? 'CaretUpOutlined' : 'CaretDownOutlined'" />
                        </template>
                        {{ expandedVersionId === ver.id ? '收起' : '试卷' }}
                      </a-button>
                    </div>
                    <div v-if="expandedVersionId === ver.id" class="version-sub-panel">
                      <div class="sub-panel-header-row">
                        <span class="sub-panel-title-text">
                          <EditOutlined />
                          关联试卷
                        </span>
                        <button
                          v-if="!versionPapers[ver.id] && !versionPapersLoading[ver.id]"
                          class="sub-refresh-btn"
                          @click.stop="fetchVersionPapers(ver)"
                        >
                          <ReloadOutlined :spin="false" />
                          加载试卷
                        </button>
                      </div>
                      <div class="sub-panel-content">
                        <div v-if="versionPapersLoading[ver.id]" class="sub-panel-loading">
                          <LoadingOutlined spin />
                          <span>正在获取试卷数据...</span>
                        </div>
                        <div v-else-if="versionPapers[ver.id] && versionPapers[ver.id].length > 0" class="sub-papers-list">
                          <div
                            v-for="paper in versionPapers[ver.id]"
                            :key="paper.paperId"
                            :class="['sub-paper-item', { 'is-clickable': paper.status === 'published' }]"
                            @click.stop="handleStartPaperExam(paper, ver)"
                          >
                            <div class="sub-paper-header">
                              <span class="sub-paper-title">{{ paper.title }}</span>
                              <a-tag :color="paper.status === 'published' ? 'green' : 'blue'" size="small">
                                {{ paper.status === 'published' ? '已发布' : paper.status === 'draft' ? '草稿' : (paper.status || '未知') }}
                              </a-tag>
                              <span v-if="paper.status === 'published'" class="sub-paper-action-hint">点击开始考试</span>
                            </div>
                            <div class="sub-paper-meta">
                              <span>题目数: {{ paper.questionCount || 0 }}</span>
                              <span v-if="paper.totalScore">总分: {{ paper.totalScore }}</span>
                              <span v-if="paper.duration">时长: {{ paper.duration }}分钟</span>
                            </div>
                          </div>
                        </div>
                        <div v-else class="sub-panel-empty">
                          <p>该版本暂无关联试卷</p>
                          <button class="sub-retry-btn" @click.stop="fetchVersionPapers(ver)">
                            <ReloadOutlined />
                            重新加载
                          </button>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        </div>

    <!-- ========== 文件审批面板 ========== -->
    <div v-show="activeTab === 'approve'" class="exam-container">
      <div class="read-content">
        <div class="approve-header">
          <span class="approve-title">文件审批</span>
          <span class="approve-stats">
            待审 <strong>{{ pendingCount }}</strong> · 通过 {{ approvedCount }} · 驳回 {{ rejectedCount }}
          </span>
        </div>

        <div class="filter-bar">
          <a-input
            placeholder="搜索文件..."
            v-model:value="approveSearchQuery"
            class="form-input"
            allow-clear
            size="small"
          >
            <template #prefix><SearchOutlined /></template>
          </a-input>
          <select v-model="approveFilterStatus" class="form-select">
            <option value="pending">待审批</option>
            <option value="approved">已通过</option>
            <option value="rejected">已驳回</option>
            <option value="all">全部</option>
          </select>
          <a-button size="small" @click="loadPendingFiles">
            <template #icon><ReloadOutlined /></template>
            刷新
          </a-button>
        </div>

        <div class="exam-card">
          <div class="card-title">
            <span>{{ approveFilterStatus === 'pending' ? '待审批' : approveFilterStatus === 'approved' ? '已通过' : approveFilterStatus === 'rejected' ? '已驳回' : '全部' }}</span>
            <span class="card-subtitle">{{ approveTotal }} 个文件</span>
          </div>

          <div v-if="paginatedApproveFiles.length > 0" class="approve-list">
            <div v-for="file in paginatedApproveFiles" :key="file.id" class="approve-item">
              <div class="approve-item-left">
                <FileTextOutlined class="approve-file-icon" />
                <div class="approve-item-info">
                  <span class="approve-file-name">{{ file.fileName }}</span>
                  <span class="approve-file-meta">
                    {{ file.uploadUserName || '未知' }} · {{ file.createTime?.split('T')[0] || file.updateTime?.split('T')[0] || '-' }}{{ file.deptName ? ` · ${file.deptName}` : '' }} · {{ formatFileSize(file.fileSize) }}
                  </span>
                  <span v-if="file.description" class="approve-file-desc">{{ file.description }}</span>
                </div>
              </div>
              <div class="approve-item-actions">
                <span :class="['approve-status', file.auditStatus === 1 ? 'approved' : file.auditStatus === 2 ? 'rejected' : 'pending']">
                  {{ file.auditStatus === 1 ? '已通过' : file.auditStatus === 2 ? '已驳回' : '待审批' }}
                </span>
                <template v-if="file.auditStatus === 0">
                  <button class="action-btn approve-btn" @click="handleApproveFile(file)" title="批准该文件">
                    <CheckCircleOutlined class="action-icon" />
                    <span class="action-text">通过</span>
                  </button>
                  <button class="action-btn reject-btn" @click="showRejectConfirm(file)" title="驳回该文件">
                    <CloseCircleOutlined class="action-icon" />
                    <span class="action-text">驳回</span>
                  </button>
                </template>
              </div>
            </div>
          </div>

          <div v-if="approveTotal > 0" class="file-pagination">
            <a-pagination
              v-model:current="approveCurrentPage"
              :total="approveTotal"
              :page-size="approvePageSize"
              :show-total="(total) => `共 ${total} 个`"
              :show-quick-jumper="true"
              :show-size-changer="false"
              size="small"
              @change="handleApprovePageChange"
            />
          </div>

          <div v-if="paginatedApproveFiles.length === 0 && !approveLoading" class="empty-state">
            <AuditOutlined class="empty-icon" />
            <p>暂无{{ approveFilterStatus === 'pending' ? '待审批' : '' }}文件</p>
          </div>
        </div>
      </div>
    </div>

    <!-- ========== 题目管理与审批面板 ========== -->
    <div v-show="activeTab === 'questionBank'" class="exam-container">
      <div class="read-content">
        <QuestionBankPanel />
      </div>
    </div>

    <!-- ========== 文档阅读器面板 ========== -->
    <div v-show="activeTab === 'reader'" class="exam-container">
      <div class="reader-container" :class="{ 'reader-container-no-scroll': currentViewerFileType === 'pdf' }">
          <div v-if="selectedFileId" class="pdf-container">
            <UniversalFileReader
              ref="fileReaderRef"
              :file-id="selectedFileId"
              :file-name="selectedFileName"
              :file-extension="selectedFileExtension"
              :annotations="annotations"
              :current-mode="currentMode"
              :selected-color="selectedColor"
              :current-page="currentPage"
              :num-pages="numPages"
              :scale="scale"
              :show-highlights="showHighlights"
              :is-reading="isReading"
              :reading-speed="readingSpeed"
              :reading-range="readingRange"
              @back="handleBackToList"
              @page-change="(page) => currentPage = page"
              @num-pages-change="(pages) => numPages = pages"
              @scale-change="(newScale) => scale = newScale"
              @annotations-change="handleAnnotationsChange"
              @highlight-click="handleAnnotationClick"
              @reading-status-change="(status) => isReading = status"
              @reading-progress-change="(progress) => readingProgress = progress * 100"
              @reading-text-change="(text) => readingText = text"
            />
          </div>
          <div v-else class="pdf-placeholder">
            <FileTextOutlined />
            <h2>{{ selectedDocument ? '正在加载文件...' : '请选择文件' }}</h2>
            <p>{{ selectedDocument ? '请稍候，正在加载文件...' : '点击返回列表选择文件' }}</p>
            <a-button v-if="!selectedDocument" type="primary" class="btn btn-primary" @click="handleBackToList">
              <template #icon><UploadOutlined /></template>
              返回列表
            </a-button>
          </div>
        </div>
    </div>

    <!-- 无权限提示 -->
    <div v-if="!canViewFileList && !canReadDocument && !canUploadFile && !canFavorite && !canApproveFile && !canViewQuestionBank" class="empty-state no-permission">
      <FileTextOutlined />
      <h3>暂无权限</h3>
      <p>您没有制度文件查看与学习模块的任何权限，请联系管理员</p>
    </div>

    <!-- 文件上传弹窗 -->
    <div v-if="showUploadModal" class="upload-modal-overlay" @click.self="closeUploadModal">
      <div class="upload-modal">
        <div class="upload-modal-header">
          <h3>上传文件</h3>
          <button class="upload-modal-close" @click="closeUploadModal">✕</button>
        </div>
        
        <div class="upload-modal-body">
          <div class="form-group">
            <label class="form-label">选择文件 <span class="required">*</span></label>
            <div 
              class="file-drop-zone"
              :class="{ 'drag-over': isDragOver }"
              @dragover.prevent="isDragOver = true"
              @dragleave.prevent="isDragOver = false"
              @drop.prevent="handleFileDrop"
              @click="$refs.uploadFileInput?.click()"
            >
              <input
                ref="uploadFileInput"
                type="file"
                accept=".pdf,.doc,.docx,.ppt,.pptx,.txt,.xlsx,.xls,.png,.jpg,.jpeg,.gif,.webp,.json,.xml,.csv,.md,.html,.log,.js,.css,.bmp,.svg"
                style="display:none;"
                @change="handleFileSelect"
              />
              <div v-if="!uploadForm.file" class="drop-placeholder">
                <UploadOutlined class="drop-icon" />
                <p class="drop-text">点击选择或拖拽文件至此</p>
              </div>
              <div v-else class="selected-file-info">
                <FileTextOutlined class="selected-file-icon" />
                <div class="selected-file-detail">
                  <span class="file-name">{{ uploadForm.file.name }}</span>
                  <span class="file-size">{{ formatFileSize(uploadForm.file.size) }}</span>
                </div>
                <button class="remove-file-btn" @click.stop="removeSelectedFile">✕</button>
              </div>
            </div>
          </div>

          <div v-if="uploadForm.isPublic" class="form-group">
            <label class="form-label">所属部门 <span class="required">*</span></label>
            <select 
              v-model="uploadForm.deptId" 
              :disabled="!isAdminUser(userInfo)"
              class="form-select"
            >
              <option v-if="isAdminUser(userInfo)" value="">请选择部门</option>
              <option 
                v-for="dept in uploadDepartmentList" 
                :key="dept.id" 
                :value="dept.id"
              >
                {{ dept.deptName || dept.name }}
              </option>
            </select>
            <p class="form-hint">{{ isAdminUser(userInfo) ? '管理员可选择任意部门上传' : '当前用户所属部门（不可修改）' }}</p>
          </div>
          <div v-else class="form-group">
            <label class="form-label inactive">所属部门</label>
            <div class="dept-hint-box">
              个人文件无需指定所属部门
            </div>
          </div>

          <div class="form-group">
            <label class="form-label">文件类型 <span class="required">*</span></label>
            <div class="radio-group">
              <label class="radio-item" :class="{ active: uploadForm.isPublic === true }">
                <input type="radio" v-model="uploadForm.isPublic" :value="true" />
                <div class="radio-text">
                  <span class="radio-label">公开文件</span>
                  <span class="radio-desc">所有用户均可查看</span>
                </div>
              </label>
              <label class="radio-item" :class="{ active: uploadForm.isPublic === false }">
                <input type="radio" v-model="uploadForm.isPublic" :value="false" />
                <div class="radio-text">
                  <span class="radio-label">个人文件</span>
                  <span class="radio-desc">仅自己可查看</span>
                </div>
              </label>
            </div>
          </div>

          <div class="form-group">
            <label class="form-label">文件描述</label>
            <textarea
              v-model="uploadForm.description"
              class="form-textarea"
              rows="3"
              placeholder="请输入文件描述（选填）..."
              maxlength="500"
            ></textarea>
            <p class="char-count">{{ uploadForm.description.length }}/500</p>
          </div>

          <div class="form-group">
            <label class="checkbox-label">
              <input type="checkbox" v-model="uploadForm.autoVectorize" />
              <span class="checkbox-text">上传后自动向量化到知识库</span>
            </label>
            <p class="form-hint">勾选后，文件上传成功将自动进行向量处理，可用于AI问答和出题</p>
          </div>
        </div>

        <div class="upload-modal-footer">
          <button class="btn btn-secondary" @click="closeUploadModal">取消</button>
          <button 
            class="btn btn-primary" 
            @click="handleUploadSubmit"
            :disabled="!isUploadFormValid || isUploading"
          >
            {{ isUploading ? '上传中...' : '确认上传' }}
          </button>
        </div>
      </div>
    </div>

    <!-- 驳回确认弹窗 -->
    <div v-if="showRejectModal" class="modal-overlay" @click.self="showRejectModal = false">
      <div class="modal-content">
        <div class="modal-header">
          <h3>驳回文件</h3>
          <button class="close-btn" @click="showRejectModal = false">
            <CloseOutlined />
          </button>
        </div>
        <div class="modal-body">
          <p style="margin-bottom: var(--space-md); color: var(--text-secondary);">
            确定要驳回文件 <strong>"{{ rejectingFile?.fileName }}"</strong> 吗？驳回后文件将不会展示在文件列表中。
          </p>
        </div>
        <div class="upload-modal-footer">
          <button class="btn btn-secondary" @click="showRejectModal = false">取消</button>
          <button class="btn btn-danger" @click="handleRejectFile(rejectingFile)">确认驳回</button>
        </div>
      </div>
    </div>

    <!-- 版本对比弹窗 -->
    <div v-if="showCompareModal" class="modal-overlay" @click.self="resetCompare">
      <div class="compare-modal">
        <div class="modal-header">
          <h3>版本对比</h3>
          <button class="close-btn" @click="resetCompare">
            <CloseOutlined />
          </button>
        </div>
        <div class="compare-toolbar">
          <div class="compare-versions">
            <div class="version-selector">
              <span class="selector-label">版本A：</span>
              <a-select v-model:value="compareLeftVersion" style="width: 160px" @change="performCompare">
                <a-select-option v-for="v in (selectedFileForVersions?.versions || [])" :key="v.id" :value="v.id">
                  v{{ v.version }}
                </a-select-option>
              </a-select>
            </div>
            <div class="vs-text">VS</div>
            <div class="version-selector">
              <span class="selector-label">版本B：</span>
              <a-select v-model:value="compareRightVersion" style="width: 160px" @change="performCompare">
                <a-select-option v-for="v in (selectedFileForVersions?.versions || [])" :key="v.id" :value="v.id">
                  v{{ v.version }}
                </a-select-option>
              </a-select>
            </div>
          </div>
          <div class="compare-actions">
            <div class="compare-mode-switch">
              <button 
                :class="['mode-btn', { active: compareMode === 'side-by-side' }]"
                @click="compareMode = 'side-by-side'"
              >并排</button>
              <button 
                :class="['mode-btn', { active: compareMode === 'inline' }]"
                @click="compareMode = 'inline'"
              >内联</button>
            </div>
            <button 
              :class="['btn', 'btn-small', { 'btn-primary': showDiffOnly }]"
              @click="toggleDiffOnly"
            >
              只显示差异
            </button>
          </div>
        </div>

        <div class="compare-body">
          <div class="compare-unsupported" v-if="compareError === 'unsupported'">
            <FileUnknownOutlined style="font-size:48px;color:#ADB5BD;margin-bottom:16px;" />
            <p class="compare-msg-title">该文件类型不支持文本对比</p>
            <p class="compare-msg-desc">当前文件为 <strong>.{{ compareFileType }}</strong> 格式，无法提取文本内容进行差异对比。</p>
            <p class="compare-msg-tip">支持文本对比的格式：TXT、MD、CSV、JSON、XML、HTML、JS、PY、SQL 等</p>
          </div>
          <div class="compare-empty" v-else-if="compareError === 'empty'">
            <FileTextOutlined style="font-size:48px;color:#ADB5BD;margin-bottom:16px;" />
            <p class="compare-msg-title">两个版本的文件内容均为空</p>
          </div>
          <div class="compare-error" v-else-if="compareError === 'error'">
            <WarningOutlined style="font-size:48px;color:#E67700;margin-bottom:16px;" />
            <p class="compare-msg-title">加载版本内容时出错</p>
            <a-button type="primary" @click="performCompare">重试</a-button>
          </div>
          <div v-else-if="compareLoading" class="compare-loading-wrap">
            <a-spin tip="正在加载版本内容并计算差异..." size="large" />
          </div>
          <template v-else-if="compareLeftContent || compareRightContent">
            <div v-if="compareMode === 'side-by-side'" class="compare-container">
              <div class="compare-card">
                <div class="compare-header">
                  <span>版本 A</span>
                  <span class="header-version">{{ compareLeftRecord?.version ? 'v' + compareLeftRecord.version : '—' }}</span>
                </div>
                <div class="compare-content" v-html="compareLeftContent"></div>
              </div>
              <div class="compare-card">
                <div class="compare-header">
                  <span>版本 B</span>
                  <span class="header-version">{{ compareRightRecord?.version ? 'v' + compareRightRecord.version : '—' }}</span>
                </div>
                <div class="compare-content" v-html="compareRightContent"></div>
              </div>
            </div>
            <div v-else class="compare-container-inline">
              <div class="compare-content-inline" v-html="compareInlineContent"></div>
            </div>
          </template>
          <div v-else class="compare-prompt">
            <SwapOutlined style="font-size:36px;color:#ADB5BD;margin-bottom:12px;" />
            <p>请在上方分别选择两个版本进行对比</p>
            <p class="prompt-hint">请在上方下拉选择器中分别选择两个版本进行对比</p>
          </div>
        </div>
      </div>
    </div>

    <!-- 版本回滚确认弹窗 -->
    <div v-if="showRollbackModal" class="modal-overlay" @click.self="showRollbackModal = false">
      <div class="modal-content">
        <div class="modal-header">
          <h3>版本回退</h3>
          <button class="close-btn" @click="showRollbackModal = false">
            <CloseOutlined />
          </button>
        </div>
        <div class="modal-body">
          <div class="rollback-info">
            <span class="rollback-label">回退目标版本：</span>
            <span class="version-tag">v{{ rollbackTargetVersion?.version }}</span>
            <span style="margin-left:8px;color:#6C757D;font-size:13px;">
              上传时间：{{ rollbackTargetVersion?.updateDate || '' }}
            </span>
          </div>
          <div class="rollback-warning">
            <WarningOutlined style="margin-right:8px;color:#E67700;" />
            <div>
              <p style="margin:0 0 4px;font-weight:600;color:#212529;">回退说明</p>
              <p style="margin:0;color:#495057;font-size:13px;line-height:1.6;">
                回退操作会将目标版本的文件内容作为新版本重新上传。回退后该文档会新增一个版本，不会删除任何现有版本。
              </p>
            </div>
          </div>
        </div>
        <div class="upload-modal-footer">
          <button class="btn btn-secondary" @click="showRollbackModal = false">取消</button>
          <button class="btn btn-primary" @click="confirmRollback" :disabled="rollbackLoading">
            {{ rollbackLoading ? '回退中...' : '确认回退' }}
          </button>
        </div>
      </div>
    </div>

    <!-- 上传新版本弹窗 -->
    <div v-if="showVersionUploadModal" class="modal-overlay" @click.self="showVersionUploadModal = false">
      <div class="modal-content">
        <div class="modal-header">
          <h3>上传新版本</h3>
          <button class="close-btn" @click="showVersionUploadModal = false">
            <CloseOutlined />
          </button>
        </div>
        <div class="modal-body">
          <p style="margin-bottom:12px;color:#495057;font-size:13px;">
            为 <strong>{{ selectedFileForVersions?.title }}</strong> 上传新版本文件：
          </p>
          <div class="upload-zone" @click="$refs.versionFileInput.click()">
            <CloudUploadOutlined style="font-size:32px;color:#ADB5BD;margin-bottom:8px;" />
            <p v-if="!uploadVersionFile" style="margin:0;color:#6C757D;">点击选择文件</p>
            <p v-else style="margin:0;color:#212529;font-weight:500;">{{ uploadVersionFile.name }}</p>
            <input 
              ref="versionFileInput"
              type="file"
              style="display:none;"
              @change="onVersionFileChange"
            />
          </div>
        </div>
        <div class="upload-modal-footer">
          <button class="btn btn-secondary" @click="showVersionUploadModal = false">取消</button>
          <button class="btn btn-primary" @click="handleVersionUpload" :disabled="!uploadVersionFile || uploadVersionLoading">
            {{ uploadVersionLoading ? '上传中...' : '确认上传' }}
          </button>
        </div>
      </div>
    </div>

    <!-- 文件向量化弹窗 -->
    <Teleport to="body">
      <Transition name="modal-fade">
        <div v-if="showVectorizeModal" class="vectorize-modal-overlay" @click.self="closeVectorizeModal">
          <div class="vectorize-modal">
            <!-- 弹窗头部 -->
            <div class="vectorize-modal-header">
              <div class="modal-title">
                <div class="modal-icon">
                  <DatabaseOutlined />
                </div>
                <div>
                  <h3>文件向量化</h3>
                  <p class="modal-subtitle">将文件转换为向量格式，便于 AI 检索和问答</p>
                </div>
              </div>
              <button class="modal-close-btn" @click="closeVectorizeModal">
                <CloseOutlined />
              </button>
            </div>

            <!-- 弹窗内容区 -->
            <div class="vectorize-modal-body">
              <!-- 提示信息 -->
              <div class="vectorize-hint-box">
                <InfoCircleOutlined class="hint-icon" />
                <p>将文件 <strong>v{{ vectorizeTargetVersion?.version }}</strong> 向量化到知识库后，AI 将能够检索该文件内容并基于其进行问答。</p>
              </div>

              <!-- 文件信息卡片 -->
              <div class="vectorize-file-card" v-if="vectorizeTargetVersion">
                <div class="file-card-header">
                  <FileTextOutlined class="file-icon" />
                  <div class="file-info">
                    <span class="file-name">{{ selectedFileForVersions?.fileName || vectorizeTargetVersion?.fileName || '未知文件' }}</span>
                    <span class="file-version">版本 {{ vectorizeTargetVersion.version }}</span>
                  </div>
                </div>
                <div class="file-card-meta">
                  <span class="meta-item">
                    <DatabaseOutlined />
                    {{ formatFileSize(vectorizeTargetVersion.fileSize) }}
                  </span>
                  <span class="meta-item">
                    <CalendarOutlined />
                    {{ vectorizeTargetVersion.updateDate }}
                  </span>
                </div>
              </div>

              <!-- 智能向量化提示（无需手动选择知识库） -->
              <div class="vectorize-form-group">
                <label class="form-label">
                  <span>目标知识库</span>
                  <span class="auto-badge">智能识别</span>
                </label>

                <div class="vectorize-auto-detect-box">
                  <ThunderboltOutlined class="auto-icon" />
                  <div class="auto-info">
                    <p class="auto-title">系统将根据文件的所属部门自动匹配对应的知识库</p>
                    <p class="auto-desc">有部门ID → 部门向量库 | 无部门ID但公开 → 公共向量库 | 其他 → 私有向量库</p>
                  </div>
                </div>
              </div>
            </div>

            <!-- 弹窗底部 -->
            <div class="vectorize-modal-footer">
              <button 
                class="btn btn-secondary" 
                @click="closeVectorizeModal" 
                :disabled="isVectorizing"
              >
                取消
              </button>
              <button
                class="btn btn-primary"
                @click="confirmVectorize"
                :disabled="isVectorizing"
              >
                <LoadingOutlined v-if="isVectorizing" spin style="margin-right: 6px;" />
                {{ isVectorizing ? '提交任务中...' : '确认向量化' }}
              </button>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<script setup>
import { ref, computed, reactive, nextTick, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { message, Modal } from 'ant-design-vue'
import {
  FileTextOutlined,
  UploadOutlined,
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  AppstoreOutlined,
  CloseOutlined,
  AudioOutlined,
  SearchOutlined,
  ShareAltOutlined,
  MoreOutlined,
  EyeOutlined,
  CalendarOutlined,
  ClockCircleOutlined,
  StarOutlined,
  HighlightOutlined,
  EditOutlined,
  ExportOutlined,
  ImportOutlined,
  UserOutlined,
  AuditOutlined,
  OrderedListOutlined,
  CheckCircleOutlined,
  CloseCircleOutlined,
  ReloadOutlined,
  FolderOutlined,
  DeleteOutlined,
  SwapOutlined,
  WarningOutlined,
  FileUnknownOutlined,
  CloudUploadOutlined,
  HistoryOutlined,
  DiffOutlined,
  RollbackOutlined,
  DatabaseOutlined,
  LoadingOutlined,
  InfoCircleOutlined,
  InboxOutlined,
  CheckOutlined,
  CaretUpOutlined,
  CaretDownOutlined,
  DownOutlined,
  CopyOutlined,
  MinusCircleOutlined,
  QuestionCircleOutlined,
  DislikeOutlined
} from '@ant-design/icons-vue'
import AdvancedPDFReader from './pdfReader/AdvancedPDFReader.vue'
import UniversalFileReader from './UniversalFileReader.vue'
import TabsBar from './common/TabsBar.vue'
import PageHeader from './common/PageHeader.vue'
import StatsRow from './common/StatsRow.vue'
import { fileAPI } from '../api/file.js'
import { authAPI, departmentAPI, userAPI } from '../api/auth.js'
import { ragAPI } from '../api/rag.js'
import { examAPI } from '../api/exam.js'
import { getDirectPermission } from '../utils/permission.js'
import { triggerNavigateToExamWithFile } from '../utils/eventBus.js'
import eventBus from '../utils/eventBus.js'
import UploadTaskPanel from './exam/UploadTaskPanel.vue'
import QuestionBankPanel from './exam/QuestionBankPanel.vue'
import { useUploadTasks } from './exam/composables/useUploadTasks.js'

const router = useRouter()

// 用户信息缓存
const userInfoCache = ref(new Map())

const fetchUserNames = async (userIds) => {
  if (!userIds || userIds.length === 0) return

  const uniqueIds = [...new Set(userIds.filter(id => id))]
  const missingIds = uniqueIds.filter(id => !userInfoCache.value.has(String(id)))

  if (missingIds.length === 0) return

  const results = await Promise.allSettled(
    missingIds.map(id => userAPI.getUserById(id))
  )

  results.forEach((result, index) => {
    const userId = missingIds[index]
    if (result.status === 'fulfilled' && result.value.data?.code === 200 && result.value.data?.data) {
      const user = result.value.data.data
      const displayName = user.realName || user.name || user.username || '未知用户'
      userInfoCache.value.set(String(userId), displayName)
    } else {
      userInfoCache.value.set(String(userId), '未知用户')
    }
  })
}

const activeTab = ref('list')
const selectedDocument = ref(null)
const selectedFile = ref(null)
const selectedFileId = ref(null)
const selectedFileExtension = ref('')
const selectedFileName = ref('')
const selectedFileForVersions = ref(null)
const showVersionList = ref(false)

const showCompareModal = ref(false)
const compareLeftVersion = ref(null)
const compareRightVersion = ref(null)
const compareLeftContent = ref('')
const compareRightContent = ref('')
const compareInlineContent = ref('')
const compareLoading = ref(false)
const showDiffOnly = ref(false)
const compareMode = ref('side-by-side')
const compareError = ref(null)
const compareFileType = ref(null)
const compareLeftRecord = ref(null)

const expandedVersionId = ref(null)
const versionPapers = ref({})
const versionPapersLoading = ref({})
const versionPaperPage = ref({})
const VERSION_PAGE_SIZE = 10
const compareRightRecord = ref(null)
const comparePendingVersion = ref(null)

const showVectorizeModal = ref(false)
const vectorizeTargetVersion = ref(null)
const vectorizeSelectedCollection = ref('')
const vectorizeCollections = ref([])
const vectorizeCollectionLoading = ref(false)
const isVectorizing = ref(false)
const vectorizingFileId = ref(null)

const vectorizePollTimers = ref({})
const vectorizeProgressInfo = ref(new Map())

const isCheckingVectorize = ref(null)
const devectorizingId = ref(null) // 正在去向量化中的文件ID
const generatingExamId = ref(null) // 正在生成题目中的文件ID
const editingTagsId = ref(null) // 正在编辑标签的文件ID
const editingTagsText = ref('') // 编辑中的标签文本
const versionVectorizeStatus = ref(new Map())

const showRollbackModal = ref(false)
const rollbackTargetVersion = ref(null)
const rollbackLoading = ref(false)

const showVersionUploadModal = ref(false)
const uploadVersionFile = ref(null)
const uploadVersionLoading = ref(false)

const TEXT_EXTENSIONS = ['txt', 'md', 'csv', 'json', 'xml', 'html', 'css', 'js', 'ts', 'java', 'py', 'sql', 'log', 'yml', 'yaml', 'ini', 'conf', 'properties']
const annotations = ref(null)
const leftSidebarVisible = ref(false)
const rightSidebarVisible = ref(false)
const searchQuery = ref('')
const isReading = ref(false)
const readingSpeed = ref(1)
const readingRange = ref('current')
const showReadingControls = ref(false)
const readingProgress = ref(0)
const readingText = ref('')
const showFloatMenu = ref(false)
const selectedCategory = ref('all')
const currentMode = ref('comment')
const selectedColor = ref(null)
const currentPage = ref(1)
const numPages = ref(1)
const scale = ref(1)
const showHighlights = ref(true)
const fileUploadRef = ref(null)
const uploadFileInputRef = ref(null)
const fileReaderRef = ref(null)

// 上传弹窗相关状态
const showUploadModal = ref(false)
const isDragOver = ref(false)
const isUploading = ref(false)
const uploadForm = reactive({
  file: null,
  deptId: '',
  isPublic: true,
  description: '',
  autoVectorize: false
})

// 计算属性：上传表单是否有效
const isUploadFormValid = computed(() => {
  return uploadForm.file
})

// 计算属性：上传弹窗的部门列表（管理员可选所有部门，非管理员只显示自己的部门）
const uploadDepartmentList = computed(() => {
  if (isAdminUser(userInfo.value)) {
    return departmentList.value.map(dept => ({
      ...dept,
      id: String(dept.id || dept.deptId),
      deptId: String(dept.id || dept.deptId)
    }))
  }
  // 非管理员：只显示用户自己的部门
  const userDeptId = userInfo.value?.deptId || userInfo.value?.departmentId || ''
  const userDeptName = userInfo.value?.deptName || userInfo.value?.departmentName || ''
  
  if (userDeptId) {
    return [{ id: String(userDeptId), deptId: String(userDeptId), name: userDeptName, deptName: userDeptName }]
  }
  
  // 如果用户没有部门信息，用部门列表的第一个
  if (departmentList.value.length > 0) {
    const firstDept = departmentList.value[0]
    return [{
      id: String(firstDept.id || firstDept.deptId),
      deptId: String(firstDept.id || firstDept.deptId),
      name: firstDept.deptName || firstDept.name,
      deptName: firstDept.deptName || firstDept.name
    }]
  }
  
  return []
})
const importAnnotationsRef = ref(null)
const loading = ref(false)
const documentList = ref([])
const showColorPicker = ref(false)
const showSpeechControls = ref(false)
const userInfo = ref(null)
const isAdmin = ref(false)
const currentFileType = ref('system') // 'system' 或 'personal'
const currentViewerFileType = ref('pdf') // 当前查看的文件类型
const selectedSheet = ref('') // Excel工作表选择
const textSearchQuery = ref('') // 文本搜索查询

const highlightColors = ['#ffeb3b', '#4caf50', '#2196f3', '#ff9800', '#f44336', '#9c27b0']

// 文件列表分页 - 使用后端分页参数
const fileCurrentPage = ref(1)
const filePageSize = ref(40) // 每页40个文件集合
const fileTotal = ref(0) // 后端返回的总记录数
const filePages = ref(0) // 后端返回的总页数

// 细粒度权限检查
const canViewFileList = computed(() => getDirectPermission('read:view'))
const canReadDocument = computed(() => getDirectPermission('read:read'))
const canUploadFile = computed(() => getDirectPermission('read:upload'))
const canFavorite = computed(() => getDirectPermission('read:favorite'))
const canApproveFile = computed(() => getDirectPermission('read:approve'))
const canViewQuestionBank = computed(() => getDirectPermission('read:questionBank'))
const canManageVersion = computed(() => getDirectPermission('manage:file'))

const readModuleTabs = computed(() => {
  const tabs = []
  if (canViewFileList.value) {
    tabs.push({
      key: 'list',
      label: '文件列表',
      icon: '<FileTextOutlined />'
    })
  }
  if (canApproveFile.value) {
    tabs.push({
      key: 'approve',
      label: '文件审批',
      icon: '<AuditOutlined />'
    })
  }
  if (canViewQuestionBank.value) {
    tabs.push({
      key: 'questionBank',
      label: '题目管理与审批',
      icon: '<OrderedListOutlined />'
    })
  }
  return tabs
})

const approveLoading = ref(false)
const allFilesCache = ref([])
const approveSearchQuery = ref('')
const approveFilterStatus = ref('pending')
const showRejectModal = ref(false)
const rejectingFile = ref(null)
const approveCurrentPage = ref(1)
const approvePageSize = ref(30)
const approveTotal = ref(0)
const approvePages = ref(0)

const rawFileList = ref([])
const extractVersionFromPath = (filePath) => {
  if (!filePath) return 'v1'

  const normalizedPath = filePath.replace(/\\/g, '/')

  const vDigitPattern = /\/v(\d+)\//i
  const match = normalizedPath.match(vDigitPattern)

  if (match) {
    return `v${match[1]}`
  }

  const fallbackPatterns = [
    /\/v([\d.]+)\//i,
    /\/version-([\d.]+)\//i,
    /\/v_([\d.]+)\//i,
    /\/([\d.]+)\//i,
  ]

  for (const pattern of fallbackPatterns) {
    const fallbackMatch = normalizedPath.match(pattern)
    if (fallbackMatch) {
      const version = fallbackMatch[1]
      return version.includes('.') ? `v${version}` : `v${version}`
    }
  }

  return 'v1'
}

const parseVersionNumber = (versionStr) => {
  if (!versionStr) return 1
  const match = versionStr.match(/v?([\d.]+)/i)
  return match ? parseFloat(match[1]) : 1
}

const mapBackendToFrontend = (backendFile) => {
  const version = extractVersionFromPath(backendFile.filePath)

  let uploaderDisplayName = '未知'
  if (backendFile.uploadUserId) {
    uploaderDisplayName = userInfoCache.value.get(String(backendFile.uploadUserId)) || backendFile.uploadUserName || '未知'
  }
  
  // 统一处理部门ID（兼容不同的字段名）
  const normalizedDeptId = backendFile.deptId || backendFile.departmentId
  
  return {
    id: backendFile.id,
    title: backendFile.fileName,
    category: backendFile.deptName || backendFile.departmentName || '未分类',
    version: version,
    updateDate: backendFile.updateTime ? backendFile.updateTime.split('T')[0] : '',
    views: backendFile.downloadCount || 0,
    status: (backendFile.fileCategory === 'rule' && backendFile.auditStatus !== 0) ? 'active' : 'review',
    // 按文档1.1规范：tags为JSON类型，兼容数组和JSON字符串
    tags: parseTagsField(backendFile.tags),
    description: `${backendFile.fileType || ''} | ${formatFileSize(backendFile.fileSize)} | 上传者: ${uploaderDisplayName}`,
    pdfUrl: backendFile.filePath,
    priority: 'medium',
    pages: '-',
    rawFileName: backendFile.fileName,
    extension: backendFile.extension,
    uploadUserId: backendFile.uploadUserId,
    uploadUserName: uploaderDisplayName,
    deptId: normalizedDeptId, // 使用统一后的部门ID
    departmentId: normalizedDeptId, // 同时赋值以兼容
    deptName: backendFile.deptName || backendFile.departmentName,
    fileSize: backendFile.fileSize,
    fileType: backendFile.fileType,
    createTime: backendFile.createTime,
    updateTime: backendFile.updateTime,
    auditStatus: backendFile.auditStatus,
    fileCategory: backendFile.fileCategory,
    processStepStatus: backendFile.processStepStatus || backendFile.process_step_status || '',
    processStepMessage: backendFile.processStepMessage || backendFile.process_step_message || '',
    examStatus: backendFile.examStatus || backendFile.exam_status || '',
    processMessage: backendFile.processMessage || backendFile.process_message || ''
  }
}

// 按文档1.1规范解析tags字段（JSON类型，兼容数组和JSON字符串）
const parseTagsField = (rawTags) => {
  if (!rawTags) return []
  if (Array.isArray(rawTags)) return rawTags
  if (typeof rawTags === 'string') {
    try {
      const parsed = JSON.parse(rawTags)
      return Array.isArray(parsed) ? parsed : []
    } catch {
      // 非JSON字符串，按逗号分隔
      return rawTags.split(/[,，]/).map(t => t.trim()).filter(t => t.length > 0)
    }
  }
  if (typeof rawTags === 'object') {
    // 对象格式 { version: "v2", category: "important" } → 转为值数组
    return Object.values(rawTags).map(String).filter(v => v.length > 0)
  }
  return []
}

const formatFileSize = (bytes) => {
  if (!bytes || 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 mergeFilesByName = (files) => {
  const fileMap = new Map()

  files.forEach((file, idx) => {
    // 合并键：文件名__文件类型__部门ID - 不同部门的同名文件不合并
    const mergeKey = `${file.rawFileName}__${file.fileCategory || 'unknown'}__${file.deptId || 'nodept'}`


    if (!fileMap.has(mergeKey)) {
      fileMap.set(mergeKey, {
        ...file,
        versions: [file],
        currentVersion: file.version
      })
    } else {
      const existing = fileMap.get(mergeKey)
      existing.versions.push(file)
      const currentVerNum = parseVersionNumber(existing.currentVersion)
      const newVerNum = parseVersionNumber(file.version)
      if (newVerNum > currentVerNum) {
        existing.currentVersion = file.version
        Object.assign(existing, file)
      }
      fileMap.set(mergeKey, existing)
    }
  })

  const result = Array.from(fileMap.values()).map((file) => {
    const sortedVersions = [...file.versions].sort((a, b) => {
      const verA = parseVersionNumber(a.version)
      const verB = parseVersionNumber(b.version)
      return verB - verA
    })
    return {
      ...file,
      versions: sortedVersions
    }
  }).sort((a, b) => {
    return new Date(b.updateTime) - new Date(a.updateTime)
  })
  

return result
}

const fetchUserInfo = async () => {
  try {
    const response = await authAPI.getUserInfo()
    console.log('[用户信息] API响应:', response)
    console.log('[用户信息] response.data:', response?.data)

    if (response.data.code === 200 && response.data.data) {
      let data = response.data.data
      console.log('[用户信息] 解析后的用户数据:', data)
      console.log('[用户信息] 部门相关字段:', {
        deptName: data.deptName,
        departmentName: data.departmentName,
        department: data.department,
        deptId: data.deptId,
        departmentId: data.departmentId
      })

      if (!data.deptId && !data.departmentId && data.id) {
        try {
          console.log('[用户信息] 基本信息无部门ID，使用getUsers接口获取完整信息（与PermissionModule一致）...')
          
          // ✅ 修复：使用getUsers分页查询接口而不是getUserById
          // 原因：getUsers接口返回的用户数据包含完整的deptId字段（已验证）
          // 而getUserById接口不返回部门字段
          const usersRes = await userAPI.getUsers({
            current: 1,
            size: 100
          })
          
          if (usersRes?.data?.code === 200 && usersRes.data.data?.records) {
            const allUsers = usersRes.data.data.records
            console.log('[用户信息] getUsers返回用户数量:', allUsers.length)
            
            // 从用户列表中查找当前登录用户
            const currentUser = allUsers.find(u => 
              u.id === data.id || 
              u.username === data.username
            )
            
            if (currentUser) {
              console.log('[用户信息] 找到当前用户完整数据:', currentUser)
              console.log('[用户信息] 用户完整数据中的部门字段:', {
                deptId: currentUser.deptId,
                departmentId: currentUser.departmentId,
                deptName: currentUser.deptName,
                departmentName: currentUser.departmentName
              })
              
              data = { ...data, ...currentUser }
            } else {
              console.warn('[用户信息] 未在用户列表中找到当前用户, userId:', data.id)
            }
          }
        } catch (detailErr) {
          console.warn('[用户信息] getUsers调用失败:', detailErr.message)
        }
      }

      userInfo.value = data
      isAdmin.value = isAdminUser(data)
      localStorage.setItem('userInfo', JSON.stringify(data))

      console.log('[用户信息] 最终存储的用户信息:', {
        id: userInfo.value.id,
        deptId: userInfo.value.deptId,
        departmentId: userInfo.value.departmentId,
        deptName: userInfo.value.deptName
      })
    } else {
      console.warn('[用户信息] API返回非200或无data, code:', response.data?.code)
      let storedUserInfo = null
      try {
        const stored = localStorage.getItem('userInfo')
        if (stored) {
          storedUserInfo = JSON.parse(stored)
          userInfo.value = storedUserInfo
          isAdmin.value = isAdminUser(storedUserInfo)
          console.log('[用户信息] 使用缓存数据:', storedUserInfo)
        }
      } catch {}
    }
  } catch (error) {
    console.warn('获取用户信息失败:', error.message)
  }
}

const fetchDepartments = async () => {
  try {
    const response = await departmentAPI.getDepartments()
    if (response.data.code === 200 && Array.isArray(response.data.data)) {
      let depts = response.data.data
      
      // 查找公共部门
      const publicDept = depts.find(d => 
        (d.name && d.name.includes('公共')) || 
        (d.deptName && d.deptName.includes('公共'))
      )
      
      // 管理员和普通用户的部门列表处理
      if (!isAdmin.value && userInfo.value?.departmentId) {
        const userDeptId = userInfo.value.departmentId
        depts = depts.filter(dept => dept.id === userDeptId || dept.deptId === userDeptId)
      }
      
      departmentList.value = depts
      const deptNameList = depts.map(dept => dept.name || dept.deptName)
      
      // 设置默认选中公共部门
      if (publicDept) {
        selectedCategory.value = publicDept.name || publicDept.deptName
      } else {
        selectedCategory.value = isAdmin.value ? 'all' : (deptNameList[0] || 'all')
      }
      
      categories.value = isAdmin.value ? ['all', ...deptNameList] : deptNameList
    }
  } catch (error) {
    console.warn('获取部门列表失败:', error.message)
  }
}

// 是否已经加载过文件数据
const hasLoadedFiles = ref(false)

const loadFileList = async (deptId = null, force = false) => {
  if (hasLoadedFiles.value && !force) {
    return
  }

  if (force) {
    documentList.value = []
    rawFileList.value = []
  }

  loading.value = true

  let records = []
  try {
    const params = {
      size: 100,
      current: 1,
      pageNum: 1,
      pageSize: 100
    }

    const response = await fileAPI.getFiles(params)
    console.log('[/file/page] 响应状态:', response.status, '数据:', response.data)

    if (response.data.code === 200 && response.data.data?.records) {
      const data = response.data.data
      records = data.records
      
      console.log('📥 [原始数据] 从后端获取到的文件列表:', records)

      records = records.filter(file => {
        return file.auditStatus !== 0
      })
      
      console.log('✅ [筛选后] auditStatus不为0的文件:', records)

      rawFileList.value = records
    } else if (response.data.code === 401) {
      message.error('请先登录后再查看文件列表')
      return
    } else {
      message.error(response.data.message || `加载失败(状态码:${response.data.code})`)
      return
    }
  } catch (error) {
    console.error('[/file/page] 请求失败:', error.message, '状态码:', error.response?.status, '响应:', error.response?.data)
    if (error.response) {
      if (error.response.status === 401 || error.response.status === 403) {
        message.error('登录已过期，请重新登录')
      } else if (error.response.status === 404) {
        message.error('接口不存在，请检查后端服务')
      } else if (error.response.status >= 500) {
        message.error('服务器内部错误，请稍后重试')
      } else {
        message.error(`请求失败(${error.response.status})`)
      }
    } else if (error.request) {
      message.error('无法连接服务器，请确保后端服务在 localhost:8080 运行')
    } else {
      message.error('请求配置错误: ' + error.message)
    }
    return
  } finally {
    loading.value = false
  }

  if (records.length === 0) return

  const uploadUserIds = records.map(file => file.uploadUserId).filter(id => id)
  await fetchUserNames(uploadUserIds)

  const mappedFiles = records.map(mapBackendToFrontend)

  

  console.log('📋 [上传/审批调试] 文件分类信息:')
  mappedFiles.forEach((file, idx) => {
    const originalRecord = records[idx]
    console.log(`  [${idx + 1}] ${file.title}:`)
    console.log(`      - id: ${file.id}`)
    console.log(`      - fileCategory: ${file.fileCategory}`)
    console.log(`      - auditStatus: ${file.auditStatus}`)
    console.log(`      - deptId: ${file.deptId}`)
    console.log(`      - deptName: ${file.deptName}`)
    console.log(`      - isPublic: ${originalRecord.isPublic}`)
    if (file.fileCategory === 'personal' && originalRecord.isPublic === 1 && originalRecord.auditStatus === 1) {
      console.warn(`      ⚠️ 异常！公开且已审批通过，但显示为"个人文件"`)
    }
  })

  documentList.value = mergeFilesByName(mappedFiles)
  
  

  hasLoadedFiles.value = true

  fileTotal.value = documentList.value.length
  const totalPages = Math.ceil(fileTotal.value / filePageSize.value)
  filePages.value = totalPages

  if (fileCurrentPage.value > totalPages && totalPages > 0) {
    fileCurrentPage.value = totalPages
  } else if (totalPages === 0) {
    fileCurrentPage.value = 1
  }

  message.success(`成功加载 ${documentList.value.length} 个文件`)
}

const handleVersionChange = (fileItem, version) => {
  const targetVersion = fileItem.versions.find(v => v.version === version)
  if (targetVersion) {
    const index = documentList.value.findIndex(f => f.rawFileName === fileItem.rawFileName)
    if (index !== -1) {
      documentList.value[index] = { 
        ...targetVersion, 
        versions: fileItem.versions, 
        currentVersion: version 
      }
    }
  }
}

const tocData = [
  { id: 1, title: '第一章 总则', page: 1 },
  { id: 2, title: '1.1 制度目的', page: 1 },
  { id: 3, title: '1.2 适用范围', page: 2 },
  { id: 4, title: '第二章 考勤管理', page: 3 },
  { id: 5, title: '2.1 工作时间', page: 3 },
  { id: 6, title: '2.2 考勤方式', page: 4 },
]

const relatedRecommendations = [
  {
    id: 1,
    title: '公司加班管理办法',
    summary: '规范加班申请、审批和补偿流程',
    relevance: 90,
    tags: ['考勤', '加班', '补偿']
  },
  {
    id: 2,
    title: '员工请假管理规定',
    summary: '详细说明各类请假的申请流程和审批要求',
    relevance: 85,
    tags: ['请假', '审批', '流程']
  },
  {
    id: 3,
    title: '工作时间管理规范',
    summary: '定义标准工作时间和弹性工作制',
    relevance: 80,
    tags: ['工作时间', '弹性工作', '规范']
  }
]

const categories = ref(['all'])
const departmentList = ref([])

const getUserDepartmentName = () => {
  console.log('[部门查询] userInfo.value:', JSON.stringify(userInfo.value))
  console.log('[部门查询] departmentList长度:', departmentList.value.length)

  if (!userInfo.value || typeof userInfo.value !== 'object') return ''

  const deptName = userInfo.value?.deptName || userInfo.value?.departmentName || ''
  if (deptName) {
    console.log('[部门查询] 直接获取到部门名:', deptName)
    return deptName
  }

  // ✅ 修复：与PermissionModule保持一致，优先使用deptId字段
  const userDeptId = String(userInfo.value?.deptId || userInfo.value?.departmentId || '')
  console.log('[部门查询] 用户deptId:', userDeptId)

  if (userDeptId && userDeptId !== 'undefined' && userDeptId !== 'null' && departmentList.value.length > 0) {
    console.log('[部门查询] 部门列表详情:', departmentList.value.map(d => ({ id: d.id, deptId: d.deptId, name: d.name, deptName: d.deptName })))

    // ✅ 与PermissionModule的匹配逻辑完全一致
    const matchedDept = departmentList.value.find(d =>
      d.id === userDeptId ||
      d.deptId === userDeptId
    )

    if (matchedDept) {
      console.log('[部门查询] 匹配到部门:', matchedDept.name || matchedDept.deptName)
      return matchedDept.name || matchedDept.deptName
    }
  }

  console.log('[部门查询] 未找到匹配的部门')
  return ''
}

const totalViews = computed(() => filteredDocuments.value.reduce((sum, doc) => sum + (doc.views || 0), 0))
const updatedToday = computed(() => {
  const today = new Date().toISOString().split('T')[0]
  return filteredDocuments.value.filter(doc => doc.updateDate === today).length
})
const highPriorityCount = computed(() => filteredDocuments.value.filter(doc => doc.priority === 'high').length)

const fileStats = computed(() => [
  {
    value: filteredDocuments.value.length,
    label: currentFileType.value === 'system' ? '制度文件' : '个人文件'
  },
  {
    value: totalViews.value,
    label: '总浏览量'
  },
  {
    value: updatedToday.value,
    label: '今日更新'
  },
  {
    value: highPriorityCount.value,
    label: '重要文件'
  }
])

// ==========================================
// 个人文件访问控制逻辑 - 重新设计
// ==========================================

/**
 * 判断用户是否为系统管理员
 * @param {Object} user - 用户信息对象
 * @returns {boolean} 是否为管理员
 */
const isAdminUser = (user) => {
  if (!user) return false
  return user.userType === 1
}

/**
 * 判断文件是否属于当前用户
 * @param {Object} file - 文件对象
 * @param {Object} user - 当前用户对象
 * @returns {boolean} 文件是否属于当前用户
 */
const isFileOwnedByUser = (file, user) => {
  if (!user) return false
  
  // 获取用户ID（支持多种字段名）
  const userId = user.id || user.userId || user.uid
  
  // 检查文件上传者ID（支持多种字段名）
  const uploadUserId = file.uploadUserId || file.uploaderId || file.creatorId
  
  // 判断归属
  return String(uploadUserId) === String(userId)
}

/**
 * 个人文件访问权限验证
 * @param {Object} file - 文件对象
 * @returns {boolean} 是否有权限访问该个人文件
 */
const hasAccessToPersonalFile = (file) => {
  try {
    const isAdmin = isAdminUser(userInfo.value)
    
    // 情况1：管理员跳过权限验证，直接允许访问
    if (isAdmin) {
      return true
    }

    const isOwned = isFileOwnedByUser(file, userInfo.value)
    if (isOwned) {
      return true
    } else {
      return false
    }
  } catch (error) {
    return false
  }
}

/**
 * 判断文件是否为个人文件
 * @param {Object} doc - 文件对象
 * @returns {boolean} 是否为个人文件
 */
const isPersonalFile = (doc) => {
  if (!doc) return false
  const category = doc.fileCategory || doc.file_category
  return category === 'personal'
}

const isSystemFile = (doc) => {
  if (!doc) return false
  const category = doc.fileCategory || doc.file_category
  return category === 'rule'
}

const filteredDocuments = computed(() => {
  const isAdmin = isAdminUser(userInfo.value)
  const userName = userInfo.value?.userName || userInfo.value?.username || userInfo.value?.name || '未登录'

  console.log('🔍 [筛选开始]');
  console.log('  - 当前文件类型:', currentFileType.value);
  console.log('  - 当前选中部门:', selectedCategory.value);
  console.log('  - 部门列表:', departmentList.value);

  const result = documentList.value.filter(doc => {
    // 1. 文件类型过滤
    let matchesFileType = true
    if (currentFileType.value === 'system') {
      matchesFileType = isSystemFile(doc)
    } else if (currentFileType.value === 'personal') {
      matchesFileType = isPersonalFile(doc)
      
      // 2. 个人文件权限验证（核心访问控制逻辑）
      if (matchesFileType) {
        matchesFileType = hasAccessToPersonalFile(doc)
      }
    }
    
    // 3. 部门分类过滤 - 完全基于部门ID进行匹配
    let matchesCategory = true
    if (selectedCategory.value !== 'all') {
      // 查找选中部门的ID
      const selectedDept = departmentList.value.find(d => 
        (d.name && d.name === selectedCategory.value) || 
        (d.deptName && d.deptName === selectedCategory.value)
      )
      

console.log(`  - 选中部门:`, selectedDept);
      
      if (selectedDept) {
        const selectedDeptId = selectedDept.id || selectedDept.deptId
        // 判断是否为公共部门
        const isPublicDept = selectedDept.name?.includes('公共') || selectedDept.deptName?.includes('公共')
        
        if (isPublicDept) {
          // 公共部门展示所有系统文件
          matchesCategory = true
          console.log(`  - 公共部门，匹配`);
        } else {
          // 其他部门：优先用 deptId 精确匹配，兼容不同字段名
          const docDeptId = doc.deptId || doc.departmentId
          matchesCategory = docDeptId == selectedDeptId // 使用 == 兼容类型差异
          console.log(`  - 文件deptId: ${docDeptId}, 选中部门ID: ${selectedDeptId}, 匹配: ${matchesCategory}`);
        }
      } else {
        matchesCategory = doc.category === selectedCategory.value
      }
    }
    
    // 4. 搜索关键词过滤
    const matchesSearch = doc.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
                          (doc.summary || '').toLowerCase().includes(searchQuery.value.toLowerCase())
    
    const shouldShow = matchesFileType && matchesCategory && matchesSearch
    if (shouldShow) {
      console.log(`✅ [显示] ${doc.title} (ID: ${doc.id}, deptId: ${doc.deptId})`);
    } else {
      
    }
    
    return shouldShow
  })
  
  console.log(`📊 [筛选结果] 共 ${result.length} 个文件`);
  return result
})

// 文件列表分页计算属性 - 前端分页，按文件集合分页
const fileTotalPages = computed(() => Math.ceil(filteredDocuments.value.length / filePageSize.value))
const paginatedDocuments = computed(() => {
  const start = (fileCurrentPage.value - 1) * filePageSize.value
  const end = start + filePageSize.value
  return filteredDocuments.value.slice(start, end)
})

// 更新fileTotal和filePages为filteredDocuments的统计
watch(filteredDocuments, (newDocs) => {
  fileTotal.value = newDocs.length
  const totalPages = Math.ceil(newDocs.length / filePageSize.value)
  filePages.value = totalPages
  
  if (fileCurrentPage.value > totalPages && totalPages > 0) {
    fileCurrentPage.value = totalPages
  } else if (totalPages === 0) {
    fileCurrentPage.value = 1
  }
})

const handleFilePageChange = (page) => {
  fileCurrentPage.value = page
}

const pendingCount = computed(() => allFilesCache.value.filter(f => f.auditStatus === 0).length)
const approvedCount = computed(() => allFilesCache.value.filter(f => f.auditStatus === 1).length)
const rejectedCount = computed(() => allFilesCache.value.filter(f => f.auditStatus === 2).length)

const filteredPendingFiles = computed(() => {
  let list = allFilesCache.value
  if (approveFilterStatus.value === 'pending') {
    list = list.filter(f => f.auditStatus === 0)
  } else if (approveFilterStatus.value === 'approved') {
    list = list.filter(f => f.auditStatus === 1)
  } else if (approveFilterStatus.value === 'rejected') {
    list = list.filter(f => f.auditStatus === 2)
  }
  if (approveSearchQuery.value) {
    const q = approveSearchQuery.value.toLowerCase()
    list = list.filter(f => (f.fileName || '').toLowerCase().includes(q) || (f.uploadUserName || '').toLowerCase().includes(q))
  }
  return list
})

const paginatedApproveFiles = computed(() => {
  const start = (approveCurrentPage.value - 1) * approvePageSize.value
  const end = start + approvePageSize.value
  return filteredPendingFiles.value.slice(start, end)
})

watch(approveFilterStatus, () => {
  approveCurrentPage.value = 1
})

watch(approveSearchQuery, () => {
  approveCurrentPage.value = 1
})

watch(filteredPendingFiles, (newList) => {
  approveTotal.value = newList.length
  approvePages.value = Math.ceil(newList.length / approvePageSize.value)
  if (approveCurrentPage.value > approvePages.value && approvePages.value > 0) {
    approveCurrentPage.value = approvePages.value
  } else if (approvePages.value === 0) {
    approveCurrentPage.value = 1
  }
})

const handleApprovePageChange = (page) => {
  approveCurrentPage.value = page
}

const loadPendingFiles = async () => {
  approveLoading.value = true
  try {
    const res = await fileAPI.getFiles({ pageNum: 1, pageSize: 500 })
    if (res.data?.code === 200) {
      allFilesCache.value = res.data.data?.records || []
    } else {
      allFilesCache.value = []
    }
  } catch (e) {
    allFilesCache.value = []
  } finally {
    approveLoading.value = false
  }
}

const handleApproveFile = async (file) => {
  try {
    const res = await fileAPI.approveFile(file.id)
    if (res.data?.code === 200) {
      message.success(`文件"${file.fileName}"审批通过`)
      await loadPendingFiles()
      await loadFileList(null, true)
    } else {
      message.error(res.data?.message || '审批操作失败')
    }
  } catch (e) {
    message.error('审批操作失败: ' + (e.message || '请检查网络连接'))
  }
}
const handleRejectFile = async (file) => {
  try {
    const res = await fileAPI.rejectFile(file.id)
    if (res.data?.code === 200) {
      message.success(`文件"${file.fileName}"已驳回`)
      showRejectModal.value = false
      rejectingFile.value = null
      await loadPendingFiles()
    } else {
      message.error(res.data?.message || '驳回操作失败')
    }
  } catch (e) {
    message.error('驳回操作失败: ' + (e.message || '请检查网络连接'))
  }
}

const showRejectConfirm = (file) => {
  rejectingFile.value = file
  showRejectModal.value = true
}

const handleDeleteFile = async (doc) => {
  const fileId = doc.id || doc.fileId
  if (!fileId) {
    message.error('无法删除：文件ID不存在')
    return
  }
  try {
    const res = await fileAPI.deleteFile(fileId)
    if (res.data?.code === 200) {
      // 按文档5.1.2规范：返回 Result<Map<String, Object>>，含 deletedQuestionCount
      const resultData = res.data?.data || {}
      const deletedCount = resultData.deletedQuestionCount ?? null
      const fileName = resultData.fileName || doc.title || doc.name || doc.fileName || '该文件'

      if (deletedCount !== null && deletedCount > 0) {
        message.success(`已软删除文件"${fileName}"及关联的 ${deletedCount} 道题目`)
      } else {
        message.success(`文件"${fileName}"已软删除`)
      }

      hasLoadedFiles.value = false
      await loadFileList(null, true)
      if (canApproveFile.value) {
        loadPendingFiles()
      }
      if (selectedFileForVersions.value) {
        const updated = documentList.value.find(d => d.rawFileName === doc.rawFileName && d.fileCategory === doc.fileCategory)
        if (updated) {
          updated.versions = updated.versions.filter(v => (v.id || v.fileId) !== fileId)
          selectedFileForVersions.value = updated.versions.length > 0 ? updated : null
          if (!updated.versions.length) {
            showVersionList.value = false
            selectedFileForVersions.value = null
          }
        } else {
          showVersionList.value = false
          selectedFileForVersions.value = null
        }
      }
    } else {
      message.error(res.data?.message || '删除文件失败')
    }
  } catch (e) {
    message.error('删除文件失败: ' + (e.message || '请检查网络连接'))
  }
}

// 上传任务管理
const showTaskPanel = ref(false)
const {
  tasks: uploadTasks,
  loading: taskLoading,
  polling: taskPolling,
  statusFilter: taskStatusFilter,
  searchQuery: taskSearchQuery,
  currentPage: taskCurrentPage,
  stats: taskStats,
  paginatedTasks: taskPaginatedTasks,
  totalFilteredPages: taskTotalPages,
  parseVectorStatus,
  parseExamStatus,
  addTask: addUploadTask,
  fetchTasks: fetchUploadTasks,
  refreshTasks: refreshUploadTasks,
  startPolling: startTaskPolling,
  stopPolling: stopTaskPolling
} = useUploadTasks()

onMounted(async () => {
  const [userInfoResult, deptResult] = await Promise.allSettled([fetchUserInfo(), fetchDepartments()])
  
  await nextTick()
  
  if (canViewFileList.value) {
    loadFileList(null)
  }
  
  if (canApproveFile.value) {
    loadPendingFiles()
  }
  
  if (canApproveFile.value && !canViewFileList.value) {
    activeTab.value = 'approve'
  } else if (canViewFileList.value) {
    activeTab.value = 'list'
  } else if (canViewQuestionBank.value) {
    activeTab.value = 'questionBank'
  } else if (canApproveFile.value) {
    activeTab.value = 'approve'
  }
  
  // 加载上传任务列表
  await fetchUploadTasks()
  const hasActiveTasks = uploadTasks.value.some(t => {
    const pss = t.processStepStatus
    return ['UPLOADED', 'VECTORIZING', 'EXAM_GENERATING'].includes(pss)
  })
  if (hasActiveTasks) {
    showTaskPanel.value = true
    startTaskPolling()
  }
})

// 监听activeTab变化，当切换到审批页面时自动加载审批列表
watch(activeTab, (newTab) => {
  if (newTab === 'approve' && canApproveFile.value) {
    loadPendingFiles()
  }
})

watch(canViewFileList, (newVal) => {
  if (newVal && rawFileList.value.length === 0 && !loading.value) {
    loadFileList(null)
  }
}, { immediate: false })

// 监听部门分类变化，重置到第1页（使用前端筛选）
watch(selectedCategory, (newCategory) => {
  fileCurrentPage.value = 1
  // 不再调用接口，使用前端筛选
})

// 监听文件类型切换
watch(currentFileType, () => {
  fileCurrentPage.value = 1 // 重置到第1页
})

const handleReadingToggle = () => {
  if (!selectedFile.value) {
    message.error('请先选择PDF文件')
    return
  }
  isReading.value = !isReading.value
  if (!isReading.value) {
    message.success('开始语音朗读')
  } else {
    message.info('暂停语音朗读')
  }
}

const handleReadingStop = () => {
  isReading.value = false
  message.info('停止语音朗读')
  readingProgress.value = 0
  readingText.value = ''
}

const handleAnnotationClick = (highlightId) => {
  const highlight = annotations.value?.highlights?.find(h => h.id === highlightId)
  if (highlight) {
    currentPage.value = highlight.pageNumber
  }
}

const handleFileUploadClick = async () => {
  await fetchUserInfo()

  if (departmentList.value.length === 0) {
    await fetchDepartments()
  }

  uploadForm.file = null
  uploadForm.description = ''
  uploadForm.isPublic = true

  if (isAdminUser(userInfo.value)) {
    uploadForm.deptId = ''
  } else {
    const deptList = uploadDepartmentList.value
    if (deptList.length > 0) {
      uploadForm.deptId = deptList[0].id
    }
  }

  showUploadModal.value = true
}

// 选择文件
const handleFileSelect = (e) => {
  const file = e.target.files[0]
  if (!file) return
  
  validateAndSetFile(file)
}

// 拖拽文件
const handleFileDrop = (e) => {
  isDragOver.value = false
  const file = e.dataTransfer.files[0]
  if (!file) return
  
  validateAndSetFile(file)
}

// 验证并设置文件
const validateAndSetFile = (file) => {
  // 支持的 MIME 类型
  const allowedTypes = [
    'application/pdf',
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.ms-excel',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/vnd.ms-powerpoint',
    'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'text/plain',
    'text/markdown',
    'text/csv',
    'application/json',
    'application/xml',
    'text/xml',
    'text/html',
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/webp',
    'image/bmp',
    'image/svg+xml',
    'application/epub+zip',
    'application/rtf'
  ]
  
  // 支持的文件扩展名
  const allowedExtensions = [
    '.pdf', '.doc', '.docx', 
    '.ppt', '.pptx',
    '.xls', '.xlsx',
    '.txt', '.md', '.markdown',
    '.csv', '.json', '.xml',
    '.html', '.htm', '.xhtml',
    '.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.ico', '.tiff', '.tif',
    '.epub', '.rtf'
  ]
  
  const fileName = file.name.toLowerCase()
  const hasValidExtension = allowedExtensions.some(ext => fileName.endsWith(ext))
  
  if (!allowedTypes.includes(file.type) && !hasValidExtension) {
    message.error('请上传支持的文件格式（PDF、Word、Excel、PPT、图片、文本、代码等）')
    return
  }
  
  // 文件大小限制（1GB）
  const maxSize = 1024 * 1024 * 1024
  if (file.size > maxSize) {
    message.error('文件大小不能超过1GB')
    return
  }
  
  uploadForm.file = file
}

// 移除选中的文件
const removeSelectedFile = () => {
  uploadForm.file = null
  if (uploadFileInputRef.value) {
    uploadFileInputRef.value.value = ''
  }
}

// 关闭上传弹窗
const closeUploadModal = () => {
  showUploadModal.value = false
  isDragOver.value = false
  uploadForm.file = null
  uploadForm.deptId = ''
  uploadForm.isPublic = true // 默认选中公开文件
  uploadForm.description = ''
  uploadForm.autoVectorize = false
  isUploading.value = false
}

// 提交上传
const handleUploadSubmit = async () => {
  if (!isUploadFormValid.value) {
    message.warning('请选择文件')
    return
  }
  
  // 表单验证：如果是管理员上传公开文件，必须选择部门
  const isAdmin = isAdminUser(userInfo.value)
  if (isAdmin && uploadForm.isPublic && !uploadForm.deptId) {
    message.warning('请选择部门')
    return
  }
  
  isUploading.value = true
  
  try {
    console.log('🚀 开始上传文件...')
    console.log('   - 文件名:', uploadForm.file.name)
    console.log('   - 用户类型:', isAdmin ? '管理员' : '普通用户')
    console.log('   - 是否公开:', uploadForm.isPublic)
    console.log('   - 选择的部门ID:', uploadForm.deptId)
    console.log('   - 描述:', uploadForm.description)
    console.log('   - 自动向量化:', uploadForm.autoVectorize)
    
    // 创建FormData对象
    const formData = new FormData()
    formData.append('file', uploadForm.file)
    
    // 根据用户类型和文件类型决定是否传递deptId
    if (isAdminUser(userInfo.value) && uploadForm.isPublic) {
      // 管理员上传制度文件：传递deptId，后端直接通过无需审核
      console.log('📝 管理员上传制度文件，传递deptId，直接通过审核')
      if (uploadForm.deptId) {
        formData.append('deptId', uploadForm.deptId)
      }
    } else if (!isAdminUser(userInfo.value) && uploadForm.isPublic) {
      // 普通用户上传公共文件：不传deptId（触发审核流程），但fileCategory会是rule
      console.log('📝 普通用户上传公共文件，不传deptId，等待审核')
      // 注意：不传deptId，但isPublic=true，后端determineFileCategory仍会返回"rule"
    }
    // 个人文件：不传递deptId参数
    
    formData.append('isPublic', uploadForm.isPublic) // true=公开文件，false=个人文件
    if (uploadForm.description.trim()) {
      formData.append('description', uploadForm.description.trim())
    }
    
    // 调用API上传
    const response = await fileAPI.uploadFile(formData, undefined, {
      autoVectorize: uploadForm.autoVectorize
    })
    
    if (response.data.code === 200) {
      const uploadedFile = response.data.data
      
      const fileTypeText = uploadForm.isPublic ? '公开文件' : '个人文件'
      
      let successMsg = `${fileTypeText}"${uploadForm.file.name}"上传成功！`
      if (!isAdminUser(userInfo.value) && uploadForm.isPublic) {
        successMsg += ' 文件已进入待审批队列，请等待管理员审核！'
      }
      
      message.success(successMsg)
      closeUploadModal()

      // 仅勾选了向量化时才展示任务面板
      if (uploadForm.autoVectorize) {
        addUploadTask(uploadedFile)
        showTaskPanel.value = true
        startTaskPolling()
      }
      
      await loadFileList(null, true)
      if (canApproveFile.value) {
        loadPendingFiles()
      }
    } else {
      throw new Error(response.data.message || '上传失败')
    }
  } catch (error) {
    message.error(error.message || '文件上传失败，请稍后重试')
  } finally {
    isUploading.value = false
  }
}

const handleDocumentClick = (document) => {
  selectedFileForVersions.value = document
  showVersionList.value = true
  
  versionVectorizeStatus.value.clear()
  checkVersionsVectorizeStatus(document)
}

const checkVersionsVectorizeStatus = async (document) => {
  if (!document?.versions?.length) return
  
  try {
    const userDeptName = getUserDepartmentName()
    if (!userDeptName) {
      await fetchUserInfo()
      await fetchDepartments()
    }
    
    if (departmentList.value.length === 0) {
      await fetchDepartments()
    }
    
    const response = await ragAPI.getCollections()
    
    let allCollections = []
    
    if (Array.isArray(response)) {
      allCollections = response.map(item => typeof item === 'string' ? { name: item, department: '' } : item)
    } else if (response && Array.isArray(response.collections)) {
      allCollections = response.collections.map(item => typeof item === 'string' ? { name: item, department: '' } : item)
    } else if (response && response.data && Array.isArray(response.data)) {
      allCollections = response.data.map(item => typeof item === 'string' ? { name: item, department: '' } : item)
    }
    
    const isAdmin = isAdminUser(userInfo.value)
    
    console.log('[出题预检] 用户部门:', userDeptName, '部门列表长度:', departmentList.value.length)
    
    const targetKbs = allCollections.filter(item => {
      if (isAdmin) return true
      
      const kbVisibility = item.visibility || ''
      const kbDept = item.department || ''
      
      const isPublicVisible = kbVisibility === 'public'
      const isPublicDept = kbDept.includes('公共')
      const isSameDept = kbDept && kbDept === userDeptName
      
      return isPublicVisible || isPublicDept || isSameDept
    })
    
    const statusMap = new Map()

    for (const ver of document.versions) {
      let foundInCollection = null
      let foundDocInfo = null

      for (const kb of targetKbs) {
        try {
          const docRes = await ragAPI.getDocumentList(kb.name)

          // 🔍 深度调试：打印完整API返回数据
          console.log(`[出题预检] 🔍 知识库 "${kb.name}" 完整API响应:`, JSON.stringify(docRes, null, 2))
          console.log(`[出题预检] 📊 响应摘要:`, {
            type: typeof docRes,
            isArray: Array.isArray(docRes),
            isObject: docRes && typeof docRes === 'object',
            keys: docRes && typeof docRes === 'object' ? Object.keys(docRes) : [],
            topLevelFields: docRes,
            hasSuccess: 'success' in (docRes || {}),
            hasDocuments: 'documents' in (docRes || {}),
            hasData: 'data' in (docRes || {}),
            hasStatusCode: 'status_code' in (docRes || {})
          })

          // ✅ 修复：兼容多种可能的数据结构
          let documents = []

          if (docRes?.success && docRes?.documents && Array.isArray(docRes.documents)) {
            documents = docRes.documents
          } else if (docRes?.data?.success && docRes?.data?.documents && Array.isArray(docRes.data.documents)) {
            documents = docRes.data.documents
            console.log(`[出题预检] ✅ 使用 data.documents 路径`)
          } else if (Array.isArray(docRes)) {
            documents = docRes
            console.log(`[出题预检] ✅ 响应本身是数组`)
          } else if (docRes?.data && Array.isArray(docRes.data)) {
            documents = docRes.data
            console.log(`[出题预检] ✅ 使用 data 数组路径`)
          }

          console.log(`[出题预检] 📄 最终文档列表长度: ${documents.length}`)

          if (documents.length > 0) {
            // ✅ 修复：兼容多种字段名（fileId, doc_id, id）
            const foundDoc = documents.find(doc => {
              const docId = doc.fileId || doc.doc_id || doc.id
              return String(docId) === String(ver.id)
            })

            if (foundDoc) {
              console.log(`[出题预检] ✅ 版本 v${ver.version} 在知识库 "${kb.name}" 中找到向量化文件:`, foundDoc)
              foundInCollection = kb
              foundDocInfo = foundDoc
              break
            }
          }
        } catch (err) {
          console.warn(`[出题预检] 获取知识库 ${kb.name} 文件列表失败:`, err.message)
          continue
        }

        if (foundInCollection) break
      }

      if (foundInCollection && foundDocInfo) {
        statusMap.set(String(ver.id), {
          found: true,
          collectionName: foundInCollection.name,
          docInfo: foundDocInfo
        })
        console.log(`[出题预检] ✅ 版本 v${ver.ver} (ID:${ver.id}) 状态: 已向量化 → 显示出题按钮`)
      } else {
        statusMap.set(String(ver.id), { found: false })
        console.log(`[出题预检] ⚠️ 版本 v${ver.version} (ID:${ver.id}) 状态: 未向量化 → 隐藏出题按钮`)
      }
    }

    versionVectorizeStatus.value = statusMap
  } catch (error) {
    console.error('[出题预检] ❌ 检查版本向量化状态失败:', error)
  }
}

const isVersionVectorized = (versionId) => {
  const status = versionVectorizeStatus.value.get(String(versionId))
  return status?.found === true
}

const getAccessibleKbForExam = async () => {
  const userDeptName = getUserDepartmentName()
  if (!userDeptName) {
    await fetchUserInfo()
    await fetchDepartments()
  }
  
  if (departmentList.value.length === 0) {
    await fetchDepartments()
  }
  
  const response = await ragAPI.getCollections()
  
  let allCollections = []
  
  if (Array.isArray(response)) {
    allCollections = response.map(item => typeof item === 'string' ? { name: item, department: '' } : item)
  } else if (response && Array.isArray(response.collections)) {
    allCollections = response.collections.map(item => typeof item === 'string' ? { name: item, department: '' } : item)
  } else if (response && response.data && Array.isArray(response.data)) {
    allCollections = response.data.map(item => typeof item === 'string' ? { name: item, department: '' } : item)
  }
  
  const isAdmin = isAdminUser(userInfo.value)
  
  console.log('[出题] 用户部门:', userDeptName)
  
  return allCollections.filter(item => {
    if (isAdmin) return true
    
    const kbVisibility = item.visibility || ''
    const kbDept = item.department || ''
    
    const isPublicVisible = kbVisibility === 'public'
    const isPublicDept = kbDept.includes('公共')
    const isSameDept = kbDept && kbDept === userDeptName
    
    return isPublicVisible || isPublicDept || isSameDept
  })
}

const handleToggleVersionExpand = async (ver) => {
  if (expandedVersionId.value === ver.id) {
    expandedVersionId.value = null
    return
  }
  expandedVersionId.value = ver.id
  if (!versionPapers.value[ver.id]) {
    fetchVersionPapers(ver)
  }
}

const handleSubTabChange = (tab, ver) => {
}

const getQuestionPagination = (versionId) => ({ records: [], total: 0, currentPage: 1, totalPages: 1 })

const getPaperPagination = (versionId) => {
  const pageState = versionPaperPage.value[versionId] || { current: 1, pageSize: VERSION_PAGE_SIZE, total: 0 }
  const records = versionPapers.value[versionId] || []
  return {
    records,
    total: pageState.total || records.length,
    currentPage: pageState.current,
    pageSize: pageState.pageSize,
    totalPages: Math.max(1, Math.ceil((pageState.total || records.length) / pageState.pageSize))
  }
}

const handleQuestionPageChange = () => {}
const handlePaperPageChange = async (versionId, direction) => {
  const pageState = versionPaperPage.value[versionId] || { current: 1, pageSize: VERSION_PAGE_SIZE, total: 0 }
  let newPage = direction === 'prev' ? pageState.current - 1 : pageState.current + 1
  const maxPages = Math.max(1, Math.ceil(pageState.total / pageState.pageSize))
  newPage = Math.max(1, Math.min(newPage, maxPages))
  if (newPage !== pageState.current) {
    versionPaperPage.value = { ...versionPaperPage.value, [versionId]: { ...pageState, current: newPage } }
    await fetchVersionPapersById(versionId)
  }
}

const paperStartingLoading = ref(false)

const handleStartPaperExam = async (paper, ver) => {
  if (paper.status !== 'published') {
    message.warning('该试卷尚未发布，无法开始考试')
    return
  }

  if (paperStartingLoading.value) return
  paperStartingLoading.value = true

  try {
    const response = await examAPI.start(paper.paperId)
    if (!response || !response.data) throw new Error('接口返回异常')

    const result = response.data
    if (result.code !== undefined && result.code !== 200 && result.code !== 0 && result.code !== 'success') {
      throw new Error(result.message || `启动考试失败（${result.code}）`)
    }

    const recordData = result.data || result

    eventBus.emit('navigateToExamWithFileEvent', {
      file: ver,
      source: 'version-paper',
      paperId: paper.paperId,
      paperTitle: paper.title,
      examRecordId: recordData.recordId || recordData.examRecordId || recordData.id,
      startFromRecord: true
    })

    await nextTick()
    message.success(`已启动考试：${paper.title}`)
  } catch (err) {
    console.error('启动试卷考试失败:', err)
    const errMsg = err?.response?.data?.message || err?.data?.message || err?.message || '启动考试失败，请稍后重试'
    message.error(errMsg)
  } finally {
    paperStartingLoading.value = false
  }
}

const fetchVersionPapers = async (ver) => {
  await fetchVersionPapersById(ver.id, ver)
}

const fetchVersionPapersById = async (versionId, ver) => {
  versionPapersLoading.value = { ...versionPapersLoading.value, [versionId]: true }
  try {
    const result = await examAPI.getAdminPapersByFileId(ver?.id || versionId)
    const matchingPapers = result?.records || []
    versionPapers.value = { ...versionPapers.value, [versionId]: matchingPapers }
    versionPaperPage.value = { ...versionPaperPage.value, [versionId]: { current: 1, pageSize: VERSION_PAGE_SIZE, total: matchingPapers.length } }
  } catch (err) {
    console.error('获取版本试卷失败:', err)
    versionPapers.value = { ...versionPapers.value, [versionId]: [] }
  } finally {
    versionPapersLoading.value = { ...versionPapersLoading.value, [versionId]: false }
  }
}

const handleVersionSelect = async (version) => {
  if (!canReadDocument.value) {
    message.warning('您没有阅读文档的权限')
    return
  }

  router.push({
    name: 'Reader',
    query: {
      id: version.id,
      title: version.title || version.rawFileName || '文件',
      extension: version.extension || '.pdf'
    }
  })
  message.success({ content: `正在打开《${version.title}》...`, key: 'loadFile' })
}

const handleJumpToReaderPage = () => {
  // 如果有选中的文件，跳转到该文件的阅读器
  if (selectedFileForVersions.value && selectedFileForVersions.value.versions && selectedFileForVersions.value.versions.length > 0) {
    const latestVersion = selectedFileForVersions.value.versions[0]
    router.push({
      name: 'Reader',
      query: {
        id: latestVersion.id,
        title: latestVersion.title || latestVersion.rawFileName || '文件',
        extension: latestVersion.extension || '.pdf'
      }
    })
  } else {
    // 否则直接跳转到阅读器页面，不带文件参数
    router.push({
      name: 'Reader',
      query: {}
    })
  }
}

// 根据文件扩展名识别当前查看的文件类型
const getCurrentViewerFileType = (extension) => {
  if (!extension) return 'pdf'
  const ext = extension.toLowerCase()
  
  const normalizedExt = ext.startsWith('.') ? ext : '.' + ext
  
  if (['.pdf'].includes(normalizedExt)) return 'pdf'
  if (['.doc', '.docx'].includes(normalizedExt)) return 'docx'
  if (['.xls', '.xlsx'].includes(normalizedExt)) return 'xlsx'
  if (['.txt', '.json', '.xml', '.csv', '.md', '.html', '.htm', '.log', '.js', '.css'].includes(normalizedExt)) return 'text'
  if (['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'].includes(normalizedExt)) return 'image'
  
  return 'pdf' // 默认为pdf
}

// 处理缩放
const handleZoomIn = () => {
  if (currentViewerFileType.value === 'pdf') {
    scale.value = Math.min(scale.value + 0.25, 3)
  } else if (['image', 'docx'].includes(currentViewerFileType.value) && fileReaderRef?.value) {
    fileReaderRef.value.zoomIn()
  }
}

const handleZoomOut = () => {
  if (currentViewerFileType.value === 'pdf') {
    scale.value = Math.max(scale.value - 0.25, 0.5)
  } else if (['image', 'docx'].includes(currentViewerFileType.value) && fileReaderRef?.value) {
    fileReaderRef.value.zoomOut()
  }
}

const handleResetImageZoom = () => {
  if (fileReaderRef?.value?.resetImageZoom) {
    fileReaderRef.value.resetImageZoom()
  }
}

const handleFitToWidth = () => {
  if (currentViewerFileType.value === 'pdf') {
    scale.value = 1.0
  }
}

const handleFitToPage = () => {
  if (currentViewerFileType.value === 'pdf') {
    scale.value = 0.85
  }
}

// 处理Excel工作表变化
const handleSheetChange = (sheetName) => {
  if (fileReaderRef?.value?.setActiveSheet) {
    fileReaderRef.value.setActiveSheet(sheetName)
  }
}

// 处理文本搜索
const handleTextSearch = () => {
  if (fileReaderRef?.value?.searchText) {
    fileReaderRef.value.searchText()
  }
}

const handlePrevSearchMatch = () => {
  if (fileReaderRef?.value?.prevSearchMatch) {
    fileReaderRef.value.prevSearchMatch()
  }
}

const handleNextSearchMatch = () => {
  if (fileReaderRef?.value?.nextSearchMatch) {
    fileReaderRef.value.nextSearchMatch()
  }
}

// 处理文本功能
const handleCopyText = () => {
  if (fileReaderRef?.value?.copyText) {
    fileReaderRef.value.copyText()
  }
}

const handleToggleWordWrap = () => {
  if (fileReaderRef?.value?.toggleWordWrap) {
    fileReaderRef.value.toggleWordWrap()
  }
}

const handleBackFromVersions = () => {
  showVersionList.value = false
  selectedFileForVersions.value = null
  resetCompare()
}

const handleDocumentSelect = (document) => {
  handleVersionSelect(document)
}

// ========== 版本对比功能 ==========

const isTextFile = (fileName) => {
  const ext = fileName?.split('.').pop()?.toLowerCase() || ''
  return TEXT_EXTENSIONS.includes(ext)
}

const escapeHtml = (str) => {
  if (!str) return ''
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/ /g, '&nbsp;').replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
}

const buildDiffHtml = (leftText, rightText, diffOnly, side) => {
  const leftLines = leftText.split('\n')
  const rightLines = rightText.split('\n')
  const maxLen = Math.max(leftLines.length, rightLines.length)
  const result = []

  for (let i = 0; i < maxLen; i++) {
    const left = leftLines[i] ?? ''
    const right = rightLines[i] ?? ''
    const lineNum = i + 1

    if (left === right && left === '') continue

    if (left === right) {
      if (!diffOnly) {
        result.push(`<div class="diff-line diff-same" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content">${escapeHtml(left)}</span></div>`)
      }
    } else if (side === 'left') {
      if (left) {
        result.push(`<div class="diff-line diff-remove" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content"><span class="diff-marker">-</span>${escapeHtml(left)}</span></div>`)
      } else {
        result.push(`<div class="diff-line diff-empty-left" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content"></span></div>`)
      }
    } else {
      if (right) {
        result.push(`<div class="diff-line diff-add" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content"><span class="diff-marker">+</span>${escapeHtml(right)}</span></div>`)
      } else {
        result.push(`<div class="diff-line diff-empty-right" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content"></span></div>`)
      }
    }
  }

  if (result.length === 0) {
    return `<div style="text-align:center;padding:40px;color:#6C757D;">两个版本内容完全相同</div>`
  }
  return result.join('')
}

const buildInlineDiffHtml = (leftText, rightText, diffOnly) => {
  const leftLines = leftText.split('\n')
  const rightLines = rightText.split('\n')
  const maxLen = Math.max(leftLines.length, rightLines.length)
  const result = []

  for (let i = 0; i < maxLen; i++) {
    const left = leftLines[i] ?? ''
    const right = rightLines[i] ?? ''
    const lineNum = i + 1

    if (left === right && left === '') continue

    if (left === right) {
      if (!diffOnly) {
        result.push(`<div class="diff-line diff-same" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content">${escapeHtml(left)}</span></div>`)
      }
    } else {
      if (left && right) {
        result.push(`<div class="diff-line diff-modify" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content"><span class="diff-marker">~</span><del class="diff-del">${escapeHtml(left)}</del><ins class="diff-ins">${escapeHtml(right)}</ins></span></div>`)
      } else if (left) {
        result.push(`<div class="diff-line diff-remove" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content"><span class="diff-marker">-</span>${escapeHtml(left)}</span></div>`)
      } else {
        result.push(`<div class="diff-line diff-add" data-line="${lineNum}"><span class="line-num">${lineNum}</span><span class="line-content"><span class="diff-marker">+</span>${escapeHtml(right)}</span></div>`)
      }
    }
  }

  if (result.length === 0) {
    return `<div style="text-align:center;padding:40px;color:#6C757D;">两个版本内容完全相同</div>`
  }
  return result.join('')
}

const openCompareModal = () => {
  comparePendingVersion.value = null
  showCompareModal.value = true
}

const selectForCompare = (ver) => {
  if (!comparePendingVersion.value) {
    comparePendingVersion.value = ver
    message.info(`已选择 v${ver.version}，请点击另一个版本的「对比」按钮`)
  } else if (comparePendingVersion.value.id === ver.id) {
    comparePendingVersion.value = null
    message.info('已取消版本选择')
  } else {
    const leftVer = comparePendingVersion.value
    const rightVer = ver
    comparePendingVersion.value = null
    compareLeftVersion.value = leftVer.id
    compareLeftRecord.value = leftVer
    compareRightVersion.value = rightVer.id
    compareRightRecord.value = rightVer
    showCompareModal.value = true
    performCompare()
  }
}

const performCompare = async () => {
  if (!compareLeftVersion.value || !compareRightVersion.value) return

  const versions = selectedFileForVersions.value?.versions || []
  const leftFile = versions.find(v => v.id === compareLeftVersion.value)
  const rightFile = versions.find(v => v.id === compareRightVersion.value)
  const leftName = leftFile?.rawFileName || leftFile?.title || ''
  const rightName = rightFile?.rawFileName || rightFile?.title || ''

  if (!isTextFile(leftName) && !isTextFile(rightName)) {
    compareFileType.value = (leftName.split('.').pop() || '').toLowerCase()
    compareError.value = 'unsupported'
    compareLeftContent.value = ''
    compareRightContent.value = ''
    compareInlineContent.value = ''
    return
  }

  compareLoading.value = true
  compareError.value = null
  compareFileType.value = null
  try {
    const [leftRes, rightRes] = await Promise.all([
      fileAPI.getTextFileContent(compareLeftVersion.value).catch(e => ({ error: e })),
      fileAPI.getTextFileContent(compareRightVersion.value).catch(e => ({ error: e }))
    ])

    let leftText = ''
    let rightText = ''

    if (leftRes.error) {
      leftText = '[无法加载该版本的内容]'
    } else {
      leftText = leftRes?.data?.data || leftRes?.data || ''
      if (typeof leftText !== 'string') leftText = String(leftText || '')
    }

    if (rightRes.error) {
      rightText = '[无法加载该版本的内容]'
    } else {
      rightText = rightRes?.data?.data || rightRes?.data || ''
      if (typeof rightText !== 'string') rightText = String(rightText || '')
    }

    if (!leftText && !rightText) {
      compareError.value = 'empty'
      return
    }

    compareLeftContent.value = buildDiffHtml(leftText, rightText, showDiffOnly.value, 'left')
    compareRightContent.value = buildDiffHtml(leftText, rightText, showDiffOnly.value, 'right')
    compareInlineContent.value = buildInlineDiffHtml(leftText, rightText, showDiffOnly.value)
  } catch (e) {
    console.error('对比失败:', e)
    compareError.value = 'error'
    message.error('版本对比失败: ' + (e.message || ''))
  } finally {
    compareLoading.value = false
  }
}

const toggleDiffOnly = () => {
  showDiffOnly.value = !showDiffOnly.value
  if (compareLeftVersion.value && compareRightVersion.value) {
    performCompare()
  }
}

const resetCompare = () => {
  showCompareModal.value = false
  compareLeftVersion.value = null
  compareRightVersion.value = null
  compareLeftRecord.value = null
  compareRightRecord.value = null
  compareLeftContent.value = ''
  compareRightContent.value = ''
  compareInlineContent.value = ''
  compareError.value = null
  compareFileType.value = null
  showDiffOnly.value = false
  comparePendingVersion.value = null
}

// ========== 向量化功能 ==========

const openVectorizeModal = async (ver) => {
  if (!ver || !ver.id) {
    message.warning('无法获取文件信息')
    return
  }
  vectorizeTargetVersion.value = ver
  showVectorizeModal.value = true
}

// 去向量化：按文档5.2.2规范，删除向量化数据并软删除关联题目
const handleDevectorize = async (ver) => {
  if (!ver || !ver.id) {
    message.warning('无法获取文件信息')
    return
  }
  if (devectorizingId.value === ver.id) return

  // 从缓存中获取该版本关联的知识库名称
  const cachedStatus = versionVectorizeStatus.value.get(String(ver.id))
  const collectionName = cachedStatus?.collectionName

  if (!collectionName) {
    message.warning('未找到该文件关联的知识库信息，请确认文件已向量化')
    return
  }

  // 确认弹窗（ant-design-vue 的 Modal.confirm 直接返回 Promise）
  try {
    await Modal.confirm({
      title: '确认去向量化',
      content: `确定要删除文件「${ver.fileName || '未知文件'}」的向量化数据吗？\n\n此操作将：\n1. 软删除该文件关联的所有已生成题目\n2. 从知识库中移除向量数据\n3. 重置文件状态为待处理\n\n注意：操作不可逆，如需使用需重新向量化。`,
      okText: '确认去向量化',
      okType: 'danger',
      cancelText: '取消'
    })
  } catch (_e) {
    return // 用户取消
  }

  devectorizingId.value = ver.id

  try {
    const res = await ragAPI.devectorize(collectionName, ver.id)

    const resultData = res || {}
    const deletedCount = resultData.deletedQuestionCount ?? null
    const fileName = resultData.fileName || ver.fileName || ver.title || '该文件'

    if (deletedCount !== null && deletedCount > 0) {
      message.success(`"${fileName}"的向量化数据已删除，同时软删除了 ${deletedCount} 道关联题目`)
    } else {
      message.success(`"${fileName}"的向量化数据已删除`)
    }

    // 更新本地状态：从versionVectorizeStatus中移除该版本的向量化记录
    versionVectorizeStatus.value.delete(String(ver.id))

    // 刷新文件列表以同步最新状态
    hasLoadedFiles.value = false
    await loadFileList(null, true)
  } catch (e) {
    if (e.message === 'cancelled') return
    message.error('去向量化失败: ' + (e.message || '请检查网络连接或服务状态'))
  } finally {
    devectorizingId.value = null
  }
}

// ========== 文件标签编辑（按文档5.1.3规范）==========

const startEditTags = (ver) => {
  editingTagsId.value = ver.id
  // 将tags数组转为逗号分隔的字符串
  const existingTags = ver.tags || []
  editingTagsText.value = Array.isArray(existingTags) ? existingTags.join(', ') : ''
}

const cancelEditTags = () => {
  editingTagsId.value = null
  editingTagsText.value = ''
}

const saveFileTags = async (ver) => {
  const fileId = ver.id || ver.fileId
  if (!fileId) {
    message.error('无法保存：文件ID不存在')
    return
  }

  // 解析标签文本为数组，过滤空值
  const tagArray = editingTagsText.value
    .split(/[,，]/)
    .map(t => t.trim())
    .filter(t => t.length > 0)

  try {
    // 按文档5.1.3规范：PUT /file/{id} 的 tags 为 Map(JSON对象)格式
    // 将简单标签数组转为兼容的JSON对象：{ "tag_0": "标签1", "tag_1": "标签2" }
    const tagsMap = {}
    tagArray.forEach((tag, idx) => { tagsMap[`tag_${idx}`] = tag })
    const res = await fileAPI.updateFile(fileId, { tags: tagsMap })

    if (res.data?.code === 200) {
      // 更新本地数据（同时存数组和Map，展示用数组）
      ver.tags = tagArray
      ver._tagsMap = tagsMap
      message.success('文件标签已更新')

      // 如果该版本在documentList中，同步更新
      const docItem = documentList.value.find(d =>
        d.versions && d.versions.some(v => (v.id || v.fileId) === fileId)
      )
      if (docItem) {
        const targetVer = docItem.versions.find(v => (v.id || v.fileId) === fileId)
        if (targetVer) targetVer.tags = tagArray
      }
    } else {
      message.error(res.data?.message || '更新标签失败')
    }
  } catch (e) {
    message.error('保存标签失败: ' + (e.message || '请检查网络连接'))
  } finally {
    editingTagsId.value = null
    editingTagsText.value = ''
  }
}

// 按文档5.2.3规范：基于已向量化文件生成考试题目
// 跳转到考察与训练页面并自动选中该文件
const handleGenerateExamForFile = async (fileItem) => {
  if (generatingExamId.value === fileItem.id) return

  // 从缓存中获取该版本关联的知识库（避免重复查询）
  const cachedStatus = versionVectorizeStatus.value.get(String(fileItem.id))
  const collectionName = cachedStatus?.collectionName

  if (!collectionName) {
    message.warning('未找到该文件关联的知识库信息，请确认文件已向量化')
    return
  }

  generatingExamId.value = fileItem.id

  try {
    // 跳转到考察与训练页面，自动选中文档并填充知识库名称
    triggerNavigateToExamWithFile({
      fileData: {
        id: fileItem.id,
        title: fileItem.title || fileItem.fileName || fileItem.rawFileName,
        fileName: fileItem.fileName || fileItem.rawFileName,
        rawFileName: fileItem.rawFileName || fileItem.fileName,
        currentVersion: fileItem.version || 1,
        extension: fileItem.extension
      },
      collectionName: collectionName,
      docId: String(fileItem.id),
      docName: fileItem.fileName || fileItem.rawFileName || fileItem.title
    })

    console.log('[文件出题] 已跳转到考察与训练，目标文件:', fileItem.fileName)
  } catch (e) {
    console.error('[文件出题] ❌ 失败:', e)
    message.error('出题失败: ' + (e.message || '请检查网络连接或服务状态'))
  } finally {
    generatingExamId.value = null
  }
}

const handleGenerateExamForVersion = async (versionItem) => {
  if (generatingExamId.value === versionItem.id) return

  // 优先从缓存获取知识库名称
  let collectionName = versionVectorizeStatus.value.get(String(versionItem.id))?.collectionName

  // 缓存未命中时，回退到遍历知识库查找
  if (!collectionName) {
    const fileId = versionItem.id || versionItem.fileId
    const knowledgeBases = await getAccessibleKbForExam()

    for (const kb of knowledgeBases) {
      try {
        const docRes = await ragAPI.getDocumentList(kb.name)
        let documents = []
        if (docRes?.success && docRes?.documents && Array.isArray(docRes.documents)) {
          documents = docRes.documents
        } else if (docRes?.data?.success && docRes?.data?.documents && Array.isArray(docRes.data.documents)) {
          documents = docRes.data.documents
        } else if (Array.isArray(docRes)) {
          documents = docRes
        } else if (docRes?.data && Array.isArray(docRes.data)) {
          documents = docRes.data
        }

        if (documents.length > 0) {
          const foundDoc = documents.find(doc => {
            const docId = doc.fileId || doc.doc_id || doc.id
            return String(docId) === String(fileId)
          })
          if (foundDoc) {
            collectionName = kb.name
            // 缓存结果避免重复查询
            versionVectorizeStatus.value.set(String(versionItem.id), {
              found: true,
              collectionName: kb.name,
              docInfo: foundDoc
            })
            break
          }
        }
      } catch (_err) { continue }
      if (collectionName) break
    }
  }

  if (!collectionName) {
    message.warning({
      content: `该文件版本 v${versionItem.version} 尚未向量化到知识库中，无法用于出题。`,
      duration: 5
    })
    return
  }

  generatingExamId.value = versionItem.id

  try {
    // 跳转到考察与训练页面，自动选中文档并填充知识库名称
    triggerNavigateToExamWithFile({
      fileData: {
        id: versionItem.id,
        title: selectedFileForVersions.value?.title || versionItem.fileName,
        fileName: versionItem.fileName,
        rawFileName: versionItem.rawFileName || versionItem.fileName,
        currentVersion: versionItem.version,
        extension: versionItem.extension || selectedFileForVersions.value?.extension
      },
      collectionName: collectionName,
      docId: String(versionItem.id),
      docName: versionItem.fileName || versionItem.rawFileName
    })

    console.log(`[版本出题] 已跳转到考察与训练，目标文件 v${versionItem.version}`)
  } catch (e) {
    console.error('[版本出题] ❌ 失败:', e)
    message.error('出题失败: ' + (e.message || '请检查网络连接或服务状态'))
  } finally {
    generatingExamId.value = null
  }
}

const closeVectorizeModal = () => {
  showVectorizeModal.value = false
  vectorizeTargetVersion.value = null
  vectorizeSelectedCollection.value = ''
}

const loadVectorizeCollections = async () => {
  vectorizeCollectionLoading.value = true
  try {
    const userDeptName = getUserDepartmentName()
    if (!userDeptName) {
      console.log('[向量化] 用户部门为空，正在重新获取用户信息和部门列表...')
      await fetchUserInfo()
      await fetchDepartments()
    }
    
    if (departmentList.value.length === 0) {
      console.log('[向量化] 部门列表为空，正在加载...')
      await fetchDepartments()
    }
    
    const response = await ragAPI.getCollections()
    
    let allCollections = []
    
    if (Array.isArray(response)) {
      allCollections = response.map(item => typeof item === 'string' ? { name: item, displayName: item } : item)
    } else if (response && Array.isArray(response.collections)) {
      allCollections = response.collections.map(item => typeof item === 'string' ? { name: item, displayName: item } : item)
    } else if (response && response.data && Array.isArray(response.data)) {
      allCollections = response.data.map(item => typeof item === 'string' ? { name: item, displayName: item } : item)
    }
    
    const isAdmin = isAdminUser(userInfo.value)
    
    console.log('[向量化] 用户部门:', userDeptName, '是否管理员:', isAdmin)
    console.log('[向量化] 用户完整信息:', {
      deptName: userInfo.value?.deptName,
      departmentName: userInfo.value?.departmentName,
      department: userInfo.value?.department,
      deptId: userInfo.value?.deptId,
      departmentId: userInfo.value?.departmentId
    })
    console.log('[向量化] 部门列表:', departmentList.value.map(d => ({ id: d.id, deptId: d.deptId, name: d.name, deptName: d.deptName })))
    console.log('[向量化] 全部知识库:', allCollections.map(c => ({ name: c.name, dept: c.department, visibility: c.visibility })))
    
    const filteredList = allCollections.filter(item => {
      if (isAdmin) return true
      
      const kbVisibility = item.visibility || ''
      const kbDept = item.department || ''
      
      const isPublicVisible = kbVisibility === 'public'
      const isPublicDept = kbDept.includes('公共')
      const isSameDept = kbDept && kbDept === userDeptName
      
      const canAccess = isPublicVisible || isPublicDept || isSameDept
      
      console.log(`[向量化] ${item.name}: dept="${kbDept}", visibility="${kbVisibility}" → ${canAccess ? '✅可访问' : '❌不可访问'}`, { isPublicVisible, isPublicDept, isSameDept })
      
      return canAccess
    })
    
    const list = filteredList.map(item => ({
      name: item.name || item.id || '',
      displayName: item.displayName || item.description || item.name || item.id || '',
      department: item.department || ''
    }))
    
    vectorizeCollections.value = list

    if (list.length > 0 && !vectorizeSelectedCollection.value) {
      const deptKb = list.find(c => c.department?.includes('公共'))
      vectorizeSelectedCollection.value = deptKb ? deptKb.name : list[0].name
    }
  } catch (error) {
    console.error('加载知识库列表失败:', error)
    message.error('加载知识库列表失败')
    vectorizeCollections.value = []
  } finally {
    vectorizeCollectionLoading.value = false
  }
}

const confirmVectorize = async () => {
  if (!vectorizeTargetVersion.value?.id) {
    message.warning('请选择要向量化的文件版本')
    return
  }

  const fileId = vectorizeTargetVersion.value.id
  const fileVersion = vectorizeTargetVersion.value.version

  console.log(`[向量化] 智能提交任务: fileId=${fileId}, version=v${fileVersion}`)

  isVectorizing.value = true
  vectorizingFileId.value = fileId

  try {
    // 使用智能向量化接口，自动根据文件部门ID匹配知识库
    const result = await ragAPI.smartVectorizeFile(fileId)

    // 从响应中提取系统分配的知识库名称
    const assignedCollection = result.collection || result.data?.collection || ''

    // 无论同步还是异步，都先添加任务到面板
    const version = vectorizeTargetVersion.value
    addUploadTask({
      id: fileId,
      fileName: version.fileName || version.rawFileName || version.title || selectedFileForVersions?.value?.fileName || '未知文件',
      fileSize: version.fileSize || 0,
      processStepStatus: 'UPLOADED'
    })
    showTaskPanel.value = true
    startTaskPolling()

    // 判断是否需要异步轮询：有 submitted 标志 或 状态码表明处理中
    const isAsync = !!(result.submitted ||
      result.status === 'VECTORIZING' ||
      result.status_code >= 2000)

    if (isAsync) {
      console.log('[向量化] 智能任务已提交，开始轮询状态:', { fileId, collection: assignedCollection })
      message.success({
        content: `文件 v${fileVersion} 已自动识别知识库「${assignedCollection || '系统默认'}」，正在后台处理...`,
        duration: 4
      })
      startVectorizePolling(fileId, assignedCollection)
    } else {
      console.log('[向量化] 服务端快速响应完成:', result)
      message.success(`文件 v${fileVersion} 已成功向量化到「${assignedCollection || '系统默认'}」`)
      versionVectorizeStatus.value.set(String(fileId), {
        found: true,
        collectionName: assignedCollection
      })
    }

    closeVectorizeModal()
  } catch (error) {
    console.error('[向量化] 提交失败:', {
      code: error.code,
      message: error.message,
      status: error.status ?? error.response?.status,
      originalError: error.originalError ?? error.response?.data
    })

    const errMsg = error.code === 'NOT_FOUND'
      ? '智能向量化接口不存在（404），请确认后端已部署最新版本'
      : error.code === 'INVALID_PARAM'
        ? (error.message || '向量化参数无效，请刷新页面重试')
        : error.code === 'RAG_UNAVAILABLE'
          ? (error.message || 'RAG 服务暂不可用')
          : error.message || '智能向量化任务提交失败'

    message.error('向量化失败: ' + errMsg)
  } finally {
    isVectorizing.value = false
    vectorizingFileId.value = null
  }
}

// ========== 向量化状态轮询 ==========

const VECTORIZE_POLL_INTERVAL = 3000

const getVectorizeLocalStatus = (fileId) => {
  return vectorizeProgressInfo.value.get(String(fileId))
}

const startVectorizePolling = (fileId, assignedCollection = '') => {
  const key = String(fileId)
  stopVectorizePolling(fileId)

  // 使用智能接口返回的知识库名，或回退到手动选择的
  const targetCollection = assignedCollection || vectorizeSelectedCollection.value

  vectorizeProgressInfo.value.set(key, {
    processStatus: 'VECTORIZING',
    processMessage: '正在向量化...',
    isPolling: true,
    collectionName: targetCollection
  })

  const timer = setInterval(async () => {
    try {
      const status = await ragAPI.getFileVectorizeStatus(fileId)
      console.log(`[向量化轮询] 文件 ${fileId}: processStatus=${status.processStatus}`, status.processMessage)

      vectorizeProgressInfo.value.set(key, {
        processStatus: status.processStatus,
        processMessage: status.processMessage || '',
        isPolling: true,
        collectionName: targetCollection
      })

      if (status.processStatus === 'INDEXED' || status.processStatus === 'FAILED') {
        stopVectorizePolling(fileId)

        // 向量化成功 → 更新 versionVectorizeStatus 使 isVersionVectorized 返回 true
        if (status.processStatus === 'INDEXED') {
          versionVectorizeStatus.value.set(String(fileId), {
            found: true,
            collectionName: targetCollection
          })
          // 刷新文件列表同步最新状态
          hasLoadedFiles.value = false
          loadFileList(null, true)
        }

        message.success({
          content: status.processStatus === 'INDEXED'
            ? `文件向量化完成！已入库「${targetCollection}」${status.processMessage ? ` (${status.processMessage})` : ''}`
            : `文件向量化失败: ${status.processMessage || '未知错误'}`,
          duration: 5
        })
      }
    } catch (error) {
      console.warn(`[向量化轮询] 获取文件 ${fileId} 状态失败:`, error.message)
    }
  }, VECTORIZE_POLL_INTERVAL)

  vectorizePollTimers.value[key] = timer
}

const stopVectorizePolling = (fileId) => {
  const key = String(fileId)
  const timer = vectorizePollTimers.value[key]
  if (timer) {
    clearInterval(timer)
    delete vectorizePollTimers.value[key]
  }
  const info = vectorizeProgressInfo.value.get(key)
  if (info) {
    info.isPolling = false
  }
}

const stopAllVectorizePolling = () => {
  Object.keys(vectorizePollTimers.value).forEach(key => {
    const timer = vectorizePollTimers.value[key]
    if (timer) clearInterval(timer)
  })
  vectorizePollTimers.value = {}
}

const getVersionVectorizeBadge = (versionId) => {
  const info = vectorizeProgressInfo.value.get(String(versionId))
  if (!info || !info.isPolling) {
    const cached = versionVectorizeStatus.value.get(String(versionId))
    if (!cached) return null
    return cached.found ? { type: 'success', text: '已入库', key: 'indexed' } : null
  }

  switch (info.processStatus) {
    case 'PENDING': return { type: 'processing', text: '待处理', key: 'pending', pulse: true }
    case 'VECTORIZING': return { type: 'processing', text: '向量化中...', key: 'vectorizing', pulse: true }
    case 'INDEXED': return { type: 'success', text: '已入库', key: 'indexed' }
    case 'FAILED': return { type: 'error', text: '失败', key: 'failed' }
    default: return { type: 'default', text: info.processStatus || '未知', key: 'unknown' }
  }
}

// ========== 版本回退功能 ==========

const openRollbackModal = (ver) => {
  rollbackTargetVersion.value = ver
  showRollbackModal.value = true
}

const confirmRollback = async () => {
  if (!rollbackTargetVersion.value) {
    message.warning('请选择要回退的版本')
    return
  }
  rollbackLoading.value = true
  try {
    const targetFile = rollbackTargetVersion.value
    const res = await fileAPI.getFileContent(targetFile.id)
    const content = res.data?.data || res.data
    const blob = new Blob([content instanceof ArrayBuffer ? new Uint8Array(content) : content], {
      type: targetFile.fileType || 'application/octet-stream'
    })
    const file = new File([blob], targetFile.rawFileName || targetFile.fileName, { type: blob.type })
    const formData = new FormData()
    formData.append('file', file)
    if (targetFile.deptId) {
      formData.append('deptId', targetFile.deptId)
    }
    formData.append('isPublic', targetFile.isPublic != null ? String(targetFile.isPublic) : 'true')
    const uploadRes = await fileAPI.uploadFile(formData)
    if (uploadRes.data?.code === 200) {
      message.success('版本回退成功！已将目标版本内容作为新版本上传。')
      showRollbackModal.value = false
      rollbackTargetVersion.value = null
      hasLoadedFiles.value = false
      await loadFileList(null, true)
    } else {
      message.error('回退失败: ' + (uploadRes.data?.message || '未知错误'))
    }
  } catch (e) {
    console.error('版本回退失败:', e)
    message.error('版本回退失败: ' + (e.message || ''))
  } finally {
    rollbackLoading.value = false
  }
}

// ========== 上传新版本功能 ==========

const openVersionUpload = () => {
  uploadVersionFile.value = null
  showVersionUploadModal.value = true
}

const onVersionFileChange = (e) => {
  const file = e.target.files?.[0]
  if (!file) return
  const maxSize = 1024 * 1024 * 1024
  if (file.size > maxSize) {
    message.error('文件大小不能超过1G')
    return
  }
  uploadVersionFile.value = file
}

const handleVersionUpload = async () => {
  if (!uploadVersionFile.value) {
    message.warning('请选择要上传的文件')
    return
  }
  if (!selectedFileForVersions.value) {
    message.warning('请先选择目标文档')
    return
  }
  const targetDoc = selectedFileForVersions.value
  uploadVersionLoading.value = true
  try {
    const formData = new FormData()
    formData.append('file', uploadVersionFile.value)
    if (targetDoc.deptId) {
      formData.append('deptId', targetDoc.deptId)
    }
    formData.append('isPublic', targetDoc.isPublic != null ? String(targetDoc.isPublic) : 'true')
    const res = await fileAPI.uploadFile(formData)
    if (res.data?.code === 200) {
      message.success('新版本上传成功！')
      showVersionUploadModal.value = false
      uploadVersionFile.value = null
      hasLoadedFiles.value = false
      await loadFileList(null, true)
    } else {
      message.error('上传失败: ' + (res.data?.message || '未知错误'))
    }
  } catch (e) {
    console.error('版本上传失败:', e)
    message.error('版本上传失败: ' + (e.message || ''))
  } finally {
    uploadVersionLoading.value = false
  }
}

const handleBackToList = () => {
  activeTab.value = 'list'
  selectedDocument.value = null
  selectedFileId.value = null
  selectedFileExtension.value = ''
  selectedFileName.value = ''
  
  if (selectedFile.value && selectedFile.value.startsWith('blob:')) {
    URL.revokeObjectURL(selectedFile.value)
  }
  
  selectedFile.value = null
}

const handleAnnotationsChange = (data) => {
  annotations.value = data
}

const handleModeChange = (mode) => {
  currentMode.value = mode
}

const handleColorChange = (color) => {
  selectedColor.value = color
}

const handlePreviousPage = () => {
  if (currentPage.value > 1) {
    currentPage.value--
  }
}

const handleNextPage = () => {
  if (currentPage.value < numPages.value) {
    currentPage.value++
  }
}

const handlePageChange = (e) => {
  const newPage = parseInt(e.target.value)
  if (!isNaN(newPage) && newPage >= 1 && newPage <= numPages.value) {
    currentPage.value = newPage
  }
}

const handleColorSelect = (color) => {
  selectedColor.value = color
  showColorPicker.value = false
  handleModeChange('highlight')
}

const toggleSpeechPanel = () => {
  showSpeechControls.value = !showSpeechControls.value
  showColorPicker.value = false
  if (!showSpeechControls.value) {
    handleReadingToggle()
  } else {
    if (isReading.value) {
      handleReadingStop()
    }
  }
}

const testSpeech = () => {
  try {
    const synthesis = window.speechSynthesis
    const testText = '这是一段测试语音，用于检查朗读功能是否正常工作。'
    const utterance = new SpeechSynthesisUtterance(testText)
    utterance.lang = 'zh-CN'
    utterance.rate = readingSpeed.value
    synthesis.speak(utterance)
  } catch (e) {
    message.error('测试朗读失败，请检查浏览器设置')
  }
}

const handleExportAnnotations = () => {
  if (annotations.value) {
    const dataStr = JSON.stringify(annotations.value, null, 2)
    const dataBlob = new Blob([dataStr], { type: 'application/json' })
    const url = URL.createObjectURL(dataBlob)
    const link = document.createElement('a')
    link.href = url
    link.download = 'annotations.json'
    link.click()
    URL.revokeObjectURL(url)
  }
}

const handleImportAnnotationsClick = () => {
  importAnnotationsRef.value?.click()
}

const handleImportAnnotations = (e) => {
  const file = e.target.files[0]
  if (!file) return

  const reader = new FileReader()
  reader.onload = (event) => {
    try {
      const data = JSON.parse(event.target.result)
      annotations.value = data
    } catch (err) {
      console.error('Failed to import annotations:', err)
      message.error('导入批注失败，请检查文件格式')
    }
  }
  reader.readAsText(file)
}

const openDocumentFromQA = async (payload) => {
  const { filename, page = 1 } = payload
  
  selectedCategory.value = 'all'
  await nextTick()
  
  if (documentList.value.length > 0) {
    const targetDoc = documentList.value.find(doc => {
      const docName = doc.title || doc.name || ''
      const searchName = filename || ''
      return docName.includes(searchName.replace(/\.\w+$/, '')) || searchName.includes(docName.replace(/\.\w+$/, ''))
    })
    
    if (targetDoc) {
      router.push({
        name: 'Reader',
        query: {
          id: targetDoc.id,
          title: targetDoc.title || targetDoc.name || '',
          extension: targetDoc.extension || '.pdf',
          page: page
        }
      })
      message.success(`已打开：${targetDoc.title} 第${page}页`)
      return
    }
  }
  
  message.warning(`未找到文件：${filename}，请先上传该文件到系统中`)
}

defineExpose({
  openDocumentFromQA
})
</script>

<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
}
.modal-content {
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  width: 90%;
  max-width: 480px;
  overflow: hidden;
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-sm) var(--space-lg);
  border-bottom: 1px solid var(--border-light);
}
.modal-header h3 {
  margin: 0;
  font-size: var(--font-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.modal-body {
  padding: var(--space-lg);
}

.read-page {
  height: 100%;
  min-height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 1600px;
  margin: 0 auto;
  background: var(--bg-page);
}

.doc-item {
  cursor: pointer;
  transition: all var(--transition-fast);
}

.doc-item:hover:not(.doc-item-disabled) {
  background-color: var(--bg-hover);
}

.doc-item-disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.doc-item-disabled:hover {
  background-color: transparent;
}

/* 文件卡片网格 - 固定5列2行布局 */
/* ===== 文件条形列表 ===== */
.file-list-bar {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--card-padding);
  width: 100%;
  flex-shrink: 0;
}

.file-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: var(--bg-card);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--transition-fast);
  box-sizing: border-box;
}

.file-bar-empty {
  background: transparent;
  border: 2px dashed var(--border-medium);
  cursor: default;
  opacity: 0.5;
}

.file-bar-empty:hover {
  border-color: var(--border-medium);
  box-shadow: none;
  transform: none;
}

.file-bar:hover {
  border-color: var(--color-primary);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}

.file-bar-icon {
  width: 36px;
  height: 36px;
  background: var(--bg-container);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  flex-shrink: 0;
}

.file-bar-icon svg {
  font-size: 16px;
}

.file-bar-ext {
  font-size: 9px;
  font-weight: var(--weight-medium);
  color: var(--text-tertiary);
}

.file-bar-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.file-bar-title {
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.file-bar-tags {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.file-bar-tags :deep(.ant-tag) {
  font-size: 10px;
  padding: 0 6px;
  line-height: 1.6;
  border-radius: 3px;
  font-weight: var(--weight-normal);
  margin: 0;
}

.file-bar-meta {
  display: flex;
  gap: 12px;
  font-size: 12px;
  color: var(--text-disabled);
  white-space: nowrap;
  flex-shrink: 0;
}

.file-bar-meta span {
  display: flex;
  align-items: center;
  gap: 4px;
}

.file-bar-uploader {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--text-tertiary);
  background: var(--bg-container);
  padding: 2px 10px;
  border-radius: 12px;
  white-space: nowrap;
  flex-shrink: 0;
}

/* 文件类型切换条 */
.file-type-toggle-bar {
  display: flex;
  gap: var(--space-sm);
  background: var(--bg-card);
  padding: var(--space-sm);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-md);
  border: 1px solid var(--border-light);
}

.toggle-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: 12px 16px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--bg-card);
  cursor: pointer;
  transition: all var(--transition-normal);
}

.toggle-btn:hover {
  border-color: var(--color-primary);
  background: var(--bg-hover);
}

.toggle-btn.active {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--text-inverse);
}

.toggle-btn.active .toggle-icon {
  color: var(--text-inverse);
}

.toggle-icon {
  font-size: var(--font-lg);
  color: var(--text-secondary);
}

.toggle-text {
  font-size: var(--font-lg);
  font-weight: var(--weight-medium);
}

.read-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--space-lg);
  min-width: 0;
}

.tab-icon {
  font-size: var(--font-lg);
}

.filter-bar {
  display: flex;
  gap: var(--space-md);
  align-items: center;
  margin-bottom: var(--space-md);
  flex-wrap: wrap;
  padding: var(--space-md) 0;
}

.form-input {
  flex: 1;
  min-width: 250px;
}

.form-select {
  width: 160px;
}

.exam-container {
  width: 100%;
}

.exam-card {
  background: var(--bg-card);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  border: 1px solid var(--border-light);
  width: 100%;
  flex-shrink: 0;
}

.card-title {
  font-size: var(--font-md);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  margin-bottom: var(--space-sm);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.card-subtitle {
  font-size: var(--font-xs);
  color: var(--text-tertiary);
}

.filter-bar {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: var(--space-md);
  flex-shrink: 0;
  min-height: var(--input-height);
}

.form-input {
  width: 300px;
  height: var(--input-height);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-default);
  padding: 0 var(--space-md);
  font-size: var(--font-md);
  transition: all 0.3s ease;
  box-sizing: border-box;
}

.form-input:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-alpha-20);
}

.form-select {
  width: 140px;
  height: var(--input-height);
  padding: 0 28px 0 var(--space-md);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  background: var(--bg-container) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%238c8c8c' d='M6 8.825L1.175 4 2.238 2.938 6 6.7l3.763-3.762L10.825 4z'/%3E%3C/svg%3E") no-repeat right 10px center;
  background-size: 10px;
  font-size: var(--font-md);
  color: var(--text-primary);
  cursor: pointer;
  transition: all 0.25s ease;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  box-sizing: border-box;
}

.form-select:hover {
  border-color: var(--border-default);
  background-color: var(--bg-card);
}

.form-select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-alpha-15);
  background-color: var(--bg-card);
}

.btn {
  border-radius: var(--radius-md);
  padding: 0 var(--space-md);
  height: var(--input-height);
  font-size: var(--font-md);
  font-weight: var(--weight-medium);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  box-sizing: border-box;
}

.btn-primary {
  background: var(--color-primary);
  color: var(--bg-card);
}

.btn-primary:hover {
  background: var(--color-primary-hover);
}

.btn-success {
  background: var(--color-success, #2b7a3f);
  color: var(--bg-card);
}

.btn-success:hover {
  filter: brightness(1.1);
}

.btn-secondary {
  background: var(--border-default);
  color: var(--text-secondary);
}

.btn-secondary:hover {
  background: var(--bg-hover);
}

.btn-warning {
  background: var(--color-warning, #d97706);
  color: var(--bg-card);
}

.btn-warning:hover {
  filter: brightness(1.1);
}

.btn-danger {
  background: var(--color-error);
  color: var(--bg-card);
}

.btn-danger:hover {
  filter: brightness(1.1);
}

.btn-small {
  padding: 3px 10px;
  font-size: 12px;
  height: 28px;
}

.doc-item {
  cursor: pointer;
  border-radius: var(--radius-sm) !important;
  margin-bottom: 4px !important;
  padding: 8px 10px !important;
  border: 1px solid var(--border-light) !important;
  background-color: var(--bg-card) !important;
}

.doc-item:hover {
  border-color: var(--color-primary) !important;
  background-color: var(--bg-hover) !important;
}

.doc-avatar {
  width: 28px !important;
  height: 28px !important;
  background: var(--bg-container) !important;
  border-radius: var(--radius-sm) !important;
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  justify-content: center !important;
  border: 1px solid var(--border-light) !important;
  flex-shrink: 0 !important;
  color: var(--text-secondary) !important;
  font-size: 14px !important;
}

.doc-avatar span {
  font-size: 8px !important;
  font-weight: var(--weight-medium) !important;
}

.doc-title {
  display: flex !important;
  align-items: center !important;
  gap: 6px !important;
  margin-bottom: 2px !important;
  flex-wrap: wrap !important;
}

.doc-title > span:first-child {
  font-size: var(--font-sm) !important;
  font-weight: var(--weight-medium) !important;
  color: var(--text-primary) !important;
}

.doc-desc p {
  font-size: var(--font-xs) !important;
  color: var(--text-tertiary) !important;
  margin-bottom: 4px !important;
  line-height: 1.35 !important;
}

.doc-meta {
  display: flex !important;
  align-items: center !important;
  justify-content: space-between !important;
  flex-wrap: wrap !important;
  gap: 4px !important;
}

.doc-tags {
  display: flex !important;
  align-items: center !important;
  gap: 4px !important;
  flex-wrap: wrap !important;
}

.doc-tags-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  flex-wrap: wrap;
}

.doc-tags-label {
  font-size: 11px;
  color: var(--text-secondary, #999);
  white-space: nowrap;
}

.doc-tags-empty {
  font-size: 11px;
  color: var(--text-disabled, #ccc);
  font-style: italic;
}

.doc-stats {
  display: flex !important;
  align-items: center !important;
  gap: 8px !important;
  font-size: 10px !important;
  color: var(--text-disabled) !important;
}

.doc-stats span {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* 标签样式优化，更紧凑 */
.doc-item :deep(.ant-tag) {
  font-size: 9px;
  padding: 0 4px;
  height: auto;
  line-height: 1.4;
  border-radius: 2px;
}

.doc-item :deep(.ant-badge-status-dot) {
  width: 5px;
  height: 5px;
}

.empty-state {
  text-align: center;
  padding: var(--space-2xl) 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.empty-state.no-permission {
  height: 60vh;
}

.empty-state.no-permission :deep(.anticon) {
  font-size: 40px;
  color: var(--text-disabled);
  margin-bottom: var(--space-md);
}

.empty-state.no-permission h3 {
  color: var(--text-tertiary);
  margin-bottom: var(--space-xs);
}

.empty-state.no-permission p {
  color: var(--text-tertiary);
}

.empty-icon {
  font-size: 24px;
  color: var(--text-disabled);
  margin-bottom: var(--space-sm);
}

.empty-state p {
  color: var(--text-tertiary);
  font-size: var(--font-sm);
}

.reader-container {
  min-height: 100%;
  width: 100%;
  position: relative;
  overflow: auto;
}

.reader-container-no-scroll {
  overflow: hidden;
  height: 100vh;
}

/* 文档阅读工具栏容器 - 现代化设计 */
.reader-toolbar-wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-container);
  border-bottom: 1px solid var(--border-light);
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: 100;
}

.reader-toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 1400px;
  width: 100%;
  overflow-x: auto;
  padding: 2px 0;
}

.reader-toolbar::-webkit-scrollbar {
  height: 3px;
}

.reader-toolbar::-webkit-scrollbar-track {
  background: transparent;
}

.reader-toolbar::-webkit-scrollbar-thumb {
  background: var(--border-default);
  border-radius: 2px;
}

.reader-toolbar::-webkit-scrollbar-thumb:hover {
  background: var(--text-tertiary);
}

.toolbar-divider {
  width: 1px;
  height: 20px;
  background: var(--border-light);
  margin: 0 6px;
}

.doc-info {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  background: var(--bg-container);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-light);
  margin-right: 4px;
}

.doc-info span {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
}

.toolbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* 页面控制组 */
.page-controls {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px;
  background: var(--bg-container);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-light);
}

.page-controls .form-input {
  width: 48px;
  text-align: center;
  height: 28px;
  font-size: var(--font-xs);
}

.page-controls span {
  font-size: var(--font-xs);
  color: var(--text-tertiary);
  font-weight: var(--weight-medium);
}

.zoom-controls {
  display: flex;
  align-items: center;
  gap: 4px;
}

.zoom-controls span {
  font-size: var(--font-xs);
  color: var(--text-disabled);
  min-width: 36px;
  text-align: center;
}

.toolbar-group {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  background: var(--bg-container);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-light);
}

.toolbar-group .form-input {
  width: 140px;
  height: 28px;
  font-size: var(--font-xs);
}

/* 缩放控制组 - 优化样式 */
.zoom-group {
  background: var(--bg-container);
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-light);
}

.zoom-level {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  min-width: 40px;
  text-align: center;
  user-select: none;
}

.page-input {
  width: 48px !important;
  text-align: center;
}

.color-picker-wrapper {
  position: relative;
}

.color-dot {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-light);
  vertical-align: middle;
}

.active-color {
  border-color: var(--color-primary);
}

.btn-speaking {
  background: var(--color-primary) !important;
  color: var(--bg-card) !important;
  border: none !important;
}

/* 按钮优化 - 现代化样式 */
.reader-toolbar .btn-small {
  height: 32px;
  padding: 0 14px;
  font-size: 13px;
  font-weight: var(--weight-medium);
  border-radius: var(--radius-md);
  transition: background 0.15s ease, border-color 0.15s ease;
  border: 1px solid var(--border-light);
  background: var(--bg-card);
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  gap: 6px;
}

.reader-toolbar .btn-small:hover:not(:disabled) {
  background: var(--bg-hover);
  border-color: var(--border-default);
}

.reader-toolbar .btn-small.btn-primary {
  background: var(--color-primary);
  color: var(--bg-card);
  border: none;
}

.reader-toolbar .btn-small.btn-primary:hover {
  background: var(--color-primary-hover);
}

/* 颜色选择器下拉 */
.color-picker-dropdown {
  position: absolute;
  top: calc(100% + var(--space-sm));
  left: 0;
  padding: var(--space-sm);
  background: var(--bg-card);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-light);
  display: flex;
  gap: var(--space-xs);
  z-index: 1001;
}

.color-option {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
  cursor: pointer;
  border: 2px solid transparent;
}

.color-option:hover {
  border-color: var(--text-tertiary);
}

.color-option.active {
  border-color: var(--color-primary);
}

/* 语音控制组 */
.speech-group {
  position: relative;
}

/* 语音控制下拉 */
.speech-dropdown {
  position: absolute;
  top: calc(100% + var(--space-sm));
  right: 0;
  padding: var(--space-md);
  background: var(--bg-card);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-light);
  z-index: 1001;
  min-width: 200px;
}

.speech-control-item {
  margin-bottom: var(--space-sm);
}

.speech-control-item:last-child {
  margin-bottom: 0;
}

.control-label {
  display: block;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
  margin-bottom: var(--space-xs);
}

.rate-control {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.rate-value {
  flex: 1;
  text-align: center;
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  background: var(--bg-container);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
}

.label-btn {
  cursor: pointer;
}

/* PDF容器 - 优化布局 */
.pdf-container {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  min-height: calc(100vh - 80px);
  flex: 1;
  overflow: auto;
  padding: var(--space-md);
}

.pdf-container::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

.pdf-container::-webkit-scrollbar-track {
  background: transparent;
}

.pdf-container::-webkit-scrollbar-thumb {
  background: var(--border-default);
  border-radius: 3px;
}

.pdf-container::-webkit-scrollbar-thumb:hover {
  background: var(--text-tertiary);
}

.pdf-placeholder {
  width: 100%;
  height: 100%;
  background: var(--bg-page);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

.pdf-placeholder .anticon {
  font-size: 48px;
  color: var(--text-disabled);
  margin-bottom: var(--space-md);
}

.pdf-placeholder h2 {
  color: var(--text-primary);
  margin-bottom: var(--space-sm);
}

.pdf-placeholder p {
  color: var(--text-tertiary);
  margin-bottom: var(--space-md);
}

.dropdown-enter-active,
.dropdown-leave-active {
  transition: all .2s ease;
}

.dropdown-enter-from,
.dropdown-leave-to {
  opacity: 0;
  transform: translateY(-6px);
}

/* ========== 上传弹窗样式 ========== */
.upload-modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
}

.upload-modal {
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  width: 90%;
  max-width: 560px;
  max-height: 85vh;
  overflow-y: auto;
}

.upload-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-sm) var(--space-lg);
  border-bottom: 1px solid var(--border-light);
}

.upload-modal-header h3 {
  margin: 0;
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}

.upload-modal-close {
  width: 24px;
  height: 24px;
  border: none;
  background: var(--bg-container);
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: var(--radius-sm);
  font-size: var(--font-md);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
}

.upload-modal-close:hover {
  background: var(--bg-hover);
  color: var(--text-secondary);
}

.upload-modal-body {
  padding: var(--space-lg);
}

.upload-modal-footer {
  padding: var(--space-sm) var(--space-lg) var(--space-lg);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  border-top: 1px solid var(--border-light);
}

.file-drop-zone {
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
  text-align: center;
  cursor: pointer;
  transition: all var(--transition-normal);
  background: var(--bg-container);
}

.file-drop-zone:hover,
.file-drop-zone.drag-over {
  border-color: var(--color-primary);
  background: var(--bg-page);
}

.drop-icon {
  font-size: 18px;
  color: var(--text-tertiary);
}

.drop-text {
  margin: var(--space-xs) 0 0;
  color: var(--text-secondary);
  font-size: var(--font-sm);
}

.selected-file-info {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
}

.selected-file-icon {
  font-size: 16px;
  color: var(--text-tertiary);
}

.selected-file-detail {
  flex: 1;
  text-align: left;
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
}

.selected-file-info .file-name {
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  font-size: var(--font-sm);
}

.selected-file-info .file-size {
  font-size: var(--font-xs);
  color: var(--text-tertiary);
}

.remove-file-btn {
  width: 18px;
  height: 18px;
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-xs);
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
}

.remove-file-btn:hover {
  background: var(--bg-error);
  color: var(--color-error);
}

.form-group {
  margin-bottom: var(--space-md);
}

.form-label {
  display: block;
  margin-bottom: var(--space-xs);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  font-size: var(--font-md);
}

.form-label .required {
  color: var(--color-error);
}

.form-label.inactive {
  color: var(--text-tertiary);
}

.dept-hint-box {
  color: var(--text-tertiary);
  font-size: var(--font-sm);
  margin: 0;
  padding: var(--space-sm);
  background: var(--bg-container);
  border-radius: var(--radius-md);
}

.form-hint {
  margin-top: var(--space-xs);
  font-size: var(--font-xs);
  color: var(--text-tertiary);
}

.form-textarea {
  width: 100%;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  font-size: var(--font-md);
  resize: vertical;
  transition: all var(--transition-normal);
  box-sizing: border-box;
}

.form-textarea:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-alpha-15);
}

.char-count {
  text-align: right;
  font-size: var(--font-xs);
  color: var(--text-tertiary);
  margin-top: 2px;
}

.radio-group {
  display: flex;
  gap: var(--space-sm);
}

.radio-item {
  flex: 1;
  min-width: 0;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--transition-normal);
}

.radio-item:hover {
  border-color: var(--color-primary-hover);
}

.radio-item.active {
  border-color: var(--color-primary);
  background: var(--bg-container);
}

.radio-item input[type="radio"] {
  display: none;
}

.radio-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.radio-label {
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  font-size: var(--font-sm);
  line-height: 1.3;
}

.radio-item.active .radio-label {
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
}

.radio-desc {
  font-size: var(--font-xs);
  color: var(--text-tertiary);
  line-height: 1.3;
}

.approve-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: var(--space-sm) 0;
  margin-bottom: var(--space-xs);
}

.approve-title {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}

.approve-stats {
  font-size: var(--font-sm);
  color: var(--text-tertiary);
}

.approve-stats strong {
  color: var(--text-primary);
  font-weight: var(--weight-semibold);
}

.approve-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.approve-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-lg) var(--space-xl);
  background: var(--bg-card);
  border-radius: var(--radius-xl);
  border: 1px solid var(--border-light);
  transition: border-color var(--transition-normal), box-shadow var(--transition-normal);
}

.approve-item:hover {
  border-color: var(--border-medium);
  box-shadow: var(--shadow-sm);
}

.approve-item-left {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  flex: 1;
  min-width: 0;
}

.approve-file-icon {
  font-size: var(--font-2xl);
  color: var(--text-tertiary);
  flex-shrink: 0;
  margin-top: 0;
  background: var(--bg-container);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  transition: color var(--transition-fast), background var(--transition-fast);
}

.approve-item:hover .approve-file-icon {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

.approve-item-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.approve-file-name {
  font-size: var(--font-xl);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.approve-file-meta {
  font-size: var(--font-sm);
  color: var(--text-disabled);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.approve-file-desc {
  font-size: var(--font-sm);
  color: var(--text-secondary);
  line-height: 1.5;
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.approve-item-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-shrink: 0;
  margin-left: var(--space-md);
}

.action-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 4px var(--space-lg);
  height: var(--input-height);
  border-radius: var(--radius-md);
  font-size: var(--font-md);
  font-weight: var(--weight-medium);
  border: none;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), filter var(--transition-fast);
  box-sizing: border-box;
}

.action-btn .action-icon {
  font-size: var(--font-xl);
}

.approve-btn {
  background: var(--color-success, #2B8A3E);
  color: var(--bg-card);
}

.approve-btn:hover {
  filter: brightness(1.12);
}

.reject-btn {
  background: var(--color-error, #C92A2A);
  color: var(--bg-card);
}

.reject-btn:hover {
  filter: brightness(1.12);
}

@media (max-width: 767px) {
  .action-btn {
    padding: 4px var(--space-sm);
    height: 26px;
  }

  .action-text {
    display: none;
  }
}

.approve-status {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  line-height: 1.6;
}

.approve-status.pending {
  color: var(--color-warning, #E67700);
  background: var(--bg-warning, #FFF9DB);
}

.approve-status.approved {
  color: var(--color-success, #2B8A3E);
  background: var(--bg-success, #EBFBEE);
}

.approve-status.rejected {
  color: var(--color-error, #C92A2A);
  background: var(--bg-error, #FFF5F5);
}

.btn-danger-outline {
  background: transparent;
  border: 1px solid var(--color-error);
  color: var(--color-error);
}

.btn-danger-outline:hover {
  background: var(--color-error-alpha-10);
  border-color: var(--color-error);
}

.btn-success-outline {
  background: transparent;
  border: 1px solid #52c41a;
  color: #52c41a;
}

.btn-success-outline:hover:not(:disabled) {
  background: #f6ffed;
  border-color: #52c41a;
}

.btn-warning-outline {
  background: transparent;
  border: 1px solid #faad14;
  color: #faad14;
}

.btn-warning-outline:hover:not(:disabled) {
  background: #fffbe6;
  border-color: #faad14;
}

/* ========== 版本详情面板 ========== */
.version-detail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}

.version-detail-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.version-detail-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
}

.version-detail-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.version-meta-bar {
  display: flex;
  gap: 16px;
  padding: 12px 16px;
  background: var(--bg-container);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  margin-bottom: 16px;
}

.meta-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.meta-label {
  font-size: 11px;
  color: var(--text-tertiary);
  font-weight: 500;
}

.meta-value {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}

/* ========== 版本时间线 ========== */
.version-timeline {
  position: relative;
  padding-left: 24px;
}

.version-timeline-item {
  position: relative;
  padding-bottom: 20px;
  padding-left: 20px;
}

.version-timeline-item:last-child {
  padding-bottom: 0;
}

.timeline-dot {
  position: absolute;
  left: -24px;
  top: 8px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--border-medium);
  border: 2px solid var(--bg-card);
  box-shadow: 0 0 0 2px var(--border-light);
  z-index: 1;
}

.version-timeline-item.is-latest .timeline-dot {
  background: var(--text-primary);
  box-shadow: 0 0 0 2px var(--border-default);
}

.timeline-connector {
  position: absolute;
  left: -19px;
  top: 20px;
  bottom: 0;
  width: 2px;
  background: var(--border-light);
}

.version-timeline-card {
  background: var(--bg-card);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  transition: all 0.15s ease;
}

.version-timeline-card:hover {
  border-color: var(--border-default);
  box-shadow: var(--shadow-sm);
}

.version-timeline-item.is-latest .version-timeline-card {
  border-color: var(--text-primary);
}

.version-timeline-item.compare-pending .version-timeline-card {
  border-color: var(--color-primary, #228be6);
  box-shadow: 0 0 0 2px rgba(34, 139, 230, 0.2);
  background: rgba(34, 139, 230, 0.03);
}

.version-timeline-item.compare-pending .timeline-dot {
  background: var(--color-primary, #228be6);
  box-shadow: 0 0 0 2px rgba(34, 139, 230, 0.3);
}

.timeline-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
  flex-wrap: wrap;
  gap: 8px;
}

.timeline-card-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.version-tag {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  background: var(--bg-container);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 600;
  color: var(--text-primary);
}

.latest-badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  background: var(--text-primary);
  color: var(--bg-card);
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-weight: 600;
}

.timeline-card-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.timeline-card-body {
  padding-top: 4px;
}

.timeline-card-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  color: var(--text-tertiary);
}

.timeline-card-toggle {
  margin-top: var(--space-md, 8px);
  padding-top: var(--space-md, 8px);
}

.timeline-card-toggle .btn-text {
  font-size: var(--font-sm, 11px);
  color: var(--text-tertiary, #6C757D);
  gap: var(--space-xs, 2px);
  font-weight: var(--weight-medium, 500);
  letter-spacing: 0.2px;
  transition: color var(--transition-fast, 0.15s ease);
}

.timeline-card-toggle .btn-text:hover {
  color: var(--color-primary, #212529);
}

.version-sub-panel {
  margin-top: var(--space-lg, 12px);
  border-radius: var(--radius-lg, 6px);
  overflow: hidden;
  background: transparent;
  animation: panelSlideIn var(--transition-normal, 0.2s) ease-out;
}

@keyframes panelSlideIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.sub-panel-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--bg-container, #F8F9FA);
}

.sub-panel-title-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-xs, 11px);
  font-weight: var(--weight-semibold, 600);
  color: var(--text-primary, #212529);
  letter-spacing: 0.3px;
}

.sub-refresh-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 12px;
  font-size: var(--font-xs, 10px);
  color: var(--text-secondary, #495057);
  background: transparent;
  border: 1px solid var(--border-light, #E9ECEF);
  border-radius: var(--radius-sm, 4px);
  cursor: pointer;
  transition: all var(--transition-fast, 0.15s ease);
}

.sub-refresh-btn:hover {
  border-color: var(--color-primary, #212529);
  color: var(--color-primary, #212529);
  background: rgba(33, 37, 41, 0.03);
}

.sub-panel-content {
  padding: var(--space-xl, 16px) var(--card-padding, 12px);
  max-height: 280px;
  overflow-y: auto;
  background: var(--bg-container, #F8F9FA);
  scrollbar-width: thin;
  scrollbar-color: var(--border-light, #E9ECEF) transparent;
}

.sub-panel-content::-webkit-scrollbar {
  width: 4px;
}

.sub-panel-content::-webkit-scrollbar-track {
  background: transparent;
}

.sub-panel-content::-webkit-scrollbar-thumb {
  background-color: var(--border-dark, #ADB5BD);
  border-radius: calc(var(--radius-sm, 3px) * 10);
}

.sub-panel-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md, 8px);
  padding: var(--space-3xl, 24px) 0;
  color: var(--text-tertiary, #6C757D);
  font-size: var(--font-xs, 10px);
  animation: fadeIn 0.25s ease;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.sub-panel-empty {
  text-align: center;
  padding: var(--space-2xl, 20px) var(--space-lg, 12px);
  color: var(--text-disabled, #ADB5BD);
  font-size: var(--font-xs, 10px);
}

.sub-panel-empty p {
  margin-bottom: 10px;
}

.sub-retry-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 14px;
  font-size: var(--font-xs, 10px);
  color: var(--text-secondary, #495057);
  background: transparent;
  border: 1px solid var(--border-light, #E9ECEF);
  border-radius: var(--radius-sm, 4px);
  cursor: pointer;
  transition: all var(--transition-fast, 0.15s ease);
}

.sub-retry-btn:hover {
  border-color: var(--color-primary, #212529);
  color: var(--color-primary, #212529);
  background: rgba(33, 37, 41, 0.04);
}

.sub-paper-item {
  position: relative;
  padding: var(--space-lg, 12px) var(--card-padding, 12px);
  margin-bottom: var(--space-sm, 6px);
  background: var(--bg-card, #FFFFFF);
  border-radius: var(--radius-md, 4px);
  transition: all var(--transition-normal, 0.2s) ease;
  cursor: default;
}

.sub-paper-item:last-child {
  margin-bottom: 0;
}

.sub-paper-item.is-clickable {
  cursor: pointer;
}

.sub-paper-item.is-clickable:hover {
  transform: translateX(2px);
  box-shadow: var(--shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.08));
  border-color: var(--color-primary, #212529);
}

.sub-paper-item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 2px;
  height: 0%;
  background: var(--color-primary, #212529);
  border-radius: 1px;
  transition: height var(--transition-normal, 0.2s) ease;
}

.sub-paper-item.is-clickable:hover::before {
  height: 60%;
}

.sub-paper-header {
  display: flex;
  align-items: center;
  gap: var(--space-md, 8px);
  margin-bottom: var(--space-sm, 6px);
}

.sub-paper-header :deep(.ant-tag) {
  font-size: var(--font-xs, 10px);
  line-height: 18px;
  padding: 0 6px;
  border-radius: var(--radius-sm, 3px);
  border: none;
  font-weight: var(--weight-medium, 500);
  flex-shrink: 0;
}

.sub-paper-action-hint {
  margin-left: auto;
  font-size: 9px;
  color: var(--color-primary, #212529);
  background: rgba(33, 37, 41, 0.06);
  padding: 2px 8px;
  border-radius: calc(var(--radius-sm, 3px) * 10);
  white-space: nowrap;
  animation: hintPulse 2s ease-in-out infinite;
}

@keyframes hintPulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

.sub-paper-title {
  font-size: var(--font-md, 12px);
  font-weight: var(--weight-semibold, 600);
  color: var(--text-primary, #212529);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  letter-spacing: 0.15px;
}

.sub-paper-meta {
  display: flex;
  gap: var(--space-xl, 16px);
  font-size: var(--font-xs, 10px);
  color: var(--text-disabled, #ADB5BD);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.2px;
}

/* ========== 响应式适配 ========== */

@media (max-width: 767px) {
  .version-sub-panel {
    margin-top: var(--space-md, 8px);
    border-radius: var(--radius-md, 4px);
  }

  .sub-panel-header-row {
    padding: 8px 10px;
  }

  .sub-panel-title-text {
    font-size: 10px;
  }

  .sub-refresh-btn,
  .sub-retry-btn {
    font-size: 9px;
    padding: 3px 10px;
  }

  .sub-panel-content {
    max-height: 220px;
    padding: var(--space-md, 8px) var(--space-md, 8px);
  }

  .sub-paper-item {
    padding: var(--space-md, 8px) var(--space-sm, 6px);
    margin-bottom: var(--space-xs, 2px);
  }

  .sub-paper-header :deep(.ant-tag) {
    font-size: 9px;
    padding: 0 4px;
  }

  .sub-paper-title {
    font-size: var(--font-xs, 10px);
  }

  .sub-paper-action-hint {
    font-size: 8px;
    padding: 1px 6px;
  }

  .sub-paper-meta {
    gap: var(--space-md, 8px);
    font-size: 9px;
  }

  .sub-panel-loading {
    padding: var(--space-xl, 16px) 0;
  }

  .sub-panel-empty {
    padding: var(--space-lg, 12px) var(--space-md, 8px);
    font-size: 9px;
  }
}

@media (min-width: 768px) and (max-width: 1023px) {
  .sub-panel-tabs {
    padding: var(--space-xs, 2px);
  }

  .sub-tab-btn {
    font-size: 10px;
    padding: var(--tab-btn-padding-v, 4px) 12px;
  }

  .sub-panel-content {
    max-height: 260px;
    padding: var(--space-lg, 12px) var(--card-padding, 12px);
  }

  .sub-question-item,
  .sub-paper-item {
    padding: 10px var(--card-padding, 12px);
  }

  .sub-question-content {
    font-size: var(--font-md, 12px);
  }

  .sub-paper-meta {
    gap: var(--space-xl, 16px);
  }
}

@media (min-width: 1440px) {
  .version-sub-panel {
    margin-top: var(--space-xl, 16px);
  }

  .sub-panel-tabs {
    padding: var(--space-sm, 6px);
  }

  .sub-tab-btn {
    font-size: var(--font-md, 12px);
    padding: 6px 16px;
    gap: var(--space-md, 8px);
  }

  .sub-tab-count {
    min-width: 18px;
    height: 18px;
    font-size: var(--font-sm, 11px);
  }

  .sub-panel-content {
    max-height: 340px;
    padding: var(--space-2xl, 20px) var(--space-xl, 16px);
  }

  .sub-question-item,
  .sub-paper-item {
    padding: var(--space-xl, 16px) var(--space-lg, 12px);
    margin-bottom: var(--space-md, 8px);
    border-radius: var(--radius-lg, 6px);
  }

  .sub-question-header {
    gap: var(--space-md, 8px);
    margin-bottom: var(--space-md, 8px);
  }

  .sub-question-header :deep(.ant-tag),
  .sub-paper-header :deep(.ant-tag) {
    font-size: var(--font-sm, 11px);
    line-height: 22px;
    padding: 0 8px;
  }

  .sub-question-score {
    font-size: var(--font-sm, 11px);
  }

  .sub-question-content {
    font-size: var(--font-lg, 13px);
    line-height: 1.7;
  }

  .sub-paper-title {
    font-size: var(--font-lg, 13px);
  }

  .sub-paper-meta {
    gap: var(--space-2xl, 20px);
    font-size: var(--font-md, 12px);
  }
}

/* ========== 版本对比弹窗 ========== */
.compare-modal {
  width: 90%;
  max-width: 960px;
  max-height: 90vh;
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.compare-modal .modal-header {
  flex-shrink: 0;
}

.compare-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 20px;
  background: var(--bg-container);
  border-bottom: 1px solid var(--border-light);
  flex-wrap: wrap;
  gap: 8px;
  flex-shrink: 0;
}

.compare-versions {
  display: flex;
  align-items: center;
  gap: 16px;
}

.version-selector {
  display: flex;
  align-items: center;
  gap: 8px;
}

.selector-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
}

.vs-text {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-disabled);
}

.compare-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.compare-mode-switch {
  display: flex;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.mode-btn {
  padding: 4px 12px;
  border: none;
  background: var(--bg-card);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.15s ease;
}

.mode-btn.active {
  background: var(--text-primary);
  color: var(--bg-card);
}

.mode-btn:hover:not(.active) {
  background: var(--bg-hover);
}

.compare-body {
  flex: 1;
  overflow: auto;
  padding: 16px 20px;
}

.compare-container {
  display: flex;
  gap: 16px;
  min-height: 400px;
}

.compare-card {
  flex: 1;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  overflow: hidden;
  min-width: 0;
}

.compare-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  background: var(--text-primary);
  color: var(--bg-card);
  font-size: 13px;
  font-weight: 600;
}

.header-version {
  font-size: 11px;
  font-weight: 400;
  opacity: 0.85;
  background: rgba(255,255,255,0.2);
  padding: 1px 8px;
  border-radius: var(--radius-sm);
}

.compare-content {
  padding: 12px;
  font-size: 13px;
  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
  overflow-x: auto;
  max-height: 500px;
  overflow-y: auto;
}

.compare-container-inline {
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  overflow: auto;
  max-height: 600px;
}

.compare-content-inline {
  padding: 12px;
  font-size: 13px;
  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
}

/* ========== Diff样式 ========== */
.diff-line {
  display: flex;
  align-items: flex-start;
  min-height: 22px;
  line-height: 1.6;
  padding: 1px 0;
}

.diff-line:hover {
  filter: brightness(0.97);
}

.line-num {
  display: inline-block;
  width: 48px;
  text-align: right;
  padding-right: 12px;
  user-select: none;
  color: var(--text-disabled);
  font-size: 12px;
  border-right: 1px solid var(--border-light);
  margin-right: 8px;
  flex-shrink: 0;
}

.line-content {
  white-space: pre-wrap;
  word-break: break-all;
  flex: 1;
  padding: 0 4px;
}

.diff-marker {
  display: inline-block;
  width: 18px;
  font-weight: bold;
  text-align: center;
  margin-right: 4px;
  flex-shrink: 0;
}

.diff-same {
  color: var(--text-secondary);
}

.diff-same .line-num {
  color: var(--text-disabled);
  background: var(--bg-container);
}

.diff-add {
  background-color: #EBFBEE;
  color: #2B8A3E;
}

.diff-add .line-num {
  color: #2B8A3E;
  background: #D3F9D8;
}

.diff-add .diff-marker {
  color: #2B8A3E;
}

.diff-remove {
  background-color: #FFF5F5;
  color: #C92A2A;
}

.diff-remove .line-num {
  color: #C92A2A;
  background: #FFE3E3;
}

.diff-remove .diff-marker {
  color: #C92A2A;
}

.diff-modify {
  background-color: #FFF9DB;
  color: #E67700;
}

.diff-modify .diff-marker {
  color: #E67700;
}

.diff-del {
  text-decoration: line-through;
  background: #FFE3E3;
  color: #C92A2A;
  padding: 0 2px;
  border-radius: 2px;
}

.diff-ins {
  background: #D3F9D8;
  color: #2B8A3E;
  padding: 0 2px;
  border-radius: 2px;
}

.diff-empty-left,
.diff-empty-right {
  background-color: var(--bg-container);
}

.diff-empty-left .line-num,
.diff-empty-right .line-num {
  color: var(--text-disabled);
}

/* ========== 对比弹窗提示样式 ========== */
.compare-unsupported,
.compare-empty,
.compare-error,
.compare-prompt,
.compare-loading-wrap {
  text-align: center;
  padding: 60px 40px;
  color: var(--text-tertiary);
}

.compare-msg-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 8px;
}

.compare-msg-desc {
  font-size: 13px;
  color: var(--text-tertiary);
  margin: 0 0 16px;
  line-height: 1.6;
}

.compare-msg-tip {
  font-size: 12px;
  color: var(--text-disabled);
  margin: 0;
}

.prompt-hint {
  font-size: 12px;
  color: var(--text-disabled);
  margin: 8px 0 0;
}

/* ========== 版本回退弹窗 ========== */
.rollback-info {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  padding: 12px;
  background: var(--bg-container);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-light);
}

.rollback-label {
  font-size: 13px;
  color: var(--text-secondary);
}

.rollback-warning {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 12px;
  background: var(--bg-warning);
  border: 1px solid #FFE066;
  border-radius: var(--radius-md);
}

/* ========== 上传新版本弹窗 ========== */
.upload-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px;
  border: 2px dashed var(--border-default);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.15s ease;
  background: var(--bg-container);
}

.upload-zone:hover {
  border-color: var(--text-primary);
  background: var(--bg-hover);
}

/* ========== 向量化弹窗样式 ========== */

.vectorize-modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal);
  padding: var(--space-lg);
  backdrop-filter: blur(2px);
}

.vectorize-modal {
  background: var(--bg-card);
  border-radius: var(--radius-xl);
  width: 100%;
  max-width: 520px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-xl);
  border: 1px solid var(--border-light);
  overflow: hidden;
}

/* 弹窗头部 */
.vectorize-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-xl);
  border-bottom: 1px solid var(--border-light);
  background: var(--bg-header);
}

.modal-title {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.modal-icon {
  width: 36px;
  height: 36px;
  background: linear-gradient(135deg, var(--color-info) 0%, #1971C2 100%);
  border-radius: var(--radius-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-inverse);
  flex-shrink: 0;
}

.modal-icon svg {
  font-size: 18px;
}

.modal-title h3 {
  margin: 0;
  font-size: var(--font-2xl);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}

.modal-subtitle {
  margin: 2px 0 0;
  font-size: var(--font-sm);
  color: var(--text-tertiary);
}

.modal-close-btn {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  color: var(--text-tertiary);
  transition: all var(--transition-fast);
}

.modal-close-btn:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

/* 弹窗内容区 */
.vectorize-modal-body {
  flex: 1;
  padding: var(--space-xl);
  overflow-y: auto;
}

/* 提示信息框 */
.vectorize-hint-box {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  padding: var(--space-lg);
  background: var(--bg-info);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-xl);
  border-left: 3px solid var(--color-info);
}

.hint-icon {
  font-size: 16px;
  color: var(--color-info);
  flex-shrink: 0;
  margin-top: 2px;
}

.vectorize-hint-box p {
  margin: 0;
  font-size: var(--font-md);
  color: var(--text-secondary);
  line-height: 1.6;
}

.vectorize-hint-box strong {
  color: var(--text-primary);
  font-weight: var(--weight-semibold);
}

/* 文件信息卡片 */
.vectorize-file-card {
  background: var(--bg-container);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  margin-bottom: var(--space-xl);
  border: 1px solid var(--border-light);
}

.file-card-header {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}

.file-icon {
  width: 40px;
  height: 40px;
  background: var(--bg-active);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-info);
  font-size: 20px;
}

.file-info {
  flex: 1;
  min-width: 0;
}

.file-name {
  display: block;
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-version {
  display: block;
  font-size: var(--font-xs);
  color: var(--text-tertiary);
  margin-top: 2px;
}

.file-card-meta {
  display: flex;
  gap: var(--space-lg);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-light);
}

.meta-item {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-xs);
  color: var(--text-tertiary);
}

.meta-item svg {
  font-size: 12px;
}

/* 表单组 */
.vectorize-form-group {
  display: flex;
  flex-direction: column;
}

.form-label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--font-md);
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
  margin-bottom: var(--space-md);
}

.required-mark {
  color: var(--color-error);
  font-size: var(--font-lg);
}

/* 加载状态 */
.vectorize-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: 40px 20px;
  color: var(--text-tertiary);
  font-size: var(--font-md);
}

/* 空状态 */
.vectorize-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
  text-align: center;
}

.empty-icon {
  width: 48px;
  height: 48px;
  color: var(--text-disabled);
  margin-bottom: var(--space-md);
}

.vectorize-empty p {
  margin: 0 0 4px;
  font-size: var(--font-md);
  color: var(--text-secondary);
}

.empty-hint {
  font-size: var(--font-xs) !important;
  color: var(--text-tertiary) !important;
}

/* 知识库列表 */
.vectorize-collection-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-height: 240px;
  overflow-y: auto;
  padding: var(--space-xs);
}

/* 智能向量化 - 自动识别知识库 */
.auto-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  font-size: var(--font-xs);
  font-weight: 600;
  color: #fff;
  background: linear-gradient(135deg, #667eea, #764ba2);
  border-radius: 10px;
  margin-left: var(--space-sm);
}

.vectorize-auto-detect-box {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  padding: var(--space-lg);
  background: linear-gradient(135deg, #f0f4ff 0%, #e8f0fe 100%);
  border: 1px solid #c5d4f7;
  border-radius: var(--radius-lg);
}

.auto-icon {
  font-size: 24px;
  color: #667eea;
  flex-shrink: 0;
  margin-top: 2px;
}

.auto-info {
  flex: 1;
}

.auto-title {
  margin: 0 0 6px;
  font-size: var(--font-md);
  font-weight: 600;
  color: #334155;
}

.auto-desc {
  margin: 0;
  font-size: var(--font-xs);
  color: #64748b;
  line-height: 1.6;
}

/* 选项样式 */
.collection-option {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  background: var(--bg-card);
  border: 2px solid transparent;
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition: all var(--transition-fast);
  user-select: none;
}

.collection-option:hover {
  background: var(--bg-hover);
  border-color: var(--border-light);
}

.collection-option.selected {
  background: var(--bg-info);
  border-color: var(--color-info);
}

.option-radio {
  position: relative;
  flex-shrink: 0;
}

.option-radio input[type="radio"] {
  opacity: 0;
  position: absolute;
  width: 100%;
  height: 100%;
  cursor: pointer;
}

.radio-indicator {
  display: block;
  width: 18px;
  height: 18px;
  border: 2px solid var(--border-medium);
  border-radius: 50%;
  transition: all var(--transition-fast);
}

.collection-option:hover .radio-indicator {
  border-color: var(--color-info);
}

.collection-option.selected .radio-indicator {
  border-color: var(--color-info);
  background: var(--color-info);
}

.collection-option.selected .radio-indicator::after {
  content: '';
  display: block;
  width: 6px;
  height: 6px;
  background: var(--text-inverse);
  border-radius: 50%;
  margin: 4px auto;
}

.option-content {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex: 1;
  min-width: 0;
}

.option-icon-wrapper {
  width: 32px;
  height: 32px;
  background: var(--bg-container);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.collection-option.selected .option-icon-wrapper {
  background: rgba(28, 126, 214, 0.15);
}

.option-icon {
  font-size: 14px;
  color: var(--text-tertiary);
}

.collection-option.selected .option-icon {
  color: var(--color-info);
}

.option-info {
  flex: 1;
  min-width: 0;
}

.option-name {
  display: block;
  font-size: var(--font-md);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.option-desc {
  display: block;
  font-size: var(--font-xs);
  color: var(--text-tertiary);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.option-check {
  color: var(--color-info);
  font-size: 16px;
  flex-shrink: 0;
}

/* 弹窗底部 */
.vectorize-modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-md);
  padding: var(--space-lg) var(--space-xl);
  border-top: 1px solid var(--border-light);
  background: var(--bg-container);
}

.vectorize-modal-footer .btn {
  min-width: 80px;
}

/* 弹窗动画 */
.modal-fade-enter-active,
.modal-fade-leave-active {
  transition: opacity var(--transition-normal);
}

.modal-fade-enter-from,
.modal-fade-leave-to {
  opacity: 0;
}

.btn-primary-outline {
  color: #228BE6 !important;
  border: 1px solid #BEE3F8 !important;
  background: #EBF8FF !important;
}

.btn-primary-outline:hover:not(:disabled) {
  background: #D0EFFF !important;
  border-color: #74C0FC !important;
  color: #1864AB !important;
}

.btn-exam-outline {
  color: #7048E8 !important;
  border: 1px solid #D6BCFA !important;
  background: #F5F0FF !important;
}

.btn-exam-outline:hover:not(:disabled) {
  background: #EBE4FF !important;
  border-color: #B197FC !important;
  color: #5B3CC4 !important;
}

/* ========== 向量化状态徽章 ========== */

.vectorize-action-group {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.vectorize-badge {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 8px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.6;
  white-space: nowrap;
}

.badge-pending,
.badge-vectorizing {
  background: #FFF9DB;
  color: #E67700;
  border: 1px solid #FFE066;
}

.badge-indexed {
  background: #EBFBEE;
  color: #2B8A3E;
  border: 1px solid #D3F9D8;
}

.badge-failed {
  background: #FFF5F5;
  color: #C92A2A;
  border: 1px solid #FFC9C9;
}

.badge-default {
  background: #F1F3F5;
  color: #868E96;
  border: 1px solid #DEE2E6;
}

@keyframes badge-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

.badge-pulse {
  animation: badge-pulse 1.5s ease-in-out infinite;
}
</style>
