// ==UserScript== // @name 综合网页搜索BZ // @namespace http://tampermonkey.net/ // @version 0.1 // @description 搜索/检索单个网页并在原网页和弹窗显示多个搜索结果,支持动态搜索和显示方式选择,搜索不区分大小写,支持移动搜索容器和弹窗,点击弹窗结果滑动到对应位置并高亮 // @author 你自己 // @match *://*/* // @grant none // ==/UserScript== (function () { 'use strict'; // 创建搜索容器 const searchContainer = document.createElement('div'); searchContainer.style.position = 'fixed'; searchContainer.style.top = '10px'; searchContainer.style.left = '10px'; searchContainer.style.zIndex = '9999'; searchContainer.style.backgroundColor = '#f9f9f9'; searchContainer.style.padding = '15px'; searchContainer.style.border = '1px solid #ddd'; searchContainer.style.borderRadius = '8px'; searchContainer.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)'; makeDraggable(searchContainer); // 创建搜索输入框 const searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.placeholder = '输入关键词,用分号分隔'; searchInput.style.padding = '8px'; searchInput.style.border = '1px solid #ccc'; searchInput.style.borderRadius = '4px'; searchContainer.appendChild(searchInput); // 创建添加关键词按钮 const addKeywordButton = document.createElement('button'); addKeywordButton.textContent = '添加关键词'; addKeywordButton.style.padding = '8px 12px'; addKeywordButton.style.marginLeft = '8px'; addKeywordButton.style.backgroundColor = '#007BFF'; addKeywordButton.style.color = 'white'; addKeywordButton.style.border = 'none'; addKeywordButton.style.borderRadius = '4px'; addKeywordButton.style.cursor = 'pointer'; addKeywordButton.addEventListener('click', function () { const newKeyword = prompt('请输入要添加的关键词:'); if (newKeyword) { const currentValue = searchInput.value; searchInput.value = currentValue? currentValue + ';' + newKeyword : newKeyword; } searchInput.focus(); }); searchContainer.appendChild(addKeywordButton); // 创建搜索动态数据按钮 const searchDynamicButton = document.createElement('button'); searchDynamicButton.textContent = '开启动态搜索'; searchDynamicButton.style.padding = '8px 12px'; searchDynamicButton.style.marginLeft = '8px'; searchDynamicButton.style.backgroundColor = '#ffc107'; searchDynamicButton.style.color = 'white'; searchDynamicButton.style.border = 'none'; searchDynamicButton.style.borderRadius = '4px'; searchDynamicButton.style.cursor = 'pointer'; searchContainer.appendChild(searchDynamicButton); // 创建搜索按钮 const searchButton = document.createElement('button'); searchButton.textContent = '搜索'; searchButton.style.padding = '8px 12px'; searchButton.style.marginLeft = '8px'; searchButton.style.backgroundColor = '#dc3545'; searchButton.style.color = 'white'; searchButton.style.border = 'none'; searchButton.style.borderRadius = '4px'; searchButton.style.cursor = 'pointer'; searchContainer.appendChild(searchButton); // 显示方式选择 const displayOptionSelect = document.createElement('select'); const originalOption = document.createElement('option'); originalOption.value = 'original'; originalOption.textContent = '在原网页显示'; const popupOption = document.createElement('option'); popupOption.value = 'popup'; popupOption.textContent = '弹窗显示'; displayOptionSelect.appendChild(originalOption); displayOptionSelect.appendChild(popupOption); displayOptionSelect.style.marginLeft = '5px'; searchContainer.appendChild(displayOptionSelect); document.body.appendChild(searchContainer); // 用于存储最新的文本节点 let textNodes = []; // 收集文本节点的函数 function collectTextNodes(node) { if (node.nodeType === Node.TEXT_NODE) { textNodes.push(node); } else { const children = node.childNodes; for (let i = 0; i < children.length; i++) { collectTextNodes(children[i]); } } } // 搜索按钮点击事件 searchButton.addEventListener('click', function () { // 重置状态 textNodes = []; collectTextNodes(document.body); // 清除之前原网页的高亮显示 clearHighlightedSpans(); // 清除之前的弹窗搜索结果容器 clearPopupResultsContainer(); const keywords = searchInput.value.split(';').map(keyword => keyword.trim().toLowerCase()); const searchDynamic = isDynamicSearchEnabled; const displayOption = displayOptionSelect.value; const searchResults = []; keywords.forEach(keyword => { const matches = findMatches(keyword, searchDynamic); matches.forEach(match => { searchResults.push({ keyword, match }); }); }); if (displayOption === 'popup') { const resultsContainer = document.createElement('div'); resultsContainer.style.position = 'fixed'; resultsContainer.style.top = '70px'; resultsContainer.style.right = '10px'; resultsContainer.style.zIndex = '9999'; resultsContainer.style.backgroundColor = 'white'; resultsContainer.style.border = '1px solid #ddd'; resultsContainer.style.borderRadius = '5px'; resultsContainer.style.padding = '20px'; resultsContainer.style.width = '400px'; resultsContainer.style.height = '400px'; resultsContainer.style.overflow = 'auto'; resultsContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)'; makeDraggable(resultsContainer); const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.style.position = 'absolute'; closeButton.style.top = '10px'; closeButton.style.right = '10px'; closeButton.style.padding = '5px 10px'; closeButton.style.backgroundColor = '#dc3545'; closeButton.style.color = 'white'; closeButton.style.border = 'none'; closeButton.style.borderRadius = '3px'; closeButton.style.cursor = 'pointer'; closeButton.addEventListener('mouseover', function () { closeButton.style.backgroundColor = '#c82333'; }); closeButton.addEventListener('mouseout', function () { closeButton.style.backgroundColor = '#dc3545'; }); closeButton.addEventListener('click', function () { document.body.removeChild(resultsContainer); }); resultsContainer.appendChild(closeButton); searchResults.forEach((result, index) => { const text = result.match.node.textContent; const originalKeyword = result.keyword; const keyword = originalKeyword; const indexOfKeyword = text.toLowerCase().indexOf(keyword); let start = Math.max(0, indexOfKeyword - 20); let end = Math.min(text.length, indexOfKeyword + keyword.length + 20); let context = text.slice(start, end); const resultDiv = document.createElement('div'); resultDiv.style.padding = '10px'; resultDiv.style.borderBottom = '1px solid #eee'; resultDiv.innerHTML = `${index + 1}. ${context.replace(originalKeyword, `${originalKeyword}`)}`; resultDiv.addEventListener('click', function () { // 清除之前所有的高亮显示 clearHighlightedSpans(); const targetElement = result.match.node.parentElement; targetElement.scrollIntoView({ behavior:'smooth', block: 'center' }); const spans = targetElement.querySelectorAll('span'); spans.forEach(span => { if (span.textContent.includes(originalKeyword)) { span.style.backgroundColor = 'yellow'; } }); }); resultDiv.addEventListener('mouseover', function () { resultDiv.style.backgroundColor = '#f9f9f9'; }); resultDiv.addEventListener('mouseout', function () { resultDiv.style.backgroundColor = 'white'; }); resultsContainer.appendChild(resultDiv); }); document.body.appendChild(resultsContainer); } else { showOriginalResults(searchResults); } if (searchResults.length === 0) { // 未找到结果提示 const existingAlert = document.querySelector('div[style*="position: fixed; top: 10px; left: 50%; transform: translateX(-50%); z-index: 9999;"]'); if (existingAlert) { document.body.removeChild(existingAlert); } const alertDiv = document.createElement('div'); alertDiv.style.position = 'fixed'; alertDiv.style.top = '10px'; alertDiv.style.left = '50%'; alertDiv.style.transform = 'translateX(-50%)'; alertDiv.style.zIndex = '9999'; alertDiv.style.backgroundColor = 'red'; alertDiv.style.color = 'white'; alertDiv.style.padding = '10px'; alertDiv.style.borderRadius = '5px'; alertDiv.textContent = '未找到匹配结果。'; document.body.appendChild(alertDiv); setTimeout(() => { if (alertDiv.parentNode) { document.body.removeChild(alertDiv); } }, 3000); } }); // 创建 MutationObserver 来监听页面变化 const observer = new MutationObserver((mutationsList) => { textNodes = []; collectTextNodes(document.body); }); // 配置观察选项 const config = { childList: true, subtree: true }; // 动态搜索开关状态 let isDynamicSearchEnabled = false; // 动态搜索开关按钮点击事件 searchDynamicButton.addEventListener('click', function () { if (isDynamicSearchEnabled) { observer.disconnect(); searchDynamicButton.textContent = '开启动态搜索'; searchDynamicButton.style.backgroundColor = '#ffc107'; } else { observer.observe(document.body, config); searchDynamicButton.textContent = '关闭动态搜索'; searchDynamicButton.style.backgroundColor = '#dc3545'; } isDynamicSearchEnabled =!isDynamicSearchEnabled; }); /** * 查找匹配项 * @param {string} keyword - 搜索关键词 * @param {boolean} searchDynamic - 是否搜索动态数据 * @returns {Array} - 匹配结果数组 */ function findMatches(keyword, searchDynamic) { const matches = []; textNodes.forEach(node => { let nodeText = node.textContent.toLowerCase().trim(); keyword = keyword.trim().toLowerCase(); const index = nodeText.indexOf(keyword); if (index!== -1) { matches.push({ node, index }); } }); if (searchDynamic) { // 这里可以添加搜索动态数据的逻辑,例如监听 DOM 变化 } return matches; } /** * 在原网页中显示搜索结果 * @param {Array} results - 搜索结果数组 */ function showOriginalResults(results) { results.forEach((result, index) => { const node = result.match.node; const text = node.textContent; const originalKeyword = result.keyword; const keyword = originalKeyword; const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = new RegExp(escapedKeyword, 'gi'); const newText = text.replace(regex, `${originalKeyword}`); const range = document.createRange(); range.selectNodeContents(node); const fragment = range.createContextualFragment(newText); node.parentNode.replaceChild(fragment, node); }); } /** * 清除原网页中高亮显示的 标签 */ function clearHighlightedSpans() { const highlightedSpans = document.querySelectorAll('span[style*="background-color"]'); highlightedSpans.forEach(span => { const parent = span.parentNode; const textNode = document.createTextNode(span.textContent); parent.insertBefore(textNode, span); parent.removeChild(span); }); } /** * 清除弹窗中的搜索结果容器 */ function clearPopupResultsContainer() { const existingResultsContainer = document.querySelector('div[style*="position: fixed; top: 70px; right: 10px; z-index: 9999;"]'); if (existingResultsContainer) { document.body.removeChild(existingResultsContainer); } } /** * 获取唯一的颜色 * @param {number} index - 结果索引 * @returns {string} 颜色值 */ function getUniqueColor(index) { const baseColors = ['#FFFF00', '#FF00FF', '#00FFFF', '#FFA500', '#00FF00']; const hueShift = (index % baseColors.length) * 360 / baseColors.length; const saturation = '80%'; const lightness = '50%'; return `hsl(${hueShift}, ${saturation}, ${lightness})`; } /** * 使元素可拖动 * @param {HTMLElement} element - 要设置为可拖动的元素 */ function makeDraggable(element) { let isDragging = false; let offsetX, offsetY; element.addEventListener('mousedown', (e) => { if (e.target === element) { isDragging = true; offsetX = e.clientX - parseInt(getComputedStyle(element).left); offsetY = e.clientY - parseInt(getComputedStyle(element).top); e.preventDefault(); } }); document.addEventListener('mousemove', (e) => { if (isDragging) { element.style.left = (e.clientX - offsetX) + 'px'; element.style.top = (e.clientY - offsetY) + 'px'; } }); document.addEventListener('mouseup', () => { isDragging = false; }); } })();