// ==UserScript== // @name 题目解析导出工具 v2.2 // @namespace http://tampermonkey.net/ // @version 2.2 // @description 支持学习通&中国大学MOOC双平台的题目解析与导出工具 - 离线可用 // @author xuzhiy // @match *://*.chaoxing.com/* // @match *://*.fanya.chaoxing.com/* // @match *://*.mooc.chaoxing.com/* // @match *://mooc.chaoxing.com/* // @match *://i.chaoxing.com/* // @match *://*chaoxing.com/* // @match *://*.icourse163.org/* // @match *://icourse163.org/* // @match *://*/*exam* // @match *://*/*test* // @grant GM_xmlhttpRequest // @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // @license MIT // ==/UserScript== (function() { 'use strict'; // ===== 工具常量 ===== const TOOL_ID = 'QAnalysis'; // 工具唯一ID const BOX_ID = TOOL_ID + '_box'; // 工具箱ID const FLOAT_BTN_ID = TOOL_ID + '_float_btn'; // 悬浮按钮ID const PROGRESS_CONTAINER_ID = TOOL_ID + '_progress_container'; // 进度条容器ID const PROGRESS_BAR_ID = TOOL_ID + '_progress_bar'; // 进度条ID const AI_TOOL_ID = TOOL_ID + '_ai'; // AI工具ID const AI_ANSWER_ID = AI_TOOL_ID + '_answer'; // AI答案容器ID // ===== 平台检测 ===== const PLATFORM = detectPlatform(); function detectPlatform() { const hostname = window.location.hostname.toLowerCase(); if (hostname.includes('icourse163.org')) return 'mooc'; if (hostname.includes('chaoxing.com') || hostname.includes('fanya.chaoxing.com') || hostname.includes('mooc.chaoxing.com')) return 'chaoxing'; // 通过DOM元素特征检测 if (document.querySelector('.u-questionItem, .j-quizPool')) return 'mooc'; if (document.querySelector('.mark_item, .questionLi')) return 'chaoxing'; return 'unknown'; } function getPlatformName() { return PLATFORM === 'mooc' ? '中国大学MOOC' : PLATFORM === 'chaoxing' ? '学习通' : '未知平台'; } function getPlatformIcon() { return PLATFORM === 'mooc' ? '🎓' : '📚'; } // 平台主题色 function getPrimaryColor() { return PLATFORM === 'mooc' ? '#e74c3c' : '#4285f4'; } function getPrimaryGradient() { return PLATFORM === 'mooc' ? '#e74c3c,#c0392b' : '#4285f4,#3270d8'; } function getAccentGradient() { return PLATFORM === 'mooc' ? '#e74c3c,#f39c12' : '#4285f4,#34a853'; } // ===== 全局变量 ===== let toolInitialized = false; // 工具初始化状态 let allQsObject = []; // 所有问题对象 let allStr = ""; // 所有问题文本 let isProcessing = false; // 处理状态 let selectedQuestions = new Set(); // 已选中的问题ID集合 let lastSelectedQuestionId = null; // 上次选中的问题ID(用于Shift多选) let activeQuestions = {}; // 活动问题(用于AI解答) let isAnswering = false; // AI解答状态 // 用户设置 let hideMyAnswers = false; // 是否隐藏我的答案 let includeTimestamp = true; // 是否包含时间戳 let showExplanation = true; // 是否显示题目解析 let darkMode = false; // 暗色模式 let customTitle = ""; // 自定义标题 let animationsEnabled = true; // 是否启用动画效果 // AI设置 let aiSettings = { apiType: 'openai', // API类型: openai, deepseek, gemini, anthropic apiKey: '', // API密钥 temperature: 0.7, // 温度参数 defaultPrompt: '你是一位专业的题目解析助手,请根据以下题目给出详细的解答和分析。', // 默认提示词 customPrompts: { math: '你是一位数学专家,请分析以下数学题目,给出详细的解题步骤和思路。', english: '你是一位优秀的英语教师,请分析以下英语题目,解释相关语法、词汇知识点和答案依据。', science: '你是一位理科专家,请分析以下科学题目,给出详细的解答并解释相关科学原理。', wrong: '你是一位专业的题目解析助手,请分析以下题目,给出详细的解答步骤、思路分析和结论。如果题目有正确答案,请结合正确答案进行解析。' // 全题解析专用提示词 }, showInToolbox: true // 是否在工具箱显示AI设置 }; // ===== 设置管理 ===== // 加载设置 function loadSettings() { try { const savedSettings = localStorage.getItem(TOOL_ID + '_settings'); if (savedSettings) { const settings = JSON.parse(savedSettings); // 加载基本设置 hideMyAnswers = settings.hideMyAnswers !== undefined ? settings.hideMyAnswers : hideMyAnswers; includeTimestamp = settings.includeTimestamp !== undefined ? settings.includeTimestamp : includeTimestamp; showExplanation = settings.showExplanation !== undefined ? settings.showExplanation : showExplanation; darkMode = settings.darkMode !== undefined ? settings.darkMode : darkMode; customTitle = settings.customTitle !== undefined ? settings.customTitle : customTitle; animationsEnabled = settings.animationsEnabled !== undefined ? settings.animationsEnabled : animationsEnabled; // 加载AI设置 if (settings.aiSettings) { aiSettings = {...aiSettings, ...settings.aiSettings}; // 确保customPrompts对象存在 if (!aiSettings.customPrompts) { aiSettings.customPrompts = { math: '你是一位数学专家,请分析以下数学题目,给出详细的解题步骤和思路。', english: '你是一位优秀的英语教师,请分析以下英语题目,解释相关语法、词汇知识点和答案依据。', science: '你是一位理科专家,请分析以下科学题目,给出详细的解答并解释相关科学原理。' }; } } } } catch (e) { console.error("加载设置失败:", e); } } // 保存设置 function saveSettings() { try { const settings = { hideMyAnswers, includeTimestamp, showExplanation, darkMode, customTitle, animationsEnabled, aiSettings }; localStorage.setItem(TOOL_ID + '_settings', JSON.stringify(settings)); } catch (e) { console.error("保存设置失败:", e); } } // ===== 样式和界面 ===== // 插入CSS样式 function insertStyle() { const style = document.createElement('style'); style.textContent = ` /* 基础动画定义 */ @keyframes ${TOOL_ID}_fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes ${TOOL_ID}_fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes ${TOOL_ID}_slideInRight { from { transform: translateX(100px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes ${TOOL_ID}_slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100px); opacity: 0; } } @keyframes ${TOOL_ID}_slideInUp { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes ${TOOL_ID}_pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } @keyframes ${TOOL_ID}_shimmer { 0% { background-position: -1000px 0; } 100% { background-position: 1000px 0; } } @keyframes ${TOOL_ID}_rotateIn { from { transform: rotate(-10deg) scale(0.8); opacity: 0; } to { transform: rotate(0) scale(1); opacity: 1; } } @keyframes ${TOOL_ID}_shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); } 20%, 40%, 60%, 80% { transform: translateX(5px); } } @keyframes ${TOOL_ID}_gradientBg { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes ${TOOL_ID}_spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes ${TOOL_ID}_expandWidth { from { width: 0; } to { width: 100%; } } @keyframes ${TOOL_ID}_cardFlip { 0% { transform: perspective(1000px) rotateY(0deg); } 100% { transform: perspective(1000px) rotateY(180deg); } } @keyframes ${TOOL_ID}_highlight { 0% { box-shadow: 0 0 0 0 rgba(66, 133, 244, 0.6); } 70% { box-shadow: 0 0 0 10px rgba(66, 133, 244, 0); } 100% { box-shadow: 0 0 0 0 rgba(66, 133, 244, 0); } } /* 工具箱样式 */ #${BOX_ID} { position: fixed; top: 50px; right: 20px; width: 380px; height: 650px; background-color: #ffffff; box-shadow: 0 10px 30px rgba(0,0,0,0.15); border-radius: 12px; z-index: 9999; display: none; overflow: hidden; font-family: 'Microsoft YaHei', Arial, sans-serif; font-size: 14px; color: #333; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.1); } .${TOOL_ID}_animations_enabled #${BOX_ID}.visible { animation: ${TOOL_ID}_rotateIn 0.5s forwards; } #${BOX_ID}.dark-mode { background-color: #222; color: #eee; box-shadow: 0 10px 30px rgba(0,0,0,0.3); } #${BOX_ID}_header { background: linear-gradient(135deg, ${getPrimaryGradient()}); background-size: 200% 200%; color: white; padding: 14px 18px; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; border-radius: 12px 12px 0 0; position: relative; overflow: hidden; } .${TOOL_ID}_animations_enabled #${BOX_ID}_header { animation: ${TOOL_ID}_gradientBg 5s ease infinite; } #${BOX_ID}_header:after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0) 100%); background-size: 200% 100%; pointer-events: none; } .${TOOL_ID}_animations_enabled #${BOX_ID}_header:after { animation: ${TOOL_ID}_shimmer 3s infinite; } #${BOX_ID}_header_title { font-weight: 600; font-size: 16px; letter-spacing: 0.5px; display: flex; align-items: center; } #${BOX_ID}_header_title:before { content: '${getPlatformIcon()}'; margin-right: 8px; font-size: 18px; } .${TOOL_ID}_animations_enabled #${BOX_ID}_header_title:before { animation: ${TOOL_ID}_pulse 2s infinite; display: inline-block; } #${BOX_ID}_close_btn { background: rgba(255,255,255,0.1); border: none; color: white; font-size: 20px; cursor: pointer; opacity: 0.9; transition: all 0.2s; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } #${BOX_ID}_close_btn:hover { background-color: rgba(255,255,255,0.25); opacity: 1; transform: rotate(90deg); } #${BOX_ID}_content { padding: 20px; height: calc(100% - 60px); overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ccc transparent; position: relative; } #${BOX_ID}.dark-mode #${BOX_ID}_content { scrollbar-color: #555 #222; } #${BOX_ID}_content::-webkit-scrollbar { width: 6px; } #${BOX_ID}_content::-webkit-scrollbar-track { background: transparent; border-radius: 10px; } #${BOX_ID}_content::-webkit-scrollbar-thumb { background-color: #ccc; border-radius: 10px; } #${BOX_ID}.dark-mode #${BOX_ID}_content::-webkit-scrollbar-thumb { background-color: #555; } #${BOX_ID}_content::-webkit-scrollbar-thumb:hover { background-color: #aaa; } .dark-mode #${BOX_ID}_content::-webkit-scrollbar-thumb:hover { background-color: #777; } #${BOX_ID}_title { margin-top: 0; margin-bottom: 20px; font-size: 18px; font-weight: bold; color: #333; text-align: center; position: relative; padding-bottom: 10px; } #${BOX_ID}_title:after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 50px; height: 3px; background: linear-gradient(90deg, #4285f4, #34a853); border-radius: 3px; } .${TOOL_ID}_animations_enabled #${BOX_ID}_title:after { animation: ${TOOL_ID}_expandWidth 2s ease-out; width: 80px; } #${BOX_ID}.dark-mode #${BOX_ID}_title { color: #eee; } /* 选项卡样式 */ .${TOOL_ID}_tabs { display: flex; background-color: #f8f9fa; border-radius: 10px; padding: 3px; margin-bottom: 20px; position: relative; overflow: hidden; border: 1px solid #eee; } #${BOX_ID}.dark-mode .${TOOL_ID}_tabs { background-color: #333; border-color: #444; } .${TOOL_ID}_tab { flex: 1; padding: 10px 15px; background: none; border: none; cursor: pointer; font-size: 14px; color: #666; position: relative; z-index: 2; transition: all 0.3s; border-radius: 8px; text-align: center; font-weight: 500; } #${BOX_ID}.dark-mode .${TOOL_ID}_tab { color: #aaa; } .${TOOL_ID}_tab.active { color: #fff; } .${TOOL_ID}_tab_slider { position: absolute; top: 3px; left: 3px; bottom: 3px; background: linear-gradient(135deg, #4285f4, #3270d8); z-index: 1; border-radius: 8px; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_tab:hover:not(.active) { transform: translateY(-2px); } .${TOOL_ID}_tab_content { display: none; opacity: 0; transform: translateY(10px); transition: all 0.3s ease; } .${TOOL_ID}_tab_content.active { display: block; opacity: 1; transform: translateY(0); } /* 开关样式 */ .${TOOL_ID}_switch_container { display: flex; align-items: center; margin-bottom: 15px; padding: 10px 12px; border-radius: 8px; background-color: #f8f9fa; transition: all 0.2s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_switch_container:hover { background-color: #f1f3f5; transform: translateX(5px); } #${BOX_ID}.dark-mode .${TOOL_ID}_switch_container { background-color: #333; } #${BOX_ID}.dark-mode .${TOOL_ID}_switch_container:hover { background-color: #3a3a3a; } .${TOOL_ID}_switch { position: relative; display: inline-block; width: 50px; height: 26px; margin-right: 12px; } .${TOOL_ID}_switch input { opacity: 0; width: 0; height: 0; } .${TOOL_ID}_slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 26px; } #${BOX_ID}.dark-mode .${TOOL_ID}_slider { background-color: #555; } .${TOOL_ID}_slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } .${TOOL_ID}_switch input:checked + .${TOOL_ID}_slider { background-color: #4285f4; } .${TOOL_ID}_switch input:focus + .${TOOL_ID}_slider { box-shadow: 0 0 2px #4285f4; } .${TOOL_ID}_switch input:checked + .${TOOL_ID}_slider:before { transform: translateX(24px); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_switch input:checked + .${TOOL_ID}_slider:before { animation: ${TOOL_ID}_pulse 0.3s; } .${TOOL_ID}_switch_label { font-size: 14px; color: #555; flex: 1; } #${BOX_ID}.dark-mode .${TOOL_ID}_switch_label { color: #bbb; } /* 输入框样式 */ .${TOOL_ID}_input_label { display: block; margin-bottom: 8px; font-size: 14px; color: #555; font-weight: 500; } #${BOX_ID}.dark-mode .${TOOL_ID}_input_label { color: #bbb; } .${TOOL_ID}_input { width: 100%; padding: 12px 15px; border: 1px solid #ddd; border-radius: 8px; margin-bottom: 20px; font-size: 14px; transition: all 0.3s; background-color: #fff; color: #333; } #${BOX_ID}.dark-mode .${TOOL_ID}_input { background-color: #333; border: 1px solid #555; color: #eee; } .${TOOL_ID}_input:focus { border-color: #4285f4; outline: none; box-shadow: 0 0 0 3px rgba(77, 118, 255, 0.2); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_input:focus { animation: ${TOOL_ID}_highlight 1.5s; } /* 按钮样式 */ .${TOOL_ID}_btn_container { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; } .${TOOL_ID}_btn { background: linear-gradient(135deg, #4285f4, #3270d8); color: white; border: none; padding: 12px 16px; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.3s; display: flex; align-items: center; justify-content: center; flex: 1; min-width: 100px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); position: relative; overflow: hidden; } .${TOOL_ID}_btn:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0) 100%); transform: translateX(-100%); transition: transform 0.5s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_btn:hover:after { transform: translateX(100%); } .${TOOL_ID}_btn:hover { transform: translateY(-2px); box-shadow: 0 5px 12px rgba(0,0,0,0.15); } .${TOOL_ID}_btn:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .${TOOL_ID}_btn:disabled { background: linear-gradient(135deg, #b0bec5, #90a4ae); cursor: not-allowed; box-shadow: none; transform: none; } .${TOOL_ID}_btn:disabled:after { display: none; } .${TOOL_ID}_btn_icon { margin-right: 8px; font-size: 16px; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_btn_icon { display: inline-block; transform-origin: center; transition: transform 0.3s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_btn:hover .${TOOL_ID}_btn_icon { transform: scale(1.2) rotate(5deg); } .${TOOL_ID}_loading { display: inline-block; width: 18px; height: 18px; border: 2px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: white; animation: ${TOOL_ID}_spin 1s ease-in-out infinite; margin-right: 10px; } /* 状态指示器样式 */ .${TOOL_ID}_status { display: flex; align-items: center; padding: 15px; margin: 20px 0; background-color: #f5f7fa; border-radius: 8px; font-size: 14px; color: #555; box-shadow: 0 2px 5px rgba(0,0,0,0.05); transition: all 0.3s; } #${BOX_ID}.dark-mode .${TOOL_ID}_status { background-color: #333; color: #bbb; box-shadow: 0 2px 5px rgba(0,0,0,0.15); } .${TOOL_ID}_status.active { background-color: #e3f2fd; color: #1565c0; animation: ${TOOL_ID}_pulse 2s infinite; } .${TOOL_ID}_status.success { background-color: #e8f5e9; color: #2e7d32; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_status.success { animation: ${TOOL_ID}_highlight 1.5s; } .${TOOL_ID}_status.error { background-color: #fdecea; color: #d32f2f; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_status.error { animation: ${TOOL_ID}_shake 0.5s; } #${BOX_ID}.dark-mode .${TOOL_ID}_status.active { background-color: #0a2742; color: #64b5f6; } #${BOX_ID}.dark-mode .${TOOL_ID}_status.success { background-color: #0f2a19; color: #66bb6a; } #${BOX_ID}.dark-mode .${TOOL_ID}_status.error { background-color: #3e1c1a; color: #ef5350; } .${TOOL_ID}_status_icon { margin-right: 10px; font-size: 18px; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_status_icon { display: inline-block; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_status.active .${TOOL_ID}_status_icon { animation: ${TOOL_ID}_spin 1.5s linear infinite; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_status.success .${TOOL_ID}_status_icon { animation: ${TOOL_ID}_pulse 1s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_status.error .${TOOL_ID}_status_icon { animation: ${TOOL_ID}_shake 0.5s; } /* 进度条样式 */ #${PROGRESS_CONTAINER_ID} { margin: 20px 0; display: none; } #${PROGRESS_BAR_ID} { height: 8px; background-color: #e0e0e0; border-radius: 10px; overflow: hidden; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } #${BOX_ID}.dark-mode #${PROGRESS_BAR_ID} { background-color: #444; } #${PROGRESS_BAR_ID}_fill { height: 100%; width: 0%; background: linear-gradient(90deg, #4285f4, #34a853); transition: width 0.5s cubic-bezier(0.165, 0.84, 0.44, 1); border-radius: 10px; position: relative; overflow: hidden; } #${PROGRESS_BAR_ID}_fill:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0) 100%); background-size: 50% 100%; animation: ${TOOL_ID}_shimmer 1.5s infinite; } #${PROGRESS_BAR_ID}_text { font-size: 12px; color: #666; text-align: center; margin-top: 8px; font-weight: 500; } #${BOX_ID}.dark-mode #${PROGRESS_BAR_ID}_text { color: #aaa; } /* 题目列表样式 */ #${BOX_ID}_qlist { margin-top: 20px; } .${TOOL_ID}_empty_state { text-align: center; padding: 60px 20px; color: #999; background-color: #f9f9f9; border-radius: 10px; margin: 20px 0; transition: all 0.3s; } #${BOX_ID}.dark-mode .${TOOL_ID}_empty_state { color: #777; background-color: #2a2a2a; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_empty_state:hover { transform: scale(1.02); box-shadow: 0 5px 15px rgba(0,0,0,0.1); } #${BOX_ID}.dark-mode .${TOOL_ID}_empty_state:hover { box-shadow: 0 5px 15px rgba(0,0,0,0.3); } .${TOOL_ID}_empty_icon { font-size: 48px; margin-bottom: 20px; display: inline-block; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_empty_icon { animation: ${TOOL_ID}_pulse 2s infinite; } .${TOOL_ID}_empty_text { font-size: 18px; margin-bottom: 10px; font-weight: 500; } /* 题目部分样式 */ .${TOOL_ID}_question_section { margin-bottom: 25px; background-color: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 3px 10px rgba(0,0,0,0.08); transition: all 0.3s; transform-origin: center; opacity: 0; transform: translateY(20px); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_section.animated { animation: ${TOOL_ID}_slideInUp 0.5s forwards; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_section:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(0,0,0,0.12); } #${BOX_ID}.dark-mode .${TOOL_ID}_question_section { background-color: #2a2a2a; box-shadow: 0 3px 10px rgba(0,0,0,0.2); } #${BOX_ID}.dark-mode .${TOOL_ID}_question_section:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.35); } .${TOOL_ID}_question_section_title { background: linear-gradient(135deg, #f5f7fa, #e4e7eb); padding: 15px 20px; font-size: 16px; font-weight: 600; color: #333; border-bottom: 1px solid #eee; display: flex; align-items: center; justify-content: space-between; } .${TOOL_ID}_question_section_title:before { content: '📚'; margin-right: 10px; font-size: 18px; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_section_title:before { display: inline-block; transition: all 0.3s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_section:hover .${TOOL_ID}_question_section_title:before { transform: scale(1.2) rotate(10deg); } #${BOX_ID}.dark-mode .${TOOL_ID}_question_section_title { background: linear-gradient(135deg, #333, #2a2a2a); color: #eee; border-bottom: 1px solid #444; } .${TOOL_ID}_question_item { padding: 18px; border-bottom: 1px solid #f0f0f0; transition: all 0.3s; position: relative; overflow: hidden; } .${TOOL_ID}_question_item:before { content: ''; position: absolute; left: 0; top: 0; height: 100%; width: 3px; background-color: #4285f4; opacity: 0; transition: all 0.3s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_item:hover:before { opacity: 1; } .${TOOL_ID}_question_item:last-child { border-bottom: none; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_item:hover { background-color: #f8f9fa; } #${BOX_ID}.dark-mode .${TOOL_ID}_question_item { border-bottom: 1px solid #383838; } #${BOX_ID}.dark-mode .${TOOL_ID}_question_item:hover { background-color: #333; } .${TOOL_ID}_question_header { display: flex; margin-bottom: 12px; } .${TOOL_ID}_question_title { color: #333; font-weight: 500; line-height: 1.5; flex: 1; transition: all 0.3s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_item:hover .${TOOL_ID}_question_title { color: #4285f4; } #${BOX_ID}.dark-mode .${TOOL_ID}_question_title { color: #eee; } #${BOX_ID}.dark-mode .${TOOL_ID}_question_item:hover .${TOOL_ID}_question_title { color: #64b5f6; } .${TOOL_ID}_question_options { margin-left: 30px; margin-bottom: 15px; position: relative; } .${TOOL_ID}_question_options:before { content: ''; position: absolute; left: -15px; top: 0; bottom: 0; width: 2px; background-color: #e0e0e0; border-radius: 2px; } #${BOX_ID}.dark-mode .${TOOL_ID}_question_options:before { background-color: #444; } .${TOOL_ID}_question_option { margin: 8px 0; color: #555; transition: all 0.3s; padding: 5px 0; position: relative; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_question_option:hover { transform: translateX(5px); color: #333; } #${BOX_ID}.dark-mode .${TOOL_ID}_question_option { color: #bbb; } #${BOX_ID}.dark-mode .${TOOL_ID}_question_option:hover { color: #eee; } .${TOOL_ID}_my_answer { color: #1976d2; background-color: #e3f2fd; padding: 8px 12px; border-radius: 6px; font-size: 14px; display: inline-block; transition: all 0.3s; margin-right: 10px; box-shadow: 0 2px 5px rgba(25, 118, 210, 0.1); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_my_answer:hover { transform: translateY(-3px); box-shadow: 0 5px 10px rgba(25, 118, 210, 0.2); } #${BOX_ID}.dark-mode .${TOOL_ID}_my_answer { background-color: #0a2742; color: #64b5f6; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } #${BOX_ID}.dark-mode .${TOOL_ID}_my_answer:hover { box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); } .${TOOL_ID}_correct_answer { color: #2e7d32; background-color: #e8f5e9; padding: 8px 12px; border-radius: 6px; font-size: 14px; display: inline-block; transition: all 0.3s; box-shadow: 0 2px 5px rgba(46, 125, 50, 0.1); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_correct_answer:hover { transform: translateY(-3px); box-shadow: 0 5px 10px rgba(46, 125, 50, 0.2); } #${BOX_ID}.dark-mode .${TOOL_ID}_correct_answer { background-color: #0f2a19; color: #66bb6a; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } #${BOX_ID}.dark-mode .${TOOL_ID}_correct_answer:hover { box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); } .${TOOL_ID}_mismatch_indicator { color: #d32f2f; background-color: #fdecea; padding: 8px 12px; border-radius: 6px; font-size: 14px; margin-top: 12px; display: inline-block; animation: ${TOOL_ID}_pulse 2s infinite; box-shadow: 0 2px 5px rgba(211, 47, 47, 0.1); } #${BOX_ID}.dark-mode .${TOOL_ID}_mismatch_indicator { background-color: #3e1c1a; color: #ef5350; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } .${TOOL_ID}_explanation { margin-top: 20px; padding-top: 15px; border-top: 1px dashed #eee; font-size: 14px; color: #555; transition: all 0.3s; position: relative; padding-left: 15px; } .${TOOL_ID}_explanation:before { content: ''; position: absolute; left: 0; top: 15px; bottom: 0; width: 3px; background-color: #4285f4; border-radius: 3px; opacity: 0.6; } #${BOX_ID}.dark-mode .${TOOL_ID}_explanation { border-top: 1px dashed #444; color: #bbb; } .${TOOL_ID}_explanation_title { font-weight: 600; margin-bottom: 10px; color: #333; display: flex; align-items: center; } .${TOOL_ID}_explanation_title:before { content: '💡'; margin-right: 8px; font-size: 16px; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_explanation_title:before { display: inline-block; animation: ${TOOL_ID}_pulse 2s infinite; } #${BOX_ID}.dark-mode .${TOOL_ID}_explanation_title { color: #eee; } /* 图片样式 */ .${TOOL_ID}_img_container { margin: 15px 0; text-align: center; transition: all 0.3s; position: relative; overflow: hidden; border-radius: 8px; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_img_container:hover { transform: scale(1.02); } .${TOOL_ID}_img { max-width: 100%; max-height: 300px; border: 1px solid #ddd; padding: 5px; border-radius: 8px; background-color: #fff; box-shadow: 0 3px 10px rgba(0,0,0,0.08); transition: all 0.3s; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_img:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.15); } #${BOX_ID}.dark-mode .${TOOL_ID}_img { border: 1px solid #444; background-color: #333; box-shadow: 0 3px 10px rgba(0,0,0,0.25); } #${BOX_ID}.dark-mode .${TOOL_ID}_img:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.4); } .${TOOL_ID}_img_caption { font-size: 12px; color: #666; margin-top: 8px; padding: 5px 10px; background-color: rgba(0,0,0,0.03); border-radius: 20px; display: inline-block; } #${BOX_ID}.dark-mode .${TOOL_ID}_img_caption { color: #aaa; background-color: rgba(255,255,255,0.05); } /* 浮动按钮样式 */ #${FLOAT_BTN_ID} { position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg, #4285f4, #3270d8); color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 5px 15px rgba(0,0,0,0.2); z-index: 9998; font-size: 28px; border: none; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); overflow: hidden; } #${FLOAT_BTN_ID}:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 70%); opacity: 0; transition: all 0.5s; } .${TOOL_ID}_animations_enabled #${FLOAT_BTN_ID}:hover { transform: translateY(-5px) rotate(10deg); box-shadow: 0 8px 25px rgba(0,0,0,0.3); } .${TOOL_ID}_animations_enabled #${FLOAT_BTN_ID}:hover:after { opacity: 1; } #${FLOAT_BTN_ID}:active { transform: translateY(0) scale(0.95); box-shadow: 0 2px 8px rgba(0,0,0,0.2); } .${TOOL_ID}_animations_enabled #${FLOAT_BTN_ID} { animation: ${TOOL_ID}_pulse 2s infinite; } /* 题目选择相关样式 */ .${TOOL_ID}_question_checkbox { flex-shrink: 0; margin-right: 12px; margin-top: 3px; } .${TOOL_ID}_checkbox_container { display: block; position: relative; width: 22px; height: 22px; cursor: pointer; } .${TOOL_ID}_checkbox_container input { position: absolute; opacity: 0; cursor: pointer; height: 0; width: 0; } .${TOOL_ID}_checkbox_checkmark { position: absolute; top: 0; left: 0; height: 22px; width: 22px; background-color: #eee; border-radius: 6px; transition: all 0.3s; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } #${BOX_ID}.dark-mode .${TOOL_ID}_checkbox_checkmark { background-color: #444; } .${TOOL_ID}_checkbox_container:hover .${TOOL_ID}_checkbox_checkmark { background-color: #ddd; } #${BOX_ID}.dark-mode .${TOOL_ID}_checkbox_container:hover .${TOOL_ID}_checkbox_checkmark { background-color: #555; } .${TOOL_ID}_checkbox_container input:checked ~ .${TOOL_ID}_checkbox_checkmark { background-color: #4285f4; box-shadow: 0 2px 5px rgba(66,133,244,0.3); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_checkbox_container input:checked ~ .${TOOL_ID}_checkbox_checkmark { animation: ${TOOL_ID}_pulse 0.3s; } .${TOOL_ID}_checkbox_container input:checked ~ .${TOOL_ID}_checkbox_checkmark:after { content: ""; position: absolute; display: block; left: 8px; top: 4px; width: 6px; height: 12px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } /* 预览模态框样式 */ .${TOOL_ID}_modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 10000; display: flex; justify-content: center; align-items: center; opacity: 0; visibility: hidden; transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_modal { transform: scale(1.1); } .${TOOL_ID}_modal.active { opacity: 1; visibility: visible; transform: scale(1); } .${TOOL_ID}_modal_content { background-color: #fff; width: 85%; height: 90%; border-radius: 15px; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 10px 40px rgba(0,0,0,0.3); transform: translateY(30px); opacity: 0; transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1); transition-delay: 0.1s; } .${TOOL_ID}_modal.active .${TOOL_ID}_modal_content { transform: translateY(0); opacity: 1; } #${BOX_ID}.dark-mode .${TOOL_ID}_modal_content, .dark-mode .${TOOL_ID}_modal_content { background-color: #222; color: #eee; } .${TOOL_ID}_modal_header { background: linear-gradient(135deg, #4285f4, #3270d8); color: white; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; position: relative; overflow: hidden; } .${TOOL_ID}_modal_header:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0) 100%); transform: translateX(-100%); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_modal_header:after { animation: ${TOOL_ID}_shimmer 3s infinite; } .${TOOL_ID}_modal_title { font-size: 18px; font-weight: 600; letter-spacing: 0.5px; display: flex; align-items: center; } .${TOOL_ID}_modal_title:before { content: '👁️'; margin-right: 10px; font-size: 20px; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_modal_title:before { display: inline-block; animation: ${TOOL_ID}_pulse 2s infinite; } .${TOOL_ID}_modal_close { background: rgba(255,255,255,0.1); border: none; color: white; font-size: 22px; cursor: pointer; opacity: 0.9; transition: all 0.3s; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } .${TOOL_ID}_modal_close:hover { background-color: rgba(255,255,255,0.25); opacity: 1; transform: rotate(90deg); } .${TOOL_ID}_modal_body { flex: 1; overflow-y: auto; padding: 25px; scrollbar-width: thin; scrollbar-color: #ccc transparent; } #${BOX_ID}.dark-mode .${TOOL_ID}_modal_body, .dark-mode .${TOOL_ID}_modal_body { scrollbar-color: #555 #222; } .${TOOL_ID}_modal_body::-webkit-scrollbar { width: 8px; } .${TOOL_ID}_modal_body::-webkit-scrollbar-track { background: transparent; border-radius: 10px; } .${TOOL_ID}_modal_body::-webkit-scrollbar-thumb { background-color: #ccc; border-radius: 10px; } #${BOX_ID}.dark-mode .${TOOL_ID}_modal_body::-webkit-scrollbar-thumb, .dark-mode .${TOOL_ID}_modal_body::-webkit-scrollbar-thumb { background-color: #555; } .${TOOL_ID}_modal_body::-webkit-scrollbar-thumb:hover { background-color: #aaa; } .dark-mode .${TOOL_ID}_modal_body::-webkit-scrollbar-thumb:hover { background-color: #777; } .${TOOL_ID}_modal_footer { padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; border-top: 1px solid #eee; background-color: #f8f9fa; } #${BOX_ID}.dark-mode .${TOOL_ID}_modal_footer, .dark-mode .${TOOL_ID}_modal_footer { border-top: 1px solid #444; background-color: #333; } .${TOOL_ID}_tabs { display: flex; border-bottom: 1px solid #eee; margin-bottom: 20px; position: relative; } .dark-mode .${TOOL_ID}_tabs { border-bottom: 1px solid #444; } .${TOOL_ID}_tab { padding: 12px 20px; background: none; border: none; border-bottom: 3px solid transparent; cursor: pointer; font-size: 15px; color: #666; transition: all 0.3s; position: relative; overflow: hidden; z-index: 1; } .${TOOL_ID}_tab:after { content: ''; position: absolute; bottom: 0; left: 50%; width: 0; height: 3px; background: linear-gradient(90deg, #4285f4, #34a853); transition: all 0.3s; transform: translateX(-50%); z-index: -1; } .dark-mode .${TOOL_ID}_tab { color: #aaa; } .${TOOL_ID}_tab.active { color: #4285f4; font-weight: 500; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_tab.active:after, .${TOOL_ID}_animations_enabled .${TOOL_ID}_tab:hover:after { width: 100%; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_tab:hover:not(.active) { color: #4285f4; background-color: rgba(0,0,0,0.02); } .dark-mode .${TOOL_ID}_tab.active { color: #64b5f6; } .dark-mode .${TOOL_ID}_tab:hover:not(.active) { background-color: #333; color: #64b5f6; } .${TOOL_ID}_tab_content { display: none; opacity: 0; transform: translateY(10px); transition: all 0.4s ease; } .${TOOL_ID}_tab_content.active { display: block; opacity: 1; transform: translateY(0); } .${TOOL_ID}_form_group { margin-bottom: 20px; } .${TOOL_ID}_label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; } .dark-mode .${TOOL_ID}_label { color: #ccc; } .${TOOL_ID}_select, .${TOOL_ID}_textarea { width: 100%; padding: 12px 15px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; transition: all 0.3s; background-color: #fff; color: #333; } .${TOOL_ID}_textarea { min-height: 100px; resize: vertical; line-height: 1.5; } .dark-mode .${TOOL_ID}_select, .dark-mode .${TOOL_ID}_textarea { background-color: #333; border: 1px solid #555; color: #eee; } .${TOOL_ID}_select:focus, .${TOOL_ID}_textarea:focus { border-color: #4285f4; outline: none; box-shadow: 0 0 0 3px rgba(77, 118, 255, 0.2); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_select:focus, .${TOOL_ID}_animations_enabled .${TOOL_ID}_textarea:focus { animation: ${TOOL_ID}_highlight 1.5s; } /* AI解答按钮样式 */ .${AI_TOOL_ID}_btn { background: linear-gradient(135deg, #4d76ff, #3a5ccc); color: white; border: none; padding: 10px 16px; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.3s; display: flex; align-items: center; justify-content: center; margin: 15px 0; box-shadow: 0 3px 8px rgba(77, 118, 255, 0.2); position: relative; overflow: hidden; } .${AI_TOOL_ID}_btn:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0) 100%); transform: translateX(-100%); transition: transform 0.5s; } .${TOOL_ID}_animations_enabled .${AI_TOOL_ID}_btn:hover:after { transform: translateX(100%); } .${AI_TOOL_ID}_btn:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(77, 118, 255, 0.25); } .${AI_TOOL_ID}_btn:active { transform: translateY(-1px); box-shadow: 0 3px 8px rgba(77, 118, 255, 0.2); } .${AI_TOOL_ID}_btn:disabled { background: linear-gradient(135deg, #b0bec5, #90a4ae); cursor: not-allowed; box-shadow: none; transform: none; } .${AI_TOOL_ID}_btn:disabled:after { display: none; } .${AI_TOOL_ID}_config_btn { background-color: rgba(0,0,0,0.05); color: #666; border: 1px solid #ddd; padding: 6px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; margin-left: 10px; transition: all 0.3s; display: flex; align-items: center; justify-content: center; } .${AI_TOOL_ID}_config_btn:hover { background-color: rgba(0,0,0,0.08); border-color: #ccc; transform: translateY(-2px); } .dark-mode .${AI_TOOL_ID}_config_btn { color: #ccc; border-color: #555; background-color: rgba(255,255,255,0.05); } .dark-mode .${AI_TOOL_ID}_config_btn:hover { background-color: rgba(255,255,255,0.1); border-color: #666; } .${AI_TOOL_ID}_loading { display: inline-block; width: 18px; height: 18px; border: 3px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: white; animation: ${AI_TOOL_ID}_spin 1s ease-in-out infinite; margin-right: 10px; } @keyframes ${AI_TOOL_ID}_spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .${AI_TOOL_ID}_answer_container { margin-top: 20px; padding: 20px; background-color: #f8f9ff; border-radius: 10px; border-left: 4px solid #4d76ff; font-size: 14px; line-height: 1.6; position: relative; box-shadow: 0 3px 10px rgba(77, 118, 255, 0.1); transition: all 0.3s; transform: translateY(10px); opacity: 0; animation: ${TOOL_ID}_slideInUp 0.5s forwards; } .${TOOL_ID}_animations_enabled .${AI_TOOL_ID}_answer_container:hover { box-shadow: 0 6px 15px rgba(77, 118, 255, 0.15); transform: translateY(-3px); } .dark-mode .${AI_TOOL_ID}_answer_container { background-color: #2d2d3d; border-left: 4px solid #4d76ff; box-shadow: 0 3px 10px rgba(0,0,0,0.2); } .dark-mode .${AI_TOOL_ID}_answer_container:hover { box-shadow: 0 6px 15px rgba(0,0,0,0.3); } .${AI_TOOL_ID}_answer_header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid rgba(0,0,0,0.05); font-weight: 600; color: #333; } .dark-mode .${AI_TOOL_ID}_answer_header { border-bottom: 1px solid rgba(255,255,255,0.1); color: #eee; } .${AI_TOOL_ID}_answer_header:before { content: '🤖'; margin-right: 8px; font-size: 16px; } .${TOOL_ID}_animations_enabled .${AI_TOOL_ID}_answer_header:before { display: inline-block; animation: ${TOOL_ID}_pulse 2s infinite; } .${AI_TOOL_ID}_answer_content { color: #333; white-space: pre-wrap; position: relative; padding: 0 5px; } .${AI_TOOL_ID}_answer_content:before { content: ''; position: absolute; left: -10px; top: 0; bottom: 0; width: 2px; background-color: rgba(77, 118, 255, 0.2); border-radius: 2px; } .dark-mode .${AI_TOOL_ID}_answer_content { color: #ddd; } .dark-mode .${AI_TOOL_ID}_answer_content:before { background-color: rgba(77, 118, 255, 0.4); } .${AI_TOOL_ID}_answer_actions { display: flex; justify-content: flex-end; margin-top: 15px; gap: 10px; } .${AI_TOOL_ID}_action_btn { background-color: rgba(0,0,0,0.03); border: 1px solid #ddd; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; display: flex; align-items: center; transition: all 0.3s; color: #555; } .${AI_TOOL_ID}_action_btn:hover { background-color: rgba(0,0,0,0.05); transform: translateY(-2px); box-shadow: 0 2px 5px rgba(0,0,0,0.05); } .dark-mode .${AI_TOOL_ID}_action_btn { border: 1px solid #555; color: #ddd; background-color: rgba(255,255,255,0.05); } .dark-mode .${AI_TOOL_ID}_action_btn:hover { background-color: rgba(255,255,255,0.08); box-shadow: 0 2px 5px rgba(0,0,0,0.15); } .${AI_TOOL_ID}_action_icon { margin-right: 6px; font-size: 14px; display: inline-block; } .${TOOL_ID}_animations_enabled .${AI_TOOL_ID}_action_btn:hover .${AI_TOOL_ID}_action_icon { animation: ${TOOL_ID}_pulse 1s; } /* AI浮动按钮样式 */ #${AI_TOOL_ID}_float_btn { position: fixed; bottom: 20px; left: 20px; width: 55px; height: 55px; border-radius: 50%; background: linear-gradient(135deg, #4d76ff, #3a5ccc); color: white; border: none; box-shadow: 0 5px 15px rgba(0,0,0,0.2); z-index: 9999; display: flex; align-items: center; justify-content: center; font-size: 28px; cursor: pointer; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); overflow: hidden; } #${AI_TOOL_ID}_float_btn:after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 70%); opacity: 0; transition: all 0.5s; } .${TOOL_ID}_animations_enabled #${AI_TOOL_ID}_float_btn { animation: ${TOOL_ID}_pulse 2s infinite; } .${TOOL_ID}_animations_enabled #${AI_TOOL_ID}_float_btn:hover { transform: scale(1.1) rotate(-10deg); box-shadow: 0 8px 25px rgba(0,0,0,0.3); } .${TOOL_ID}_animations_enabled #${AI_TOOL_ID}_float_btn:hover:after { opacity: 1; } #${AI_TOOL_ID}_float_btn:active { transform: scale(0.95); box-shadow: 0 2px 10px rgba(0,0,0,0.2); } /* 选择控制区样式 */ .${TOOL_ID}_selection_controls { margin-bottom: 20px; background-color: #f9fafc; padding: 15px; border-radius: 10px; font-size: 14px; color: #333; box-shadow: 0 3px 10px rgba(0,0,0,0.05); border: 1px solid rgba(0,0,0,0.05); transition: all 0.3s; } #${BOX_ID}.dark-mode .${TOOL_ID}_selection_controls { background-color: #2a2a2a; color: #eee; box-shadow: 0 3px 10px rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.05); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_selection_controls:hover { box-shadow: 0 5px 15px rgba(0,0,0,0.08); transform: translateY(-2px); } #${BOX_ID}.dark-mode .${TOOL_ID}_selection_controls:hover { box-shadow: 0 5px 15px rgba(0,0,0,0.25); } .${TOOL_ID}_selection_header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .${TOOL_ID}_selection_title { font-weight: 600; display: flex; align-items: center; } .${TOOL_ID}_selection_title:before { content: '✓'; margin-right: 8px; background-color: #4285f4; color: white; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; border-radius: 50%; font-size: 12px; } .${TOOL_ID}_selection_count { padding: 4px 10px; background-color: rgba(66, 133, 244, 0.1); border-radius: 20px; color: #4285f4; font-size: 13px; font-weight: 500; transition: all 0.3s; } #${BOX_ID}.dark-mode .${TOOL_ID}_selection_count { background-color: rgba(100, 181, 246, 0.1); color: #64b5f6; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_selection_count { animation: ${TOOL_ID}_pulse 2s infinite; } .${TOOL_ID}_selection_buttons { display: flex; flex-wrap: wrap; gap: 10px; } .${TOOL_ID}_select_btn { font-size: 13px; padding: 8px 12px; border-radius: 6px; background-color: #f1f3f5; color: #555; border: none; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; min-width: 100px; justify-content: center; } .${TOOL_ID}_select_btn:before { margin-right: 6px; font-size: 14px; } .${TOOL_ID}_select_all:before { content: '✓'; } .${TOOL_ID}_deselect_all:before { content: '✗'; } .${TOOL_ID}_select_wrong:before { content: '❌'; } .${TOOL_ID}_select_correct:before { content: '✅'; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_select_btn:hover { transform: translateY(-2px); box-shadow: 0 3px 8px rgba(0,0,0,0.1); background-color: #e9ecef; color: #333; } #${BOX_ID}.dark-mode .${TOOL_ID}_select_btn { background-color: #333; color: #bbb; } #${BOX_ID}.dark-mode .${TOOL_ID}_select_btn:hover { background-color: #444; color: #eee; box-shadow: 0 3px 8px rgba(0,0,0,0.2); } /* 统计信息样式 */ .${TOOL_ID}_stats_container { margin-bottom: 25px; background: linear-gradient(135deg, #f9fafc, #f1f3f6); padding: 18px; border-radius: 10px; font-size: 14px; box-shadow: 0 3px 15px rgba(0,0,0,0.05); border: 1px solid rgba(0,0,0,0.05); position: relative; overflow: hidden; transition: all 0.3s; } .${TOOL_ID}_stats_container:before { content: ''; position: absolute; top: 0; right: 0; width: 120px; height: 120px; background: radial-gradient(circle, rgba(66, 133, 244, 0.1) 0%, rgba(66, 133, 244, 0) 70%); border-radius: 50%; transform: translate(30%, -30%); } #${BOX_ID}.dark-mode .${TOOL_ID}_stats_container { background: linear-gradient(135deg, #2a2a2a, #222); box-shadow: 0 3px 15px rgba(0,0,0,0.15); border: 1px solid rgba(255,255,255,0.05); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_stats_container:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.08); } #${BOX_ID}.dark-mode .${TOOL_ID}_stats_container:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.25); } .${TOOL_ID}_stats_header { display: flex; align-items: center; margin-bottom: 15px; border-bottom: 1px solid rgba(0,0,0,0.05); padding-bottom: 10px; } #${BOX_ID}.dark-mode .${TOOL_ID}_stats_header { border-bottom: 1px solid rgba(255,255,255,0.05); } .${TOOL_ID}_stats_title { font-weight: 600; color: #333; font-size: 15px; display: flex; align-items: center; } .${TOOL_ID}_stats_title:before { content: '📊'; margin-right: 8px; font-size: 16px; } .${TOOL_ID}_animations_enabled .${TOOL_ID}_stats_title:before { display: inline-block; animation: ${TOOL_ID}_pulse 2s infinite; } #${BOX_ID}.dark-mode .${TOOL_ID}_stats_title { color: #eee; } .${TOOL_ID}_stats_grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; } .${TOOL_ID}_stat_item { background-color: rgba(255,255,255,0.5); padding: 12px; border-radius: 8px; display: flex; flex-direction: column; transition: all 0.3s; border: 1px solid rgba(0,0,0,0.03); } #${BOX_ID}.dark-mode .${TOOL_ID}_stat_item { background-color: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.03); } .${TOOL_ID}_animations_enabled .${TOOL_ID}_stat_item:hover { transform: translateY(-3px); box-shadow: 0 3px 10px rgba(0,0,0,0.05); } #${BOX_ID}.dark-mode .${TOOL_ID}_stat_item:hover { box-shadow: 0 3px 10px rgba(0,0,0,0.15); } .${TOOL_ID}_stat_value { font-size: 22px; font-weight: 700; color: #4285f4; margin-bottom: 5px; } #${BOX_ID}.dark-mode .${TOOL_ID}_stat_value { color: #64b5f6; } .${TOOL_ID}_stat_label { font-size: 13px; color: #666; } #${BOX_ID}.dark-mode .${TOOL_ID}_stat_label { color: #aaa; } /* 通知提示样式 */ .${TOOL_ID}_toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(20px); background-color: rgba(0, 0, 0, 0.85); color: white; padding: 12px 24px; border-radius: 30px; font-size: 14px; z-index: 10001; opacity: 0; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); box-shadow: 0 5px 20px rgba(0,0,0,0.2); display: flex; align-items: center; } .${TOOL_ID}_toast.shown { opacity: 1; transform: translateX(-50%) translateY(0); } .${TOOL_ID}_toast:before { content: '✓'; margin-right: 10px; background-color: rgba(255,255,255,0.2); width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; } .${TOOL_ID}_toast.error:before { content: '!'; } .${TOOL_ID}_toast.success { background-color: rgba(46, 125, 50, 0.9); } .${TOOL_ID}_toast.error { background-color: rgba(211, 47, 47, 0.9); } .${TOOL_ID}_toast.info { background-color: rgba(25, 118, 210, 0.9); } /* MOOC平台主题色覆盖 */ ${PLATFORM === 'mooc' ? ` .${TOOL_ID}_tab_slider { background: linear-gradient(135deg, #e74c3c, #c0392b) !important; } .${TOOL_ID}_switch input:checked + .${TOOL_ID}_slider { background-color: #e74c3c !important; } .${TOOL_ID}_switch input:focus + .${TOOL_ID}_slider { box-shadow: 0 0 2px #e74c3c !important; } .${TOOL_ID}_input:focus { border-color: #e74c3c !important; } .${TOOL_ID}_btn { background: linear-gradient(135deg, #e74c3c, #c0392b) !important; } .${TOOL_ID}_btn:disabled { background: linear-gradient(135deg, #b0bec5, #90a4ae) !important; } #${FLOAT_BTN_ID} { background: linear-gradient(135deg, #e74c3c, #c0392b) !important; } #${PROGRESS_BAR_ID} { background: linear-gradient(90deg, #e74c3c, #f39c12) !important; } .${TOOL_ID}_stat_value { color: #e74c3c !important; } .${AI_TOOL_ID}_btn { border-color: #e74c3c !important; color: #e74c3c !important; } .${AI_TOOL_ID}_btn:hover { background: #e74c3c !important; color: #fff !important; } .${AI_TOOL_ID}_answer_header { color: #e74c3c !important; } .${AI_TOOL_ID}_loading { border-top-color: #e74c3c !important; } .${TOOL_ID}_question_item:hover { border-color: #e74c3c !important; } #${BOX_ID}_title:after { background: linear-gradient(90deg, #e74c3c, #f39c12) !important; } ` : ''} `; document.head.appendChild(style); } // ===== 工具箱主体 ===== // 创建悬浮按钮 function createFloatingButton() { if (document.getElementById(FLOAT_BTN_ID)) { return; } const floatingBtn = document.createElement('button'); floatingBtn.id = FLOAT_BTN_ID; floatingBtn.innerHTML = '📝'; floatingBtn.title = '打开题目解析工具'; document.body.appendChild(floatingBtn); floatingBtn.addEventListener('click', toggleToolBox); } // 创建AI浮动按钮 function createAIFloatingButton() { const floatBtnId = AI_TOOL_ID + '_float_btn'; // 避免重复创建 if (document.getElementById(floatBtnId)) return; const button = document.createElement('button'); button.id = floatBtnId; button.innerHTML = '🤖'; button.title = 'AI解答助手设置'; document.body.appendChild(button); button.addEventListener('click', function() { openAISettingsModal(); }); } // 切换工具箱显示状态 function toggleToolBox() { let box = document.getElementById(BOX_ID); if (!box) { createToolBox(); box = document.getElementById(BOX_ID); } if (box.style.display === 'none' || box.style.display === '') { box.style.display = 'block'; // 添加动画类 if (animationsEnabled) { // 先设置起始状态 box.style.opacity = '0'; box.style.transform = 'scale(0.9) rotate(-3deg)'; setTimeout(() => { box.classList.add('visible'); }, 10); } else { box.style.opacity = '1'; box.style.transform = 'none'; } // 如果有数据,刷新显示 if (allQsObject.length > 0) { displayQuestions(allQsObject); } } else { // 添加隐藏动画 if (animationsEnabled) { box.classList.remove('visible'); box.style.opacity = '0'; box.style.transform = 'scale(0.9) rotate(3deg)'; } else { box.style.opacity = '0'; box.style.transform = 'translateY(-20px)'; } setTimeout(() => { box.style.display = 'none'; }, 300); } } // 创建工具箱 function createToolBox() { if (document.getElementById(BOX_ID)) { return; } // 加载保存的设置 loadSettings(); // 设置动画全局类 if (animationsEnabled) { document.body.classList.add(`${TOOL_ID}_animations_enabled`); } else { document.body.classList.remove(`${TOOL_ID}_animations_enabled`); } const box = document.createElement('div'); box.id = BOX_ID; box.style.opacity = '0'; box.style.transform = 'scale(0.9) rotate(-3deg)'; // 应用暗色模式 if (darkMode) { box.classList.add('dark-mode'); } box.innerHTML = `
题目解析工具 ${getPlatformName()}

