// ==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 = `
题目解析
选择AI模型
DeepSeek
OpenAI
Google Gemini
Anthropic Claude
API密钥
默认提示词
数学题提示词
英语题提示词
科学题提示词
全题提示词
📋 解析题目
🤖 AI解析全部题目
👁️ 预览导出
📊 下载Excel
📄 下载Word
📄 Office兼容Word
📑 下载PDF
📗 考试宝题库
⏳
等待操作
`;
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 ?
`每次5题(推荐用于大量题目)
每次10题 ` : '';
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 = `
`;
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 = `
`;
// 统计信息区域
const statsHtml = `
`;
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 = `
`;
}
// 处理答案
const myAnswerHtml = hideMyAnswers
? ''
: `我的答案: ${qItem.myAn}
`;
// 答案匹配指示
const mismatchHtml = (!hideMyAnswers && qItem.myAn && qItem.an && qItem.myAn !== qItem.an)
? `答案不匹配
`
: '';
// 处理题目解析
const explanationHtml = showExplanation && qItem.explanation
? `
`
: '';
// 处理图片
let imagesHtml = '';
if (qItem.images && qItem.images.length > 0) {
qItem.images.forEach(img => {
const imgUrl = img.data || img.src;
imagesHtml += `
`;
});
}
// AI解答按钮
const aiButtonHtml = `
🤖 AI解答
⚙️
`;
// 题目选择框
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 = `
`;
questionsHtml += questionHtml;
// 记录问题数据用于AI解答
activeQuestions[questionId] = {
questionText: qItem.q,
options: qItem.slt,
correctAnswer: qItem.an,
myAnswer: qItem.myAn,
explanation: qItem.explanation
};
});
const sectionHtml = `
`;
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 = ` `;
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}
`;
});
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 += `
`;
});
} 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}
`;
});
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 += `
`;
}
// 添加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}
`;
});
}
// 添加选项
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 += `
`;
}
// 添加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 = `
正在思考问题,请稍候...
`;
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 = `
${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 = `
默认提示词
数学题提示词
英语题提示词
科学题提示词
全题解析提示词
`;
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) => `
${key}
`).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 += `${cellValue} `;
});
html += ' ';
});
html += `
数据预览
显示 ${data.length} 行数据,完整内容将在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 += `
`;
}
// 添加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文档中可用
💾 下载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文档中可用
💾 下载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 => `
${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 += `
${displayQuestion}
${questionType}
${displayOptions.map(opt => `
${opt || '- '}
`).join('')}
${correctAnswer}
${displayExplanation}
${chapterName || '- '}
`;
});
html += `
考试宝题库导入格式
显示 ${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中打开,并支持中文显示
💾 下载Office兼容Word文件
`;
container.innerHTML = html;
}
})();