// ==UserScript== // @name 智能定时刷新器 // @namespace https://github.com/your-namespace // @version 1.0.1 // @description Material Design 3风格的网页定时刷新工具,支持多标签页管理和灵活配置 // @author Axuan 薛神 // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // @grant window.focus // @run-at document-end // ==/UserScript== (function() { 'use strict'; // Material Design 3 样式 GM_addStyle(` @import url('https://api.fonts.coollabs.io/css2?family=Roboto:wght@300;400;500;700&display=swap'); @import url('https://api.fonts.coollabs.io/icon?family=Material+Icons'); .refresh-controller { position: fixed; top: 20px; right: 20px; width: 320px; background: #fef7ff; border: 1px solid #e8def8; border-radius: 28px; box-shadow: 0 4px 8px 3px rgba(0,0,0,0.15), 0 1px 3px rgba(0,0,0,0.3); font-family: 'Roboto', sans-serif; z-index: 10000; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(10px); } .refresh-controller.minimized { width: 56px; height: 56px; border-radius: 28px; overflow: hidden; } .refresh-header { display: flex; align-items: center; padding: 16px 20px; background: linear-gradient(135deg, #6750a4 0%, #7c5dfa 100%); color: #ffffff; border-radius: 28px 28px 0 0; cursor: move; } .refresh-controller.minimized .refresh-header { border-radius: 28px; padding: 16px; justify-content: center; } .refresh-title { flex: 1; font-size: 16px; font-weight: 500; margin: 0; } .refresh-controller.minimized .refresh-title { display: none; } .toggle-btn { background: none; border: none; color: #ffffff; cursor: pointer; padding: 4px; border-radius: 12px; transition: background-color 0.2s; } .toggle-btn:hover { background: rgba(255,255,255,0.1); } .refresh-content { padding: 20px; display: block; } .refresh-controller.minimized .refresh-content { display: none; } .form-group { margin-bottom: 20px; } .form-label { display: block; font-size: 14px; font-weight: 500; color: #49454f; margin-bottom: 8px; } .form-input { width: 100%; padding: 12px 16px; border: 1px solid #79747e; border-radius: 12px; font-size: 14px; font-family: 'Roboto', sans-serif; background: #fef7ff; color: #1d1b20; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); box-sizing: border-box; } .form-input:focus { outline: none; border-color: #6750a4; box-shadow: 0 0 0 2px rgba(103, 80, 164, 0.2); } .form-input:disabled { background: #f7f2fa; color: #9a9999; border-color: #c4c7c5; } .switch-container { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; } .switch { position: relative; width: 52px; height: 32px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e8def8; transition: .3s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 16px; border: 2px solid #79747e; } .slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 4px; bottom: 4px; background-color: #79747e; transition: .3s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 50%; } input:checked + .slider { background-color: #6750a4; border-color: #6750a4; } input:checked + .slider:before { transform: translateX(20px); background-color: #ffffff; } .btn { padding: 6px 12px; border: none; border-radius: 20px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); font-family: 'Roboto', sans-serif; display: inline-flex; align-items: center; gap: 8px; } .btn-primary { background: #6750a4; color: #ffffff; } .btn-primary:hover { background: #5a47a1; box-shadow: 0 2px 4px 1px rgba(103, 80, 164, 0.3); } .btn-secondary { background: #e8def8; color: #6750a4; } .btn-secondary:hover { background: #ddd2ea; } .btn-danger { background: #b3261e; color: #ffffff; } .btn-danger:hover { background: #8c1d18; } .btn-row { display: flex; gap: 12px; margin-top: 20px; } .status-info { background: #e6f3ff; border: 1px solid #bbdefb; border-radius: 12px; padding: 12px; margin-bottom: 16px; font-size: 13px; color: #1565c0; } .progress-bar { width: 100%; height: 4px; background: #e8def8; border-radius: 2px; overflow: hidden; margin-top: 8px; } .progress-fill { height: 100%; background: linear-gradient(90deg, #6750a4, #7c5dfa); transition: width 0.1s linear; border-radius: 2px; } .tab-selector { margin-bottom: 16px; } .tab-option { display: flex; align-items: center; padding: 8px 12px; margin: 4px 0; border-radius: 8px; cursor: pointer; transition: background-color 0.2s; font-size: 13px; } .tab-option:hover { background: #f3eff4; } .tab-option.selected { background: #e8def8; color: #6750a4; } .tab-option input[type="radio"] { margin-right: 8px; } .material-icons { font-size: 13px; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } } .pulsing { animation: pulse 2s infinite; } `); class AutoRefreshController { constructor() { this.isActive = false; this.interval = null; this.currentCount = 0; this.settings = this.loadSettings(); this.isMinimized = GM_getValue('isMinimized', false); this.init(); } loadSettings() { return { refreshInterval: GM_getValue('refreshInterval', 30), maxRuns: GM_getValue('maxRuns', 0), targetTab: GM_getValue('targetTab', 'current'), isEnabled: GM_getValue('isEnabled', false) }; } saveSettings() { GM_setValue('refreshInterval', this.settings.refreshInterval); GM_setValue('maxRuns', this.settings.maxRuns); GM_setValue('targetTab', this.settings.targetTab); GM_setValue('isEnabled', this.settings.isEnabled); } init() { this.createUI(); this.bindEvents(); if (this.settings.isEnabled) { this.start(); } this.updateUI(); this.makeDraggable(); } createUI() { this.container = document.createElement('div'); this.container.className = 'refresh-controller'; if (this.isMinimized) { this.container.classList.add('minimized'); } this.container.innerHTML = `

