您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
基于 v0.1 稳定版,完整修复并集成了考试功能与格式正确的答案导出功能。
// ==UserScript== // @name 微伴助手 // @namespace http://tampermonkey.net/ // @version 0.2.1 // @description 基于 v0.1 稳定版,完整修复并集成了考试功能与格式正确的答案导出功能。 // @author Yi & 毫厘 // @match https://weiban.mycourse.cn/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @connect weiban.mycourse.cn // @connect safety.mycourse.cn // @license MIT // ==/UserScript== (function () { 'use strict'; // ======================================================================== // 变量定义与初始化 (来自 v0.1) // ======================================================================== let USER_PROJECT_ID; let countdownInterval = null; const hashParams = window.location.hash.substring(1); if (hashParams) { const paramsPart = hashParams.indexOf('?') > -1 ? hashParams.substring(hashParams.indexOf('?') + 1) : ''; if (paramsPart) { const urlParams = new URLSearchParams(paramsPart); USER_PROJECT_ID = urlParams.get('projectId'); } } const COURSE_TYPES = ['3', '2']; let singleButton, allButton, examButton, exportButton, logPanel, statusPanel; if (!USER_PROJECT_ID) { console.error("非课程页面,脚本将保持静默。"); return; } // ======================================================================== // 核心工具函数 (来自 v0.1 并适配) // ======================================================================== function getUserInfo() { const userData = localStorage.getItem('user'); if (!userData) { logMessage("错误:未找到用户信息,请确保已登录。", 'error'); return null; } try { const user = JSON.parse(userData); if (!user.token || !user.userId || !user.tenantCode) { logMessage("错误:用户信息不完整。", 'error'); return null; } return user; } catch (e) { logMessage("错误:解析用户信息失败。", 'error'); return null; } } function makeRequest(options) { const userInfo = getUserInfo(); if (!userInfo) return Promise.reject("无法获取用户信息"); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || "POST", url: options.url, headers: options.headers || { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "x-token": userInfo.token }, data: options.data, responseType: options.responseType || "json", onload: function (response) { if (response.status >= 200 && response.status < 300) { let responseData = response.response; if (options.responseType === 'text') { try { const text = response.responseText; responseData = JSON.parse(text.substring(text.indexOf('(') + 1, text.lastIndexOf(')'))); } catch (e) { resolve(response.responseText); return; } } if (responseData && responseData.code && responseData.code !== "0") { logMessage(`API错误: ${responseData.message || responseData.msg}`, 'error'); reject(responseData); } else { resolve(responseData); } } else { logMessage(`HTTP 错误: ${response.status}`, 'error'); reject(response); } }, onerror: function (response) { logMessage(`网络错误: ${response.statusText}`, 'error'); reject(response); } }); }); } function logMessage(message, type = 'info') { if (!logPanel) return; const entry = document.createElement('div'); const timestamp = new Date().toLocaleTimeString(); entry.className = 'log-entry'; const timeSpan = document.createElement('span'); timeSpan.className = 'log-timestamp'; timeSpan.textContent = `[${timestamp}]`; entry.appendChild(timeSpan); const messageSpan = document.createElement('span'); messageSpan.className = `log-message log-${type}`; switch (type) { case 'error': messageSpan.textContent = `错误: ${message}`; break; case 'success': messageSpan.textContent = `成功: ${message}`; break; case 'warning': messageSpan.textContent = `警告: ${message}`; break; case 'category-start': messageSpan.textContent = `--- ${message} ---`; break; case 'category-end': messageSpan.textContent = `--- ${message} ---`; entry.appendChild(messageSpan); logPanel.appendChild(entry); { const divider = document.createElement('div'); divider.className = 'log-divider'; logPanel.appendChild(divider); } logPanel.scrollTop = logPanel.scrollHeight; return; default: messageSpan.textContent = message; break; } entry.appendChild(messageSpan); logPanel.appendChild(entry); logPanel.scrollTop = logPanel.scrollHeight; } async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function setAllButtonsDisabled(disabled) { [singleButton, allButton, examButton, exportButton].forEach(btn => { if (btn) btn.disabled = disabled; }); } // ======================================================================== // 课程学习相关功能 (来自 v0.1) // ======================================================================== async function fetchAllCategories(userInfo, userProjectId) { logMessage("正在获取所有课程分类...", 'info'); let allCategories = []; for (const type of COURSE_TYPES) { try { const response = await makeRequest({ url: `https://weiban.mycourse.cn/pharos/usercourse/listCategory.do?timestamp=${Date.now()}`, data: `tenantCode=${userInfo.tenantCode}&userId=${userInfo.userId}&userProjectId=${userProjectId}&chooseType=${type}` }); if (response?.data) { allCategories.push(...response.data.map(cat => ({ ...cat, chooseType: type }))); } } catch (e) { logMessage(`获取类型 [${type}] 分类失败`, 'error'); } } return allCategories; } async function attemptCompleteNextCourseInCategory(userInfo, userProjectId, categoryCode, categoryName, chooseType) { const listCourseUrl = `https://weiban.mycourse.cn/pharos/usercourse/listCourse.do?timestamp=${Date.now()}`; const listCourseData = `tenantCode=${userInfo.tenantCode}&userId=${userInfo.userId}&userProjectId=${userProjectId}&chooseType=${chooseType}&categoryCode=${categoryCode}`; const delayMilliseconds = (Math.random() * 10 + 15) * 1000; //核心模拟学习函数 try { const courseListResponse = await makeRequest({ url: listCourseUrl, data: listCourseData }); const targetCourse = courseListResponse.data?.find(c => c.finished !== 1); if (!targetCourse) return false; logMessage(`找到目标课程: ${targetCourse.resourceName}`, 'info'); await makeRequest({ url: `https://weiban.mycourse.cn/pharos/usercourse/study.do?timestamp=${Date.now()}`, data: `tenantCode=${userInfo.tenantCode}&userId=${userInfo.userId}&courseId=${targetCourse.resourceId}&userProjectId=${userProjectId}` }); updateStatus(`模拟学习: ${targetCourse.resourceName}`, Math.round(delayMilliseconds / 1000)); await sleep(delayMilliseconds); await makeRequest({ method: "GET", url: `https://weiban.mycourse.cn/pharos/usercourse/v2/${targetCourse.userCourseId}.do?callback=jQuery&userCourseId=${targetCourse.userCourseId}&tenantCode=${userInfo.tenantCode}&_=${Date.now()}`, responseType: 'text' }); await sleep(5000); for (let i = 0; i < 3; i++) { try { const verifyResponse = await makeRequest({ url: listCourseUrl, data: listCourseData }); const updatedCourse = verifyResponse.data.find(c => c.userCourseId === targetCourse.userCourseId); if (updatedCourse?.finished === 1) { logMessage(`课程 "${targetCourse.resourceName}" 已成功完成!`, 'success'); return true; } logMessage(`验证尝试 ${i + 1}/3: 状态为 ${updatedCourse?.finished ?? '未知'}`, 'warning'); } catch (e) { logMessage(`验证尝试 ${i + 1}/3 失败`, 'error'); } if (i < 2) await sleep(4000); } logMessage(`课程 "${targetCourse.resourceName}" 状态验证失败`, 'error'); return false; } catch (error) { logMessage(`处理课程时发生错误: ${error.message}`, 'error'); return false; } } async function automateSingleCategory() { await setAllButtonsDisabled(true); singleButton.textContent = '处理中...'; try { const userInfo = getUserInfo(); if (!userInfo) return; let categories = await fetchAllCategories(userInfo, USER_PROJECT_ID); const unfinishedCategory = categories.find(cat => cat.finishedNum < cat.totalNum); if (unfinishedCategory) { logMessage("开始处理下一个课程...", 'info'); await attemptCompleteNextCourseInCategory(userInfo, USER_PROJECT_ID, unfinishedCategory.categoryCode, unfinishedCategory.categoryName, unfinishedCategory.chooseType); } else { logMessage("所有课程均已完成。", "success"); } } finally { await setAllButtonsDisabled(false); singleButton.textContent = '完成下一个'; } } async function automateAllUnfinishedCategories() { await setAllButtonsDisabled(true); allButton.textContent = '处理中...'; logMessage("开始尝试完成所有未完成的课程...", 'info'); updateStatus("开始处理所有课程..."); try { const userInfo = getUserInfo(); if (!userInfo) return; let categories = await fetchAllCategories(userInfo, USER_PROJECT_ID); let unfinishedCategories = categories.filter(cat => cat.finishedNum < cat.totalNum); if (unfinishedCategories.length === 0) { logMessage("所有课程已完成。", 'success'); return; } logMessage(`发现 ${unfinishedCategories.length} 个分类有未完成的课程。`, 'info'); for (const category of unfinishedCategories) { logMessage(`处理分类: ${category.categoryName}`, 'category-start'); while (true) { const success = await attemptCompleteNextCourseInCategory(userInfo, USER_PROJECT_ID, category.categoryCode, category.categoryName, category.chooseType); if (!success) { logMessage(`分类 "${category.categoryName}" 已无课可刷或出错。`, 'info'); break; } await sleep(5000); } } logMessage("所有未完成课程处理完毕。", 'success'); } finally { await setAllButtonsDisabled(false); allButton.textContent = '完成所有课程'; updateStatus("课程处理完毕"); } } // ======================================================================== // 考试及导出功能 // ======================================================================== async function getProjectList() { return makeRequest({ url: `https://weiban.mycourse.cn/pharos/index/listMyProject.do`, data: `tenantCode=${getUserInfo().tenantCode}&userId=${getUserInfo().userId}&ended=2` }).then(res => res.data).catch(() => null); } async function getExams(userProjectId) { return makeRequest({ url: `https://weiban.mycourse.cn/pharos/exam/listPlan.do`, data: `tenantCode=${getUserInfo().tenantCode}&userId=${getUserInfo().userId}&userProjectId=${userProjectId}` }).then(res => res.data).catch(() => null); } async function getExamHistory(examPlanId, examType) { return makeRequest({ url: `https://weiban.mycourse.cn/pharos/exam/listHistory.do`, data: `tenantCode=${getUserInfo().tenantCode}&userId=${getUserInfo().userId}&examPlanId=${examPlanId}&examType=${examType}` }).then(res => res.data).catch(() => null); } async function getExamAnswer(userExamId) { return makeRequest({ url: `https://weiban.mycourse.cn/pharos/exam/reviewPaper.do`, data: `tenantCode=${getUserInfo().tenantCode}&userId=${getUserInfo().userId}&userExamId=${userExamId}&isRetake=2` }).then(res => res.data?.questions).catch(() => null); } async function startExam(userExamPlanId) { return makeRequest({ url: `https://weiban.mycourse.cn/pharos/exam/startPaper.do`, data: `tenantCode=${getUserInfo().tenantCode}&userId=${getUserInfo().userId}&userExamPlanId=${userExamPlanId}` }).then(res => res.data?.questionList).catch(() => null); } async function submitAnswer(examPlanId, userExamPlanId, questionId, answerIds) { return makeRequest({ url: `https://weiban.mycourse.cn/pharos/exam/recordQuestion.do`, data: `tenantCode=${getUserInfo().tenantCode}&userId=${getUserInfo().userId}&examPlanId=${examPlanId}&userExamPlanId=${userExamPlanId}&questionId=${questionId}&answerIds=${answerIds}&useTime=10` }).then(res => res.code === '0').catch(() => false); } async function submitPaper(userExamPlanId) { return makeRequest({ url: `https://weiban.mycourse.cn/pharos/exam/submitPaper.do`, data: `tenantCode=${getUserInfo().tenantCode}&userId=${getUserInfo().userId}&userExamPlanId=${userExamPlanId}` }).then(res => res.data).catch(() => null); } async function takeAllExams() { await setAllButtonsDisabled(true); examButton.textContent = '考试中...'; updateStatus("开始处理考试..."); try { const projects = await getProjectList(); if (!projects || projects.length === 0) { logMessage("未找到任何学习项目。", 'warning'); return; } for (const project of projects) { logMessage(`处理项目: "${project.projectName}"`, 'category-start'); const exams = await getExams(project.userProjectId); if (!exams || exams.length === 0) { logMessage("该项目下没有考试。", 'info'); continue; } // 筛选出已完成(有作答记录)的考试 const doneExams = exams.filter(x => x.examFinishNum > 0); let examAnswers = []; if (doneExams.length > 0) { logMessage(`发现 ${doneExams.length} 个已完成的考试,开始获取答案库...`, 'info'); for (const doneExam of doneExams) { const historyList = await getExamHistory(doneExam.examPlanId, doneExam.examType); if (!historyList) continue; for (const history of historyList) { const answers = await getExamAnswer(history.id); if (answers) { for (const answer of answers) { // 使用题目作为唯一标识,避免重复添加 if (!examAnswers.some(ea => ea.title === answer.title)) { examAnswers.push(answer); } } } } } if (examAnswers.length > 0) { logMessage(`成功构建答案库,共 ${examAnswers.length} 道题。`, 'success'); } else { logMessage(`未能从已完成的考试中提取到答案。`, 'warning'); } } // 如果没有任何考试记录,则进行一次“探索性”考试以生成答案库 if (examAnswers.length === 0) { logMessage("未发现可用答案库。将进行一次探索性考试以获取答案。", 'warning'); const firstExam = exams.find(e => e.examOddNum > 0); if (!firstExam) { logMessage("没有可用的考试来进行初次尝试。", "error"); continue; } logMessage(`将对《${firstExam.examPlanName}》进行初次尝试...`, 'info'); const questions = await startExam(firstExam.id); if (!questions) { logMessage("开始初次考试失败。这通常是因为需要人机验证。请手动进入该考试完成一次,然后再运行脚本。", 'error'); continue; // 继续处理下一个项目 } for (const q of questions) { // 统一选择第一个选项 await submitAnswer(firstExam.examPlanId, firstExam.id, q.id, q.optionList[0].id); } await submitPaper(firstExam.id); logMessage("初次尝试已提交。脚本将停止,请再次点击“开始考试”按钮,脚本将使用新生成的答案进行补考。", 'success'); return; // 关键:停止当前所有操作,让用户重新触发 } // 寻找需要进行且分数低于100的考试 const todoExam = exams.find(x => x.examOddNum > 0 && x.examScore < 100); if (!todoExam) { logMessage("所有考试均已完成或达标。", 'success'); logMessage(`项目: "${project.projectName}" 处理完毕`, 'category-end'); continue; } logMessage(`开始对《${todoExam.examPlanName}》进行正式考试...`, 'info'); const questions = await startExam(todoExam.id); if (!questions) { logMessage("开始正式考试失败。这通常是因为需要人机验证。请手动进入该考试完成一次,然后再运行脚本。", 'error'); continue; // 继续处理下一个项目 } let notFoundCount = 0; for (const q of questions) { const foundAnswer = examAnswers.find(a => a.title === q.title); let answerIds = []; if (foundAnswer) { // 从答案库中找到正确选项的内容 const correctOptionsContent = foundAnswer.optionList .filter(opt => opt.isCorrect === 1) .map(opt => opt.content); // 在当前考试的选项中,根据内容匹配,找到对应的ID answerIds = q.optionList .filter(currentOpt => correctOptionsContent.includes(currentOpt.content)) .map(currentOpt => currentOpt.id); } // 如果答案库中没有或者匹配失败,则默认选第一个 if (answerIds.length === 0) { notFoundCount++; answerIds.push(q.optionList[0].id); } await submitAnswer(todoExam.examPlanId, todoExam.id, q.id, answerIds.join(',')); } if (notFoundCount > 0) { logMessage(`本次有 ${notFoundCount} 道题未找到答案,已蒙第一个选项。`, 'warning'); } logMessage("所有题目回答完毕,等待3秒后交卷...", 'info'); await sleep(3000); const result = await submitPaper(todoExam.id); if (result) { logMessage(`交卷成功!本次得分: ${result.score}`, 'success'); if (result.score < 100) { logMessage("得分未满,若还有考试次数,可再次运行脚本刷分。", "info"); } } else { logMessage("交卷失败。", 'error'); } logMessage(`项目: "${project.projectName}" 处理完毕`, 'category-end'); } } finally { await setAllButtonsDisabled(false); examButton.textContent = '开始考试'; updateStatus("考试任务结束"); } } async function exportAnswersToDoc() { await setAllButtonsDisabled(true); exportButton.textContent = '导出中...'; updateStatus("开始构建答案库..."); logMessage("开始构建答案库以供导出...", "info"); try { const projects = await getProjectList(); if (!projects) { logMessage("未找到学习项目。", 'warning'); return; } let allExamAnswers = []; for (const project of projects) { const exams = await getExams(project.userProjectId); if (!exams) continue; const doneExams = exams.filter(x => x.examFinishNum > 0); if (doneExams.length === 0) continue; logMessage(`从项目 "${project.projectName}" 中提取答案...`, 'info'); for (const doneExam of doneExams) { const historyList = await getExamHistory(doneExam.examPlanId, doneExam.examType); if (!historyList) continue; for (const history of historyList) { const answers = await getExamAnswer(history.id); if (answers) { for (const answer of answers) { if (!allExamAnswers.some(ea => ea.title === answer.title)) { allExamAnswers.push(answer); } } } } } } if (allExamAnswers.length === 0) { logMessage("未能提取到任何答案。", "warning"); return; } logMessage(`答案库构建完成,共 ${allExamAnswers.length} 道题。开始生成Word...`, "success"); let contentHtml = ` <!DOCTYPE html><html><head><meta charset='utf-8'> <style> body { font-family: 'SimSun', '宋体', serif; font-size: 12pt; } .q-block { margin-bottom: 20px; border-left: 3px solid #eee; padding-left: 15px; } .q-title { font-weight: bold; } .q-options p { margin: 5px 0; } .correct-opt { color: #4CAF50; font-weight: bold; } .final-answer { font-weight: bold; margin-top: 10px; } </style> </head><body> <h1>安全微伴 考试答案库</h1><p>导出时间: ${new Date().toLocaleString()}</p><hr> `; allExamAnswers.sort((a, b) => a.sequence - b.sequence).forEach((q, index) => { const correctAnswerLetters = []; let optionsHtml = ''; q.optionList.forEach((opt, optIndex) => { const isCorrect = opt.isCorrect === 1; const optionLetter = String.fromCharCode(65 + optIndex); if (isCorrect) { correctAnswerLetters.push(optionLetter); } optionsHtml += `<p class="${isCorrect ? 'correct-opt' : ''}">${optionLetter}. ${opt.content.replace(/</g, '<').replace(/>/g, '>')} </p>`; }); contentHtml += `<div class="q-block"><p class="q-title">${index + 1}. ${q.title.replace(/</g, '<').replace(/>/g, '>')} [${q.typeLabel}]</p><div class="q-options">${optionsHtml}</div><p class="final-answer">正确答案:${correctAnswerLetters.join(', ')}</p></div>`; }); contentHtml += `</body></html>`; const blob = new Blob([contentHtml], { type: 'application/msword;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `安全微伴答案库.doc`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); logMessage("Word文档已开始下载!", "success"); } finally { await setAllButtonsDisabled(false); exportButton.textContent = '导出答案库'; updateStatus("导出任务结束"); } } // ======================================================================== // UI 设置 // ======================================================================== function updateStatus(message, countdownSeconds = 0) { if (statusPanel) { if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } statusPanel.textContent = message; if (countdownSeconds > 0) { let remainingSeconds = countdownSeconds; const countdownSpan = document.createElement('span'); countdownSpan.style.cssText = 'margin-left: 5px; color: #FFD700; font-weight: bold;'; statusPanel.appendChild(countdownSpan); const updateCountdown = () => { const minutes = Math.floor(remainingSeconds / 60); const seconds = remainingSeconds % 60; countdownSpan.textContent = ` (${minutes}:${seconds < 10 ? '0' : ''}${seconds})`; if (--remainingSeconds < 0) { clearInterval(countdownInterval); countdownSpan.textContent = ' (完成)'; setTimeout(() => { if (statusPanel.contains(countdownSpan)) { statusPanel.removeChild(countdownSpan); } }, 2000); } }; updateCountdown(); countdownInterval = setInterval(updateCountdown, 1000); } } } function setupUI() { const uiContainer = document.createElement('div'); uiContainer.style.cssText = 'position: fixed; bottom: 10px; right: 10px; z-index: 9997; display: flex; flex-direction: column; align-items: flex-end; gap: 10px;'; statusPanel = document.createElement('div'); statusPanel.textContent = '脚本已加载'; statusPanel.style.cssText = 'background-color: rgba(0, 0, 0, 0.7); color: white; padding: 5px 10px; border-radius: 5px; font-size: 13px; text-align: right;'; uiContainer.appendChild(statusPanel); logPanel = document.createElement('div'); logPanel.style.cssText = 'width: 450px; height: 300px; background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 12px; overflow-y: scroll; font-size: 13px; font-family: "SF Mono", Consolas, "Liberation Mono", Menlo, monospace; box-shadow: 0 4px 6px rgba(0,0,0,0.1); display: block; line-height: 1.5;'; const logStyle = document.createElement('style'); logStyle.textContent = `#userscript-log-panel::-webkit-scrollbar { width: 8px; } #userscript-log-panel::-webkit-scrollbar-track { background: #f1f1f1; } #userscript-log-panel::-webkit-scrollbar-thumb { background: #c1c1c1; } .log-entry { padding: 3px 0; display: flex; align-items: baseline; gap: 8px; } .log-timestamp { color: #6c757d; font-size: 12px; flex-shrink: 0; } .log-message { word-break: break-word; } .log-error { color: #dc3545; font-weight: 600; } .log-success { color: #198754; } .log-warning { color: #fd7e14; } .log-category-start, .log-category-end { color: #0d6efd; font-weight: 600; } .log-divider { height: 1px; background-color: #dee2e6; margin: 6px 0; }`; document.head.appendChild(logStyle); uiContainer.appendChild(logPanel); const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display: flex; gap: 10px;'; const lightenColor = (hex, lum) => { hex = String(hex).replace(/[^0-9a-f]/gi, ''); if (hex.length < 6) { hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; } lum = lum || 0; let rgb = "#", c; for (let i = 0; i < 3; i++) { c = parseInt(hex.substr(i*2,2),16); c = Math.round(Math.min(Math.max(0, c + (c * lum/100)), 255)).toString(16); rgb += ("00"+c).substr(c.length); } return rgb; }; const applyButtonStyles = (button, bgColor) => { button.style.cssText = `padding: 10px 18px; background-color: ${bgColor}; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: bold; transition: all 0.2s ease;`; button.onmouseover = () => { if (!button.disabled) button.style.backgroundColor = lightenColor(bgColor, -20); }; button.onmouseout = () => { if (!button.disabled) button.style.backgroundColor = bgColor; }; }; singleButton = document.createElement('button'); singleButton.textContent = '完成下一个'; applyButtonStyles(singleButton, '#2196F3'); singleButton.onclick = automateSingleCategory; buttonContainer.appendChild(singleButton); allButton = document.createElement('button'); allButton.textContent = '完成所有课程'; applyButtonStyles(allButton, '#4CAF50'); allButton.onclick = automateAllUnfinishedCategories; buttonContainer.appendChild(allButton); examButton = document.createElement('button'); examButton.textContent = '开始考试'; applyButtonStyles(examButton, '#FF9800'); examButton.onclick = takeAllExams; buttonContainer.appendChild(examButton); exportButton = document.createElement('button'); exportButton.textContent = '导出答案库'; applyButtonStyles(exportButton, '#00BCD4'); exportButton.onclick = exportAnswersToDoc; buttonContainer.appendChild(exportButton); uiContainer.appendChild(buttonContainer); document.body.appendChild(uiContainer); logMessage("课程+考试+导出脚本已初始化。"); } // ======================================================================== // 启动脚本 // ======================================================================== if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', setupUI); } else { setupUI(); } })();