您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
添加悬浮菜单,支持脚本管理功能
当前为
// ==UserScript== // @name A君 - 悬浮菜单 - 脚本管理工具 // @namespace http://tampermonkey.net/ // @version 0.1.1 // @description 添加悬浮菜单,支持脚本管理功能 // @author Your name // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { 'use strict'; // 数据结构定义 const scriptData = { groups: GM_getValue('scriptGroups', []) }; // 替换所有的 localStorage 操作 function saveData() { GM_setValue('scriptGroups', scriptData.groups); } // 更新样式 const style = document.createElement('style'); style.textContent = ` .floating-btn { position: fixed; right: 20px; bottom: 20px; z-index: 9999; padding: 10px 16px; background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); cursor: move; user-select: none; border-radius: 12px; color: #1c1c1e; font-size: 14px; min-width: 30px; text-align: center; transition: all 0.3s; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1); } .floating-btn:hover { background: rgba(255, 255, 255, 0.9); transform: translateY(-2px); } .right-drawer { position: fixed; top: 0; right: -540px; width: 540px; height: 100%; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(20px); box-shadow: -10px 0 30px rgba(0, 0, 0, 0.1); transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1); z-index: 9999; border-radius: 16px 0 0 16px; } .tree-node-content { display: flex; align-items: center; height: 40px; cursor: pointer; padding: 0 16px; margin: 4px 0; border-radius: 8px; transition: all 0.2s; } .tree-node-content:hover { background: rgba(0, 0, 0, 0.05); } .script-actions button { padding: 4px 8px; font-size: 13px; color: #007AFF; background: transparent; border: none; cursor: pointer; margin-left: 8px; border-radius: 6px; transition: all 0.2s; } .script-actions button:hover { background: rgba(0, 122, 255, 0.1); } .dialog-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(5px); z-index: 10000; display: flex; align-items: center; justify-content: center; } .dialog { background: rgba(255, 255, 255, 0.95); padding: 24px; border-radius: 16px; min-width: 360px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); backdrop-filter: blur(20px); } .dialog input, .dialog textarea { width: 100%; padding: 12px; border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 8px; font-size: 14px; background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); transition: all 0.2s; } .dialog input:focus, .dialog textarea:focus { outline: none; border-color: #007AFF; background: rgba(255, 255, 255, 0.95); } .dialog-btn { padding: 10px 24px; margin-left: 12px; border-radius: 8px; font-size: 14px; cursor: pointer; transition: all 0.2s; border: none; } .dialog-btn.primary { background: #007AFF; color: white; } .dialog-btn.primary:hover { background: #0066CC; } .dialog-btn.default { background: rgba(0, 0, 0, 0.05); color: #1c1c1e; } .dialog-btn.default:hover { background: rgba(0, 0, 0, 0.1); } #searchInput { height: 40px; background: rgba(0, 0, 0, 0.05); border: none; border-radius: 8px; padding: 0 16px; font-size: 14px; } #searchBtn { height: 40px; background: #007AFF; color: white; border: none; border-radius: 8px; padding: 0 20px; font-size: 14px; transition: all 0.2s; } #searchBtn:hover { background: #0066CC; } .action-btn.delete { color: #FF3B30; } .action-btn.delete:hover { background: rgba(255, 59, 48, 0.1); } .action-btn.run { color: #34C759; } .action-btn.run:hover { background: rgba(52, 199, 89, 0.1); } `; document.head.appendChild(style); // 创建悬浮按钮 const floatingBtn = document.createElement('div'); floatingBtn.className = 'floating-btn'; floatingBtn.innerHTML = '我的工具'; // 从GM_getValue获取保存的位置 const savedPosition = GM_getValue('floatingBtnPosition', {}); if (savedPosition.left) floatingBtn.style.left = savedPosition.left; if (savedPosition.top) floatingBtn.style.top = savedPosition.top; if (savedPosition.right) floatingBtn.style.right = savedPosition.right; if (savedPosition.bottom) floatingBtn.style.bottom = savedPosition.bottom; // 添加拖动功能 let isDragging = false; let startX, startY; let startLeft, startTop; floatingBtn.addEventListener('mousedown', function(e) { if (e.button === 0) { // 只响应左键 isDragging = true; startX = e.clientX; startY = e.clientY; const rect = floatingBtn.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; floatingBtn.style.transition = 'none'; e.preventDefault(); } }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; const newLeft = startLeft + deltaX; const newTop = startTop + deltaY; const maxX = window.innerWidth - floatingBtn.offsetWidth; const maxY = window.innerHeight - floatingBtn.offsetHeight; floatingBtn.style.left = Math.min(Math.max(0, newLeft), maxX) + 'px'; floatingBtn.style.top = Math.min(Math.max(0, newTop), maxY) + 'px'; floatingBtn.style.right = 'auto'; floatingBtn.style.bottom = 'auto'; }); document.addEventListener('mouseup', function() { if (isDragging) { isDragging = false; floatingBtn.style.transition = 'all 0.3s'; const position = { left: floatingBtn.style.left, top: floatingBtn.style.top, right: 'auto', bottom: 'auto' }; GM_setValue('floatingBtnPosition', position); } }); // 创建右侧抽屉 const drawer = document.createElement('div'); drawer.className = 'right-drawer'; // 添加抽屉头部 const drawerHeader = document.createElement('header'); drawerHeader.innerHTML = ` <div style="display:flex;align-items:center;padding:16px 20px;border-bottom:1px solid #ddd;"> <span style="flex:1;font-size:16px;">脚本管理</span> <button class="close-drawer" style="background:none;border:none;cursor:pointer;font-size:20px;">×</button> </div> `; drawer.appendChild(drawerHeader); // 添加抽屉内容 const drawerContent = document.createElement('div'); drawerContent.style.cssText = ` height: calc(100% - 57px); padding: 16px 0; overflow-y: auto; `; drawerContent.innerHTML = ` <div style="margin:0 20px;"> <div style="display:flex;align-items:center;margin-bottom:10px;"> <input type="text" placeholder="请输入脚本名称" id="searchInput" style="flex:1;height:32px;padding:0 12px;border:1px solid #dcdfe6;border-radius:4px;"> <button id="searchBtn" style="margin-left:12px;padding:8px 15px;background:#1890ff;color:white;border:none;border-radius:4px;cursor:pointer;">搜 索</button> </div> <button id="addGroupBtn" style="padding:4px 0;color:#409eff;background:none;border:none;cursor:pointer;font-size:12px;"> <i style="margin-right:4px;">+</i>新建脚本组 </button> <div id="scriptTree"></div> </div> `; drawer.appendChild(drawerContent); // 添加到页面 document.body.appendChild(floatingBtn); document.body.appendChild(drawer); // 添加右键菜单事件 floatingBtn.addEventListener('contextmenu', function(e) { e.preventDefault(); drawer.style.right = '0'; }); // 关闭抽屉 const closeBtn = drawer.querySelector('.close-drawer'); closeBtn.addEventListener('click', function() { drawer.style.right = '-540px'; }); // 点击抽屉外部关闭 document.addEventListener('click', function(e) { if (!drawer.contains(e.target) && !floatingBtn.contains(e.target)) { drawer.style.right = '-540px'; } }); // 添加创建对话框的函数 function createDialog({ title, content, onConfirm, onCancel }) { const dialog = document.createElement('div'); dialog.className = 'dialog-overlay'; dialog.innerHTML = ` <div class="dialog"> <div class="dialog-header">${title}</div> <div class="dialog-content">${content}</div> <div class="dialog-footer"> <button class="dialog-btn default" id="cancelBtn">取消</button> <button class="dialog-btn primary" id="confirmBtn">确定</button> </div> </div> `; document.body.appendChild(dialog); // 添加事件监听 dialog.querySelector('#cancelBtn').onclick = () => { onCancel && onCancel(); dialog.remove(); }; dialog.querySelector('#confirmBtn').onclick = () => { onConfirm && onConfirm(dialog); dialog.remove(); }; return dialog; } // 添加新建脚本组的处理函数 function handleAddGroup() { createDialog({ title: '新建脚本组', content: ` <input type="text" placeholder="请输入脚本组名称" id="groupNameInput"> `, onConfirm: (dialog) => { const groupName = dialog.querySelector('#groupNameInput').value.trim(); if (!groupName) { alert('请输入脚本组名称'); return; } const newGroup = { id: Date.now(), name: groupName, scripts: [], expanded: true }; scriptData.groups.push(newGroup); saveData(); // 使用新的保存方法 renderScriptTree(); } }); } // 修改渲染脚本树的函数 function renderScriptTree(searchText = '') { const treeContainer = document.querySelector('#scriptTree'); if (!treeContainer) return; const filteredGroups = scriptData.groups.map(group => { // 深拷贝组对象 const filteredGroup = {...group}; if (searchText) { // 过滤符合搜索条件的脚本 filteredGroup.scripts = group.scripts.filter(script => script.name.toLowerCase().includes(searchText.toLowerCase()) || script.content?.toLowerCase().includes(searchText.toLowerCase()) ); // 如果组内有匹配的脚本,则显示组 return filteredGroup.scripts.length > 0 ? filteredGroup : null; } return filteredGroup; }).filter(Boolean); treeContainer.innerHTML = filteredGroups.map(group => ` <div class="tree-node" data-group-id="${group.id}"> <div class="tree-node-content"> <i class="expand-icon" style="transform: rotate(${group.expanded ? '90deg' : '0deg'})"> <svg viewBox="0 0 1024 1024" width="12" height="12"> <path fill="currentColor" d="M384 192v640l384-320.064z"/> </svg> </i> <span class="node-label"> <span> <i class="icon folder-icon"> <svg viewBox="0 0 1024 1024" width="16" height="16"> <path fill="currentColor" d="M878.08 448H241.92l-96 384h636.16l96-384zM832 384v-64H485.76L357.504 192H128v448l57.92-231.744A32 32 0 0 1 216.96 384H832z"/> </svg> </i> ${group.name} </span> <span class="script-actions"> <button class="action-btn edit-group-btn" data-group-id="${group.id}">修改</button> <button class="action-btn delete delete-group-btn" data-group-id="${group.id}">删除</button> <button class="action-btn add-script-btn" data-group-id="${group.id}">添加脚本</button> </span> </span> </div> <div class="tree-node-children" style="display: ${group.expanded ? 'block' : 'none'}"> ${group.scripts.map(script => ` <div class="tree-node script-item" data-script-id="${script.id}"> <div class="tree-node-content"> <span class="node-label"> <span> <i class="icon script-icon"> <svg viewBox="0 0 1024 1024" width="14" height="14"> <path fill="currentColor" d="M516.608 290.304a32 32 0 0 0-45.248 45.248l105.344 105.344-105.344 105.344a32 32 0 0 0 45.248 45.248l128-128a32 32 0 0 0 0-45.248l-128-128zM288 392l128-128a32 32 0 0 1 45.248 0l128 128a32 32 0 0 1 0 45.248l-128 128A32 32 0 0 1 416 520l-128-128z"/> </svg> </i> ${script.name} </span> <span class="script-actions"> <button class="action-btn edit-script-btn" data-script-id="${script.id}">修改</button> <button class="action-btn delete delete-script-btn" data-script-id="${script.id}">删除</button> <button class="action-btn run run-script-btn" data-script-id="${script.id}">执行</button> </span> </span> </div> </div> `).join('')} </div> </div> `).join(''); addTreeEventListeners(); } // 修改事件监听函数,添加修改、删除和执行功能 function addTreeEventListeners() { // 展开/折叠图标点击事件 document.querySelectorAll('.expand-icon').forEach(icon => { icon.addEventListener('click', (e) => { const groupNode = e.target.closest('.tree-node'); const childrenContainer = groupNode.querySelector('.tree-node-children'); const groupId = groupNode.dataset.groupId; const group = scriptData.groups.find(g => g.id.toString() === groupId); group.expanded = !group.expanded; childrenContainer.style.display = group.expanded ? 'block' : 'none'; icon.style.transform = `rotate(${group.expanded ? '90deg' : '0deg'})`; saveData(); }); }); // 添加脚本按钮点击事件 document.querySelectorAll('.add-script-btn').forEach(btn => { btn.addEventListener('click', (e) => { const groupId = e.target.dataset.groupId; handleAddScript(groupId); }); }); // 修改脚本组 document.querySelectorAll('.edit-group-btn').forEach(btn => { btn.addEventListener('click', (e) => { const groupId = e.target.dataset.groupId; const group = scriptData.groups.find(g => g.id.toString() === groupId); if (group) { createDialog({ title: '修改脚本组', content: ` <input type="text" placeholder="请输入脚本组名称" id="groupNameInput" value="${group.name}"> `, onConfirm: (dialog) => { const newName = dialog.querySelector('#groupNameInput').value.trim(); if (!newName) { alert('请输入脚本组名称'); return; } group.name = newName; saveData(); renderScriptTree(); } }); } }); }); // 删除脚本组 document.querySelectorAll('.delete-group-btn').forEach(btn => { btn.addEventListener('click', (e) => { const groupId = e.target.dataset.groupId; const group = scriptData.groups.find(g => g.id.toString() === groupId); if (group) { if (confirm(`确定要删除脚本组"${group.name}"吗?其下所有脚本都将被删除。`)) { scriptData.groups = scriptData.groups.filter(g => g.id.toString() !== groupId); saveData(); renderScriptTree(); } } }); }); // 修改脚本 document.querySelectorAll('.edit-script-btn').forEach(btn => { btn.addEventListener('click', (e) => { const scriptId = e.target.dataset.scriptId; const script = findScript(scriptId); if (script) { createDialog({ title: '修改脚本', content: ` <div style="margin-bottom: 15px;"> <input type="text" placeholder="请输入脚本名称" id="scriptNameInput" value="${script.name}"> </div> <div style="margin-bottom: 15px;"> <textarea placeholder="请输入脚本内容" id="scriptContentInput" style="width: 100%; min-height: 100px; padding: 8px;">${script.content}</textarea> </div> <div> <textarea placeholder="请输入脚本描述" id="scriptDescInput" style="width: 100%; min-height: 60px; padding: 8px;">${script.description || ''}</textarea> </div> `, onConfirm: (dialog) => { const name = dialog.querySelector('#scriptNameInput').value.trim(); const content = dialog.querySelector('#scriptContentInput').value.trim(); const description = dialog.querySelector('#scriptDescInput').value.trim(); if (!name || !content) { alert('请输入脚本名称和内容'); return; } script.name = name; script.content = content; script.description = description; saveData(); renderScriptTree(); } }); } }); }); // 删除脚本 document.querySelectorAll('.delete-script-btn').forEach(btn => { btn.addEventListener('click', (e) => { const scriptId = e.target.dataset.scriptId; const script = findScript(scriptId); if (script && confirm(`确定要删除脚本"${script.name}"吗?`)) { scriptData.groups.forEach(group => { group.scripts = group.scripts.filter(s => s.id.toString() !== scriptId); }); saveData(); renderScriptTree(); } }); }); // 执行脚本 document.querySelectorAll('.run-script-btn').forEach(btn => { btn.addEventListener('click', (e) => { const scriptId = e.target.dataset.scriptId; const script = findScript(scriptId); if (script) { try { // 使用 Function 构造器创建一个新的函数作用域来执行脚本 const scriptFunc = new Function(script.content); scriptFunc(); } catch (error) { alert(`脚本执行出错:${error.message}`); console.error('脚本执行错误:', error); } } }); }); } // 辅助函数:查找脚本 function findScript(scriptId) { for (const group of scriptData.groups) { const script = group.scripts.find(s => s.id.toString() === scriptId); if (script) return script; } return null; } // 添加脚本的处理函数 function handleAddScript(groupId) { createDialog({ title: '添加脚本', content: ` <div style="margin-bottom: 15px;"> <input type="text" placeholder="请输入脚本名称" id="scriptNameInput"> </div> <div style="margin-bottom: 15px;"> <textarea placeholder="请输入脚本内容" id="scriptContentInput" style="width: 100%; min-height: 100px; padding: 8px;"></textarea> </div> <div> <textarea placeholder="请输入脚本描述" id="scriptDescInput" style="width: 100%; min-height: 60px; padding: 8px;"></textarea> </div> `, onConfirm: (dialog) => { const name = dialog.querySelector('#scriptNameInput').value.trim(); const content = dialog.querySelector('#scriptContentInput').value.trim(); const description = dialog.querySelector('#scriptDescInput').value.trim(); if (!name || !content) { alert('请输入脚本名称和内容'); return; } const group = scriptData.groups.find(g => g.id.toString() === groupId); if (group) { group.scripts.push({ id: Date.now(), name, content, description }); saveData(); renderScriptTree(); } } }); } // 搜索功能 const searchInput = document.querySelector('#searchInput'); const searchBtn = document.querySelector('#searchBtn'); searchBtn.addEventListener('click', () => { const searchText = searchInput.value.trim(); renderScriptTree(searchText); }); searchInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') { const searchText = searchInput.value.trim(); renderScriptTree(searchText); } }); // 绑定新建脚本组按钮的点击事件 const addGroupBtn = document.querySelector('#addGroupBtn'); addGroupBtn.addEventListener('click', handleAddGroup); // 初始渲染脚本树 renderScriptTree(); // ... 其他脚本管理相关函数 ... })();