// ==UserScript== // @name 台州职业技术学院继续教育自动学习脚本 V22 // @namespace http://tampermonkey.net/ // @version 22.0 // @description 页面加载即统计学完数量,自动检测并点击学习 // @author You // @match https://zjjxjy.tzvtc.edu.cn/home/online/kjlist.html* // @match https://zjjxjy.tzvtc.edu.cn/home/online/kjlist* // @match https://zjjxjy.tzvtc.edu.cn/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; const Log = (...a) => console.log('[AutoStudyV22]', ...a); // ==================== 配置 ==================== const CFG = { detectInterval: 3000, // 侦测间隔:3秒 clickDelay: 2000, // 点击后等待:2秒 maxRetries: 15, // 最大重试次数 retryDelay: 500, // 重试间隔:0.5秒 autoStart: true, // 页面加载后自动开始 }; // ==================== 工具函数 ==================== function $(sel) { return document.querySelector(sel); } function $$(sel) { return document.querySelectorAll(sel); } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // 模拟真实点击 function simulateClick(el) { if (!el) { Log('❌ 元素为空,无法点击'); return false; } try { const rect = el.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } const x = rect.left + rect.width/2; const y = rect.top + rect.height/2; const events = ['mouseover', 'mouseenter', 'mousedown', 'mouseup', 'click']; events.forEach(eventType => { const event = new MouseEvent(eventType, { view: window, bubbles: true, cancelable: true, clientX: x, clientY: y, button: 0 }); el.dispatchEvent(event); }); el.click(); Log('✅ 点击成功'); return true; } catch(e) { Log('❌ 点击失败:', e); return false; } } // ==================== 统计"学完"数量(独立函数,不依赖任何状态) ==================== function countXuewan() { try { let count = 0; // 方法1:在表格行中查找"学完"文本(最准确) const rows = document.querySelectorAll('tr'); for (let row of rows) { // 检查这一行是否包含"学完"文本 const cells = row.querySelectorAll('td, th'); for (let cell of cells) { // 查找独立的"学完"文本 const elements = cell.querySelectorAll('*'); for (let el of elements) { if (el.children.length === 0) { const text = el.textContent.trim(); if (text === '学完') { count++; break; } } } // 也检查cell本身的文本 const cellText = cell.textContent.trim(); if (cellText === '学完') { // 避免重复计数 const hasChildXuewan = cell.querySelector('*'); if (!hasChildXuewan || hasChildXuewan.textContent.trim() !== '学完') { count++; } } } } // 方法2:直接查找所有"学完"文本(备用) if (count === 0) { const allElements = document.querySelectorAll('*'); for (let el of allElements) { if (el.children.length === 0) { const text = el.textContent.trim(); if (text === '学完') { // 检查是否在表格中 const parent = el.closest('tr, td, .table, .list'); if (parent && parent.offsetParent !== null) { count++; } } } } } // 方法3:通过CSS类名查找 if (count === 0) { const labels = document.querySelectorAll('.label-success, .badge-success, .tag-success, [class*="success"]'); for (let label of labels) { const text = label.textContent.trim(); if (text === '学完' || text.includes('学完')) { if (label.offsetParent !== null) { count++; } } } } Log(`📊 统计学完数量: ${count}`); return count; } catch(e) { Log('❌ 统计学完出错:', e); return 0; } } // ==================== 查找"学习"按钮 ==================== function findStudyButton() { Log('🔍 查找"学习"按钮...'); // 在表格行中查找"学习"按钮(跳过已学完的行) const rows = document.querySelectorAll('tr'); for (let row of rows) { // 检查这一行是否已经有"学完" const rowText = row.textContent; if (rowText.includes('学完')) { continue; // 已学完的跳过 } // 查找这一行中的"学习"按钮 const btns = row.querySelectorAll('button, a, span, div, input[type="button"]'); for (let btn of btns) { const text = btn.textContent.trim(); if (text === '学习' || text === '开始学习') { if (btn.offsetParent !== null && !btn.disabled) { Log('✅ 在表格行中找到"学习"按钮'); return btn; } } } } // 方法2:全局查找 const allElements = document.querySelectorAll('button, a, span, div'); for (let el of allElements) { const text = el.textContent.trim(); if (text === '学习' || text === '开始学习') { // 检查是否在操作列 const parent = el.closest('td, .td, .operation, .actions, .text-center'); if (parent && el.offsetParent !== null && !el.disabled) { // 检查这一行是否已学完 const row = el.closest('tr'); if (row && row.textContent.includes('学完')) { continue; } Log('✅ 在操作列中找到"学习"按钮'); return el; } } } Log('❌ 未找到可用的"学习"按钮'); return null; } // ==================== 查找"知道了,开始学习"按钮 ==================== function findConfirmButton() { Log('🔍 查找"知道了,开始学习"按钮...'); const allElements = document.querySelectorAll('button, a, div, span, input[type="button"], input[type="submit"]'); for (let el of allElements) { const text = el.textContent.trim(); if (text === '知道了,开始学习') { if (el.offsetParent !== null && !el.disabled) { Log('✅ 找到确认按钮(精确匹配)'); return el; } } } for (let el of allElements) { const text = el.textContent.trim(); if (text.includes('知道了') && text.includes('开始学习')) { if (el.offsetParent !== null && !el.disabled) { Log('✅ 找到确认按钮(部分匹配)'); return el; } } } // 在弹窗中查找 const modals = document.querySelectorAll('.modal, .dialog, .layer, .popup, [role="dialog"], .layui-layer'); for (let modal of modals) { if (modal.offsetParent !== null) { const buttons = modal.querySelectorAll('button, a, div, span'); for (let btn of buttons) { const text = btn.textContent.trim(); if (text.includes('知道了') || text.includes('开始学习') || text === '确定' || text === '确认') { if (btn.offsetParent !== null && !btn.disabled) { Log('✅ 在弹窗中找到确认按钮:', text); return btn; } } } } } Log('❌ 未找到确认按钮'); return null; } // ==================== 查找下一页 ==================== function findNextPage() { const nextTexts = ['»', '下一页', '>', 'Next', '下页']; for (let text of nextTexts) { const allElements = document.querySelectorAll('a, button, span, li'); for (let el of allElements) { if (el.textContent.trim() === text) { if (el.offsetParent !== null && !el.disabled && !el.classList.contains('disabled')) { Log('✅ 找到下一页按钮'); return el; } } } } return null; } // ==================== 核心学习类 ==================== class AutoStudy { constructor() { this.running = false; this.isBusy = false; this.lastCount = 0; this.totalStudied = 0; this.timer = null; this.startTime = null; this.isFirstRun = true; } // 执行学习流程 async doStudyFlow() { if (this.isBusy) { Log('⏳ 流程正在执行中...'); return; } this.isBusy = true; Log('═══════════════════════════════════'); Log('🚀 开始学习流程'); Log('═══════════════════════════════════'); this.updateStatus('🔍 查找学习按钮...'); try { await sleep(500); // 1. 查找并点击"学习"按钮 let studyBtn = null; let retryCount = 0; while (!studyBtn && retryCount < CFG.maxRetries) { studyBtn = findStudyButton(); if (!studyBtn) { retryCount++; Log(`⏳ 重试查找学习按钮 (${retryCount}/${CFG.maxRetries})`); this.updateStatus(`查找学习按钮 ${retryCount}/${CFG.maxRetries}`); await sleep(CFG.retryDelay); } } if (!studyBtn) { Log('📄 当前页没有学习按钮,尝试翻页...'); const nextPage = findNextPage(); if (nextPage) { Log('📄 点击下一页'); simulateClick(nextPage); await sleep(3000); this.isBusy = false; setTimeout(() => this.doStudyFlow(), 2000); return; } else { Log('✅ 没有更多页面,所有课程已完成!'); this.updateStatus('🎉 全部完成!'); this.isBusy = false; return; } } // 高亮显示 studyBtn.style.border = '3px solid #ff6b6b'; studyBtn.style.boxShadow = '0 0 20px rgba(255,107,107,0.5)'; Log('🖱️ 点击【学习】按钮'); this.updateStatus('点击学习...'); simulateClick(studyBtn); await sleep(CFG.clickDelay); // 2. 查找并点击"知道了,开始学习"按钮 Log('🔍 查找确认按钮...'); this.updateStatus('等待确认按钮...'); let confirmBtn = null; retryCount = 0; while (!confirmBtn && retryCount < CFG.maxRetries) { confirmBtn = findConfirmButton(); if (!confirmBtn) { retryCount++; Log(`⏳ 等待确认按钮 (${retryCount}/${CFG.maxRetries})`); await sleep(CFG.retryDelay); } } if (confirmBtn) { confirmBtn.style.border = '3px solid #ff0000'; confirmBtn.style.boxShadow = '0 0 20px rgba(255,0,0,0.5)'; Log('🖱️ 点击【知道了,开始学习】'); this.updateStatus('开始学习...'); simulateClick(confirmBtn); this.totalStudied++; Log(`✅ 学习成功!已学习课程数: ${this.totalStudied}`); this.updateStatus(`✅ 已学习 ${this.totalStudied} 个`); this.updateStats(); await sleep(2000); } else { Log('❌ 未找到确认按钮'); this.updateStatus('⚠️ 未找到确认按钮'); // 尝试备选按钮 const otherBtns = document.querySelectorAll('.btn-primary, .btn-success, .layui-layer-btn0'); for (let btn of otherBtns) { const text = btn.textContent.trim(); if (text.includes('确定') || text.includes('知道') || text.includes('开始')) { Log(`🖱️ 尝试点击备用按钮: "${text}"`); simulateClick(btn); await sleep(1000); break; } } } } catch(e) { Log('❌ 学习流程错误:', e); this.updateStatus('❌ 发生错误'); } finally { this.isBusy = false; Log('═══════════════════════════════════'); Log('🏁 学习流程结束'); Log('═══════════════════════════════════'); } } // 侦测循环 - 每次都重新统计"学完"数量 async detectLoop() { if (!this.running || this.isBusy) return; try { // 每次都重新统计,不依赖任何缓存 const currentCount = countXuewan(); Log(`📊 侦测: 当前学完=${currentCount}, 上次记录=${this.lastCount}`); // 第一次运行,初始化 if (this.isFirstRun) { this.lastCount = currentCount; this.isFirstRun = false; Log(`📝 初始化记录: 学完=${currentCount}`); this.updateStatus(`初始学完: ${currentCount}`); this.updateStats(); // 如果有学完的,立即开始学习 if (currentCount > 0) { Log('🎯 检测到已有学完课程,开始学习下一个'); // 等待1秒后开始 setTimeout(() => this.doStudyFlow(), 1000); } return; } // 检测到学完数量增加 if (currentCount > this.lastCount) { const increase = currentCount - this.lastCount; Log(`🎯 学完数量增加 ${increase} 个 (${this.lastCount}→${currentCount})`); this.updateStatus(`🎯 学完+${increase},开始下一个`); this.lastCount = currentCount; this.updateStats(); await sleep(1000); await this.doStudyFlow(); return; } // 如果学完数量减少(页面刷新),更新记录 if (currentCount < this.lastCount) { Log(`🔄 学完数变化 ${this.lastCount}→${currentCount},更新记录`); this.lastCount = currentCount; } // 显示当前状态 const status = `侦测中 | 学完:${currentCount} | 已学:${this.totalStudied}`; this.updateStatus(status); this.updateStats(); Log(`⏱️ ${status}`); } catch(e) { Log('❌ 侦测错误:', e); } } // 启动 start() { if (this.running) { Log('⚠️ 脚本已经在运行中'); return; } this.running = true; this.isBusy = false; this.startTime = new Date(); this.isFirstRun = true; // 立即统计当前学完数量 this.lastCount = countXuewan(); this.totalStudied = 0; Log('═══════════════════════════════════'); Log('🚀 V22 启动'); Log('═══════════════════════════════════'); Log(`📝 初始学完数量: ${this.lastCount}`); this.updateStatus(`启动中 | 学完:${this.lastCount}`); // 启动定时侦测 if (this.timer) clearInterval(this.timer); this.timer = setInterval(() => { this.detectLoop().catch(e => Log('❌ 侦测错误:', e)); }, CFG.detectInterval); Log(`⏱️ 侦测定时器已启动(间隔 ${CFG.detectInterval/1000}秒)`); // 立即执行一次检测 setTimeout(() => { this.detectLoop(); }, 500); } // 停止 stop() { this.running = false; this.isBusy = false; if (this.timer) { clearInterval(this.timer); this.timer = null; } Log('⏹️ 已停止'); this.updateStatus('⏹️ 已停止'); const startBtn = document.querySelector('#v22-start'); if (startBtn) { startBtn.style.background = '#00d2ff'; startBtn.textContent = '▶ 开始'; } } // 更新UI状态 updateStatus(text) { const statusEl = document.querySelector('#v22-status'); if (statusEl) { statusEl.textContent = text; } } // 更新统计信息(每次都重新统计) updateStats() { const count = countXuewan(); const countEl = document.querySelector('#v22-count'); if (countEl) { countEl.textContent = count; } const studiedEl = document.querySelector('#v22-studied'); if (studiedEl) { studiedEl.textContent = this.totalStudied; } const totalCourses = 50; const progressEl = document.querySelector('#v22-progress'); if (progressEl) { const progress = Math.min(100, Math.round((this.totalStudied / totalCourses) * 100)); progressEl.textContent = progress + '%'; } } // 手动刷新统计(外部调用) refreshStats() { const count = countXuewan(); this.lastCount = count; this.updateStats(); this.updateStatus(`已刷新 | 学完:${count}`); Log(`🔄 手动刷新: 学完=${count}`); return count; } } // ==================== UI 界面 ==================== function createUI(study) { if (window.self !== window.top) { Log('在iframe中,跳过UI创建'); return; } const oldUI = document.querySelector('#auto-study-v22'); if (oldUI) oldUI.remove(); const div = document.createElement('div'); div.id = 'auto-study-v22'; div.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 2147483647; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); font-family: "Microsoft YaHei", "PingFang SC", sans-serif; min-width: 260px; font-size: 13px; border: 2px solid rgba(255,255,255,0.2); backdrop-filter: blur(10px); user-select: none; `; div.innerHTML = `