题目解析

删除我的答案
标题添加导出时间
显示题目解析
暗色模式
启用动画效果
精确 ${aiSettings.temperature} 创意
等待操作
0%
`; document.body.appendChild(box); // 更新标题 updateTitle(); // 添加事件监听器 setupEventListeners(); // 设置拖动功能 setupDraggable(); // 初始禁用导出按钮 updateExportButtons(); // 添加选项卡滑块效果 updateTabSlider(); } // 更新选项卡滑块位置 function updateTabSlider() { const activeTab = document.querySelector(`.${TOOL_ID}_tab.active`); const slider = document.querySelector(`.${TOOL_ID}_tab_slider`); if (activeTab && slider) { slider.style.width = `${activeTab.offsetWidth}px`; slider.style.left = `${activeTab.offsetLeft}px`; } } // 设置事件监听器 function setupEventListeners() { // 关闭按钮 document.getElementById(`${BOX_ID}_close_btn`).addEventListener('click', function() { toggleToolBox(); }); // 标签切换 document.querySelectorAll(`.${TOOL_ID}_tab`).forEach(tab => { tab.addEventListener('click', function() { // 移除所有活动标签 document.querySelectorAll(`.${TOOL_ID}_tab`).forEach(t => t.classList.remove('active')); document.querySelectorAll(`.${TOOL_ID}_tab_content`).forEach(c => c.classList.remove('active')); // 添加活动状态到当前标签 this.classList.add('active'); document.querySelector(`.${TOOL_ID}_tab_content[data-tab-content="${this.dataset.tab}"]`).classList.add('active'); // 更新滑块位置 updateTabSlider(); }); }); // 删除答案复选框 document.getElementById(`${BOX_ID}_hide_answers`).addEventListener('change', function() { hideMyAnswers = this.checked; saveSettings(); if (allQsObject.length > 0) { displayQuestions(allQsObject); } }); // 添加时间戳复选框 document.getElementById(`${BOX_ID}_include_timestamp`).addEventListener('change', function() { includeTimestamp = this.checked; saveSettings(); }); // 显示题目解析复选框 document.getElementById(`${BOX_ID}_show_explanation`).addEventListener('change', function() { showExplanation = this.checked; saveSettings(); if (allQsObject.length > 0) { displayQuestions(allQsObject); } }); // 暗色模式切换 document.getElementById(`${BOX_ID}_dark_mode`).addEventListener('change', function() { darkMode = this.checked; const box = document.getElementById(BOX_ID); if (box) { if (darkMode) { box.classList.add('dark-mode'); } else { box.classList.remove('dark-mode'); } } saveSettings(); }); // 动画效果切换 document.getElementById(`${BOX_ID}_animations`).addEventListener('change', function() { animationsEnabled = this.checked; if (animationsEnabled) { document.body.classList.add(`${TOOL_ID}_animations_enabled`); } else { document.body.classList.remove(`${TOOL_ID}_animations_enabled`); } saveSettings(); }); // 自定义标题输入框 document.getElementById(`${BOX_ID}_custom_title`).addEventListener('input', function() { customTitle = this.value.trim(); saveSettings(); updateTitle(); }); // AI设置相关 document.getElementById(`${BOX_ID}_ai_type`).addEventListener('change', function() { aiSettings.apiType = this.value; saveSettings(); }); document.getElementById(`${BOX_ID}_api_key`).addEventListener('change', function() { aiSettings.apiKey = this.value.trim(); saveSettings(); }); // 温度滑块 const tempSlider = document.getElementById(`${BOX_ID}_temperature`); const tempValue = document.getElementById(`${BOX_ID}_temp_value`); tempSlider.addEventListener('input', function() { tempValue.textContent = this.value; aiSettings.temperature = parseFloat(this.value); saveSettings(); }); document.getElementById(`${BOX_ID}_default_prompt`).addEventListener('input', function() { aiSettings.defaultPrompt = this.value.trim(); saveSettings(); }); // 添加自定义提示词的事件监听器 document.getElementById(`${BOX_ID}_math_prompt`).addEventListener('input', function() { aiSettings.customPrompts.math = this.value.trim(); saveSettings(); }); document.getElementById(`${BOX_ID}_english_prompt`).addEventListener('input', function() { aiSettings.customPrompts.english = this.value.trim(); saveSettings(); }); document.getElementById(`${BOX_ID}_science_prompt`).addEventListener('input', function() { aiSettings.customPrompts.science = this.value.trim(); saveSettings(); }); // 添加全题提示词的事件监听器 const wrongPromptElement = document.getElementById(`${BOX_ID}_wrong_prompt`); if (wrongPromptElement) { wrongPromptElement.addEventListener('input', function() { aiSettings.customPrompts.wrong = this.value.trim(); saveSettings(); }); } // 解析按钮 document.getElementById(`${BOX_ID}_parse_btn`).addEventListener('click', function() { // 清空数据并重新解析 allQsObject = []; allStr = ""; updateStatus("开始解析题目...", "active"); setProcessingState(true); parseQuestions(); }); // 预览按钮 document.getElementById(`${BOX_ID}_preview_btn`).addEventListener('click', function() { if (allQsObject.length === 0 && selectedQuestions.size === 0) { showToast("没有题目可供预览", "error"); return; } if (isProcessing) { return; } openPreviewModal(); }); // Excel导出按钮 document.getElementById(`${BOX_ID}_excel_btn`).addEventListener('click', function() { if ((allQsObject.length === 0 && selectedQuestions.size === 0) || isProcessing) { return; } if (selectedQuestions.size === 0) { if (!confirm("您没有选择任何题目,将导出所有题目。是否继续?")) { return; } } updateStatus("正在生成Excel文件...", "active"); setProcessingState(true); const exportData = prepareExportData(); downloadExcel(exportData.data, exportData.baseFilename + ".xlsx", exportData.title); }); // Word导出按钮 document.getElementById(`${BOX_ID}_word_btn`).addEventListener('click', function() { if ((allQsObject.length === 0 && selectedQuestions.size === 0) || isProcessing) { return; } if (selectedQuestions.size === 0) { if (!confirm("您没有选择任何题目,将导出所有题目。是否继续?")) { return; } } updateStatus("正在生成Word文件...", "active"); setProcessingState(true); const exportData = prepareExportData(); downloadWord(exportData.data, exportData.baseFilename + ".docx"); }); // Word兼容导出按钮 document.getElementById(`${BOX_ID}_word_compatible_btn`).addEventListener('click', function() { if ((allQsObject.length === 0 && selectedQuestions.size === 0) || isProcessing) { return; } if (selectedQuestions.size === 0) { if (!confirm("您没有选择任何题目,将导出所有题目。是否继续?")) { return; } } updateStatus("正在生成Office兼容的Word文件...", "active"); setProcessingState(true); const exportData = prepareExportData(); downloadCompatibleWord(exportData.data, exportData.baseFilename + ".docx"); }); // PDF导出按钮 document.getElementById(`${BOX_ID}_pdf_btn`).addEventListener('click', function() { if ((allQsObject.length === 0 && selectedQuestions.size === 0) || isProcessing) { return; } if (selectedQuestions.size === 0) { if (!confirm("您没有选择任何题目,将导出所有题目。是否继续?")) { return; } } updateStatus("正在生成PDF文件...", "active"); setProcessingState(true); const exportData = prepareExportData(); downloadPDF(exportData.data, exportData.baseFilename + ".pdf"); }); // 考试宝题库导出按钮 document.getElementById(`${BOX_ID}_kaoshibao_btn`).addEventListener('click', function() { if ((allQsObject.length === 0 && selectedQuestions.size === 0) || isProcessing) { return; } if (selectedQuestions.size === 0) { if (!confirm("您没有选择任何题目,将导出所有题目。是否继续?")) { return; } } updateStatus("正在生成考试宝题库文件...", "active"); setProcessingState(true); const exportData = prepareExportData(); downloadKaoshibao(exportData.data, exportData.baseFilename + "_考试宝.xlsx", exportData.title); }); // AI解析全部题目按钮 document.getElementById(`${BOX_ID}_ai_wrong_btn`).addEventListener('click', function() { if (isProcessing || isAnswering) return; // 获取所有题目 const allQuestions = []; allQsObject.forEach(node => { node.nodeList.forEach(qItem => { allQuestions.push(qItem); }); }); if (allQuestions.length === 0) { showToast("没有找到题目", "info"); return; } // 创建选项对话框 const batchSizeOptions = allQuestions.length > 20 ? ` ` : ''; const dialogId = `${TOOL_ID}_wrong_dialog`; const dialog = document.createElement('div'); dialog.id = dialogId; dialog.className = `${TOOL_ID}_modal`; dialog.style.opacity = '0'; dialog.style.visibility = 'hidden'; dialog.innerHTML = `
AI解析全部题目设置

