一、环境要求

电脑、Edge浏览器

二、安装浏览器插件

  • 安装“篡改猴(原tampermonkey)”,安装链接(从Edge插件商店下载):直达链接
  • 点击获取,再点击添加扩展

    三、设置浏览器

  • 在搜索框输入edge://extensions/并回车打开扩展设置页面
  • 打开浏览器开发者模式
  • 点击插件详细信息
  • 勾选允许用户脚本

    四、安装AI解题脚本

  • 点击右上角拼图状“扩展”图形
  • 点击“篡改猴”插件
  • 点击添加新脚本

    将默认的代码全部删除,并且粘贴我写的代码,最后按ctrl+S保存就大功告成了

    五、学习通网页版地址

    点击链接直达:链接

    六、其他

    我的代码(点击右上角copy按钮可直接复制):

    // ==UserScript==
    // @name         学习通考试 · DeepSeek AI 解题助手
    // @namespace    cx-deepseek-helper
    // @version      1.6.0
    // @description  提取学习通考试题目,调用 DeepSeek API 进行 AI 解析,结果浮窗展示
    // @match        *://*.chaoxing.com/*
    // @match        *://*.xuexitong.com/*
    // @grant        GM_xmlhttpRequest
    // @grant        GM_addStyle
    // @grant        GM_getValue
    // @grant        GM_setValue
    // @connect      api.deepseek.com
    // @run-at       document-end
    // ==/UserScript==
    
    (function () {
    'use strict';
    
    const CONFIG = {
      apiKey: GM_getValue('ds_api_key', ''),
      model: 'deepseek-v4-pro',
      maxTokens: 4096,
      systemPrompt: `你是一个专业的考试解题助手。
    用户会给你提供题目类型、题干和选项(如有)。
    请按以下格式回答:
    【答案】直接给出答案(单选给选项字母,多选给多个字母,判断题给"正确"或"错误",填空/简答直接给答案文字)
    【解析】用2-4句话解释理由,要简洁准确。`,
    };
    
    GM_addStyle(`
      #cx-ds-panel {
        position: fixed; top: 60px; right: 20px; z-index: 2147483647;
        width: 460px; max-height: 88vh;
        background: #fff; border-radius: 12px;
        box-shadow: 0 8px 32px rgba(0,0,0,.18);
        font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
        font-size: 13px; overflow: hidden;
        display: flex; flex-direction: column;
        pointer-events: auto !important;
      }
      .ds-header {
        background: linear-gradient(135deg,#1a73e8,#0d47a1);
        color:#fff; padding:10px 14px;
        display:flex; align-items:center; justify-content:space-between;
        cursor:move; user-select:none; flex-shrink:0;
      }
      .ds-header .title { font-weight:600; font-size:14px; }
      .ds-header .ds-btns { display:flex; gap:5px; flex-wrap:wrap; }
      .ds-header button {
        background:rgba(255,255,255,.2); border:none; color:#fff;
        border-radius:6px; padding:4px 10px; cursor:pointer; font-size:11px;
        pointer-events: auto !important;
      }
      .ds-header button:hover { background:rgba(255,255,255,.38); }
      .ds-settings {
        padding:8px 12px; background:#f5f7fa;
        border-bottom:1px solid #e8eaed; flex-shrink:0;
      }
      .ds-settings input {
        width:100%; box-sizing:border-box; padding:6px 8px;
        border:1px solid #ccc; border-radius:6px;
        font-size:12px; margin-top:3px;
      }
      .ds-settings label { font-size:12px; color:#555; }
      #cx-ds-progress-wrap { height:3px; background:#e8eaed; flex-shrink:0; }
      #cx-ds-progress-bar {
        height:100%; width:0%;
        background:linear-gradient(90deg,#1a73e8,#34a853);
        transition:width .4s ease;
      }
      #cx-ds-body { overflow-y:auto; padding:12px; flex:1; }
      .ds-q-card {
        border:1px solid #dce3f0; border-radius:10px;
        margin-bottom:16px; overflow:hidden;
        box-shadow:0 2px 8px rgba(26,115,232,.06);
      }
      .ds-q-head {
        background:linear-gradient(90deg,#e8f0fe,#f0f4ff);
        padding:8px 12px;
        display:flex; align-items:center; justify-content:space-between;
        border-bottom:1px solid #dce3f0;
      }
      .ds-q-num { font-weight:700; color:#1a73e8; font-size:13px; }
      .ds-q-type {
        font-size:11px; font-weight:700;
        background:#1a73e8; color:#fff;
        border-radius:4px; padding:2px 8px;
      }
      .ds-q-title-block {
        padding:10px 14px;
        border-bottom:1px dashed #e0e7f5;
        background:#fff;
      }
      .ds-block-label {
        font-size:10px; font-weight:700; color:#888;
        letter-spacing:.5px; margin-bottom:5px;
        display:flex; align-items:center; gap:4px;
      }
      .ds-q-title-text {
        color:#111; font-size:13px; line-height:1.8;
        white-space:pre-wrap; word-break:break-word;
        background:#f7f9ff; border:1px solid #dce3f0;
        border-radius:6px; padding:8px 10px;
        max-height:200px; overflow-y:auto;
      }
      .ds-q-options-block {
        padding:8px 14px 10px;
        background:#f8faff;
        border-bottom:1px dashed #e0e7f5;
      }
      .ds-q-option-row {
        display:flex; gap:6px; align-items:baseline;
        padding:3px 6px; border-radius:5px;
        font-size:12px; line-height:1.7; color:#333;
        margin-bottom:1px;
      }
      .ds-q-option-row:hover { background:#e8f0fe; }
      .ds-q-option-letter { font-weight:700; color:#1a73e8; min-width:18px; flex-shrink:0; }
      .ds-q-option-text { word-break:break-word; white-space:pre-wrap; }
      .ds-no-option { font-size:11px; color:#aaa; padding:4px 14px 8px; font-style:italic; }
      .ds-status-bar {
        display:flex; align-items:center; gap:8px;
        padding:8px 12px; font-size:12px;
        background:#fafafa; border-top:1px solid #eee;
      }
      .ds-status-bar.waiting { color:#999; }
      .ds-status-bar.loading { color:#1a73e8; background:rgba(232,240,254,.4); }
      .ds-status-bar.done    { color:#00897b; background:rgba(246,255,248,.6); }
      .ds-status-bar.error   { color:#d32f2f; background:rgba(255,240,240,.6); }
      .ds-q-answer-wrap { padding:10px 14px; background:#fff; }
      .ds-answer-box {
        background:#f6fff8; border:1px solid #b2dfdb;
        border-radius:7px; padding:8px 10px; margin-bottom:8px;
      }
      .ds-analysis-box {
        background:#f0f4ff; border:1px solid #c5cae9;
        border-radius:7px; padding:8px 10px;
      }
      .ds-answer-label-tag {
        display:inline-block; font-size:11px; font-weight:700;
        border-radius:4px; padding:1px 7px; margin-bottom:5px;
      }
      .ds-answer-label-tag.answer   { background:#00897b; color:#fff; }
      .ds-answer-label-tag.analysis { background:#3949ab; color:#fff; }
      .ds-answer-content { color:#1a1a1a; line-height:1.7; font-size:13px; }
      .ds-spinner {
        width:14px; height:14px; border:2px solid #ccc;
        border-top-color:#1a73e8; border-radius:50%;
        animation:ds-spin .7s linear infinite; flex-shrink:0;
      }
      @keyframes ds-spin { to{transform:rotate(360deg);} }
      #cx-ds-panel.minimized .ds-settings,
      #cx-ds-panel.minimized #cx-ds-body,
      #cx-ds-panel.minimized #cx-ds-progress-wrap { display:none; }
      .ds-count-badge {
        background:#ff5722; color:#fff; border-radius:10px;
        padding:1px 7px; font-size:11px; margin-left:6px;
      }
      /* 复制成功提示 toast */
      #cx-ds-toast {
        position:fixed; bottom:32px; left:50%; transform:translateX(-50%) translateY(20px);
        background:#323232; color:#fff; border-radius:8px;
        padding:8px 20px; font-size:13px; z-index:2147483648;
        opacity:0; transition:opacity .3s, transform .3s;
        pointer-events:none;
      }
      #cx-ds-toast.show { opacity:1; transform:translateX(-50%) translateY(0); }
    `);
    
    // ── 工具函数 ──────────────────────────────────────
    function escHtml(s) {
      return String(s)
        .replace(/&/g,'&amp;').replace(/</g,'&lt;')
        .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }
    
    function showToast(msg) {
      let t = document.getElementById('cx-ds-toast');
      if (!t) {
        t = document.createElement('div');
        t.id = 'cx-ds-toast';
        document.documentElement.appendChild(t);
      }
      t.textContent = msg;
      t.classList.add('show');
      clearTimeout(t._timer);
      t._timer = setTimeout(() => t.classList.remove('show'), 2000);
    }
    
    function cleanText(node) {
      if (!node) return '';
      const clone = node.cloneNode(true);
      clone.querySelectorAll('.colorShallow, input, script, style').forEach(el => el.remove());
      clone.querySelectorAll('br').forEach(el => el.replaceWith('\n'));
      return (clone.textContent || '').replace(/[ \t]{2,}/g,' ').replace(/\n{3,}/g,'\n\n').trim();
    }
    
    const TYPE_NAME = {
      '0':'单选题','1':'多选题','2':'填空题','3':'判断题',
      '4':'简答题','5':'名词解释','6':'论述题','7':'计算题',
      '13':'排序题','15':'阅读理解','20':'共用选项题','999':'未知类型'
    };
    
    const TYPE_MAP = new Map([
      ['单选题','0'],['A1型题','0'],['A1A2型题','0'],['A2','0'],['A1','0'],
      ['多选题','1'],['X型题','1'],
      ['填空题','2'],['判断题','3'],['简答题','4'],
      ['名词解释','5'],['论述题','6'],['计算题','7'],['排序题','13'],
      ['阅读理解','15'],
      ['B型题','20'],['B1型题','20'],['B1','20'],['共用选项题','20'],
    ]);
    
    function resolveTypeCode(raw) {
      if (!raw) return '999';
      const s = raw.replace(/[\[\]【】()()\s]/g,'').trim();
      if (TYPE_MAP.has(s)) return TYPE_MAP.get(s);
      for (const [k,v] of TYPE_MAP) { if (s.includes(k)||k.includes(s)) return v; }
      if (/^\d+$/.test(s)) return s;
      return '999';
    }
    
    // ── 题目提取 ──────────────────────────────────────
    function extractQuestions(doc) {
      if (!doc) return [];
      const questions = [];
    
      let containers = [...doc.querySelectorAll('.questionLi')];
      let mode = 'ks';
      if (!containers.length) { containers = [...doc.querySelectorAll('.noSplitBx')]; mode = 'ks'; }
      if (!containers.length) { containers = [...doc.querySelectorAll('.TiMu')]; mode = 'zj'; }
    
      containers.forEach((el, idx) => {
        let title = '', rawType = '', options = [];
    
        try {
          if (mode === 'zj') {
            title   = cleanText(el.querySelector('.fontLabel'));
            rawType = cleanText(el.querySelector('.newZy_TItle'));
            el.querySelectorAll('[class*="before-after"]').forEach(o => {
              const t = cleanText(o.querySelector('.fl.after') || o);
              if (t) options.push(t);
            });
    
          } else {
            const shallow = el.querySelector('.colorShallow');
            if (shallow) rawType = shallow.textContent.replace(/[()()\[\]【】]/g,'').trim();
            if (!rawType) rawType = el.getAttribute('typename') || '';
            if (!rawType) {
              const ti = el.querySelector('input[id^="typeName"]');
              if (ti) rawType = ti.value;
            }
            const typeValInput = el.querySelector('input[name^="type"]');
            const numericType = typeValInput ? typeValInput.value : '';
            const typeCode = resolveTypeCode(rawType) !== '999'
              ? resolveTypeCode(rawType)
              : (numericType || '999');
    
            const h3 = el.querySelector('h3.mark_name') || el.querySelector('h3');
            if (h3) title = cleanText(h3);
            title = title.replace(/^\d+\.\s*/,'').replace(/^【.*?】\s*/,'').replace(/\s*(\d+\.\d+分)$/,'').trim();
    
            const numCode = parseInt(typeCode);
    
            if (numCode === 20) {
              const sharedOpts = [];
              el.querySelectorAll('.stem_answer .clearfix').forEach(row => {
                const text = row.querySelector('.p_wid805, .fr')
                  ? (row.querySelector('.p_wid805, .fr').textContent||'').trim() : '';
                if (text) sharedOpts.push(text);
              });
              el.querySelectorAll('.B-answer-ct').forEach((sub, si) => {
                const subTitle = sub.querySelector('.B-tit')
                  ? sub.querySelector('.B-tit').textContent.trim() : ('子题'+(si+1));
                questions.push({
                  index: questions.length + 1,
                  type: '20', typeName: '共用选项题',
                  title: title ? (title + '\n' + subTitle) : subTitle,
                  options: [...sharedOpts], rawType,
                });
              });
              return;
    
            } else if (numCode === 15) {
              el.querySelectorAll('.reading_answer').forEach((sub, si) => {
                const titEl  = sub.querySelector('.reader_answer_tit');
                const subTit = titEl ? titEl.textContent.replace(/\(.*?\)/g,'').trim() : ('子题'+(si+1));
                const typeEl = sub.querySelector('.read_type');
                const subType = typeEl ? typeEl.textContent.replace(/[()()]/g,'').trim() : '简答题';
                const subCode = resolveTypeCode(subType);
                questions.push({
                  index: questions.length + 1,
                  type: subCode, typeName: TYPE_NAME[subCode] || subType,
                  title: subTit, options: [], rawType: subType,
                });
              });
              return;
    
            } else {
              el.querySelectorAll('.answerBg').forEach(opt => {
                const ap = opt.querySelector('.answer_p');
                const t  = (ap ? ap.textContent : opt.textContent).trim();
                if (t) options.push(t);
              });
            }
    
            if (!title) return;
            questions.push({
              index: questions.length + 1,
              type: typeCode, typeName: TYPE_NAME[typeCode] || '未知类型',
              title, options, rawType,
            });
            return;
          }
    
          // 章节测试通用 push
          if (!title) return;
          const typeCode = resolveTypeCode(rawType);
          questions.push({
            index: questions.length + 1,
            type: typeCode, typeName: TYPE_NAME[typeCode] || '未知类型',
            title, options, rawType,
          });
    
        } catch(e) { /* 静默忽略单题错误 */ }
      });
    
      questions.forEach((q,i) => q.index = i + 1);
      return questions;
    }
    
    function getAllQuestions() {
      let qs = extractQuestions(document);
      if (qs.length) return qs;
      for (const f of document.querySelectorAll('iframe')) {
        try {
          const fd = f.contentDocument || f.contentWindow?.document;
          if (!fd) continue;
          const r = extractQuestions(fd);
          if (r.length) return r;
        } catch(e) {}
      }
      return [];
    }
    
    // ── 题目卡片渲染 ───────────────────────────────────
    function renderCards(panel, questions, mode) {
      const body = panel.querySelector('#cx-ds-body');
      body.innerHTML = '';
      questions.forEach(q => {
        const card = document.createElement('div');
        card.className = 'ds-q-card';
        card.id = 'ds-card-' + q.index;
    
        let optBlock = '';
        if (q.options.length) {
          const rows = q.options.map((o,i) =>
            '<div class="ds-q-option-row">'
            + '<span class="ds-q-option-letter">' + String.fromCharCode(65+i) + '.</span>'
            + '<span class="ds-q-option-text">' + escHtml(o) + '</span>'
            + '</div>'
          ).join('');
          optBlock = '<div class="ds-q-options-block"><div class="ds-block-label">📋 选项(共'+q.options.length+'项)</div>'+rows+'</div>';
        } else {
          optBlock = '<div class="ds-no-option">(无选项 — ' + q.typeName + ')</div>';
        }
    
        const statusHtml = mode === 'solving'
          ? '<div class="ds-status-bar waiting" id="ds-status-'+q.index+'">⏳ 等待 AI 解析…</div>'
          : '<div class="ds-status-bar done">✅ 已提取</div>';
    
        const answerBlock = mode === 'solving'
          ? '<div class="ds-q-answer-wrap" id="ds-answer-'+q.index+'"></div>' : '';
    
        card.innerHTML =
          '<div class="ds-q-head">'
          + '<span class="ds-q-num">第 ' + q.index + ' 题</span>'
          + '<span class="ds-q-type">' + q.typeName + '</span>'
          + '</div>'
          + '<div class="ds-q-title-block">'
          + '<div class="ds-block-label">📌 题干</div>'
          + '<div class="ds-q-title-text">' + escHtml(q.title) + '</div>'
          + '</div>'
          + optBlock + statusHtml + answerBlock;
    
        body.appendChild(card);
      });
    }
    
    // ── 复制题目为纯文本 ──────────────────────────────
    function copyQuestionsText(questions) {
      const lines = questions.map(q => {
        let s = '第' + q.index + '题【' + q.typeName + '】\n' + q.title;
        if (q.options.length) {
          s += '\n' + q.options.map((o,i) => String.fromCharCode(65+i) + '. ' + o).join('\n');
        }
        return s;
      });
      const text = lines.join('\n\n');
      try {
        navigator.clipboard.writeText(text).then(
          () => showToast('✅ 已复制 ' + questions.length + ' 道题目到剪贴板'),
          () => fallbackCopy(text)
        );
      } catch(e) { fallbackCopy(text); }
    }
    
    function fallbackCopy(text) {
      const ta = document.createElement('textarea');
      ta.value = text;
      ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0;';
      document.body.appendChild(ta);
      ta.select();
      try {
        document.execCommand('copy');
        showToast('✅ 已复制题目到剪贴板');
      } catch(e) {
        showToast('❌ 复制失败,请手动复制');
      }
      document.body.removeChild(ta);
    }
    
    // ── DeepSeek API(非流式)────────────────────────
    function buildPrompt(q) {
      let t = '题目类型:' + q.typeName + '\n题干:' + q.title;
      if (q.options.length)
        t += '\n选项:\n' + q.options.map((o,i) => '  ' + String.fromCharCode(65+i) + '. ' + o).join('\n');
      return t;
    }
    
    function callDeepSeek(prompt, onDone, onError) {
      if (!CONFIG.apiKey) { onError('未配置 API Key,请在面板顶部填写'); return; }
      GM_xmlhttpRequest({
        method: 'POST',
        url: 'https://api.deepseek.com/chat/completions',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + CONFIG.apiKey,
          'Accept': 'application/json',
        },
        data: JSON.stringify({
          model: CONFIG.model,
          max_tokens: CONFIG.maxTokens,
          stream: false,
          messages: [
            { role: 'system', content: CONFIG.systemPrompt },
            { role: 'user',   content: prompt },
          ],
        }),
        timeout: 60000,
        onload(res) {
          try {
            if (res.status !== 200) { onError('HTTP ' + res.status + ':' + res.responseText.slice(0,200)); return; }
            const json = JSON.parse(res.responseText);
            const text = json.choices?.[0]?.message?.content || '';
            if (!text) { onError('API 返回内容为空'); return; }
            onDone(text);
          } catch(e) { onError('解析响应失败:' + e.message); }
        },
        onerror(e)  { onError('网络错误:' + (e.statusText || JSON.stringify(e))); },
        ontimeout() { onError('请求超时(60s),请检查网络或 API Key 余额'); },
      });
    }
    
    function formatBoxes(text) {
      if (!text) return '<div style="color:#aaa;padding:8px">(无内容)</div>';
      const am = text.match(/【答案】([\s\S]*?)(?=【解析】|$)/);
      const sm = text.match(/【解析】([\s\S]*?)$/);
      if (!am && !sm) {
        return '<div style="white-space:pre-wrap;padding:8px;font-size:13px;line-height:1.7">'
          + escHtml(text) + '</div>';
      }
      let h = '';
      if (am) h += '<div class="ds-answer-box"><span class="ds-answer-label-tag answer">📌 答案</span>'
        + '<div class="ds-answer-content">' + escHtml(am[1].trim()).replace(/\n/g,'<br>') + '</div></div>';
      if (sm) h += '<div class="ds-analysis-box"><span class="ds-answer-label-tag analysis">💡 解析</span>'
        + '<div class="ds-answer-content">' + escHtml(sm[1].trim()).replace(/\n/g,'<br>') + '</div></div>';
      return h;
    }
    
    // ── 解题流程 ──────────────────────────────────────
    function startSolving(panel, questions) {
      const bar = panel.querySelector('#cx-ds-progress-bar');
      const count = panel.querySelector('#cx-ds-count');
      count.textContent = questions.length;
      count.style.display = 'inline-block';
    
      renderCards(panel, questions, 'solving');
    
      (async () => {
        for (let i = 0; i < questions.length; i++) {
          const q      = questions[i];
          const status = document.getElementById('ds-status-' + q.index);
          const wrap   = document.getElementById('ds-answer-' + q.index);
          if (!wrap) continue;
    
          if (status) {
            status.className = 'ds-status-bar loading';
            status.innerHTML = '<div class="ds-spinner"></div> AI 正在思考第 ' + q.index + ' 题…';
          }
          document.getElementById('ds-card-' + q.index)
            ?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    
          wrap.innerHTML = '<div style="display:flex;align-items:center;gap:8px;padding:10px 12px;color:#1a73e8;font-size:12px;">'
            + '<div class="ds-spinner"></div>正在请求 DeepSeek API…</div>';
    
          await new Promise(resolve => {
            callDeepSeek(
              buildPrompt(q),
              (final) => {
                wrap.innerHTML = '<div class="ds-block-label" style="padding:6px 0 4px">🤖 AI 解答</div>'
                  + formatBoxes(final);
                if (status) { status.className = 'ds-status-bar done'; status.textContent = '✅ 解析完成'; }
                bar.style.width = ((i + 1) / questions.length * 100) + '%';
                resolve();
              },
              (err) => {
                wrap.innerHTML = '<div class="ds-status-bar error" style="padding:10px 12px">❌ ' + escHtml(err) + '</div>';
                if (status) { status.className = 'ds-status-bar error'; status.textContent = '❌ 失败'; }
                bar.style.width = ((i + 1) / questions.length * 100) + '%';
                resolve();
              }
            );
          });
    
          await new Promise(r => setTimeout(r, 500));
        }
        bar.style.width = '100%';
        showToast('✅ 全部 ' + questions.length + ' 题解析完成');
      })();
    }
    
    // ── 面板创建 ──────────────────────────────────────
    function createPanel() {
      if (document.getElementById('cx-ds-panel')) return;
    
      const panel = document.createElement('div');
      panel.id = 'cx-ds-panel';
      panel.innerHTML =
        '<div class="ds-header" id="cx-ds-drag">'
        + '<span class="title">🤖解析(QingYi♥)'
        + '<span id="cx-ds-count" class="ds-count-badge" style="display:none">0</span>'
        + '</span>'
        + '<div class="ds-btns">'
        + '<button id="cx-ds-copy"  type="button">📄 复制题目</button>'
        + '<button id="cx-ds-start" type="button">🚀 开始解析</button>'
        + '<button id="cx-ds-clear" type="button">🗑 清空</button>'
        + '<button id="cx-ds-min"   type="button">—</button>'
        + '</div></div>'
        + '<div class="ds-settings">'
        + '<label>DeepSeek API Key</label>'
        + '<input id="cx-ds-apikey" type="password" placeholder="sk-..." value="' + escHtml(CONFIG.apiKey) + '" autocomplete="off"/>'
        + '</div>'
        + '<div id="cx-ds-progress-wrap"><div id="cx-ds-progress-bar"></div></div>'
        + '<div id="cx-ds-body">'
        + '<div style="color:#999;text-align:center;padding:24px 0;font-size:12px;">'
        + '点「📄 复制题目」可将所有题目复制到剪贴板<br>点「🚀 开始解析」自动提取题目并调用 AI 解析'
        + '</div></div>';
    
      document.documentElement.appendChild(panel);
    
      panel.querySelector('#cx-ds-apikey').addEventListener('input', e => {
        CONFIG.apiKey = e.target.value.trim();
        GM_setValue('ds_api_key', CONFIG.apiKey);
      });
    
      let minimized = false;
      panel.querySelector('#cx-ds-min').addEventListener('click', () => {
        minimized = !minimized;
        panel.classList.toggle('minimized', minimized);
        panel.querySelector('#cx-ds-min').textContent = minimized ? '□' : '—';
      });
    
      panel.querySelector('#cx-ds-clear').addEventListener('click', () => {
        panel._qs = null;
        panel.querySelector('#cx-ds-body').innerHTML =
          '<div style="color:#999;text-align:center;padding:24px 0;font-size:12px;">已清空</div>';
        panel.querySelector('#cx-ds-count').style.display = 'none';
        panel.querySelector('#cx-ds-progress-bar').style.width = '0%';
      });
    
      // ★ 复制题目
      panel.querySelector('#cx-ds-copy').addEventListener('click', () => {
        const qs = panel._qs && panel._qs.length ? panel._qs : getAllQuestions();
        if (!qs.length) { showToast('❌ 未检测到题目'); return; }
        panel._qs = qs;
        copyQuestionsText(qs);
      });
    
      // ★ 开始解析(自动提取 + 解析)
      panel.querySelector('#cx-ds-start').addEventListener('click', () => {
        if (!CONFIG.apiKey) { alert('请先填写 DeepSeek API Key!'); return; }
        const qs = getAllQuestions();
        if (!qs.length) {
          alert('未检测到题目,请确认已进入考试/作业/章节测试页面。');
          return;
        }
        panel._qs = qs;
        startSolving(panel, qs);
      });
    
      makeDraggable(panel, panel.querySelector('#cx-ds-drag'));
    }
    
    function makeDraggable(el, handle) {
      let sx, sy, il, it;
      handle.addEventListener('mousedown', e => {
        if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return;
        sx = e.clientX; sy = e.clientY;
        const r = el.getBoundingClientRect(); il = r.left; it = r.top;
        el.style.right = 'auto'; el.style.left = il + 'px'; el.style.top = it + 'px';
        const mv = ev => { el.style.left=(il+ev.clientX-sx)+'px'; el.style.top=(it+ev.clientY-sy)+'px'; };
        const up = ()  => { document.removeEventListener('mousemove',mv); document.removeEventListener('mouseup',up); };
        document.addEventListener('mousemove', mv);
        document.addEventListener('mouseup', up);
        e.preventDefault();
      });
    }
    
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', createPanel);
    } else {
      createPanel();
    }
    
    })();
    

添加新评论

赞助商