// ==UserScript== // @name A君 - 悬浮菜单 - 脚本管理工具 // @namespace http://tampermonkey.net/ // @version 0.1.1 // @description 添加悬浮菜单,支持脚本管理功能 // @author Your name // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @license MIT // @downloadURL none // ==/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 = `
脚本管理
`; drawer.appendChild(drawerHeader); // 添加抽屉内容 const drawerContent = document.createElement('div'); drawerContent.style.cssText = ` height: calc(100% - 57px); padding: 16px 0; overflow-y: auto; `; drawerContent.innerHTML = `
`; 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 = `
${title}
${content}
`; 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: ` `, 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 => `
${group.name}
${group.scripts.map(script => `
${script.name}
`).join('')}
`).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: ` `, 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: `
`, 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: `
`, 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(); // ... 其他脚本管理相关函数 ... })();