将对 ${allQuestions.length} 道题目进行AI解析

`; document.body.appendChild(dialog); // 应用暗色模式 if (darkMode) { dialog.querySelector(`.${TOOL_ID}_modal_content`).classList.add('dark-mode'); } // 显示对话框动画 setTimeout(() => { dialog.style.opacity = '1'; dialog.style.visibility = 'visible'; dialog.classList.add('active'); }, 10); // 添加事件处理 - 修复选择器问题 const closeDialog = () => { dialog.classList.remove('active'); dialog.style.opacity = '0'; dialog.style.visibility = 'hidden'; setTimeout(() => { if (dialog && dialog.parentNode) { document.body.removeChild(dialog); } }, 300); }; // 修复关闭按钮事件绑定 document.getElementById(`${dialogId}_close`).addEventListener('click', closeDialog); document.getElementById('wrong_cancel_btn').addEventListener('click', closeDialog); document.getElementById('wrong_start_btn').addEventListener('click', () => { // 获取设置 const batchSize = document.getElementById('wrong_batch_size').value; const useSpecialPrompt = document.getElementById('wrong_use_special_prompt').checked; const skipExisting = document.getElementById('wrong_skip_existing').checked; // 关闭对话框 closeDialog(); // 开始批量解析 analyzeWrongQuestions(allQuestions, { batchSize: batchSize === 'all' ? allQuestions.length : parseInt(batchSize), useSpecialPrompt, skipExisting }); }); }); } // 设置拖动功能 function setupDraggable() { const header = document.getElementById(`${BOX_ID}_header`); const box = document.getElementById(BOX_ID); if (!header || !box) return; let isDragging = false; let offsetX, offsetY; header.addEventListener('mousedown', function(e) { isDragging = true; offsetX = e.clientX - box.getBoundingClientRect().left; offsetY = e.clientY - box.getBoundingClientRect().top; // 添加拖动时的视觉效果 box.style.transition = "none"; box.style.opacity = "0.9"; if (animationsEnabled) { box.style.boxShadow = "0 15px 40px rgba(0,0,0,0.2)"; } }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; box.style.left = (e.clientX - offsetX) + 'px'; box.style.top = (e.clientY - offsetY) + 'px'; box.style.right = 'auto'; }); document.addEventListener('mouseup', function() { if (isDragging) { isDragging = false; // 恢复正常外观 box.style.transition = animationsEnabled ? "all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)" : "opacity 0.3s, transform 0.3s"; box.style.opacity = "1"; if (animationsEnabled) { box.style.boxShadow = ""; } } }); } // 更新工具箱标题 function updateTitle() { const titleElement = document.querySelector(".mark_title"); const titleDisplay = document.getElementById(`${BOX_ID}_title`); const customTitleInput = document.getElementById(`${BOX_ID}_custom_title`); if (titleDisplay) { const pageTitle = titleElement ? titleElement.innerText : "题目解析"; titleDisplay.textContent = customTitle || pageTitle; } // 更新自定义标题输入框 if (customTitleInput) { customTitleInput.value = customTitle || ""; } } // 更新导出按钮状态 function updateExportButtons() { const hasData = allQsObject.length > 0; const previewBtn = document.getElementById(`${BOX_ID}_preview_btn`); const excelBtn = document.getElementById(`${BOX_ID}_excel_btn`); const wordBtn = document.getElementById(`${BOX_ID}_word_btn`); const wordCompatibleBtn = document.getElementById(`${BOX_ID}_word_compatible_btn`); const pdfBtn = document.getElementById(`${BOX_ID}_pdf_btn`); const kaoshibaoBtn = document.getElementById(`${BOX_ID}_kaoshibao_btn`); if (previewBtn) previewBtn.disabled = !hasData || isProcessing; if (excelBtn) excelBtn.disabled = !hasData || isProcessing; if (wordBtn) wordBtn.disabled = !hasData || isProcessing; if (wordCompatibleBtn) wordCompatibleBtn.disabled = !hasData || isProcessing; if (pdfBtn) pdfBtn.disabled = !hasData || isProcessing; if (kaoshibaoBtn) kaoshibaoBtn.disabled = !hasData || isProcessing; } // 设置处理状态 function setProcessingState(processing) { isProcessing = processing; // 更新按钮状态 const parseBtn = document.getElementById(`${BOX_ID}_parse_btn`); if (parseBtn) { if (processing) { parseBtn.innerHTML = `处理中...`; parseBtn.disabled = true; } else { parseBtn.innerHTML = `📋解析题目`; parseBtn.disabled = false; } } // 更新导出按钮状态 updateExportButtons(); } // 更新状态信息 function updateStatus(message, type = "") { const statusElement = document.getElementById(`${BOX_ID}_status`); if (!statusElement) return; // 移除所有状态类 statusElement.classList.remove('active', 'success', 'error'); // 设置图标和类型 let icon = "⏳"; if (type === "active") { statusElement.classList.add('active'); icon = "🔄"; } else if (type === "success") { statusElement.classList.add('success'); icon = "✅"; } else if (type === "error") { statusElement.classList.add('error'); icon = "❌"; } statusElement.innerHTML = `${icon}${message}`; } // 显示进度条 function showProgressBar() { const progressContainer = document.getElementById(PROGRESS_CONTAINER_ID); if (progressContainer) { progressContainer.style.display = 'block'; } updateProgress(0, '初始化中...'); } // 隐藏进度条 function hideProgressBar() { const progressContainer = document.getElementById(PROGRESS_CONTAINER_ID); if (progressContainer) { progressContainer.style.display = 'none'; } } // 更新进度条 function updateProgress(percent, text) { const progressFill = document.getElementById(`${PROGRESS_BAR_ID}_fill`); const progressText = document.getElementById(`${PROGRESS_BAR_ID}_text`); if (progressFill && progressText) { // 确保百分比在0-100之间 const safePercent = Math.max(0, Math.min(100, percent)); progressFill.style.width = `${safePercent}%`; // 更新文本,如果没有提供则显示百分比 progressText.textContent = text || `${Math.round(safePercent)}%`; } } // 显示通知提示 function showToast(message, type = "info", duration = 3000) { // 移除已存在的通知 let toast = document.querySelector(`.${TOOL_ID}_toast`); if (toast) { document.body.removeChild(toast); } // 创建新通知 toast = document.createElement('div'); toast.className = `${TOOL_ID}_toast ${type}`; toast.textContent = message; document.body.appendChild(toast); // 显示通知 setTimeout(() => { toast.classList.add('shown'); }, 10); // 设置通知自动消失 setTimeout(() => { toast.classList.remove('shown'); setTimeout(() => { if (toast && toast.parentNode) { document.body.removeChild(toast); } }, 300); }, duration); } // ===== 题目解析功能 ===== // 解析问题 function parseQuestions() { if (PLATFORM === 'mooc') { parseMoocQuestions(); } else { parseChaoxingQuestions(); } } // ===== 学习通平台解析(原parseQuestions) ===== function parseChaoxingQuestions() { const qlistElement = document.getElementById(`${BOX_ID}_qlist`); const nodeBox = document.getElementsByClassName("mark_item"); if (nodeBox.length === 0) { if (qlistElement) { qlistElement.innerHTML = `
📝
未找到试题内容
请确认当前页面包含试题数据
`; updateStatus("未找到题目内容", "error"); setProcessingState(false); } return; } // 记录页面上的所有图片 const totalImages = document.querySelectorAll("img").length; console.log(`页面上共有 ${totalImages} 个图片元素`); updateStatus(`分析页面结构...找到 ${totalImages} 个图片元素`, "active"); const imagePromises = []; Array.from(nodeBox).forEach(qNode => { let node = { nodeName: "", nodeList: [] }; const typeTitle = qNode.querySelector(".type_tit")?.innerText || "未命名题型"; allStr += `${typeTitle}\n`; node.nodeName = typeTitle; const questions = qNode.querySelectorAll(".questionLi"); if (questions.length === 0) { console.log(`No questions found in section: ${typeTitle}`); } questions.forEach(question => { let qItem = { slt: [], q: "", qHtml: "", // 新增:保存混排的HTML内容 myAn: "", an: "", explanation: "", images: [], options: [], // 新增:保存选项的详细信息 questionMixedContent: null // 新增:题目的混排内容 }; const qNameElement = question.querySelector(".mark_name"); // 解析题目混排内容 if (qNameElement) { const mixedContent = parseMixedContent(qNameElement); qItem.q = qNameElement.innerText || "未找到题目"; qItem.qHtml = mixedContent.html; qItem.questionMixedContent = mixedContent; console.log(`题目 "${qItem.q.substring(0, 20)}..." 解析出混排内容:`, { textLength: qItem.q.length, imagesCount: mixedContent.images.length, html: mixedContent.html.substring(0, 100) + '...' }); // 处理题目中的图片 if (mixedContent.images.length > 0) { for (let img of mixedContent.images) { const imgPromise = getImageAsBase64(img.src) .then(base64Data => { const imageData = { id: img.id, src: img.src, alt: img.alt, data: base64Data, width: img.width, height: img.height, context: { type: 'question', questionPart: 'content' } }; qItem.images.push(imageData); console.log(`✅ 题目图片处理完成: ${img.alt}`); }) .catch(error => { console.error(`❌ 题目图片处理失败:`, error); qItem.images.push({ id: img.id, src: img.src, alt: img.alt, data: null, width: img.width, height: img.height, context: { type: 'question', questionPart: 'content' }, error: error.message }); }); imagePromises.push(imgPromise); } } } allStr += `${qItem.q}\n`; // 选项 - 改进版:支持图片选项和混排内容 const qSelectBox = question.querySelector(".mark_letter"); if (qSelectBox) { const qSelectItems = qSelectBox.getElementsByTagName("li"); Array.from(qSelectItems).forEach((qSelectItem, optionIndex) => { const optionContent = parseOptionContent(qSelectItem); if (optionContent.isImageOption) { // 纯图片选项 console.log(`选项 ${String.fromCharCode(65 + optionIndex)} 是图片选项:`, { text: optionContent.text, imagesCount: optionContent.images.length }); // 添加到选项列表 const optionText = optionContent.text || `${String.fromCharCode(65 + optionIndex)}. [图片选项]`; qItem.slt.push(optionText); allStr += `${optionText}\n`; // 保存选项详细信息 qItem.options.push({ index: optionIndex, letter: String.fromCharCode(65 + optionIndex), text: optionContent.text, isImageOption: true, images: optionContent.images, html: null }); // 处理选项图片 if (optionContent.images.length > 0) { for (let img of optionContent.images) { const imgPromise = getImageAsBase64(img.src) .then(base64Data => { const imageData = { src: img.src, alt: img.alt, data: base64Data, width: img.width, height: img.height, context: { type: 'option', optionIndex: optionIndex, questionPart: 'options' } }; qItem.images.push(imageData); console.log(`✅ 选项${String.fromCharCode(65 + optionIndex)}图片处理完成: ${img.alt}`); }) .catch(error => { console.error(`❌ 选项${String.fromCharCode(65 + optionIndex)}图片处理失败:`, error); qItem.images.push({ src: img.src, alt: img.alt, data: null, width: img.width, height: img.height, context: { type: 'option', optionIndex: optionIndex, questionPart: 'options' }, error: error.message }); }); imagePromises.push(imgPromise); } } } else { // 文字选项或混排选项 const qSelectText = qSelectItem.innerText; if (qSelectText) { allStr += `${qSelectText}\n`; qItem.slt.push(qSelectText); qItem.options.push({ index: optionIndex, letter: String.fromCharCode(65 + optionIndex), text: qSelectText, isImageOption: false, images: optionContent.images || [], html: optionContent.html }); // 处理混排选项中的图片 if (optionContent.images && optionContent.images.length > 0) { for (let img of optionContent.images) { const imgPromise = getImageAsBase64(img.src) .then(base64Data => { const imageData = { id: img.id, src: img.src, alt: img.alt, data: base64Data, width: img.width, height: img.height, context: { type: 'option', optionIndex: optionIndex, questionPart: 'options' } }; qItem.images.push(imageData); console.log(`✅ 选项${String.fromCharCode(65 + optionIndex)}混排图片处理完成: ${img.alt}`); }) .catch(error => { console.error(`❌ 选项${String.fromCharCode(65 + optionIndex)}混排图片处理失败:`, error); qItem.images.push({ id: img.id, src: img.src, alt: img.alt, data: null, width: img.width, height: img.height, context: { type: 'option', optionIndex: optionIndex, questionPart: 'options' }, error: error.message }); }); imagePromises.push(imgPromise); } } } } }); } // 答案 try { const qAnswer = question.querySelector(".mark_answer .colorGreen")?.innerText || ""; const qMyAnswer = question.querySelector(".mark_answer .colorDeep")?.innerText || ""; allStr += `${qMyAnswer}\n${qAnswer}\n`; qItem.myAn = qMyAnswer; qItem.an = qAnswer; // 尝试获取题目解析 const qExplanation = question.querySelector(".mark_explain")?.innerText || question.querySelector(".explanation")?.innerText || question.querySelector(".q_analysis")?.innerText || question.querySelector(".analyze")?.innerText || ""; if (qExplanation) { allStr += `${qExplanation}\n`; qItem.explanation = qExplanation; } } catch (err) { console.log("Error parsing answers or explanation:", err); } node.nodeList.push(qItem); }); allQsObject.push(node); }); // 等待所有图片处理完成 if (imagePromises.length > 0) { updateStatus(`正在处理 ${imagePromises.length} 个图片...`, "active"); showProgressBar(); // 添加进度监控 let completedImages = 0; const totalImages = imagePromises.length; const progressPromises = imagePromises.map(promise => promise.finally(() => { completedImages++; const percent = Math.floor((completedImages / totalImages) * 100); updateProgress(percent, `处理图片 ${completedImages}/${totalImages}`); }) ); Promise.all(progressPromises) .then(() => { console.log("所有图片已处理完成"); updateStatus(`解析完成,共处理 ${imagePromises.length} 个图片`, "success"); hideProgressBar(); displayQuestions(allQsObject); setProcessingState(false); // 使用动画显示成功反馈 if (animationsEnabled) { showToast("题目解析完成!", "success"); } }) .catch(error => { console.error("处理图片时出错:", error); updateStatus("处理图片时出错,但已显示可用内容", "error"); hideProgressBar(); displayQuestions(allQsObject); setProcessingState(false); // 使用动画显示错误反馈 if (animationsEnabled) { showToast("处理图片时出错,但已显示可用内容", "error"); } }); } else { updateStatus("解析完成,未发现图片", "success"); displayQuestions(allQsObject); setProcessingState(false); // 使用动画显示成功反馈 if (animationsEnabled) { showToast("题目解析完成!", "success"); } } console.log("解析完成, 找到题目总数:", allQsObject.reduce((sum, node) => sum + node.nodeList.length, 0)); // 更新导出按钮状态 updateExportButtons(); // 更新AI解析按钮状态 updateAIWrongQuestionsButton(); } // ===== 中国大学MOOC平台解析 ===== function parseMoocQuestions() { const qlistElement = document.getElementById(`${BOX_ID}_qlist`); // 获取测验标题 const quizTitleEl = document.querySelector('.u-learn-moduletitle .j-title') || document.querySelector('.m-quizScore .j-title') || document.querySelector('h2.j-title'); const quizTitle = quizTitleEl ? quizTitleEl.textContent.trim() : 'MOOC测验'; // 获取所有题目项 const questionItems = document.querySelectorAll('.u-questionItem'); if (questionItems.length === 0) { if (qlistElement) { qlistElement.innerHTML = `
🎓
未找到试题内容
请确认已进入测验的查看解析页面
`; updateStatus("未找到题目内容,请确保已提交测验并查看解析", "error"); setProcessingState(false); } return; } updateStatus(`分析MOOC页面结构...找到 ${questionItems.length} 道题目`, "active"); const imagePromises = []; // 按题型分组收集 const typeGroups = {}; let globalIndex = 0; questionItems.forEach((qElement) => { let qItem = { slt: [], q: "", qHtml: "", myAn: "", an: "", explanation: "", images: [], options: [], questionMixedContent: null }; // 解析题目序号 const positionEl = qElement.querySelector('.position'); const questionNum = positionEl ? positionEl.textContent.trim() : String(globalIndex + 1); // 解析题目类型 const cateEl = qElement.querySelector('.qaCate span'); const questionType = cateEl ? cateEl.textContent.trim() : '未知'; const scoreEl = qElement.querySelector('.qaCate'); const scoreText = scoreEl ? scoreEl.textContent.match(/\((\d+)分\)/)?.[1] || '' : ''; // 解析题目内容 const richTxtEl = qElement.querySelector('.j-richTxt'); if (richTxtEl) { const mixedContent = parseMixedContent(richTxtEl); qItem.q = `${questionNum}. ${richTxtEl.innerText.trim()}`; qItem.qHtml = mixedContent.html; qItem.questionMixedContent = mixedContent; if (mixedContent.images.length > 0) { for (let img of mixedContent.images) { const imgPromise = getImageAsBase64(img.src).then(base64Data => { qItem.images.push({ id: img.id, src: img.src, alt: img.alt, data: base64Data, width: img.width, height: img.height, context: { type: 'question', questionPart: 'content' } }); }).catch(error => { qItem.images.push({ id: img.id, src: img.src, alt: img.alt, data: null, width: img.width, height: img.height, context: { type: 'question', questionPart: 'content' }, error: error.message }); }); imagePromises.push(imgPromise); } } } else { qItem.q = `${questionNum}. `; } allStr += `${qItem.q}\n`; // 解析选项(选择题) const choicesEl = qElement.querySelector('.j-choicebox'); if (choicesEl) { const choiceItems = choicesEl.querySelectorAll('li'); choiceItems.forEach((choiceItem, optionIndex) => { const optionPosEl = choiceItem.querySelector('.optionPos'); const optionCntEl = choiceItem.querySelector('.optionCnt'); const letter = optionPosEl ? optionPosEl.textContent.trim() : String.fromCharCode(65 + optionIndex) + '.'; const optionText = optionCntEl ? optionCntEl.innerText.trim() : ''; const fullOptionText = `${letter} ${optionText}`; qItem.slt.push(fullOptionText); allStr += `${fullOptionText}\n`; // 判断是否选中(我的答案) const isChecked = choiceItem.classList.contains('checked'); qItem.options.push({ index: optionIndex, letter: letter.replace('.', ''), text: fullOptionText, isChecked: isChecked, isImageOption: false, images: [], html: null }); // 处理选项中的图片 if (optionCntEl) { const optImgs = optionCntEl.querySelectorAll('img'); optImgs.forEach(img => { const imgPromise = getImageAsBase64(img.src).then(base64Data => { qItem.images.push({ src: img.src, alt: img.alt || `选项${letter}图片`, data: base64Data, width: img.naturalWidth || img.width, height: img.naturalHeight || img.height, context: { type: 'option', optionIndex, questionPart: 'options' } }); }).catch(error => { qItem.images.push({ src: img.src, alt: img.alt || `选项${letter}图片`, data: null, width: img.naturalWidth || img.width, height: img.naturalHeight || img.height, context: { type: 'option', optionIndex, questionPart: 'options' }, error: error.message }); }); imagePromises.push(imgPromise); }); } }); } // 解析我的答案(checked选项 / 填空题答案) const checkedOptions = qElement.querySelectorAll('li.checked'); if (checkedOptions.length > 0) { const myAnswers = []; checkedOptions.forEach(opt => { const pos = opt.querySelector('.optionPos'); if (pos) myAnswers.push(pos.textContent.trim().replace('.', '')); }); qItem.myAn = myAnswers.join(''); } else { // 填空题/主观题:尝试获取输入框中的答案 const inputEl = qElement.querySelector('input[type="text"], textarea'); if (inputEl) { qItem.myAn = inputEl.value || inputEl.textContent.trim() || ''; } } // 解析正确答案 - MOOC在解析模式下标识正确答案 const correctIndicator = qElement.querySelector('.f-rightAnswer, .j-rightAnswer'); if (correctIndicator) { qItem.an = correctIndicator.textContent.trim(); } else { const ansArea = qElement.querySelector('.j-answer, .answerArea, .f-ans'); if (ansArea) { qItem.an = ansArea.textContent.trim(); } } // 对于满分题,正确答案即我的答案 if (!qItem.an && qItem.myAn) { qItem.an = qItem.myAn; } // 解析题目解析 const analysisEl = qElement.querySelector('.j-analysis, .f-analysis, .analysis'); if (analysisEl) { qItem.explanation = analysisEl.innerText.trim(); } allStr += `我的答案: ${qItem.myAn}\n正确答案: ${qItem.an}\n`; if (qItem.explanation) allStr += `解析: ${qItem.explanation}\n`; // 按题型分组 const typeKey = questionType || '未知题型'; if (!typeGroups[typeKey]) typeGroups[typeKey] = { nodeName: typeKey, nodeList: [] }; typeGroups[typeKey].nodeList.push(qItem); globalIndex++; }); allQsObject = Object.values(typeGroups); if (imagePromises.length > 0) { updateStatus(`正在处理 ${imagePromises.length} 个图片...`, "active"); showProgressBar(); let completedImages = 0; const totalImages = imagePromises.length; const progressPromises = imagePromises.map(promise => promise.finally(() => { completedImages++; const percent = Math.floor((completedImages / totalImages) * 100); updateProgress(percent, `处理图片 ${completedImages}/${totalImages}`); }) ); Promise.all(progressPromises) .then(() => { updateStatus(`解析完成,共 ${allQsObject.reduce((s, n) => s + n.nodeList.length, 0)} 道题目 (${Object.keys(typeGroups).length} 种题型)`, "success"); hideProgressBar(); displayQuestions(allQsObject); setProcessingState(false); if (animationsEnabled) { showToast("题目解析完成!", "success"); } }) .catch(error => { updateStatus("处理图片时出错,但已显示可用内容", "error"); hideProgressBar(); displayQuestions(allQsObject); setProcessingState(false); }); } else { const totalQ = allQsObject.reduce((s, n) => s + n.nodeList.length, 0); updateStatus(`解析完成! 共找到 ${totalQ} 道题目 (${Object.keys(typeGroups).length} 种题型)`, "success"); displayQuestions(allQsObject); setProcessingState(false); if (animationsEnabled) { showToast("题目解析完成!", "success"); } } console.log("MOOC解析完成, 找到题目总数:", allQsObject.reduce((sum, node) => sum + node.nodeList.length, 0)); updateExportButtons(); updateAIWrongQuestionsButton(); } // 更新AI解析按钮状态 function updateAIWrongQuestionsButton() { const btnAIWrongQuestions = document.getElementById(`${BOX_ID}_ai_wrong_btn`); if (!btnAIWrongQuestions) return; // 计算题目数量 let totalCount = 0; allQsObject.forEach(node => { node.nodeList.forEach(qItem => { totalCount++; }); }); // 更新按钮状态和文本 if (totalCount > 0) { btnAIWrongQuestions.disabled = isProcessing || isAnswering; btnAIWrongQuestions.innerHTML = `🤖AI解析${totalCount}道题目`; } else { btnAIWrongQuestions.disabled = true; btnAIWrongQuestions.innerHTML = `🤖没有找到题目`; } } // 显示问题 - 支持选择功能和AI解答 function displayQuestions(qObject) { const qlistElement = document.getElementById(`${BOX_ID}_qlist`); if (!qlistElement) return; // 清空已选题目 selectedQuestions.clear(); lastSelectedQuestionId = null; // 题目总数和统计信息 const totalQuestions = qObject.reduce((sum, node) => sum + node.nodeList.length, 0); let correctCount = 0; let wrongCount = 0; // 计算正确和错误题目数量 qObject.forEach(node => { node.nodeList.forEach(qItem => { if (qItem.myAn && qItem.an) { if (qItem.myAn === qItem.an) { correctCount++; } else { wrongCount++; } } }); }); if (totalQuestions === 0) { qlistElement.innerHTML = `
📝
未找到题目
请点击"解析题目"按钮开始解析
`; return; } // 题目选择控制区 const selectionControlsHtml = `
题目选择
已选: 0/${totalQuestions}
`; // 统计信息区域 const statsHtml = `
题目统计
${totalQuestions}
题目总数
${qObject.length}
题型数量
${correctCount}
正确题目
${wrongCount}
错误题目
`; let sectionsHtml = ""; let questionIdCounter = 0; // 用于生成唯一的题目ID qObject.forEach((qNode) => { let questionsHtml = ""; qNode.nodeList.forEach((qItem, index) => { // 为每个题目分配一个唯一ID const questionId = `q_${questionIdCounter++}`; qItem.id = questionId; // 在原始数据中也存储ID,方便后续处理 // 处理选项 let optionsHtml = ""; if (qItem.slt.length > 0) { optionsHtml = `
${qItem.slt.map(opt => `
${opt}
`).join('')}
`; } // 处理答案 const myAnswerHtml = hideMyAnswers ? '' : `
我的答案: ${qItem.myAn}
`; // 答案匹配指示 const mismatchHtml = (!hideMyAnswers && qItem.myAn && qItem.an && qItem.myAn !== qItem.an) ? `
答案不匹配
` : ''; // 处理题目解析 const explanationHtml = showExplanation && qItem.explanation ? `
题目解析:
${qItem.explanation}
` : ''; // 处理图片 let imagesHtml = ''; if (qItem.images && qItem.images.length > 0) { qItem.images.forEach(img => { const imgUrl = img.data || img.src; imagesHtml += `
${img.alt}
${img.alt}
`; }); } // AI解答按钮 const aiButtonHtml = `
`; // 题目选择框 const checkboxHtml = `
`; // 判断是否为错题 const isWrong = !hideMyAnswers && qItem.myAn && qItem.an && qItem.myAn !== qItem.an; const isCorrect = !hideMyAnswers && qItem.myAn && qItem.an && qItem.myAn === qItem.an; // 添加数据属性,用于筛选 const dataAttributes = ` data-question-id="${questionId}" data-question-type="${qNode.nodeName}" data-is-wrong="${isWrong ? 'true' : 'false'}" data-is-correct="${isCorrect ? 'true' : 'false'}" `; const questionHtml = `
${checkboxHtml}
${qItem.q}
${imagesHtml} ${optionsHtml}
${myAnswerHtml}
正确答案: ${qItem.an}
${mismatchHtml}
${explanationHtml} ${aiButtonHtml}
`; questionsHtml += questionHtml; // 记录问题数据用于AI解答 activeQuestions[questionId] = { questionText: qItem.q, options: qItem.slt, correctAnswer: qItem.an, myAnswer: qItem.myAn, explanation: qItem.explanation }; }); const sectionHtml = `
${qNode.nodeName} (${qNode.nodeList.length}题)
${questionsHtml}
`; sectionsHtml += sectionHtml; }); qlistElement.innerHTML = selectionControlsHtml + statsHtml + sectionsHtml; // 添加动画效果 if (animationsEnabled) { // 添加动画到题目区域 const sections = document.querySelectorAll(`.${TOOL_ID}_question_section`); sections.forEach((section, index) => { setTimeout(() => { section.classList.add('animated'); }, index * 100); // 错开时间添加动画效果 }); } // 添加题目选择事件监听 setupQuestionSelectionListeners(); // 添加AI解答按钮事件监听 setupAIAnswerListeners(); // 更新选中计数 updateSelectionCount(); // 添加已解析题目选择按钮事件 const selectAnalyzedBtn = document.getElementById(`${TOOL_ID}_select_analyzed`); if (selectAnalyzedBtn) { selectAnalyzedBtn.addEventListener('click', function() { // 先清空选择 selectedQuestions.clear(); document.querySelectorAll(`.${TOOL_ID}_question_selector`).forEach(checkbox => { checkbox.checked = false; }); // 选中已解析题目 let analyzedCount = 0; allQsObject.forEach(node => { node.nodeList.forEach(qItem => { if (qItem.aiAnswer) { analyzedCount++; const checkbox = document.querySelector(`.${TOOL_ID}_question_selector[data-question-id="${qItem.id}"]`); if (checkbox) { checkbox.checked = true; selectedQuestions.add(qItem.id); } // 高亮显示已解析的题目 if (animationsEnabled) { const item = document.querySelector(`.${TOOL_ID}_question_item[data-question-id="${qItem.id}"]`); if (item) { item.style.animation = `${TOOL_ID}_highlight 1s`; setTimeout(() => { item.style.animation = ''; }, 1000); } } } }); }); updateSelectionCount(); // 添加动画反馈 if (animationsEnabled) { showToast(`已选择 ${analyzedCount} 道已解析题目`, "info"); } }); } } // 添加题目选择相关的事件监听器 function setupQuestionSelectionListeners() { // 单个题目复选框点击 document.querySelectorAll(`.${TOOL_ID}_question_selector`).forEach(checkbox => { checkbox.addEventListener('click', function(e) { const questionId = this.dataset.questionId; // Shift+点击 支持多选 if (e.shiftKey && lastSelectedQuestionId) { const checkboxes = Array.from(document.querySelectorAll(`.${TOOL_ID}_question_selector`)); const currentIndex = checkboxes.indexOf(this); const lastIndex = checkboxes.findIndex(cb => cb.dataset.questionId === lastSelectedQuestionId); const start = Math.min(currentIndex, lastIndex); const end = Math.max(currentIndex, lastIndex); for (let i = start; i <= end; i++) { const cb = checkboxes[i]; cb.checked = this.checked; if (this.checked) { selectedQuestions.add(cb.dataset.questionId); } else { selectedQuestions.delete(cb.dataset.questionId); } } } else { // 普通点击 if (this.checked) { selectedQuestions.add(questionId); } else { selectedQuestions.delete(questionId); } lastSelectedQuestionId = questionId; } updateSelectionCount(); }); }); // 全选按钮 document.getElementById(`${TOOL_ID}_select_all`).addEventListener('click', function() { document.querySelectorAll(`.${TOOL_ID}_question_selector`).forEach(checkbox => { checkbox.checked = true; selectedQuestions.add(checkbox.dataset.questionId); }); updateSelectionCount(); // 添加动画反馈 if (animationsEnabled) { showToast(`已选择全部 ${selectedQuestions.size} 个题目`, "success"); } }); // 取消全选按钮 document.getElementById(`${TOOL_ID}_deselect_all`).addEventListener('click', function() { document.querySelectorAll(`.${TOOL_ID}_question_selector`).forEach(checkbox => { checkbox.checked = false; selectedQuestions.delete(checkbox.dataset.questionId); }); updateSelectionCount(); // 添加动画反馈 if (animationsEnabled) { showToast("已取消全部选择", "info"); } }); // 选择错题按钮 document.getElementById(`${TOOL_ID}_select_wrong`).addEventListener('click', function() { // 先清空选择 selectedQuestions.clear(); document.querySelectorAll(`.${TOOL_ID}_question_selector`).forEach(checkbox => { checkbox.checked = false; }); // 选中错题 const wrongItems = document.querySelectorAll(`.${TOOL_ID}_question_item[data-is-wrong="true"]`); wrongItems.forEach(item => { const questionId = item.dataset.questionId; const checkbox = item.querySelector(`.${TOOL_ID}_question_selector`); if (checkbox) { checkbox.checked = true; selectedQuestions.add(questionId); } // 添加动画效果来高亮显示选中的错题 if (animationsEnabled) { item.style.animation = `${TOOL_ID}_highlight 1s`; setTimeout(() => { item.style.animation = ''; }, 1000); } }); updateSelectionCount(); // 添加动画反馈 if (animationsEnabled) { showToast(`已选择 ${wrongItems.length} 道错题`, "info"); } }); // 选择正确题按钮 document.getElementById(`${TOOL_ID}_select_correct`).addEventListener('click', function() { // 先清空选择 selectedQuestions.clear(); document.querySelectorAll(`.${TOOL_ID}_question_selector`).forEach(checkbox => { checkbox.checked = false; }); // 选中正确题 const correctItems = document.querySelectorAll(`.${TOOL_ID}_question_item[data-is-correct="true"]`); correctItems.forEach(item => { const questionId = item.dataset.questionId; const checkbox = item.querySelector(`.${TOOL_ID}_question_selector`); if (checkbox) { checkbox.checked = true; selectedQuestions.add(questionId); } // 添加动画效果来高亮显示选中的正确题 if (animationsEnabled) { item.style.animation = `${TOOL_ID}_highlight 1s`; setTimeout(() => { item.style.animation = ''; }, 1000); } }); updateSelectionCount(); // 添加动画反馈 if (animationsEnabled) { showToast(`已选择 ${correctItems.length} 道正确题`, "success"); } }); } // 更新选中题目的数量显示 function updateSelectionCount() { const totalQuestions = document.querySelectorAll(`.${TOOL_ID}_question_selector`).length; const countElement = document.getElementById(`${TOOL_ID}_selection_count`); if (countElement) { countElement.textContent = `已选: ${selectedQuestions.size}/${totalQuestions}`; // 如果有题目被选中,启用导出按钮,否则禁用 const exportButtons = document.querySelectorAll(`#${BOX_ID}_excel_btn, #${BOX_ID}_word_btn, #${BOX_ID}_pdf_btn, #${BOX_ID}_preview_btn, #${BOX_ID}_kaoshibao_btn`); exportButtons.forEach(button => { button.disabled = (selectedQuestions.size === 0 && allQsObject.length === 0) || isProcessing; }); } } // 查找所有图片 - 修复版:按DOM顺序查找并标记位置 function findAllImages(element) { if (!element) return []; const images = []; const walker = document.createTreeWalker( element, NodeFilter.SHOW_ELEMENT, { acceptNode: function(node) { return node.tagName === 'IMG' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; } } ); let position = 0; let node; while (node = walker.nextNode()) { if (node.src) { // 计算图片在DOM中的位置和上下文 const rect = node.getBoundingClientRect(); const context = getImageContext(node); images.push({ element: node, src: node.src, alt: node.alt || `图片${position + 1}`, position: position++, domOrder: position, offsetTop: rect.top + window.scrollY, width: node.naturalWidth || node.width || 0, height: node.naturalHeight || node.height || 0, // 关键:标记图片所在的上下文 context: context }); } } // 按照DOM出现顺序和上下文排序 return images.sort((a, b) => { // 首先按上下文类型排序(题目内容 > 选项内容) const contextOrder = { 'question': 0, 'option': 1, 'answer': 2, 'explanation': 3, 'other': 4 }; const aContext = contextOrder[a.context.type] || 4; const bContext = contextOrder[b.context.type] || 4; if (aContext !== bContext) { return aContext - bContext; } // 在选项中按选项索引排序 if (a.context.type === 'option' && b.context.type === 'option') { return a.context.optionIndex - b.context.optionIndex; } // 其他情况按DOM顺序 return a.domOrder - b.domOrder; }); } // 解析混排内容 - 保持文字和图片的原始顺序 function parseMixedContent(element) { if (!element) return { html: '', images: [] }; const walker = document.createTreeWalker( element, NodeFilter.SHOW_ALL, { acceptNode: function(node) { // 接受文本节点和图片元素 if (node.nodeType === Node.TEXT_NODE || (node.nodeType === Node.ELEMENT_NODE && (node.tagName === 'IMG' || node.tagName === 'BR' || node.tagName === 'SPAN' || node.tagName === 'DIV'))) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_SKIP; } } ); let htmlContent = ''; let images = []; let imageIndex = 0; let node; while (node = walker.nextNode()) { if (node.nodeType === Node.TEXT_NODE) { // 处理文本节点 const text = node.textContent.trim(); if (text) { htmlContent += text; } } else if (node.tagName === 'IMG') { // 处理图片节点 const imgSrc = node.src || node.getAttribute('src'); if (imgSrc) { const imgId = `img_${Date.now()}_${imageIndex++}`; htmlContent += `[图片${imageIndex}]`; images.push({ id: imgId, src: imgSrc, alt: node.alt || `图片${imageIndex}`, element: node, width: node.naturalWidth || node.width || 0, height: node.naturalHeight || node.height || 0 }); } } else if (node.tagName === 'BR') { htmlContent += '
'; } } return { html: htmlContent, images: images }; } // 解析选项内容 - 支持纯图片选项 function parseOptionContent(optionElement) { if (!optionElement) return { text: '', images: [], isImageOption: false }; const textContent = optionElement.textContent.trim(); const images = optionElement.querySelectorAll('img'); // 判断是否为纯图片选项(没有文字或只有选项标识如A、B、C、D) const isImageOption = images.length > 0 && ( !textContent || textContent.match(/^[A-Z]\s*$/) || textContent.length < 3 ); if (isImageOption) { // 纯图片选项 const imageList = Array.from(images).map((img, index) => ({ src: img.src || img.getAttribute('src'), alt: img.alt || `选项图片${index + 1}`, element: img, width: img.naturalWidth || img.width || 0, height: img.naturalHeight || img.height || 0 })); return { text: textContent, // 保留选项标识(如A、B等) images: imageList, isImageOption: true }; } else { // 文字选项或混排选项 const mixedContent = parseMixedContent(optionElement); return { text: textContent, html: mixedContent.html, images: mixedContent.images, isImageOption: false }; } } // 使用Fetch API获取图片并转换为Base64 function getImageAsBase64(url) { return new Promise((resolve, reject) => { // 检查URL是否有效 if (!url || url.trim() === '' || !url.match(/^(http|https|data)/i)) { return reject(new Error('无效的图片URL')); } // 对于已经是base64的数据,直接返回 if (url.startsWith('data:image')) { return resolve(url); } // 构建安全的URL(处理相对路径) let safeUrl = url; if (url.startsWith('//')) { safeUrl = window.location.protocol + url; } else if (url.startsWith('/')) { safeUrl = window.location.origin + url; } // 创建新的图片对象 const img = new Image(); img.crossOrigin = 'Anonymous'; // 尝试解决跨域问题 img.onload = function() { try { const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth || img.width; canvas.height = img.naturalHeight || img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); // 获取Base64数据 const dataURL = canvas.toDataURL('image/png'); resolve(dataURL); } catch (e) { console.error('转换图片到Base64失败:', e); // 如果转换失败,则返回原始URL resolve(url); } }; img.onerror = function() { console.error(`加载图片失败: ${safeUrl}`); // 如果加载失败,则返回原始URL resolve(url); }; // 设置src开始加载图片 img.src = safeUrl; }); } // 准备导出数据 - 支持选择性导出 function prepareExportData() { const titleElement = document.querySelector(".mark_title"); // 使用自定义标题(如果有),否则使用页面标题 let baseFilename = customTitle || (titleElement ? titleElement.innerText : "题目解析"); // 如果是空字符串,使用默认标题 if (!baseFilename || baseFilename.trim() === "") { baseFilename = "题目解析"; } // 保存原始标题(用于章节等字段,不含时间戳/选中数量等后缀) let title = (customTitle || (titleElement ? titleElement.innerText : "")).replace(/<[^>]*>/g, '').trim(); if (!title) title = "题目解析"; // 如果启用了时间戳选项,添加当前时间作为后缀 if (includeTimestamp) { const now = new Date(); const timeStr = now.getFullYear() + ('0' + (now.getMonth() + 1)).slice(-2) + ('0' + now.getDate()).slice(-2) + '_' + ('0' + now.getHours()).slice(-2) + ('0' + now.getMinutes()).slice(-2); baseFilename += '_' + timeStr; } // 如果已选中题目,添加选中数量信息 if (selectedQuestions.size > 0 && selectedQuestions.size < document.querySelectorAll(`.${TOOL_ID}_question_selector`).length) { baseFilename += `_已选${selectedQuestions.size}题`; } // 修改数据处理逻辑,只包含选中的题目 const data = []; allQsObject.forEach(qNode => { qNode.nodeList.forEach(qItem => { // 如果没有题目被选中,则导出所有题目 // 如果有题目被选中,则只导出被选中的题目 if (selectedQuestions.size === 0 || selectedQuestions.has(qItem.id)) { const exportItem = { '题目类型': qNode.nodeName, '题目': qItem.q, '题目混排HTML': qItem.qHtml || null, // 新增:混排HTML内容 '选项': qItem.slt.join("\n"), '选项详细': qItem.options || null, // 新增:选项详细信息 '我的答案': hideMyAnswers ? '[已隐藏]' : qItem.myAn, '正确答案': qItem.an, '是否正确': hideMyAnswers ? '-' : (qItem.myAn === qItem.an ? '✓' : '✗'), '题目解析': qItem.explanation || '-', 'aiAnswer': qItem.aiAnswer || null // 添加AI解答 }; // 添加图片信息 exportItem['图片'] = qItem.images && qItem.images.length > 0 ? qItem.images : null; data.push(exportItem); } }); }); return { data, baseFilename, title }; } // 下载Excel(按照题库模板格式:试题类型/试题题目/参考答案/A-G选项/章节/解析) function downloadExcel(data, filename, title) { if (!data || data.length === 0) { updateStatus('没有数据可供下载', 'error'); setProcessingState(false); return; } try { updateStatus("正在创建Excel文件...", "active"); // 检查XLSX是否可用 if (typeof XLSX === 'undefined') { updateStatus("错误: XLSX库未加载,请检查脚本设置中的 @require", "error"); setProcessingState(false); return; } // 章节字段使用本次测试/作业名称 const chapterName = (title || '').replace(/<[^>]*>/g, '').trim(); // 题型映射:映射为题库模板支持的题型(单选/多选/判断/填空/简答) function mapQuestionType(type) { if (!type) return '单选'; const t = type.trim(); if (t.includes('单选')) return '单选'; if (t.includes('多选') || t.includes('不定项')) return '多选'; if (t.includes('判断')) return '判断'; if (t.includes('填空')) return '填空'; if (t.includes('简答') || t.includes('问答') || t.includes('论述') || t.includes('计算')) return '简答'; // 默认为单选 return '单选'; } // 清理题目文本(去除HTML标签、多余空白) function cleanQuestionText(text) { if (!text) return ''; return String(text).replace(/<[^>]*>/g, '').replace(/ /g, ' ').replace(/\s+/g, ' ').trim(); } // 解析选项文本,提取各选项内容(A-G,共7个) function parseOptions(optionStr) { const options = ['', '', '', '', '', '', '']; if (!optionStr) return options; const parts = optionStr.split(/(?=[A-H]\s*[.、.::])/i); let foundByLetter = false; parts.forEach(part => { const match = part.match(/^([A-H])\s*[.、.::]?\s*([\s\S]*)/i); if (match) { const letter = match[1].toUpperCase(); const content = match[2].trim(); const idx = letter.charCodeAt(0) - 65; if (idx >= 0 && idx < 7) { options[idx] = content.replace(/<[^>]*>/g, '').replace(/ /g, ' ').trim(); foundByLetter = true; } } }); // 如果没有按字母分割成功,按换行符分割 if (!foundByLetter) { const lines = optionStr.split(/\n/).map(s => s.trim()).filter(s => s); lines.forEach((line, idx) => { if (idx < 7) { options[idx] = line.replace(/<[^>]*>/g, '').replace(/ /g, ' ').trim(); } }); } return options; } // 转换正确答案为题库模板格式 function convertCorrectAnswer(rawAnswer, questionType) { if (!rawAnswer) return ''; const answer = String(rawAnswer).replace(/<[^>]*>/g, '').trim(); if (questionType === '判断') { // 判断题:转换为"正确"/"错误" if (answer.includes('A') || answer === '√' || answer === '对' || answer.toLowerCase() === 'true' || answer === '正确' || answer === 'T' || answer === 'Y') { return '正确'; } if (answer.includes('B') || answer === '×' || answer === '错' || answer.toLowerCase() === 'false' || answer === '错误' || answer === 'F' || answer === 'N') { return '错误'; } return answer; } if (questionType === '填空' || questionType === '简答') { // 填空题/简答题:直接使用答案文本 return answer; } // 单选/多选:提取字母 const letters = answer.match(/[A-Ha-h]/g); if (letters && letters.length > 0) { // 去重并保持顺序 const seen = new Set(); const result = []; letters.forEach(l => { const up = l.toUpperCase(); if (!seen.has(up)) { seen.add(up); result.push(up); } }); return result.join(''); } return answer; } // 从AI解析文本中提取正确答案 function extractAnswerFromAI(aiText, questionType) { if (!aiText) return ''; const isMulti = (questionType === '多选'); const keywordMatch = aiText.match(/(?:正确答案|最终答案|参考答案)[::]*\s*([\s\S]{0,300})/i); if (!keywordMatch) return ''; const afterText = keywordMatch[1]; function dedupeLetters(letters) { const seen = new Set(); const result = []; letters.forEach(l => { const up = l.toUpperCase(); if (!seen.has(up)) { seen.add(up); result.push(up); } }); return result.join(''); } if (isMulti) { // 多选题:需要提取所有选项字母 // 1. 优先匹配连续字母组合(可能被 ** 等 markdown 符号包裹) const contiguous = afterText.match(/\*{0,2}\s*([A-H]{2,8})\s*\*{0,2}/i); if (contiguous) return contiguous[1].toUpperCase(); // 2. 匹配用分隔符分隔的多个字母 const sepMatch = afterText.match(/([A-Ha-h])\s*[、,,/\s]\s*([A-Ha-h](?:\s*[、,,/\s]\s*[A-Ha-h]){1,7})/); if (sepMatch) { const letters = sepMatch[0].match(/[A-Ha-h]/g); if (letters && letters.length >= 2) { return dedupeLetters(letters); } } // 3. 取关键词后第一段有效内容,收集其中的字母 const firstChunk = afterText.split(/[\n。;;]/)[0].replace(/\*+/g, ' ').trim(); if (firstChunk) { const letters = firstChunk.match(/[A-Ha-h]/g); if (letters && letters.length >= 2 && letters.length <= 8) { const nonSpaceLen = firstChunk.replace(/\s/g, '').length; if (nonSpaceLen > 0 && (letters.join('').length / nonSpaceLen) > 0.5) { return dedupeLetters(letters); } } } // 4. 回退:单个字母 const single = afterText.match(/\*{0,2}\s*([A-H])\s*(?:[.、.::\s\n*]|\*{0,2}\s*$)/i); if (single) return single[1].toUpperCase(); } else { // 单选题:只取第一个字母 const letterWithSep = afterText.match(/\*{0,2}\s*([A-H])\s*[.、.::\s\n*]/i); if (letterWithSep) return letterWithSep[1].toUpperCase(); const letterAlone = afterText.match(/\*{0,2}\s*([A-H])\s*(?:\*{0,2}\s*$|\n)/im); if (letterAlone) return letterAlone[1].toUpperCase(); } return ''; } // 构建题库模板格式的数据行 const rows = []; // 表头行(按照题库模板) const headerRow = ['试题类型', '试题题目', '参考答案', 'A选项', 'B选项', 'C选项', 'D选项', 'E选项', 'F选项', 'G选项', '章节', '解析']; data.forEach((item, index) => { const questionType = mapQuestionType(item['题目类型']); const questionText = cleanQuestionText(item['题目']); const options = parseOptions(item['选项']); // 计算正确答案 let correctAnswer = convertCorrectAnswer(item['正确答案'], questionType); if (!correctAnswer) { const aiAnswerText = item['aiAnswer'] ? item['aiAnswer'].replace(/<[^>]*>/g, '').trim() : ''; const extractedAnswer = extractAnswerFromAI(aiAnswerText, questionType); if (extractedAnswer) { correctAnswer = extractedAnswer; } } // 判断题:A选项=正确,B选项=错误 let optA = options[0], optB = options[1], optC = options[2], optD = options[3], optE = options[4], optF = options[5], optG = options[6]; if (questionType === '判断') { optA = '正确'; optB = '错误'; optC = ''; optD = ''; optE = ''; optF = ''; optG = ''; } // 解析:优先使用题目解析,若为空则使用AI解析 let explanation = ''; if (item['题目解析'] && item['题目解析'] !== '-') { explanation = String(item['题目解析']).replace(/<[^>]*>/g, '').trim(); } if (!explanation && item['aiAnswer']) { explanation = String(item['aiAnswer']).replace(/<[^>]*>/g, '').trim(); } rows.push([ questionType, questionText, correctAnswer, optA, optB, optC, optD, optE, optF, optG, chapterName, explanation ]); }); // 创建工作表 const ws = XLSX.utils.aoa_to_sheet([headerRow, ...rows]); // 设置列宽(参考题库模板) ws['!cols'] = [ { wch: 10 }, // 试题类型 { wch: 50 }, // 试题题目 { wch: 15 }, // 参考答案 { wch: 20 }, // A选项 { wch: 20 }, // B选项 { wch: 20 }, // C选项 { wch: 20 }, // D选项 { wch: 20 }, // E选项 { wch: 20 }, // F选项 { wch: 20 }, // G选项 { wch: 15 }, // 章节 { wch: 40 }, // 解析 ]; const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "试题"); XLSX.writeFile(wb, filename); updateStatus(`Excel文件已生成: ${filename}`, "success"); setProcessingState(false); // 添加动画反馈 if (animationsEnabled) { showToast(`Excel文件已成功生成: ${filename}`, "success"); } } catch (error) { console.error("下载Excel失败:", error); updateStatus(`下载Excel失败: ${error.message}`, "error"); setProcessingState(false); // 添加错误反馈 if (animationsEnabled) { showToast(`下载Excel失败: ${error.message}`, "error"); } } } // 渲染混排内容为HTML function renderMixedContentToHTML(htmlContent, images) { if (!htmlContent || !images || images.length === 0) { return htmlContent || ''; } let result = htmlContent; // 替换图片占位符为实际图片HTML images.forEach(img => { const placeholder = `[图片${img.alt.match(/\d+/) || ''}]`; const imgSrc = img.data || img.src; const safeAlt = (img.alt || '图片').replace(/"/g, """); const imgHtml = `${safeAlt}`; result = result.replace(placeholder, imgHtml); }); return result; } // 处理题目标题,去除重复编号 function processQuestionTitle(title, index) { if (!title) return `${index + 1}. `; // 清理各种可能的编号格式 let cleanTitle = title.trim(); // 检查是否已有编号 const hasNumbering = /^\s*(?:\d+[\s.、.]|[((]\s*\d+\s*[))]|第\s*\d+\s*[题問问])/i.test(cleanTitle); // 只有在没有编号的情况下添加编号 if (!hasNumbering) { cleanTitle = `${index + 1}. ${cleanTitle}`; } return cleanTitle; } // 处理答案文本,清理前缀和格式 function processAnswer(answerText) { if (!answerText) return ""; let answer = answerText.trim(); // 移除可能存在的"答案:"、"正确答案:"等前缀 answer = answer.replace(/^(答案[::]\s*|正确答案[::]\s*|解析[::]\s*)/i, ''); return answer; } // 下载Word文档 function downloadWord(data, filename) { if (!data || data.length === 0) { updateStatus('没有数据可供下载', 'error'); setProcessingState(false); return; } try { updateStatus("正在创建Word文档...", "active"); showProgressBar(); updateProgress(10, "准备生成Word..."); // 按题型分组 const groupedData = data.reduce((groups, item) => { const type = item['题目类型']; if (!groups[type]) { groups[type] = []; } groups[type].push(item); return groups; }, {}); updateProgress(30, "正在格式化内容..."); // 生成HTML内容 - 使用最简单的格式以确保兼容性 let htmlContent = ` ${filename}

