您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
按自定义快捷键显示输入框,提供XPath操作和功能
// ==UserScript== // @name XPath工具 // @namespace http://tampermonkey.net/ // @version 2.1 // @description 按自定义快捷键显示输入框,提供XPath操作和功能 // @author Ace // @match https://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_download // ==/UserScript== (function() { 'use strict'; let toolbar = null; let settingsPanel = null; let currentElement = null; let isModifierPressed = false; let originalBackgroundColor = ''; let isDraggingToolbar = false; let isDraggingSettings = false; let dragStartX = 0; let dragStartY = 0; let toolbarX = 0; let toolbarY = 0; let settingsX = 0; let settingsY = 0; let mouseX = 0; let mouseY = 0; const defaultConfig = { clearOnClose: true, hotkey: "Shift+X", highlightColor: "#FFF59D", selectModifier: "Shift" }; let config = { ...defaultConfig, ...GM_getValue("xpath_config", {}) }; document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }); function highlightElement(element) { if (currentElement) { currentElement.style.backgroundColor = originalBackgroundColor; } originalBackgroundColor = getComputedStyle(element).backgroundColor; element.style.backgroundColor = config.highlightColor; currentElement = element; } function clearHighlight() { if (currentElement) { currentElement.style.backgroundColor = originalBackgroundColor; currentElement = null; } } function getXPath(element) { if (element.id) return `//*[@id="${element.id}"]`; if (element === document.body) return '/html/body'; let ix = 0; const siblings = element.parentNode.childNodes; for (let sibling of siblings) { if (sibling === element) { return `${getXPath(element.parentNode)}/${element.tagName.toLowerCase()}[${ix + 1}]`; } if (sibling.nodeType === 1 && sibling.tagName === element.tagName) { ix++; } } } function exportConfig() { const data = JSON.stringify(config, null, 2); const blob = new Blob([data], {type: "application/json"}); const url = URL.createObjectURL(blob); GM_download({ url: url, name: "xpath_config.json", saveAs: true }); } function importConfig() { const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json'; input.onchange = e => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = event => { try { const imported = JSON.parse(event.target.result); config = {...config, ...imported}; GM_setValue("xpath_config", config); updateSettingsDisplay(); } catch (error) { alert("配置文件解析失败"); } }; reader.readAsText(file); }; input.click(); } function createSettingsPanel() { settingsPanel = document.createElement('div'); settingsPanel.id = 'xpath-settings'; settingsPanel.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #2c3e50; padding: 20px; border-radius: 8px; color: white; z-index: 10000; display: none; box-shadow: 0 0 15px rgba(0,0,0,0.3); min-width: 300px; user-select: none; `; settingsPanel.innerHTML = ` <h3 style="margin:0 0 15px 0; border-bottom:1px solid #34495e; padding-bottom:10px; cursor: move;">设置</h3> <div class="setting-item"> <label> <input type="checkbox" id="clearOnClose" ${config.clearOnClose ? 'checked' : ''}> 关闭时清空输入框 </label> </div> <div class="setting-item"> <label>工具栏快捷键: <input type="text" id="hotkey" value="${config.hotkey}" placeholder="例如:Shift+X" style="color: #333; background: #fff"> </label> </div> <div class="setting-item"> <label>元素选择键: <select id="selectModifier" style="color: #333; background: #fff; padding: 4px; border: 1px solid #34495e; border-radius: 4px;"> <option value="Shift" ${config.selectModifier === 'Shift' ? 'selected' : ''}>Shift</option> <option value="Ctrl" ${config.selectModifier === 'Ctrl' ? 'selected' : ''}>Ctrl</option> <option value="Alt" ${config.selectModifier === 'Alt' ? 'selected' : ''}>Alt</option> </select> </label> </div> <div class="setting-item"> <label>高亮颜色: <input type="color" id="highlightColor" value="${config.highlightColor}" style="height: 30px; padding: 2px;"> </label> </div> <div style="margin-top:15px;"> <button id="exportConfig" style="padding:6px 12px; background:#3498db; border:none; color:white; border-radius:4px;">导出配置</button> <button id="importConfig" style="margin-left:10px; padding:6px 12px; background:#3498db; border:none; color:white; border-radius:4px;">导入配置</button> </div> <div style="margin-top:20px; text-align:right;"> <button id="saveSettings" style="padding:8px 16px; background:#27ae60; border:none; color:white; border-radius:4px;">保存</button> <button id="closeSettings" style="margin-left:10px; padding:8px 16px; background:#e74c3c; border:none; color:white; border-radius:4px;">取消</button> </div> `; const header = settingsPanel.querySelector('h3'); header.addEventListener('mousedown', startDragSettings); document.addEventListener('mousemove', handleDragSettings); document.addEventListener('mouseup', stopDragSettings); settingsPanel.querySelector('#saveSettings').addEventListener('click', function(e) { e.stopPropagation(); saveSettings(); }); settingsPanel.querySelector('#closeSettings').addEventListener('click', function(e) { e.stopPropagation(); settingsPanel.style.display = 'none'; }); settingsPanel.querySelector('#exportConfig').addEventListener('click', exportConfig); settingsPanel.querySelector('#importConfig').addEventListener('click', importConfig); document.body.appendChild(settingsPanel); } function updateSettingsDisplay() { document.getElementById('clearOnClose').checked = config.clearOnClose; document.getElementById('hotkey').value = config.hotkey; document.getElementById('selectModifier').value = config.selectModifier; document.getElementById('highlightColor').value = config.highlightColor; } function saveSettings() { config.clearOnClose = document.getElementById('clearOnClose').checked; config.hotkey = document.getElementById('hotkey').value.trim(); config.selectModifier = document.getElementById('selectModifier').value; config.highlightColor = document.getElementById('highlightColor').value; GM_setValue("xpath_config", config); setupHotkeyListener(); settingsPanel.style.display = 'none'; } function startDragSettings(e) { isDraggingSettings = true; dragStartX = e.clientX; dragStartY = e.clientY; const rect = settingsPanel.getBoundingClientRect(); settingsX = rect.left; settingsY = rect.top; settingsPanel.style.transform = 'none'; } function handleDragSettings(e) { if (!isDraggingSettings) return; const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; settingsPanel.style.left = `${settingsX + dx}px`; settingsPanel.style.top = `${settingsY + dy}px`; } function stopDragSettings() { isDraggingSettings = false; } function parseHotkey(hotkey) { const parts = hotkey.split('+').map(p => p.trim().toLowerCase()); const modifiers = { shift: false, ctrl: false, alt: false, meta: false }; let mainKey = ''; for (const part of parts) { switch (part.toLowerCase()) { case 'shift': modifiers.shift = true; break; case 'ctrl': modifiers.ctrl = true; break; case 'alt': modifiers.alt = true; break; case 'meta': modifiers.meta = true; break; default: mainKey = part; } } return { modifiers, mainKey }; } function handleHotkey(event) { const { modifiers, mainKey } = parseHotkey(config.hotkey); const matchModifiers = event.shiftKey === modifiers.shift && event.ctrlKey === modifiers.ctrl && event.altKey === modifiers.alt && event.metaKey === modifiers.meta; const matchKey = mainKey ? event.key.toLowerCase() === mainKey.toLowerCase() : false; if (matchModifiers && matchKey) { event.preventDefault(); showToolbar(); } } function createToolbar() { if (document.getElementById('xpath-toolbar')) return; toolbar = document.createElement('div'); toolbar.id = 'xpath-toolbar'; toolbar.style.cssText = ` position: fixed; z-index: 9999; background: #2c3e50; color: white; padding: 15px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); display: none; min-width: 300px; user-select: none; `; toolbar.innerHTML = ` <div style="position:relative;"> <div id="toolbarHeader" style="cursor: move; margin-bottom: 10px; padding: 5px; border-radius: 4px; background: #34495e;"> XPath工具 <button id="settingsBtn" style="float:right; background:none; border:none; color:white; cursor:pointer; padding:0 5px;">⚙</button> </div> <div style="position:relative; width: 250px;"> <input type="text" id="xpath-input" placeholder="输入或生成的XPath" style="width: 100%; padding: 8px 25px 8px 8px; border: 1px solid #34495e; border-radius: 4px; background: #34495e; color: white; box-sizing: border-box;"> <span id="clearInput" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #888; padding: 0 5px; background: #34495e; border-radius: 50%;">×</span> </div> <div style="margin-top:10px;"> <button id="deleteBtn" style="padding:6px 12px; background:#e74c3c; border:none; border-radius:4px; color:white; cursor:pointer;">删除元素</button> <label style="margin-left:10px; font-size:0.9em;"> <input type="checkbox" id="hideMode"> 隐藏模式 </label> </div> </div> `; const header = toolbar.querySelector('#toolbarHeader'); const input = toolbar.querySelector('#xpath-input'); const deleteBtn = toolbar.querySelector('#deleteBtn'); const settingsBtn = toolbar.querySelector('#settingsBtn'); const clearBtn = toolbar.querySelector('#clearInput'); toolbar.addEventListener('mousedown', e => e.stopPropagation()); toolbar.addEventListener('click', e => e.stopPropagation()); header.addEventListener('mousedown', startDragToolbar); document.addEventListener('mousemove', handleDragToolbar); document.addEventListener('mouseup', stopDragToolbar); function startDragToolbar(e) { isDraggingToolbar = true; dragStartX = e.clientX; dragStartY = e.clientY; const rect = toolbar.getBoundingClientRect(); toolbarX = rect.left; toolbarY = rect.top; } function handleDragToolbar(e) { if (!isDraggingToolbar) return; const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; toolbar.style.left = `${toolbarX + dx}px`; toolbar.style.top = `${toolbarY + dy}px`; } function stopDragToolbar() { isDraggingToolbar = false; } settingsBtn.addEventListener('click', () => { settingsPanel.style.display = 'block'; updateSettingsDisplay(); }); clearBtn.addEventListener('click', () => { input.value = ''; input.focus(); }); deleteBtn.addEventListener('click', () => { const xpath = input.value; if (!xpath) return; try { const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); if (result.singleNodeValue) { if (toolbar.querySelector('#hideMode').checked) { result.singleNodeValue.style.display = 'none'; } else { result.singleNodeValue.remove(); } input.value = ''; } } catch (error) { alert('无效的XPath表达式'); } }); document.body.appendChild(toolbar); } (function init() { createToolbar(); createSettingsPanel(); setupHotkeyListener(); document.addEventListener('click', (e) => { if (settingsPanel.style.display !== 'block' && !toolbar.contains(e.target) && !isModifierPressed) { hideToolbar(); } if (settingsPanel.style.display === 'block' && !settingsPanel.contains(e.target) && !toolbar.contains(e.target)) { settingsPanel.style.display = 'none'; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hideToolbar(); }); document.addEventListener('mouseover', (e) => { if (isModifierPressed && !toolbar.contains(e.target)) { highlightElement(e.target); } }); document.addEventListener('click', (e) => { if (isModifierPressed && !toolbar.contains(e.target)) { e.preventDefault(); e.stopPropagation(); document.getElementById('xpath-input').value = getXPath(e.target); } }); document.addEventListener('keydown', (e) => { if (e.key === config.selectModifier) isModifierPressed = true; }); document.addEventListener('keyup', (e) => { if (e.key === config.selectModifier) { isModifierPressed = false; clearHighlight(); } }); GM_registerMenuCommand("XPath工具设置", () => { settingsPanel.style.display = 'block'; updateSettingsDisplay(); }); })(); function setupHotkeyListener() { document.removeEventListener('keydown', handleHotkey); document.addEventListener('keydown', handleHotkey); } function showToolbar() { toolbar.style.display = 'block'; toolbar.style.left = `${mouseX}px`; toolbar.style.top = `${mouseY}px`; document.getElementById('xpath-input').focus(); } function hideToolbar() { toolbar.style.display = 'none'; if (config.clearOnClose) { document.getElementById('xpath-input').value = ''; } } })();