网页定时刷新

网页定时刷新工具

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         网页定时刷新
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  网页定时刷新工具
// @author       huihuia24
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @noframes
// @homepage     https://github.com/huihuia24
// @source       https://github.com/huihuia24/Web-page-auto-refresh
// @icon         https://pic.uzzf.com/up/2025-5/2025591617247637.png
// ==/UserScript==
(function() {
    'use strict';

    // 单实例防护
    const UI_ID = 'realTimeRefreshTool';
    if (document.getElementById(UI_ID)) return;
    
    const SITE_KEY = window.location.hostname;
    const INSTANCE_KEY = `${SITE_KEY}_refresh_instance`;
    if (GM_getValue(INSTANCE_KEY, false)) return;
    GM_setValue(INSTANCE_KEY, true);

    // 状态存储
    const STATE_KEY = {
        isEnabled: `${SITE_KEY}_is_enabled`,
        interval: `${SITE_KEY}_interval`,
        unit: `${SITE_KEY}_unit`,
        lastStartTime: `${SITE_KEY}_last_start`,
        position: `${SITE_KEY}_ui_position`
    };

    // 恢复状态
    let isEnabled = GM_getValue(STATE_KEY.isEnabled, false);
    let currentUnit = GM_getValue(STATE_KEY.unit, 's');
    let intervalSeconds = GM_getValue(STATE_KEY.interval, 60);
    let lastStartTime = GM_getValue(STATE_KEY.lastStartTime, Date.now());
    let remainingSeconds = 0;
    let timer = null;
    let isDragging = false;
    let offsetX, offsetY;
    let animationFrameId = null;

    // 计算剩余时间
    function calculateRemaining() {
        if (!isEnabled) return intervalSeconds;
        const now = Date.now();
        const elapsed = Math.floor((now - lastStartTime) / 1000);
        return Math.max(0, intervalSeconds - elapsed);
    }
    remainingSeconds = calculateRemaining();

    // 单位转换
    function convertToSeconds(value, unit) {
        switch(unit) {
            case 'h': return value * 3600;
            case 'm': return value * 60;
            case 's': default: return value;
        }
    }
    function convertFromSeconds(seconds, unit) {
        switch(unit) {
            case 'h': return Math.floor(seconds / 3600);
            case 'm': return Math.floor(seconds / 60);
            case 's': default: return seconds;
        }
    }
    function formatTime(seconds) {
        if (seconds < 60) return `${seconds}s`;
        const m = Math.floor(seconds / 60);
        const s = seconds % 60;
        return `${m}m${s}s`;
    }

    // 创建UI(确保时长实时生效)
    function createUI() {
        // 主容器
        const container = document.createElement('div');
        container.id = UI_ID;
        container.style.cssText = `
            position: fixed;
            background: white;
            border: 1px solid #e2e8f0;
            border-radius: 12px;
            padding: 12px 16px;
            box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
            z-index: 99999;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 12px;
            user-select: none;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            will-change: transform, box-shadow;
        `;

        // 全局悬停效果
        container.addEventListener('mouseenter', () => {
            if (!isDragging) {
                container.style.transform = 'translateY(-2px)';
                container.style.boxShadow = isEnabled
                    ? '0 8px 24px rgba(239, 68, 68, 0.2)'
                    : '0 8px 24px rgba(99, 102, 241, 0.2)';
            }
        });
        container.addEventListener('mouseleave', () => {
            if (!isDragging) {
                container.style.transform = 'translateY(0)';
                container.style.boxShadow = '0 6px 18px rgba(0, 0, 0, 0.08)';
            }
        });

        // 恢复位置
        const savedPos = GM_getValue(STATE_KEY.position, { top: '20px', left: '20px' });
        container.style.top = savedPos.top;
        container.style.left = savedPos.left;

        // 图标区域
        const iconArea = document.createElement('div');
        iconArea.style.cssText = `
            width: 32px;
            height: 32px;
            border-radius: 8px;
            background: linear-gradient(135deg, #6366f1, #8b5cf6);
            display: flex;
            align-items: center;
            justify-content: center;
        `;
        iconArea.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" width="18" height="18">
                <path d="M23 4v6h-6"/>
                <path d="M1 20v-6h6"/>
                <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
            </svg>
        `;

        // 标题
        const title = document.createElement('span');
        title.textContent = '定时刷新 =';
        title.style.fontWeight = '500';
        title.style.color = '#1e293b';

        // 输入框(实时生效修复)
        const input = document.createElement('input');
        input.type = 'number';
        input.value = convertFromSeconds(intervalSeconds, currentUnit);
        input.min = currentUnit === 's' ? 5 : 1;
        input.style.cssText = `
            width: 50px;
            padding: 6px 10px;
            border: 1px solid #e2e8f0;
            border-radius: 8px;
            font-size: 14px;
            outline: none;
            transition: border-color 0.2s ease;
            cursor: text;
        `;
        
        // 关键修复:使用input事件实时更新,而非仅依赖change事件
        input.addEventListener('input', function() {
            this.value = this.value.replace(/[^0-9]/g, '');
            if (this.value.trim() === '') return;
            
            const value = Math.max(parseInt(this.value, 10) || 1, parseInt(this.min, 10));
            const newInterval = convertToSeconds(value, currentUnit);
            
            // 实时更新状态
            intervalSeconds = newInterval;
            GM_setValue(STATE_KEY.interval, intervalSeconds);
            
            // 正在运行时立即更新剩余时间
            if (isEnabled) {
                remainingSeconds = intervalSeconds;
                uiElements.statusText.textContent = `(剩余: ${formatTime(remainingSeconds)})`;
                restartTimer(); // 立即重启计时器应用新时长
            } else {
                remainingSeconds = intervalSeconds;
                uiElements.statusText.textContent = `(剩余: ${formatTime(remainingSeconds)})`;
            }
        });
        
        input.addEventListener('focus', () => {
            input.style.borderColor = '#a5b4fc';
            container.style.userSelect = 'text';
        });
        input.addEventListener('blur', () => {
            input.style.borderColor = '#e2e8f0';
            container.style.userSelect = 'none';
            // 失焦时确保值有效
            if (input.value.trim() === '') {
                input.value = convertFromSeconds(intervalSeconds, currentUnit);
            }
        });

        // 单位选择(实时生效修复)
        const unitSelect = document.createElement('select');
        unitSelect.style.cssText = `
            padding: 6px 10px;
            border: 1px solid #e2e8f0;
            border-radius: 8px;
            background: white;
            font-size: 14px;
            color: #475569;
            cursor: pointer;
            outline: none;
            appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' width='12' height='12'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 10px center;
            background-size: 12px;
            transition: border-color 0.2s ease;
        `;
        ['s', 'm', 'h'].forEach(unit => {
            const option = document.createElement('option');
            option.value = unit;
            option.textContent = { s: '秒', m: '分', h: '时' }[unit];
            option.selected = unit === currentUnit;
            unitSelect.appendChild(option);
        });
        
        // 关键修复:单位切换后立即更新并应用
        unitSelect.addEventListener('change', function() {
            const oldUnit = currentUnit;
            currentUnit = this.value;
            
            // 计算新单位下的数值
            const minValue = currentUnit === 's' ? 5 : 1;
            let newValue = convertFromSeconds(intervalSeconds, currentUnit);
            newValue = Math.max(newValue, minValue);
            
            // 更新输入框
            input.value = newValue;
            input.min = minValue;
            
            // 实时更新状态
            GM_setValue(STATE_KEY.unit, currentUnit);
            
            // 正在运行时立即应用新单位
            if (isEnabled) {
                // 重新计算剩余时间(基于新单位转换后的总时长)
                intervalSeconds = convertToSeconds(newValue, currentUnit);
                GM_setValue(STATE_KEY.interval, intervalSeconds);
                remainingSeconds = intervalSeconds;
                uiElements.statusText.textContent = `(剩余: ${formatTime(remainingSeconds)})`;
                restartTimer(); // 立即重启计时器
            } else {
                intervalSeconds = convertToSeconds(newValue, currentUnit);
                GM_setValue(STATE_KEY.interval, intervalSeconds);
                remainingSeconds = intervalSeconds;
                uiElements.statusText.textContent = `(剩余: ${formatTime(remainingSeconds)})`;
            }
        });
        
        unitSelect.addEventListener('focus', () => {
            unitSelect.style.borderColor = '#a5b4fc';
        });
        unitSelect.addEventListener('blur', () => {
            unitSelect.style.borderColor = '#e2e8f0';
        });

        // 控制按钮
        const controlBtn = document.createElement('button');
        controlBtn.style.cssText = `
            display: flex;
            align-items: center;
            gap: 6px;
            padding: 8px 16px;
            border: none;
            border-radius: 8px;
            color: white;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            background: ${isEnabled ? 'linear-gradient(135deg, #f87171, #ef4444)' : 'linear-gradient(135deg, #6366f1, #8b5cf6)'};
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        `;
        controlBtn.innerHTML = isEnabled 
            ? `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" width="16" height="16">
                    <rect x="6" y="4" width="4" height="16"/>
                    <rect x="14" y="4" width="4" height="16"/>
                </svg>
                停止
            ` 
            : `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" width="16" height="16">
                    <polygon points="5 3 19 12 5 21 5 3"/>
                </svg>
                启动
            `;

        // 状态显示
        const statusText = document.createElement('span');
        statusText.textContent = isEnabled ? `(剩余: ${formatTime(remainingSeconds)})` : '(已停止)';
        statusText.style.cssText = `
            color: #64748b;
            font-size: 13px;
            min-width: 90px;
            text-align: center;
            font-family: monospace;
        `;

        // 组装UI
        container.appendChild(iconArea);
        container.appendChild(title);
        container.appendChild(input);
        container.appendChild(unitSelect);
        container.appendChild(controlBtn);
        container.appendChild(statusText);
        document.body.appendChild(container);

        // 拖拽功能
        container.addEventListener('mousedown', (e) => {
            if (e.target === input || e.target === unitSelect) {
                return;
            }
            
            isDragging = true;
            const rect = container.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            container.style.transition = 'none';
            container.style.zIndex = '999999';
            container.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.15)';
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            
            if (animationFrameId) cancelAnimationFrame(animationFrameId);
            animationFrameId = requestAnimationFrame(() => {
                const newLeft = e.clientX - offsetX;
                const newTop = e.clientY - offsetY;
                const maxLeft = window.innerWidth - container.offsetWidth - 20;
                const maxTop = window.innerHeight - container.offsetHeight - 20;
                
                container.style.left = `${Math.max(20, Math.min(newLeft, maxLeft))}px`;
                container.style.top = `${Math.max(20, Math.min(newTop, maxTop))}px`;
            });
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                if (animationFrameId) {
                    cancelAnimationFrame(animationFrameId);
                    animationFrameId = null;
                }
                container.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
                container.style.zIndex = '99999';
                container.style.boxShadow = '0 6px 18px rgba(0, 0, 0, 0.08)';
                GM_setValue(STATE_KEY.position, {
                    top: container.style.top,
                    left: container.style.left
                });
            }
        });

        // 控制按钮事件
        controlBtn.addEventListener('click', () => {
            if (isEnabled) {
                stopTimer();
                controlBtn.innerHTML = `
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" width="16" height="16">
                        <polygon points="5 3 19 12 5 21 5 3"/>
                    </svg>
                    启动
                `;
                controlBtn.style.background = 'linear-gradient(135deg, #6366f1, #8b5cf6)';
                statusText.textContent = '(已停止)';
                container.dispatchEvent(new Event('mouseleave'));
                container.dispatchEvent(new Event('mouseenter'));
            } else {
                startTimer();
                controlBtn.innerHTML = `
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" width="16" height="16">
                        <rect x="6" y="4" width="4" height="16"/>
                        <rect x="14" y="4" width="4" height="16"/>
                    </svg>
                    停止
                `;
                controlBtn.style.background = 'linear-gradient(135deg, #f87171, #ef4444)';
                container.dispatchEvent(new Event('mouseleave'));
                container.dispatchEvent(new Event('mouseenter'));
            }
        });

        return { container, controlBtn, statusText, input, unitSelect, iconArea };
    }

    // 计时器控制(确保立即应用新时长)
    function startTimer() {
        isEnabled = true;
        lastStartTime = Date.now();
        // 强制使用当前设置的时长
        remainingSeconds = intervalSeconds;
        GM_setValue(STATE_KEY.isEnabled, true);
        GM_setValue(STATE_KEY.lastStartTime, lastStartTime);
        GM_setValue(STATE_KEY.interval, intervalSeconds); // 确保存储最新值

        if (timer) clearInterval(timer);
        timer = setInterval(() => {
            remainingSeconds--;
            uiElements.statusText.textContent = `(剩余: ${formatTime(remainingSeconds)})`;

            if (remainingSeconds <= 0) {
                clearInterval(timer);
                GM_setValue(STATE_KEY.lastStartTime, Date.now());
                window.location.reload();
            }
        }, 1000);
    }

    function stopTimer() {
        if (timer) clearInterval(timer);
        timer = null;
        isEnabled = false;
        GM_setValue(STATE_KEY.isEnabled, false);
    }

    // 关键修复:重启计时器时强制使用最新设置的时长
    function restartTimer() {
        if (timer) clearInterval(timer);
        lastStartTime = Date.now();
        remainingSeconds = intervalSeconds; // 使用最新的时长
        GM_setValue(STATE_KEY.lastStartTime, lastStartTime);
        GM_setValue(STATE_KEY.interval, intervalSeconds); // 确保存储最新值
        
        timer = setInterval(() => {
            remainingSeconds--;
            uiElements.statusText.textContent = `(剩余: ${formatTime(remainingSeconds)})`;

            if (remainingSeconds <= 0) {
                clearInterval(timer);
                GM_setValue(STATE_KEY.lastStartTime, Date.now());
                window.location.reload();
            }
        }, 1000);
    }

    // 初始化
    const uiElements = createUI();

    // 自动启动
    if (isEnabled) {
        startTimer();
    }

    // 页面卸载清理
    window.addEventListener('beforeunload', () => {
        GM_setValue(INSTANCE_KEY, false);
    });
})();