Files
yianxue-assistant/离线导出题库.html
2026-10-10 02:03:40 +08:00

93 lines
9.4 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'none'; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'">
<title>离线导出题库</title>
<style>
:root{font-family:system-ui,"Microsoft YaHei",sans-serif;color:#182a3a;background:#f4f7fa;line-height:1.65}*{box-sizing:border-box}body{margin:0;padding:40px 20px}.wrap{max-width:980px;margin:auto}.eyebrow{color:#326b95;font-size:13px;font-weight:700;letter-spacing:.12em}h1{font-size:32px;margin:8px 0 10px}p{margin:8px 0;color:#526577}.card{background:#fff;border:1px solid #dce5ed;border-radius:15px;margin:25px 0;padding:26px}.drop{display:block;border:2px dashed #a5becf;border-radius:12px;background:#f8fbfd;padding:30px;text-align:center;cursor:pointer}.drop.drag{border-color:#267eae;background:#eaf5fc}.drop strong{display:block;font-size:19px}.drop small{display:block;color:#687c8a;margin-top:4px}input[type=file]{max-width:100%;margin:18px 0 4px}button{font:inherit;font-weight:600;border:1px solid #c3d2dd;background:#fff;color:#22455f;padding:9px 17px;border-radius:8px;cursor:pointer}button.primary{background:#236d98;color:#fff;border-color:#236d98}button:disabled{opacity:.4;cursor:default}button:hover:not(:disabled){filter:brightness(.96)}.buttons{display:flex;gap:10px;flex-wrap:wrap;margin-top:20px}.status{padding:13px 16px;border-radius:9px;background:#edf3f7;white-space:pre-wrap;overflow-wrap:anywhere}.status.success{background:#e9f5ef;color:#17613e}.status.warning{background:#fff4dc;color:#755414}.status.error{background:#fff0ee;color:#a13228}.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:20px 0}.stat{padding:15px;background:#f5f8fa;border-radius:10px}.stat b{display:block;font-size:27px;line-height:1.3}.stat span{color:#637785;font-size:13px}h2{font-size:21px;margin:0 0 12px}h3{font-size:16px;margin:0 0 8px}.preview{padding:18px 0;border-top:1px solid #e1e8ee}.preview p{color:#2f4354;margin:4px 0;white-space:pre-wrap;overflow-wrap:anywhere}.preview .answer{color:#17613e;font-weight:600}.meta{font-size:13px;overflow-wrap:anywhere}.hidden{display:none}.footer{font-size:13px;color:#687b89}@media(max-width:600px){body{padding:22px 12px}h1{font-size:26px}.card{padding:18px}.stats{grid-template-columns:repeat(2,1fr)}.drop{padding:22px 12px}}
</style>
</head>
<body>
<main class="wrap">
<div class="eyebrow">LOCAL QUESTION BANK EXPORTER</div>
<h1>从已保存的练习网页导出题库</h1>
<p>先解压下载的压缩包,再选择其中的 HTML 文件。隐藏的参考答案也能读取,无需逐题点击“查看答案”。</p>
<section class="card">
<label id="drop" class="drop" for="file"><strong>选择或拖入练习网页</strong><small>支持 .html / .htm;所有处理都在本机完成</small><input id="file" type="file" accept=".html,.htm,text/html"></label>
<p class="meta">保留网页旁的资源文件夹,方便后续查看图片。此工具会导出图片链接,不会加载或上传图片。</p>
<div id="status" class="status" role="status" aria-live="polite">等待选择文件。</div>
<div class="buttons"><button id="csv" class="primary" disabled>导出 CSV(Excel)</button><button id="json" disabled>导出 JSON</button><button id="txt" disabled>导出 TXT</button></div>
</section>
<section id="result" class="card hidden">
<h2 id="title"></h2>
<div class="stats"><div class="stat"><b id="total">0</b><span>已提取题目</span></div><div class="stat"><b id="answered">0</b><span>包含参考答案</span></div><div class="stat"><b id="expected">—</b><span>网页答题卡题数</span></div><div class="stat"><b id="resolved">0</b><span>包含文字解析</span></div></div>
<p id="types"></p><p id="source" class="meta"></p>
<h2 style="margin-top:24px">题目预览</h2><p class="meta">展示前 3 题,导出文件包含全部已提取题目。</p><div id="preview"></div>
</section>
<p class="footer">CSV 使用 UTF-8 编码并带中文 BOM;JSON 额外保留题目和解析 HTML、原始图片路径。若 HTML 本身没有参考答案,本工具会标注缺失。</p>
</main>
<script src="question-bank-core.js"></script>
<script>
(function () {
'use strict';
const byId = function (id) { return document.getElementById(id); };
const buttons = ['csv', 'json', 'txt'].map(byId);
let bank = null;
let loadToken = 0;
function status(message, level) { byId('status').textContent = message; byId('status').className = 'status ' + (level || ''); }
function paragraph(parent, text, className) { const p = document.createElement('p'); p.textContent = text; if (className) p.className = className; parent.appendChild(p); }
function render() {
byId('result').classList.remove('hidden');
byId('title').textContent = bank.title;
byId('total').textContent = bank.total;
byId('answered').textContent = bank.answered;
byId('expected').textContent = bank.expectedTotal === null ? '未知' : bank.expectedTotal;
byId('resolved').textContent = bank.questions.filter(function (q) { return q.explanation; }).length;
byId('types').textContent = Object.entries(QuestionBankCore.typeCounts(bank)).map(function (entry) { return entry[0] + ' ' + entry[1] + ' 题'; }).join(' · ');
byId('source').textContent = '原始网址:' + (bank.sourceUrl || '网页未保存原始网址;图片相对路径将保留原值');
const preview = byId('preview'); preview.replaceChildren();
bank.questions.slice(0, 3).forEach(function (q) {
const article = document.createElement('article'); article.className = 'preview';
const heading = document.createElement('h3'); heading.textContent = q.number + '. [' + q.type + '] ' + q.question; article.appendChild(heading);
q.options.forEach(function (o) { paragraph(article, o.label + '. ' + o.text + (o.images.length ? ' [图片链接已保留]' : '')); });
paragraph(article, '参考答案:' + (q.answer || '缺失') + (q.answerText ? '(' + q.answerText + ')' : ''), 'answer');
paragraph(article, '解析:' + (q.explanation || '网页未提供'));
preview.appendChild(article);
});
}
async function load(file) {
const current = ++loadToken;
bank = null; buttons.forEach(function (button) { button.disabled = true; }); byId('result').classList.add('hidden');
if (!file) { status('等待选择文件。'); return; }
if (!/\.html?$/i.test(file.name)) { status('请选择已解压的 .html 或 .htm 网页文件。', 'error'); return; }
status('正在读取 ' + file.name + ' …');
try {
const html = await file.text();
if (current !== loadToken) return;
if (!window.QuestionBankCore) throw new Error('找不到 question-bank-core.js,请将它与本工具放在同一文件夹。');
bank = QuestionBankCore.extract(QuestionBankCore.parseHTML(html));
if (!bank.total) { status('没有找到练习题目。请保存已进入“练习”的完整网页后重新导入。', 'error'); bank = null; return; }
render(); buttons.forEach(function (button) { button.disabled = false; });
const issues = [];
if (bank.expectedTotal === null) issues.push('网页没有答题卡,只能核对当前 HTML 中的题目。');
else if (bank.expectedTotal !== bank.total) issues.push('答题卡为 ' + bank.expectedTotal + ' 题,当前仅提取 ' + bank.total + ' 题。');
if (bank.missingAnswers.length) issues.push('缺失答案的题号:' + bank.missingAnswers.join('、') + '。');
if (!bank.questions.some(function (q) { return q.explanation; })) issues.push('网页中的文字解析为空,导出时会保留为空。');
status('已读取 ' + bank.total + ' 题,其中 ' + bank.answered + ' 题包含参考答案。' + (issues.length ? '\n' + issues.join('\n') : '\n题目与参考答案完整。'), bank.complete ? 'success' : 'warning');
} catch (error) { if (current === loadToken) { bank = null; status('读取失败:' + error.message, 'error'); } }
}
byId('file').addEventListener('change', function (event) { load(event.target.files[0]); });
const drop = byId('drop');
['dragenter', 'dragover'].forEach(function (name) { drop.addEventListener(name, function (event) { event.preventDefault(); drop.classList.add('drag'); }); });
['dragleave', 'drop'].forEach(function (name) { drop.addEventListener(name, function (event) { event.preventDefault(); drop.classList.remove('drag'); }); });
drop.addEventListener('drop', function (event) { load(event.dataTransfer.files[0]); });
buttons.forEach(function (button) { button.addEventListener('click', function () { if (!bank) return; try { const filename = QuestionBankCore.download(bank, button.id); status('已发起下载:' + filename + '\n如未看到文件,请检查浏览器下载列表。', 'success'); } catch (error) { status('导出失败:' + error.message, 'error'); } }); });
window.addEventListener('dragover', function (event) { event.preventDefault(); });
window.addEventListener('drop', function (event) { event.preventDefault(); });
})();
</script>
</body>
</html>