${filename.replace('.docx', '')}

`; updateProgress(50, "添加题目内容..."); // 添加每个题型部分 Object.keys(groupedData).forEach((type, typeIndex) => { const questions = groupedData[type]; htmlContent += `

${type}

`; // 添加每个问题 questions.forEach((item, index) => { // 处理题目编号,去除可能的重复编号 let questionTitle = processQuestionTitle(item['题目'] || "", index); htmlContent += `
`; // 显示题目内容 - 支持混排内容 if (item['题目混排HTML']) { // 如果有混排HTML内容,使用混排显示 const questionImages = (item['图片'] || []).filter(img => img.context?.type === 'question' && img.id ); const mixedContent = renderMixedContentToHTML(item['题目混排HTML'], questionImages); htmlContent += `
${processQuestionTitle('', index)}${mixedContent}
`; } else { // 传统方式:先显示文字,再显示图片 htmlContent += `
${questionTitle}
`; // 显示题目图片 const questionImages = (item['图片'] || []).filter(img => img.context?.type === 'question' && img.context?.questionPart === 'content' ); if (questionImages.length > 0) { htmlContent += '
'; questionImages.forEach((img, imgIndex) => { const imgSrc = img.data || img.src; const safeAlt = (img.alt || `题目图片${imgIndex + 1}`).replace(/"/g, """); htmlContent += `
${safeAlt}
${safeAlt}
`; }); htmlContent += '
'; } } // 添加选项 - 支持图片选项和混排内容 if (item['选项详细'] && Array.isArray(item['选项详细'])) { // 使用新的选项数据结构 htmlContent += `
`; item['选项详细'].forEach((option) => { if (option.isImageOption) { // 纯图片选项 htmlContent += `
${option.letter}. `; // 显示选项图片 const optionImages = (item['图片'] || []).filter(img => img.context?.type === 'option' && img.context?.optionIndex === option.index ); if (optionImages.length > 0) { optionImages.forEach((img, imgIndex) => { const imgSrc = img.data || img.src; const safeAlt = (img.alt || `选项${option.letter}图片${imgIndex + 1}`).replace(/"/g, """); htmlContent += `${safeAlt}`; }); } else { htmlContent += `[图片选项]`; } htmlContent += `
`; } else { // 文字选项或混排选项 if (option.html && option.images && option.images.length > 0) { // 混排选项 const mixedContent = renderMixedContentToHTML(option.html, option.images); htmlContent += `
${option.letter}. ${mixedContent}
`; } else { // 纯文字选项 htmlContent += `
${option.text}
`; } } }); htmlContent += `
`; } else if (item['选项']) { // 兼容旧的选项格式 htmlContent += `
`; const options = item['选项'].split('\n'); options.forEach((option, optionIndex) => { if (option.trim()) { htmlContent += `
${option}
`; // 显示该选项对应的图片 if (item['图片'] && Array.isArray(item['图片'])) { const optionImages = item['图片'].filter(img => img.context?.type === 'option' && img.context?.optionIndex === optionIndex ); if (optionImages.length > 0) { htmlContent += '
'; optionImages.forEach((img, imgIndex) => { const imgSrc = img.data || img.src; const safeAlt = (img.alt || `选项${String.fromCharCode(65 + optionIndex)}图片${imgIndex + 1}`).replace(/"/g, """); htmlContent += `
${safeAlt}
${safeAlt}
`; }); htmlContent += '
'; } } } }); htmlContent += `
`; } // 添加答案区域 htmlContent += `
`; // 添加我的答案 - 如果未隐藏 if (!hideMyAnswers) { const myAnswer = processAnswer(item['我的答案']); htmlContent += `
我的答案: ${myAnswer}
`; } // 添加正确答案 if (item['正确答案']) { const correctAnswer = processAnswer(item['正确答案']); htmlContent += `
正确答案: ${correctAnswer}
`; } // 添加答案不匹配指示 if (!hideMyAnswers && item['是否正确'] === '✗') { htmlContent += `
答案不匹配
`; } htmlContent += `
`; // 添加题目解析 - 如果启用显示解析并且有解析内容 if (showExplanation && item['题目解析'] && item['题目解析'] !== '-') { htmlContent += `
题目解析:
${item['题目解析']}
`; } // 添加AI答案 - 如果有 if (item.aiAnswer) { htmlContent += `
AI解答:
${formatAnswer(item.aiAnswer)}
`; } htmlContent += `
`; // 更新进度 const progress = 50 + Math.floor((typeIndex / Object.keys(groupedData).length) * 40); updateProgress(progress, `处理第 ${typeIndex + 1}/${Object.keys(groupedData).length} 题型...`); }); }); htmlContent += ``; updateProgress(90, "创建下载链接..."); // 使用Blob API创建文档 const blob = new Blob([htmlContent], { type: 'application/vnd.ms-word;charset=utf-8' }); // 创建下载链接并触发下载 const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); updateProgress(100, "完成!"); setTimeout(() => { hideProgressBar(); updateStatus(`Word文档已成功生成并下载: ${filename}`, "success"); setProcessingState(false); // 添加动画反馈 if (animationsEnabled) { showToast(`Word文档已成功生成: ${filename}`, "success"); } }, 1000); } catch (error) { console.error("下载Word文档失败:", error); hideProgressBar(); updateStatus(`下载Word文档失败: ${error.message}`, "error"); setProcessingState(false); // 添加错误反馈 if (animationsEnabled) { showToast(`下载Word文档失败: ${error.message}`, "error"); } } } // 下载兼容Office的Word文档 function downloadCompatibleWord(data, filename) { if (!data || data.length === 0) { updateStatus('没有数据可供下载', 'error'); setProcessingState(false); return; } try { updateStatus("正在创建Office兼容的Word文件...", "active"); showProgressBar(); updateProgress(10, "准备生成兼容Word..."); // 按题型分组 const groupedData = data.reduce((groups, item) => { const type = item['题目类型']; if (!groups[type]) { groups[type] = []; } groups[type].push(item); return groups; }, {}); updateProgress(30, "正在格式化内容..."); // 使用最简单的纯HTML文档 - 兼容性最好 let htmlContent = ` ${filename.replace('.docx', '')}

