// ==UserScript== // @name 强智教务系统自动评教(通用版) // @namespace https://scriptcat.org/ // @version 1.0 // @description 适配多所学校强智教务系统的自动评教脚本,支持一键智能评教。 // @author 小哲(Gemini-3-pro辅助开发) // @tag 自动评教 强智教务 // @match *://*/jsxsd/* // @match *://*/*jsxsd/* // @match http://192.168.100.7/* // @icon https://img.icons8.com/fluency/64/maintenance.png // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-end // ==/UserScript== (function() { 'use strict'; // --- 0. 样式定义 --- GM_addStyle(` @keyframes qzt-pulse { 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); transform: scale(1); } 70% { box-shadow: 0 0 0 15px rgba(59, 130, 246, 0); transform: scale(1.05); } 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); transform: scale(1); } } .qzt-highlight-row { background-color: #eef2ff !important; border-left: 4px solid #3b82f6 !important; } .qzt-submit-glow { animation: qzt-pulse 2s infinite !important; outline: 3px solid #3b82f6 !important; font-weight: bold !important; } .qzt-btn-active { transform: scale(0.95) !important; } .qzt-list-unfinished { background-color: #fef2f2 !important; border-left: 4px solid #ef4444 !important; } .qzt-list-unfinished td { color: #b91c1c !important; } .qzt-dashboard-panel { position: fixed; bottom: 40px; right: 30px; z-index: 9998; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(8px); padding: 16px; border-radius: 12px; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); border: 1px solid #f1f5f9; min-width: 180px; transition: opacity 0.3s ease; cursor: move; user-select: none; font-family: system-ui, sans-serif; } .qzt-stat-title { font-weight: 700; color: #1e293b; border-bottom: 1px solid #e2e8f0; padding-bottom: 8px; margin-bottom: 10px; font-size: 14px; display: flex; align-items: center; gap: 6px; } .qzt-stat-row { display: flex; justify-content: space-between; margin: 6px 0; font-size: 13px; color: #475569; } .qzt-stat-val { font-weight: 600; font-family: monospace; font-size: 14px; } .qzt-tag-ok { color: #10b981; } .qzt-tag-no { color: #ef4444; } .qzt-btn-mini { width: 100%; margin-top: 8px; padding: 4px 0; background: #e2e8f0; border: none; border-radius: 6px; cursor: pointer; color: #475569; font-size: 12px; transition: all 0.2s; } .qzt-btn-mini:hover { background: #3b82f6; color: white; } .qzt-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; justify-content: center; align-items: center; backdrop-filter: blur(4px); } .qzt-modal { background: white; padding: 24px; border-radius: 16px; width: 400px; max-width: 90%; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25); animation: qzt-fadein 0.3s ease-out; } @keyframes qzt-fadein { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .qzt-setting-row { display: flex; justify-content: space-between; align-items: center; margin: 12px 0; } .qzt-switch { position: relative; display: inline-block; width: 44px; height: 24px; } .qzt-switch input { opacity: 0; width: 0; height: 0; } .qzt-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #cbd5e1; transition: .4s; border-radius: 34px; } .qzt-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } input:checked + .qzt-slider { background-color: #3b82f6; } input:checked + .qzt-slider:before { transform: translateX(20px); } .qzt-btn-base { border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-weight: 500; transition: opacity 0.2s; } .qzt-btn-save { background: #3b82f6; color: white; } .qzt-btn-cancel { background: #f1f5f9; color: #64748b; margin-right: 8px; } .qzt-select { padding: 4px 8px; border-radius: 6px; border: 1px solid #cbd5e1; outline: none; color: #475569; } .qzt-textarea { width: 100%; height: 80px; margin-top: 8px; padding: 8px; border: 1px solid #cbd5e1; border-radius: 8px; resize: vertical; font-size: 12px; font-family: inherit; } .qzt-disclaimer-text { font-size: 13px; color: #475569; line-height: 1.6; margin: 16px 0; background: #f8fafc; padding: 12px; border-radius: 8px; border: 1px dashed #cbd5e1; } .qzt-disclaimer-title { display: flex; align-items: center; gap: 8px; font-size: 18px; font-weight: bold; color: #0f172a; margin-bottom: 8px; } `); // --- 配置常量 --- const KEY_AGREE = 'qzt_agree_v3_7'; const KEY_POS_BTN = 'qzt_pos_btn_v3'; const KEY_POS_PANEL = 'qzt_pos_panel_v3'; const KEY_SETTINGS = 'qzt_settings_v3'; const DEFAULT_SETTINGS = { scoreMode: 'smart', autoSubmit: false, overwrite: true, customComments: '' }; const defaultComments = { A: ["老师授课认真,", "教学内容丰富,", "课程设计合理,", "老师治学严谨,", "授课方式新颖,", "备课非常充分,"], B: ["重点突出,条理清晰,", "课堂气氛活跃,", "理论联系实际,", "讲解深入浅出,", "善于引导学生思考,", "案例详实生动,"], C: ["我们收获很大。", "老师很有耐心。", "是一门很好的课。", "教学效果优良。", "希望能继续保持。", "对学生非常负责。"] }; let batchPageHintShown = false; function generateComment(customText) { if (customText && customText.trim().length > 0) { const lines = customText.split('\n').filter(l => l.trim().length > 1); if (lines.length > 0) return lines[Math.floor(Math.random() * lines.length)]; } const getRand = (arr) => arr[Math.floor(Math.random() * arr.length)]; return getRand(defaultComments.A) + getRand(defaultComments.B) + getRand(defaultComments.C); } const SafeStorage = { get: (key, def) => { try { return JSON.parse(GM_getValue(key)) || def; } catch (e) { return GM_getValue(key, def); } }, set: (key, val) => GM_setValue(key, JSON.stringify(val)) }; // 升级版 showToast,支持 position 参数 function showToast(message, duration = 4000, position = 'top') { const id = 'qzt-toast-' + position; // 根据位置生成不同的ID,防止互相覆盖 const oldToast = document.getElementById(id); if (oldToast) oldToast.remove(); const toast = document.createElement('div'); toast.id = id; toast.innerHTML = message; const styles = { position: 'fixed', left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(15, 23, 42, 0.95)', color: '#fff', padding: '12px 24px', borderRadius: '50px', zIndex: '2147483649', fontSize: '14px', boxShadow: '0 10px 25px rgba(0,0,0,0.2)', pointerEvents: 'none', textAlign: 'center', backdropFilter: 'blur(4px)' }; if (position === 'top') { styles.top = '30px'; } else { styles.bottom = '100px'; // 底部显示,避开右下角的面板 } Object.assign(toast.style, styles); document.body.appendChild(toast); setTimeout(() => toast.remove(), duration); } function enableDrag(element, storageKey, onClickCallback = null) { let isDragging = false, startX, startY, initialLeft, initialTop; element.onmousedown = (e) => { if(e.button !== 0) return; isDragging = false; startX = e.clientX; startY = e.clientY; const rect = element.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; element.style.bottom = 'auto'; element.style.right = 'auto'; element.style.left = initialLeft + 'px'; element.style.top = initialTop + 'px'; const onMove = (em) => { if(Math.abs(em.clientX - startX) > 3 || Math.abs(em.clientY - startY) > 3) { isDragging = true; element.style.left = (initialLeft + em.clientX - startX) + 'px'; element.style.top = (initialTop + em.clientY - startY) + 'px'; } }; const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); if(isDragging) SafeStorage.set(storageKey, { top: element.style.top, left: element.style.left }); else if(typeof onClickCallback === 'function') onClickCallback(); }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }; } // --- 核心算分 --- function extractScoreFromText(text) { if (!text) return null; let match = text.match(/[((]\s*(\d+(?:\.\d+)?)\s*(?:分)?[))]/); if (match) return parseFloat(match[1]); match = text.match(/(\d+(?:\.\d+)?)\s*分/); if (match) return parseFloat(match[1]); match = text.match(/(?:分值|权重|分数|标准分)[::]?\s*(\d+(?:\.\d+)?)/); if (match) return parseFloat(match[1]); return null; } function getSystemTotalScore() { const snapshot = document.evaluate('//*[contains(text(), "选项总分")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); if (snapshot.snapshotLength > 0) { for (let i = 0; i < snapshot.snapshotLength; i++) { const node = snapshot.snapshotItem(i); const text = node.innerText || node.textContent; const match = text.match(/选项总分[::]?\s*(\d+(?:\.\d+)?)/); if (match) return parseFloat(match[1]); } } return null; } function calculateScore(targetRadio, groupRadios, targetIndex) { let container = targetRadio.parentElement; let text = ""; if (targetRadio.nextSibling && targetRadio.nextSibling.nodeType === 3) text += targetRadio.nextSibling.textContent; if (container) text += container.innerText; let score = extractScoreFromText(text); if (score !== null) return score; let val = parseFloat(targetRadio.value); if (!isNaN(val) && val >= 0 && val <= 100) return val; let row = container; while(row && row.tagName !== 'TR') row = row.parentElement; if (row) { const rowText = row.innerText; const maxScore = extractScoreFromText(rowText); if (maxScore !== null) { const factors = [1.0, 0.85, 0.7, 0.5, 0.3]; let factor = factors[targetIndex] || 0.5; if (groupRadios.length === 2) factor = [1.0, 0.6][targetIndex]; return parseFloat((maxScore * factor).toFixed(1)); } } return 0; } // --- 业务逻辑 --- function runEvaluation() { const settings = { ...DEFAULT_SETTINGS, ...SafeStorage.get(KEY_SETTINGS, {}) }; document.querySelectorAll('.qzt-highlight-row').forEach(el => el.classList.remove('qzt-highlight-row')); const radios = document.querySelectorAll('input[type="radio"]'); const groups = {}; radios.forEach(r => { if (!groups[r.name]) groups[r.name] = []; groups[r.name].push(r); }); const groupNames = Object.keys(groups); let count = 0, totalCalculatedScore = 0; let randomTargetIndex = settings.scoreMode === 'smart' && groupNames.length > 3 ? Math.floor(Math.random() * groupNames.length) : -1; groupNames.forEach((name, index) => { const list = groups[name]; if (list.length === 0) return; let targetIndex = 0; if (settings.scoreMode === 'smart') { if (index === randomTargetIndex && list.length > 1) targetIndex = 1; } else if (settings.scoreMode === 'mixed') { if (Math.random() > 0.7 && list.length > 1) targetIndex = 1; } const target = list[targetIndex]; if (target) { target.click(); target.checked = true; const itemScore = calculateScore(target, list, targetIndex); count++; totalCalculatedScore += itemScore; if (targetIndex > 0) { let p = target.parentElement; while(p && p.tagName !== 'TR') p = p.parentElement; if(p) p.classList.add('qzt-highlight-row'); } } }); document.querySelectorAll('select').forEach(s => { if(s.options.length > 1) { s.selectedIndex = 1; for(let i=0; i { if (!t.value.trim() || settings.overwrite) { t.value = generateComment(settings.customComments); t.dispatchEvent(new Event('input')); t.dispatchEvent(new Event('change')); } }); if (count > 0) { let modeName = settings.scoreMode === 'full' ? '全满分' : (settings.scoreMode === 'mixed' ? '随机参半' : '智能扣分'); totalCalculatedScore = Math.round(totalCalculatedScore * 10) / 10; setTimeout(() => { const systemScore = getSystemTotalScore(); let scoreDisplay = ""; if (systemScore !== null) { scoreDisplay = `${systemScore} (系统)`; } else { scoreDisplay = `${totalCalculatedScore} (脚本估算)`; } let msg = `✅ 已填充 (${modeName})
预计得分: ${scoreDisplay}`; showToast(msg); const submitBtn = document.querySelector('#btn_xspj_bc') || Array.from(document.querySelectorAll('button, input[type="button"]')).find(b => b.value?.includes('提交') || b.innerText?.includes('提交')); if (submitBtn) { submitBtn.scrollIntoView({ behavior: 'smooth', block: 'center' }); submitBtn.classList.add('qzt-submit-glow'); if (settings.autoSubmit) { showToast("🚀 2秒后自动提交..."); setTimeout(() => submitBtn.click(), 2000); } } }, 100); } } // --- 页面识别与处理 --- function processListPage() { const tables = document.querySelectorAll('table'); let targetTable = null, statusColIndex = -1; for (const table of tables) { const headers = Array.from(table.querySelectorAll('th, tr:first-child td')); headers.forEach((cell, index) => { if (cell.innerText.includes('是否提交') || cell.innerText.includes('提交状态')) { statusColIndex = index; targetTable = table; } }); if (targetTable) break; } if (!targetTable || statusColIndex === -1) return; let total = 0, finished = 0, unfinished = 0, firstUnfinishedLink = null; targetTable.querySelectorAll('tr').forEach(row => { const cells = row.querySelectorAll('td'); if (cells.length <= statusColIndex) return; const statusText = cells[statusColIndex].innerText.trim(); if (statusText.includes('是否提交')) return; total++; if (statusText.match(/是|yes|已提交/i)) { finished++; row.classList.remove('qzt-list-unfinished'); } else { unfinished++; row.classList.add('qzt-list-unfinished'); if (!firstUnfinishedLink) firstUnfinishedLink = row.querySelector('a'); } }); renderDashboard(total, finished, unfinished, firstUnfinishedLink); } function processBatchPage() { if (batchPageHintShown) return; const links = Array.from(document.querySelectorAll('a')); const hasEntryLink = links.some(a => a.innerText.includes('进入评价')); const hasStatus = document.body.innerText.includes('是否提交'); if (hasEntryLink && !hasStatus) { showToast("👇 点击【进入评价】才能对评价进行操作哦", 5000); // 默认顶部 batchPageHintShown = true; } } function renderDashboard(total, done, todo, nextLink) { let panel = document.getElementById('qzt-stat-panel'); if (!panel) { panel = document.createElement('div'); panel.id = 'qzt-stat-panel'; panel.className = 'qzt-dashboard-panel'; const savedPos = SafeStorage.get(KEY_POS_PANEL, null); if (savedPos) { panel.style.top = savedPos.top; panel.style.left = savedPos.left; panel.style.bottom = 'auto'; panel.style.right = 'auto'; } document.body.appendChild(panel); enableDrag(panel, KEY_POS_PANEL); } const html = `
📊 评教进度 (可拖拽)
总课程: ${total}
已提交: ${done}
未提交: ${todo}
${todo > 0 && nextLink ? `` : `
🎉 全部完成
`} `; if (panel.innerHTML !== html) { panel.innerHTML = html; const btn = document.getElementById('qzt-btn-jump'); if (btn && nextLink) btn.onclick = () => { showToast("正在打开课程..."); nextLink.click(); }; } } function showSettings() { if(document.querySelector('.qzt-modal-overlay')) return; const settings = { ...DEFAULT_SETTINGS, ...SafeStorage.get(KEY_SETTINGS, {}) }; const overlay = document.createElement('div'); overlay.className = 'qzt-modal-overlay'; overlay.innerHTML = `

