// ==UserScript== // @name 学习通随堂练习与选人活动自动化增强 // @namespace https://scriptcat.org/ // @version 1.4.2 // @description 自动填充随堂练习设置(时长3天/客观题2分/简答题10分/督促120分钟/存入题库),以及在选人活动(selectPerson)与随堂练习统计页(quizStatisticsPage)支持勾选部分学生进行批量打分 // @author Antigravity // @match *://mobilelearn.chaoxing.com/page/quiz/addOrUpdateQuiz* // @match *://mobilelearn.chaoxing.com/page/selectperson/selectPerson* // @match *://mobilelearn.chaoxing.com/page/quiz/quizStatisticsPage* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @run-at document-end // ==/UserScript== (function () { 'use strict'; const currentPath = window.location.pathname; // ========================================================================= // 基础通用组件:气泡提示 (Toast) // ========================================================================= function showToast(message, type = 'success') { let toastContainer = document.getElementById('cx-quiz-toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'cx-quiz-toast-container'; document.body.appendChild(toastContainer); } const toast = document.createElement('div'); toast.className = `cx-quiz-toast cx-quiz-toast-${type}`; toast.innerHTML = `${message}`; toastContainer.appendChild(toast); setTimeout(() => toast.classList.add('cx-quiz-toast-show'), 10); setTimeout(() => { toast.classList.remove('cx-quiz-toast-show'); setTimeout(() => toast.remove(), 400); }, 4500); } // ========================================================================= // 全局通用样式注入 // ========================================================================= function injectGlobalStyles() { const css = ` /* 全局 Toast */ #cx-quiz-toast-container { position: fixed; top: 24px; left: 50%; transform: translateX(-50%); z-index: 999999; display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none; } .cx-quiz-toast { padding: 10px 18px; border-radius: 8px; font-size: 13px; line-height: 1.5; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); opacity: 0; transform: translateY(-20px); transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); color: #fff; max-width: 480px; text-align: center; pointer-events: auto; } .cx-quiz-toast-show { opacity: 1; transform: translateY(0); } .cx-quiz-toast-success { background: rgba(39, 174, 96, 0.95); } .cx-quiz-toast-info { background: rgba(24, 144, 255, 0.95); } .cx-quiz-toast-error { background: rgba(245, 34, 45, 0.95); } /* 批量评分工具栏通用样式 */ .cx-batch-toolbar { display: inline-flex; align-items: center; gap: 8px; margin-left: 12px; padding: 4px 10px; background: #f0f7ff; border: 1px solid #bae7ff; border-radius: 6px; font-size: 13px; color: #595959; vertical-align: middle; user-select: none; } .cx-batch-btn { padding: 2px 8px; border-radius: 4px; border: 1px solid #d9d9d9; background: #ffffff; color: #262626; font-size: 12px; cursor: pointer; transition: all 0.2s; } .cx-batch-btn:hover { border-color: #1890ff; color: #1890ff; } .cx-batch-btn-primary { background: #1890ff; border-color: #1890ff; color: #ffffff; font-weight: 500; } .cx-batch-btn-primary:hover { background: #40a9ff; border-color: #40a9ff; color: #ffffff; } .cx-count-badge { font-weight: 600; color: #1890ff; } /* 学生勾选框通用样式 */ .cx-student-check-wrap { position: absolute; z-index: 10; display: inline-flex; align-items: center; justify-content: center; user-select: none; } .cx-student-check-input { width: 18px !important; height: 18px !important; cursor: pointer !important; accent-color: #1890ff !important; display: inline-block !important; visibility: visible !important; opacity: 1 !important; margin: 0 !important; } /* 自定义现代滑动开关组件 (解决宿主页面原生 checkbox 被样式重置隐藏的问题) */ .cx-switch { position: relative; display: inline-block !important; width: 38px !important; height: 20px !important; vertical-align: middle; cursor: pointer; user-select: none; margin: 0 !important; flex-shrink: 0; } .cx-switch input[type="checkbox"] { opacity: 0 !important; width: 0 !important; height: 0 !important; position: absolute !important; margin: 0 !important; pointer-events: none; } .cx-switch .cx-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #d9d9d9; transition: background-color 0.2s, transform 0.2s; border-radius: 20px; box-sizing: border-box; } .cx-switch .cx-slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: #ffffff; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 50%; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); } .cx-switch input:checked + .cx-slider { background-color: #1890ff; } .cx-switch input:checked + .cx-slider:before { transform: translateX(18px); } /* 预设悬浮按钮:仅在创建随堂练习页出现,在打分界面坚决不出现 */ #cx-quiz-fab-btn { display: none !important; } body.cx-is-quiz-create #cx-quiz-fab-btn { display: flex !important; } /* 选人页面特定高亮与定位 */ .person-list li { position: relative !important; cursor: pointer; } .person-list li .cx-student-check-wrap { top: 6px; left: 6px; } .person-list li.cx-card-selected .p-info { outline: 2px solid #1890ff !important; background: rgba(24, 144, 255, 0.08) !important; border-radius: 8px !important; } /* 随堂练习题目卡片特定高亮与定位 */ .gap-people-box { position: relative !important; padding-left: 42px !important; /* 给复选框预留独立左侧列,避免遮挡 44px 头像 */ cursor: pointer; transition: background-color 0.2s, border-color 0.2s; } .gap-people-box .cx-student-check-wrap { top: 24px; left: 12px; } .gap-people-box.cx-box-selected { border-color: #1890ff !important; background: rgba(24, 144, 255, 0.04) !important; box-shadow: 0 0 0 1px #1890ff !important; } /* 快速批量打分弹窗 */ .cx-modal-mask { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.45); z-index: 999998; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity 0.2s, visibility 0.2s; backdrop-filter: blur(2px); } .cx-modal-mask.cx-show { opacity: 1; visibility: visible; } .cx-score-modal { background: #ffffff; width: 440px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } .cx-score-header { padding: 14px 18px; background: #fafafa; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; justify-content: space-between; } .cx-score-title { font-size: 15px; font-weight: 600; color: #262626; } .cx-score-close { font-size: 20px; color: #8c8c8c; cursor: pointer; } .cx-score-close:hover { color: #ff4d4f; } .cx-score-body { padding: 20px; } .cx-score-subtitle { font-size: 13px; color: #595959; margin-bottom: 14px; } .cx-quick-scores { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-bottom: 16px; } .cx-score-pill { padding: 7px 0; border-radius: 6px; border: 1px solid #d9d9d9; background: #ffffff; color: #1890ff; font-size: 14px; font-weight: 600; text-align: center; cursor: pointer; transition: all 0.2s; } .cx-score-pill:hover { border-color: #1890ff; background: #e6f7ff; } .cx-score-pill-neg { color: #f5222d; } .cx-score-pill-neg:hover { border-color: #f5222d; background: #fff1f0; } .cx-custom-row { display: flex; gap: 10px; align-items: center; } .cx-custom-ipt { flex: 1; height: 34px; padding: 4px 10px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; outline: none; } .cx-custom-ipt:focus { border-color: #40a9ff; box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); } `; GM_addStyle(css); } // ========================================================================= // 模块 1:选人活动页面增强 (selectPerson) - 支持勾选部分学生批量打分 // ========================================================================= function initSelectPersonModule() { console.log('[选人活动-增强] 正在初始化选人页面批量评分增强...'); document.body.classList.remove('cx-is-quiz-create'); document.getElementById('cx-quiz-fab-btn')?.remove(); document.getElementById('cx-quiz-modal-mask')?.remove(); const pageCode = function () { const TAG = '[选人评分-主世界]'; function getQueryParams() { const params = new URLSearchParams(window.location.search); return { activeId: params.get('activeId') || (window.activeId ? String(window.activeId) : ''), classId: params.get('classId') || (window.classId ? String(window.classId) : ''), courseId: params.get('courseId') || (window.courseId ? String(window.courseId) : '') }; } // 挂载顶部工具栏 function ensureToolbar() { const container = document.querySelector('.t-tip'); if (!container || document.getElementById('cx-sp-toolbar')) return; const toolbar = document.createElement('div'); toolbar.id = 'cx-sp-toolbar'; toolbar.className = 'cx-batch-toolbar'; toolbar.innerHTML = ` 已勾选 0 人 `; container.appendChild(toolbar); toolbar.querySelector('#cx-sp-select-all').addEventListener('click', () => { document.querySelectorAll('.cx-sp-chk').forEach(chk => { chk.checked = true; chk.closest('li')?.classList.add('cx-card-selected'); }); updateCount(); }); toolbar.querySelector('#cx-sp-select-invert').addEventListener('click', () => { document.querySelectorAll('.cx-sp-chk').forEach(chk => { chk.checked = !chk.checked; chk.closest('li')?.classList.toggle('cx-card-selected', chk.checked); }); updateCount(); }); toolbar.querySelector('#cx-sp-select-clear').addEventListener('click', () => { document.querySelectorAll('.cx-sp-chk').forEach(chk => { chk.checked = false; chk.closest('li')?.classList.remove('cx-card-selected'); }); updateCount(); }); toolbar.querySelector('#cx-sp-batch-grade').addEventListener('click', () => { const uids = getSelectedUids(); if (uids.length === 0) { alert('请先勾选需要打分的学生!'); return; } openScoreModal(uids); }); } function updateCount() { const count = document.querySelectorAll('.cx-sp-chk:checked').length; const badge = document.getElementById('cx-sp-selected-count'); if (badge) badge.textContent = String(count); // 同步更新原生批量评分弹窗标题(若存在) const maskTitle = document.querySelector('#gradeMask .preson-info .image-box span'); if (maskTitle && count > 0) { maskTitle.textContent = `批量评分 (已选 ${count} 人)`; } } function getSelectedUids() { const checked = Array.from(document.querySelectorAll('.cx-sp-chk:checked')); return checked.map(chk => chk.getAttribute('data-uid')).filter(Boolean); } // 为每个学生卡片注入复选框 function ensureCheckboxes() { const items = document.querySelectorAll('.person-list li'); items.forEach(li => { const pInfo = li.querySelector('.p-info'); if (!pInfo) return; const suid = pInfo.getAttribute('suid'); if (!suid || li.querySelector('.cx-student-check-wrap')) return; const wrap = document.createElement('span'); wrap.className = 'cx-student-check-wrap'; wrap.title = '勾选学生'; wrap.innerHTML = ``; li.insertBefore(wrap, li.firstChild); }); } // 打开快速打分弹窗 function openScoreModal(uids) { let modalMask = document.getElementById('cx-sp-modal-mask'); if (!modalMask) { modalMask = document.createElement('div'); modalMask.id = 'cx-sp-modal-mask'; modalMask.className = 'cx-modal-mask'; modalMask.innerHTML = `
🎯 批量为选中学生打分 ×
已勾选 0 名学生,点击快捷分值或输入分值:
+1分
+2分
+3分
+4分
+5分
+10分
+0分
-1分
-2分
-3分
`; document.body.appendChild(modalMask); modalMask.querySelector('#cx-sp-modal-close').addEventListener('click', () => { modalMask.classList.remove('cx-show'); }); modalMask.addEventListener('click', (e) => { if (e.target === modalMask) modalMask.classList.remove('cx-show'); }); // 快捷分值点击 modalMask.querySelectorAll('.cx-score-pill').forEach(btn => { btn.addEventListener('click', () => { const val = parseFloat(btn.dataset.score); submitScores(window.__cxSpTargetUids || [], val); modalMask.classList.remove('cx-show'); }); }); // 自定义输入确认 modalMask.querySelector('#cx-sp-confirm-btn').addEventListener('click', () => { const ipt = modalMask.querySelector('#cx-sp-custom-score'); const val = parseFloat(ipt.value); if (isNaN(val)) { alert('请输入有效的分值!'); return; } submitScores(window.__cxSpTargetUids || [], val); modalMask.classList.remove('cx-show'); }); } window.__cxSpTargetUids = uids; modalMask.querySelector('#cx-sp-modal-count').textContent = String(uids.length); modalMask.classList.add('cx-show'); } // 核心执行打分 async function submitScores(uids, score) { if (!uids || uids.length === 0) return; const { activeId, classId, courseId } = getQueryParams(); console.log(TAG, `开始为 ${uids.length} 名勾选学生打分: ${score}`, uids); const widgetUrl = window._CP_widget_ || (window.ServiceDomain && window.ServiceDomain.widgetDomainWithProtocol) || ''; const apiUrl = `${widgetUrl}/widget/pick/pc/updateScore`; let successCount = 0; for (const suid of uids) { try { await new Promise((resolve) => { window.$.ajax({ url: apiUrl, type: 'GET', data: { uid: suid, activeId, classId, courseId, score }, dataType: 'json', success: function (res) { if (res && res.result === 1) successCount++; resolve(res); }, error: function () { resolve(null); } }); }); // 更新 Vue 响应式状态 if (window.vm && window.vm.userChoose) { for (let s of window.vm.userChoose) { if (String(s.uid) === String(suid)) { s.score = score; } } } // 更新界面卡片分值徽章 const pInfo = document.querySelector(`.p-info[suid="${suid}"]`); if (pInfo) { const badge = pInfo.querySelector('.sel-opt'); if (badge) { badge.textContent = `${score >= 0 ? '+' : ''}${score}`; badge.className = `sel-opt ${score >= 0 ? 'bg-blue' : 'bg-red'}`; } } } catch (e) { console.error(TAG, `学生 ${suid} 打分异常:`, e); } } alert(`🎉 批量评分完成!已为勾选的 ${uids.length} 名学生打分 ${score >= 0 ? '+' : ''}${score} 分`); // 打分完成后清空勾选 document.querySelectorAll('.cx-sp-chk').forEach(chk => { chk.checked = false; chk.closest('li')?.classList.remove('cx-card-selected'); }); updateCount(); } // 拦截原生批量评分弹窗的评分点击:若勾选了学生,则只给勾选的学生打分 document.addEventListener('click', function (e) { const target = e.target; const scoreItem = target.closest('#gradeMask .score-list .score-item'); const isBatchMode = document.getElementById('gradeMask')?.classList.contains('is-batch'); if (scoreItem && isBatchMode) { const uids = getSelectedUids(); if (uids.length > 0) { e.stopPropagation(); e.stopImmediatePropagation(); const num = parseFloat(scoreItem.textContent.replace('+', '')); document.getElementById('gradeMask').style.display = 'none'; submitScores(uids, num); } } }, true); // 全局事件委托:点击复选框或学生卡片均可独立勾选/取消勾选 document.addEventListener('click', function (e) { const li = e.target.closest('.person-list li'); if (!li) return; // 排除单人评分徽章,保留原生单人评分面板 if (e.target.closest('.sel-opt')) return; const chk = li.querySelector('.cx-sp-chk'); if (!chk) return; if (e.target === chk) { // 用户直接点击复选框,浏览器已完成勾选状态反转 e.stopPropagation(); } else { // 用户点击卡片其他区域,程序化切换勾选状态并阻止原生弹窗 e.stopPropagation(); chk.checked = !chk.checked; } li.classList.toggle('cx-card-selected', chk.checked); updateCount(); }); // 循环轮询与变动监听 setInterval(() => { ensureToolbar(); ensureCheckboxes(); }, 800); }; const script = document.createElement('script'); script.textContent = `(${pageCode.toString()})();`; (document.head || document.documentElement).appendChild(script); script.remove(); } // ========================================================================= // 模块 2:随堂练习统计页面增强 (quizStatisticsPage) - 支持勾选部分学生批量打分 // ========================================================================= function initQuizStatisticsModule() { console.log('[随堂练习统计-增强] 正在初始化随堂练习统计页面批量评分增强...'); document.body.classList.remove('cx-is-quiz-create'); document.getElementById('cx-quiz-fab-btn')?.remove(); document.getElementById('cx-quiz-modal-mask')?.remove(); const pageCode = function () { const TAG = '[随堂练习统计-主世界]'; function getQueryParams() { const params = new URLSearchParams(window.location.search); return { activeId: params.get('activeId') || (window.activeId ? String(window.activeId) : '') }; } // 挂载每个题目卡片上方的工具栏 function ensureQuizToolbars() { const questionItems = document.querySelectorAll('.cql-item'); questionItems.forEach((cql, qIdx) => { const setTipsLeft = cql.querySelector('.set-tips .set-tips-left'); if (!setTipsLeft || cql.querySelector('.cx-quiz-toolbar')) return; const toolbar = document.createElement('div'); toolbar.className = 'cx-batch-toolbar cx-quiz-toolbar'; toolbar.setAttribute('data-qidx', String(qIdx)); toolbar.innerHTML = ` 已勾选 0 人 `; setTipsLeft.appendChild(toolbar); toolbar.querySelector('.cx-btn-select-all').addEventListener('click', () => { cql.querySelectorAll('.cx-quiz-chk').forEach(chk => { chk.checked = true; chk.closest('.gap-people-box')?.classList.add('cx-box-selected'); }); updateQuestionCount(cql); }); toolbar.querySelector('.cx-btn-select-invert').addEventListener('click', () => { cql.querySelectorAll('.cx-quiz-chk').forEach(chk => { chk.checked = !chk.checked; chk.closest('.gap-people-box')?.classList.toggle('cx-box-selected', chk.checked); }); updateQuestionCount(cql); }); toolbar.querySelector('.cx-btn-select-clear').addEventListener('click', () => { cql.querySelectorAll('.cx-quiz-chk').forEach(chk => { chk.checked = false; chk.closest('.gap-people-box')?.classList.remove('cx-box-selected'); }); updateQuestionCount(cql); }); toolbar.querySelector('.cx-btn-batch-grade').addEventListener('click', () => { const uids = getQuestionSelectedUids(cql); if (uids.length === 0) { alert('请先勾选本题需要打分的学生!'); return; } openQuizScoreModal(cql, qIdx, uids); }); }); } function updateQuestionCount(cql) { const count = cql.querySelectorAll('.cx-quiz-chk:checked').length; const badge = cql.querySelector('.cx-quiz-count'); if (badge) badge.textContent = String(count); // 更新本题原生下拉框文案提示 const currentScoreSpan = cql.querySelector('.set-tips-left .score-down-content .current-score span'); if (currentScoreSpan && count > 0) { currentScoreSpan.textContent = `批量评分 (已选${count}人)`; } } function getQuestionSelectedUids(cql) { const checked = Array.from(cql.querySelectorAll('.cx-quiz-chk:checked')); return checked.map(chk => chk.getAttribute('data-stuid')).filter(Boolean); } // 为每个学生的回答框注入复选框 function ensureQuizCheckboxes() { const boxes = document.querySelectorAll('.gap-people-box'); boxes.forEach(box => { if (box.querySelector('.cx-student-check-wrap')) return; // 从头像链接解析 stuid const img = box.querySelector('.avatar-img'); let stuid = ''; if (img && img.src) { const m = img.src.match(/\/p\/(\d+)_/); if (m) stuid = m[1]; } if (!stuid && box.__vue__ && box.__vue__.mem) { stuid = String(box.__vue__.mem.stuid || ''); } if (!stuid) { // 兜底:从内部组件或姓名附近寻找 const nameEl = box.querySelector('h5'); if (nameEl) stuid = nameEl.textContent.trim(); } const wrap = document.createElement('span'); wrap.className = 'cx-student-check-wrap'; wrap.title = '勾选此学生'; wrap.innerHTML = ``; box.insertBefore(wrap, box.firstChild); }); } // 打开随堂练习批量打分弹窗 function openQuizScoreModal(cql, qIdx, uids) { let modalMask = document.getElementById('cx-quiz-modal-mask'); if (!modalMask) { modalMask = document.createElement('div'); modalMask.id = 'cx-quiz-modal-mask'; modalMask.className = 'cx-modal-mask'; modalMask.innerHTML = `
🎯 批量为本题选中学生打分 ×
本题已勾选 0 名学生,点击快捷分值或输入分值:
+1分
+2分
+3分
+4分
+5分
+10分
+0分
-1分
-2分
-3分
`; document.body.appendChild(modalMask); modalMask.querySelector('#cx-quiz-modal-close').addEventListener('click', () => { modalMask.classList.remove('cx-show'); }); modalMask.addEventListener('click', (e) => { if (e.target === modalMask) modalMask.classList.remove('cx-show'); }); modalMask.querySelectorAll('.cx-score-pill').forEach(btn => { btn.addEventListener('click', () => { const val = parseFloat(btn.dataset.score); const ctx = window.__cxQuizTargetContext; if (ctx) submitQuizScores(ctx.cql, ctx.qIdx, ctx.uids, val); modalMask.classList.remove('cx-show'); }); }); modalMask.querySelector('#cx-quiz-confirm-btn').addEventListener('click', () => { const ipt = modalMask.querySelector('#cx-quiz-custom-score'); const val = parseFloat(ipt.value); if (isNaN(val)) { alert('请输入有效的分值!'); return; } const ctx = window.__cxQuizTargetContext; if (ctx) submitQuizScores(ctx.cql, ctx.qIdx, ctx.uids, val); modalMask.classList.remove('cx-show'); }); } window.__cxQuizTargetContext = { cql, qIdx, uids }; modalMask.querySelector('#cx-quiz-modal-title').textContent = `🎯 批量为第 ${qIdx + 1} 题选中学生打分`; modalMask.querySelector('#cx-quiz-modal-count').textContent = String(uids.length); modalMask.classList.add('cx-show'); } // 核心执行随堂练习打分 async function submitQuizScores(cql, qIdx, uids, score) { if (!uids || uids.length === 0) return; const { activeId } = getQueryParams(); const vm = window.vm; const que = (vm && vm.questionList && vm.questionList[qIdx]) ? vm.questionList[qIdx] : null; const questionId = que ? que.id : (cql.getAttribute('data-question-id') || ''); console.log(TAG, `开始为第 ${qIdx + 1} 题(ID: ${questionId})选中的 ${uids.length} 名学生打分: ${score}`, uids); const widgetUrl = window._CP_widget_ || (window.ServiceDomain && window.ServiceDomain.widgetDomainWithProtocol) || ''; const apiUrl = `${widgetUrl}/v2/apis/quizScore/setScoreForAnswer`; let successCount = 0; for (const uid of uids) { try { await new Promise((resolve) => { window.$.ajax({ url: apiUrl, type: 'GET', data: { activeId, questionId, uid, score }, dataType: 'json', success: function (res) { if (res && res.result === 1) successCount++; resolve(res); }, error: function () { resolve(null); } }); }); // 更新 Vue 模型 if (que && que.memberList) { for (let m of que.memberList) { if (String(m.stuid) === String(uid)) { m.score = score; } } } // 更新 DOM 显示 const box = Array.from(cql.querySelectorAll('.gap-people-box')).find(b => { return b.querySelector(`[data-stuid="${uid}"]`); }); if (box) { const curSpan = box.querySelector('.score-down-content .current-score span'); if (curSpan) curSpan.textContent = `${score}分`; } } catch (e) { console.error(TAG, `第 ${qIdx + 1} 题学生 ${uid} 打分异常:`, e); } } alert(`🎉 批量评分完成!已为第 ${qIdx + 1} 题勾选的 ${uids.length} 名学生打分 ${score >= 0 ? '+' : ''}${score} 分`); // 尝试调用官方刷新接口 if (vm && typeof vm.getStudentQuestion === 'function') { try { vm.getStudentQuestion(null, qIdx, questionId, 0); } catch (e) { } } // 清空本题勾选 cql.querySelectorAll('.cx-quiz-chk').forEach(chk => { chk.checked = false; chk.closest('.gap-people-box')?.classList.remove('cx-box-selected'); }); updateQuestionCount(cql); } // 拦截题目头部原生的批量修改积分下拉框 document.addEventListener('click', function (e) { const li = e.target.closest('.set-tips-left .score-down-list li'); if (!li) return; const cql = li.closest('.cql-item'); if (!cql) return; const uids = getQuestionSelectedUids(cql); if (uids.length > 0) { // 勾选了部分学生:拦截全部打分,转为只对勾选学生打分 e.stopPropagation(); e.stopImmediatePropagation(); const numText = li.textContent.trim().replace('+', ''); const num = parseFloat(numText); const qIdx = parseInt(cql.querySelector('.cx-quiz-toolbar')?.getAttribute('data-qidx') || '0', 10); // 关闭下拉框 li.closest('.score-down-list').style.display = 'none'; submitQuizScores(cql, qIdx, uids, num); } }, true); // 全局事件委托:点击复选框或学生答题卡片均可独立勾选/取消勾选 document.addEventListener('click', function (e) { const box = e.target.closest('.gap-people-box'); if (!box) return; // 排除单人评分下拉、状态指示、反馈面板、大图预览及链接/按钮 if (e.target.closest('.score-down-content, .gap-status-indicator, .gap-feedback-panel, .attach-img-list, .html-content img, a, button')) { return; } const chk = box.querySelector('.cx-quiz-chk'); if (!chk) return; if (e.target === chk) { // 用户直接点击复选框,浏览器已完成勾选状态反转 e.stopPropagation(); } else { // 用户点击卡片空白区、头像、姓名或回答内容:程序化切换勾选 e.stopPropagation(); chk.checked = !chk.checked; } box.classList.toggle('cx-box-selected', chk.checked); const cql = box.closest('.cql-item'); if (cql) updateQuestionCount(cql); }); setInterval(() => { ensureQuizToolbars(); ensureQuizCheckboxes(); }, 800); }; const script = document.createElement('script'); script.textContent = `(${pageCode.toString()})();`; (document.head || document.documentElement).appendChild(script); script.remove(); } // ========================================================================= // 模块 3:添加/更新随堂练习页面 (addOrUpdateQuiz) - 保持原有自动预设功能 // ========================================================================= function initAddOrUpdateQuizModule() { console.log('[随堂练习预设] 启动随堂练习预设自动填充模块...'); document.body.classList.add('cx-is-quiz-create'); const DEFAULT_CONFIG = { objectiveScore: 2, shortAnswerScore: 10, durationDay: 3, durationHour: 0, durationMinute: 0, enableUrge: true, urgeBeforeMinutes: 120, saveToBank: true, bankFolderName: '随堂练习', autoApplyOnLoad: true }; function getConfig() { const saved = GM_getValue('chaoxing_quiz_preset', null); if (!saved) return { ...DEFAULT_CONFIG }; try { return Object.assign({}, DEFAULT_CONFIG, typeof saved === 'string' ? JSON.parse(saved) : saved); } catch (e) { return { ...DEFAULT_CONFIG }; } } function saveConfig(cfg) { GM_setValue('chaoxing_quiz_preset', JSON.stringify(cfg)); } function injectAndRunInPageWorld(cfg) { const pageCode = function (CONFIG) { const TAG = '[随堂练习-主世界]'; console.log(TAG, '启动主世界预设注入...', CONFIG); if (window.__cxIsApplying) { console.log(TAG, '已有预设注入任务在执行中,忽略重复触发'); return; } window.__cxIsApplying = true; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); function getPageVm() { if (window.vm) return window.vm; const app = document.getElementById('app'); if (app && app.__vue__) return app.__vue__; return null; } async function setDuration(day, hour, min) { const vm = getPageVm(); if (vm) { vm.timelong_day = String(day); vm.timelong_hour = String(hour); vm.timelong_minute = String(min); vm.handleEnd_temp = false; } const dayInp = document.getElementById('day'); const hourInp = document.getElementById('hour'); const minInp = document.getElementById('minute'); if (dayInp) { dayInp.value = String(day); dayInp.setAttribute('value', String(day)); dayInp.dispatchEvent(new Event('input', { bubbles: true })); dayInp.dispatchEvent(new Event('change', { bubbles: true })); } if (hourInp) { hourInp.value = String(hour); hourInp.setAttribute('value', String(hour)); hourInp.dispatchEvent(new Event('input', { bubbles: true })); hourInp.dispatchEvent(new Event('change', { bubbles: true })); } if (minInp) { minInp.value = String(min); minInp.setAttribute('value', String(min)); minInp.dispatchEvent(new Event('input', { bubbles: true })); minInp.dispatchEvent(new Event('change', { bubbles: true })); } if (typeof window.reInitTestSelect === 'function') { try { window.reInitTestSelect(day != 0 || hour != 0); } catch (e) { } } console.log(TAG, `活动时长已设置为: ${day}天 ${hour}小时 ${min}分钟`); } async function setUrge(enable, minutes) { const vm = getPageVm(); if (vm) { vm.isPrompt = !!enable; vm.promptBeforeTime = minutes; } const swCheckbox = document.getElementById('isPrompt_checkbox'); const hiddenPrompt = document.querySelector('input[name="isPrompt"]'); if (swCheckbox) { swCheckbox.checked = !!enable; if (enable) { swCheckbox.classList.add('checked'); } else { swCheckbox.classList.remove('checked'); } swCheckbox.dispatchEvent(new Event('change', { bubbles: true })); } if (hiddenPrompt) { hiddenPrompt.value = enable ? '1' : '0'; } const promptContent = document.getElementById('promptContent'); if (promptContent) { promptContent.style.display = enable ? 'block' : 'none'; } if (enable) { const promptInp = document.getElementById('Prompt-select') || document.querySelector('input[name="promptBeforeTime"]'); if (promptInp) { promptInp.value = String(minutes); promptInp.setAttribute('value', String(minutes)); promptInp.dispatchEvent(new Event('input', { bubbles: true })); promptInp.dispatchEvent(new Event('change', { bubbles: true })); promptInp.dispatchEvent(new Event('blur', { bubbles: true })); console.log(TAG, `督促未交学生已开启,提前提醒时间已设置为: ${minutes} 分钟`); } } } async function setBankFolder(saveToBank, folderName) { const vm = getPageVm(); if (vm) { vm.isSaveToReservoir = !!saveToBank; } const swBank = document.getElementById('isSaveToReservoir_checkbox'); const hiddenBank = document.querySelector('input[name="isSaveToReservoir"]'); if (swBank) { swBank.checked = !!saveToBank; if (saveToBank) { swBank.classList.add('checked'); } else { swBank.classList.remove('checked'); } swBank.dispatchEvent(new Event('change', { bubbles: true })); } if (hiddenBank) { hiddenBank.value = saveToBank ? '1' : '0'; } const bankContent = document.getElementById('isSaveToReservoirContent'); if (bankContent) { bankContent.style.display = saveToBank ? 'block' : 'none'; } if (!saveToBank) return; const currentText = document.getElementById('selDirectoryName')?.textContent?.trim() || ''; if (currentText.includes(folderName)) { console.log(TAG, '题库目标目录已是:', currentText); return; } const tkDirectoryBtn = document.getElementById('tkDirectory'); if (tkDirectoryBtn) { console.log(TAG, '打开题库目录选择弹窗...'); tkDirectoryBtn.click(); await sleep(800); await handleBankModal(folderName); } } async function handleBankModal(targetName) { const vm = getPageVm(); const findTargetNode = () => { const nodes = Array.from(document.querySelectorAll('.popMove_name')); return nodes.find(n => { const name = n.dataset.folderName || n.getAttribute('data-folder-name') || n.textContent?.trim(); return name === targetName; }); }; let target = findTargetNode(); if (!target) { console.log(TAG, `尚未发现【${targetName}】文件夹,先聚焦选中根目录并点击新建...`); const rootNode = document.querySelector('.popMove_name[data-folder-id="0"]') || document.querySelector('.popMove_name'); if (rootNode) { rootNode.click(); await sleep(300); } const newFolderBtn = document.querySelector('.popMove_newFolder'); if (newFolderBtn) { newFolderBtn.click(); await sleep(500); const renameInp = document.querySelector('.rename_input'); if (renameInp) { renameInp.value = targetName; renameInp.dispatchEvent(new Event('input', { bubbles: true })); renameInp.dispatchEvent(new Event('change', { bubbles: true })); await sleep(300); const renameYes = document.querySelector('.rename_yes'); if (renameYes) { renameYes.click(); console.log(TAG, `已请求创建文件夹【${targetName}】,等待目录树刷新...`); await sleep(1200); } } } target = findTargetNode(); } if (target) { console.log(TAG, `成功选中目标目录【${targetName}】`); target.click(); await sleep(300); if (vm && typeof vm.moveFolderOrQuestion === 'function') { vm.moveFolderOrQuestion(); } else { const confirmBtn = document.getElementById('confirm-move-btn'); if (confirmBtn) confirmBtn.click(); } await sleep(400); } const cancelBtn = document.querySelector('.popMoveDele'); if (cancelBtn) cancelBtn.click(); } function updateQuestionScores(objScore, saScore) { const vm = getPageVm(); if (vm && vm.latestTopicSet) { vm.latestTopicSet.latestSingleChoice = vm.latestTopicSet.latestSingleChoice || {}; vm.latestTopicSet.latestSingleChoice.score = objScore; vm.latestTopicSet.latestMultipleChoice = vm.latestTopicSet.latestMultipleChoice || {}; vm.latestTopicSet.latestMultipleChoice.score = objScore; vm.latestTopicSet.latestFillBlank = vm.latestTopicSet.latestFillBlank || {}; vm.latestTopicSet.latestFillBlank.score = objScore; vm.latestTopicSet.latestTrueOrFalse = vm.latestTopicSet.latestTrueOrFalse || {}; vm.latestTopicSet.latestTrueOrFalse.score = objScore; vm.latestTopicSet.latestShortAnswer = vm.latestTopicSet.latestShortAnswer || {}; vm.latestTopicSet.latestShortAnswer.score = saScore; } const cards = Array.from(document.querySelectorAll('.part-one-operate')); console.log(TAG, `扫描到 ${cards.length} 道题目卡片`); cards.forEach((card, idx) => { const typeInput = card.querySelector('input[name^="question_type_"]'); const typeVal = typeInput ? typeInput.value : ''; const selectOne = card.querySelector('input[id^="selectOne"]'); const typeText = selectOne ? selectOne.value : ''; const cardContent = card.textContent || ''; const isShortAnswer = typeVal === '4' || typeText === '简答题' || cardContent.includes('作答获得课程积分') || cardContent.includes('简答'); const targetScore = isShortAnswer ? saScore : objScore; const scoreInp = card.querySelector('.right-scores input, input[name^="item_score_"]'); if (scoreInp) { scoreInp.value = String(targetScore); scoreInp.setAttribute('value', String(targetScore)); scoreInp.dispatchEvent(new Event('input', { bubbles: true })); scoreInp.dispatchEvent(new Event('change', { bubbles: true })); if (window.$) { window.$(scoreInp).val(targetScore).attr('value', targetScore); } if (vm && vm.questionList && vm.questionList[idx]) { vm.questionList[idx].integral = targetScore; } try { if (typeof window.setQueScore === 'function') { window.setQueScore(scoreInp, idx); } } catch (e) { } console.log(TAG, `题目 ${idx + 1}【${isShortAnswer ? '简答题' : '客观题'}】积分成功设定为: ${targetScore} 分`); } }); } function hookOfficialAddQuestion(objScore, saScore) { if (typeof window.addQuestion === 'function' && !window.__cxAddQuestionHooked) { window.__cxAddQuestionHooked = true; const origAddQuestion = window.addQuestion; window.addQuestion = function (qtype) { const res = origAddQuestion.apply(this, arguments); setTimeout(() => { updateQuestionScores(objScore, saScore); }, 250); return res; }; console.log(TAG, '已成功挂钩官方 addQuestion 函数'); } const sortBox = document.querySelector('.sorttable-box'); if (sortBox && !window.__cxSortBoxHooked) { window.__cxSortBoxHooked = true; const obs = new MutationObserver((mutations) => { if (window.__cxIsApplying) return; const hasNewCard = mutations.some(m => Array.from(m.addedNodes).some(n => n.nodeType === 1 && (n.classList?.contains('part-one-operate') || n.querySelector?.('.part-one-operate')))); if (hasNewCard) { if (window.__cxScoreDebounceTimer) clearTimeout(window.__cxScoreDebounceTimer); window.__cxScoreDebounceTimer = setTimeout(() => { updateQuestionScores(objScore, saScore); }, 250); } }); obs.observe(sortBox, { childList: true }); } } (async () => { try { await setDuration(CONFIG.durationDay, CONFIG.durationHour, CONFIG.durationMinute); await setUrge(CONFIG.enableUrge, CONFIG.urgeBeforeMinutes); await setBankFolder(CONFIG.saveToBank, CONFIG.bankFolderName); updateQuestionScores(CONFIG.objectiveScore, CONFIG.shortAnswerScore); hookOfficialAddQuestion(CONFIG.objectiveScore, CONFIG.shortAnswerScore); console.log(TAG, '🎉 全流程预设填充顺利完成!'); } catch (err) { console.error(TAG, '执行过程异常:', err); } finally { window.__cxIsApplying = false; } })(); }; const script = document.createElement('script'); script.textContent = `(${pageCode.toString()})(${JSON.stringify(cfg)});`; (document.head || document.documentElement).appendChild(script); script.remove(); } function applyAllPresets(cfg = getConfig()) { showToast('🚀 正在执行随堂练习预设自动填充...', 'info'); injectAndRunInPageWorld(cfg); showToast( `🎉 随堂练习预设填充指令已发送!
` + `⏱️ 时长:${cfg.durationDay}天${cfg.durationHour}小时${cfg.durationMinute}分 | ` + `💯 客观题:${cfg.objectiveScore}分,简答题:${cfg.shortAnswerScore}分
` + `🔔 督促:${cfg.enableUrge ? `提前${cfg.urgeBeforeMinutes}分钟` : '已关闭'} | ` + `📁 题库:${cfg.saveToBank ? `根目录/${cfg.bankFolderName}` : '未启用'}`, 'success' ); } // 创建随堂练习设置弹窗与悬浮按钮 function createPresetUI() { // 严格防护:仅在创建/编辑随堂练习页面注入悬浮设置按钮,打分界面绝不出现 if (!window.location.pathname.includes('/page/quiz/addOrUpdateQuiz')) { return; } if (document.getElementById('cx-quiz-fab-btn')) return; const fab = document.createElement('div'); fab.id = 'cx-quiz-fab-btn'; fab.title = '随堂练习自动预设设置'; fab.style.cssText = ` position: fixed; right: 24px; bottom: 80px; z-index: 99999; width: 44px; height: 44px; border-radius: 50%; background: linear-gradient(135deg, #1890ff, #096dd9); color: #fff; box-shadow: 0 4px 12px rgba(24, 144, 255, 0.4); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; user-select: none; `; fab.innerHTML = ` `; document.body.appendChild(fab); const mask = document.createElement('div'); mask.id = 'cx-quiz-modal-mask'; mask.className = 'cx-modal-mask'; const cfg = getConfig(); mask.innerHTML = `
⚙️ 随堂练习自动预设设置 ×
题目积分预设
客观题: 分
主观题: 分
默认活动时长
天 小时 分钟
活动结束前: 分钟自动督促
题库路径: 根目录 /
进入随堂练习创建页时自动填入上述预设
`; document.body.appendChild(mask); const openModal = () => { const current = getConfig(); document.getElementById('cx-cfg-obj-score').value = current.objectiveScore; document.getElementById('cx-cfg-sa-score').value = current.shortAnswerScore; document.getElementById('cx-cfg-dur-day').value = current.durationDay; document.getElementById('cx-cfg-dur-hour').value = current.durationHour; document.getElementById('cx-cfg-dur-min').value = current.durationMinute; document.getElementById('cx-cfg-enable-urge').checked = current.enableUrge; document.getElementById('cx-cfg-urge-min').value = current.urgeBeforeMinutes; document.getElementById('cx-cfg-save-bank').checked = current.saveToBank; document.getElementById('cx-cfg-bank-folder').value = current.bankFolderName; document.getElementById('cx-cfg-auto-apply').checked = current.autoApplyOnLoad; mask.classList.add('cx-show'); }; const closeModal = () => mask.classList.remove('cx-show'); fab.addEventListener('click', openModal); document.getElementById('cx-modal-close-btn').addEventListener('click', closeModal); mask.addEventListener('click', (e) => { if (e.target === mask) closeModal(); }); const getFormData = () => ({ objectiveScore: parseInt(document.getElementById('cx-cfg-obj-score').value, 10) || 2, shortAnswerScore: parseInt(document.getElementById('cx-cfg-sa-score').value, 10) || 10, durationDay: parseInt(document.getElementById('cx-cfg-dur-day').value, 10) || 0, durationHour: parseInt(document.getElementById('cx-cfg-dur-hour').value, 10) || 0, durationMinute: parseInt(document.getElementById('cx-cfg-dur-min').value, 10) || 0, enableUrge: document.getElementById('cx-cfg-enable-urge').checked, urgeBeforeMinutes: parseInt(document.getElementById('cx-cfg-urge-min').value, 10) || 120, saveToBank: document.getElementById('cx-cfg-save-bank').checked, bankFolderName: document.getElementById('cx-cfg-bank-folder').value.trim() || '随堂练习', autoApplyOnLoad: document.getElementById('cx-cfg-auto-apply').checked }); document.getElementById('cx-btn-save').addEventListener('click', () => { const newCfg = getFormData(); saveConfig(newCfg); showToast('✅ 预设已成功保存!', 'success'); closeModal(); }); document.getElementById('cx-btn-apply-now').addEventListener('click', () => { const newCfg = getFormData(); saveConfig(newCfg); closeModal(); applyAllPresets(newCfg); }); document.getElementById('cx-btn-reset').addEventListener('click', () => { saveConfig(DEFAULT_CONFIG); openModal(); showToast('已重置为系统默认配置', 'info'); }); GM_registerMenuCommand('⚙️ 打开随堂练习预设设置', openModal); GM_registerMenuCommand('⚡ 立即执行预设填充', () => applyAllPresets(getConfig())); } createPresetUI(); const cfg = getConfig(); if (cfg.autoApplyOnLoad) { let retry = 0; const checkReady = setInterval(() => { retry++; const hasForm = !!document.getElementById('app') && (!!document.getElementById('day') || !!document.getElementById('isPrompt_checkbox')); if (hasForm || retry > 20) { clearInterval(checkReady); setTimeout(() => { applyAllPresets(cfg); }, 800); } }, 300); } } // ========================================================================= // 入口调度:依据当前访问的页面路由分别加载对应模块 // ========================================================================= injectGlobalStyles(); if (currentPath.includes('/page/selectperson/selectPerson')) { initSelectPersonModule(); } else if (currentPath.includes('/page/quiz/quizStatisticsPage')) { initQuizStatisticsModule(); } else if (currentPath.includes('/page/quiz/addOrUpdateQuiz')) { initAddOrUpdateQuizModule(); } })();