${filename.replace('.docx', '')}

`; updateProgress(50, "添加题目内容..."); // 添加每个题型部分 Object.keys(groupedData).forEach((type, typeIndex) => { const questions = groupedData[type]; // 添加题型标题 htmlContent += `

${type}

`; // 添加每个问题 questions.forEach((item, index) => { // 处理题目编号,去除可能的重复编号 let questionTitle = processQuestionTitle(item['题目'] || "", index); // 添加题目 htmlContent += `

${questionTitle}

`; // 添加图片提示 if (item['图片'] && Array.isArray(item['图片']) && item['图片'].length > 0) { htmlContent += `

[图片内容: ${item['图片'].length}张图片]

`; } // 添加选项 if (item['选项']) { htmlContent += `
`; const options = item['选项'].split('\n'); options.forEach(option => { if (option.trim()) { htmlContent += `

${option}

`; } }); htmlContent += `
`; } // 添加我的答案 if (!hideMyAnswers && item['我的答案']) { const myAnswer = processAnswer(item['我的答案']); htmlContent += `

我的答案: ${myAnswer}

`; } // 添加正确答案 if (item['正确答案']) { const correctAnswer = processAnswer(item['正确答案']); htmlContent += `

正确答案: ${correctAnswer}

`; } // 添加答案不匹配指示 if (!hideMyAnswers && item['是否正确'] === '✗') { htmlContent += `

