// ==UserScript== // @name 正方教务教学评价 // @namespace https://scriptcat.org/ // @version 2.0.3 // @description 支持批量评价、自动切换课程、检测绕过的教学评价自动化脚本 // @author 征途wd // @match https://*.edu.cn/jwglxt/xspjgl/xspj_cxXspjIndex.html* // @match http://*.edu.cnw/jwglxt/xspjgl/xspj_cxXspjIndex.html* // @grant none // @license MIT // @supportURL https://github.com/wdvipa/ZF_evaluation_script/issues/new // @homepageURL https://scriptcat.org/zh-CN/script-show-page/4914 // ==/UserScript== (function () { const DEBUG_MODE = false; const Version = '2.0.3'; const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay)); const CSS_STYLES = { // 按钮样式 button: { base: ` width: 100%; height: 35px; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold; transition: all 0.3s ease; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `, hover: { opacity: '0.8', transform: 'scale(1.02)' }, normal: { opacity: '1', transform: 'scale(1)' } }, // 面板样式 panel: { main: ` position: fixed; left: 50%; top: 10px; transform: translateX(-50%); z-index: 9999; background: rgba(30, 30, 30, 0.85); backdrop-filter: blur(15px); padding: 15px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); width: 300px; height: 650px; max-height: 90vh; min-width: 200px; min-height: 200px; overflow-y: auto; cursor: move; user-select: none; color: white; font-family: 'Consolas', 'Monaco', monospace; display: flex; flex-direction: column; `, titleBar: ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 2px solid #e0e0e0; `, title: ` margin: 0; color: #fff; font-size: 16px; font-weight: bold; `, authorInfo: ` display: flex; align-items: center; gap: 8px; margin-top: 5px; font-size: 12px; color: #ccc; `, authorLink: ` color: #00a1d6; text-decoration: none; cursor: pointer; transition: color 0.3s; ` }, // 控制按钮样式 controls: { minimizeBtn: ` background: rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 4px; width: 30px; height: 30px; cursor: pointer; font-size: 20px; line-height: 1; color: white; transition: all 0.3s; `, clearLogBtn: ` background: rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 4px; width: 30px; height: 30px; cursor: pointer; font-size: 14px; line-height: 1; color: white; transition: all 0.3s; margin-right: 5px; ` }, // 设置区域样式 settings: { area: ` margin-bottom: 10px; padding: 10px; background: rgba(255, 255, 255, 0.1); border-radius: 6px; border: 1px solid rgba(255, 255, 255, 0.2); `, title: ` font-size: 14px; font-weight: bold; color: #fff; margin-bottom: 8px; `, radioGroup: ` display: flex; gap: 20px; align-items: center; `, radioLabel: ` display: flex; align-items: center; cursor: pointer; font-size: 13px; color: #fff; `, divider: ` margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(255, 255, 255, 0.2); `, select: ` width: 100%; padding: 4px 8px; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 4px; background: rgba(255, 255, 255, 0.1); color: #fff; font-size: 12px; ` }, // 按钮容器样式 buttonContainer: ` display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 6px; margin-bottom: 10px; `, // 日志区域样式 log: { area: ` margin-top: 10px; padding: 10px; background: rgba(0, 0, 0, 0.3); border-radius: 6px; border: 1px solid rgba(255, 255, 255, 0.2); min-height: 120px; overflow-y: auto; font-size: 12px; line-height: 1.4; flex: 1; display: flex; flex-direction: column; `, title: ` font-size: 13px; font-weight: bold; color: #4CAF50; margin-bottom: 5px; border-bottom: 1px solid rgba(255, 255, 255, 0.2); padding-bottom: 3px; `, content: ` color: #e0e0e0; font-family: 'Consolas', 'Monaco', monospace; white-space: pre-wrap; word-break: break-all; flex: 1; overflow-y: auto; `, line: (color) => ` color: ${color}; margin-bottom: 2px; padding: 2px 0; border-left: 3px solid ${color}; padding-left: 8px; margin-left: 5px; flex-shrink: 0; ` }, // 调整大小手柄样式 resizeHandle: ` position: absolute; bottom: 0; right: 0; width: 20px; height: 20px; background: linear-gradient(-45deg, transparent 30%, rgba(255,255,255,0.4) 30%, rgba(255,255,255,0.4) 70%, transparent 70%); cursor: nw-resize; border-bottom-right-radius: 12px; transition: all 0.3s ease; `, // 状态显示样式 status: { submitted: ` margin-top: 10px; padding: 8px 12px; background: rgba(76, 175, 80, 0.2); border: 1px solid rgba(76, 175, 80, 0.5); border-radius: 6px; color: #4CAF50; font-size: 13px; text-align: center; font-weight: bold; ` }, // 通知样式 notification: ` position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 10px 15px; border-radius: 5px; z-index: 10000; font-size: 14px; `, // 按钮颜色 colors: { excellent: '#28a745', good: '#6f42c1', qualified: '#007bff', unqualified: '#dc3545', batch: '#17a2b8', navigation: '#795548', donate: '#FFD700', debug: '#6c757d' }, // 悬停效果 hover: { authorLink: { color: '#40c5ff' }, minimizeBtn: { background: 'rgba(255, 255, 255, 0.3)' }, clearLogBtn: { background: 'rgba(255, 100, 100, 0.3)' }, resizeHandle: { background: 'linear-gradient(-45deg, transparent 20%, rgba(255,255,255,0.6) 20%, rgba(255,255,255,0.6) 80%, transparent 80%)' } } }; const applyStyles = (element, styleString) => { element.style.cssText = styleString; }; const addHoverEffect = (element, hoverStyles, normalStyles) => { element.addEventListener('mouseenter', () => { Object.assign(element.style, hoverStyles); }); element.addEventListener('mouseleave', () => { Object.assign(element.style, normalStyles); }); }; const createButton = (id, text, color) => { const button = document.createElement('button'); button.id = id; applyStyles(button, CSS_STYLES.button.base + `background-color: ${color};`); button.innerText = text; addHoverEffect(button, CSS_STYLES.button.hover, CSS_STYLES.button.normal ); return button; }; const addButton = (parent, button) => { parent.appendChild(button); }; const selectAllItems = (doc, optionIndex) => { let selectedCount = 0; for (let i = optionIndex; i < doc.length; i += 4) { if (doc[i]) { doc[i].checked = true; selectedCount++; } } logSuccess(`已选择 ${selectedCount} 个评价项的选项 ${['优秀', '良好', '合格', '不合格'][optionIndex]}`); const requiredItems = document.querySelectorAll('.tr-xspj[data-sfbt="1"]'); logDebug(`页面共有 ${requiredItems.length} 个必填评价项`); }; const autoSave = () => { const saveBtn = document.querySelector('#btn_xspj_bc') || document.querySelector('button[onclick*="baocun"]') || document.querySelector('input[value*="保存"]'); if (saveBtn) { if (!$(saveBtn).data("enter")) { $(saveBtn).data("enter", "1"); } const mouseEnterEvent = new MouseEvent('mouseenter', { bubbles: true, cancelable: true, view: window }); saveBtn.dispatchEvent(mouseEnterEvent); setTimeout(() => { saveBtn.click(); logSuccess('✅ 已自动保存评价'); }, 100); return true; } else { logError('❌ 未找到保存按钮'); return false; } }; const autoSubmit = () => { const saved = autoSave(); if (saved) { setTimeout(() => { const submitBtn = document.querySelector('#btn_xspj_tj') || document.querySelector('button[onclick*="tijiao"]') || document.querySelector('input[value*="提交"]') || document.querySelector('button:contains("提交")'); if (submitBtn) { if (!$(submitBtn).data("enter")) { $(submitBtn).data("enter", "1"); } const mouseEnterEvent = new MouseEvent('mouseenter', { bubbles: true, cancelable: true, view: window }); submitBtn.dispatchEvent(mouseEnterEvent); setTimeout(() => { submitBtn.click(); logSuccess('✅ 已自动提交评价'); }, 100); } else { logError('❌ 未找到提交按钮'); } }, 1000); } }; const configPanel = document.createElement('div'); configPanel.id = 'evalConfigPanel'; applyStyles(configPanel, CSS_STYLES.panel.main); const titleBar = document.createElement('div'); applyStyles(titleBar, CSS_STYLES.panel.titleBar); const title = document.createElement('h4'); applyStyles(title, CSS_STYLES.panel.title); title.textContent = `🎯 评价助手 v${Version}`; const authorInfo = document.createElement('div'); applyStyles(authorInfo, CSS_STYLES.panel.authorInfo); const authorText = document.createElement('span'); authorText.textContent = 'by:'; const authorLink = document.createElement('a'); authorLink.href = 'https://space.bilibili.com/353379484'; authorLink.target = '_blank'; authorLink.textContent = '征途wd'; applyStyles(authorLink, CSS_STYLES.panel.authorLink); addHoverEffect(authorLink, CSS_STYLES.hover.authorLink, { color: '#00a1d6' } ); authorInfo.appendChild(authorText); authorInfo.appendChild(authorLink); const minimizeBtn = document.createElement('button'); minimizeBtn.textContent = '−'; applyStyles(minimizeBtn, CSS_STYLES.controls.minimizeBtn); addHoverEffect(minimizeBtn, CSS_STYLES.hover.minimizeBtn, { background: 'rgba(255, 255, 255, 0.2)' } ); const clearLogBtn = document.createElement('button'); clearLogBtn.textContent = '🗑️'; clearLogBtn.title = '清空日志'; applyStyles(clearLogBtn, CSS_STYLES.controls.clearLogBtn); addHoverEffect(clearLogBtn, CSS_STYLES.hover.clearLogBtn, { background: 'rgba(255, 255, 255, 0.2)' } ); const buttonGroup = document.createElement('div'); buttonGroup.style.display = 'flex'; buttonGroup.appendChild(clearLogBtn); buttonGroup.appendChild(minimizeBtn); const titleContainer = document.createElement('div'); titleContainer.appendChild(title); titleContainer.appendChild(authorInfo); titleBar.appendChild(titleContainer); titleBar.appendChild(buttonGroup); // 创建设置区域 const settingsArea = document.createElement('div'); applyStyles(settingsArea, CSS_STYLES.settings.area); const settingsTitle = document.createElement('div'); applyStyles(settingsTitle, CSS_STYLES.settings.title); settingsTitle.textContent = '⚙️ 自动操作设置'; const radioGroup = document.createElement('div'); applyStyles(radioGroup, CSS_STYLES.settings.radioGroup); // 自动保存选项 const saveRadio = document.createElement('label'); applyStyles(saveRadio, CSS_STYLES.settings.radioLabel); saveRadio.innerHTML = ` 自动保存 `; // 自动提交选项 const submitRadio = document.createElement('label'); applyStyles(submitRadio, CSS_STYLES.settings.radioLabel); submitRadio.innerHTML = ` 自动提交 `; // 不自动操作选项 const noneRadio = document.createElement('label'); applyStyles(noneRadio, CSS_STYLES.settings.radioLabel); noneRadio.innerHTML = ` 不自动操作 `; radioGroup.appendChild(saveRadio); radioGroup.appendChild(submitRadio); radioGroup.appendChild(noneRadio); // 添加自动切换课程选项 const autoSwitchDiv = document.createElement('div'); applyStyles(autoSwitchDiv, CSS_STYLES.settings.divider); const autoSwitchLabel = document.createElement('label'); applyStyles(autoSwitchLabel, CSS_STYLES.settings.radioLabel); autoSwitchLabel.innerHTML = ` 自动跳过已提交课程 `; autoSwitchDiv.appendChild(autoSwitchLabel); // 添加评价后自动切换选项 const autoNextDiv = document.createElement('div'); applyStyles(autoNextDiv, CSS_STYLES.settings.divider); const autoNextLabel = document.createElement('label'); applyStyles(autoNextLabel, CSS_STYLES.settings.radioLabel); autoNextLabel.innerHTML = ` 评价后自动切换下一个 `; autoNextDiv.appendChild(autoNextLabel); // 添加批量评价等级选择 const batchLevelDiv = document.createElement('div'); applyStyles(batchLevelDiv, CSS_STYLES.settings.divider); const batchLevelTitle = document.createElement('div'); applyStyles(batchLevelTitle, CSS_STYLES.settings.title.replace('14px', '12px').replace('8px', '5px')); batchLevelTitle.textContent = '批量评价等级:'; const batchLevelSelect = document.createElement('select'); batchLevelSelect.id = 'batchEvalLevel'; applyStyles(batchLevelSelect, CSS_STYLES.settings.select); const levelOptions = [ ['🌟 优秀 (100分)', '100'], ['👍 良好 (85分)', '85'], ['✅ 合格 (75分)', '75'], ['❌ 不合格 (50分)', '50'] ]; levelOptions.forEach(([text, score], index) => { const option = document.createElement('option'); Object.assign(option, { value: index, textContent: text }); option.setAttribute('data-score', score); batchLevelSelect.appendChild(option); }); // 默认选择优秀 batchLevelSelect.value = '0'; batchLevelDiv.appendChild(batchLevelTitle); batchLevelDiv.appendChild(batchLevelSelect); settingsArea.appendChild(settingsTitle); settingsArea.appendChild(radioGroup); settingsArea.appendChild(autoSwitchDiv); settingsArea.appendChild(autoNextDiv); settingsArea.appendChild(batchLevelDiv); // 创建按钮容器 const btnContainer = document.createElement('div'); applyStyles(btnContainer, CSS_STYLES.buttonContainer); const btna = createButton('btna', '🌟 优秀', CSS_STYLES.colors.excellent); const btnb = createButton('btnb', '👍 良好', CSS_STYLES.colors.good); const btnc = createButton('btnc', '✅ 合格', CSS_STYLES.colors.qualified); const btnd = createButton('btnd', '❌ 不合格', CSS_STYLES.colors.unqualified); const btnAuto = createButton('btnAuto', '⚡ 批量评价', CSS_STYLES.colors.batch); // 添加课程切换按钮 const btnPrev = createButton('btnPrev', '⬅️ 上一个', CSS_STYLES.colors.navigation); const btnNext = createButton('btnNext', '➡️ 下一个', CSS_STYLES.colors.navigation); // 添加打赏按钮 const btnDonate = createButton('btnDonate', '💰 打赏作者', CSS_STYLES.colors.donate); addButton(btnContainer, btna); addButton(btnContainer, btnb); addButton(btnContainer, btnc); addButton(btnContainer, btnd); addButton(btnContainer, btnAuto); addButton(btnContainer, btnPrev); addButton(btnContainer, btnNext); addButton(btnContainer, btnDonate); // 创建日志区域 const logArea = document.createElement('div'); applyStyles(logArea, CSS_STYLES.log.area); const logTitle = document.createElement('div'); applyStyles(logTitle, CSS_STYLES.log.title); logTitle.textContent = '📋 运行日志'; const logContent = document.createElement('div'); logContent.id = 'scriptLog'; applyStyles(logContent, CSS_STYLES.log.content); logArea.appendChild(logTitle); logArea.appendChild(logContent); // 创建调整大小手柄 const resizeHandle = document.createElement('div'); applyStyles(resizeHandle, CSS_STYLES.resizeHandle); // 添加悬停效果 addHoverEffect(resizeHandle, CSS_STYLES.hover.resizeHandle, { background: 'linear-gradient(-45deg, transparent 30%, rgba(255,255,255,0.4) 30%, rgba(255,255,255,0.4) 70%, transparent 70%)' } ); // 组装面板 configPanel.appendChild(titleBar); configPanel.appendChild(logArea); configPanel.appendChild(settingsArea); configPanel.appendChild(btnContainer); configPanel.appendChild(resizeHandle); document.body.appendChild(configPanel); // 实现拖拽功能 let isDragging = false; let isResizing = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; titleBar.addEventListener('mousedown', dragStart); resizeHandle.addEventListener('mousedown', resizeStart); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); function dragStart(e) { if (e.target === titleBar || e.target === title) { // 如果是最小化状态,点击标题栏展开 if (isMinimized) { minimizeBtn.click(); return; } initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; isDragging = true; e.preventDefault(); } } function resizeStart(e) { isResizing = true; initialX = e.clientX; initialY = e.clientY; e.preventDefault(); e.stopPropagation(); } function handleMouseMove(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; configPanel.style.transform = `translate(calc(-50% + ${currentX}px), ${currentY}px)`; } else if (isResizing) { e.preventDefault(); const deltaX = e.clientX - initialX; const deltaY = e.clientY - initialY; const currentWidth = parseInt(configPanel.style.width) || 350; // 获取当前高度,如果是auto则使用实际高度 let currentHeight; if (configPanel.style.height === 'auto' || !configPanel.style.height) { currentHeight = configPanel.offsetHeight; } else { currentHeight = parseInt(configPanel.style.height); } const newWidth = Math.max(300, currentWidth + deltaX); const newHeight = Math.max(200, currentHeight + deltaY); configPanel.style.width = newWidth + 'px'; configPanel.style.height = newHeight + 'px'; // 确保面板有滚动功能 configPanel.style.overflowY = 'auto'; // 更新日志区域布局 updateLogAreaLayout(); initialX = e.clientX; initialY = e.clientY; } } function handleMouseUp(e) { if (isDragging) { initialX = currentX; initialY = currentY; } isDragging = false; isResizing = false; } // 更新日志区域响应式布局 const updateLogAreaLayout = () => { const logElement = document.getElementById('scriptLog'); if (logElement) { // 确保滚动到底部 setTimeout(() => { logElement.scrollTop = logElement.scrollHeight; }, 10); } }; // 日志系统 const originalConsoleLog = console.log; const logToPanel = (message, type = 'info') => { const timestamp = new Date().toLocaleTimeString(); const logElement = document.getElementById('scriptLog'); if (logElement) { const colors = { info: '#e0e0e0', success: '#4CAF50', warning: '#FF9800', error: '#f44336', debug: '#2196F3' }; const logLine = document.createElement('div'); applyStyles(logLine, CSS_STYLES.log.line(colors[type] || colors.info)); logLine.textContent = `[${timestamp}] ${message}`; logElement.appendChild(logLine); // 自动滚动到底部 updateLogAreaLayout(); // 限制日志条数,避免内存占用过多 while (logElement.children.length > 150) { logElement.removeChild(logElement.firstChild); } } }; console.log = function(...args) { const message = args.join(' '); logToPanel(message, 'info'); originalConsoleLog.apply(console, args); }; const logFunctions = { logSuccess: 'success', logWarning: 'warning', logError: 'error' }; Object.entries(logFunctions).forEach(([name, type]) => { window[name] = (message) => logToPanel(message, type); }); window.logDebug = (message) => { if (DEBUG_MODE) { logToPanel(message, 'debug'); } }; // 清空日志功能 clearLogBtn.addEventListener('click', () => { const logElement = document.getElementById('scriptLog'); if (logElement) { logElement.innerHTML = ''; logSuccess('日志已清空'); updateLogAreaLayout(); } }); // 最小化/展开功能 let isMinimized = false; let originalWidth = '300px'; let originalHeight = '650px'; minimizeBtn.addEventListener('click', () => { isMinimized = !isMinimized; if (isMinimized) { // 保存当前尺寸 originalWidth = configPanel.style.width || '300px'; originalHeight = configPanel.style.height || '650px'; settingsArea.style.display = 'none'; btnContainer.style.display = 'none'; logArea.style.display = 'none'; resizeHandle.style.display = 'none'; // 隐藏状态提示框 const statusDiv = document.getElementById('submissionStatusDiv'); if (statusDiv) { statusDiv.style.display = 'none'; } // 最小化样式优化 minimizeBtn.textContent = '📌'; minimizeBtn.title = '展开面板'; title.textContent = '🎯 评价助手'; authorInfo.style.display = 'none'; configPanel.style.width = '140px'; configPanel.style.height = 'auto'; configPanel.style.minHeight = 'auto'; configPanel.style.padding = '8px 12px'; configPanel.style.flexDirection = 'row'; configPanel.style.cursor = 'pointer'; titleBar.style.marginBottom = '0'; titleBar.style.paddingBottom = '0'; titleBar.style.borderBottom = 'none'; titleBar.style.justifyContent = 'flex-start'; titleBar.style.gap = '8px'; // 添加点击标题栏展开的功能 titleBar.style.cursor = 'pointer'; } else { settingsArea.style.display = 'block'; btnContainer.style.display = 'grid'; logArea.style.display = 'flex'; resizeHandle.style.display = 'block'; // 显示状态提示框 const statusDiv = document.getElementById('submissionStatusDiv'); if (statusDiv) { statusDiv.style.display = 'block'; } // 恢复展开样式 minimizeBtn.textContent = '−'; minimizeBtn.title = '最小化面板'; title.textContent = `🎯 评价助手 v${Version}`; authorInfo.style.display = 'flex'; configPanel.style.width = originalWidth; configPanel.style.height = originalHeight; configPanel.style.minHeight = '200px'; configPanel.style.padding = '15px'; configPanel.style.flexDirection = 'column'; configPanel.style.cursor = 'move'; titleBar.style.marginBottom = '10px'; titleBar.style.paddingBottom = '10px'; titleBar.style.borderBottom = '2px solid #e0e0e0'; titleBar.style.justifyContent = 'space-between'; titleBar.style.gap = ''; titleBar.style.cursor = 'move'; // 确保日志区域正确显示 setTimeout(() => { const logElement = document.getElementById('scriptLog'); if (logElement && logElement.scrollHeight > logElement.clientHeight) { logElement.scrollTop = logElement.scrollHeight; } }, 100); } }); // 获取当前设置 const getAutoAction = () => { const selected = document.querySelector('input[name="autoAction"]:checked'); return selected ? selected.value : 'save'; }; // 获取自动切换设置 const getAutoSwitchSetting = () => { const checkbox = document.getElementById('autoSwitchCourse'); return checkbox ? checkbox.checked : false; }; // 获取评价后自动切换下一个设置 const getAutoNextAfterEvalSetting = () => { const checkbox = document.getElementById('autoNextAfterEval'); return checkbox ? checkbox.checked : false; }; // 获取批量评价等级设置 const getBatchEvalLevel = () => { const select = document.getElementById('batchEvalLevel'); if (!select) return { index: 0, score: '100', name: '优秀' }; const selectedOption = select.options[select.selectedIndex]; const levelNames = ['优秀', '良好', '合格', '不合格']; return { index: parseInt(select.value), score: selectedOption.getAttribute('data-score'), name: levelNames[parseInt(select.value)] }; }; // 课程切换功能 const switchCourse = (direction) => { logSuccess(`🔄 正在切换到${direction === 'next' ? '下一个' : '上一个'}课程...`); try { // 方法1: 通过jqGrid表格切换课程 const grid = $('#tempGrid'); if (grid.length > 0) { const currentRowId = grid.jqGrid('getGridParam', 'selrow'); const allRowIds = grid.jqGrid('getDataIDs'); logDebug(`当前选中行ID: ${currentRowId}, 总行数: ${allRowIds.length}`); if (allRowIds.length > 0) { let targetRowId = null; const currentIndex = allRowIds.indexOf(currentRowId); if (direction === 'next') { // 查找下一个课程 for (let i = currentIndex + 1; i < allRowIds.length; i++) { const rowData = grid.jqGrid('getRowData', allRowIds[i]); // 如果开启了自动跳过,则跳过已提交的课程 if (getAutoSwitchSetting() && (rowData.tjztmc === '提交')) { logDebug(`跳过已提交课程: ${rowData.jxbmc}`); continue; } targetRowId = allRowIds[i]; break; } // 如果没找到,从头开始找 if (!targetRowId) { for (let i = 0; i < currentIndex; i++) { const rowData = grid.jqGrid('getRowData', allRowIds[i]); if (getAutoSwitchSetting() && (rowData.tjztmc === '提交')) { continue; } targetRowId = allRowIds[i]; break; } } } else { // 查找上一个课程 for (let i = currentIndex - 1; i >= 0; i--) { const rowData = grid.jqGrid('getRowData', allRowIds[i]); if (getAutoSwitchSetting() && (rowData.tjztmc === '提交')) { logDebug(`跳过已提交课程: ${rowData.jxbmc}`); continue; } targetRowId = allRowIds[i]; break; } // 如果没找到,从末尾开始找 if (!targetRowId) { for (let i = allRowIds.length - 1; i > currentIndex; i--) { const rowData = grid.jqGrid('getRowData', allRowIds[i]); if (getAutoSwitchSetting() && (rowData.tjztmc === '提交')) { continue; } targetRowId = allRowIds[i]; break; } } } if (targetRowId) { const targetRowData = grid.jqGrid('getRowData', targetRowId); logSuccess(`🎯 切换到课程: ${targetRowData.jxbmc} (${targetRowData.jzgmc})`); // 选中目标行 grid.jqGrid('setSelection', targetRowId); // 触发行选择事件,加载课程评价内容 setTimeout(() => { const row = $(`#${targetRowId}`); if (row.length > 0) { row.trigger('click'); // 等待页面加载完成后重新检测提交状态 setTimeout(() => { logDebug('🔍 重新检测课程提交状态...'); displaySubmissionStatus(); }, 2000); } }, 500); return true; } else { if (getAutoSwitchSetting()) { logWarning('❌ 没有找到未提交的课程'); } else { logWarning('❌ 已经是最后一个课程'); } return false; } } } // 方法2: 通过课程链接切换(备用方案) const courseRows = document.querySelectorAll('#tempGrid tr[role="row"]'); if (courseRows.length > 1) { // 排除表头 logDebug(`找到 ${courseRows.length - 1} 个课程行`); // 查找当前选中的行 const selectedRow = document.querySelector('#tempGrid tr.ui-state-highlight'); if (selectedRow) { const allRows = Array.from(courseRows).slice(1); // 排除表头 const currentIndex = allRows.indexOf(selectedRow); let targetIndex = -1; if (direction === 'next') { targetIndex = (currentIndex + 1) % allRows.length; } else { targetIndex = currentIndex > 0 ? currentIndex - 1 : allRows.length - 1; } if (targetIndex >= 0 && allRows[targetIndex]) { const targetRow = allRows[targetIndex]; targetRow.click(); const courseName = targetRow.querySelector('td[aria-describedby*="jxbmc"]')?.textContent || '未知课程'; const teacherName = targetRow.querySelector('td[aria-describedby*="jzgmc"]')?.textContent || '未知教师'; logSuccess(`🎯 切换到课程: ${courseName} (${teacherName})`); // 等待页面加载完成后重新检测提交状态 setTimeout(() => { logDebug('🔍 重新检测课程提交状态...'); displaySubmissionStatus(); }, 2000); return true; } } } return true; } catch (error) { logError(`❌ 课程切换失败: ${error.message}`); return false; } }; // 自动跳过已提交课程 const autoSkipSubmittedCourse = () => { if (!getAutoSwitchSetting()) { return false; } if (checkSubmissionStatus()) { logWarning('🔄 检测到已提交课程,自动跳转到下一个...'); setTimeout(() => { const switched = switchCourse('next'); if (!switched) { logWarning('🚫 无法自动切换,可能已经是最后一个课程'); } }, 2000); return true; } return false; }; // 检测是否已提交 const checkSubmissionStatus = () => { // 检查方法1: 检查tjzt隐藏字段 const tjztElement = document.getElementById('tjzt'); if (tjztElement && tjztElement.value === '1') { return true; } // 检查方法2: 检查是否存在单选按钮 const radioButtons = document.getElementsByClassName("radio-pjf"); if (radioButtons.length === 0) { return true; } // 检查方法3: 检查评价项是否为纯文本显示 const evaluationRows = document.querySelectorAll('.tr-xspj td:nth-child(2)'); if (evaluationRows.length > 0) { // 如果第一个评价项只包含文本(如"优秀")而不包含input元素 const firstRow = evaluationRows[0]; const hasInputs = firstRow.querySelector('input, select, textarea'); if (!hasInputs && firstRow.textContent.trim().length > 0) { return true; } } return false; }; // 显示提交状态 const displaySubmissionStatus = () => { const isSubmitted = checkSubmissionStatus(); // 清除之前的状态显示 const existingStatus = settingsArea.parentNode.querySelector('div[style*="rgba(76, 175, 80, 0.2)"]'); if (existingStatus) { existingStatus.remove(); } if (isSubmitted) { // 创建已提交状态显示 const statusDiv = document.createElement('div'); statusDiv.id = 'submissionStatusDiv'; applyStyles(statusDiv, CSS_STYLES.status.submitted); statusDiv.innerHTML = '✅ 评价已提交完成'; // 重置所有按钮状态 const buttons = btnContainer.querySelectorAll('button'); buttons.forEach(btn => { btn.disabled = false; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; btn.title = ''; }); // 禁用评价按钮,但保留导航和一键优秀按钮 buttons.forEach(btn => { // 不禁用调试、一键优秀、上一个、下一个、打赏按钮 if (btn.id !== 'btnDebug' && btn.id !== 'btnAuto' && btn.id !== 'btnPrev' && btn.id !== 'btnNext' && btn.id !== 'btnDonate') { btn.disabled = true; btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; btn.title = '评价已提交,无法再次操作'; } }); // 重置设置选项 const radioInputs = settingsArea.querySelectorAll('input[type="radio"]'); radioInputs.forEach(radio => { radio.disabled = false; }); // 添加状态显示到设置区域后面 settingsArea.parentNode.insertBefore(statusDiv, btnContainer); logWarning('🚫 检测到评价已提交,评价功能已禁用'); // 获取总分信息 const scoreElement = document.querySelector('.xspjSum'); if (scoreElement) { const scoreText = scoreElement.textContent || scoreElement.innerText; logSuccess('📊 ' + scoreText); } // 保持批量优秀按钮功能不变 // 检查是否需要自动跳过 setTimeout(() => { autoSkipSubmittedCourse(); }, 1000); return true; } else { // 重置所有按钮状态 const buttons = btnContainer.querySelectorAll('button'); buttons.forEach(btn => { btn.disabled = false; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; btn.title = ''; }); // 重置设置选项 const radioInputs = settingsArea.querySelectorAll('input[type="radio"]'); radioInputs.forEach(radio => { radio.disabled = false; }); // 确保批量优秀按钮显示正确的文本 if (btnAuto) { btnAuto.innerText = '⚡ 批量评价'; btnAuto.title = '批量评价所有未提交课程'; btnAuto.style.backgroundColor = CSS_STYLES.colors.batch; // 恢复原色 } logSuccess('✅ 评价未提交,功能正常可用'); return false; } }; if (DEBUG_MODE) { logDebug('🔧 调试模式已开启,调试按钮可用'); } // 添加脚本信息提示 logSuccess(`🎯 正方教务评价脚本已加载 - 脚本猫版本 v${Version}`); logSuccess('📝 使用说明:点击对应按钮进行评价选择'); logSuccess('🚀 批量评价:可在设置中选择评价等级,批量处理所有未提交课程'); // 调整面板初始高度以显示所有内容 const adjustPanelHeight = () => { // 等待所有元素渲染完成 setTimeout(() => { const panelContent = configPanel.scrollHeight; const viewportHeight = window.innerHeight * 0.9; // 90vh const defaultHeight = 650; // 如果内容高度超过视口高度,使用最大高度并启用滚动 if (panelContent > viewportHeight) { configPanel.style.height = viewportHeight + 'px'; configPanel.style.overflowY = 'auto'; } else if (panelContent > defaultHeight) { configPanel.style.height = 'auto'; configPanel.style.overflowY = 'visible'; } else { configPanel.style.height = defaultHeight + 'px'; configPanel.style.overflowY = 'visible'; } }, 100); }; // 检测提交状态 setTimeout(() => { displaySubmissionStatus(); if (btnAuto) { btnAuto.innerText = '⚡ 批量评价'; btnAuto.title = '批量评价所有未提交课程'; btnAuto.style.backgroundColor = '#17a2b8'; } // 调整面板高度 adjustPanelHeight(); }, 500); // 监听自动切换设置变化 setTimeout(() => { const autoSwitchCheckbox = document.getElementById('autoSwitchCourse'); if (autoSwitchCheckbox) { autoSwitchCheckbox.addEventListener('change', function() { const isEnabled = this.checked; logSuccess(`🔄 自动跳过已提交课程: ${isEnabled ? '已开启' : '已关闭'}`); // 如果刚开启且当前课程已提交,立即跳转 if (isEnabled && checkSubmissionStatus()) { logWarning('🔄 当前课程已提交,即将自动跳转...'); setTimeout(() => { switchCourse('next'); }, 1000); } }); } const autoNextCheckbox = document.getElementById('autoNextAfterEval'); if (autoNextCheckbox) { autoNextCheckbox.addEventListener('change', function() { const isEnabled = this.checked; logSuccess(`⏭️ 评价后自动切换下一个: ${isEnabled ? '已开启' : '已关闭'}`); if (isEnabled) { logSuccess('💡 提示: 点击评价按钮后将自动切换到下一个课程'); } else { logSuccess('💡 提示: 评价后需要手动切换课程'); } }); } const batchLevelSelect = document.getElementById('batchEvalLevel'); if (batchLevelSelect) { batchLevelSelect.addEventListener('change', function() { const evalLevel = getBatchEvalLevel(); logSuccess(`🎯 批量评价等级已设置为: ${evalLevel.name} (${evalLevel.score}分)`); }); } }, 1000); // 绕过检测系统 const bypassDetection = () => { // 1. 绕过脚本注入检测 if (window.$ && $.i18n && $.i18n.get) { const originalGet = $.i18n.get; $.i18n.get = function(key) { // 拦截脚本注入警告 if (key === 'qwsyjbzr') { logWarning('🛡️ 已拦截脚本注入检测'); return ''; } return originalGet.call(this, key); }; } // 2. 模拟正常的设备检测 Object.defineProperty(navigator, 'userAgent', { get: function() { return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'; }, configurable: true }); // 3. 绕过进度检测 window.inProgress = false; // 4. 模拟鼠标事件 const simulateMouseEvents = () => { document.addEventListener('click', function(e) { if (e.target.id === 'btn_xspj_bc' || e.target.id === 'btn_xspj_tj') { // 确保按钮有enter标记 if (!$(e.target).data("enter")) { $(e.target).data("enter", "1"); } } }, true); }; simulateMouseEvents(); logDebug('🛡️ 检测绕过系统已启用'); }; // 启用绕过系统 bypassDetection(); // 监听窗口大小变化,确保日志区域正确响应 window.addEventListener('resize', () => { updateLogAreaLayout(); }); // 使用ResizeObserver监听面板大小变化 if (window.ResizeObserver) { const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { if (entry.target === configPanel) { updateLogAreaLayout(); } } }); resizeObserver.observe(configPanel); } // 监听页面内容变化,自动重新检测提交状态 const observePageChanges = () => { const targetNode = document.getElementById('panel_content'); if (targetNode) { const observer = new MutationObserver((mutations) => { let shouldRecheck = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' || mutation.type === 'subtree') { // 检查是否有重要内容变化 if (mutation.target.querySelector('.xspjSum') || mutation.target.querySelector('#tjzt') || mutation.target.querySelector('.radio-pjf')) { shouldRecheck = true; } } }); if (shouldRecheck) { logDebug('🔍 检测到页面内容变化,重新检测提交状态...'); setTimeout(() => { displaySubmissionStatus(); }, 1000); } }); observer.observe(targetNode, { childList: true, subtree: true, attributes: true, attributeFilter: ['value'] }); logDebug('📡 页面变化监听器已启动'); } }; // 启动页面变化监听 setTimeout(observePageChanges, 2000); // 拦截AJAX检测请求 const interceptAjaxDetection = () => { if (window.jQuery && jQuery.post) { const originalPost = jQuery.post; jQuery.post = function(url, data, callback, type) { // 拦截数据异常检测 if (url && (url.includes('cxInsjjg') || url.includes('cxSftf') || url.includes('cxInmfzb') || url.includes('cxInmffz'))) { logDebug('🛡️ 已拦截检测请求: ' + url); // 返回正常结果 if (typeof callback === 'function') { setTimeout(() => callback(0), 100); } return; } return originalPost.apply(this, arguments); }; } // 拦截设备检测函数 if (window.isPc2) { window.isPc2 = function() { return true; // 始终返回PC }; } if (window.detectMobileOrTablet) { window.detectMobileOrTablet = function() { return "desktop"; // 始终返回桌面 }; } logDebug('🛡️ AJAX检测拦截已启用'); }; // 延迟启用AJAX拦截,确保jQuery已加载 setTimeout(interceptAjaxDetection, 1000); // 监听系统弹窗和函数调用 const originalAlert = window.alert; window.alert = function(message) { logWarning('🚨 [系统弹窗] Alert: ' + message); // 拦截特定的检测警告 if (message.includes('脚本注入') || message.includes('qwsyjbzr')) { logWarning('🛡️ 已拦截脚本检测警告'); return; } return originalAlert.call(window, message); }; const originalConfirm = window.confirm; window.confirm = function(message) { logWarning('❓ [系统弹窗] Confirm: ' + message); const result = originalConfirm.call(window, message); logSuccess('✅ [用户选择]: ' + (result ? '确定' : '取消')); return result; }; // 通用评价处理函数 const handleEvaluation = async (index, score, name) => { if (checkSubmissionStatus()) { logError('❌ 评价已提交,无法再次操作'); return; } await sleep(200); const doc = document.getElementsByClassName("radio-pjf"); const input = document.getElementsByClassName("input-sm input-pjf"); if (doc.length === 0) { logError('❌ 未找到评价选项,可能已提交'); return; } if (input.length > 0) { input[0].value = score; } selectAllItems(doc, index); logSuccess(`✅ 已选择${name}评价`); await sleep(800); const action = getAutoAction(); if (action === 'save') { autoSave(); } else if (action === 'submit') { autoSubmit(); } if (getAutoNextAfterEvalSetting()) { logSuccess('🔄 评价完成,准备自动切换到下一个课程...'); setTimeout(() => { const switched = switchCourse('next'); if (!switched) { logWarning('❌ 无法自动切换,可能已经是最后一个课程'); } }, 2000); } }; // 评价按钮事件处理 btna.addEventListener('click', () => handleEvaluation(0, "100", "优秀")); btnb.addEventListener('click', () => handleEvaluation(1, "85", "良好")); btnc.addEventListener('click', () => handleEvaluation(2, "75", "合格")); btnd.addEventListener('click', () => handleEvaluation(3, "50", "不合格")); // 批量评价所有未提交课程的功能 const batchEvaluateAllCourses = async () => { logSuccess('🚀 开始批量评价所有未提交课程...'); const grid = $('#tempGrid'); if (grid.length === 0) { logError('❌ 未找到课程列表'); return false; } const allRowIds = grid.jqGrid('getDataIDs'); const unsubmittedCourses = []; // 查找所有未提交的课程 for (let rowId of allRowIds) { const rowData = grid.jqGrid('getRowData', rowId); if (rowData.tjztmc !== '提交') { unsubmittedCourses.push({ id: rowId, name: rowData.jxbmc, teacher: rowData.jzgmc, status: rowData.tjztmc }); } } if (unsubmittedCourses.length === 0) { logWarning('🎉 所有课程都已提交完成!'); return true; } logSuccess(`📋 找到 ${unsubmittedCourses.length} 个未提交课程,开始批量评价...`); let successCount = 0; let failCount = 0; for (let i = 0; i < unsubmittedCourses.length; i++) { const course = unsubmittedCourses[i]; logSuccess(`📚 [${i + 1}/${unsubmittedCourses.length}] 正在评价: ${course.name} (${course.teacher})`); try { // 切换到目标课程 grid.jqGrid('setSelection', course.id); await sleep(500); // 触发行点击事件加载课程内容 const row = $(`#${course.id}`); if (row.length > 0) { row.trigger('click'); await sleep(2000); // 等待页面加载 } // 检查是否已经提交(可能在加载过程中状态发生变化) if (checkSubmissionStatus()) { logWarning(`⚠️ 课程 ${course.name} 已提交,跳过`); continue; } // 执行评价 const doc = document.getElementsByClassName("radio-pjf"); const input = document.getElementsByClassName("input-sm input-pjf"); if (doc.length === 0) { logError(`❌ 课程 ${course.name} 未找到评价选项`); failCount++; continue; } const evalLevel = getBatchEvalLevel(); if (input.length > 0) { input[0].value = evalLevel.score; } selectAllItems(doc, evalLevel.index); logSuccess(`✅ 已完成课程 ${course.name} 的${evalLevel.name}评价`); await sleep(800); const action = getAutoAction(); if (action === 'save') { autoSave(); await sleep(1500); } else if (action === 'submit') { autoSubmit(); await sleep(2500); } else { autoSave(); await sleep(1500); } successCount++; logSuccess(`🎯 课程 ${course.name} 评价完成 (${successCount}/${unsubmittedCourses.length})`); await sleep(1000); } catch (error) { logError(`❌ 评价课程 ${course.name} 时出错: ${error.message}`); failCount++; } } // 显示批量评价结果 logSuccess('🏁 批量评价完成!'); logSuccess(`📊 成功: ${successCount} 个课程`); if (failCount > 0) { logWarning(`⚠️ 失败: ${failCount} 个课程`); } logSuccess('🔄 正在刷新页面状态...'); // 刷新页面状态 setTimeout(() => { location.reload(); }, 2000); return true; }; // 一键批量评价功能 btnAuto.addEventListener('click', async function () { const grid = $('#tempGrid'); if (grid.length === 0) { logError('❌ 未找到课程列表'); return; } const allRowIds = grid.jqGrid('getDataIDs'); const unsubmittedCount = allRowIds.filter(rowId => { const rowData = grid.jqGrid('getRowData', rowId); return rowData.tjztmc !== '提交'; }).length; if (unsubmittedCount === 0) { logSuccess('🎉 所有课程都已提交完成!'); logSuccess('✅ 无需进行批量评价操作'); return; } const isCurrentSubmitted = checkSubmissionStatus(); if (isCurrentSubmitted) { logSuccess(`💡 当前课程已提交,将批量评价其他 ${unsubmittedCount} 个未提交课程`); } const actionText = getAutoAction() === 'submit' ? '提交' : '保存'; const evalLevel = getBatchEvalLevel(); if (confirm(`确定要批量评价所有未提交的课程吗?\n\n📊 未提交课程数量: ${unsubmittedCount} 个\n🎯 评价等级: ${evalLevel.name} (${evalLevel.score}分)\n⚙️ 操作模式: 自动${actionText}\n⏱️ 预计耗时: ${Math.ceil(unsubmittedCount * 6)} 秒\n\n⚠️ 注意: 此操作将自动处理所有未提交课程,请确认后继续。`)) { this.disabled = true; this.style.opacity = '0.5'; this.innerText = '🔄 批量评价中...'; try { await batchEvaluateAllCourses(); } catch (error) { logError(`❌ 批量评价过程中出错: ${error.message}`); } finally { setTimeout(() => { this.disabled = false; this.style.opacity = '1'; this.innerText = '⚡ 批量评价'; }, 3000); } } }); // 通用课程切换处理函数 const handleCourseSwitch = function(direction) { this.disabled = true; this.style.opacity = '0.5'; const switched = switchCourse(direction); if (!switched) { logWarning('❌ 切换失败,请手动选择课程'); } setTimeout(() => { this.disabled = false; this.style.opacity = '1'; }, 3000); }; // 课程切换按钮事件 btnPrev.addEventListener('click', function() { handleCourseSwitch.call(this, 'prev'); }); btnNext.addEventListener('click', function() { handleCourseSwitch.call(this, 'next'); }); // 打赏按钮事件处理 btnDonate.addEventListener('click', function() { window.open('https://dg.wdvip.top/dashang', '_blank'); logSuccess('💰 感谢您的支持!已打开打赏页面'); }); // 根据调试模式添加调试按钮 if (DEBUG_MODE) { const btnDebug = createButton('btnDebug', '🔧 调试', CSS_STYLES.colors.debug); addButton(btnContainer, btnDebug); } // 调试按钮事件处理(仅在调试模式下) if (DEBUG_MODE) { const btnDebug = btnContainer.querySelector('#btnDebug'); if (btnDebug) { btnDebug.addEventListener('click', function() { const doc = document.getElementsByClassName("radio-pjf"); const requiredItems = document.querySelectorAll('.tr-xspj[data-sfbt="1"]'); const isSubmitted = checkSubmissionStatus(); const tjztElement = document.getElementById('tjzt'); logDebug('=== 调试信息 ==='); logDebug(`提交状态: ${isSubmitted ? '已提交' : '未提交'}`); logDebug(`tjzt值: ${tjztElement ? tjztElement.value : '未找到'}`); logDebug(`总共找到 ${doc.length} 个单选按钮`); logDebug(`总共找到 ${requiredItems.length} 个必填评价项`); logDebug(`预期应该有 ${requiredItems.length * 4} 个单选按钮`); logDebug(`当前设置: ${getAutoAction()}`); logDebug(`自动跳过已提交: ${getAutoSwitchSetting() ? '开启' : '关闭'}`); logDebug(`评价后自动切换: ${getAutoNextAfterEvalSetting() ? '开启' : '关闭'}`); const evalLevel = getBatchEvalLevel(); logDebug(`批量评价等级: ${evalLevel.name} (${evalLevel.score}分)`); logDebug(`批量评价按钮状态: ${btnAuto ? btnAuto.innerText : '未找到'}`); const scoreElement = document.querySelector('.xspjSum'); if (scoreElement) { logDebug(`评价总分: ${scoreElement.textContent || scoreElement.innerText}`); } const buttons = btnContainer.querySelectorAll('button'); const enabledButtons = Array.from(buttons).filter(btn => !btn.disabled).map(btn => btn.innerText); logDebug(`可用按钮: ${enabledButtons.join(', ')}`); const grid = $('#tempGrid'); if (grid.length > 0) { const currentRowId = grid.jqGrid('getGridParam', 'selrow'); const allRowIds = grid.jqGrid('getDataIDs'); logDebug(`jqGrid - 当前选中: ${currentRowId}, 总课程数: ${allRowIds.length}`); if (currentRowId) { const currentRowData = grid.jqGrid('getRowData', currentRowId); logDebug(`当前课程: ${currentRowData.jxbmc} - ${currentRowData.jzgmc} (${currentRowData.tjztmc})`); } } const courseRows = document.querySelectorAll('#tempGrid tr[role="row"]'); logDebug(`找到 ${courseRows.length - 1} 个课程行`); if (grid.length > 0) { const allRowIds = grid.jqGrid('getDataIDs'); const unsubmittedCount = allRowIds.filter(rowId => { const rowData = grid.jqGrid('getRowData', rowId); return rowData.tjztmc !== '提交'; }).length; const submittedCount = allRowIds.length - unsubmittedCount; logDebug(`课程状态统计: 已提交 ${submittedCount} 个, 未提交 ${unsubmittedCount} 个`); } if (doc.length > 0) { logDebug('--- 单选按钮状态 ---'); for (let i = 0; i < Math.min(10, doc.length); i++) { const btn = doc[i]; const label = btn.parentElement.textContent.trim(); logDebug(`按钮${i}: ${label} - 选中: ${btn.checked}`); } } else { logDebug('--- 评价项状态 (已提交) ---'); const evaluationRows = document.querySelectorAll('.tr-xspj'); for (let i = 0; i < Math.min(5, evaluationRows.length); i++) { const row = evaluationRows[i]; const title = row.querySelector('td:first-child')?.textContent?.trim(); const value = row.querySelector('td:nth-child(2)')?.textContent?.trim(); if (title && value) { logDebug(`${title.replace('*', '').substring(0, 20)}... = ${value}`); } } } }); } } setTimeout(() => { const notification = document.createElement('div'); applyStyles(notification, CSS_STYLES.notification.replace('top: 20px', 'top: 60px').replace('linear-gradient(135deg, #667eea 0%, #764ba2 100%)', '#28a745')); notification.innerText = '🎯 评价脚本已就绪!'; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); const doc = document.getElementsByClassName("radio-pjf"); const requiredItems = document.querySelectorAll('.tr-xspj[data-sfbt="1"]'); logSuccess('🎯 脚本加载完成'); logDebug(`📊 检测到 ${requiredItems.length} 个评价项,${doc.length} 个选项按钮`); }, 1000); })();