// ==UserScript== // @name 智能考试助手(拾取选择器版·已修复) // @namespace http://tampermonkey.net/ // @version 1.4 // @description 支持悬停题目、点击题目、划词选中、拾取选择器(无需控制台)、自定义选择器即时生效 // @author You // @icon https://tse2-mm.cn.bing.net/th/id/OIP-C.s8WZvq7biii2N7NkGdXGTwAAAA?rs=1 // @match *://*/* // @grant GM_addStyle // @grant GM_setClipboard // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ============= 题库数据(请替换为你的完整题库) ============= const QUESTION_DATABASE = [ // 示例数据(实际使用时请替换为你的所有题目) { "question": "1、()是制定《安全生产法》的根本出发点和落脚点。", "options": [], "answer": "重视和保护人的生命权", "type": "choice" }, { "question": "2、安装在进风流中的局部通风机距回风口不得小于()。", "options": [], "answer": "10m", "type": "choice" } // ... 请在此处粘贴你的完整题库数据 ]; // ============= 脚本配置 ============= const DEBUG = true; let lastClickedQuestion = null; let lastHoveredQuestion = null; let customQuestionSelector = localStorage.getItem('customQuestionSelector') || ''; let pickMode = false; // 拾取模式标志 let originalCursor = ''; // 保存原始光标 let highlightElement = null; // 高亮元素 function log(...args) { if (DEBUG) console.log('[智能考试助手]', ...args); } log('脚本已加载,题库大小:', QUESTION_DATABASE.length); if (customQuestionSelector) log('使用自定义选择器:', customQuestionSelector); // ============= 工具函数 ============= // 从文本中提取纯净的题干(去掉选项部分) function extractPureQuestion(text) { if (!text) return ''; // 1. 去掉开头的题型标签(如【单选题】、【多选题】等) let cleaned = text.replace(/^【[^】]+】\s*/, ''); // 2. 查找第一个选项字母的位置(如 A. A、等) const optionPattern = /\s+[A-D][\.、]/; const match = cleaned.match(optionPattern); if (match) { const index = match.index; if (index > 0) { cleaned = cleaned.substring(0, index).trim(); } } // 3. 如果还有换行,取第一行(作为题干) const lines = cleaned.split('\n'); if (lines.length > 1 && lines[0].trim().length > 5) { return lines[0].trim(); } return cleaned; } // 从元素中智能提取题干(优先查找常见子元素,否则分析文本) function getQuestionTextFromElement(elem) { if (!elem) return ''; // 优先查找可能的题干子元素(类名包含 title, stem, question 等) const titleSelectors = [ '.exam-topic-item-title-name', '.question-title', '.topic-title', '.question-stem', '.exam-question-title', '[class*="title"]', '[class*="stem"]', '[class*="question"]' ]; for (const sel of titleSelectors) { const sub = elem.querySelector(sel); if (sub && sub.textContent.trim()) { let text = sub.textContent.trim(); text = extractPureQuestion(text); if (text) return text; } } // 如果没有找到子元素,获取整个元素文本并提取 const fullText = elem.textContent.trim(); if (fullText) { // 先尝试直接提取纯净题干 let cleaned = extractPureQuestion(fullText); if (cleaned) return cleaned; // 如果文本较长(包含很多内容),尝试取第一段 if (fullText.length > 200) { const lines = fullText.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (/^\d+[\.、]/.test(line) && line.length > 5 && line.length < 200) { cleaned = extractPureQuestion(line); if (cleaned) return cleaned; } } if (lines[0] && lines[0].trim().length > 5) { cleaned = extractPureQuestion(lines[0]); if (cleaned) return cleaned; } } return cleaned || fullText; } return ''; } // 生成元素的唯一CSS选择器(优先id,其次class组合,最后标签+索引) function generateSelector(element) { if (!element) return ''; // 优先使用 id if (element.id) { return `#${element.id}`; } // 尝试获取元素的主要 class(优先取第一个) const getMainClass = (el) => { if (!el.className || typeof el.className !== 'string') return ''; const classes = el.className.trim().split(/\s+/); // 过滤掉常见的表示位置、大小、颜色等无关类(可自定义) const blacklist = ['active', 'selected', 'hover', 'focus', 'clearfix', 'left', 'right', 'content', 'info']; const valid = classes.filter(c => !blacklist.includes(c)); return valid.length ? valid[0] : classes[0]; }; // 向上查找可能包含题目特征的父容器(最大深度5) let path = []; let current = element; let depth = 0; while (current && current !== document.body && depth < 5) { const tag = current.tagName.toLowerCase(); let selector = tag; const mainClass = getMainClass(current); if (mainClass) { selector += `.${mainClass}`; } else if (current.id) { selector = `#${current.id}`; } // 如果该选择器在当前上下文唯一,则停止继续向上(避免过度组合) if (depth === 0) { // 当前元素本身 if (selector !== tag) { // 有 class 或 id,检查唯一性 if (document.querySelectorAll(selector).length === 1) { return selector; } } } // 组合进路径 path.unshift(selector); current = current.parentElement; depth++; } // 如果路径为空,返回原始标签名(最坏情况) if (path.length === 0) return element.tagName.toLowerCase(); // 组合最终选择器,去掉可能出现的 :nth-of-type(我们避免使用) let finalSelector = path.join(' > '); // 移除可能存在的 :nth-of-type(我们不用) finalSelector = finalSelector.replace(/:nth-of-type\(\d+\)/g, ''); // 检查是否唯一,如果不唯一,提示用户手动修改 if (document.querySelectorAll(finalSelector).length > 1) { // 不唯一,尝试简化(取最后一段) const lastPart = path[path.length - 1]; if (document.querySelectorAll(lastPart).length === 1) { return lastPart; } // 否则返回带提示 return finalSelector + ' (可能不唯一,建议手动修改)'; } return finalSelector; } // 退出拾取模式 function exitPickMode() { if (!pickMode) return; pickMode = false; document.body.style.cursor = originalCursor; if (highlightElement) { highlightElement.style.outline = ''; highlightElement = null; } // 移除临时事件监听 document.removeEventListener('mouseover', pickMouseOver); document.removeEventListener('click', pickClick); document.removeEventListener('keydown', pickKeydown); log('退出拾取模式'); } // 拾取模式鼠标悬停高亮 function pickMouseOver(e) { const target = e.target; if (highlightElement === target) return; if (highlightElement) { highlightElement.style.outline = ''; } highlightElement = target; target.style.outline = '2px solid #ff6b6b'; } // 拾取模式点击 function pickClick(e) { e.preventDefault(); e.stopPropagation(); if (e.target === pickBtn || pickBtn.contains(e.target)) return; const target = e.target; let selector = generateSelector(target); // 获取文本预览 let preview = target.textContent.trim().substring(0, 50); if (preview) preview = '文本预览: ' + preview; // 如果选择器可能不唯一,提示用户手动修改 let message = `已捕获元素选择器:\n${selector}\n${preview}\n是否应用为题目选择器?`; if (selector.includes('(可能不唯一)')) { message = `注意:该选择器可能不唯一!\n${selector}\n${preview}\n建议手动修改,是否继续应用?\n\n可以点击“取消”然后手动输入。`; } const userConfirmed = confirm(message); if (userConfirmed) { customQuestionSelector = selector.replace(/\s*\(可能不唯一.*\)/, ''); // 去掉提示文字 localStorage.setItem('customQuestionSelector', customQuestionSelector); alert(`选择器已保存并立即生效!\n${customQuestionSelector}`); log('自定义选择器更新为:', customQuestionSelector); lastHoveredQuestion = null; lastClickedQuestion = null; } else { // 允许用户手动输入 const manualSelector = prompt('请输入CSS选择器(例如 .exam-topic-item):', selector.replace(/\s*\(可能不唯一.*\)/, '')); if (manualSelector && manualSelector.trim()) { customQuestionSelector = manualSelector.trim(); localStorage.setItem('customQuestionSelector', customQuestionSelector); alert(`选择器已保存!\n${customQuestionSelector}`); lastHoveredQuestion = null; lastClickedQuestion = null; } } exitPickMode(); } // 拾取模式下按ESC退出 function pickKeydown(e) { if (e.key === 'Escape') { exitPickMode(); } } // 进入拾取模式 function enterPickMode() { if (pickMode) return; pickMode = true; originalCursor = document.body.style.cursor; document.body.style.cursor = 'crosshair'; document.addEventListener('mouseover', pickMouseOver); document.addEventListener('click', pickClick); document.addEventListener('keydown', pickKeydown); log('进入拾取模式,点击任意元素生成选择器'); } // ============= 题库搜索引擎 ============= function normalizeText(text) { let normalized = text.replace(/(/g, '(').replace(/)/g, ')'); normalized = normalized.replace(/\s+/g, '') .replace(/[,,.。??!!::;;(())\[\]【】]/g, '') .toLowerCase(); return normalized; } function calculateSimilarity(str1, str2) { const set1 = new Set(str1); const set2 = new Set(str2); let intersection = 0; for (const char of set1) { if (set2.has(char)) intersection++; } const union = set1.size + set2.size - intersection; return intersection / union; } function findQuestionInDatabase(query) { if (!query) return null; const cleanQuery = normalizeText(query); log('搜索查询:', cleanQuery); for (const item of QUESTION_DATABASE) { const cleanQuestion = normalizeText(item.question); if (cleanQuestion === cleanQuery) return item; } for (const item of QUESTION_DATABASE) { const cleanQuestion = normalizeText(item.question); if (cleanQuestion.includes(cleanQuery) || cleanQuery.includes(cleanQuestion)) return item; } let bestMatch = null; let highestSimilarity = 0.7; for (const item of QUESTION_DATABASE) { const cleanQuestion = normalizeText(item.question); const similarity = calculateSimilarity(cleanQuery, cleanQuestion); if (similarity > highestSimilarity) { highestSimilarity = similarity; bestMatch = item; } } if (bestMatch) log('相似匹配,相似度:', highestSimilarity); return bestMatch; } // ============= 样式设置 ============= GM_addStyle(` #search-panel { position: fixed; background: #ffffff; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); padding: 10px; z-index: 999999; display: none; font-size: 13px; color: #333; width: 300px; max-height: 400px; overflow-y: auto; transition: opacity 0.3s ease; cursor: default; opacity: 0.95; top: 100px; left: 100px; } #search-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; border-bottom: 1px solid #eee; padding-bottom: 5px; cursor: move; user-select: none; } #header-controls { display: flex; align-items: center; gap: 10px; } #pin-button { cursor: pointer; color: #999; font-size: 16px; padding: 0 5px; transition: all 0.3s ease; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 4px; } #pin-button:hover { background-color: #f0f0f0; color: #666; } #pin-button.pinned { color: #4CAF50; background-color: #e8f5e9; } #opacity-control { display: flex; align-items: center; margin-bottom: 8px; padding: 5px; border-bottom: 1px solid #eee; font-size: 12px; } #opacity-slider { flex: 1; margin: 0 10px; cursor: pointer; } #search-title { font-weight: bold; font-size: 14px; } #search-close { cursor: pointer; color: #999; font-size: 16px; } #search-close:hover { color: #555; } #selected-text { font-weight: bold; margin-bottom: 8px; white-space: normal; word-break: break-word; background: #f9f9f9; padding: 5px; border-radius: 3px; } .action-buttons { display: flex; margin-bottom: 8px; flex-wrap: wrap; } .action-button { cursor: pointer; padding: 3px 8px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 3px; margin-right: 8px; margin-bottom: 5px; font-size: 12px; } .action-button:hover { background: #e9e9e9; } #search-result { border-top: 1px dashed #eee; padding-top: 8px; max-height: 250px; overflow-y: auto; font-size: 13px; line-height: 1.5; } .result-item { margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px dashed #eee; } .loading { text-align: center; color: #888; padding: 10px 0; } .no-result { color: #888; text-align: center; padding: 15px 0; } .options-list { margin-top: 5px; padding-left: 20px; } .option { margin-bottom: 3px; } .answer { font-weight: bold; color: #2c7a4d; margin-top: 5px; } .hint { color: #888; font-size: 12px; margin: 5px 0; font-style: italic; } .result-actions { margin-top: 10px; font-size: 12px; color: #666; } .result-action { cursor: pointer; color: #1a75ff; margin-right: 10px; } .result-action:hover { text-decoration: underline; } #cached-answers { font-size: 12px; color: #888; margin-top: 5px; } #db-stats { font-size: 11px; color: #888; margin-top: 5px; } #activate-button, #settings-button, #pick-button { position: fixed; bottom: 20px; background: #4CAF50; color: white; padding: 8px 12px; border-radius: 4px; cursor: pointer; z-index: 999998; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: 0.2s; } #activate-button { right: 20px; } #settings-button { right: 100px; background: #2196F3; } #pick-button { right: 180px; background: #ff9800; } #activate-button:hover, #settings-button:hover, #pick-button:hover { opacity: 0.9; } `); // ============= 界面元素创建 ============= const activateButton = document.createElement('div'); activateButton.id = 'activate-button'; activateButton.textContent = '激活搜题助手'; document.body.appendChild(activateButton); const settingsBtn = document.createElement('div'); settingsBtn.id = 'settings-button'; settingsBtn.textContent = '⚙️ 手动设置'; document.body.appendChild(settingsBtn); const pickBtn = document.createElement('div'); pickBtn.id = 'pick-button'; pickBtn.textContent = '🔍 拾取选择器'; document.body.appendChild(pickBtn); const searchPanel = document.createElement('div'); searchPanel.id = 'search-panel'; searchPanel.innerHTML = `
智能考试助手
📌
×
透明度: 95%
题库: ${QUESTION_DATABASE.length}题
`; document.body.appendChild(searchPanel); // ============= 面板状态 ============= let isPinned = false; let panelVisible = false; let lastPanelPosition = { left: 100, top: 100 }; const answerCache = {}; function closePanel() { searchPanel.style.display = 'none'; activateButton.style.display = 'block'; panelVisible = false; isPinned = false; pinButton.classList.remove('pinned'); } function showPanelWithContent(text) { if (!panelVisible) { searchPanel.style.left = `${lastPanelPosition.left}px`; searchPanel.style.top = `${lastPanelPosition.top}px`; searchPanel.style.display = 'block'; panelVisible = true; activateButton.style.display = 'none'; } document.getElementById('selected-text').textContent = text; if (text && text !== "请选中题目文本") { setTimeout(searchAnswer, 300); } } // ============= 提取题目(优先使用悬停/点击记录) ============= function extractCurrentQuestion() { try { if (lastHoveredQuestion) { log('使用悬停记录:', lastHoveredQuestion); return lastHoveredQuestion; } if (lastClickedQuestion) { log('使用点击记录:', lastClickedQuestion); return lastClickedQuestion; } if (customQuestionSelector) { const elem = document.querySelector(customQuestionSelector); if (elem) { const text = getQuestionTextFromElement(elem); if (text) { log('使用自定义选择器获取题目:', text); return text; } } } const titleElement = document.querySelector('.exam-topic-item-title-name'); if (titleElement && titleElement.textContent) { const text = titleElement.textContent.trim(); if (text) return text; } return null; } catch (e) { log('提取题目失败', e); return null; } } function searchAnswer() { const query = document.getElementById('selected-text').textContent.trim(); if (!query) return; document.getElementById('search-result').innerHTML = '
正在搜索答案...
'; if (answerCache[query]) { displayResults(answerCache[query]); return; } setTimeout(() => { const result = findQuestionInDatabase(query); if (result) { answerCache[query] = result; displayResults(result); } else { document.getElementById('search-result').innerHTML = '
未找到答案,请尝试修改关键词
'; } updateCacheStats(); }, 300); } function displayResults(result) { const container = document.getElementById('search-result'); if (!result) return; let html = '
'; html += `
题目: ${escapeHtml(result.question)}
`; if (result.options && result.options.length) { html += '
'; result.options.forEach(opt => { const isAnswer = opt.includes(result.answer) || opt.startsWith(result.answer + '.'); html += `
${escapeHtml(opt)}
`; }); html += '
'; } html += `
答案: ${escapeHtml(result.answer)}
`; if (result.explanation) { html += `
解析: ${escapeHtml(result.explanation)}
`; } html += `
复制答案 复制全部
`; container.innerHTML = html; container.querySelector('.copy-answer')?.addEventListener('click', () => { GM_setClipboard(result.answer); alert('答案已复制'); }); container.querySelector('.copy-all')?.addEventListener('click', () => { let text = `题目: ${result.question}\n`; if (result.options?.length) text += result.options.join('\n') + '\n'; text += `答案: ${result.answer}`; if (result.explanation) text += `\n解析: ${result.explanation}`; GM_setClipboard(text); alert('已复制题目和答案'); }); } function escapeHtml(str) { return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }); } // 自动填答案 function autoFillAnswer(answer) { if (!answer) return false; log('自动填写答案:', answer); let cleanAnswer = answer.trim().replace(/^【正确答案】/, '').replace(/^答案[::]/, '').trim(); const trueValues = ['正确', '对', '是', '√', 'A', 'A.']; const falseValues = ['错误', '错', '否', '×', 'B', 'B.']; const radios = document.querySelectorAll('input[type="radio"]'); const checkboxes = document.querySelectorAll('input[type="checkbox"]'); if (radios.length > 0) { if (trueValues.some(v => cleanAnswer.includes(v)) && falseValues.some(v => cleanAnswer.includes(v))) { for (let radio of radios) { const label = getLabelForInput(radio); if (label && label.textContent.includes(cleanAnswer)) { radio.click(); return true; } } } else { for (let radio of radios) { const label = getLabelForInput(radio); if (!label) continue; const labelText = label.textContent.trim(); if (labelText.startsWith(cleanAnswer + '.') || labelText === cleanAnswer || (cleanAnswer.length === 1 && 'ABCD'.includes(cleanAnswer.toUpperCase()) && labelText.toUpperCase().startsWith(cleanAnswer.toUpperCase()))) { radio.click(); return true; } if (labelText.includes(cleanAnswer)) { radio.click(); return true; } } } } if (checkboxes.length > 0) { const parts = cleanAnswer.split(/[,,]/).map(p => p.trim()); for (let part of parts) { for (let checkbox of checkboxes) { const label = getLabelForInput(checkbox); if (!label) continue; const labelText = label.textContent.trim(); if (labelText.startsWith(part + '.') || labelText === part || (part.length === 1 && 'ABCD'.includes(part.toUpperCase()) && labelText.toUpperCase().startsWith(part.toUpperCase()))) { checkbox.click(); } else if (labelText.includes(part)) { checkbox.click(); } } } return true; } const textInputs = document.querySelectorAll('input[type="text"], textarea'); if (textInputs.length) { textInputs[0].value = cleanAnswer; textInputs[0].dispatchEvent(new Event('input', { bubbles: true })); return true; } return false; } function getLabelForInput(input) { if (input.labels && input.labels.length) return input.labels[0]; const id = input.id; if (id) { const label = document.querySelector(`label[for="${id}"]`); if (label) return label; } let parent = input.parentElement; while (parent) { if (parent.tagName === 'LABEL') return parent; parent = parent.parentElement; } return null; } function getCurrentAnswer() { const answerDiv = document.querySelector('#search-result .answer'); if (answerDiv) { let text = answerDiv.textContent; const match = text.match(/答案[::]\s*(.+)/); return match ? match[1].trim() : text.trim(); } return null; } function updateCacheStats() { const count = Object.keys(answerCache).length; const elem = document.getElementById('cached-answers'); if (count > 0) elem.textContent = `已缓存 ${count} 个答案`; else elem.textContent = ''; } // ============= 事件监听 ============= const pinButton = document.getElementById('pin-button'); pinButton.addEventListener('click', () => { isPinned = !isPinned; pinButton.classList.toggle('pinned', isPinned); pinButton.title = isPinned ? '取消置顶' : '置顶'; }); document.getElementById('search-close').addEventListener('click', closePanel); document.addEventListener('mousedown', (e) => { if (!isPinned && panelVisible && !searchPanel.contains(e.target) && !activateButton.contains(e.target) && !settingsBtn.contains(e.target) && !pickBtn.contains(e.target)) { closePanel(); } }); // 快捷键 document.addEventListener('keydown', (e) => { if (!isPinned && e.key === 'Escape' && panelVisible) { closePanel(); e.preventDefault(); } if (e.key === 'q' && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { const activeEl = document.activeElement; const isInputField = activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable || activeEl.getAttribute('contenteditable') === 'true'); if (!isInputField) { const question = extractCurrentQuestion(); if (question) { showPanelWithContent(question); } else { showPanelWithContent("请选中题目文本"); } e.preventDefault(); } } if (e.altKey && e.key === 'a' && panelVisible) { document.getElementById('search-btn').click(); e.preventDefault(); } }); activateButton.addEventListener('click', () => { const question = extractCurrentQuestion(); if (question) { showPanelWithContent(question); } else { showPanelWithContent("请选中题目文本"); } }); settingsBtn.addEventListener('click', () => { const newSelector = prompt( '请输入题目文本的CSS选择器(例如 .question-title, .exam-topic-item-title-name):\n' + '留空则使用自动检测。\n\n当前选择器:' + (customQuestionSelector || '未设置'), customQuestionSelector ); if (newSelector !== null) { customQuestionSelector = newSelector.trim(); localStorage.setItem('customQuestionSelector', customQuestionSelector); alert('选择器已保存并立即生效!无需刷新页面。'); lastHoveredQuestion = null; lastClickedQuestion = null; } }); // 拾取按钮(修复:不会捕获按钮自身) pickBtn.addEventListener('click', (e) => { e.stopPropagation(); if (pickMode) { exitPickMode(); } else { enterPickMode(); } }); document.getElementById('copy-btn').addEventListener('click', () => { const text = document.getElementById('selected-text').textContent; if (text) { GM_setClipboard(text); const btn = document.getElementById('copy-btn'); const orig = btn.textContent; btn.textContent = '已复制!'; setTimeout(() => btn.textContent = orig, 1500); } }); document.getElementById('search-btn').addEventListener('click', searchAnswer); document.getElementById('auto-answer').addEventListener('click', () => { const answer = getCurrentAnswer(); if (answer) { autoFillAnswer(answer); } else { alert('请先搜索答案'); } }); // ============= 划词选中自动搜索 ============= let selectionTimer = null; document.addEventListener('mouseup', (e) => { if (searchPanel.contains(e.target) || activateButton.contains(e.target) || settingsBtn.contains(e.target) || pickBtn.contains(e.target)) return; if (selectionTimer) clearTimeout(selectionTimer); selectionTimer = setTimeout(() => { const selection = window.getSelection(); const selectedText = selection.toString().trim(); if (selectedText && selectedText.length >= 3) { let rect = null; if (selection.rangeCount > 0) { rect = selection.getRangeAt(0).getBoundingClientRect(); } const pos = rect ? { left: rect.left + window.scrollX, top: rect.bottom + window.scrollY + 5 } : { left: window.innerWidth / 2 - 150, top: window.innerHeight / 3 }; if (!panelVisible) { searchPanel.style.left = `${pos.left}px`; searchPanel.style.top = `${pos.top}px`; } showPanelWithContent(selectedText); } }, 50); }); // ============= 鼠标悬停捕获题目(精确提取题干) ============= document.addEventListener('mouseenter', (e) => { if (pickMode) return; // 拾取模式下不干扰 const targetElem = e.target.nodeType === Node.ELEMENT_NODE ? e.target : e.target.parentElement; if (!targetElem) return; if (customQuestionSelector) { let parent = targetElem; let depth = 0; while (parent && parent !== document.body && depth < 10) { if (parent.matches && parent.matches(customQuestionSelector)) { const question = getQuestionTextFromElement(parent); if (question) { lastHoveredQuestion = question; log('悬停捕获题目(自定义选择器):', lastHoveredQuestion); return; } } parent = parent.parentElement; depth++; } } const topicItem = targetElem.closest('.exam-topic-item'); if (topicItem) { const titleElem = topicItem.querySelector('.exam-topic-item-title-name'); if (titleElem && titleElem.textContent) { lastHoveredQuestion = titleElem.textContent.trim(); log('悬停捕获题目(类名):', lastHoveredQuestion); return; } } let target = targetElem; let depth = 0; while (target && target !== document.body && depth < 10) { if (target.nodeType === Node.ELEMENT_NODE) { const text = target.textContent ? target.textContent.trim() : ''; if (/^\d+[\.、]/.test(text) && text.length > 10 && text.length < 500 && !target.querySelector('input[type="radio"], input[type="checkbox"]')) { const pure = extractPureQuestion(text); if (pure) { lastHoveredQuestion = pure; log('悬停捕获题目(后备):', lastHoveredQuestion); return; } } } target = target.parentElement; depth++; } }, true); // 点击捕获题目(同样精确提取) document.addEventListener('click', (e) => { if (pickMode) return; const targetElem = e.target.nodeType === Node.ELEMENT_NODE ? e.target : e.target.parentElement; if (!targetElem) return; if (customQuestionSelector) { let parent = targetElem; let depth = 0; while (parent && parent !== document.body && depth < 10) { if (parent.matches && parent.matches(customQuestionSelector)) { const question = getQuestionTextFromElement(parent); if (question) { lastClickedQuestion = question; log('点击捕获题目(自定义选择器):', lastClickedQuestion); return; } } parent = parent.parentElement; depth++; } } const topicItem = targetElem.closest('.exam-topic-item'); if (topicItem) { const titleElem = topicItem.querySelector('.exam-topic-item-title-name'); if (titleElem && titleElem.textContent) { lastClickedQuestion = titleElem.textContent.trim(); log('点击捕获题目(类名):', lastClickedQuestion); return; } } let target = targetElem; let depth = 0; while (target && target !== document.body && depth < 10) { if (target.nodeType === Node.ELEMENT_NODE) { const text = target.textContent ? target.textContent.trim() : ''; if (/^\d+[\.、]/.test(text) && text.length > 10 && text.length < 500 && !target.querySelector('input')) { const pure = extractPureQuestion(text); if (pure) { lastClickedQuestion = pure; log('点击捕获题目(后备):', lastClickedQuestion); return; } } } target = target.parentElement; depth++; } }); // ============= 面板拖拽 ============= let dragActive = false; let dragStartX = 0, dragStartY = 0; let panelStartLeft = 0, panelStartTop = 0; function onDragStart(e) { if (e.target.closest('#search-result, .action-buttons, #opacity-control')) return; if (e.target === searchPanel || e.target.closest('#search-header')) { dragActive = true; dragStartX = e.clientX; dragStartY = e.clientY; const style = window.getComputedStyle(searchPanel); panelStartLeft = parseFloat(style.left); panelStartTop = parseFloat(style.top); e.preventDefault(); } } function onDragMove(e) { if (!dragActive) return; e.preventDefault(); const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; let newLeft = panelStartLeft + dx; let newTop = panelStartTop + dy; newLeft = Math.max(0, Math.min(window.innerWidth - searchPanel.offsetWidth, newLeft)); newTop = Math.max(0, Math.min(window.innerHeight - searchPanel.offsetHeight, newTop)); searchPanel.style.left = `${newLeft}px`; searchPanel.style.top = `${newTop}px`; } function onDragEnd() { if (dragActive) { lastPanelPosition.left = parseFloat(searchPanel.style.left); lastPanelPosition.top = parseFloat(searchPanel.style.top); dragActive = false; } } searchPanel.addEventListener('mousedown', onDragStart); document.addEventListener('mousemove', onDragMove); document.addEventListener('mouseup', onDragEnd); // 透明度控制 const slider = document.getElementById('opacity-slider'); const opacityVal = document.getElementById('opacity-value'); slider.addEventListener('input', (e) => { const val = e.target.value; searchPanel.style.opacity = val / 100; opacityVal.textContent = `${val}%`; }); log('初始化完成,题库数量:', QUESTION_DATABASE.length); })();