智能定时刷新器

状态:已停止
启用自动刷新
tab 当前标签页
tab_unselected 所有标签页(同域名)
`; document.body.appendChild(this.container); } bindEvents() { // 最小化/展开按钮 const minimizeBtn = this.container.querySelector('#minimizeBtn'); minimizeBtn.addEventListener('click', () => this.toggleMinimize()); // 开关 const enableSwitch = this.container.querySelector('#enableSwitch'); enableSwitch.addEventListener('change', (e) => { this.settings.isEnabled = e.target.checked; this.saveSettings(); if (e.target.checked) { this.start(); } else { this.stop(); } }); // 输入框 const intervalInput = this.container.querySelector('#intervalInput'); intervalInput.addEventListener('change', (e) => { this.settings.refreshInterval = parseInt(e.target.value) || 30; this.saveSettings(); if (this.isActive) { this.restart(); } }); const maxRunsInput = this.container.querySelector('#maxRunsInput'); maxRunsInput.addEventListener('change', (e) => { this.settings.maxRuns = parseInt(e.target.value) || 0; this.saveSettings(); }); // 标签页选择 const tabOptions = this.container.querySelectorAll('input[name="targetTab"]'); tabOptions.forEach(option => { option.addEventListener('change', (e) => { this.settings.targetTab = e.target.value; this.saveSettings(); // 更新选中状态 this.container.querySelectorAll('.tab-option').forEach(tab => { tab.classList.remove('selected'); }); e.target.closest('.tab-option').classList.add('selected'); }); }); // 按钮 this.container.querySelector('#startBtn').addEventListener('click', () => this.start()); this.container.querySelector('#stopBtn').addEventListener('click', () => this.stop()); this.container.querySelector('#resetBtn').addEventListener('click', () => this.reset()); // 键盘快捷键 document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'R') { e.preventDefault(); this.toggle(); } }); } toggleMinimize() { this.isMinimized = !this.isMinimized; GM_setValue('isMinimized', this.isMinimized); this.container.classList.toggle('minimized', this.isMinimized); const minimizeBtn = this.container.querySelector('#minimizeBtn .material-icons'); minimizeBtn.textContent = this.isMinimized ? 'expand_more' : 'remove'; } makeDraggable() { const header = this.container.querySelector('.refresh-header'); let isDragging = false; let startX, startY, startLeft, startTop; header.addEventListener('mousedown', (e) => { if (e.target.closest('.toggle-btn')) return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = this.container.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); e.preventDefault(); }); const handleMouseMove = (e) => { if (!isDragging) return; const newLeft = startLeft + (e.clientX - startX); const newTop = startTop + (e.clientY - startY); this.container.style.left = Math.max(0, Math.min(window.innerWidth - this.container.offsetWidth, newLeft)) + 'px'; this.container.style.top = Math.max(0, Math.min(window.innerHeight - this.container.offsetHeight, newTop)) + 'px'; this.container.style.right = 'auto'; }; const handleMouseUp = () => { isDragging = false; document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; } start() { if (this.isActive) return; this.isActive = true; this.currentCount = 0; this.settings.isEnabled = true; this.saveSettings(); const enableSwitch = this.container.querySelector('#enableSwitch'); enableSwitch.checked = true; this.scheduleNextRefresh(); this.updateUI(); } stop() { this.isActive = false; this.settings.isEnabled = false; this.saveSettings(); const enableSwitch = this.container.querySelector('#enableSwitch'); enableSwitch.checked = false; if (this.interval) { clearTimeout(this.interval); this.interval = null; } this.updateUI(); } restart() { this.stop(); setTimeout(() => this.start(), 100); } reset() { this.stop(); this.currentCount = 0; this.updateUI(); } toggle() { if (this.isActive) { this.stop(); } else { this.start(); } } scheduleNextRefresh() { if (!this.isActive) return; const intervalMs = this.settings.refreshInterval * 1000; let timeLeft = intervalMs; // 更新进度条 const updateProgress = () => { if (!this.isActive) return; const progress = ((intervalMs - timeLeft) / intervalMs) * 100; const progressFill = this.container.querySelector('#progressFill'); if (progressFill) { progressFill.style.width = progress + '%'; } timeLeft -= 100; if (timeLeft > 0) { setTimeout(updateProgress, 100); } }; updateProgress(); this.interval = setTimeout(() => { this.performRefresh(); }, intervalMs); } performRefresh() { if (!this.isActive) return; this.currentCount++; if (this.settings.targetTab === 'current') { window.location.reload(); } else if (this.settings.targetTab === 'all') { // 对于所有标签页模式,我们刷新当前页面 // 注意:由于安全限制,用户脚本无法直接操作其他标签页 window.location.reload(); } // 检查是否达到最大运行次数 if (this.settings.maxRuns > 0 && this.currentCount >= this.settings.maxRuns) { this.stop(); return; } // 继续下一次刷新 this.scheduleNextRefresh(); this.updateUI(); } updateUI() { const statusInfo = this.container.querySelector('#statusInfo'); const startBtn = this.container.querySelector('#startBtn'); const stopBtn = this.container.querySelector('#stopBtn'); const progressFill = this.container.querySelector('#progressFill'); if (this.isActive) { const maxText = this.settings.maxRuns > 0 ? ` / ${this.settings.maxRuns}` : ''; statusInfo.innerHTML = ` 状态:运行中 | 已刷新:${this.currentCount}${maxText} 次 | 间隔:${this.settings.refreshInterval}秒
`; statusInfo.classList.add('pulsing'); startBtn.disabled = true; stopBtn.disabled = false; } else { const maxText = this.settings.maxRuns > 0 ? ` / ${this.settings.maxRuns}` : ''; statusInfo.innerHTML = ` 状态:已停止 | 总计刷新:${this.currentCount}${maxText} 次
`; statusInfo.classList.remove('pulsing'); startBtn.disabled = false; stopBtn.disabled = true; if (progressFill) { progressFill.style.width = '0%'; } } } } // 等待页面完全加载后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { new AutoRefreshController(); }); } else { new AutoRefreshController(); } })();