// ==UserScript== // @name 安徽中小学教师教育网 // @namespace http://yourdomain.com/ // @version 2.0 // @description 解除新标签页限制,支持多标签页同时计时 // @author denglao // @match https://www.jsjy.ah.cn/* // @grant none // ==/UserScript== (function() { 'use strict'; // ========== 第一部分:解除新标签页限制 ========== console.log('🔄 正在初始化新标签页解除限制...'); // 1. 保存原始 window.open 方法 const originalWindowOpen = window.open; // 2. 生成唯一名称的计数器 let windowCounter = 0; // 3. 覆盖 window.open,每次使用不同的窗口名称 window.open = function(url, name, features) { if (url) { // 生成一个唯一的窗口名称,确保每次都打开新标签页 const uniqueName = '_blank_' + Date.now() + '_' + (windowCounter++); // 使用原始的 window.open 方法,传入唯一的名称 return originalWindowOpen(url, uniqueName, features); } return null; }; // 4. 拦截所有链接的点击事件,确保使用我们的自定义打开方式 document.addEventListener('click', function(e) { // 查找被点击的链接 let target = e.target; while (target && target.tagName !== 'A') { target = target.parentElement; } if (target && target.tagName === 'A') { const href = target.getAttribute('href'); const targetAttr = target.getAttribute('target'); // 如果链接有 target="_blank" 或者用户按下了 Ctrl/Meta 键 if (targetAttr === '_blank' || e.ctrlKey || e.metaKey) { e.preventDefault(); if (href && href !== '#' && !href.startsWith('javascript:')) { // 解析相对路径为绝对路径 const absoluteUrl = new URL(href, window.location.href).href; // 使用我们的自定义方法打开 window.open(absoluteUrl, '_blank'); } } } }, true); // 使用捕获阶段 // 5. 确保 target="_blank" 的链接正常工作 function ensureLinksOpenInNewTab() { document.querySelectorAll('a[target="_blank"]').forEach(link => { // 为每个链接添加点击事件,确保它们在新标签页打开 link.addEventListener('click', function(e) { e.preventDefault(); const href = this.getAttribute('href'); if (href && href !== '#' && !href.startsWith('javascript:')) { const absoluteUrl = new URL(href, window.location.href).href; window.open(absoluteUrl, '_blank'); } }); }); } // 6. 使用 MutationObserver 处理动态添加的链接 const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1) { // 元素节点 // 如果添加的是链接 if (node.tagName === 'A' && node.getAttribute('target') === '_blank') { node.addEventListener('click', function(e) { e.preventDefault(); const href = this.getAttribute('href'); if (href && href !== '#' && !href.startsWith('javascript:')) { const absoluteUrl = new URL(href, window.location.href).href; window.open(absoluteUrl, '_blank'); } }); } // 检查添加的元素内部是否有链接 if (node.querySelectorAll) { node.querySelectorAll('a[target="_blank"]').forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const href = this.getAttribute('href'); if (href && href !== '#' && !href.startsWith('javascript:')) { const absoluteUrl = new URL(href, window.location.href).href; window.open(absoluteUrl, '_blank'); } }); }); } } }); }); }); // 开始观察 DOM 变化 observer.observe(document.body, { childList: true, subtree: true }); // 7. 页面加载完成后执行 document.addEventListener('DOMContentLoaded', function() { ensureLinksOpenInNewTab(); }); // 8. 立即执行一次 if (document.readyState === 'complete' || document.readyState === 'interactive') { ensureLinksOpenInNewTab(); } else { document.addEventListener('DOMContentLoaded', ensureLinksOpenInNewTab); } console.log('✅ 已解除新标签页限制,每次点击都会在新标签页打开!'); // ========== 第二部分:多标签页同时计时 ========== console.log('🔄 正在初始化多标签同时计时...'); // 1. 获取学习时长显示元素 const learnTimer = document.getElementById('learnTimer'); if (!learnTimer) { console.log('⚠️ 未找到学习时长元素,计时功能未启用'); return; } // 2. 解析当前时间字符串为秒数 function parseTimeToSeconds(timeStr) { if (!timeStr) return 0; const parts = timeStr.split(':'); if (parts.length === 3) { return parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]); } return 0; } // 3. 将秒数格式化为时间字符串 function formatTime(totalSeconds) { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; } // 4. 获取初始时间 const initialTimeStr = learnTimer.textContent; const initialSeconds = parseTimeToSeconds(initialTimeStr); // 5. 记录脚本开始运行的时间 const scriptStartTime = Date.now(); // 6. 更新计时器函数 function updateTimer() { const elapsedSeconds = Math.floor((Date.now() - scriptStartTime) / 1000); const totalSeconds = initialSeconds + elapsedSeconds; learnTimer.textContent = formatTime(totalSeconds); } // 7. 每秒钟更新一次时间 setInterval(updateTimer, 1000); // 8. 立即更新一次 updateTimer(); // 9. 覆盖 visibilitychange 事件,防止网站暂停计时 document.addEventListener('visibilitychange', function(e) { // 阻止默认行为 e.stopPropagation(); }, true); // 10. 覆盖 document.hidden 属性,始终返回 false Object.defineProperty(document, 'hidden', { get: function() { return false; } }); // 11. 覆盖 document.visibilityState 属性,始终返回 'visible' Object.defineProperty(document, 'visibilityState', { get: function() { return 'visible'; } }); // 12. 覆盖 document.webkitHidden 属性(某些浏览器) Object.defineProperty(document, 'webkitHidden', { get: function() { return false; } }); // 13. 覆盖 document.webkitVisibilityState 属性 Object.defineProperty(document, 'webkitVisibilityState', { get: function() { return 'visible'; } }); // 14. 覆盖 document.msHidden 属性(IE浏览器) Object.defineProperty(document, 'msHidden', { get: function() { return false; } }); // 15. 覆盖 document.msVisibilityState 属性 Object.defineProperty(document, 'msVisibilityState', { get: function() { return 'visible'; } }); // 16. 如果网站使用 requestAnimationFrame 来计时,覆盖它 const originalRequestAnimationFrame = window.requestAnimationFrame; window.requestAnimationFrame = function(callback) { // 使用 setTimeout 代替,确保即使标签页切换也能执行 return setTimeout(callback, 16); // 约60fps }; // 17. 覆盖 pagehide 事件,防止浏览器暂停 window.addEventListener('pagehide', function(e) { e.stopPropagation(); }, true); // 18. 如果网站有暂停计时的逻辑,尝试找到并覆盖它 const pauseFunctions = [ 'pauseTimer', 'stopTimer', 'pauseLearning', 'stopLearning', 'pauseVideo', 'stopVideo', 'pauseAll', 'stopAll' ]; pauseFunctions.forEach(funcName => { if (window[funcName]) { window[funcName] = function() { console.log(`已阻止 ${funcName} 调用`); return false; }; } }); console.log('✅ 已启用多标签同时计时功能!'); console.log('📊 初始时间:', initialTimeStr); console.log('📊 初始秒数:', initialSeconds); console.log('📊 脚本启动时间:', new Date(scriptStartTime).toLocaleString()); // 19. 定期检查时间是否同步 setInterval(function() { const currentDisplay = learnTimer.textContent; const expectedTime = formatTime(initialSeconds + Math.floor((Date.now() - scriptStartTime) / 1000)); // 如果显示时间与预期时间相差超过2秒,强制更新 if (Math.abs(parseTimeToSeconds(currentDisplay) - parseTimeToSeconds(expectedTime)) > 2) { learnTimer.textContent = expectedTime; console.log('🔄 时间已同步'); } }, 5000); // 每5秒检查一次 // ========== 第三部分:显示提示信息 ========== // 创建提示信息元素 function createNotification() { // 检查是否已经存在提示信息 if (document.getElementById('learningAssistantNotification')) { return; } // 等待 document.body 存在 if (!document.body) { setTimeout(createNotification, 100); return; } const notification = document.createElement('div'); notification.id = 'learningAssistantNotification'; notification.textContent = '📚 学习助手已经启动,实现标签多开和同时计时。'; // 设置样式 - 使用 !important 确保不被覆盖 notification.setAttribute('style', 'position: fixed !important; ' + 'bottom: 20px !important; ' + 'left: 20px !important; ' + 'background: #667eea !important; ' + 'color: white !important; ' + 'padding: 12px 20px !important; ' + 'border-radius: 8px !important; ' + 'font-size: 14px !important; ' + 'font-weight: bold !important; ' + 'box-shadow: 0 4px 15px rgba(0,0,0,0.2) !important; ' + 'z-index: 999999 !important; ' + 'cursor: pointer !important; ' + 'max-width: 350px !important; ' + 'font-family: Microsoft YaHei, Arial, sans-serif !important; ' + 'transition: all 0.3s ease !important; ' + 'opacity: 1 !important; ' + 'display: block !important; ' + 'visibility: visible !important;' ); // 点击关闭 notification.addEventListener('click', function() { this.style.opacity = '0'; this.style.transform = 'translateY(20px)'; setTimeout(() => { if (this.parentNode) { this.parentNode.removeChild(this); } }, 300); }); // 自动淡出(10秒后自动消失) setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateY(20px)'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, 10000); // 添加到页面 document.body.appendChild(notification); console.log('✅ 提示信息已显示'); } // 使用多种方式确保提示信息显示 function tryShowNotification() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { setTimeout(createNotification, 500); }); } else { setTimeout(createNotification, 500); } } // 执行显示 tryShowNotification(); console.log('🎯 脚本初始化完成!'); console.log('📌 功能1: 新标签页限制已解除'); console.log('📌 功能2: 多标签同时计时已启用'); console.log('📌 功能3: 左下角提示信息已显示'); })();