// ==UserScript== // @name Welearn 时长精灵 // @namespace http://tampermonkey.net/ // @version 39.7 // @description 具备智能时长控制,保证时长有效性,计算时长数据的小工具 // @author Assistant // @match *://welearn.sflep.com/student/course_info.aspx* // @match *://welearn.sflep.com/student/StudyCourse.aspx* // @match *://centercourseware.sflep.com/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; const DB_KEY = 'WL_DATA_V39'; const TASK_KEY = 'WL_TASK_V39_6'; const UI_KEY = 'WL_UI_POS_V39'; const HOST = window.location.hostname; const IS_TOP = (window.self === window.top); const style = document.createElement('style'); style.textContent = ` /* 主面板基础样式 */ #wl-panel { position: fixed; width: 340px; max-height: 90vh; background: rgba(20,20,20,0.98); color: #e0e0e0; border: 1px solid #444; z-index: 2147483647; border-radius: 12px; font-family: 'Microsoft YaHei', sans-serif; display: flex; flex-direction: column; box-shadow: 0 10px 50px rgba(0,0,0,0.8); backdrop-filter: blur(5px); transition: height 0.3s, background-color 0.3s, border-color 0.3s, box-shadow 0.3s; } /* 1. 赞助小圆按钮 */ .wl-donate-btn { display: block; margin: 0 auto 8px; /* 居中并与下方文字保持间距 */ width: 30px; height: 30px; line-height: 30px; background: linear-gradient(135deg, #89f7fe, #66a6ff); /* 浅蓝流光色 */ color: #fff; border-radius: 50%; font-size: 14px; font-weight: bold; cursor: pointer; box-shadow: 0 2px 10px rgba(102, 166, 255, 0.4); transition: 0.2s; } /* 2. 弹窗背后的半透明遮罩 */ #wl-donate-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); /* 深色半透明 */ backdrop-filter: blur(5px); /* 背景模糊,显高级 */ display: none; /* 默认隐藏 */ justify-content: center; align-items: center; z-index: 2147483648; /* 确保在最前面 */ } /* 3. 存放图片的卡片 */ .wl-donate-card { background: #1a1a1a; padding: 25px; border-radius: 16px; border: 1px solid #555; text-align: center; position: relative; } .wl-donate-card img { width: 200px; height: 200px; border-radius: 8px; margin: 10px 0; } /* === 主题变色 (恶搞模式) === */ #wl-panel.wl-theme-fun { border-color: #ff00cc; background: rgba(30, 10, 30, 0.98); } #wl-panel.wl-theme-fun .wl-header { background: rgba(255, 0, 200, 0.1); border-bottom-color: #ff00cc; } #wl-panel.wl-theme-fun .wl-status-text { color: #ff00cc !important; } #wl-panel.wl-theme-fun .wl-hl { color: #00ffff !important; } #wl-panel.wl-theme-fun input[type=number]:focus, #wl-panel.wl-theme-fun .wl-input-group:focus-within { border-color: #ff00cc !important; } #wl-panel.wl-theme-fun .wl-running { color: #ff00cc !important; animation: wl-pulse-fun 1s infinite !important; } #wl-panel.wl-theme-fun .wl-sec-row.active { border-left-color: #ff00cc; background: rgba(255,0,200,0.1); } #wl-panel.wl-theme-fun .wl-checkbox { accent-color: #ff00cc; } #wl-panel.wl-theme-fun #wl-btn-run { background: linear-gradient(45deg, #ff00cc, #333399) !important; } /* 最小化状态 */ #wl-panel.wl-minimized { width: 180px !important; height: auto !important; max-height: 50px !important; border-radius: 25px; border: 1px solid #555; overflow: hidden; } #wl-panel.wl-minimized .wl-body, #wl-panel.wl-minimized .wl-footer-area, #wl-panel.wl-minimized .wl-setting-box, #wl-panel.wl-minimized .wl-filter-box, #wl-panel.wl-minimized .wl-timer-full, #wl-panel.wl-minimized .wl-main-btn { display: none !important; } #wl-panel.wl-minimized .wl-header { border:none; background: transparent; padding: 10px 15px; cursor: move; } #wl-panel.wl-minimized .wl-status-bar { margin: 0; justify-content: center; } /* 头部区域 */ .wl-header { padding:15px; border-bottom:1px solid #333; background:rgba(255,255,255,0.05); flex-shrink:0; border-radius:12px 12px 0 0; cursor: move; user-select: none; transition: background-color 0.3s; } .wl-ctrl-btns { display: flex; align-items: center; gap: 8px; } .wl-icon-btn { cursor: pointer; color: #888; font-weight: bold; font-size: 14px; transition: 0.2s; padding: 2px 5px; } .wl-icon-btn:hover { color: #fff; background: #444; border-radius: 4px; } /* 滚动区域 */ .wl-body { overflow-y:auto; flex-grow:1; padding:0; scrollbar-width: thin; scrollbar-color: #444 #222; } .wl-row { display:flex; align-items:center; margin-bottom:8px; font-size:13px; justify-content: space-between; } .wl-status-bar { display:flex; justify-content:space-between; align-items:center; } .wl-setting-box { background:#2a2a2a; padding:12px 15px; border-radius:8px; margin-bottom:8px; border:1px solid #333; } .wl-setting-box.small { padding: 8px 12px; background:#222; border-color:#333; } .wl-big-text { font-size:14px; font-weight:bold; color:#ddd; width: 80px; } .wl-small-text { font-size:12px; color:#aaa; margin-right: 5px; } /* 输入框组合 */ .wl-input-group { display: flex; align-items: center; background: #111; border-radius: 6px; border: 1px solid #444; padding: 2px; } .wl-input-group:focus-within { border-color: #0f0; } .wl-spin-btn { width: 24px; height: 24px; background: transparent; border: none; color: #888; cursor: pointer; font-size: 16px; line-height: 1; display: flex; align-items: center; justify-content: center; transition: 0.2s; border-radius: 4px; } .wl-spin-btn:hover { background: #333; color: #fff; } input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } input[type=number] { background:transparent; border:none; color:#fff; text-align:center; font-size:15px; font-weight:bold; outline:none; width: 50px; height: 24px; -moz-appearance: textfield; } .wl-btn { border:none; border-radius:4px; padding:4px 10px; cursor:pointer; font-size:12px; color:#fff; transition:0.2s; font-weight:bold; } .wl-btn:hover { opacity:0.8; } .wl-btn-main { width:100%; font-size:16px; padding:10px; border-radius:6px; letter-spacing:1px; margin-top:5px; text-shadow:0 1px 2px rgba(0,0,0,0.5); } .wl-unit { border-bottom:1px solid #333; } .wl-unit-bar { padding:10px 12px; background:#181818; cursor:pointer; display:flex; align-items:center; font-size:13px; transition:0.2s; } .wl-unit-bar:hover { background:#222; } .wl-sec-row { padding:8px 12px 8px 35px; border-left:3px solid transparent; display:flex; align-items:center; font-size:12px; border-bottom:1px solid #222; background:#111; } .wl-sec-row.active { border-left-color:#0f0; background:rgba(0,255,0,0.05); } .wl-hl { color:#0f0; font-weight:bold; font-family:monospace; font-size:14px; } .wl-checkbox { margin-right:10px; cursor:pointer; width:16px; height:16px; accent-color: #0f0; } /* [修改] 底部署名样式 - 浅蓝流光渐变 */ .wl-footer-area { background:#111; padding:8px; border-radius:0 0 12px 12px; text-align:center; cursor:pointer; user-select:none; border-top: 1px solid #222; } .wl-footer-text { font-size: 13px; font-weight: 900; letter-spacing: 1px; /* 从浅蓝开始,过渡到紫、粉,再回到浅蓝 */ background: linear-gradient(90deg, #89f7fe, #66a6ff, #af40ff, #ff5e62, #89f7fe); background-size: 300% 100%; -webkit-background-clip: text; color: transparent; animation: wl-blue-shift 6s linear infinite; display: inline-block; transition: transform 0.2s; } .wl-footer-area:active .wl-footer-text { transform: scale(0.95); } /* 动画关键帧 */ @keyframes wl-pulse { 0% { opacity:0.6; color:#4dff4d; } 50% { opacity:1; color:#fff; } 100% { opacity:0.6; color:#4dff4d; } } @keyframes wl-pulse-fun { 0% { opacity:0.6; color:#ff00cc; } 50% { opacity:1; color:#fff; } 100% { opacity:0.6; color:#ff00cc; } } /* [修改] 新的浅蓝渐变动画 */ @keyframes wl-blue-shift { 0% { background-position: 0% 50%; } 100% { background-position: 100% 50%; } } /* 表情粒子动画 */ .wl-emoji-particle { position: fixed; pointer-events: none; font-size: 24px; animation: wl-emoji-pop 0.8s cubic-bezier(0.18, 0.89, 0.32, 1.28) forwards; z-index: 2147483648; } @keyframes wl-emoji-pop { 0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0; } 50% { opacity: 1; } 100% { transform: translate(-50%, -80px) scale(1.2); opacity: 0; } } .wl-body::-webkit-scrollbar { width: 6px; } .wl-body::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; } `; document.head.appendChild(style); const t2s = (str) => { if (!str) return 0; let s = str.replace(/用时|\s/g, ''); let p = s.split(':').map(Number); return p.length === 3 ? p[0]*3600+p[1]*60+p[2] : (p.length === 2 ? p[0]*60+p[1] : 0); }; const s2t = (s) => { if (isNaN(s) || s < 0) return "00:00"; s = Math.floor(s); let h = Math.floor(s/3600), m = Math.floor((s%3600)/60), sec = s%60; return (h > 0 ? h + ":" : "") + m.toString().padStart(2,'0') + ":" + sec.toString().padStart(2,'0'); }; if (HOST.includes('centercourseware')) { setInterval(() => { const live = window.sco_time || (window.CourseAPI && window.CourseAPI.getTime()); if (live) window.parent.postMessage({ type: 'WL_TICK', val: parseInt(live) }, '*'); }, 1000); return; } if (IS_TOP) { let state = { currentId: "", sessionInc: 0, expandedUnits: new Set(), selectedIds: new Set(), addMin: 2, floatMin: 0, timeUnit: 'min', currentRandomTarget: 0, filterMin: 9999, isAuto: false, taskStartTime: 0, lastSyncCount: 0, isMinimized: false, posX: 'right: 15px', posY: 'top: 15px', isFunTheme: false }; try { const saved = JSON.parse(localStorage.getItem(TASK_KEY) || "{}"); if (saved.selectedIds) state.selectedIds = new Set(saved.selectedIds); if (saved.addMin) state.addMin = saved.addMin; if (saved.floatMin) state.floatMin = saved.floatMin; if (saved.timeUnit) state.timeUnit = saved.timeUnit; if (saved.isAuto) state.isAuto = saved.isAuto; state.taskStartTime = 0; state.currentRandomTarget = 0; } catch(e) {} try { const uiSaved = JSON.parse(localStorage.getItem(UI_KEY) || "{}"); if (uiSaved.posX) state.posX = uiSaved.posX; if (uiSaved.posY) state.posY = uiSaved.posY; if (uiSaved.isMinimized !== undefined) state.isMinimized = uiSaved.isMinimized; if (uiSaved.isFunTheme !== undefined) state.isFunTheme = uiSaved.isFunTheme; } catch(e) {} const saveState = () => localStorage.setItem(TASK_KEY, JSON.stringify({ selectedIds: Array.from(state.selectedIds), addMin: state.addMin, floatMin: state.floatMin, timeUnit: state.timeUnit, isAuto: state.isAuto })); const saveUIState = () => localStorage.setItem(UI_KEY, JSON.stringify({ posX: state.posX, posY: state.posY, isMinimized: state.isMinimized, isFunTheme: state.isFunTheme })); function scrape() { let freshData = {}; let count = 0; const items = document.querySelectorAll('li[onclick*="StartSCO"], li[id^="liSCO_"]'); items.forEach(li => { let id = null; if (li.id && li.id.startsWith('liSCO_')) { id = li.id.replace('liSCO_', ''); } else { let clickStr = li.getAttribute('onclick') || ""; let m = clickStr.match(/['"]([^'"]+)['"]/); if (m) id = m[1]; } if (id) { let spans = li.querySelectorAll('span.pull-right'); let timeText = ""; spans.forEach(sp => { let txt = sp.textContent; if (txt.includes('用时') && !sp.style.display.includes('none')) { timeText = txt.replace('用时', '').trim(); } }); if (!timeText) { spans.forEach(sp => { let txt = sp.textContent; if (txt.includes(':') && !sp.style.display.includes('none')) { timeText = txt.trim(); } }); } if (timeText) { freshData[id] = timeText; count++; } } }); if (count > 0) { localStorage.setItem(DB_KEY, JSON.stringify(freshData)); state.lastSyncCount = count; } } window.addEventListener('message', e => { if (e.data.type === 'WL_TICK') state.sessionInc = e.data.val; }); function makeDraggable(el) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const header = el.querySelector(".wl-header"); header.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; if (['INPUT', 'BUTTON'].includes(e.target.tagName) || e.target.closest('.wl-icon-btn') || e.target.closest('.wl-spin-btn') || e.target.closest('.wl-input-group')) return; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; el.style.top = (el.offsetTop - pos2) + "px"; el.style.left = (el.offsetLeft - pos1) + "px"; el.style.right = 'auto'; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; state.posX = `left: ${el.style.left}`; state.posY = `top: ${el.style.top}`; saveUIState(); } } function initPanel() { if (document.getElementById('wl-panel')) return; const div = document.createElement('div'); div.id = 'wl-panel'; div.style.cssText = `${state.posX}; ${state.posY};`; if (state.isMinimized) div.classList.add('wl-minimized'); if (state.isFunTheme) div.classList.add('wl-theme-fun'); div.innerHTML = `
welearn 时长助手
${state.isMinimized ? '⬜' : '➖'}
📚 全书进度: --:--
每节刷:
随机波动 (±):
过滤出小于
分钟的
×
感谢支持 ✨
二维码
扫码请作者喝杯咖啡
`; document.body.appendChild(div); const dBtn = document.getElementById('wl-btn-donate'); const dMask = document.getElementById('wl-donate-mask'); const dClose = dMask.querySelector('.wl-donate-close'); dBtn.onclick = (e) => { e.stopPropagation(); dMask.style.display = 'flex'; }; dClose.onclick = () => { dMask.style.display = 'none'; }; dMask.onclick = (e) => { if (e.target === dMask) dMask.style.display = 'none'; }; makeDraggable(div); const footer = document.getElementById('wl-footer'); footer.onclick = (e) => { state.isFunTheme = !state.isFunTheme; div.classList.toggle('wl-theme-fun'); saveUIState(); updateList(); const emojis = ['🤡', '🏠', '✨', '🌈', '🔥', '💊', '🎉', '👻']; const el = document.createElement('div'); el.className = 'wl-emoji-particle'; el.innerText = emojis[Math.floor(Math.random() * emojis.length)]; el.style.left = e.clientX + 'px'; el.style.top = (e.clientY - 10) + 'px'; el.style.fontSize = (25 + Math.random() * 15) + 'px'; document.body.appendChild(el); setTimeout(() => el.remove(), 1000); }; const minBtn = document.getElementById('wl-min-btn'); minBtn.onclick = () => { state.isMinimized = !state.isMinimized; div.classList.toggle('wl-minimized'); minBtn.innerText = state.isMinimized ? '⬜' : '➖'; saveUIState(); updateList(); }; const fIn = document.getElementById('wl-filter-in'); const tIn = document.getElementById('wl-target-in'); const flIn = document.getElementById('wl-float-in'); const uBtn = document.getElementById('wl-unit-btn'); const getFactor = () => { if (state.timeUnit === 's') return 60; if (state.timeUnit === 'h') return 1/60; return 1; }; const updateInputDisplay = () => { const factor = getFactor(); if(document.activeElement !== tIn) tIn.value = parseFloat((state.addMin * factor).toFixed(2)); if(document.activeElement !== flIn) flIn.value = parseFloat((state.floatMin * factor).toFixed(2)); uBtn.innerText = state.timeUnit; }; const saveFromInputs = () => { const factor = getFactor(); let valT = parseFloat(tIn.value); let valF = parseFloat(flIn.value); if (!isNaN(valT)) state.addMin = valT / factor; if (!isNaN(valF)) state.floatMin = Math.abs(valF / factor); if(state.addMin > 30) state.addMin = 30; if(state.addMin <= 0) state.addMin = 0.1; saveState(); }; [tIn, flIn].forEach(el => { el.oninput = saveFromInputs; el.onchange = () => { saveFromInputs(); updateInputDisplay(); }; }); const setupSpin = (btnId, inputEl, isAdd, isFloat) => { document.getElementById(btnId).onclick = () => { let step = 1; if(state.timeUnit === 'h') step = 0.1; if(state.timeUnit === 's') step = 10; let curr = parseFloat(inputEl.value) || 0; curr = isAdd ? curr + step : curr - step; curr = parseFloat(curr.toFixed(2)); inputEl.value = curr; saveFromInputs(); updateInputDisplay(); }; }; setupSpin('btn-inc-t', tIn, true, false); setupSpin('btn-dec-t', tIn, false, false); setupSpin('btn-inc-f', flIn, true, true); setupSpin('btn-dec-f', flIn, false, true); fIn.oninput = (e) => { state.filterMin = parseInt(e.target.value) || 9999; updateList(); }; uBtn.onclick = () => { const units = ['s', 'min', 'h']; state.timeUnit = units[(units.indexOf(state.timeUnit) + 1) % 3]; tIn.blur(); flIn.blur(); updateInputDisplay(); saveState(); }; document.getElementById('wl-btn-rst').onclick = () => { fIn.value = ""; state.filterMin = 9999; state.selectedIds.clear(); saveState(); updateList(); }; document.getElementById('wl-btn-sel').onclick = () => { const dict = JSON.parse(localStorage.getItem(DB_KEY)||"{}"); document.querySelectorAll('.wl-sec-chk').forEach(chk => { const t = t2s(dict[chk.dataset.id]); if (t < state.filterMin * 60) state.selectedIds.add(chk.dataset.id); }); saveState(); updateList(); }; document.getElementById('wl-btn-run').onclick = () => { saveFromInputs(); if (!state.selectedIds.size && !state.isAuto) return alert("❌ 请先勾选需要刷的小节"); state.isAuto = !state.isAuto; state.taskStartTime = 0; state.currentRandomTarget = 0; saveState(); updateList(); }; updateInputDisplay(); } function updateList() { const body = document.getElementById('wl-body'); if (!body) return; const runBtn = document.getElementById('wl-btn-run'); const statusEl = document.getElementById('wl-status'); const miniTimer = document.getElementById('wl-mini-timer'); const timerBox = document.getElementById('wl-timer-wrap'); const countEl = document.getElementById('wl-countdown'); const targetDisplay = document.getElementById('wl-target-display'); if (state.isAuto) { runBtn.innerHTML = "⏹ 停止运行"; runBtn.style.background = "#c0392b"; if (!state.isFunTheme) { statusEl.innerText = "🔥 运行中"; statusEl.style.color = "#0f0"; } } else { runBtn.innerHTML = "🚀 开始自动刷时长"; runBtn.style.background = "#28a745"; if (!state.isFunTheme) { statusEl.innerText = "😴 休息中"; statusEl.style.color = "#888"; } } if (state.isAuto && state.currentId) { if (state.selectedIds.has(state.currentId)) { if (state.isMinimized) { miniTimer.style.display = "block"; timerBox.style.display = "none"; } else { miniTimer.style.display = "none"; timerBox.style.display = "block"; } if (!state.taskStartTime || !state.currentRandomTarget) { state.taskStartTime = Date.now(); const minVal = Math.max(0.1, state.addMin - state.floatMin); const maxVal = Math.min(30, state.addMin + state.floatMin); state.currentRandomTarget = Math.random() * (maxVal - minVal) + minVal; } const elapsed = (Date.now() - state.taskStartTime) / 1000; const totalSec = state.currentRandomTarget * 60; const remain = totalSec - elapsed; const remainTxt = s2t(remain > 0 ? remain : 0); targetDisplay.innerText = s2t(totalSec); countEl.innerText = remainTxt; miniTimer.innerText = `剩余: ${remainTxt}`; if (remain <= 0) { state.selectedIds.delete(state.currentId); state.taskStartTime = 0; state.currentRandomTarget = 0; saveState(); let nextId = null; const allLis = document.querySelectorAll('li[id^="liSCO_"]'); for (let li of allLis) { const anchor = li.querySelector('a'); if (!anchor || !anchor.getAttribute('onclick') || !anchor.getAttribute('onclick').includes('SelectSCO')) continue; const id = li.id.replace('liSCO_', ''); if (state.selectedIds.has(id)) { nextId = id; break; } } if (nextId) { const nextLi = document.getElementById('liSCO_' + nextId); if (nextLi) { const anchor = nextLi.querySelector('a'); if(anchor) anchor.click(); else { const s = document.createElement('script'); s.textContent = `SelectSCO('${nextId}')`; document.body.appendChild(s); setTimeout(()=>s.remove(), 100); } } } else { state.isAuto = false; saveState(); alert("✅ 恭喜!所有选中的课程已刷完!"); } } } else { timerBox.style.display = "none"; miniTimer.style.display = "none"; if (state.selectedIds.size > 0 && !state.taskStartTime) { setTimeout(() => { let nextId = null; const allLis = document.querySelectorAll('li[id^="liSCO_"]'); for (let li of allLis) { const anchor = li.querySelector('a'); if (!anchor || !anchor.getAttribute('onclick') || !anchor.getAttribute('onclick').includes('SelectSCO')) continue; const id = li.id.replace('liSCO_', ''); if (state.selectedIds.has(id)) { nextId = id; break; } } if(nextId) { const s = document.createElement('script'); s.textContent = `SelectSCO('${nextId}')`; document.body.appendChild(s); setTimeout(()=>s.remove(), 100); } }, 2000); } } } else { timerBox.style.display = "none"; miniTimer.style.display = "none"; state.taskStartTime = 0; state.currentRandomTarget = 0; } const dict = JSON.parse(localStorage.getItem(DB_KEY) || "{}"); let grandTotal = 0; let html = ""; document.querySelectorAll('.courseware_list_1').forEach(uNode => { const head = uNode.querySelector('.courseware_list_1_1'); if (!head) return; const uName = head.querySelector('.v_1')?.innerText || "Unit"; const uId = head.id; let uSum = 0, secHtml = "", uActive = false, uAllSel = true; uNode.querySelectorAll('li[id^="liSCO_"]').forEach(li => { const anchor = li.querySelector('a'); if (!anchor || !anchor.getAttribute('onclick') || !anchor.getAttribute('onclick').includes('SelectSCO')) return; const id = li.id.replace('liSCO_', ''); const name = anchor.getAttribute('title') || "Section"; const isCur = li.classList.contains('courseware_current'); const base = t2s(dict[id]); let now = base; if (isCur) { state.currentId = id; now += state.sessionInc; uActive = true; } uSum += now; grandTotal += now; if (!state.selectedIds.has(id)) uAllSel = false; if (base < state.filterMin * 60) { secHtml += `
${name}
${s2t(now)}
`; } }); const isExp = state.expandedUnits.has(uId) || uActive; if (secHtml) { html += `
${isExp?'▼':'▶'} ${uName} ${s2t(uSum)}
${secHtml}
`; } }); document.getElementById('wl-body').innerHTML = html; document.getElementById('wl-total-time').innerText = s2t(grandTotal); bindListEvents(); } function bindListEvents() { const panel = document.getElementById('wl-panel'); panel.querySelectorAll('.wl-unit-trigger').forEach(el => { el.onclick = (e) => { const uid = el.dataset.uid; state.expandedUnits.has(uid) ? state.expandedUnits.delete(uid) : state.expandedUnits.add(uid); e.stopPropagation(); const content = el.parentNode.nextElementSibling; content.style.display = content.style.display === 'none' ? 'block' : 'none'; }; }); panel.querySelectorAll('.wl-unit-chk').forEach(chk => { chk.onchange = (e) => { const subChks = e.target.parentNode.nextElementSibling.querySelectorAll('.wl-sec-chk'); subChks.forEach(sc => { const sid = sc.dataset.id; e.target.checked ? state.selectedIds.add(sid) : state.selectedIds.delete(sid); }); saveState(); updateList(); }; }); panel.querySelectorAll('.wl-sec-chk').forEach(chk => { chk.onchange = (e) => { const id = e.target.dataset.id; e.target.checked ? state.selectedIds.add(id) : state.selectedIds.delete(id); saveState(); }; }); } setInterval(() => { const onDash = window.location.href.includes('course_info.aspx'); if(!document.getElementById('wl-panel')) initPanel(); if (onDash) { scrape(); const now = new Date(); const timeStr = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; document.getElementById('wl-body').innerHTML = `
💾
[主页同步模式]
数据已强力同步
检测到 ${state.lastSyncCount} 个小节
最后更新: ${timeStr}
`; } else { updateList(); } }, 1000); } })();