🛠️ 评教助手设置

📊 评分模式
⚡ 填充后自动提交
📝 自定义评语库 (每行一条)
`; document.body.appendChild(overlay); const close = () => overlay.remove(); overlay.querySelector('.qzt-btn-cancel').onclick = close; overlay.querySelector('.qzt-btn-save').onclick = () => { SafeStorage.set(KEY_SETTINGS, { scoreMode: document.getElementById('set-mode').value, autoSubmit: document.getElementById('set-auto').checked, overwrite: true, customComments: document.getElementById('set-comments').value }); showToast("✅ 设置已保存"); close(); }; overlay.onclick = (e) => { if(e.target === overlay) close(); }; } // --- 免责声明功能 --- function showDisclaimer() { if(document.querySelector('.qzt-modal-overlay')) return; const overlay = document.createElement('div'); overlay.className = 'qzt-modal-overlay qzt-disclaimer-modal'; overlay.innerHTML = `
⚠️ 免责声明

脚本作者:小哲(Gemini-3-pro辅助开发)

1. 本脚本仅供学习和技术交流使用,严禁用于任何商业或非法用途。

2. 脚本提供的自动化功能仅为辅助工具,请在提交前务必核对评价内容及分数

3. 使用本脚本即代表您愿意自行承担因使用而产生的所有后果。作者不对任何损失负责。

