吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1175|回复: 23
上一主题 下一主题
收起左侧

[其他原创] 智能题库转换工具(增强版)---无需联网

  [复制链接]
跳转到指定楼层
楼主
努力的小白 发表于 2025-12-16 13:22 回帖奖励
本帖最后由 努力的小白 于 2025-12-16 13:31 编辑

本篇是基于前面我发的帖子 Excel题库转换工具(单机版) 和  Word题库转换工具(增强版)--单机版  
评论区 大家想要更多的格式支持  输入格式Word(.docx)、Excel(.xlsx/.xls)、PDF、文本文件(.txt)  同时加入了自动识别文件类型,也可手动指定解析模式
看到我前面的帖子得到了大家的认可  很开心 感谢大伙的支持{:1_893:}
我分享的是个人原创工具(开发中使用了AI辅助),工具内无任何推广信息。我的初衷是纯分享,若无意违反版规,恳请指出具体问题以便我改正。感谢!
需要兄弟姐妹们,多鼓励多支持   

1. 文件上传与解析
  • 支持格式:.docx、.xlsx、.xls、.pdf、.txt。
  • 上传方式:点击或拖拽上传。
  • 文件类型检测:自动检测文件类型,也可手动选择解析模式(Word、Excel、PDF、文本)。
  • 解析能力
    • Word:使用Mammoth库提取文本,支持表格和段落解析。
    • Excel:使用SheetJS(XLSX)解析多个工作表,可选择特定工作表。
    • PDF:使用PDF.js提取文本,可设置页面范围。
    • 文本文件:按行解析,支持自定义模板。

2. 模板系统
  • 内置模板:标准模板、简易模板、考试模板。
  • 自定义模板:用户可自定义正则表达式和字段映射,支持测试和保存。
  • 模板匹配:根据模板规则自动提取题目、选项、答案等信息。
3. 题目解析与智能清洗
  • 题型识别:自动识别判断题、单选题、多选题。
  • 答案解析:支持多种答案格式(字母、中文选项、文本匹配),并计算置信度(高、中、低)。
  • 智能清洗:自动清理选项文本,去除冗余符号,标准化格式。
  • 批量修正:提供批量编辑和自动修复建议,可批量标记为已确认。
4. 预览与编辑
  • 实时预览:分页显示题目,支持搜索和筛选(按题型、置信度)。
  • 单题编辑:可修改题型、题目、选项、答案、置信度等。
  • 批量操作:全选、标记确认、生成修复建议、应用修复。
5. 导出功能
  • 导出格式:JSON、JS(含辅助函数)、XLSX、CSV。
  • 筛选导出:可导出当前筛选后的题目。
  • PDF生成:将题库生成为PDF文档(功能待实现)。
6. 云存储与API
  • 云存储:将题库保存到云端(基于本地存储模拟),支持加载和同步。
  • API接口:设置API密钥,支持发布到API和从API导入。
7. 协作模式
  • 协作链接:生成加密链接,邀请他人共同编辑题库。
  • 权限控制:设置是否允许编辑和下载。

下载地址 :https://wwber.lanzouu.com/ihiEw3drpfih
主要代码如下

