// ==UserScript== // @name 21tb 题库下载(自定义列顺序版) // @namespace http://tampermonkey.net/ // @version 2.6 // @description 按指定列顺序导出:试题类型、试题题目、参考答案、A-G选项、章节、解析 // @match *://sxjtzx.21tb.com/*newExercise.fullExerciseTemp.do* // @run-at document-end // @grant none // ==/UserScript== (function () { 'use strict'; // 全局状态管理 const state = { isLoading: false, xlsxLoaded: false, fileSaverLoaded: false }; // 备用CDN地址列表 const CDN_URLS = { xlsx: [ 'https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js', 'https://unpkg.com/xlsx@0.18.5/dist/xlsx.full.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js' ], fileSaver: [ 'https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js', 'https://unpkg.com/file-saver@2.0.5/dist/FileSaver.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js' ] }; // 创建状态提示元素 function createStatusIndicator() { const indicator = document.createElement('div'); Object.assign(indicator.style, { position: 'fixed', right: '20px', bottom: '140px', padding: '8px 12px', background: 'rgba(0, 0, 0, 0.7)', color: '#fff', borderRadius: '4px', fontSize: '12px', zIndex: 2147483647, display: 'none', maxWidth: '250px', wordWrap: 'break-word' }); indicator.id = 'download-status'; document.body.appendChild(indicator); return indicator; } // 显示状态消息 function showStatus(message) { const indicator = document.getElementById('download-status') || createStatusIndicator(); indicator.textContent = message; indicator.style.display = 'block'; if (!message.includes('加载中') && !message.includes('处理中')) { setTimeout(() => { indicator.style.display = 'none'; }, 5000); } } // 尝试从多个CDN加载资源 function loadWithFallback(urls, libName, globalVar) { return new Promise((resolve, reject) => { if (window[globalVar]) { state[`${libName}Loaded`] = true; resolve(); return; } function tryLoad(index) { if (index >= urls.length) { reject(new Error(`${libName}库所有地址都加载失败`)); return; } showStatus(`正在尝试加载${libName}库 (${index + 1}/${urls.length})`); const script = document.createElement('script'); script.src = urls[index]; script.timeout = 15000; script.onload = () => { if (window[globalVar]) { state[`${libName}Loaded`] = true; showStatus(`${libName}库加载成功`); resolve(); } else { showStatus(`${libName}库加载但未初始化,尝试下一个地址`); script.remove(); tryLoad(index + 1); } }; script.onerror = () => { showStatus(`${libName}库加载失败,尝试下一个地址`); script.remove(); tryLoad(index + 1); }; script.ontimeout = () => { showStatus(`${libName}库加载超时,尝试下一个地址`); script.remove(); tryLoad(index + 1); }; document.head.appendChild(script); } tryLoad(0); }); } // 动态加载所需库 function loadLibraries() { showStatus('正在准备所需组件...'); return Promise.allSettled([ loadWithFallback(CDN_URLS.xlsx, 'xlsx', 'XLSX'), loadWithFallback(CDN_URLS.fileSaver, 'fileSaver', 'saveAs') ]).then(results => { const errors = []; results.forEach((result, index) => { if (result.status === 'rejected') { errors.push(index === 0 ? 'Excel处理' : 'Word保存'); } }); if (errors.length > 0) { showStatus(`部分组件加载失败: ${errors.join('、')}功能可能无法使用`); } else { showStatus('所有组件加载完成'); } }); } // 添加简易版saveAs实现作为最后的备用方案 function addFallbackSaveAs() { if (!window.saveAs) { window.saveAs = function(blob, name) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = name; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 0); }; state.fileSaverLoaded = true; } } // 创建下载按钮 function createDownloadButtons() { const container = document.createElement('div'); Object.assign(container.style, { position: 'fixed', right: '20px', bottom: '20px', zIndex: 2147483647, display: 'flex', flexDirection: 'column', gap: '10px' }); container.id = 'download-buttons-container'; document.body.appendChild(container); const excelBtn = createButton( '下载Excel题库', '#409EFF', handleExcelDownload ); excelBtn.id = 'download-excel-btn'; const wordBtn = createButton( '下载Word题库', '#67C23A', handleWordDownload ); wordBtn.id = 'download-word-btn'; container.appendChild(excelBtn); container.appendChild(wordBtn); checkPageReady(); } // 创建单个按钮 function createButton(text, bgColor, clickHandler) { const btn = document.createElement('button'); btn.textContent = text; Object.assign(btn.style, { padding: '10px 16px', background: bgColor, color: '#fff', border: 'none', borderRadius: '8px', fontSize: '14px', cursor: 'pointer', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', transition: 'all 0.2s ease', width: '160px' }); btn.addEventListener('mouseover', () => { btn.style.opacity = '0.9'; btn.style.transform = 'translateY(-2px)'; }); btn.addEventListener('mouseout', () => { btn.style.opacity = '1'; btn.style.transform = 'translateY(0)'; }); btn.addEventListener('click', clickHandler); return btn; } // 检查页面是否准备就绪 function checkPageReady() { const questions = document.querySelectorAll('.question-panel-middle'); const excelBtn = document.getElementById('download-excel-btn'); const wordBtn = document.getElementById('download-word-btn'); if (questions.length > 0) { showStatus(`检测到 ${questions.length} 道题目,可下载`); [excelBtn, wordBtn].forEach(btn => { btn.disabled = false; btn.style.opacity = '1'; }); } else { showStatus('等待题目加载...'); [excelBtn, wordBtn].forEach(btn => { btn.disabled = true; btn.style.opacity = '0.7'; }); setTimeout(checkPageReady, 3000); } } // 处理Excel下载 async function handleExcelDownload() { if (state.isLoading) return; try { state.isLoading = true; const btn = document.getElementById('download-excel-btn'); btn.textContent = 'Excel处理中...'; if (!state.xlsxLoaded) { showStatus('正在重新加载Excel组件...'); await loadWithFallback(CDN_URLS.xlsx, 'xlsx', 'XLSX'); } if (!window.XLSX) { throw new Error('Excel处理组件仍未加载成功,请检查网络'); } showStatus('正在提取题目数据...'); const { header, rows } = extractQuestionData(); if (rows.length === 0) { alert('未提取到任何题目数据,请确认页面已完全加载'); return; } showStatus('正在生成Excel文件...'); generateExcelFile(header, rows); showStatus(`成功导出 ${rows.length} 道题目到Excel`); } catch (error) { console.error('Excel下载过程出错:', error); showStatus('Excel导出失败: ' + error.message); alert('Excel导出过程出错: ' + error.message); } finally { state.isLoading = false; const btn = document.getElementById('download-excel-btn'); btn.textContent = '下载Excel题库'; } } // 处理Word下载 async function handleWordDownload() { if (state.isLoading) return; try { state.isLoading = true; const btn = document.getElementById('download-word-btn'); btn.textContent = 'Word处理中...'; if (!state.fileSaverLoaded) { showStatus('正在尝试加载Word保存组件...'); try { await loadWithFallback(CDN_URLS.fileSaver, 'fileSaver', 'saveAs'); } catch (e) { showStatus('使用备用保存方案...'); addFallbackSaveAs(); } } if (!window.saveAs) { throw new Error('Word保存组件仍未准备好,请检查网络'); } showStatus('正在提取题目数据...'); const questions = extractQuestionDataForWord(); if (questions.length === 0) { alert('未提取到任何题目数据,请确认页面已完全加载'); return; } showStatus('正在生成Word文件...'); generateWordFile(questions); showStatus(`成功导出 ${questions.length} 道题目到Word`); } catch (error) { console.error('Word下载过程出错:', error); showStatus('Word导出失败: ' + error.message); alert('Word导出过程出错: ' + error.message); } finally { state.isLoading = false; const btn = document.getElementById('download-word-btn'); btn.textContent = '下载Word题库'; } } // 提取题目数据(通用) function extractQuestionData() { // 按要求的顺序定义表头:试题类型、试题题目、参考答案、A-G选项、章节、解析 const optionLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; const optionHeaders = optionLetters.map(letter => `${letter}选项`); const header = [ '试题类型', '试题题目', '参考答案', ...optionHeaders, '章节', '解析' ]; const rows = []; const questions = document.querySelectorAll('.question-panel-middle'); if (!questions.length) return { header, rows }; questions.forEach((q, index) => { try { // 提取题型 const typeAttr = q.getAttribute('questtype') || ''; let typeLabel = getQuestionTypeLabel(typeAttr); // 提取题干(保留所有点号,不做特殊处理) let stemElement = q.querySelector('.question-stem .name') || q.querySelector('.question-stem'); let stem = stemElement ? stemElement.innerText.trim() : ''; // 只移除开头的题号,保留其他所有字符(包括点号) stem = stem.replace(/^\d+\.\s*/, ''); // 提取所有选项 const opts = Array.from( q.querySelectorAll('.question-options li, .question-options .item') ).map(opt => { const text = (opt.querySelector('.item-detail')?.innerText || opt.innerText).trim(); return text.replace(/^[A-Za-z]\.\s*/, ''); // 移除选项字母前缀 }); // 确保选项数量与表头一致(A-G共7个选项) const options = [...opts]; while (options.length < 7) { options.push(''); } // 提取正确答案 let ans = ''; const answerElement = q.querySelector('.true-answer, .answer-content'); if (answerElement) { ans = answerElement.innerText .replace(/参考答案\s*[::]?\s*/, '') .replace(/正确答案\s*[::]?\s*/, '') .trim(); } // 提取章节信息 let chapter = ''; const chapterElement = q.closest('.question-panel')?.querySelector('.chapter-title'); if (chapterElement) { chapter = chapterElement.innerText.trim().replace(/^章节:/, ''); } // 提取解析 let analysis = ''; const analysisElement = q.querySelector('.true-analysis, .analysis-content'); if (analysisElement) { analysis = analysisElement.innerText .replace(/解析\s*[::]?\s*/, '') .trim(); } // 按指定顺序组合一行数据 rows.push([ typeLabel, // 试题类型 stem, // 试题题目 ans, // 参考答案 ...options, // A-G选项 chapter, // 章节 analysis // 解析 ]); } catch (error) { console.error(`处理第 ${index + 1} 题时出错:`, error); const errorRow = [`处理出错: ${typeAttr}`, `第 ${index + 1} 题解析失败`, '']; // 填充选项 for (let i = 0; i < 7; i++) { errorRow.push(''); } errorRow.push('', error.message); // 章节和解析 rows.push(errorRow); } }); return { header, rows }; } // 提取题目数据(Word专用) function extractQuestionDataForWord() { const questions = []; const questionElements = document.querySelectorAll('.question-panel-middle'); if (!questionElements.length) return questions; questionElements.forEach((q, index) => { try { const typeAttr = q.getAttribute('questtype') || ''; let typeLabel = getQuestionTypeLabel(typeAttr); let stemElement = q.querySelector('.question-stem .name') || q.querySelector('.question-stem'); let stem = stemElement ? stemElement.innerText.trim() : ''; stem = stem.replace(/^\d+\.\s*/, ''); // 提取所有选项 const opts = Array.from( q.querySelectorAll('.question-options li, .question-options .item') ).map((opt, i) => { const text = (opt.querySelector('.item-detail')?.innerText || opt.innerText).trim(); const optionLetter = String.fromCharCode(65 + i); return `${optionLetter}. ${text.replace(/^[A-Za-z]\.\s*/, '')}`; }); // 提取正确答案 let ans = ''; const answerElement = q.querySelector('.true-answer, .answer-content'); if (answerElement) { ans = answerElement.innerText .replace(/参考答案\s*[::]?\s*/, '') .replace(/正确答案\s*[::]?\s*/, '') .trim(); } // 提取章节信息 let chapter = ''; const chapterElement = q.closest('.question-panel')?.querySelector('.chapter-title'); if (chapterElement) { chapter = chapterElement.innerText.trim().replace(/^章节:/, ''); } // 提取解析 let analysis = ''; const analysisElement = q.querySelector('.true-analysis, .analysis-content'); if (analysisElement) { analysis = analysisElement.innerText .replace(/解析\s*[::]?\s*/, '') .trim(); } questions.push({ number: index + 1, type: typeLabel, stem, options: opts, answer: ans, chapter: chapter, analysis }); } catch (error) { console.error(`处理第 ${index + 1} 题时出错:`, error); questions.push({ number: index + 1, type: `处理出错`, stem: `第 ${index + 1} 题解析失败`, options: [], answer: '', chapter: '', analysis: error.message }); } }); return questions; } // 获取题型标签 function getQuestionTypeLabel(typeAttr) { const typeMap = { 'SINGLE': '单选题', 'MULTI': '多选题', 'MULTIPLE': '多选题', 'JUDGE': '判断题', 'JUDGMENT': '判断题', 'FILL': '填空题', 'ESSAY': '简答题', 'CALCULATE': '计算题' }; return typeMap[typeAttr.toUpperCase()] || `未知题型(${typeAttr})`; } // 生成并下载Excel文件 function generateExcelFile(header, rows) { try { const ws = XLSX.utils.aoa_to_sheet([header, ...rows]); // 按新列顺序调整列宽 const wscols = [ { wch: 10 }, // 试题类型 { wch: 60 }, // 试题题目 { wch: 10 }, // 参考答案 { wch: 20 }, // A选项 { wch: 20 }, // B选项 { wch: 20 }, // C选项 { wch: 20 }, // D选项 { wch: 20 }, // E选项 { wch: 20 }, // F选项 { wch: 20 }, // G选项 { wch: 20 }, // 章节 { wch: 40 } // 解析 ]; ws['!cols'] = wscols; const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '题库'); const dateStr = getDateString(); const fileName = `21tb_题库_${dateStr}.xlsx`; XLSX.writeFile(wb, fileName); } catch (error) { console.error('生成Excel文件失败:', error); throw new Error('生成Excel文件失败'); } } // 生成并下载Word文件 function generateWordFile(questions) { try { let htmlContent = ` 21tb题库
21tb题库 (共${questions.length}题)
`; questions.forEach(q => { htmlContent += `
${q.chapter ? `
【章节】${q.chapter}
` : ''}
第${q.number}题(${q.type}):${q.stem}
`; if (q.options && q.options.length > 0) { htmlContent += '
'; q.options.forEach(option => { htmlContent += `
${option}
`; }); htmlContent += '
'; } htmlContent += `
【正确答案】:${q.answer || '未找到答案'}
`; if (q.analysis) { htmlContent += `
【解析】:${q.analysis}
`; } htmlContent += '
'; }); htmlContent += ` `; const blob = new Blob(['\ufeff', htmlContent], { type: 'application/msword;charset=utf-8' }); const dateStr = getDateString(); const fileName = `21tb_题库_${dateStr}.doc`; window.saveAs(blob, fileName); } catch (error) { console.error('生成Word文件失败:', error); throw new Error('生成Word文件失败'); } } // 获取格式化的日期字符串 function getDateString() { const date = new Date(); return `${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`; } // 初始化 function init() { loadLibraries().then(() => { createDownloadButtons(); addFallbackSaveAs(); }).catch((error) => { showStatus('组件加载遇到问题: ' + error.message); createDownloadButtons(); addFallbackSaveAs(); }); } // 启动脚本 init(); })();