4. 点击下方“我同意并继续”即表示您已阅读并接受本声明的所有条款。

`; document.body.appendChild(overlay); document.getElementById('btn-disagree').onclick = () => { overlay.innerHTML = '
🚫
您拒绝了免责声明,脚本已停止运行。
请刷新页面重试。
'; }; document.getElementById('btn-agree').onclick = () => { SafeStorage.set(KEY_AGREE, true); // 这里指定 'bottom',将欢迎提示显示在底部 showToast("✅ 欢迎使用,请理性评教", 4000, 'bottom'); overlay.remove(); init(); }; } function createButton() { if (document.getElementById('qzt-entry-btn')) return; const btn = document.createElement('div'); btn.id = 'qzt-entry-btn'; btn.innerHTML = '⚡ 一键评教'; btn.title = "左键:自动填充 | 右键:设置 | 拖拽移动"; const pos = SafeStorage.get(KEY_POS_BTN, { top: '80%', left: '85%' }); Object.assign(btn.style, { position: 'fixed', zIndex: '9999', padding: '10px 18px', background: 'linear-gradient(135deg, #3b82f6, #2563eb)', color: '#fff', borderRadius: '50px', cursor: 'pointer', boxShadow: '0 4px 12px rgba(37,99,235,0.4)', fontWeight: '600', fontSize: '14px', top: pos.top, left: pos.left }); enableDrag(btn, KEY_POS_BTN, () => { btn.classList.add('qzt-btn-active'); setTimeout(() => btn.classList.remove('qzt-btn-active'), 150); runEvaluation(); }); btn.oncontextmenu = (e) => { e.preventDefault(); showSettings(); }; document.body.appendChild(btn); } function init() { const hasRadios = document.querySelectorAll('input[type="radio"]').length > 0; const isListPage = !!document.querySelector('table') && (document.body.innerText.includes('是否提交') || document.body.innerText.includes('提交状态')); const isBatchPage = Array.from(document.querySelectorAll('a')).some(a => a.innerText.includes('进入评价')); if (!hasRadios && !isListPage && !isBatchPage) { return; } const hasAgreed = SafeStorage.get(KEY_AGREE, false); if (!hasAgreed) { if (!document.querySelector('.qzt-disclaimer-modal')) { showDisclaimer(); } return; } if (hasRadios) { createButton(); } else { if (isListPage) processListPage(); if (isBatchPage) processBatchPage(); } } setTimeout(() => { init(); setInterval(init, 1500); }, 1000); GM_registerMenuCommand("脚本设置", showSettings); GM_registerMenuCommand("查看免责声明", () => { SafeStorage.set(KEY_AGREE, false); init(); }); })();