// ==UserScript==
// @name 物理实验助手
// @namespace http://tampermonkey.net/
// @version 1.2.0
// @description 完美解决乱序、空选项导致判断题错位的问题。自动提取隐藏域答案并勾选,支持自动答题记忆、禁用网页弹窗、面板自由拖动。
// @author 毫厘
// @match http://59.69.101.153/lab/Reports/*
// @match http://59.69.103.147/Reports/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 1. 创建美化的 UI 面板
function createPanel() {
if (document.getElementById('auto-helper-panel')) return; // 防止重复注入
const panel = document.createElement('div');
panel.id = 'auto-helper-panel';
panel.innerHTML = `
`;
document.body.appendChild(panel);
const style = document.createElement('style');
style.textContent = `
#auto-helper-panel {
position: fixed; top: 20px; right: 20px; width: 230px;
background: #ffffff; border-radius: 8px; z-index: 10000;
box-shadow: 0 4px 20px rgba(0,0,0,0.15); font-family: sans-serif;
overflow: hidden; border: 1px solid #e0e0e0; transition: height 0.3s ease;
}
#helper-header {
background: #4CAF50; color: white; padding: 10px 15px;
display: flex; justify-content: space-between; align-items: center;
font-weight: bold; cursor: move; user-select: none;
}
#toggle-btn {
background: rgba(255,255,255,0.2); border: none; color: white;
cursor: pointer; border-radius: 4px; padding: 2px 8px; font-weight: bold;
}
#helper-body { padding: 15px; }
#answer-list { margin-bottom: 12px; font-size: 13px; color: #333; line-height: 1.6; max-height: 250px; overflow-y: auto;}
.answer-item { border-bottom: 1px dashed #eee; padding: 4px 0; }
.answer-val { color: #e91e63; font-weight: bold; margin-left: 5px; }
.auto-run-wrapper {
display: flex; align-items: center; margin-bottom: 12px; font-size: 13px;
color: #2c3e50; cursor: pointer; user-select: none;
}
.auto-run-wrapper input { margin-right: 6px; cursor: pointer; }
#auto-fill-btn {
width: 100%; background: #4CAF50; color: white; border: none;
padding: 10px; border-radius: 5px; cursor: pointer; font-size: 14px;
transition: background 0.2s; font-weight: bold;
}
#auto-fill-btn:hover { background: #45a049; }
#helper-footer { font-size: 11px; color: #999; margin-top: 10px; text-align: center; }
.collapsed #helper-body { display: none; }
`;
document.head.appendChild(style);
// 面板折叠逻辑
document.getElementById('toggle-btn').onclick = (e) => {
e.stopPropagation(); // 防止触发拖拽
panel.classList.toggle('collapsed');
document.getElementById('toggle-btn').textContent = panel.classList.contains('collapsed') ? '+' : '-';
};
// 面板拖动逻辑
makeDraggable(panel, document.getElementById('helper-header'));
}
// 实现拖拽功能的通用函数
function makeDraggable(panel, handle) {
let isDragging = false, offsetX, offsetY;
handle.addEventListener('mousedown', (e) => {
if (e.target.id === 'toggle-btn') return;
isDragging = true;
const rect = panel.getBoundingClientRect();
// 将right定位转换为left定位,防止拖拽时跳动
panel.style.right = 'auto';
panel.style.left = rect.left + 'px';
panel.style.top = rect.top + 'px';
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
panel.style.transition = 'none'; // 拖动时取消动画避免迟钝
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
let x = e.clientX - offsetX;
let y = e.clientY - offsetY;
// 防止拖出屏幕可视区域
x = Math.max(0, Math.min(x, window.innerWidth - panel.offsetWidth));
y = Math.max(0, Math.min(y, window.innerHeight - panel.offsetHeight));
panel.style.left = `${x}px`;
panel.style.top = `${y}px`;
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
panel.style.transition = 'height 0.3s ease'; // 恢复动画
}
});
}
// 2. 完美提取数据(彻底解决乱序和空白选项问题)
function getQuestionsData() {
const data = [];
const hiddenInputs = document.querySelectorAll('input[id^="result_refer_pre"]');
hiddenInputs.forEach((hiddenInput) => {
const match = hiddenInput.id.match(/result_refer_pre(\d+)/);
if (!match) return;
const index = parseInt(match[1]);
const value = hiddenInput.value;
const selectedLetters = [];
const elementsToClick =[];
// 获取该题目下所有带有真实文本(label)的有效选项
const validLabels = document.querySelectorAll(`label[for^="pre${index}_op"]`);
const labelMap = {};
validLabels.forEach((lbl, idx) => {
let forId = lbl.getAttribute('for');
labelMap[forId] = {
text: lbl.innerText.trim(),
visualIndex: idx // 该选项在可见选项里的实际排序 0->A, 1->B...
};
});
// 遍历答案隐藏域,比如 "0010"
for (let i = 0; i < value.length; i++) {
if (value[i] === '1') {
let targetId = `pre${index}_op${i}`;
let targetCheckbox = document.querySelector(`#${targetId}`);
if (targetCheckbox) {
elementsToClick.push(targetCheckbox);
let labelInfo = labelMap[targetId];
if (labelInfo) {
// 优先尝试从 label 文本截取如 "A.正确" 中的 "A"
let letterMatch = labelInfo.text.match(/^[A-G]/i);
if (letterMatch) {
selectedLetters.push(letterMatch[0].toUpperCase());
} else {
// 兜底:如果文本没写A/B,按它在所有可见选项中的顺位映射 (0=A, 1=B)
const optionsMap = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
selectedLetters.push(optionsMap[labelInfo.visualIndex] || '?');
}
} else {
// 如果连 label 都没有,记作未知 ?
selectedLetters.push('?');
}
}
}
}
data.push({
index: index,
rawLetters: [...new Set(selectedLetters)].sort(), // 去重并按字母表排序(如 AC 而不会是 CA)
elements: elementsToClick
});
});
return data.sort((a, b) => a.index - b.index); // 确保按题号排序
}
// 3. 更新面板显示
function updateAnswers(data) {
let listHtml = '';
if (data.length === 0) {
listHtml = '未找到题目
请确认是否在预习页
';
} else {
data.forEach((item, i) => {
listHtml += `第${i + 1}题:${item.rawLetters.join('') || '无'}
`;
});
}
document.getElementById('answer-list').innerHTML = listHtml;
}
// 4. 执行自动勾选逻辑
function selectAnswers(isAuto = false) {
const data = getQuestionsData();
data.forEach(item => {
// 先获取当前题目的所有选项,用于覆盖和取消原本勾错的选项
const allOptionsDom = document.querySelectorAll(`input[id^="pre${item.index}_op"]`);
allOptionsDom.forEach(el => {
// 如果当前复选框被禁用(例如已提交状态),跳过点击以免报错
if (el.disabled) return;
let shouldBeChecked = item.elements.includes(el);
// 如果当前状态与目标状态不一致,则触发点击
if (el.checked !== shouldBeChecked) {
el.click(); // 触发页面本身的监听事件
if (el.checked !== shouldBeChecked) {
el.checked = shouldBeChecked; // 强制兜底勾选
el.dispatchEvent(new Event('change', { bubbles: true }));
}
}
});
});
// 按钮状态反馈
const btn = document.getElementById('auto-fill-btn');
btn.textContent = isAuto ? '已自动勾选完毕!' : '勾选完成!';
btn.style.background = '#2196F3';
setTimeout(() => { btn.textContent = '手动一键勾选'; btn.style.background = '#4CAF50'; }, 2000);
}
// 初始化脚本
window.addEventListener('load', () => {
createPanel();
// 解析数据并渲染面板
const questionsData = getQuestionsData();
updateAnswers(questionsData);
// 处理自动答题功能
const autoRunChk = document.getElementById('auto-run-chk');
const isAutoRun = localStorage.getItem('physics_helper_autorun') === 'true';
autoRunChk.checked = isAutoRun;
// 勾选框事件
autoRunChk.addEventListener('change', (e) => {
localStorage.setItem('physics_helper_autorun', e.target.checked);
if (e.target.checked) selectAnswers(true);
});
// 手动点击事件
document.getElementById('auto-fill-btn').onclick = () => selectAnswers(false);
// 如果开启了自动答题,延迟500ms执行(等待DOM节点彻底渲染完毕)
if (isAutoRun && questionsData.length > 0) {
setTimeout(() => selectAnswers(true), 500);
}
});
})();