// ==UserScript== // @name 高级标签页跳转器 // @namespace https://docs.scriptcat.org/ // @version 1.1 // @description 自动循环跳转预设网页,支持运行状态下自动最小化、编辑锁定、保存提醒及 Alt+M/R 快捷键控制。 // @tag 标签页 自动跳转 面板操作 主题适应 // @author yangwenren // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // --- 状态初始化 --- const DEFAULT_URLS = []; let state = { urls: GM_getValue('cycle_urls', DEFAULT_URLS), interval: GM_getValue('cycle_interval', 30), isRunning: GM_getValue('is_running', false), currentIndex: GM_getValue('current_index', 0), isMinimized: GM_getValue('is_running', false) ? true : GM_getValue('is_minimized', false), isHidden: false, expandedPos: GM_getValue('expanded_pos', { x: 20, y: 20 }), minimizedPos: { x: 20, y: 20 }, timer: null }; // --- 样式注入 (支持自适应暗色模式) --- GM_addStyle(` :root { --jump-bg: rgba(255, 255, 255, 0.9); --jump-text: #333333; --jump-border: rgba(0, 0, 0, 0.1); --jump-input-bg: #ffffff; --jump-input-disabled: #f5f5f5; --jump-header-bg: rgba(0, 0, 0, 0.03); --jump-shadow: rgba(0, 0, 0, 0.15); } @media (prefers-color-scheme: dark) { :root { --jump-bg: rgba(30, 30, 30, 0.9); --jump-text: #e0e0e0; --jump-border: rgba(255, 255, 255, 0.2); --jump-input-bg: #2d2d2d; --jump-input-disabled: #1a1a1a; --jump-header-bg: rgba(255, 255, 255, 0.05); --jump-shadow: rgba(0, 0, 0, 0.5); } } #jump-panel { position: fixed; z-index: 2147483647; background: var(--jump-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid var(--jump-border); border-radius: 12px; box-shadow: 0 8px 32px var(--jump-shadow); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; width: 240px; display: flex; flex-direction: column; user-select: none; overflow: hidden; color: var(--jump-text); transition: width 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.2), height 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.2), bottom 0.4s ease, right 0.4s ease, opacity 0.3s ease; } #jump-panel.minimized { width: 56px !important; height: 56px !important; border-radius: 28px !important; cursor: pointer; border-color: #1890ff; } #jump-panel.minimized.running { border-color: #ff4d4f; } .panel-header { background: var(--jump-header-bg); padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; cursor: move; border-bottom: 1px solid var(--jump-border); } .panel-title { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; } .minimized .panel-header, .minimized .panel-content { display: none !important; } .status-container { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; width: 100%; } .run-dot { width: 8px; height: 8px; border-radius: 50%; background: #888; } .run-dot.active { background: #ff4d4f; box-shadow: 0 0 8px #ff4d4f; animation: breathe 1.5s infinite; } .status-text { font-size: 10px; font-weight: 900; color: #888; margin-top: 2px; } .status-text.active { color: #ff4d4f; } @keyframes breathe { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.3); opacity: 0.7; } } .panel-content { padding: 12px; display: flex; flex-direction: column; gap: 8px; } textarea, input { background: var(--jump-input-bg); color: var(--jump-text); border: 1px solid var(--jump-border); border-radius: 6px; } textarea { width: 100%; height: 90px; font-size: 12px; padding: 8px; resize: none; outline: none; } textarea:disabled, input:disabled { background: var(--jump-input-disabled); color: #888; cursor: not-allowed; } .control-row { display: flex; justify-content: space-between; align-items: center; font-size: 12px; } input[type="number"] { width: 50px; padding: 3px; text-align: center; outline: none; } button { border: none; border-radius: 6px; cursor: pointer; font-weight: bold; width: 100%; transition: all 0.2s; } #save-btn { padding: 8px; background: #52c41a; color: white; font-size: 12px; display: none; margin-bottom: 2px; } #save-btn.show { display: block; animation: slideDown 0.3s ease; } #start-btn { padding: 10px; background: #1890ff; color: white; } #start-btn.running { background: #ff4d4f; } @keyframes slideDown { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } } .header-btns button { background: none; color: var(--jump-text); font-size: 18px; padding: 0 4px; width: auto; opacity: 0.6; } .header-btns button:hover { opacity: 1; } `); const panel = document.createElement('div'); panel.id = 'jump-panel'; document.body.appendChild(panel); const updatePosition = (useAnimation = true) => { panel.style.transition = useAnimation ? "" : "none"; const targetPos = state.isMinimized ? state.minimizedPos : state.expandedPos; panel.style.right = targetPos.x + 'px'; panel.style.bottom = targetPos.y + 'px'; }; const render = () => { panel.style.display = state.isHidden ? 'none' : 'flex'; panel.style.opacity = state.isHidden ? '0' : '1'; updatePosition(true); if (state.isMinimized) { panel.classList.add('minimized'); panel.innerHTML = `
${state.isRunning ? 'RUN' : 'OFF'}
`; } else { panel.classList.remove('minimized'); const isDisabled = state.isRunning ? 'disabled' : ''; panel.innerHTML = `
页面跳转器
间隔 (秒):
`; } bindEvents(); }; function executeJump() { if (!state.isRunning || state.urls.length === 0) return; let nextIndex = (state.currentIndex + 1) % state.urls.length; GM_setValue('current_index', nextIndex); window.location.href = state.urls[nextIndex]; } function bindEvents() { if (state.isMinimized) { panel.onclick = (e) => { if (!panel.hasAttribute('dragging')) toggleMin(); panel.removeAttribute('dragging'); }; } else { panel.onclick = null; const urlTextarea = document.getElementById('url-list'); const saveBtn = document.getElementById('save-btn'); const startBtn = document.getElementById('start-btn'); const timeInput = document.getElementById('time-input'); if (!state.isRunning) { urlTextarea.oninput = () => { saveBtn.classList.toggle('show', urlTextarea.value.trim() !== state.urls.join('\n').trim()); }; saveBtn.onclick = () => { state.urls = urlTextarea.value.split('\n').filter(u => u.trim() !== ''); GM_setValue('cycle_urls', state.urls); saveBtn.classList.remove('show'); }; timeInput.onchange = (e) => { state.interval = parseInt(e.target.value) || 30; GM_setValue('cycle_interval', state.interval); }; } startBtn.onclick = () => { if (!state.isRunning) { const currentUrls = urlTextarea.value.split('\n').filter(u => u.trim() !== ''); if (currentUrls.length === 0) return alert("请先输入网址!"); state.urls = currentUrls; state.interval = parseInt(timeInput.value) || 30; state.isRunning = true; state.isMinimized = true; } else { state.isRunning = false; if (state.timer) clearTimeout(state.timer); } GM_setValue('is_running', state.isRunning); GM_setValue('cycle_urls', state.urls); GM_setValue('cycle_interval', state.interval); GM_setValue('is_minimized', state.isMinimized); render(); if (state.isRunning) setTimeout(executeJump, 300); }; document.getElementById('min-btn').onclick = (e) => { e.stopPropagation(); toggleMin(); }; document.getElementById('hide-btn').onclick = (e) => { e.stopPropagation(); toggleHide(); }; } const dragHandle = state.isMinimized ? panel : document.getElementById('panel-drag-handle'); if (dragHandle) { dragHandle.onmousedown = function(e) { if (['BUTTON', 'TEXTAREA', 'INPUT'].includes(e.target.tagName)) return; let isMove = false; const startX = e.clientX, startY = e.clientY; const initialRight = state.isMinimized ? state.minimizedPos.x : state.expandedPos.x; const initialBottom = state.isMinimized ? state.minimizedPos.y : state.expandedPos.y; panel.style.transition = "none"; document.onmousemove = function(ev) { if (Math.abs(ev.clientX - startX) > 5 || Math.abs(ev.clientY - startY) > 5) { isMove = true; const nx = initialRight + (startX - ev.clientX); const ny = initialBottom + (startY - ev.clientY); panel.style.right = nx + 'px'; panel.style.bottom = ny + 'px'; } }; document.onmouseup = function(ev) { document.onmousemove = document.onmouseup = null; panel.style.transition = ""; if (isMove) { panel.setAttribute('dragging', 'true'); const finalPos = { x: initialRight + (startX - ev.clientX), y: initialBottom + (startY - ev.clientY) }; if (state.isMinimized) state.minimizedPos = finalPos; else state.expandedPos = finalPos; GM_setValue(state.isMinimized ? 'minimized_pos' : 'expanded_pos', finalPos); } }; }; } } function toggleMin() { state.isMinimized = !state.isMinimized; GM_setValue('is_minimized', state.isMinimized); render(); } function toggleHide() { state.isHidden = !state.isHidden; render(); } window.addEventListener('keydown', (e) => { if (e.altKey && e.code === 'KeyM') { e.preventDefault(); toggleMin(); } if (e.altKey && e.code === 'KeyR') { e.preventDefault(); toggleHide(); } }); render(); if (state.isRunning) { state.timer = setTimeout(executeJump, state.interval * 1000); } })();