[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>多功能题库转换工具(增强版)</title>
  <!-- Word 解析 -->
  <script src="https://unpkg.com/mammoth@1.5.1/mammoth.browser.min.js"></script>
  <!-- Excel 解析与导出 -->
  <script src="https://unpkg.com/xlsx/dist/xlsx.full.min.js"></script>
  <!-- PDF.js 用于PDF解析 -->
  <script src="https://unpkg.com/pdfjs-dist@3.11.174/build/pdf.min.js"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:"Microsoft YaHei",Segoe UI,Arial,sans-serif;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);min-height:100vh;padding:18px}
    .container{max-width:1400px;margin:0 auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 20px 40px rgba(0,0,0,.12)}
    .header{background:linear-gradient(135deg,#2c3e50,#3498db);color:#fff;padding:18px;text-align:center}
    .header h1{font-size:22px;margin-bottom:6px}
    .header p{opacity:.95;font-size:14px}
    .content{display:grid;grid-template-columns:450px 1fr;gap:20px;padding:20px}
    .card{background:#f8f9fa;padding:16px;border-radius:10px}
    .upload-label{display:block;user-select:none;border:3px dashed #3498db;border-radius:10px;padding:20px;text-align:center;cursor:pointer;background:#ecf0f1}
    .upload-label:hover{background:#d6eaf8;border-color:#2980b9}
    .upload-label i{font-size:36px;color:#3498db;margin-bottom:8px}
    .file-type-tabs{display:flex;gap:6px;margin-top:10px}
    .tab-btn{padding:8px 14px;border-radius:6px;background:#ecf0f1;border:none;cursor:pointer;flex:1;font-size:13px}
    .tab-btn.active{background:#3498db;color:white}
    .btn{display:inline-block;background:linear-gradient(135deg,#3498db,#2980b9);color:#fff;border:none;padding:8px 14px;border-radius:18px;font-size:13px;cursor:pointer;margin:6px}
    .btn:disabled{background:#bdc3c7;cursor:not-allowed}
    .btn-download{background:linear-gradient(135deg,#27ae60,#229954)}
    .btn-json{background:linear-gradient(135deg,#f39c12,#e67e22)}
    .btn-ghost{background:#fff;border:1px solid #ccc;color:#333}
    .btn-pdf{background:linear-gradient(135deg,#e74c3c,#c0392b)}
    .stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:12px}
    .stat-card{background:#fff;padding:10px;border-radius:8px;text-align:center;box-shadow:0 6px 18px rgba(0,0,0,.04)}
    .stat-number{font-size:18px;color:#3498db;font-weight:700}
    .preview{max-height:640px;overflow:auto;background:#fff;padding:12px;border-radius:8px;margin-top:10px}
    .q-item{padding:10px;border-radius:8px;margin-bottom:10px;border-left:4px solid #3498db;background:#fff;box-shadow:0 2px 6px rgba(0,0,0,.04)}
    .q-meta{display:flex;align-items:center;gap:8px}
    .q-type{color:#fff;padding:3px 8px;border-radius:12px;font-size:12px}
    .type-judgment{background:#e74c3c}
    .type-single{background:#3498db}
    .type-multi{background:#9b59b6}
    .q-actions{margin-left:auto;display:flex;gap:8px}
    .btn-edit{background:#fff;color:#2980b9;border:1px solid #2980b9;padding:6px;border-radius:6px;cursor:pointer}
    .btn-mark{background:#fff;color:#27ae60;border:1px solid #27ae60;padding:6px;border-radius:6px;cursor:pointer}
    .uncertain{border-left-color:#e74c3c;background:#fff6f6}
    .uncertain-badge{display:inline-block;background:#e74c3c;color:#fff;padding:2px 6px;border-radius:10px;font-size:12px;margin-left:8px}
    .hint{color:#f39c12;font-size:13px}
    .log{margin-top:12px;padding:12px;background:#2c3e50;color:#ecf0f1;border-radius:8px;font-family:monospace;font-size:13px;max-height:240px;overflow:auto;display:none}
    .log .warn{color:#f1c40f}
    .log .error{color:#e74c3c}
    .log .success{color:#2ecc71}
    .log .info{color:#3498db}
    .modal-backdrop{position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;z-index:9999}
    .modal{background:#fff;border-radius:10px;padding:16px;min-width:320px;max-width:920px;max-height:90vh;overflow:auto}
    .modal h3{margin-bottom:10px}
    .form-row{display:flex;gap:8px;margin-bottom:8px}
    .form-row .col{flex:1}
    input[type="text"], textarea, select{width:100%;padding:8px;border-radius:6px;border:1px solid #ccc;font-size:13px}
    textarea{min-height:80px;resize:vertical}
    .batch-list{max-height:420px;overflow:auto;padding:8px;border:1px dashed #ddd;border-radius:6px;background:#fff}
    .batch-item{display:flex;align-items:center;gap:8px;padding:6px;border-bottom:1px solid #f1f1f1}
    .fix-preview{max-height:420px;overflow:auto;padding:8px;border:1px dashed #bcd;border-radius:6px;background:#fff}
    .fix-row{padding:6px;border-bottom:1px solid #eee}
    .small{font-size:12px;color:#666}
    .controls{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:8px}
    .controls .col{flex:1;min-width:160px}
    .footer{padding:12px;text-align:center;background:#f3f6f9;color:#7f8c8d}
    .template-selector{margin:12px 0}
    .template-item{padding:8px;border:1px solid #ddd;border-radius:6px;margin:4px 0;cursor:pointer;background:#fff}
    .template-item:hover{background:#f0f7ff}
    .template-item.active{border-color:#3498db;background:#e3f2fd}
    .cloud-section{margin-top:12px;padding:12px;background:#fff;border-radius:8px;border:1px dashed #3498db}
    .cloud-actions{display:flex;gap:8px;margin-top:8px}
    .progress-bar{width:100%;height:6px;background:#ecf0f1;border-radius:3px;overflow:hidden;margin-top:8px}
    .progress-fill{height:100%;background:#3498db;transition:width 0.3s}
    .api-section{margin-top:12px;padding:12px;background:#f8f9fa;border-radius:8px}
    .api-key-input{display:flex;gap:8px;margin-top:8px}
    .api-key-input input{flex:1}
    .export-format-selector{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
    .export-format-selector label{display:flex;align-items:center;gap:4px}
    @media(max-width:1100px){.content{grid-template-columns:1fr}}
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>&#128218; 智能题库转换工具(增强版)</h1>
      <p>支持 .docx .xlsx .xls .pdf .txt · 智能清洗 · 模板匹配 · 云存储 · API接口</p>
    </div>

    <div class="content">
      <!-- 左侧 -->
      <div class="card">
        <h2>&#128228; 上传与处理</h2>

        <label id="uploadLabel" class="upload-label" for="fileInput" tabindex="0" role="button" aria-label="点击或拖拽上传文件">
          <i class="fas fa-cloud-upload-alt"></i>
          <div><strong>点击或拖拽文件到此区域</strong></div>
          <div id="uploadStatus" class="small">支持 .docx .xlsx .xls .pdf .txt</div>
        </label>

        <input type="file" id="fileInput" accept=".docx,.xlsx,.xls,.pdf,.txt" style="display:none">

        <div class="file-type-tabs" id="fileTypeTabs">
          <button class="tab-btn active" data-type="auto">智能检测</button>
          <button class="tab-btn" data-type="word">Word模式</button>
          <button class="tab-btn" data-type="excel">Excel模式</button>
          <button class="tab-btn" data-type="pdf">PDF模式</button>
        </div>

        <!-- 模板选择器 -->
        <div class="template-selector" id="templateSelector" style="display:none">
          <label class="small">选择题库模板:</label>
          <div id="templateList">
            <div class="template-item active" data-template="standard">标准模板 (题型-题目-选项-答案)</div>
            <div class="template-item" data-template="simple">简易模板 (题目[选项]答案)</div>
            <div class="template-item" data-template="exam">考试模板 (题号.题目 选项 答案)</div>
            <div class="template-item" data-template="custom">自定义模板...</div>
          </div>
        </div>

        <!-- PDF 页面范围选择 -->
        <div id="pdfPageContainer" style="margin-top:8px;display:none">
          <label class="small">PDF页面范围:</label>
          <div style="display:flex;gap:8px;margin-top:6px">
            <input type="number" id="pdfStartPage" placeholder="起始页" min="1" style="flex:1;padding:8px" value="1">
            <input type="number" id="pdfEndPage" placeholder="结束页" style="flex:1;padding:8px">
          </div>
        </div>

        <div style="margin-top:12px;text-align:center">
          <button class="btn" id="processBtn" disabled>&#128260; 智能转换</button>
          <button class="btn btn-download" id="downloadBtn" disabled>&#128190; 下载 JS</button>
          <button class="btn btn-json" id="downloadJsonBtn" disabled>&#128196; 导出 JSON</button>
          <button class="btn btn-pdf" id="downloadPdfBtn" disabled>&#128216; 生成 PDF</button>
        </div>

        <!-- 进度条 -->
        <div class="progress-bar" id="progressBar" style="display:none">
          <div class="progress-fill" id="progressFill" style="width:0%"></div>
        </div>

        <!-- Excel 工作表选择 -->
        <div id="excelSheetContainer" style="margin-top:8px;display:none">
          <label class="small">选择工作表:</label>
          <select id="sheetSelect" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ccc;margin-top:6px"></select>
        </div>

        <!-- Word 解析模式 -->
        <div id="wordParseContainer" style="margin-top:8px;display:none">
          <label class="small">解析模式:</label>
          <select id="parseMode" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ccc;margin-top:6px">
            <option value="auto">智能识别(表格/段落)</option>
            <option value="table">表格优先</option>
            <option value="paragraph">段落识别</option>
          </select>
        </div>

        <div class="stats" id="stats" style="margin-top:12px">
          <div class="stat-card"><div class="stat-number" id="stat-jud">0</div><div class="small">判断题</div></div>
          <div class="stat-card"><div class="stat-number" id="stat-single">0</div><div class="small">单选题</div></div>
          <div class="stat-card"><div class="stat-number" id="stat-multi">0</div><div class="small">多选题</div></div>
        </div>

        <!-- API接口设置 -->
        <div class="api-section" id="apiSection">
          <label class="small"><i class="fas fa-plug"></i> API接口设置</label>
          <div class="api-key-input">
            <input type="password" id="apiKeyInput" placeholder="输入API密钥(可选)">
            <button class="btn-ghost">保存</button>
          </div>
          <div style="margin-top:8px">
            <button class="btn-ghost" id="exportApiBtn" disabled>&#127760; 发布到API</button>
            <button class="btn-ghost" id="importApiBtn">&#128229; 从API导入</button>
          </div>
        </div>

        <!-- 云存储 -->
        <div class="cloud-section" id="cloudSection">
          <label class="small"><i class="fas fa-cloud"></i> 云存储</label>
          <div class="cloud-actions">
            <button class="btn-ghost" id="saveCloudBtn" disabled>&#128190; 保存到云端</button>
            <button class="btn-ghost" id="loadCloudBtn">&#128194; 从云端加载</button>
            <button class="btn-ghost" id="syncCloudBtn" disabled>&#128260; 同步云端</button>
          </div>
        </div>

        <div style="margin-top:12px">
          <button class="btn-ghost" id="batchBtn" disabled>&#129520; 批量修正</button>
          <button class="btn-ghost" id="exportLogBtn" disabled>&#128229; 导出日志</button>
          <button class="btn-ghost" id="collabBtn">&#128101; 协作模式</button>
        </div>

        <div class="log" id="logContainer"><strong>处理日志:</strong><div id="logContent"></div></div>
      </div>

      <!-- 右侧 -->
      <div class="card">
        <h2>&#128065;&#65039; 预览与手动修正</h2>

        <div class="controls" style="margin-bottom:10px">
          <div class="col"><input type="text" id="searchInput" placeholder="搜索题目 / 选项 / 答案 / ID" /></div>
          <div style="min-width:120px">
            <select id="filterType" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ccc">
              <option value="all">所有题型</option><option value="判断">判断</option><option value="单选">单选</option><option value="多选">多选</option>
            </select>
          </div>
          <div style="min-width:140px">
            <select id="filterConf" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ccc">
              <option value="all">所有置信度</option><option value="high">high</option><option value="medium">medium</option><option value="low">low</option>
            </select>
          </div>
          <div style="min-width:120px">
            <select id="pageSize" style="width:100%;padding:8px;border-radius:6px;border:1px solid #ccc">
              <option value="20">每页 20</option><option value="50">50</option><option value="100">100</option><option value="500">500</option>
            </select>
          </div>
        </div>

        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
          <div>
            <button class="btn-ghost" id="exportFilteredJsonBtn" disabled>导出筛选 JSON</button>
            <button class="btn-ghost" id="exportFilteredXlsxBtn" disabled>导出筛选 XLSX</button>
            <button class="btn-ghost" id="exportFilteredCsvBtn" disabled>导出筛选 CSV</button>
          </div>
          <div style="display:flex;gap:8px;align-items:center">
            <button class="btn-ghost" id="prevPageBtn">上一页</button>
            <span id="pageInfo" class="small">第 1 / 1 页</span>
            <button class="btn-ghost" id="nextPageBtn">下一页</button>
          </div>
        </div>

        <div class="preview" id="previewContent">
          <p class="small" style="padding:14px;text-align:center;color:#888">转换结果将显示在这里...</p>
        </div>
      </div>
    </div>

    <div class="footer">
      <p>&#169; 2025 智能题库转换工具(增强版)</p>
      <p class="small" style="margin-top:4px">版本 2.0 | 支持离线使用 | 数据安全加密</p>
    </div>
  </div>

  <!-- 编辑模态 -->
  <div class="modal-backdrop" id="editModalBackdrop">
    <div class="modal" role="dialog" aria-modal="true">
      <h3>编辑题目 <span id="editTitleId"></span></h3>
      <div class="form-row">
        <div class="col"><label>题型</label><select id="editType"><option>判断</option><option>单选</option><option>多选</option></select></div>
        <div class="col"><label>题目</label><input type="text" id="editQuestion"></div>
      </div>
      <div class="form-row">
        <div class="col"><label>选项(用 | 分隔)</label><textarea id="editOptions" placeholder="例如:北京 | 上海 | 广州 | 深圳"></textarea></div>
      </div>
      <div class="form-row">
        <div class="col"><label>答案(字母或文本)</label><input type="text" id="editAnswer"></div>
        <div class="col"><label>提示</label><input type="text" id="editHint"></div>
      </div>
      <div class="form-row">
        <div class="col"><label>置信度</label><select id="editConfidence"><option>high</option><option>medium</option><option>low</option></select></div>
        <div class="col"><label>原因(可选)</label><input type="text" id="editReason"></div>
      </div>
      <div style="text-align:right;margin-top:8px">
        <button class="btn-ghost">取消</button>
        <button class="btn">保存</button>
      </div>
    </div>
  </div>

  <!-- 批量修正模态 -->
  <div class="modal-backdrop" id="batchModalBackdrop">
    <div class="modal">
      <h3>批量修正工具(含自动修复预览)</h3>

      <div style="margin:8px 0">
        <button class="btn">全选</button>
        <button class="btn">全不选</button>
        <button class="btn btn-ghost">标为已确认(高置信度)</button>
        <button class="btn btn-ghost">生成自动修复建议</button>
        <button class="btn btn-ghost">应用所选修复</button>
      </div>

      <div style="display:flex;gap:8px;margin-bottom:8px">
        <div style="flex:1">
          <div class="small" style="margin-bottom:6px">待处理题目(可单条编辑)</div>
          <div class="batch-list" id="batchList"></div>
        </div>
        <div style="width:48%;min-width:300px">
          <div class="small" style="margin-bottom:6px">自动修复预览(生成后显示)</div>
          <div class="fix-preview" id="fixPreview">
            <div class="small" style="color:#999">请先点击 "生成自动修复建议" 查看修复预览</div>
          </div>
        </div>
      </div>

      <div style="text-align:right;margin-top:8px">
        <button class="btn-ghost">关闭</button>
      </div>
    </div>
  </div>

  <!-- 协作模式模态 -->
  <div class="modal-backdrop" id="collabModalBackdrop">
    <div class="modal">
      <h3>&#128101; 协作模式</h3>
      <div style="margin-bottom:12px">
        <p class="small">生成协作链接,邀请他人共同编辑题库:</p>
        <div style="display:flex;gap:8px">
          <input type="text" id="collabLink" readonly style="flex:1;padding:8px;background:#f8f9fa">
          <button class="btn">复制链接</button>
        </div>
        <div style="margin-top:8px">
          <label><input type="checkbox" id="allowEdit"> 允许他人编辑</label>
          <label style="margin-left:12px"><input type="checkbox" id="allowDownload"> 允许他人下载</label>
        </div>
      </div>
      <div style="margin-top:12px">
        <p class="small">当前协作者:</p>
        <div id="collaboratorList" style="max-height:200px;overflow:auto;padding:8px;border:1px solid #ddd;border-radius:6px">
          <div class="small" style="color:#999">无协作者</div>
        </div>
      </div>
      <div style="text-align:right;margin-top:12px">
        <button class="btn-ghost">关闭</button>
        <button class="btn">生成新链接</button>
      </div>
    </div>
  </div>

  <!-- 自定义模板模态 -->
  <div class="modal-backdrop" id="templateModalBackdrop">
    <div class="modal">
      <h3>自定义模板设置</h3>
      <div style="margin-bottom:12px">
        <label>模板名称:</label>
        <input type="text" id="templateName" placeholder="输入模板名称" style="width:100%;margin-top:4px">
      </div>
      <div style="margin-bottom:12px">
        <label>正则表达式匹配规则:</label>
        <textarea id="templateRegex" placeholder="例如:^(\d+)\.\s*(.*?)\s*A[\.:]\s*(.*?)\s*B[\.:]\s*(.*?)\s*C[\.:]\s*(.*?)\s*答案[::]\s*([A-D]+)" rows="4"></textarea>
        <p class="small" style="color:#666;margin-top:4px">使用正则表达式匹配题目格式,使用 $1, $2 等捕获组</p>
      </div>
      <div style="margin-bottom:12px">
        <label>字段映射:</label>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:4px">
          <div><label class="small">题型(可选)</label><input type="text" id="mapType" placeholder="$1"></div>
          <div><label class="small">题目</label><input type="text" id="mapQuestion" placeholder="$2"></div>
          <div><label class="small">选项A</label><input type="text" id="mapOptionA" placeholder="$3"></div>
          <div><label class="small">选项B</label><input type="text" id="mapOptionB" placeholder="$4"></div>
          <div><label class="small">选项C</label><input type="text" id="mapOptionC" placeholder="$5"></div>
          <div><label class="small">选项D</label><input type="text" id="mapOptionD" placeholder="$6"></div>
          <div><label class="small">答案</label><input type="text" id="mapAnswer" placeholder="$7"></div>
        </div>
      </div>
      <div style="margin-bottom:12px">
        <label>测试文本:</label>
        <textarea id="testText" placeholder="粘贴一段题目文本来测试模板" rows="3"></textarea>
        <button class="btn-ghost" style="margin-top:4px">测试模板</button>
        <div id="testResult" class="small" style="margin-top:4px;padding:4px;background:#f8f9fa;border-radius:4px;display:none"></div>
      </div>
      <div style="text-align:right;margin-top:12px">
        <button class="btn-ghost">取消</button>
        <button class="btn">保存模板</button>
      </div>
    </div>
  </div>
</body>
</html>

<script>
/* ==================== 全局数据 ==================== */
let questionBank = [];
let currentId = 1;
let pageSize = 20;
let currentPage = 1;

// Excel 相关
let excelWorkbook = null;
let excelSheetDataMap = {};
let excelSheetName = '';

// PDF 相关
let pdfDocument = null;

// 文件类型
let currentFileType = 'auto';
let currentFileName = '';
let currentTemplate = 'standard';

// 云存储相关
let cloudStorage = {
  apiKey: localStorage.getItem('question_tool_api_key') || '',
  cloudData: JSON.parse(localStorage.getItem('question_cloud_data') || '{}'),
  lastSync: localStorage.getItem('question_last_sync') || ''
};

// 模板库
const TEMPLATES = {
  standard: {
    name: '标准模板',
    regex: /题型[::]\s*(.*?)\s*题目[::]\s*(.*?)\s*(?:选项A[::]\s*(.*?)\s*)?(?:选项B[::]\s*(.*?)\s*)?(?:选项C[::]\s*(.*?)\s*)?(?:选项D[::]\s*(.*?)\s*)?答案[::]\s*(.*)/i,
    mapping: { type: '$1', question: '$2', options: ['$3','$4','$5','$6'], answer: '$7' }
  },
  simple: {
    name: '简易模板',
    regex: /(.*?)\s*\[(.*?)\]\s*答案[::]\s*(.*)/i,
    mapping: { question: '$1', options: '$2'.split(/[;,]/), answer: '$3' }
  },
  exam: {
    name: '考试模板',
    regex: /^(\d+)\.\s*(.*?)\s*A[\.:]\s*(.*?)\s*B[\.:]\s*(.*?)\s*C[\.:]\s*(.*?)\s*D[\.:]\s*(.*?)\s*答案[::]\s*([A-D]+)/i,
    mapping: { id: '$1', question: '$2', options: ['$3','$4','$5','$6'], answer: '$7' }
  }
};

/* ==================== 工具函数 ==================== */
function addLog(msg, level='info'){
  const lc = document.getElementById('logContainer');
  const content = document.getElementById('logContent');
  if(!lc || !content) return console.log(msg);
  lc.style.display='block';
  const d = document.createElement('div');
  d.className = level;
  d.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
  content.appendChild(d);
  content.scrollTop = content.scrollHeight;
  console.log(msg);
}

function escapeHtml(str){
  if(str===undefined||str===null) return '';
  return String(str)
    .replace(/&/g,'&')
    .replace(/</g,'<')
    .replace(/>/g,'>')
    .replace(/"/g,'"')
    .replace(/'/g,''')
    .replace(/\n/g,'<br/>');
}

function updateProgress(percent, message = ''){
  const progressBar = document.getElementById('progressBar');
  const progressFill = document.getElementById('progressFill');
  if(progressBar && progressFill){
    progressBar.style.display = 'block';
    progressFill.style.width = percent + '%';
    if(message) addLog(message, 'info');
  }
}

/* ==================== 模板匹配系统 ==================== */
function applyTemplate(text, templateName){
  const template = TEMPLATES[templateName];
  if(!template) return null;
  
  const lines = text.split('\n').filter(line => line.trim());
  const questions = [];
  
  for(let line of lines){
    const match = line.match(template.regex);
    if(match){
      const q = {};
      for(const [key, value] of Object.entries(template.mapping)){
        if(Array.isArray(value)){
          q[key] = value.map(v => {
            if(v && v.startsWith('$')){
              const idx = parseInt(v.substring(1));
              return match[idx] || '';
            }
            return v;
          }).filter(opt => opt.trim());
        } else if(value && value.startsWith('$')){
          const idx = parseInt(value.substring(1));
          q[key] = match[idx] || '';
        } else {
          q[key] = value;
        }
      }
      
      // 自动推断题型
      if(!q.type){
        if(q.options && q.options.length >= 2){
          if(q.answer && q.answer.length > 1){
            q.type = '多选';
          } else {
            q.type = '单选';
          }
        } else {
          q.type = '判断';
          q.options = ['正确', '错误'];
        }
      }
      
      questions.push(q);
    }
  }
  
  return questions;
}

/* ==================== 自定义模板功能 ==================== */
function openTemplateModal(){
  // 清空表单
  document.getElementById('templateName').value = '';
  document.getElementById('templateRegex').value = '^(\d+)\.\s*(.*?)\s*A[\.:]\s*(.*?)\s*B[\.:]\s*(.*?)\s*C[\.:]\s*(.*?)\s*D[\.:]\s*(.*?)\s*答案[::]\s*([A-D]+)';
  document.getElementById('mapType').value = '';
  document.getElementById('mapQuestion').value = '$2';
  document.getElementById('mapOptionA').value = '$3';
  document.getElementById('mapOptionB').value = '$4';
  document.getElementById('mapOptionC').value = '$5';
  document.getElementById('mapOptionD').value = '$6';
  document.getElementById('mapAnswer').value = '$7';
  document.getElementById('testText').value = '';
  document.getElementById('testResult').style.display = 'none';
  
  document.getElementById('templateModalBackdrop').style.display = 'flex';
}

function closeTemplateModal(){
  document.getElementById('templateModalBackdrop').style.display = 'none';
}

function testTemplate(){
  const regexStr = document.getElementById('templateRegex').value;
  const testText = document.getElementById('testText').value;
  const resultDiv = document.getElementById('testResult');
  
  if(!regexStr || !testText){
    resultDiv.innerHTML = '<span style="color:#e74c3c">请输入正则表达式和测试文本</span>';
    resultDiv.style.display = 'block';
    return;
  }
  
  try {
    const regex = new RegExp(regexStr, 'i');
    const match = testText.match(regex);
    
    if(match){
      let resultHtml = '<span style="color:#27ae60">&#9989; 匹配成功!</span><br>';
      resultHtml += '<div style="margin-top:8px;font-size:11px">';
      for(let i = 0; i < match.length; i++){
        resultHtml += `<strong>$${i}</strong>: ${escapeHtml(match[i] || '')}<br>`;
      }
      resultHtml += '</div>';
      
      // 预览提取的字段
      const type = document.getElementById('mapType').value || '';
      const question = document.getElementById('mapQuestion').value || '';
      const optionA = document.getElementById('mapOptionA').value || '';
      const optionB = document.getElementById('mapOptionB').value || '';
      const optionC = document.getElementById('mapOptionC').value || '';
      const optionD = document.getElementById('mapOptionD').value || '';
      const answer = document.getElementById('mapAnswer').value || '';
      
      resultHtml += '<div style="margin-top:8px;border-top:1px solid #ddd;padding-top:8px">';
      resultHtml += '<strong>字段映射预览:</strong><br>';
      
      const getValue = (placeholder) => {
        if(!placeholder || !placeholder.startsWith('$')) return placeholder;
        const idx = parseInt(placeholder.substring(1));
        return match[idx] || '';
      };
      
      if(type) resultHtml += `题型: ${getValue(type)}<br>`;
      if(question) resultHtml += `题目: ${getValue(question)}<br>`;
      if(optionA) resultHtml += `选项A: ${getValue(optionA)}<br>`;
      if(optionB) resultHtml += `选项B: ${getValue(optionB)}<br>`;
      if(optionC) resultHtml += `选项C: ${getValue(optionC)}<br>`;
      if(optionD) resultHtml += `选项D: ${getValue(optionD)}<br>`;
      if(answer) resultHtml += `答案: ${getValue(answer)}<br>`;
      
      resultHtml += '</div>';
      
      resultDiv.innerHTML = resultHtml;
    } else {
      resultDiv.innerHTML = '<span style="color:#e74c3c">&#10060; 匹配失败,请检查正则表达式</span>';
    }
  } catch(err){
    resultDiv.innerHTML = `<span style="color:#e74c3c">&#10060; 正则表达式错误: ${escapeHtml(err.message)}</span>`;
  }
  
  resultDiv.style.display = 'block';
}

function saveCustomTemplate(){
  const name = document.getElementById('templateName').value.trim();
  const regexStr = document.getElementById('templateRegex').value.trim();
  
  if(!name){
    alert('请输入模板名称');
    return;
  }
  
  if(!regexStr){
    alert('请输入正则表达式');
    return;
  }
  
  try {
    // 测试正则表达式是否有效
    new RegExp(regexStr, 'i');
  } catch(err){
    alert(`正则表达式无效: ${err.message}`);
    return;
  }
  
  // 构建映射对象
  const mapping = {};
  const type = document.getElementById('mapType').value.trim();
  const question = document.getElementById('mapQuestion').value.trim();
  const optionA = document.getElementById('mapOptionA').value.trim();
  const optionB = document.getElementById('mapOptionB').value.trim();
  const optionC = document.getElementById('mapOptionC').value.trim();
  const optionD = document.getElementById('mapOptionD').value.trim();
  const answer = document.getElementById('mapAnswer').value.trim();
  
  if(type) mapping.type = type;
  if(question) mapping.question = question;
  
  const options = [];
  if(optionA) options.push(optionA);
  if(optionB) options.push(optionB);
  if(optionC) options.push(optionC);
  if(optionD) options.push(optionD);
  
  if(options.length > 0) mapping.options = options;
  if(answer) mapping.answer = answer;
  
  // 保存模板
  TEMPLATES[name] = {
    name: name,
    regex: new RegExp(regexStr, 'i'),
    mapping: mapping
  };
  
  // 更新模板选择器
  updateTemplateList();
  
  // 选中新创建的模板
  currentTemplate = name;
  document.querySelectorAll('.template-item').forEach(item => {
    item.classList.remove('active');
    if(item.dataset.template === name){
      item.classList.add('active');
    }
  });
  
  addLog(`自定义模板 "${name}" 已保存`, 'success');
  closeTemplateModal();
}

function updateTemplateList(){
  const templateList = document.getElementById('templateList');
  const customTemplates = Object.keys(TEMPLATES).filter(key => 
    !['standard', 'simple', 'exam'].includes(key)
  );
  
  // 移除现有的自定义模板项(除了标准的那三个)
  document.querySelectorAll('.template-item[data-template^="custom_"]').forEach(item => {
    item.remove();
  });
  
  // 添加新的自定义模板
  customTemplates.forEach(key => {
    const template = TEMPLATES[key];
    const item = document.createElement('div');
    item.className = 'template-item';
    item.dataset.template = key;
    item.textContent = `${template.name} (自定义)`;
    item.addEventListener('click', () => {
      document.querySelectorAll('.template-item').forEach(i => i.classList.remove('active'));
      item.classList.add('active');
      currentTemplate = key;
    });
    
    templateList.appendChild(item);
  });
}

/* ==================== PDF解析功能 ==================== */
async function extractTextFromPDF(file, startPage = 1, endPage = null){
  updateProgress(0, '正在加载PDF文档...');
  
  try {
    const arrayBuffer = await file.arrayBuffer();
    pdfDocument = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
    
    const numPages = pdfDocument.numPages;
    if(!endPage || endPage > numPages) endPage = numPages;
    
    let fullText = '';
    for(let i = startPage; i <= endPage; i++){
      updateProgress(((i - startPage) / (endPage - startPage + 1)) * 100, `正在解析第 ${i}/${endPage} 页...`);
      
      const page = await pdfDocument.getPage(i);
      const textContent = await page.getTextContent();
      const pageText = textContent.items.map(item => item.str).join(' ');
      fullText += pageText + '\n';
    }
    
    updateProgress(100, 'PDF解析完成');
    return fullText;
  } catch(err){
    addLog(`PDF解析失败: ${err.message}`, 'error');
    throw err;
  }
}

/* ==================== TXT文件解析 ==================== */
function parseTextFile(text, templateName = 'auto'){
  if(templateName === 'auto'){
    // 尝试自动检测模板
    for(const [name, template] of Object.entries(TEMPLATES)){
      const sampleLines = text.split('\n').slice(0, 5).join('\n');
      if(template.regex.test(sampleLines)){
        currentTemplate = name;
        break;
      }
    }
  }
  
  let questions;
  if(currentTemplate in TEMPLATES){
    questions = applyTemplate(text, currentTemplate);
  } else {
    // 使用默认段落解析
    questions = parseWordTextToQuestions(text, 'paragraph');
  }
  
  return questions.map((q, idx) => {
    const parsed = parseAnswer(q.answer || '', q.options || [], q.type || '未知');
    let hint = '请查看教材';
    if(q.type==='判断') hint = parsed.answer==='A'?'正确':'错误';
    else if(q.options && q.options.length>0){
      const hintParts = [];
      for(const ch of String(parsed.answer)){
        if(/[A-Z]/.test(ch)){
          const idx = ch.charCodeAt(0)-65;
          if(q.options[idx]) hintParts.push(q.options[idx]);
        }
      }
      if(hintParts.length>0) hint = hintParts.join(', ');
    }
    
    return {
      id: currentId++,
      type: q.type || '未知',
      question: q.question || '',
      options: q.options || [],
      answer: parsed.answer,
      hint,
      confidence: parsed.confidence,
      reason: parsed.reason,
      sourceRow: idx+1,
      sourceFile: currentFileName,
      rawRow: [q.question, ...(q.options || []), q.answer]
    };
  });
}

/* ==================== 答案解析核心 ==================== */
function mapChineseLetterToLatin(s){
  if(!s) return s;
  const MAP = {
    '甲':'A','乙':'B','丙':'C','丁':'D','甲)':'A','乙)':'B','丙)':'C','丁)':'D',
    '一':'A','二':'B','三':'C','四':'D','①':'A','②':'B','③':'C','④':'D',
    '1':'A','2':'B','3':'C','4':'D','Ⅰ':'A','Ⅱ':'B','Ⅲ':'C','Ⅳ':'D'
  };
  return s.split('').map(ch=>MAP[ch]||ch).join('');
}

function parseAnswer(rawAnswer, options, type){
  if(rawAnswer===undefined||rawAnswer===null) return {answer:'', confidence:'low', reason:'空答案'};
  let s = String(rawAnswer).trim();
  s = s.replace(/[\s ]+/g,' ').replace(/[,、;;]/g,',').trim();
  const mapped = mapChineseLetterToLatin(s);
  const normalized = String(mapped).replace(/[,,\s\/\\]+/g,',').trim();

  if(/判断|对错|是非/i.test(type) || /^[对错是否√×ABab]$/.test(s) || /^[ABab]$/.test(mapped)){
    if(/^(对|正确|T|True|是|√|yes|A)$/i.test(s) || /^[Aa]$/.test(mapped)) return {answer:'A', confidence:'high', reason:'识别为正确'};
    if(/^(错|错误|F|False|否|×|no|B)$/i.test(s) || /^[Bb]$/.test(mapped)) return {answer:'B', confidence:'high', reason:'识别为错误'};
    return {answer:s, confidence:'low', reason:'无法识别判断题答案'};
  }

  if(/^[A-Za-z]+([,,\s\/\\][A-Za-z]+)*$/.test(normalized)){
    const letters = (normalized.match(/[A-Za-z]/g)||[]).map(l=>l.toUpperCase());
    const uniq = [...new Set(letters)].sort().join('');
    return {answer:uniq, confidence:'high', reason:'字母形式答案'};
  }

  const parts = normalized.split(/[,]+/).filter(Boolean);
  if(parts.length===1 && options && options.length>0){
    const part = parts[0].toLowerCase();
    for(let i=0;i<options.length;i++){
      const opt = String(options[i]||'').trim().toLowerCase();
      if(opt===part) return {answer:String.fromCharCode(65+i), confidence:'high', reason:'选项文本精确匹配'};
    }
  }

  if(options && options.length>0){
    const matched = [];
    for(let i=0;i<options.length;i++){
      const opt = String(options[i]||'').trim();
      if(!opt) continue;
      const loOpt = opt.toLowerCase(), loS = s.toLowerCase();
      if(loOpt.includes(loS) || loS.includes(loOpt)) matched.push(i);
    }
    if(matched.length>0){
      const ans = matched.map(i=>String.fromCharCode(65+i)).sort().join('');
      const conf = (matched.length===1 || /多选|multiple/i.test(type)) ? 'high' : 'medium';
      return {answer:ans, confidence:conf, reason:`模糊匹配到选项 (${matched.map(i=>String.fromCharCode(65+i)).join(',')})`};
    }
  }

  const letters = mapped.match(/[A-Za-z]/g);
  if(letters){
    const uniq = [...new Set(letters.map(l=>l.toUpperCase()))].sort().join('');
    return {answer:uniq, confidence:'medium', reason:'从文本中提取到字母'};
  }

  return {answer:s, confidence:'low', reason:'无法匹配答案'};
}

/* ==================== 文件处理 ==================== */
function handleFile(file){
  if(!file) return;
  currentFileName = file.name;
  currentId = 1;
  
  const ext = file.name.split('.').pop().toLowerCase();
  const tabs = document.querySelectorAll('.tab-btn');
  
  // 显示模板选择器
  document.getElementById('templateSelector').style.display = 'block';
  
  let detectedType = 'auto';
  if(ext.match(/xlsx?/)) detectedType = 'excel';
  else if(ext === 'docx') detectedType = 'word';
  else if(ext === 'pdf') detectedType = 'pdf';
  else if(ext === 'txt') detectedType = 'txt';
  
  tabs.forEach(tab => {
    if(tab.dataset.type === detectedType){
      tab.classList.add('active');
      currentFileType = detectedType;
    } else {
      tab.classList.remove('active');
    }
  });
  
  updateUIByFileType();

  if(ext.match(/xlsx?/)){
    handleExcelFile(file);
  } else if(ext === 'docx'){
    handleWordFile(file);
  } else if(ext === 'pdf'){
    handlePDFFile(file);
  } else if(ext === 'txt'){
    handleTextFile(file);
  } else {
    alert('不支持的文件类型,请上传 .docx .xlsx .xls .pdf .txt 文件');
    return;
  }
}

async function handlePDFFile(file){
  try {
    window.pdfFile = file;
    
    const uploadStatus = document.getElementById('uploadStatus');
    if(uploadStatus) uploadStatus.textContent = `PDF文件: ${file.name}`;
    
    // 显示PDF页面范围选择
    document.getElementById('pdfPageContainer').style.display = 'block';
    
    document.getElementById('processBtn').disabled = false;
    document.getElementById('exportLogBtn').disabled = false;
    
    addLog(`PDF文件 "${file.name}" 已加载,请设置页面范围后点击转换`, 'success');
  } catch(err){
    addLog(`PDF文件处理失败: ${err.message}`, 'error');
    alert('PDF文件处理失败: ' + err.message);
  }
}

async function handleTextFile(file){
  const reader = new FileReader();
  reader.onload = async e=>{
    try{
      const text = e.target.result;
      window.txtRawText = text;
      
      const uploadStatus = document.getElementById('uploadStatus');
      if(uploadStatus) uploadStatus.textContent = `文本文件: ${file.name} (${text.length}字符)`;
      
      document.getElementById('processBtn').disabled = false;
      document.getElementById('exportLogBtn').disabled = false;
      addLog(`文本文件 "${file.name}" 加载成功`, 'success');
    } catch(err){
      addLog(`文本文件解析失败: ${err.message}`, 'error');
      alert('文本文件解析失败: ' + err.message);
    }
  };
  reader.readAsText(file);
}

/* ==================== 更新UI ==================== */
function updateUIByFileType(){
  const excelContainer = document.getElementById('excelSheetContainer');
  const wordContainer = document.getElementById('wordParseContainer');
  const pdfContainer = document.getElementById('pdfPageContainer');
  
  if(currentFileType === 'excel'){
    excelContainer.style.display = 'block';
    wordContainer.style.display = 'none';
    pdfContainer.style.display = 'none';
  } else if(currentFileType === 'word'){
    excelContainer.style.display = 'none';
    wordContainer.style.display = 'block';
    pdfContainer.style.display = 'none';
  } else if(currentFileType === 'pdf'){
    excelContainer.style.display = 'none';
    wordContainer.style.display = 'none';
    pdfContainer.style.display = 'block';
  } else {
    // auto模式根据文件名判断
    if(currentFileName.match(/\.xlsx?$/i)){
      excelContainer.style.display = 'block';
      wordContainer.style.display = 'none';
      pdfContainer.style.display = 'none';
    } else if(currentFileName.match(/\.docx$/i)){
      excelContainer.style.display = 'none';
      wordContainer.style.display = 'block';
      pdfContainer.style.display = 'none';
    } else if(currentFileName.match(/\.pdf$/i)){
      excelContainer.style.display = 'none';
      wordContainer.style.display = 'none';
      pdfContainer.style.display = 'block';
    } else {
      excelContainer.style.display = 'none';
      wordContainer.style.display = 'none';
      pdfContainer.style.display = 'none';
    }
  }
}

/* ==================== 处理文件 ==================== */
async function processFile(){
  document.getElementById('processBtn').disabled = true;
  const logEl = document.getElementById('logContainer');
  if(logEl) logEl.style.display='block';
  addLog('开始智能转换...', 'info');

  questionBank = [];
  currentId = 1;
  updateProgress(0);

  try {
    if(currentFileType === 'excel' || currentFileName.match(/\.xlsx?$/i)){
      if(!excelWorkbook || !excelSheetName || !excelSheetDataMap[excelSheetName]){
        alert('请先加载Excel文件并选择工作表');
        document.getElementById('processBtn').disabled = false;
        return;
      }
      const data = excelSheetDataMap[excelSheetName];
      questionBank = convertExcelToQuestionBank(data, excelSheetName);
      addLog(`Excel转换完成,共 ${questionBank.length} 题。`, 'success');
      
    } else if(currentFileType === 'word' || currentFileName.match(/\.docx$/i)){
      if(!window.wordRawText){
        alert('请先加载Word文件');
        document.getElementById('processBtn').disabled = false;
        return;
      }
      const parseMode = document.getElementById('parseMode').value;
      questionBank = parseWordTextToQuestions(window.wordRawText, parseMode);
      addLog(`Word转换完成,共 ${questionBank.length} 题。`, 'success');
      
    } else if(currentFileType === 'pdf' || currentFileName.match(/\.pdf$/i)){
      if(!window.pdfFile){
        alert('请重新上传PDF文件');
        document.getElementById('processBtn').disabled = false;
        return;
      }
      const startPage = parseInt(document.getElementById('pdfStartPage').value) || 1;
      const endPage = parseInt(document.getElementById('pdfEndPage').value) || null;
      
      const text = await extractTextFromPDF(window.pdfFile, startPage, endPage);
      questionBank = parseTextFile(text, currentTemplate);
      addLog(`PDF转换完成,共 ${questionBank.length} 题。`, 'success');
      
    } else if(currentFileType === 'txt' || currentFileName.match(/\.txt$/i)){
      if(!window.txtRawText){
        alert('请先加载文本文件');
        document.getElementById('processBtn').disabled = false;
        return;
      }
      questionBank = parseTextFile(window.txtRawText, currentTemplate);
      addLog(`文本文件转换完成,共 ${questionBank.length} 题。`, 'success');
    }

    updateProgress(100);
    updateStats(questionBank);
    pageSize = Number(document.getElementById('pageSize').value || 20);
    currentPage = 1;
    updatePreview();
    
    document.getElementById('downloadBtn').disabled = questionBank.length===0;
    document.getElementById('downloadJsonBtn').disabled = questionBank.length===0;
    document.getElementById('batchBtn').disabled = questionBank.length===0;
    document.getElementById('saveCloudBtn').disabled = questionBank.length===0;
    document.getElementById('syncCloudBtn').disabled = questionBank.length===0;
    document.getElementById('exportApiBtn').disabled = questionBank.length===0;
    document.getElementById('downloadPdfBtn').disabled = questionBank.length===0;
    
  } catch(err){
    addLog(`转换过程中出错: ${err.message}`, 'error');
    document.getElementById('processBtn').disabled = false;
  }
}

/* ==================== Excel处理函数 ==================== */
function handleExcelFile(file){
  const reader = new FileReader();
  reader.onload = e=>{
    try{
      const data = new Uint8Array(e.target.result);
      const wb = XLSX.read(data, {type:'array'});
      excelWorkbook = wb;
      excelSheetDataMap = {};
      const sheetSelect = document.getElementById('sheetSelect');
      sheetSelect.innerHTML = '';
      wb.SheetNames.forEach((name)=>{
        const ws = wb.Sheets[name];
        const arr = XLSX.utils.sheet_to_json(ws, {header:1});
        excelSheetDataMap[name] = arr;
        const opt = document.createElement('option');
        opt.value = name;
        opt.textContent = `${name} (${Math.max(0, arr.length-1)}行)`;
        sheetSelect.appendChild(opt);
      });
      excelSheetName = wb.SheetNames[0];
      sheetSelect.value = excelSheetName;
      
      const uploadStatus = document.getElementById('uploadStatus');
      if(uploadStatus) uploadStatus.textContent = `Excel文件: ${file.name} (${wb.SheetNames.length}个工作表)`;
      
      document.getElementById('processBtn').disabled=false;
      document.getElementById('exportLogBtn').disabled=false;
      addLog(`Excel文件 "${file.name}" 加载成功,${wb.SheetNames.length}个工作表`, 'success');
      
      sheetSelect.onchange = function(){ 
        excelSheetName = this.value; 
        const s = excelSheetDataMap[excelSheetName]||[]; 
        if(uploadStatus) uploadStatus.textContent = `已选择工作表: "${excelSheetName}", 共 ${Math.max(0, s.length-1)} 行`; 
      };
    }catch(err){
      addLog(`Excel文件读取失败: ${err.message}`, 'error');
      alert('Excel文件读取失败: ' + err.message);
    }
  };
  reader.readAsArrayBuffer(file);
}

function convertExcelToQuestionBank(data, sheetName='Sheet1'){
  const all = [];
  const header = (data[0]||[]).map(h=>String(h||'').trim());
  function findHeaderIndex(regex, defIndex){ const idx = header.findIndex(h=>regex.test(String(h))); return idx>=0?idx:defIndex; }
  const colMap = {
    type: findHeaderIndex(/类型|题型|category/i, 0),
    question: findHeaderIndex(/题目|问题|题干|question/i, 1),
    answer: findHeaderIndex(/答案|正确答案|answer/i, header.length-1)
  };

  let optionCols = [];
  for(let i=0;i<header.length;i++){
    if(i===colMap.type||i===colMap.question||i===colMap.answer) continue;
    if(/^[A-D]$|选项|选项\d|A项|B项|C项|D项|option/i.test(header[i]||'')) optionCols.push(i);
  }
  if(optionCols.length===0){
    const start = Math.max(0, colMap.question+1);
    const end = Math.max(colMap.answer, header.length);
    for(let j=start;j<end;j++) optionCols.push(j);
  }

  for(let i=1;i<data.length;i++){
    const row = data[i]||[];
    if(row.filter(Boolean).length===0) continue;
    const rawType = String(row[colMap.type]||'').trim();
    const question = String(row[colMap.question]||'').trim();
    const rawAnswer = row[colMap.answer]===undefined?'':String(row[colMap.answer]).trim();
    if(!question){ addLog(`第 ${i+1} 行:题目为空,已跳过。`, 'warn'); continue; }

    const opts = [];
    for(const ci of optionCols){
      const cell = row[ci];
      if(cell!==undefined && String(cell).trim()!=='') opts.push(String(cell).trim());
    }

    let type = '未知';
    if(/判断|对错|是非/.test(rawType)) type='判断';
    else if(/单选/.test(rawType)) type='单选';
    else if(/多选/.test(rawType)) type='多选';
    else {
      if(rawAnswer && /^[A-Za-z]$/.test(String(rawAnswer).trim())) type='单选';
      else if(rawAnswer && /[A-Za-z].*[A-Za-z]/.test(String(rawAnswer).trim())) type='多选';
    }
    if(type==='未知'){ addLog(`第 ${i+1} 行:无法识别题型 "${rawType}",已跳过。`, 'warn'); continue; }
    if(type==='判断' && opts.length===0) opts.push('正确','错误');

    const parsed = parseAnswer(rawAnswer, opts, type);
    const answer = parsed.answer;
    const confidence = parsed.confidence;
    const reason = parsed.reason || '';

    let hint = '请查看教材';
    if(type==='判断'){ hint = answer==='A'?'正确':answer==='B'?'错误':answer; }
    else {
      const hintParts = [];
      for(const ch of String(answer)){
        if(/[A-Z]/.test(ch)){
          const idx = ch.charCodeAt(0)-65;
          if(opts[idx]) hintParts.push(opts[idx]);
        }
      }
      if(hintParts.length>0) hint = hintParts.join(', ');
    }

    const q = {
      id: currentId++,
      type,
      question,
      options: opts,
      answer,
      hint,
      confidence,
      reason,
      sourceSheet: sheetName,
      sourceRow: i+1,
      rawRow: row.map(c=>c===undefined?'':String(c))
    };
    all.push(q);

    if(confidence==='low') addLog(`第 ${i+1} 行 题目 #${q.id} 答案无法识别: "${rawAnswer}" (原因: ${reason})`, 'warn');
    else if(confidence==='medium') addLog(`第 ${i+1} 行 题目 #${q.id} 答案模糊匹配: "${rawAnswer}" (原因: ${reason})`, 'warn');
  }
  return all;
}

/* ==================== Word处理函数 ==================== */
async function handleWordFile(file){
  const reader = new FileReader();
  reader.onload = async e=>{
    try{
      const arrayBuffer = e.target.result;
      const result = await mammoth.extractRawText({ arrayBuffer });
      const text = result.value;
      
      window.wordRawText = text;
      
      const uploadStatus = document.getElementById('uploadStatus');
      if(uploadStatus) uploadStatus.textContent = `Word文件: ${file.name} (${text.length}字符)`;
      
      document.getElementById('processBtn').disabled=false;
      document.getElementById('exportLogBtn').disabled=false;
      addLog(`Word文件 "${file.name}" 加载成功,${text.length}字符`, 'success');
    }catch(err){
      addLog(`Word文件解析失败: ${err.message}`, 'error');
      alert('Word文件解析失败: ' + err.message);
    }
  };
  reader.readAsArrayBuffer(file);
}

function parseWordTextToQuestions(text, parseMode='auto'){
  const lines = text.split(/\n/).map(l=>l.trim()).filter(l=>l);
  const questions = [];
  let current = null;
  let optionPattern = /^[A-Da-d][\..、::)\)]\s*(.+)$/;
  let answerPattern = /答案[::]\s*(.+)/i;

  for(let i=0; i<lines.length; i++){
    const line = lines[i];
    if(/^\d+[\..]/.test(line) || /^[一二三四五六七八九十]+、/.test(line) || /^第.+题/.test(line)){
      if(current) questions.push(current);
      current = { type: '未知', question: line.replace(/^\d+[\..]|^[一二三四五六七八九十]+、|^第.+题/, '').trim(), options: [], answer: '' };
      continue;
    }
    if(!current) continue;
    const optMatch = line.match(optionPattern);
    if(optMatch){
      current.options.push(optMatch[1].trim());
      continue;
    }
    const ansMatch = line.match(answerPattern);
    if(ansMatch){
      current.answer = ansMatch[1].trim();
      continue;
    }
    if(/判断|单选|多选/.test(line)){
      current.type = line.includes('判断') ? '判断' : line.includes('多选') ? '多选' : '单选';
    }
  }
  if(current) questions.push(current);

  return questions.map((q, idx)=>{
    if(q.type==='未知'){
      if(q.answer && q.answer.length===1) q.type='单选';
      else if(q.answer && q.answer.length>1) q.type='多选';
      else if(q.options.length===0) q.type='判断';
    }
    if(q.type==='判断' && q.options.length===0) q.options=['正确','错误'];
    const parsed = parseAnswer(q.answer, q.options, q.type);
    let hint = '请查看教材';
    if(q.type==='判断') hint = parsed.answer==='A'?'正确':'错误';
    else {
      const hintParts = [];
      for(const ch of String(parsed.answer)){
        if(/[A-Z]/.test(ch)){
          const idx = ch.charCodeAt(0)-65;
          if(q.options[idx]) hintParts.push(q.options[idx]);
        }
      }
      if(hintParts.length>0) hint = hintParts.join(', ');
    }
    return {
      id: currentId++,
      type: q.type,
      question: q.question,
      options: q.options,
      answer: parsed.answer,
      hint,
      confidence: parsed.confidence,
      reason: parsed.reason,
      sourceRow: idx+1,
      rawRow: [q.question, ...q.options, q.answer]
    };
  });
}

/* ==================== 预览和分页功能 ==================== */
function updateStats(all){
  const s = {jud:0,sin:0,mul:0};
  (all||questionBank).forEach(q=>{ 
    if(q.type==='判断') s.jud++; 
    else if(q.type==='单选') s.sin++; 
    else if(q.type==='多选') s.mul++; 
  });
  document.getElementById('stat-jud').textContent = s.jud;
  document.getElementById('stat-single').textContent = s.sin;
  document.getElementById('stat-multi').textContent = s.mul;
}

function getFilteredQuestions(){
  const txt = (document.getElementById('searchInput').value||'').trim().toLowerCase();
  const type = document.getElementById('filterType').value;
  const conf = document.getElementById('filterConf').value;
  return questionBank.filter(q=>{
    if(type!=='all' && q.type!==type) return false;
    if(conf!=='all' && q.confidence!==conf) return false;
    if(!txt) return true;
    if(String(q.id).includes(txt)) return true;
    if((q.question||'').toLowerCase().includes(txt)) return true;
    if((q.answer||'').toLowerCase().includes(txt)) return true;
    if((q.options||[]).some(o=>o.toLowerCase().includes(txt))) return true;
    return (q.reason||'').toLowerCase().includes(txt) || (q.hint||'').toLowerCase().includes(txt);
  });
}

function updatePreview(all){
  const root = document.getElementById('previewContent');
  const filtered = all || getFilteredQuestions();
  if(!filtered || filtered.length===0){
    root.innerHTML = '<p class="small" style="padding:14px;text-align:center;color:#888">未找到有效题目。</p>';
    updatePageInfo(0,1);
    return;
  }
  const total = filtered.length;
  const pSize = pageSize;
  const totalPages = Math.max(1, Math.ceil(total/pSize));
  if(currentPage > totalPages) currentPage = totalPages;
  const start = (currentPage-1)*pSize, end = Math.min(total, start+pSize);
  const pageItems = filtered.slice(start, end);

  let html = '';
  for(const q of pageItems){
    const typeClass = q.type==='判断'?'type-judgment':q.type==='单选'?'type-single':'type-multi';
    const uncertain = q.confidence!=='high' ? 'uncertain' : '';
    const badge = q.confidence!=='high' ? `<span class="uncertain-badge">${q.confidence==='low'?'无法识别':'模糊匹配'}</span>` : '';
    const optsTxt = q.options && q.options.length>0 ? `<div class="small" style="margin-top:6px;color:#666">选项: ${escapeHtml(q.options.join(' | '))}</div>` : '';
    const reasonTxt = q.reason ? `<div class="small" style="margin-top:6px;color:#b03">原因: ${escapeHtml(q.reason)}</div>` : '';
    const sourceTxt = q.sourceSheet ? 
      `<div class="small" style="margin-top:6px;color:#999">来源: ${escapeHtml(q.sourceSheet||'')} 行 ${q.sourceRow||''}</div>` :
      `<div class="small" style="margin-top:6px;color:#999">来源: 行 ${q.sourceRow||''}</div>`;
    html += `<div class="q-item ${uncertain}" id="q-${q.id}">
      <div class="q-meta">
        <span class="q-type ${typeClass}">${q.type}</span>
        <strong>#${q.id}</strong>
        <div style="margin-left:10px;color:#333;font-weight:600">${escapeHtml(q.question)}</div>
        <div class="q-actions">
          <button class="btn-edit"><i class="fa fa-edit"></i> 编辑</button>
          <button class="btn-mark"><i class="fa fa-check"></i> 标为已确认</button>
        </div>
        ${badge}
      </div>
      ${optsTxt}
      <div style="margin-top:8px;color:#27ae60">答案: <span id="ans-${q.id}">${escapeHtml(q.answer)}</span></div>
      <div class="hint">提示: ${escapeHtml(q.hint||'')}</div>
      ${reasonTxt}
      ${sourceTxt}
    </div>`;
  }
  html += `<div style="text-align:center;margin-top:8px;color:#888">共 ${total} 道题(第 ${currentPage} 页,显示 ${start+1} - ${end})</div>`;
  root.innerHTML = html;
  updatePageInfo(currentPage, Math.max(1, Math.ceil(total/pSize)));
  document.getElementById('exportFilteredJsonBtn').disabled = total===0;
  document.getElementById('exportFilteredXlsxBtn').disabled = total===0;
  document.getElementById('exportFilteredCsvBtn').disabled = total===0;
}

function updatePageInfo(page, totalPages){
  document.getElementById('pageInfo').textContent = `第 ${page} / ${totalPages} 页`;
  document.getElementById('prevPageBtn').disabled = page<=1;
  document.getElementById('nextPageBtn').disabled = page>=totalPages;
}

/* ==================== 编辑功能 ==================== */
let editingId = null;
function openEditModal(id){
  const q = questionBank.find(x=>x.id===id);
  if(!q) return alert('题目未找到');
  editingId = id;
  document.getElementById('editTitleId').textContent = `#${id}`;
  document.getElementById('editType').value = q.type;
  document.getElementById('editQuestion').value = q.question;
  document.getElementById('editOptions').value = q.options.join(' | ');
  document.getElementById('editAnswer').value = q.answer;
  document.getElementById('editHint').value = q.hint || '';
  document.getElementById('editConfidence').value = q.confidence || 'high';
  document.getElementById('editReason').value = q.reason || '';
  document.getElementById('editModalBackdrop').style.display = 'flex';
}
function closeEditModal(){ document.getElementById('editModalBackdrop').style.display = 'none'; editingId = null; }
function saveEditModal(){
  if(editingId===null) return;
  const q = questionBank.find(x=>x.id===editingId);
  if(!q) return alert('题目未找到');
  q.type = document.getElementById('editType').value;
  q.question = String(document.getElementById('editQuestion').value||'').trim();
  q.options = String(document.getElementById('editOptions').value||'').split('|').map(s=>s.trim()).filter(Boolean);
  const rawAns = String(document.getElementById('editAnswer').value||'').trim();
  const parsed = parseAnswer(rawAns, q.options, q.type);
  q.answer = parsed.answer;
  q.confidence = document.getElementById('editConfidence').value || 'high';
  q.reason = String(document.getElementById('editReason').value||'').trim() || '手动修正';
  q.hint = String(document.getElementById('editHint').value||'').trim() || q.hint;
  addLog(`题目 #${q.id} 已保存。`, 'success');
  closeEditModal();
  updatePreview();
  updateStats(questionBank);
}

function markConfirmed(id){
  const q = questionBank.find(x=>x.id===id);
  if(!q) return alert('题目未找到');
  q.confidence = 'high';
  q.reason = (q.reason? q.reason + ' | ':'') + '手动确认';
  addLog(`题目 #${q.id} 标记为已确认。`, 'success');
  updatePreview();
}

/* ==================== 批量功能 ==================== */
function populateBatchList(){
  const list = document.getElementById('batchList');
  list.innerHTML = '';
  const items = questionBank;
  if(items.length===0){ list.innerHTML = '<div class="small" style="padding:8px">当前没有题目。</div>'; return; }
  for(const q of items){
    const div = document.createElement('div');
    div.className = 'batch-item';
    div.innerHTML = `<input type="checkbox" class="batch-chk" data-id="${q.id}">
      <div style="min-width:36px;"><strong>#${q.id}</strong></div>
      <div style="flex:1"><div style="font-weight:600">${escapeHtml(q.question)}</div><div class="small">答案: ${escapeHtml(q.answer)} | 置信度: ${q.confidence}</div></div>
      <div><button class="btn-edit">编辑</button></div>`;
    list.appendChild(div);
  }
}

function openBatchModal(){
  populateBatchList();
  document.getElementById('fixPreview').innerHTML = '<div class="small" style="color:#999">请点击"生成自动修复建议"以查看清洗结果。</div>';
  document.getElementById('batchModalBackdrop').style.display = 'flex';
}
function closeBatchModal(){ document.getElementById('batchModalBackdrop').style.display = 'none'; }

function selectAllBatch(flag){ document.querySelectorAll('#batchList .batch-chk').forEach(cb=>cb.checked = !!flag); }
function batchMarkConfirmed(){
  const chks = Array.from(document.querySelectorAll('#batchList .batch-chk')).filter(c=>c.checked);
  if(chks.length===0) return alert('请先选择要标记的题目。');
  chks.forEach(cb=>{
    const id = Number(cb.getAttribute('data-id'));
    const q = questionBank.find(x=>x.id===id);
    if(q){ q.confidence='high'; q.reason=(q.reason? q.reason+' | ':'')+'批量手动确认'; }
  });
  addLog(`批量标记 ${chks.length} 道题为已确认。`, 'success');
  updatePreview();
}

function batchGenerateFixPreview(){
  const suggestions = generateFixSuggestions();
  const previewEl = document.getElementById('fixPreview');
  previewEl.innerHTML = '';
  if(suggestions.length===0){
    previewEl.innerHTML = '<div class="small" style="color:#999;padding:8px">未生成任何修复建议。</div>';
    return;
  }
  const container = document.createElement('div');
  suggestions.forEach(sug=>{
    const row = document.createElement('div');
    row.className = 'fix-row';
    row.innerHTML = `<div style="display:flex;align-items:flex-start;gap:8px">
      <input type="checkbox" class="fix-chk" data-id="${sug.id}" checked />
      <div style="flex:1">
        <div style="font-weight:700">#${sug.id}</div>
        <div class="small" style="margin-top:6px">原选项: ${escapeHtml(sug.originalOptions.join(' | '))}</div>
        <div class="small" style="margin-top:6px;color:#2d3748">建议选项: ${escapeHtml(sug.cleanedOptions.join(' | '))}</div>
        <div class="small" style="margin-top:6px;color:#27ae60">原答案: ${escapeHtml(sug.originalAnswer)} → 建议答案: ${escapeHtml(sug.suggestedAnswer)}(置信度 ${sug.origConfidence} → ${sug.suggestedConfidence})</div>
        <div class="small" style="margin-top:4px;color:#999">原因: ${escapeHtml(sug.reason)}</div>
      </div>
    </div>`;
    container.appendChild(row);
  });
  previewEl.appendChild(container);
  previewEl._suggestions = suggestions;
  addLog(`生成 ${suggestions.length} 条自动修复建议。`, 'info');
}

function batchApplySelectedFixes(){
  const previewEl = document.getElementById('fixPreview');
  if(!previewEl || !previewEl._suggestions) return alert('请先生成修复建议');
  const suggestions = previewEl._suggestions;
  const checks = Array.from(previewEl.querySelectorAll('.fix-chk')).filter(c=>c.checked).map(c=>Number(c.getAttribute('data-id')));
  if(checks.length===0) return alert('请先选择要应用的修复建议');
  let applied = 0;
  suggestions.forEach(sug=>{
    if(checks.includes(sug.id)){
      const ok = applySuggestion(sug);
      if(ok) applied++;
    }
  });
  addLog(`批量应用修复完成:已应用 ${applied} 条建议。`, 'success');
  updatePreview();
  populateBatchList();
}

function generateFixSuggestions(){
  const suggestions = [];
  for(const q of questionBank){
    if(!q || q.confidence==='high') continue;
    const cleaned = normalizeOptionsForQuestion(q);
    const parsedAfter = parseAnswer(q.answer || '', cleaned, q.type);
    let fallbackParsed = null;
    if((parsedAfter.confidence==='low' || parsedAfter.confidence==='medium') && q.rawRow){
      const combined = q.rawRow.join(' ');
      fallbackParsed = parseAnswer(combined, cleaned, q.type);
    }
    const finalParsed = (fallbackParsed && fallbackParsed.confidence>'low') ? fallbackParsed : parsedAfter;

    const optsChanged = JSON.stringify(cleaned) !== JSON.stringify(q.options.map(o=>String(o)));
    const answerChanged = finalParsed.answer !== q.answer;
    const confImproved = (finalParsed.confidence==='high' && q.confidence!=='high') || (finalParsed.confidence==='medium' && q.confidence==='low');
    if(optsChanged || answerChanged || confImproved){
      suggestions.push({
        id: q.id,
        originalOptions: q.options.slice(),
        cleanedOptions: cleaned,
        originalAnswer: q.answer,
        suggestedAnswer: finalParsed.answer,
        origConfidence: q.confidence,
        suggestedConfidence: finalParsed.confidence,
        reason: finalParsed.reason || '清洗后建议'
      });
    }
  }
  return suggestions;
}

function applySuggestion(sug){
  const q = questionBank.find(x=>x.id===sug.id);
  if(!q) return false;
  q.options = sug.cleanedOptions.slice();
  q.answer = sug.suggestedAnswer;
  q.confidence = sug.suggestedConfidence || 'medium';
  q.reason = (q.reason? q.reason + ' | ':'') + `自动修复(${sug.reason||'清洗'})`;
  addLog(`题目 #${q.id} 应用自动修复: 答案 ${sug.originalAnswer} -> ${sug.suggestedAnswer}, 置信度 ${sug.origConfidence} -> ${sug.suggestedConfidence}`, 'success');
  return true;
}

function cleanOptionText(text){
  if(text===undefined || text===null) return '';
  let s = String(text).trim();
  s = s.replace(/^[\s"']*(?:$|()?[A-Za-z甲乙丙丁一二三四①②③④ⅠⅡⅢⅣ0-90-9]+(?:$|))?[\..、::)\)]*\s*/i, '');
  s = s.replace(/^[\s\-\–\—\u2014\.\:\:\、\)]+/, '').replace(/[\s\.\,\:\:\、\)]+$/,'').trim();
  s = s.replace(/[\s ]+/g, ' ').trim();
  return s;
}

function normalizeOptionsForQuestion(q){
  if(!q || !Array.isArray(q.options)) return [];
  return q.options.map(o=>cleanOptionText(o));
}

/* ==================== 导出功能 ==================== */
function downloadJSON(){
  if(!questionBank.length) return alert('没有可导出的题库');
  const json = JSON.stringify(questionBank, null, 2);
  const blob = new Blob([json], {type:'application/json;charset=utf-8'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href=url; a.download='question_bank.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
  addLog('JSON 文件已开始下载。', 'success');
}

function generateJSFile(questions){
  const jsonText = JSON.stringify(questions, null, 2);
  const safe = jsonText.replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/\r/g,'').replace(/\n/g,'\\n').replace(/<\/script>/gi,'<\\/script>');
  const header = `// 题库数据 - 自动生成\n// 生成时间: ${new Date().toLocaleString()}\n// 题目总数: ${questions.length}\n\n`;
  const restore = `var questionBank = JSON.parse('${safe}');\n\n`;
  const helpers = `questionBank.getStats = function(){ return { judgment: this.filter(q=>q.type==="判断").length, singleChoice: this.filter(q=>q.type==="单选").length, multiChoice: this.filter(q=>q.type==="多选").length, total: this.length }; };\nfunction getRandomQuestion(type=null){ let pool = questionBank; if(type) pool = questionBank.filter(q=>q.type===type); if(pool.length===0) return null; return pool[Math.floor(Math.random()*pool.length)]; }\nfunction findQuestionById(id){ return questionBank.find(q=>q.id===id)||null; }\nfunction checkAnswer(question, userAnswer){ if(!question || userAnswer===undefined || userAnswer===null) return false; const stdUser = String(userAnswer).replace(/[,,\\s]+/g,'').toUpperCase(); const stdCorrect = String(question.answer).replace(/[,,\\s]+/g,'').toUpperCase(); return stdUser===stdCorrect; }\n(function(){ if(typeof global!=='undefined'){ global.questionBank = questionBank; } if(typeof window!=='undefined'){ window.questionBank = questionBank; } if(typeof self!=='undefined'){ self.questionBank = questionBank; } })();\nconsole.log('题库加载完成!');\n`;
  return header + restore + helpers;
}

function downloadJS(){
  if(!questionBank.length) return alert('没有可导出的题库');
  const content = generateJSFile(questionBank);
  const blob = new Blob([content], {type:'application/javascript'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href=url; a.download='question_bank.js'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
  addLog('JS 文件已开始下载。', 'success');
}

function exportFilteredJSON(){
  const arr = getFilteredQuestions();
  const blob = new Blob([JSON.stringify(arr, null, 2)], {type:'application/json;charset=utf-8'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href=url; a.download='filtered_questions.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
  addLog(`导出筛选后的 ${arr.length} 道题(JSON)。`, 'success');
}

function exportFilteredXLSX(){
  const arr = getFilteredQuestions().map(q=>({id:q.id, type:q.type, question:q.question, options:q.options.join(' | '), answer:q.answer, confidence:q.confidence, reason:q.reason, sourceSheet:q.sourceSheet, sourceRow:q.sourceRow}));
  const ws = XLSX.utils.json_to_sheet(arr);
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'questions');
  XLSX.writeFile(wb, 'filtered_questions.xlsx');
  addLog(`导出筛选后的 ${arr.length} 道题(XLSX)。`, 'success');
}

function exportFilteredCSV(){
  const arr = getFilteredQuestions().map(q=>({id:q.id, type:q.type, question:q.question, options:q.options.join(' | '), answer:q.answer, confidence:q.confidence, reason:q.reason}));
  const ws = XLSX.utils.json_to_sheet(arr);
  const csv = XLSX.utils.sheet_to_csv(ws);
  const blob = new Blob([csv], {type:'text/csv;charset=utf-8'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href=url; a.download='filtered_questions.csv'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
  addLog(`导出筛选后的 ${arr.length} 道题(CSV)。`, 'success');
}

function exportAuditLog(){
  const items = Array.from(document.getElementById('logContent').children).map(n=>n.textContent).join('\n');
  const blob = new Blob([items], {type:'text/plain;charset=utf-8'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href = url; a.download = 'audit_log.txt'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
  addLog('审计日志已导出。', 'success');
}

/* ==================== 事件处理 ==================== */
function onFilterChange(){ currentPage = 1; updatePreview(); }
function onPageSizeChange(){ pageSize = Number(document.getElementById('pageSize').value); currentPage = 1; updatePreview(); }
function prevPage(){ if(currentPage>1){ currentPage--; updatePreview(); } }
function nextPage(){ const filtered = getFilteredQuestions(); const totalPages = Math.max(1, Math.ceil(filtered.length/pageSize)); if(currentPage<totalPages){ currentPage++; updatePreview(); } }

/* ==================== 初始化 ==================== */
document.addEventListener('DOMContentLoaded', ()=>{
  const uploadLabel = document.getElementById('uploadLabel');
  const fileInput = document.getElementById('fileInput');
  const tabBtns = document.querySelectorAll('.tab-btn');

  addLog('智能题库转换工具已就绪(增强版)', 'info');

  // 文件上传
  fileInput.addEventListener('change', (e)=>{
    if(e.target.files && e.target.files.length>0){
      const file = e.target.files[0];
      handleFile(file);
      
      if(file.name.match(/\.pdf$/i)){
        window.pdfFile = file;
      }
      
      fileInput.value = '';
    }
  });

  // 标签切换
  tabBtns.forEach(btn => {
    btn.addEventListener('click', () => {
      tabBtns.forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      currentFileType = btn.dataset.type;
      updateUIByFileType();
    });
  });

  // 模板选择
  const templateItems = document.querySelectorAll('.template-item');
  templateItems.forEach(item => {
    item.addEventListener('click', () => {
      templateItems.forEach(i => i.classList.remove('active'));
      item.classList.add('active');
      currentTemplate = item.dataset.template;
      
      if(currentTemplate === 'custom'){
        openTemplateModal();
      }
    });
  });

  // 拖拽上传
  uploadLabel.addEventListener('dragover', (e)=>{ e.preventDefault(); uploadLabel.style.background='#d6eaf8'; });
  uploadLabel.addEventListener('dragleave', ()=>{ uploadLabel.style.background=''; });
  uploadLabel.addEventListener('drop', (e)=>{
    e.preventDefault();
    uploadLabel.style.background='';
    const files = e.dataTransfer && e.dataTransfer.files;
    if(files && files.length>0){
      const file = files[0];
      handleFile(file);
      
      if(file.name.match(/\.pdf$/i)){
        window.pdfFile = file;
      }
    }
  });
  
  // 加载已保存的API密钥
  document.getElementById('apiKeyInput').value = cloudStorage.apiKey;
  
  // 检查URL中的协作参数
  const urlParams = new URLSearchParams(window.location.search);
  const collabData = urlParams.get('collab');
  if(collabData){
    try {
      const data = JSON.parse(atob(collabData));
      questionBank = data.questions;
      addLog(`从协作链接加载了 ${questionBank.length} 道题`, 'success');
      updateStats(questionBank);
      updatePreview();
    } catch(err){
      addLog('协作链接解析失败', 'error');
    }
  }
  
  // 暴露全局函数
  window.processFile = processFile;
  window.downloadJS = downloadJS;
  window.downloadJSON = downloadJSON;
  window.openEditModal = openEditModal;
  window.openBatchModal = openBatchModal;
  window.closeBatchModal = closeBatchModal;
  window.closeEditModal = closeEditModal;
  window.selectAllBatch = selectAllBatch;
  window.batchMarkConfirmed = batchMarkConfirmed;
  window.batchGenerateFixPreview = batchGenerateFixPreview;
  window.batchApplySelectedFixes = batchApplySelectedFixes;
  window.exportFilteredJSON = exportFilteredJSON;
  window.exportFilteredXLSX = exportFilteredXLSX;
  window.exportFilteredCSV = exportFilteredCSV;
  window.prevPage = prevPage;
  window.nextPage = nextPage;
  window.onFilterChange = onFilterChange;
  window.onPageSizeChange = onPageSizeChange;
  window.exportAuditLog = exportAuditLog;
  window.downloadTemplatePDF = downloadTemplatePDF;
  window.saveApiKey = saveApiKey;
  window.exportToAPI = exportToAPI;
  window.importFromAPI = importFromAPI;
  window.openCollaborationModal = openCollaborationModal;
  window.closeCollaborationModal = closeCollaborationModal;
  window.generateCollaborationLink = generateCollaborationLink;
  window.copyCollaborationLink = copyCollaborationLink;
  window.saveToCloud = saveToCloud;
  window.loadFromCloud = loadFromCloud;
  window.syncWithCloud = syncWithCloud;
  window.markConfirmed = markConfirmed;
  window.openTemplateModal = openTemplateModal;
  window.closeTemplateModal = closeTemplateModal;
  window.testTemplate = testTemplate;
  window.saveCustomTemplate = saveCustomTemplate;
});
</script>



免费评分

参与人数 11吾爱币 +17 热心值 +11 收起 理由
mali0509 + 1 + 1 收藏了,感谢
sunlit + 1 + 1 谢谢@Thanks!
winner2014 + 1 + 1 我很赞同!
guo2210 + 1 + 1 我很赞同!
hrh123 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
pjj811885 + 1 + 1 我很赞同!
189678 + 1 + 1 谢谢@Thanks!
笨笨家的唯一 + 1 + 1 我很赞同!
zr2019 + 1 + 1 谢谢@Thanks!
Dongfanglong + 1 + 1 我很赞同!
Fr1day + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
wuxiajian 发表于 2025-12-19 22:51
希望能做一个万能答题神器。
特别是在网页答题中,禁止复制题目类,或者将题目转换为图片类的,都能自动识别题目内容,并自动判定正确答案(最常见的题库答案内容为word文档,相当于是题目复习资料类的)
沙发
fofawb 发表于 2025-12-16 17:04
3#
Sigh9 发表于 2025-12-16 17:18
4#
zt185 发表于 2025-12-16 18:04
很实用转换工具,下载收藏了!
5#
zr2019 发表于 2025-12-16 19:22
谢谢分享原创作品,收藏备用
6#
笨笨家的唯一 发表于 2025-12-16 19:59
感谢楼主分享,以前都是自己手动搞的,有这个小工具会方便很多
7#
189678 发表于 2025-12-16 20:03
希望越做越好,越来越完善
8#
xiangyang0109 发表于 2025-12-16 21:07
这个挺有趣的,下来试试
9#
小锤子起钉儿 发表于 2025-12-16 21:24
不错的分享
10#
dongxu818 发表于 2025-12-16 21:36
先收藏了,谢谢分享!!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-12-25 11:22

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表