答案不匹配

`; } // 添加题目解析 if (showExplanation && item['题目解析'] && item['题目解析'] !== '-') { htmlContent += `

题目解析:

${item['题目解析'].replace(/\n/g, '
')}

`; } // 添加AI答案 if (item.aiAnswer) { htmlContent += `

AI解答:

${formatAnswer(item.aiAnswer)}

`; } // 添加分隔线 htmlContent += `
`; // 更新进度 const progress = 50 + Math.floor((typeIndex / Object.keys(groupedData).length) * 40); updateProgress(progress, `处理第 ${typeIndex + 1}/${Object.keys(groupedData).length} 题型...`); }); }); // 结束HTML文档 htmlContent += ` `; updateProgress(90, "创建下载链接..."); // 使用Blob API创建HTML文档 - 使用UTF-8编码 const blob = new Blob(["\uFEFF" + htmlContent], { type: 'application/msword;charset=utf-8' }); // 文件名保持为.doc后缀,便于Word打开 const docFilename = filename.replace('.docx', '.doc'); // 创建下载链接并触发下载 const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = docFilename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); updateProgress(100, "完成!"); setTimeout(() => { hideProgressBar(); updateStatus(`Office兼容文档已成功生成并下载: ${docFilename}`, "success"); setProcessingState(false); // 添加动画反馈 if (animationsEnabled) { showToast(`Office兼容文档已成功生成: ${docFilename}`, "success"); } }, 1000); } catch (error) { console.error("下载Office兼容文档失败:", error); hideProgressBar(); updateStatus(`下载Office兼容文档失败: ${error.message}`, "error"); setProcessingState(false); // 添加错误反馈 if (animationsEnabled) { showToast(`下载Office兼容文档失败: ${error.message}`, "error"); } } } // 下载PDF function downloadPDF(data, filename) { if (!data || data.length === 0) { updateStatus('没有数据可供下载', 'error'); setProcessingState(false); return; } try { updateStatus("正在创建PDF文件...", "active"); // 显示进度条 showProgressBar(); updateProgress(0, '准备生成PDF...'); // 检查jsPDF是否可用 if (typeof jspdf === 'undefined') { hideProgressBar(); updateStatus("错误: jsPDF库未加载,请检查脚本设置中的 @require", "error"); setProcessingState(false); // 错误反馈 if (animationsEnabled) { showToast("错误: jsPDF库未加载", "error"); } return; } // 创建一个临时容器来渲染内容 const tempContainer = document.createElement('div'); tempContainer.style.position = 'fixed'; tempContainer.style.top = '-9999px'; tempContainer.style.left = '-9999px'; tempContainer.style.width = '800px'; // 固定宽度以便于转换 tempContainer.style.fontFamily = 'SimSun, Arial'; document.body.appendChild(tempContainer); // 按题型分组 const groupedData = data.reduce((groups, item) => { const type = item['题目类型']; if (!groups[type]) { groups[type] = []; } groups[type].push(item); return groups; }, {}); // 生成HTML内容 updateProgress(5, '生成HTML内容...'); // 使用自定义标题或默认标题 const docTitle = customTitle || filename.replace('.pdf', ''); let htmlContent = `

${docTitle}

`; // 添加每个题型部分 Object.keys(groupedData).forEach(type => { const questions = groupedData[type]; htmlContent += `

${type}

`; // 添加每个问题 questions.forEach((item, index) => { let questionTitle = processQuestionTitle(item['题目'] || "", index); htmlContent += `
${questionTitle}
`; // 图片需要处理为base64格式才能嵌入PDF if (item['图片'] && Array.isArray(item['图片']) && item['图片'].length > 0) { item['图片'].forEach(img => { if (!img) return; const imgSrc = img.data || img.src; if (!imgSrc) return; const safeAlt = (img.alt || "题目图片").replace(/"/g, """); htmlContent += `
${safeAlt}
${safeAlt}
`; }); } // 添加选项 if (item['选项']) { htmlContent += `
`; const options = item['选项'].split('\n'); options.forEach(option => { if (option.trim()) { htmlContent += `
${option}
`; } }); htmlContent += `
`; } // 添加答案区域 htmlContent += `
`; // 添加我的答案 if (!hideMyAnswers) { const myAnswer = processAnswer(item['我的答案']); htmlContent += `
我的答案: ${myAnswer}
`; } // 添加正确答案 if (item['正确答案']) { const correctAnswer = processAnswer(item['正确答案']); htmlContent += `
正确答案: ${correctAnswer}
`; } // 添加答案不匹配指示 if (!hideMyAnswers && item['是否正确'] === '✗') { htmlContent += `
答案不匹配
`; } htmlContent += `
`; // 添加解析 if (showExplanation && item['题目解析'] && item['题目解析'] !== '-') { htmlContent += `
题目解析:
${item['题目解析']}
`; } // 添加AI解答 - 如果有 if (item.aiAnswer) { htmlContent += `
🤖AI解答:
${formatAnswer(item.aiAnswer)}
`; } htmlContent += `
`; }); }); htmlContent += `
`; // 设置临时容器的内容 tempContainer.innerHTML = htmlContent; updateProgress(10, '解析内容结构...'); // 计算总数 - 用于进度条 const totalElements = tempContainer.querySelectorAll('h2, div[style*="margin-bottom: 25px"]').length; let processedElements = 0; // 分页处理函数 const processPages = async () => { updateProgress(15, '创建PDF...'); // 创建PDF实例 const { jsPDF } = jspdf; const pdf = new jsPDF('p', 'pt', 'a4'); const pageWidth = pdf.internal.pageSize.getWidth(); const pageHeight = pdf.internal.pageSize.getHeight(); // 设置文档属性 pdf.setProperties({ title: customTitle || filename.replace('.pdf', ''), subject: '题目解析', author: '题目解析工具', keywords: '题目,答案,解析', creator: '题目解析工具' }); // 暂时计算每页的合理高度(实际用canvas高度决定) const elementsToRender = tempContainer.querySelectorAll('h2, div[style*="margin-bottom: 25px"]'); let currentY = 40; // 页面顶部边距 let pageIndex = 0; // 依次处理每个区块(题型标题或题目) for (let i = 0; i < elementsToRender.length; i++) { const element = elementsToRender[i]; // 计算当前进度 processedElements++; const progressPercent = 15 + Math.floor((processedElements / totalElements) * 80); updateProgress(progressPercent, `渲染第 ${processedElements}/${totalElements} 个元素...`); // 创建区块的副本进行单独处理 const tempElement = document.createElement('div'); tempElement.style.position = 'absolute'; tempElement.style.top = '0'; tempElement.style.left = '0'; tempElement.style.width = '800px'; tempElement.innerHTML = element.outerHTML; document.body.appendChild(tempElement); // 使用html2canvas捕获区块 try { const canvas = await html2canvas(tempElement, { scale: 1.5, // 提高清晰度 useCORS: true, // 处理跨域图片 logging: false, allowTaint: true }); // 计算缩放比例,使其适合PDF页面宽度 const imgWidth = pageWidth - 40; // 页面边距 const imgHeight = (canvas.height * imgWidth) / canvas.width; // 检查是否需要新页面 if (currentY + imgHeight > pageHeight - 40) { if (pageIndex > 0) { pdf.addPage(); } pageIndex++; currentY = 40; // 重置到新页面顶部 updateProgress(progressPercent, `添加第 ${pageIndex} 页...`); } // 将canvas转换为图片并添加到PDF const imgData = canvas.toDataURL('image/jpeg', 0.95); pdf.addImage(imgData, 'JPEG', 20, currentY, imgWidth, imgHeight); currentY += imgHeight + 20; // 添加一些间距 // 添加页码 - 在当前页的底部(使用数字避免中文乱码) const currentPage = pageIndex + 1; pdf.setFontSize(10); pdf.setTextColor(100, 100, 100); pdf.text(`Page ${currentPage}`, pageWidth / 2, pageHeight - 20, { align: 'center' }); // 清理临时元素 document.body.removeChild(tempElement); } catch (e) { console.error("渲染题目内容失败:", e); // 继续处理下一个元素 document.body.removeChild(tempElement); } } // 更新进度并准备保存 updateProgress(95, '完成 PDF 生成...'); // 添加最后一页的页码(如果尚未添加) const totalPages = pageIndex + 1; pdf.setFontSize(10); pdf.setTextColor(100, 100, 100); pdf.text(`Page ${totalPages}`, pageWidth / 2, pageHeight - 20, { align: 'center' }); // 保存PDF pdf.save(filename); // 清理临时容器 document.body.removeChild(tempContainer); updateStatus(`PDF文件已成功生成并下载 (共 ${totalPages} 页)`, "success"); // 动画反馈 if (animationsEnabled) { showToast(`PDF文件已成功生成 (共 ${totalPages} 页)`, "success"); } // 完成 - 100% updateProgress(100, '完成!'); setTimeout(() => { hideProgressBar(); setProcessingState(false); }, 1500); // 1.5秒后隐藏进度条 }; // 执行分页处理 processPages().catch(error => { console.error("生成PDF失败:", error); document.body.removeChild(tempContainer); updateStatus(`生成PDF失败: ${error.message}`, "error"); updateProgress(0, '出错了!'); // 错误反馈 if (animationsEnabled) { showToast(`生成PDF失败: ${error.message}`, "error"); } setTimeout(() => { hideProgressBar(); setProcessingState(false); }, 1500); }); } catch (error) { console.error("下载PDF失败:", error); updateStatus(`下载PDF失败: ${error.message}`, "error"); hideProgressBar(); setProcessingState(false); // 添加错误反馈 if (animationsEnabled) { showToast(`下载PDF失败: ${error.message}`, "error"); } } } // 下载考试宝题库导入格式Excel function downloadKaoshibao(data, filename, title) { if (!data || data.length === 0) { updateStatus('没有数据可供下载', 'error'); setProcessingState(false); return; } // 章节字段使用本次测试/作业名称 const chapterName = (title || '').replace(/<[^>]*>/g, '').trim(); try { updateStatus("正在创建考试宝题库文件...", "active"); showProgressBar(); updateProgress(0, '准备数据...'); // 检查XLSX是否可用 if (typeof XLSX === 'undefined') { hideProgressBar(); updateStatus("错误: XLSX库未加载,请检查脚本设置中的 @require", "error"); setProcessingState(false); if (animationsEnabled) { showToast("错误: XLSX库未加载", "error"); } return; } updateProgress(10, '转换题目格式...'); // 构建考试宝格式的数据行 const rows = []; // 表头行(第1行) const headerRow = ['题干(必填)', '题型 (必填)', '选项 A', '选项 B', '选项 C', '选项 D', '选项E\n(勿删)', '选项F\n(勿删)', '选项G\n(勿删)', '选项H\n(勿删)', '正确答案\n(必填)', '解析\n(勿删)', '章节\n(勿删)', '难度']; // 题型映射:将原始题目类型映射为考试宝支持的题型 function mapQuestionType(type) { if (!type) return '单选题'; const t = type.trim(); if (t.includes('单选') || t === '单选题') return '单选题'; if (t.includes('多选') || t === '多选题') return '多选题'; if (t.includes('不定项')) return '不定项选择题'; if (t.includes('判断')) return '判断题'; if (t.includes('填空')) return '填空题'; if (t.includes('简答') || t.includes('问答')) return '简答题'; if (t.includes('排序')) return '排序题'; if (t.includes('计算')) return '计算题'; if (t.includes('论述')) return '论述题'; // 默认为单选题 return '单选题'; } // 解析选项文本,提取各选项内容 function parseOptions(optionStr) { const options = ['', '', '', '', '', '', '', '']; if (!optionStr) return options; // 按选项字母标记分割,将内容正确放入对应位置 // 处理两种格式: // 1. "A. 选项内容\nB. 选项内容" (字母和内容在同一行) // 2. "A.\n\n选项内容\nB.\n\n选项内容" (字母和内容在不同行) const parts = optionStr.split(/(?=[A-H]\s*[.、.::])/i); let foundByLetter = false; parts.forEach(part => { const trimmed = part.trim(); if (!trimmed) return; // 提取字母和内容 const match = trimmed.match(/^([A-H])\s*[.、.::]\s*([\s\S]*)/i); if (match) { const letter = match[1].toUpperCase(); const content = match[2].trim(); const index = letter.charCodeAt(0) - 65; // A=0, B=1, ... if (index >= 0 && index < 8) { options[index] = content; foundByLetter = true; } } }); // 如果按字母标记分割未找到任何选项,回退到逐行解析 if (!foundByLetter) { const lines = optionStr.split('\n').filter(l => l.trim()); lines.forEach((line, index) => { if (index < 8) { let opt = line.replace(/^[A-H][.、.::\s]\s*/, '').trim(); options[index] = opt; } }); } return options; } // 将正确答案转换为考试宝格式 function convertCorrectAnswer(answer, questionType) { if (!answer) return ''; const a = answer.trim(); // 判断题:A=正确/√, B=错误/× if (questionType === '判断题') { if (a === '对' || a === '正确' || a === '√' || a === '是' || a === 'A' || a === 'a') return 'A'; if (a === '错' || a === '错误' || a === '×' || a === '否' || a === 'B' || a === 'b') return 'B'; // 尝试其他判断 if (a.includes('对') || a.includes('正确') || a.includes('是')) return 'A'; if (a.includes('错') || a.includes('错误') || a.includes('否')) return 'B'; return a; } // 选择题:将答案转换为ABCD格式 if (questionType === '单选题' || questionType === '多选题' || questionType === '不定项选择题') { // 如果答案已经是ABCD格式(如 "A", "AB", "ABC"等) if (/^[A-Ha-h]+$/.test(a)) return a.toUpperCase(); // 如果答案是数字格式(1,2,3...),转换为字母 if (/^\d+$/.test(a)) { return a.split('').map(n => String.fromCharCode(64 + parseInt(n))).join(''); } // 如果答案带括号如 (A) 或 (A) const match = a.match(/[((]\s*([A-Ha-h]+)\s*[))]/); if (match) return match[1].toUpperCase(); return a; } // 填空题:直接返回答案文本 if (questionType === '填空题') return a; // 简答题/计算题/论述题:直接返回答案文本 return a; } const totalItems = data.length; // 清理题干文本:去除题号、分值、题型等前缀 function cleanQuestionText(text) { if (!text) return ''; let cleaned = text.replace(/<[^>]*>/g, '').trim(); // 去除前缀如 "1. (单选题, 2.0 分)\n\n18." 或 "3. (单选题, 2.0 分) 50." cleaned = cleaned.replace(/^\d+\s*[.、.]\s*[\((][^))]*[))]\s*[\n\s]*(?:\d+\s*[.、.]\s*)?/, '').trim(); return cleaned; } // 从AI解析文本中提取正确答案 function extractAnswerFromAI(aiText, questionType) { if (!aiText) return ''; const isMulti = (questionType === '多选题' || questionType === '不定项选择题'); // 查找"正确答案"/"最终答案"/"参考答案"关键词后的内容 const keywordMatch = aiText.match(/(?:正确答案|最终答案|参考答案)[::]*\s*([\s\S]{0,300})/i); if (!keywordMatch) return ''; const afterText = keywordMatch[1]; // 去重并保持顺序的辅助函数 function dedupeLetters(letters) { const seen = new Set(); const result = []; letters.forEach(l => { const up = l.toUpperCase(); if (!seen.has(up)) { seen.add(up); result.push(up); } }); return result.join(''); } if (isMulti) { // 多选题/不定项:需要提取所有选项字母 // 1. 优先匹配连续字母组合(可能被 ** 等 markdown 符号包裹),如 **ABCDE** 或 ABCDE const contiguous = afterText.match(/\*{0,2}\s*([A-H]{2,8})\s*\*{0,2}/i); if (contiguous) return contiguous[1].toUpperCase(); // 2. 匹配用分隔符(、,,/ 空格)分隔的多个字母,如 A、B、C、D、E 或 A,B,C const sepMatch = afterText.match(/([A-Ha-h])\s*[、,,/\s]\s*([A-Ha-h](?:\s*[、,,/\s]\s*[A-Ha-h]){1,7})/); if (sepMatch) { const letters = sepMatch[0].match(/[A-Ha-h]/g); if (letters && letters.length >= 2) { return dedupeLetters(letters); } } // 3. 取关键词后第一段有效内容,收集其中的字母 const firstChunk = afterText.split(/[\n。;;]/)[0].replace(/\*+/g, ' ').trim(); if (firstChunk) { const letters = firstChunk.match(/[A-Ha-h]/g); if (letters && letters.length >= 2 && letters.length <= 8) { // 仅当该段内容主要是字母时才采用(避免从解析正文中误取) const nonSpaceLen = firstChunk.replace(/\s/g, '').length; if (nonSpaceLen > 0 && (letters.join('').length / nonSpaceLen) > 0.5) { return dedupeLetters(letters); } } } // 4. 回退:单个字母(不定项可能只有一个答案) const single = afterText.match(/\*{0,2}\s*([A-H])\s*(?:[.、.::\s\n*]|\*{0,2}\s*$)/i); if (single) return single[1].toUpperCase(); } else { // 单选题/判断题/其他:只取第一个字母 // 匹配字母后跟分隔符的情况,如 "B." "B、" "B." "B:" "B:" const letterWithSep = afterText.match(/\*{0,2}\s*([A-H])\s*[.、.::\s\n*]/i); if (letterWithSep) return letterWithSep[1].toUpperCase(); // 匹配单独字母的情况,如 "E" 在行末 const letterAlone = afterText.match(/\*{0,2}\s*([A-H])\s*(?:\*{0,2}\s*$|\n)/im); if (letterAlone) return letterAlone[1].toUpperCase(); } return ''; } data.forEach((item, index) => { const questionType = mapQuestionType(item['题目类型']); const questionText = cleanQuestionText(item['题目']); const options = parseOptions(item['选项']); // 如果正确答案为空,尝试从AI解析中提取 let correctAnswer = convertCorrectAnswer(item['正确答案'], questionType); if (!correctAnswer) { const aiAnswerText = item['aiAnswer'] ? item['aiAnswer'].replace(/<[^>]*>/g, '').trim() : ''; const extractedAnswer = extractAnswerFromAI(aiAnswerText, questionType); if (extractedAnswer) { correctAnswer = extractedAnswer; } } const explanation = (item['题目解析'] && item['题目解析'] !== '-') ? item['题目解析'].replace(/<[^>]*>/g, '').trim() : ''; // 如果有AI解析,将其加入解析列;如果原解析非空,则合并原解析和AI解析 const aiAnswer = item['aiAnswer'] ? item['aiAnswer'].replace(/<[^>]*>/g, '').trim() : ''; let finalExplanation = ''; if (explanation && aiAnswer) { finalExplanation = explanation + '\n' + aiAnswer; } else if (aiAnswer) { finalExplanation = aiAnswer; } else { finalExplanation = explanation; } const chapter = chapterName; const difficulty = ''; // 填空题特殊处理:答案填在选项列 if (questionType === '填空题') { // 填空题的正确答案可以留空(非必填) // 如果有多个空格答案,用 | 分隔填入选项A列 const blankAnswer = item['正确答案'] ? item['正确答案'].replace(/<[^>]*>/g, '').trim() : ''; if (blankAnswer) { options[0] = blankAnswer; options[1] = ''; options[2] = ''; options[3] = ''; } } // 判断题特殊处理:选项固定为 对/错 if (questionType === '判断题') { options[0] = '对'; options[1] = '错'; options[2] = ''; options[3] = ''; options[4] = ''; options[5] = ''; options[6] = ''; options[7] = ''; } const row = [ questionText, // 题干 questionType, // 题型 options[0], // 选项A options[1], // 选项B options[2], // 选项C options[3], // 选项D options[4], // 选项E options[5], // 选项F options[6], // 选项G options[7], // 选项H correctAnswer, // 正确答案 finalExplanation, // 解析 chapter, // 章节 difficulty // 难度 ]; rows.push(row); const progress = 10 + Math.floor(((index + 1) / totalItems) * 70); updateProgress(progress, `转换第 ${index + 1}/${totalItems} 题...`); }); updateProgress(85, '创建工作表...'); // 创建工作表(第1行为表头,无导入须知说明行) const ws = XLSX.utils.aoa_to_sheet([headerRow, ...rows]); // 设置列宽 ws['!cols'] = [ { wch: 40 }, // 题干 { wch: 12 }, // 题型 { wch: 15 }, // 选项A { wch: 15 }, // 选项B { wch: 15 }, // 选项C { wch: 15 }, // 选项D { wch: 12 }, // 选项E { wch: 12 }, // 选项F { wch: 12 }, // 选项G { wch: 12 }, // 选项H { wch: 15 }, // 正确答案 { wch: 25 }, // 解析 { wch: 15 }, // 章节 { wch: 8 }, // 难度 ]; const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "试题案例,直接导入试试"); updateProgress(95, '保存文件...'); XLSX.writeFile(wb, filename); updateProgress(100, '完成!'); setTimeout(() => { hideProgressBar(); updateStatus(`考试宝题库文件已生成: ${filename}`, "success"); setProcessingState(false); if (animationsEnabled) { showToast(`考试宝题库文件已成功生成: ${filename}`, "success"); } }, 1000); } catch (error) { console.error("下载考试宝题库失败:", error); hideProgressBar(); updateStatus(`下载考试宝题库失败: ${error.message}`, "error"); setProcessingState(false); if (animationsEnabled) { showToast(`下载考试宝题库失败: ${error.message}`, "error"); } } } // ===== AI答题功能 ===== // 设置AI解答按钮事件监听 function setupAIAnswerListeners() { // 所有AI解答按钮 document.querySelectorAll(`.${AI_TOOL_ID}_btn`).forEach(button => { button.addEventListener('click', function() { const questionId = this.dataset.questionId; toggleAnswer(questionId, this); }); }); // AI设置按钮 document.querySelectorAll(`.${AI_TOOL_ID}_config_btn`).forEach(button => { button.addEventListener('click', function(e) { e.stopPropagation(); openAISettingsModal(); }); }); } // 切换显示/隐藏答案,或请求新答案 function toggleAnswer(questionId, button) { const answerContainer = document.getElementById(`${AI_ANSWER_ID}_${questionId}`); if (!answerContainer) { console.error(`找不到答案容器: ${AI_ANSWER_ID}_${questionId}`); return; } // 如果答案容器已有内容且正在显示,则隐藏 if (answerContainer.innerHTML !== '' && answerContainer.style.display !== 'none') { if (animationsEnabled) { // 添加隐藏动画 const answerElement = answerContainer.querySelector(`.${AI_TOOL_ID}_answer_container`); if (answerElement) { answerElement.style.opacity = '0'; answerElement.style.transform = 'translateY(10px)'; setTimeout(() => { answerContainer.style.display = 'none'; }, 300); } else { answerContainer.style.display = 'none'; } } else { answerContainer.style.display = 'none'; } button.innerHTML = `🤖AI解答`; return; } // 显示答案容器 answerContainer.style.display = 'block'; // 如果已有答案内容,直接显示 if (answerContainer.innerHTML !== '') { button.innerHTML = `🤖隐藏解答`; // 如果有动画,添加显示动画到已存在的答案 if (animationsEnabled) { const answerElement = answerContainer.querySelector(`.${AI_TOOL_ID}_answer_container`); if (answerElement) { answerElement.style.opacity = '0'; answerElement.style.transform = 'translateY(10px)'; setTimeout(() => { answerElement.style.opacity = '1'; answerElement.style.transform = 'translateY(0)'; }, 10); } } return; } // 否则请求新答案 button.disabled = true; button.innerHTML = `生成中...`; isAnswering = true; // 创建临时答案容器 const tempAnswer = document.createElement('div'); tempAnswer.className = `${AI_TOOL_ID}_answer_container`; tempAnswer.innerHTML = `
AI解答中...
正在思考问题,请稍候...
`; answerContainer.appendChild(tempAnswer); // 生成提示词 const prompt = generatePrompt(questionId); // 请求AI答案 requestAIAnswer(prompt, questionId) .then(answer => { if (answer) { showAnswer(questionId, answer, button); } else { showAnswerError(questionId, "获取回答失败,请检查API设置并重试。", button); } }) .catch(error => { console.error("AI答案请求失败:", error); showAnswerError(questionId, "API请求错误: " + error.message, button); }) .finally(() => { button.disabled = false; button.innerHTML = `🤖隐藏解答`; isAnswering = false; }); } // 生成完整提示词 function generatePrompt(questionId, options = {}) { const question = activeQuestions[questionId]; if (!question) return ''; // 根据题目内容选择合适的提示词模板 let promptTemplate = aiSettings.defaultPrompt; // 如果指定使用全题专用提示词(forWrongQuestion),优先使用 if (options.forWrongQuestion && aiSettings.customPrompts.wrong) { promptTemplate = aiSettings.customPrompts.wrong; } else { // 简单的题目分类判断 if (question.questionText.match(/[\d+\-*/^=()]+/) || question.questionText.includes('解方程') || question.questionText.includes('计算') || question.questionText.includes('求值')) { promptTemplate = aiSettings.customPrompts.math; } else if (question.questionText.match(/[a-zA-Z]{3,}/) || question.questionText.includes('translate') || question.questionText.includes('英语') || question.options.some(opt => opt.match(/[a-zA-Z]{5,}/))) { promptTemplate = aiSettings.customPrompts.english; } else if (question.questionText.includes('化学') || question.questionText.includes('物理') || question.questionText.includes('生物') || question.questionText.includes('分子')) { promptTemplate = aiSettings.customPrompts.science; } } // 构建完整提示词 let fullPrompt = promptTemplate + '\n\n'; fullPrompt += '题目:' + question.questionText + '\n\n'; if (question.options && question.options.length > 0) { fullPrompt += '选项:\n'; question.options.forEach((option, i) => { fullPrompt += option + '\n'; }); fullPrompt += '\n'; } if (question.correctAnswer) { fullPrompt += '正确答案:' + question.correctAnswer + '\n\n'; } fullPrompt += '请提供详细解答,包括思路分析和结论。'; return fullPrompt; } // 显示答案 function showAnswer(questionId, answer, button) { const answerContainer = document.getElementById(`${AI_ANSWER_ID}_${questionId}`); if (!answerContainer) return; // 清空容器 answerContainer.innerHTML = ''; // 创建答案显示 const answerElement = document.createElement('div'); answerElement.className = `${AI_TOOL_ID}_answer_container`; const apiName = getAPIName(aiSettings.apiType); answerElement.innerHTML = `
${apiName} 解答
${formatAnswer(answer)}
`; answerContainer.appendChild(answerElement); // 存储AI答案到问题数据结构中 if (activeQuestions[questionId]) { activeQuestions[questionId].aiAnswer = answer; } // 在原始问题中也添加AI答案 for (let section of allQsObject) { for (let question of section.nodeList) { if (question.id === questionId) { question.aiAnswer = answer; break; } } } // 添加动作按钮事件 const copyBtn = answerElement.querySelector(`[data-action="copy"]`); const regenerateBtn = answerElement.querySelector(`[data-action="regenerate"]`); copyBtn.addEventListener('click', () => { const textToCopy = answer.trim(); navigator.clipboard.writeText(textToCopy).then(() => { copyBtn.innerHTML = `已复制`; // 添加动画反馈 if (animationsEnabled) { showToast("已复制到剪贴板", "success"); } setTimeout(() => { copyBtn.innerHTML = `📋复制`; }, 2000); }); }); regenerateBtn.addEventListener('click', () => { // 清空答案容器 answerContainer.innerHTML = ''; // 重新请求答案 button.disabled = true; button.innerHTML = `重新生成...`; isAnswering = true; // 创建临时答案容器 const tempAnswer = document.createElement('div'); tempAnswer.className = `${AI_TOOL_ID}_answer_container`; tempAnswer.innerHTML = `
重新生成中...
正在思考问题,请稍候...
`; answerContainer.appendChild(tempAnswer); // 生成提示词并添加变化以获得不同回答 const prompt = generatePrompt(questionId) + '\n请提供与之前不同的解答方法和角度。'; // 请求AI答案 requestAIAnswer(prompt, questionId) .then(newAnswer => { if (newAnswer) { showAnswer(questionId, newAnswer, button); // 添加动画反馈 if (animationsEnabled) { showToast("已重新生成答案", "success"); } } else { showAnswerError(questionId, "重新生成失败,请检查API设置并重试。", button); } }) .catch(error => { console.error("重新生成失败:", error); showAnswerError(questionId, "API请求错误: " + error.message, button); }) .finally(() => { button.disabled = false; button.innerHTML = `🤖隐藏解答`; isAnswering = false; }); }); } // 显示答案错误 function showAnswerError(questionId, errorMessage, button) { const answerContainer = document.getElementById(`${AI_ANSWER_ID}_${questionId}`); if (!answerContainer) return; // 清空容器 answerContainer.innerHTML = ''; // 创建错误显示 const errorElement = document.createElement('div'); errorElement.className = `${AI_TOOL_ID}_answer_container`; errorElement.style.borderLeftColor = '#f44336'; errorElement.innerHTML = `
错误
${errorMessage}
`; answerContainer.appendChild(errorElement); // 添加错误反馈 if (animationsEnabled) { errorElement.style.animation = `${TOOL_ID}_shake 0.5s`; showToast(errorMessage, "error"); } // 添加动作按钮事件 const retryBtn = errorElement.querySelector(`[data-action="retry"]`); const settingsBtn = errorElement.querySelector(`[data-action="settings"]`); retryBtn.addEventListener('click', () => { // 清空答案容器 answerContainer.innerHTML = ''; // 触发按钮点击以重新请求 button.click(); }); settingsBtn.addEventListener('click', () => { openAISettingsModal(); }); } // 格式化答案,处理换行和Markdown function formatAnswer(answer) { if (!answer) return ''; // 处理基本的Markdown元素 let formattedAnswer = answer // 转义HTML .replace(/&/g, '&') .replace(//g, '>') // 处理换行 .replace(/\n/g, '
') // 处理粗体 .replace(/\*\*(.*?)\*\*/g, '$1') // 处理斜体 .replace(/\*(.*?)\*/g, '$1') // 处理代码 .replace(/`(.*?)`/g, '$1'); return formattedAnswer; } // 获取API名称 function getAPIName(apiType) { switch (apiType) { case 'deepseek': return 'DeepSeek'; case 'openai': return 'OpenAI'; case 'gemini': return 'Gemini'; case 'anthropic': return 'Claude'; default: return 'AI'; } } // 请求AI答案 - 支持多种API function requestAIAnswer(prompt, questionId) { return new Promise((resolve, reject) => { if (!aiSettings.apiKey) { reject(new Error('未设置API密钥')); return; } let apiUrl, requestData, headers; // 根据不同API配置请求 switch (aiSettings.apiType) { case 'deepseek': apiUrl = 'https://api.deepseek.com/v1/chat/completions'; requestData = { model: "deepseek-chat", messages: [{ role: "user", content: prompt }], temperature: parseFloat(aiSettings.temperature) || 0.7 }; headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${aiSettings.apiKey}` }; break; case 'openai': apiUrl = 'https://api.openai.com/v1/chat/completions'; requestData = { model: "gpt-3.5-turbo", messages: [{ role: "user", content: prompt }], temperature: parseFloat(aiSettings.temperature) || 0.7 }; headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${aiSettings.apiKey}` }; break; case 'gemini': apiUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent'; requestData = { contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: parseFloat(aiSettings.temperature) || 0.7 } }; // 添加API密钥作为URL参数 apiUrl += `?key=${aiSettings.apiKey}`; headers = { 'Content-Type': 'application/json' }; break; case 'anthropic': apiUrl = 'https://api.anthropic.com/v1/messages'; requestData = { model: "claude-3-haiku-20240307", messages: [{ role: "user", content: prompt }], max_tokens: 4000, temperature: parseFloat(aiSettings.temperature) || 0.7 }; headers = { 'Content-Type': 'application/json', 'x-api-key': aiSettings.apiKey, 'anthropic-version': '2023-06-01' }; break; default: reject(new Error('不支持的API类型')); return; } // 发送API请求 GM_xmlhttpRequest({ method: 'POST', url: apiUrl, headers: headers, data: JSON.stringify(requestData), responseType: 'json', onload: function(response) { if (response.status >= 200 && response.status < 300) { try { let answer = ''; // 根据不同API解析响应 switch (aiSettings.apiType) { case 'deepseek': case 'openai': answer = response.response.choices[0].message.content; break; case 'gemini': answer = response.response.candidates[0].content.parts[0].text; break; case 'anthropic': answer = response.response.content[0].text; break; } resolve(answer); } catch (e) { console.error('解析API响应失败:', e, response); reject(new Error('解析响应失败: ' + e.message)); } } else { console.error('API响应错误:', response); // 尝试解析错误信息 let errorMsg = '请求失败,状态码: ' + response.status; try { if (response.response && response.response.error) { errorMsg = response.response.error.message || errorMsg; } } catch (e) {} reject(new Error(errorMsg)); } }, onerror: function(error) { console.error('请求出错:', error); reject(new Error('网络请求失败')); }, ontimeout: function() { reject(new Error('请求超时')); } }); }); } // 打开AI设置模态框 function openAISettingsModal() { // 检查是否已存在 let modal = document.getElementById(`${AI_TOOL_ID}_settings_modal`); if (!modal) { modal = document.createElement('div'); modal.id = `${AI_TOOL_ID}_settings_modal`; modal.className = `${TOOL_ID}_modal`; modal.innerHTML = `
AI解答设置
精确 ${aiSettings.temperature} 创意
`; document.body.appendChild(modal); // 初始化滑块位置 setTimeout(() => { updateAITabSlider(); }, 10); // 添加事件监听器 document.getElementById(`${AI_TOOL_ID}_temp_value`).textContent = aiSettings.temperature; document.getElementById(`${AI_TOOL_ID}_temperature`).addEventListener('input', function() { document.getElementById(`${AI_TOOL_ID}_temp_value`).textContent = this.value; }); // 标签切换 document.querySelectorAll(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_tab`).forEach(tab => { tab.addEventListener('click', function() { // 移除所有活动标签 document.querySelectorAll(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_tab`).forEach(t => t.classList.remove('active')); document.querySelectorAll(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_tab_content`).forEach(c => c.classList.remove('active')); // 添加活动状态到当前标签 this.classList.add('active'); document.querySelector(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_tab_content[data-tab-content="${this.dataset.tab}"]`).classList.add('active'); // 更新滑块位置 updateAITabSlider(); }); }); // 关闭按钮 document.querySelector(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_modal_close`).addEventListener('click', function() { closeAISettingsModal(); }); // 取消按钮 document.getElementById(`${AI_TOOL_ID}_cancel_btn`).addEventListener('click', function() { closeAISettingsModal(); }); // 保存按钮 document.getElementById(`${AI_TOOL_ID}_save_btn`).addEventListener('click', function() { saveAISettingsFromModal(); closeAISettingsModal(); }); // 应用暗色模式 if (darkMode) { document.querySelector(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_modal_content`).classList.add('dark-mode'); } } // 显示模态框 modal.classList.add('active'); } // 更新AI设置选项卡滑块位置 function updateAITabSlider() { const activeTab = document.querySelector(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_tab.active`); const slider = document.querySelector(`#${AI_TOOL_ID}_settings_modal .${TOOL_ID}_tab_slider`); if (activeTab && slider) { slider.style.width = `${activeTab.offsetWidth}px`; slider.style.left = `${activeTab.offsetLeft}px`; } } // 关闭AI设置模态框 function closeAISettingsModal() { const modal = document.getElementById(`${AI_TOOL_ID}_settings_modal`); if (modal) { modal.classList.remove('active'); } } // 从模态框保存AI设置 function saveAISettingsFromModal() { const wrongPromptEl = document.getElementById(`${AI_TOOL_ID}_wrong_prompt`); aiSettings = { apiType: document.getElementById(`${AI_TOOL_ID}_api_type`).value, apiKey: document.getElementById(`${AI_TOOL_ID}_api_key`).value, temperature: document.getElementById(`${AI_TOOL_ID}_temperature`).value, defaultPrompt: document.getElementById(`${AI_TOOL_ID}_default_prompt`).value, customPrompts: { math: document.getElementById(`${AI_TOOL_ID}_math_prompt`).value, english: document.getElementById(`${AI_TOOL_ID}_english_prompt`).value, science: document.getElementById(`${AI_TOOL_ID}_science_prompt`).value, wrong: wrongPromptEl ? wrongPromptEl.value : (aiSettings.customPrompts.wrong || '你是一位专业的题目解析助手,请分析以下题目,给出详细的解答步骤、思路分析和结论。如果题目有正确答案,请结合正确答案进行解析。') }, showInToolbox: true }; saveSettings(); showToast("AI设置已保存", "success"); } // ===== 预览功能 ===== // 创建预览模态框 function createPreviewModal() { // 检查模态框是否已存在 if (document.getElementById(`${TOOL_ID}_preview_modal`)) { return; } const modal = document.createElement('div'); modal.id = `${TOOL_ID}_preview_modal`; modal.className = `${TOOL_ID}_modal`; modal.innerHTML = `
导出预览
`; document.body.appendChild(modal); // 添加模态框事件监听器 document.getElementById(`${TOOL_ID}_preview_modal`).querySelector(`.${TOOL_ID}_modal_close`).addEventListener('click', closePreviewModal); // 添加格式选择按钮事件监听器 document.querySelectorAll(`#${TOOL_ID}_format_selector .${TOOL_ID}_btn`).forEach(btn => { btn.addEventListener('click', function() { // 移除所有按钮的激活样式 document.querySelectorAll(`#${TOOL_ID}_format_selector .${TOOL_ID}_btn`).forEach(b => { b.style.opacity = '0.7'; b.style.transform = 'none'; }); // 添加激活样式到当前点击的按钮 this.style.opacity = '1'; if (animationsEnabled) { this.style.transform = 'translateY(-5px)'; this.style.boxShadow = '0 8px 15px rgba(0,0,0,0.2)'; } // 根据选择的格式更新预览内容 generatePreview(this.dataset.format); }); }); // 添加下载按钮事件监听器 document.getElementById(`${TOOL_ID}_download_btn`).addEventListener('click', function() { // 获取当前激活的格式 const activeBtn = document.querySelector(`#${TOOL_ID}_format_selector .${TOOL_ID}_btn[style*="opacity: 1"]`); if (!activeBtn) return; const activeFormat = activeBtn.dataset.format; // 关闭模态框 closePreviewModal(); // 触发对应的下载按钮点击 if (activeFormat === 'word_compatible') { document.getElementById(`${BOX_ID}_word_compatible_btn`).click(); } else { document.getElementById(`${BOX_ID}_${activeFormat}_btn`).click(); } }); } // 打开预览模态框 function openPreviewModal() { // 确保模态框已创建 createPreviewModal(); // 应用暗色模式(如果启用) if (darkMode) { document.querySelector(`#${TOOL_ID}_preview_modal .${TOOL_ID}_modal_content`).classList.add('dark-mode'); } else { document.querySelector(`#${TOOL_ID}_preview_modal .${TOOL_ID}_modal_content`).classList.remove('dark-mode'); } // 显示模态框 const modal = document.getElementById(`${TOOL_ID}_preview_modal`); modal.classList.add('active'); // 默认选中Excel格式并生成预览 const excelBtn = document.querySelector(`#${TOOL_ID}_format_selector .${TOOL_ID}_btn[data-format="excel"]`); excelBtn.click(); // 防止背景滚动 document.body.style.overflow = 'hidden'; } // 关闭预览模态框 function closePreviewModal() { const modal = document.getElementById(`${TOOL_ID}_preview_modal`); if (modal) { modal.classList.remove('active'); // 恢复背景滚动 document.body.style.overflow = ''; } } // 根据选择的格式生成预览内容 function generatePreview(format) { if (isProcessing) return; const previewContent = document.getElementById(`${TOOL_ID}_preview_content`); if (!previewContent) return; // 清空之前的内容并显示加载动画 previewContent.innerHTML = `
正在生成预览...
`; // 获取导出数据 const exportData = prepareExportData(); if (!exportData || !exportData.data || exportData.data.length === 0) { previewContent.innerHTML = `
📝
没有数据可供预览
请先解析题目或选择题目后再进行预览
`; return; } // 根据格式生成对应的预览 switch (format) { case 'excel': setTimeout(() => generateExcelPreview(exportData, previewContent), 100); break; case 'word': setTimeout(() => generateWordPreview(exportData, previewContent), 100); break; case 'word_compatible': setTimeout(() => generateCompatibleWordPreview(exportData, previewContent), 100); break; case 'pdf': setTimeout(() => generatePDFPreview(exportData, previewContent), 100); break; case 'kaoshibao': setTimeout(() => generateKaoshibaoPreview(exportData, previewContent), 100); break; default: previewContent.innerHTML = `
⚠️
不支持预览该格式
请选择其他格式进行预览
`; } } // 生成Excel预览 function generateExcelPreview(exportData, container) { const { data, baseFilename } = exportData; // 获取所有唯一的键作为表头 const allKeys = new Set(); data.forEach(item => { Object.keys(item).forEach(key => allKeys.add(key)); }); // 转换为数组并按逻辑排序 const preferredOrder = ['题目类型', '题目', '选项', '我的答案', '正确答案', '是否正确', '题目解析']; const keys = Array.from(allKeys).sort((a, b) => { const indexA = preferredOrder.indexOf(a); const indexB = preferredOrder.indexOf(b); if (indexA === -1 && indexB === -1) return a.localeCompare(b); if (indexA === -1) return 1; if (indexB === -1) return -1; return indexA - indexB; }); // 创建表格HTML let html = `

${baseFilename}.xlsx

${keys.map((key, index) => ` `).join('')} `; // 添加数据行 data.forEach((item, index) => { html += ``; keys.forEach(key => { let cellValue = item[key] || ''; // 处理特殊情况 if (key === '图片' && Array.isArray(item[key]) && item[key].length > 0) { cellValue = `包含${item[key].length}张图片`; } else if (key === '选项' && cellValue) { // 限制预览中的选项长度 const options = cellValue.split('\n'); if (options.length > 3) { cellValue = options.slice(0, 3).join('
') + '
...'; } else { cellValue = options.join('
'); } } else if (cellValue.length > 100) { // 截断过长的文本 cellValue = cellValue.substring(0, 100) + '...'; } else if (key === '是否正确') { if (cellValue === '✓') { cellValue = ``; } else if (cellValue === '✗') { cellValue = ``; } } html += ``; }); html += ''; }); html += `
${key}
${cellValue}
数据预览
显示 ${data.length} 行数据,完整内容将在Excel文件中可用。
💾下载Excel文件
`; container.innerHTML = html; } // 生成Word预览 function generateWordPreview(exportData, container) { const { data, baseFilename } = exportData; // 按题型分组 const groupedData = data.reduce((groups, item) => { const type = item['题目类型']; if (!groups[type]) { groups[type] = []; } groups[type].push(item); return groups; }, {}); // 开始构建HTML let html = `

${baseFilename}

`; // 添加每个部分 Object.keys(groupedData).forEach((type, typeIndex) => { const questions = groupedData[type]; html += `

${type}

`; // 预览中只显示有限的问题 const showQuestions = questions.slice(0, 3); const remainingCount = questions.length - showQuestions.length; // 添加每个问题 showQuestions.forEach((item, index) => { // 处理问题标题 let questionTitle = processQuestionTitle(item['题目'] || "", index); html += `
${questionTitle}
`; // 在题目左侧添加彩色标记 if (!hideMyAnswers && item['是否正确'] !== '-') { const isCorrect = item['是否正确'] === '✓'; html += `
`; } // 添加图片占位符 if (item['图片'] && Array.isArray(item['图片']) && item['图片'].length > 0) { html += `
🖼️
包含 ${item['图片'].length} 张图片(导出时显示)
`; } // 添加选项 if (item['选项']) { html += `
`; const options = item['选项'].split('\n'); options.forEach((option, i) => { if (option.trim()) { html += `
${option}
`; } }); html += `
`; } // 添加答案区域 html += `
`; // 添加我的答案 if (!hideMyAnswers) { const myAnswer = processAnswer(item['我的答案']); html += `
我的答案: ${myAnswer}
`; } // 添加正确答案 if (item['正确答案']) { const correctAnswer = processAnswer(item['正确答案']); html += `
正确答案: ${correctAnswer}
`; } // 添加答案不匹配指示 if (!hideMyAnswers && item['是否正确'] === '✗') { html += `
答案不匹配
`; } html += `
`; // 添加解析 if (showExplanation && item['题目解析'] && item['题目解析'] !== '-') { const explanation = item['题目解析'].length > 100 ? item['题目解析'].substring(0, 100) + '...' : item['题目解析']; html += `
💡题目解析:
${explanation}
`; } // 添加AI解答 - 如果有 if (item.aiAnswer) { const aiAnswer = item.aiAnswer.length > 100 ? item.aiAnswer.substring(0, 100) + '...' : item.aiAnswer; html += `
🤖AI解答:
${formatAnswer(aiAnswer)}
`; } html += `
`; }); // 显示剩余数量 if (remainingCount > 0) { html += `
还有 ${remainingCount} 道题未显示在预览中
完整内容将在Word文档中可用
`; } html += `
`; }); html += `
预览效果
完整内容将在Word文档中可用
`; container.innerHTML = html; } // 生成PDF预览 function generatePDFPreview(exportData, container) { const { data, baseFilename } = exportData; // 类似于Word预览,但添加页面分隔 const groupedData = data.reduce((groups, item) => { const type = item['题目类型']; if (!groups[type]) { groups[type] = []; } groups[type].push(item); return groups; }, {}); // 开始构建带有PDF样式页面的HTML let html = `
`; // 第一页 - 标题页 html += `
PDF预览

${baseFilename.replace('.pdf', '')}

总共 ${data.length} 道题目
包含 ${Object.keys(groupedData).length} 种题型
生成时间: ${new Date().toLocaleString()}
1
`; // 内容页 - 在预览中限制为2页 let currentPage = 2; let typesShown = 0; for (const type of Object.keys(groupedData)) { // 限制在预览中只显示2种题型 if (typesShown >= 2) { break; } const questions = groupedData[type]; html += `

${type}

`; // 只显示几个问题 const showQuestions = questions.slice(0, 2); const remainingCount = questions.length - showQuestions.length; showQuestions.forEach((item, index) => { let questionTitle = processQuestionTitle(item['题目'] || "", index); html += `
${questionTitle}
`; // 在题目左侧添加彩色标记 if (!hideMyAnswers && item['是否正确'] !== '-') { const isCorrect = item['是否正确'] === '✓'; html += `
`; } // 简化的预览内容 - 只显示基本信息 if (item['选项']) { const options = item['选项'].split('\n'); if (options.length > 0) { html += `
`; const displayOptions = options.slice(0, Math.min(options.length, 4)); displayOptions.forEach((option, i) => { if (option.trim()) { html += `
${option}
`; } }); if (options.length > 4) { html += `
...
`; } html += `
`; } } // 添加答案部分 html += `
`; if (item['正确答案']) { html += `
正确答案: ${item['正确答案']}
`; } html += `
`; // 添加AI解答 - 如果有 if (item.aiAnswer) { const aiAnswer = item.aiAnswer.length > 100 ? item.aiAnswer.substring(0, 100) + '...' : item.aiAnswer; html += `
🤖AI解答:
${formatAnswer(aiAnswer)}
`; } html += `
`; }); // 显示剩余数量 if (remainingCount > 0) { html += `
还有 ${remainingCount} 道题未显示在预览中
`; } html += `
${currentPage}
`; currentPage++; typesShown++; } // 如果还有更多题型未显示 if (typesShown < Object.keys(groupedData).length) { const remainingTypes = Object.keys(groupedData).length - typesShown; html += `
还有 ${remainingTypes} 种题型未在预览中显示
完整内容将在PDF文件中包含 ${currentPage - 1 + Math.ceil(remainingTypes * 1.5)} 页左右
${currentPage}
`; } html += `
预览效果
完整内容将在PDF文档中可用
`; container.innerHTML = html; } // ===== 初始化 ===== // 检查页面是否包含题目 // 检测是否为学习通相关网站 function isValidSite() { const hostname = window.location.hostname.toLowerCase(); const validHostnames = [ 'chaoxing.com', 'fanya.chaoxing.com', 'mooc.chaoxing.com', 'i.chaoxing.com', 'icourse163.org' ]; const url = window.location.href.toLowerCase(); const validPaths = [ '/exam', '/test', '/work', '/homework', '/quiz', '/practice', '/quizscore', '/learn' ]; // 检查主机名 const validHostname = validHostnames.some(host => hostname.includes(host)); // 检查路径(适用于包含考试/测试相关的页面) const validPath = validPaths.some(path => url.includes(path)); // 检查页面是否包含题目相关的DOM元素 const hasQuestionElements = document.getElementsByClassName("mark_item").length > 0 || document.getElementsByClassName("questionLi").length > 0 || document.getElementsByClassName("u-questionItem").length > 0; return validHostname || validPath || hasQuestionElements; } function hasQuestions() { return document.getElementsByClassName("mark_item").length > 0 || document.getElementsByClassName("questionLi").length > 0 || document.getElementsByClassName("u-questionItem").length > 0 || document.querySelector('.j-quizPool') !== null; } // 初始化工具 function initTool() { if (toolInitialized) return; const platform = detectPlatform(); // 检查网站兼容性 if (!isValidSite()) { console.log("当前网站可能不是支持的页面,工具可能无法正常工作"); // 仍然尝试初始化,但给出提示 } if (hasQuestions()) { // 先加载保存的设置 loadSettings(); insertStyle(); createFloatingButton(); createAIFloatingButton(); toolInitialized = true; const platformName = platform === 'mooc' ? '中国大学MOOC' : '学习通'; console.log(`题目解析工具已初始化 (平台: ${platformName})`); // 显示初始化通知 if (animationsEnabled) { setTimeout(() => { showToast(`${platformName}题目解析工具已初始化,点击右下角按钮打开工具`, "info", 5000); }, 1000); } } else { const platformName = platform === 'mooc' ? '中国大学MOOC' : '学习通'; console.log(`当前页面未找到题目(${platformName}),工具未初始化`); } } // 设置页面观察器,处理动态加载的内容 function setupPageObserver() { // 使用MutationObserver监视DOM变化 const observer = new MutationObserver(function(mutations) { if (!toolInitialized && hasQuestions()) { initTool(); } }); // 开始观察document.body observer.observe(document.body, { childList: true, subtree: true }); // 每隔2秒检查一次(MOOC等SPA页面需要更长时间加载) setInterval(function() { if (!toolInitialized && hasQuestions()) { initTool(); } }, 2000); } // 在页面加载后初始化 function initialize() { if (document.readyState === 'loading') { window.addEventListener('load', function() { initTool(); setupPageObserver(); }); } else { initTool(); setupPageObserver(); } } // 执行初始化 initialize(); // 批量解析题目 function batchAnalyzeWrongQuestions(wrongQuestions, options = {}) { // 删除这个函数,使用下面的analyzeWrongQuestions函数代替 } // 新增函数,用于批量分析题目,替代旧的实现 function analyzeWrongQuestions(questionsToAnalyze, options = {}) { if (questionsToAnalyze.length === 0) return; // 默认选项 const defaultOptions = { batchSize: questionsToAnalyze.length, // 默认一次处理全部 useSpecialPrompt: true, // 默认使用全题专用提示词 skipExisting: true // 默认跳过已有解析的题目 }; // 合并选项 const settings = {...defaultOptions, ...options}; console.log("解析设置:", settings); // 过滤跳过的题目 let questionsToProcess = questionsToAnalyze; if (settings.skipExisting) { questionsToProcess = questionsToAnalyze.filter(q => !q.aiAnswer); // 如果所有题目都已处理,显示提示并返回 if (questionsToProcess.length === 0) { showToast("所有题目已有AI解析,无需重复处理", "info"); return; } } // 设置处理状态 isAnswering = true; setProcessingState(true); updateStatus(`准备解析 ${questionsToProcess.length} 道题目...`, "active"); showProgressBar(); updateProgress(0, `0/${questionsToProcess.length}`); // 禁用AI解析按钮 const btnAIWrongQuestions = document.getElementById(`${BOX_ID}_ai_wrong_btn`); if (btnAIWrongQuestions) { btnAIWrongQuestions.disabled = true; btnAIWrongQuestions.innerHTML = `解析中...`; } // 批量处理的计数器和完成函数 let completedCount = 0; let errorCount = 0; let processingBatch = false; // 结束函数 const finishProcessing = () => { updateProgress(100, `完成: ${completedCount}/${questionsToProcess.length}`); setTimeout(() => { hideProgressBar(); isAnswering = false; setProcessingState(false); if (errorCount > 0) { updateStatus(`AI解析完成,${completedCount} 道题成功,${errorCount} 道题失败`, "error"); showToast(`题目解析完成,但有 ${errorCount} 道题失败`, "error"); } else { updateStatus(`成功解析 ${completedCount} 道题目`, "success"); showToast(`成功解析 ${completedCount} 道题目`, "success"); } // 刷新显示 displayQuestions(allQsObject); // 恢复按钮状态 if (btnAIWrongQuestions) { updateAIWrongQuestionsButton(); } }, 1000); }; // 递归处理题目 const processNextQuestion = (index) => { if (index >= questionsToProcess.length) { finishProcessing(); return; } const batchEndIndex = Math.min(index + settings.batchSize, questionsToProcess.length); const currentBatch = questionsToProcess.slice(index, batchEndIndex); processingBatch = true; // 更新进度 const progress = Math.floor((index / questionsToProcess.length) * 100); updateProgress(progress, `${index}/${questionsToProcess.length}`); if (currentBatch.length === 1) { updateStatus(`正在解析第 ${index+1}/${questionsToProcess.length} 题`, "active"); } else { updateStatus(`正在批量解析 ${index+1}-${batchEndIndex}/${questionsToProcess.length} 题`, "active"); } // 处理当前批次 const batchPromises = currentBatch.map(question => { return new Promise((resolve) => { try { const questionId = question.id; // 如果已经有AI解析且设置了跳过,直接返回成功 if (question.aiAnswer && settings.skipExisting) { resolve(true); return; } // 确保activeQuestions中有该题目信息 if (!activeQuestions[questionId]) { activeQuestions[questionId] = { questionText: question.q, options: question.slt || [], correctAnswer: question.an, myAnswer: question.myAn, explanation: question.explanation }; } // 生成提示词 - 使用全题专用提示词 let prompt = generatePrompt(questionId, { forWrongQuestion: settings.useSpecialPrompt }); // 请求AI答案 requestAIAnswer(prompt, questionId) .then(answer => { if (answer) { // 保存AI解析到问题数据中 question.aiAnswer = answer; // 保存到activeQuestions中备用 if (activeQuestions[questionId]) { activeQuestions[questionId].aiAnswer = answer; } resolve(true); } else { console.error(`题目 ${questionId} 的AI解析失败`); resolve(false); } }) .catch(error => { console.error(`题目 ${questionId} 的AI解析出错:`, error); resolve(false); }); } catch (e) { console.error("处理题目时出错:", e); resolve(false); } }); }); // 等待批次处理完成 Promise.all(batchPromises) .then(results => { // 更新计数 results.forEach(success => { if (success) { completedCount++; } else { errorCount++; } }); // 处理下一批 processingBatch = false; setTimeout(() => processNextQuestion(batchEndIndex), 1000); }) .catch(error => { console.error("批量处理过程中出错:", error); processingBatch = false; errorCount += currentBatch.length; setTimeout(() => processNextQuestion(batchEndIndex), 1000); }); }; // 开始处理第一批 processNextQuestion(0); } // 生成考试宝题库预览 function generateKaoshibaoPreview(exportData, container) { const { data, baseFilename, title } = exportData; // 章节字段使用本次测试/作业名称 const chapterName = (title || '').replace(/<[^>]*>/g, '').trim(); // 题型映射 function mapQuestionType(type) { if (!type) return '单选题'; const t = type.trim(); if (t.includes('单选') || t === '单选题') return '单选题'; if (t.includes('多选') || t === '多选题') return '多选题'; if (t.includes('不定项')) return '不定项选择题'; if (t.includes('判断')) return '判断题'; if (t.includes('填空')) return '填空题'; if (t.includes('简答') || t.includes('问答')) return '简答题'; if (t.includes('排序')) return '排序题'; if (t.includes('计算')) return '计算题'; if (t.includes('论述')) return '论述题'; return '单选题'; } // 解析选项 - 按字母标记分割,确保选项放入正确位置 function parseOptions(optionStr) { const options = ['', '', '', '', '', '', '', '']; if (!optionStr) return options; // 按选项字母标记分割,将内容正确放入对应位置 const parts = optionStr.split(/(?=[A-H]\s*[.、.::])/i); let foundByLetter = false; parts.forEach(part => { const trimmed = part.trim(); if (!trimmed) return; const match = trimmed.match(/^([A-H])\s*[.、.::]\s*([\s\S]*)/i); if (match) { const letter = match[1].toUpperCase(); const content = match[2].trim(); const index = letter.charCodeAt(0) - 65; if (index >= 0 && index < 8) { options[index] = content; foundByLetter = true; } } }); // 如果按字母标记分割未找到任何选项,回退到逐行解析 if (!foundByLetter) { const lines = optionStr.split('\n').filter(l => l.trim()); lines.forEach((line, index) => { if (index < 8) { let opt = line.replace(/^[A-H][.、.::\s]\s*/, '').trim(); options[index] = opt; } }); } return options; } // 清理题干文本:去除题号、分值、题型等前缀 function cleanQuestionText(text) { if (!text) return ''; let cleaned = text.replace(/<[^>]*>/g, '').trim(); // 去除前缀如 "1. (单选题, 2.0 分)\n\n18." 或 "3. (单选题, 2.0 分) 50." cleaned = cleaned.replace(/^\d+\s*[.、.]\s*[\((][^))]*[))]\s*[\n\s]*(?:\d+\s*[.、.]\s*)?/, '').trim(); return cleaned; } // 从AI解析文本中提取正确答案 function extractAnswerFromAI(aiText, questionType) { if (!aiText) return ''; const isMulti = (questionType === '多选题' || questionType === '不定项选择题'); // 查找"正确答案"/"最终答案"/"参考答案"关键词后的内容 const keywordMatch = aiText.match(/(?:正确答案|最终答案|参考答案)[::]*\s*([\s\S]{0,300})/i); if (!keywordMatch) return ''; const afterText = keywordMatch[1]; // 去重并保持顺序的辅助函数 function dedupeLetters(letters) { const seen = new Set(); const result = []; letters.forEach(l => { const up = l.toUpperCase(); if (!seen.has(up)) { seen.add(up); result.push(up); } }); return result.join(''); } if (isMulti) { // 多选题/不定项:需要提取所有选项字母 // 1. 优先匹配连续字母组合(可能被 ** 等 markdown 符号包裹),如 **ABCDE** 或 ABCDE const contiguous = afterText.match(/\*{0,2}\s*([A-H]{2,8})\s*\*{0,2}/i); if (contiguous) return contiguous[1].toUpperCase(); // 2. 匹配用分隔符(、,,/ 空格)分隔的多个字母,如 A、B、C、D、E 或 A,B,C const sepMatch = afterText.match(/([A-Ha-h])\s*[、,,/\s]\s*([A-Ha-h](?:\s*[、,,/\s]\s*[A-Ha-h]){1,7})/); if (sepMatch) { const letters = sepMatch[0].match(/[A-Ha-h]/g); if (letters && letters.length >= 2) { return dedupeLetters(letters); } } // 3. 取关键词后第一段有效内容,收集其中的字母 const firstChunk = afterText.split(/[\n。;;]/)[0].replace(/\*+/g, ' ').trim(); if (firstChunk) { const letters = firstChunk.match(/[A-Ha-h]/g); if (letters && letters.length >= 2 && letters.length <= 8) { // 仅当该段内容主要是字母时才采用(避免从解析正文中误取) const nonSpaceLen = firstChunk.replace(/\s/g, '').length; if (nonSpaceLen > 0 && (letters.join('').length / nonSpaceLen) > 0.5) { return dedupeLetters(letters); } } } // 4. 回退:单个字母(不定项可能只有一个答案) const single = afterText.match(/\*{0,2}\s*([A-H])\s*(?:[.、.::\s\n*]|\*{0,2}\s*$)/i); if (single) return single[1].toUpperCase(); } else { // 单选题/判断题/其他:只取第一个字母 const letterWithSep = afterText.match(/\*{0,2}\s*([A-H])\s*[.、.::\s\n*]/i); if (letterWithSep) return letterWithSep[1].toUpperCase(); const letterAlone = afterText.match(/\*{0,2}\s*([A-H])\s*(?:\*{0,2}\s*$|\n)/im); if (letterAlone) return letterAlone[1].toUpperCase(); } return ''; } // 转换正确答案 function convertCorrectAnswer(answer, questionType) { if (!answer) return ''; const a = answer.trim(); if (questionType === '判断题') { if (a === '对' || a === '正确' || a === '√' || a === '是' || a === 'A' || a === 'a') return 'A'; if (a === '错' || a === '错误' || a === '×' || a === '否' || a === 'B' || a === 'b') return 'B'; if (a.includes('对') || a.includes('正确') || a.includes('是')) return 'A'; if (a.includes('错') || a.includes('错误') || a.includes('否')) return 'B'; return a; } if (questionType === '单选题' || questionType === '多选题' || questionType === '不定项选择题') { if (/^[A-Ha-h]+$/.test(a)) return a.toUpperCase(); if (/^\d+$/.test(a)) { return a.split('').map(n => String.fromCharCode(64 + parseInt(n))).join(''); } const match = a.match(/[((]\s*([A-Ha-h]+)\s*[))]/); if (match) return match[1].toUpperCase(); return a; } return a; } const headers = ['题干', '题型', '选项A', '选项B', '选项C', '选项D', '选项E', '选项F', '选项G', '选项H', '正确答案', '解析', '章节', '难度']; // 题型统计 const typeStats = {}; data.forEach(item => { const qt = mapQuestionType(item['题目类型']); typeStats[qt] = (typeStats[qt] || 0) + 1; }); let html = `

${baseFilename}_考试宝.xlsx

考试宝题库导入格式说明
选择题答案填写ABCD,不加标点 | 判断题答案A=对 B=错 | 填空题答案填在选项A列
支持题型:单选题/多选题/不定项选择题/判断题/填空题/简答题/排序题/计算题/论述题
${Object.entries(typeStats).map(([type, count]) => ` ${type}: ${count}题 `).join('')}
${headers.map(h => ` `).join('')} `; // 添加数据行 data.forEach((item, index) => { const questionType = mapQuestionType(item['题目类型']); const questionText = cleanQuestionText(item['题目']); const optionsList = parseOptions(item['选项']); // 如果正确答案为空,尝试从AI解析中提取 let correctAnswer = convertCorrectAnswer(item['正确答案'], questionType); if (!correctAnswer) { const aiAnswerText = item['aiAnswer'] ? item['aiAnswer'].replace(/<[^>]*>/g, '').trim() : ''; const extractedAnswer = extractAnswerFromAI(aiAnswerText, questionType); if (extractedAnswer) { correctAnswer = extractedAnswer; } } const explanation = (item['题目解析'] && item['题目解析'] !== '-') ? item['题目解析'].replace(/<[^>]*>/g, '').trim() : ''; // 如果有AI解析,将其加入解析列;如果原解析非空,则合并原解析和AI解析 const aiAnswer = item['aiAnswer'] ? item['aiAnswer'].replace(/<[^>]*>/g, '').trim() : ''; let finalExplanation = ''; if (explanation && aiAnswer) { finalExplanation = explanation + '\n' + aiAnswer; } else if (aiAnswer) { finalExplanation = aiAnswer; } else { finalExplanation = explanation; } // 构建选项数组(补齐8个) const displayOptions = ['', '', '', '', '', '', '', '']; if (questionType === '判断题') { displayOptions[0] = '对'; displayOptions[1] = '错'; } else if (questionType === '填空题') { const blankAnswer = item['正确答案'] ? item['正确答案'].replace(/<[^>]*>/g, '').trim() : ''; displayOptions[0] = blankAnswer; optionsList.forEach((opt, i) => { if (i < 8) displayOptions[i] = opt; }); } else { optionsList.forEach((opt, i) => { if (i < 8) displayOptions[i] = opt; }); } // 截断过长的题干 const displayQuestion = questionText.length > 80 ? questionText.substring(0, 80) + '...' : questionText; const displayExplanation = finalExplanation.length > 50 ? finalExplanation.substring(0, 50) + '...' : finalExplanation; html += ` ${displayOptions.map(opt => ` `).join('')} `; }); html += `
${h}
${displayQuestion} ${questionType} ${opt || '-'}${correctAnswer} ${displayExplanation} ${chapterName || '-'}
考试宝题库导入格式
显示 ${data.length} 道题目,文件可直接导入考试宝APP使用。
📗下载考试宝题库
`; container.innerHTML = html; } // 生成Office兼容Word预览 function generateCompatibleWordPreview(exportData, container) { const { data, baseFilename } = exportData; // 与常规Word预览基本相同,但强调兼容性 let html = `

${baseFilename} (Office兼容格式)

✓ 兼容Microsoft Office Word
此格式使用简化的HTML格式导出为.doc文件
解决了兼容性问题,支持中文正常显示
`; // 按题型分组显示部分数据 const groupedData = data.reduce((groups, item) => { const type = item['题目类型']; if (!groups[type]) { groups[type] = []; } groups[type].push(item); return groups; }, {}); // 只显示一部分题型和题目 const sampleTypes = Object.keys(groupedData).slice(0, 1); sampleTypes.forEach(type => { const questions = groupedData[type]; html += `

${type}

`; // 只显示少量题目作为预览 const sampleQuestions = questions.slice(0, 2); sampleQuestions.forEach((item, index) => { let questionTitle = processQuestionTitle(item['题目'] || "", index); html += `
${questionTitle}
`; // 选项和答案 if (item['选项']) { const options = item['选项'].split('\n'); if (options.length > 0) { html += `
`; const displayOptions = options.slice(0, Math.min(options.length, 3)); displayOptions.forEach(option => { if (option.trim()) { html += `
${option}
`; } }); html += `
`; } } if (item['正确答案']) { html += `
正确答案: ${item['正确答案']}
`; } html += `
`; }); html += `
`; }); // 添加预览说明 html += `
兼容性说明
已更新为HTML格式,输出为.doc文件
可在Microsoft Office Word和WPS中打开,并支持中文显示
`; container.innerHTML = html; } })();