// ==UserScript== // @name 职教云下一节辅助工具 // @namespace https://icve.com.cn/ // @version 1.0.0 // @description 视频结束提醒、返回课程列表、定位下一节未完成课件 // @match *://*.icve.com.cn/* // @match *://icve.com.cn/* // @match *://*.zjy2.icve.com.cn/* // @run-at document-idle // @grant none // ==/UserScript== (() => { 'use strict'; if (window.top !== window.self) return; if (window.__zhijiaoHelperInstalled) return; window.__zhijiaoHelperInstalled = true; const PREFIX = '[职教云辅助]'; const log = (...args) => console.log(PREFIX, ...args); const visible = el => !!el && el.isConnected && el.getClientRects().length > 0; const textOf = el => (el?.innerText || el?.textContent || '').trim(); // 查找页面中可见的按钮或链接 function findVisibleControl(pattern) { return [...document.querySelectorAll( 'button, a, [role="button"]' )].find(el => visible(el) && pattern.test(textOf(el) || el.getAttribute('aria-label') || '') ); } // 创建辅助面板 const panel = document.createElement('div'); panel.style.cssText = ` position:fixed; right:18px; bottom:20px; z-index:2147483647; width:250px; padding:14px; background:#fff; color:#25264b; border:1px solid #8174ff; border-radius:12px; box-shadow:0 5px 20px #0003; font:14px/1.7 Arial,sans-serif; `; panel.innerHTML = `
职教云学习辅助
正在识别页面……
`; document.body.appendChild(panel); const status = panel.querySelector('#zj-status'); const result = panel.querySelector('#zj-result'); // 尝试识别课程列表中尚未完成的课件 function findIncompleteLesson() { const progressNodes = [ ...document.querySelectorAll('body *') ].filter(el => { if (!visible(el)) return false; if (el.children.length > 2) return false; const t = textOf(el); return /学习进度\s*[::]\s*\d+(?:\.\d+)?\s*%/.test(t); }); const lessons = []; const seen = new Set(); for (const node of progressNodes) { const match = textOf(node).match( /学习进度\s*[::]\s*(\d+(?:\.\d+)?)\s*%/ ); if (!match) continue; const progress = Number(match[1]); if (progress >= 100) continue; // 向上寻找同时包含进度与“查看”按钮的最小容器 let container = node; for (let i = 0; i < 8 && container; i++) { const controls = [ ...container.querySelectorAll( 'button, a, [role="button"]' ) ]; const hasView = controls.some(el => visible(el) && /查看/.test(textOf(el)) ); const hasProgress = /学习进度/.test(textOf(container)); if (hasView && hasProgress) break; container = container.parentElement; } if (!container || seen.has(container)) continue; const viewButton = [ ...container.querySelectorAll( 'button, a, [role="button"]' ) ].find(el => visible(el) && /查看/.test(textOf(el)) ); if (!viewButton) continue; seen.add(container); lessons.push({ container, viewButton, progress, title: textOf(container).slice(0, 100) }); } // 按页面从上到下排列 lessons.sort((a, b) => a.container.getBoundingClientRect().top - b.container.getBoundingClientRect().top ); return lessons[0] || null; } // 在列表页定位下一节未完成的课件 function locateNext() { const lesson = findIncompleteLesson(); if (!lesson) { status.textContent = '未找到可识别的未完成课件'; result.textContent = '请确认当前是课程列表页,并且进度文字为“学习进度:xx%”。'; return; } lesson.container.scrollIntoView({ behavior: 'smooth', block: 'center' }); lesson.container.style.outline = '3px solid #8877ff'; lesson.container.style.outlineOffset = '3px'; status.textContent = `找到下一节候选课件:${lesson.progress}%`; result.textContent = '已定位到未完成课件。请核对课件名称,再点击该课件右侧的“查看”。'; log('候选课件', lesson.title, lesson.progress + '%'); } // 返回课程活动列表 function returnToList() { const button = findVisibleControl(/返回课堂活动列表/); if (button) { button.click(); result.textContent = '已尝试返回课程活动列表。'; return; } result.textContent = '当前页面未找到“返回课堂活动列表”按钮,请手动返回列表。'; } panel.querySelector('#zj-locate') .addEventListener('click', locateNext); panel.querySelector('#zj-return') .addEventListener('click', returnToList); // 检查当前页面是否有视频 const videos = [...document.querySelectorAll('video')]; if (videos.length) { status.textContent = '已检测到视频播放器'; videos.forEach(video => { video.addEventListener('ended', () => { status.textContent = '视频播放结束'; result.textContent = '请确认学习记录已保存,再点击“返回列表”,定位下一节。'; log('检测到视频 ended 事件'); }); }); } else { status.textContent = '辅助面板已启动'; } log('初始化完成', location.href); })();