// ==UserScript== // @name 0501快捷跳转·圆形按钮版 + 批量导入(跨站统一存储) // @namespace http://tampermonkey.net/ // @version 14.0 // @description 圆形站点按钮,支持收藏当前页、批量导入、导出全部,跨域名统一存储(GM存储) // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @run-at document-idle // @downloadURL https://update.greasyfork.icu/scripts/576177/0501%E5%BF%AB%E6%8D%B7%E8%B7%B3%E8%BD%AC%C2%B7%E5%9C%86%E5%BD%A2%E6%8C%89%E9%92%AE%E7%89%88%20%2B%20%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%EF%BC%88%E8%B7%A8%E7%AB%99%E7%BB%9F%E4%B8%80%E5%AD%98%E5%82%A8%EF%BC%89.user.js // @updateURL https://update.greasyfork.icu/scripts/576177/0501%E5%BF%AB%E6%8D%B7%E8%B7%B3%E8%BD%AC%C2%B7%E5%9C%86%E5%BD%A2%E6%8C%89%E9%92%AE%E7%89%88%20%2B%20%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%EF%BC%88%E8%B7%A8%E7%AB%99%E7%BB%9F%E4%B8%80%E5%AD%98%E5%82%A8%EF%BC%89.meta.js // ==/UserScript== (function() { 'use strict'; // 存储键名 const SITE_KEY = "quick_nav_all_sites_circle_global"; const POS_KEY = "quick_nav_panel_position_circle"; const FOLD_KEY = "quick_nav_folded_circle"; // 默认示例网站 const DEFAULT_SITES = [ { name: "B站", url: "https://www.bilibili.com/" }, { name: "X", url: "https://x.com/" }, { name: "油管", url: "https://www.youtube.com/" }, { name: "P站", url: "https://www.pinterest.com/" }, { name: "TikTok", url: "https://www.tiktok.com/" }, { name: "Kimi", url: "https://kimi.moonshot.cn/" }, { name: "豆包", url: "https://www.doubao.com/" }, { name: "百度", url: "https://www.baidu.com/" }, { name: "知乎", url: "https://www.zhihu.com/" } ]; // ---------- 全局存储读写(跨站统一)---------- function getSiteList() { let stored = GM_getValue(SITE_KEY, null); if (stored === null) { // 首次运行:尝试从 localStorage 迁移数据 try { const oldData = localStorage.getItem("quick_nav_all_sites_circle"); if (oldData) { const parsed = JSON.parse(oldData); if (Array.isArray(parsed)) { stored = parsed; GM_setValue(SITE_KEY, stored); // 可选:清除旧 localStorage 避免混乱(但保留也无害) localStorage.removeItem("quick_nav_all_sites_circle"); } } } catch(e) {} // 如果仍然为空,使用默认列表 if (!stored) stored = [...DEFAULT_SITES]; } return Array.isArray(stored) ? stored : [...DEFAULT_SITES]; } function saveSiteList(arr) { GM_setValue(SITE_KEY, arr); } // 位置读写(使用 localStorage 也可以,因为位置不要求跨站统一,但为了方便依然用 GM 存储) function getPos() { const pos = GM_getValue(POS_KEY, null); if (pos) return pos; return { left: 20, top: 320 }; } function savePos(left, top) { GM_setValue(POS_KEY, { left, top }); } // 折叠状态 function getFolded() { return GM_getValue(FOLD_KEY, false); } function setFolded(val) { GM_setValue(FOLD_KEY, val); } // 防重复创建面板 if (document.getElementById('quick-nav-circle')) return; // 创建面板(样式和之前几乎一致,只调整了按钮字体大小等细节) const root = document.createElement('div'); root.id = 'quick-nav-circle'; const initPos = getPos(); root.style.cssText = ` all: initial; position: fixed; z-index: 9999998; left: ${initPos.left}px; top: ${initPos.top}px; width: 280px; font-family: system-ui, sans-serif; user-select: none; `; document.body.appendChild(root); const bar = document.createElement('div'); bar.style.cssText = ` background: #ff4081; color: #fff; padding: 8px 12px; border-radius: 10px 10px 0 0; font-size: 13px; font-weight: 500; display: flex; justify-content: space-between; align-items: center; cursor: move; `; bar.innerHTML = `⚡ 快捷跳转`; root.appendChild(bar); const panel = document.createElement('div'); panel.style.cssText = ` background: rgba(255,255,255,0.95); backdrop-filter: blur(8px); border-radius: 0 0 12px 12px; padding: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-height: 400px; overflow-y: auto; `; root.appendChild(panel); const circleGrid = document.createElement('div'); circleGrid.style.cssText = ` display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-bottom: 15px; padding-bottom: 8px; border-bottom: 1px solid #eee; `; panel.appendChild(circleGrid); const toolArea = document.createElement('div'); toolArea.style.cssText = ` display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; `; panel.appendChild(toolArea); // 渲染圆形按钮 function renderCircles() { circleGrid.innerHTML = ''; const sites = getSiteList(); if (sites.length === 0) { const hint = document.createElement('div'); hint.textContent = '暂无站点,点击下方“收藏本页”或“批量导入”'; hint.style.cssText = 'width:100%; text-align:center; color:#999; font-size:12px; padding:10px;'; circleGrid.appendChild(hint); } else { sites.forEach((site, idx) => { const btnWrap = document.createElement('div'); btnWrap.style.position = 'relative'; const btn = document.createElement('button'); let displayName = site.name; if (displayName.length > 4) displayName = displayName.slice(0,3)+'..'; btn.textContent = displayName; btn.title = `${site.name}\n${site.url}`; btn.style.cssText = ` width: 60px; height: 60px; border-radius: 50%; background: #f0f2f5; border: none; color: #1e2a3a; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; text-align: center; line-height: 1.2; padding: 0 4px; `; btn.onmouseenter = () => btn.style.background = '#ffe0e7'; btn.onmouseleave = () => btn.style.background = '#f0f2f5'; btn.onclick = () => window.open(site.url, '_blank'); const delBtn = document.createElement('span'); delBtn.textContent = '×'; delBtn.style.cssText = ` position: absolute; top: -6px; right: -6px; background: #ff4d4f; color: white; border-radius: 50%; width: 18px; height: 18px; font-size: 12px; line-height: 18px; text-align: center; cursor: pointer; display: none; font-weight: bold; box-shadow: 0 1px 2px rgba(0,0,0,0.2); `; btnWrap.onmouseenter = () => delBtn.style.display = 'block'; btnWrap.onmouseleave = () => delBtn.style.display = 'none'; delBtn.onclick = (e) => { e.stopPropagation(); const current = getSiteList(); current.splice(idx, 1); saveSiteList(current); renderCircles(); tip('已删除'); }; btnWrap.appendChild(btn); btnWrap.appendChild(delBtn); circleGrid.appendChild(btnWrap); }); } } function tip(msg) { const t = document.createElement('div'); t.textContent = msg; t.style.cssText = ` position:fixed; bottom:80px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.7); color:#fff; padding:6px 12px; border-radius:20px; font-size:12px; z-index:999999; pointer-events:none; `; document.body.appendChild(t); setTimeout(() => t.remove(), 1500); } function buildTools() { // 收藏当前页面(跨站统一) const collectBtn = document.createElement('button'); collectBtn.textContent = '⭐ 收藏本页'; collectBtn.style.cssText = `flex:1; padding:6px; background:#22c55e; border:none; border-radius:20px; color:#fff; cursor:pointer; font-size:12px;`; collectBtn.onclick = () => { let name = document.title.trim(); if (name.length > 20) name = name.slice(0,17)+'…'; if (!name) name = location.hostname; const url = location.href; const sites = getSiteList(); // 可选去重(基于 URL 完全匹配) const exists = sites.some(s => s.url === url); if (exists) { tip('当前页面已在收藏列表中'); return; } sites.push({ name, url }); saveSiteList(sites); renderCircles(); tip(`已收藏:${name}`); }; // 批量导入(同上,但存储到 GM) const importBtn = document.createElement('button'); importBtn.textContent = '📥 批量导入'; importBtn.style.cssText = `flex:1; padding:6px; background:#8c6cf7; border:none; border-radius:20px; color:#fff; cursor:pointer; font-size:12px;`; importBtn.onclick = () => { const input = prompt( "请粘贴网站列表(支持两种格式):\n\n" + "1. JSON 数组: [{\"name\":\"站点名\",\"url\":\"https://...\"}, ...]\n" + "2. 纯文本,每行格式:站点名|https://...\n\n" + "示例:\n豆瓣|https://www.douban.com/\nGitHub|https://github.com/" ); if (!input) return; let newSites = []; try { const parsed = JSON.parse(input); if (Array.isArray(parsed) && parsed.every(p => p.name && p.url)) { newSites = parsed; } else throw new Error(); } catch(e) { const lines = input.split(/\r?\n/); for (let line of lines) { line = line.trim(); if (!line) continue; const sep = line.includes('|') ? '|' : (line.includes(',') ? ',' : null); if (sep) { let [name, url] = line.split(sep); if (name && url && url.startsWith('http')) { newSites.push({ name: name.trim(), url: url.trim() }); } else { tip(`跳过无效行: ${line.substring(0,30)}`); } } else { tip(`格式错误: ${line}`); } } } if (newSites.length === 0) { tip('没有有效站点被导入'); return; } const current = getSiteList(); const merged = [...current, ...newSites]; const unique = []; const urlSet = new Set(); for (let s of merged) { if (!urlSet.has(s.url)) { urlSet.add(s.url); unique.push(s); } } saveSiteList(unique); renderCircles(); tip(`成功导入 ${newSites.length} 个站点,去重后共 ${unique.length} 个`); }; // 导出全部 const exportBtn = document.createElement('button'); exportBtn.textContent = '📋 导出全部'; exportBtn.style.cssText = `flex:1; padding:6px; background:#1677ff; border:none; border-radius:20px; color:#fff; cursor:pointer; font-size:12px;`; exportBtn.onclick = () => { const sites = getSiteList(); const jsonStr = JSON.stringify(sites, null, 2); navigator.clipboard.writeText(jsonStr).then(() => { tip(`已复制 ${sites.length} 个站点的配置到剪贴板`); }).catch(() => tip('复制失败')); }; // 清空全部 const clearBtn = document.createElement('button'); clearBtn.textContent = '🗑 清空'; clearBtn.style.cssText = `flex:1; padding:6px; background:#f97316; border:none; border-radius:20px; color:#fff; cursor:pointer; font-size:12px;`; clearBtn.onclick = () => { if (confirm('确定清空所有站点吗?默认示例也会被清除。')) { saveSiteList([]); renderCircles(); tip('已清空'); } }; toolArea.innerHTML = ''; toolArea.appendChild(collectBtn); toolArea.appendChild(importBtn); toolArea.appendChild(exportBtn); toolArea.appendChild(clearBtn); } // 折叠逻辑 const foldSpan = bar.querySelector('#circle-fold'); if (getFolded()) { panel.style.display = 'none'; foldSpan.textContent = '+'; } else { panel.style.display = 'block'; foldSpan.textContent = '−'; } foldSpan.onclick = (e) => { e.stopPropagation(); if (panel.style.display === 'none') { panel.style.display = 'block'; foldSpan.textContent = '−'; setFolded(false); } else { panel.style.display = 'none'; foldSpan.textContent = '+'; setFolded(true); } }; // 拖拽移动 let isDragging = false, startX, startY, startLeft, startTop; bar.addEventListener('mousedown', (e) => { if (e.target === foldSpan) return; e.preventDefault(); isDragging = true; startX = e.clientX; startY = e.clientY; startLeft = root.offsetLeft; startTop = root.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; root.style.left = (startLeft + dx) + 'px'; root.style.top = (startTop + dy) + 'px'; } function onMouseUp(e) { if (!isDragging) return; isDragging = false; savePos(root.offsetLeft, root.offsetTop); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } // 初始化 renderCircles(); buildTools(); })();