// ==UserScript== // @name 椰子云验证码助手 // @namespace http://tampermonkey.net/ // @version 0.3 // @description 通过椰子云API获取手机验证码 // @author You // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // 检测当前页面是否包含'验证码'字样 if (!document.body.innerText.includes('验证码')) { console.log('当前页面未检测到验证码字样,脚本停止执行'); return; } // 配置信息 let config = { username: GM_getValue('username', ''), password: GM_getValue('password', ''), token: GM_getValue('token', ''), apiDomain: GM_getValue('apiDomain', 'http://api.sqhyw.net:90'), // 默认主域名 projectId: GM_getValue('projectId', ''), // 默认项目ID special: '0', // 不使用专属号码 currentMobile: '', // 当前使用的手机号 floatingButton: GM_getValue('floatingButton', true), // 是否显示悬浮按钮 projects: [], // 项目列表缓存 autoGetSms: GM_getValue('autoGetSms', true), // 自动获取短信 smsPollingTimer: null, // 短信轮询定时器 smsPollingCount: 0 // 短信轮询次数 }; // 统计'验证码'字样出现的次数 const verificationCodeCount = (document.body.innerText.match(/验证码/g) || []).length; // 如果'验证码'字样出现超过2次,则停止执行 if (verificationCodeCount > 2) { console.log('当前页面检测到验证码字样超过2次,脚本停止执行'); return; } // 添加CSS样式 GM_addStyle(` #yz-float-btn { position: fixed; top: 140px; left: 140px; width: 50px; height: 50px; border-radius: 50%; background: #3498db; color: white; text-align: center; line-height: 50px; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.3); z-index: 9998; font-size: 20px; user-select: none; } #yz-float-btn:hover { background: #2980b9; } `); // 创建UI界面 function createUI() { // 创建插件面板 const panel = document.createElement('div'); panel.id = 'yz-panel'; panel.style.cssText = ` position: fixed; width: 300px; background: #fff; border: 1px solid #ccc; border-radius: 5px; padding: 10px; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.2); font-family: Arial, sans-serif; display: none; `; // 添加标题 const title = document.createElement('div'); title.textContent = '椰子云验证码助手'; title.style.cssText = 'font-weight: bold; margin-bottom: 10px; font-size: 14px; text-align: center;'; panel.appendChild(title); // 添加内容容器 const content = document.createElement('div'); content.id = 'yz-content'; panel.appendChild(content); // 创建配置界面 const configHtml = ` <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">用户名:</div> <input id="yz-username" type="text" value="${config.username}" style="width: 100%; padding: 5px; box-sizing: border-box;"> </div> <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">密码:</div> <input id="yz-password" type="password" value="${config.password}" style="width: 100%; padding: 5px; box-sizing: border-box;"> </div> <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">接口域名:</div> <select id="yz-domain" style="width: 100%; padding: 5px; box-sizing: border-box;"> <option value="http://api.sqhyw.net:90" ${config.apiDomain === 'http://api.sqhyw.net:90' ? 'selected' : ''}>主域名</option> <option value="http://api.nnanx.com:90" ${config.apiDomain === 'http://api.nnanx.com:90' ? 'selected' : ''}>备用域名</option> </select> </div> <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">项目ID/对接码:</div> <div style="display: flex;"> <input id="yz-project-id" type="text" value="${config.projectId}" style="flex: 1; padding: 5px; box-sizing: border-box; margin-right: 5px;"> <button id="yz-search-project" style="padding: 5px 10px;">搜索项目</button> </div> </div> <div style="margin-bottom: 10px;"> <label> <input id="yz-floating-btn" type="checkbox" ${config.floatingButton ? 'checked' : ''}> 显示悬浮按钮 </label> </div> <div style="margin-bottom: 10px;"> <label> <input id="yz-auto-sms-config" type="checkbox" ${config.autoGetSms ? 'checked' : ''}> 自动获取验证码 </label> </div> <div style="display: flex; justify-content: space-between;"> <button id="yz-save" style="padding: 5px 10px;">保存配置</button> <button id="yz-login" style="padding: 5px 10px;">登录</button> <button id="yz-cancel" style="padding: 5px 10px;">取消</button> </div> `; content.innerHTML = configHtml; // 添加面板到页面 document.body.appendChild(panel); // 创建悬浮按钮 if (config.floatingButton) { createFloatingButton(); } // 添加事件监听 document.getElementById('yz-save').addEventListener('click', saveConfig); document.getElementById('yz-login').addEventListener('click', login); document.getElementById('yz-cancel').addEventListener('click', togglePanel); document.getElementById('yz-search-project').addEventListener('click', searchProjects); // 注册菜单命令 GM_registerMenuCommand('椰子云验证码助手', togglePanel); } // 创建悬浮按钮 function createFloatingButton() { // 如果已经存在,先移除 const existingButton = document.getElementById('yz-float-btn'); if (existingButton) { existingButton.remove(); } if (!config.floatingButton) { return; } const floatBtn = document.createElement('div'); floatBtn.id = 'yz-float-btn'; floatBtn.textContent = '验'; floatBtn.title = '椰子云验证码助手'; floatBtn.addEventListener('click', togglePanel); document.body.appendChild(floatBtn); // Add event listeners to make the icon draggable const icon = document.getElementById('yz-float-btn'); let isDragging = false; let offsetX, offsetY; // Restore icon position from localStorage const savedPosition = localStorage.getItem('iconPosition'); if (savedPosition) { const { left, top } = JSON.parse(savedPosition); icon.style.left = `${left}px`; icon.style.top = `${top}px`; } else { // Set default position if no saved position icon.style.left = '140px'; icon.style.top = '140px'; } icon.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - icon.getBoundingClientRect().left; offsetY = e.clientY - icon.getBoundingClientRect().top; }); window.addEventListener('mousemove', (e) => { if (isDragging) { const left = e.clientX - offsetX; const top = e.clientY - offsetY; icon.style.left = `${left}px`; icon.style.top = `${top}px`; // Store icon position in localStorage localStorage.setItem('iconPosition', JSON.stringify({ left, top })); // Update window position based on icon position const windowElement = document.getElementById('yz-panel'); windowElement.style.left = `${left + 50}px`; // Adjust the offset as needed windowElement.style.top = `${top + 50}px`; // Adjust the offset as needed } }); window.addEventListener('mouseup', () => { isDragging = false; }); } // 切换到操作界面 function switchToOperation() { const content = document.getElementById('yz-content'); content.innerHTML = ` <div> <div style="margin-bottom: 5px;">余额: <span id="yz-balance">0</span></div> <div style="margin-bottom: 10px;">当前号码: <span id="yz-mobile">无</span></div> <div style="display: flex; margin-bottom: 10px;"> <button id="yz-get-mobile" style="flex: 1; margin-right: 5px; padding: 5px;">获取号码</button> <button id="yz-get-sms" style="flex: 1; margin-right: 5px; padding: 5px;">获取短信</button> <button id="yz-free-mobile" style="flex: 1; padding: 5px;">释放号码</button> </div> <div style="display: flex; margin-bottom: 10px;"> <div style="flex: 1; margin-right: 5px;">项目ID:</div> <input id="yz-main-project-id" type="text" value="${config.projectId}" style="flex: 2; padding: 5px; box-sizing: border-box;"> </div> <div style="margin-bottom: 10px;"> <div style="display: flex; align-items: center; margin-bottom: 5px;"> <label style="display: flex; align-items: center; margin-right: 10px;" title="自动获取验证码"> <input id="yz-auto-sms" type="checkbox" ${config.autoGetSms ? 'checked' : ''} style="margin-right: 0;"> </label> <div>验证码: <span id="yz-sms-status"></span></div> </div> <div id="yz-sms-content" style="padding: 5px; background: #f5f5f5; min-height: 50px; word-break: break-all;"></div> </div> <div style="display: flex; justify-content: space-between;"> <button id="yz-settings" style="padding: 5px 10px;">设置</button> <button id="yz-close" style="padding: 5px 10px;">关闭</button> </div> </div> `; // 添加操作界面的事件监听 document.getElementById('yz-get-mobile').addEventListener('click', getMobile); document.getElementById('yz-get-sms').addEventListener('click', getSms); document.getElementById('yz-free-mobile').addEventListener('click', freeMobile); document.getElementById('yz-settings').addEventListener('click', switchToConfig); document.getElementById('yz-close').addEventListener('click', togglePanel); // 项目ID更改时自动保存 document.getElementById('yz-main-project-id').addEventListener('change', function() { const newProjectId = this.value.trim(); config.projectId = newProjectId; GM_setValue('projectId', newProjectId); showMessage('项目ID已更新'); }); // 自动获取短信选项变更 document.getElementById('yz-auto-sms').addEventListener('change', function() { config.autoGetSms = this.checked; GM_setValue('autoGetSms', config.autoGetSms); showMessage(config.autoGetSms ? '已开启自动获取验证码' : '已关闭自动获取验证码'); }); // 获取余额信息 getBalance(); } // 切换到配置界面 function switchToConfig() { const content = document.getElementById('yz-content'); content.innerHTML = ` <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">用户名:</div> <input id="yz-username" type="text" value="${config.username}" style="width: 100%; padding: 5px; box-sizing: border-box;"> </div> <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">密码:</div> <input id="yz-password" type="password" value="${config.password}" style="width: 100%; padding: 5px; box-sizing: border-box;"> </div> <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">接口域名:</div> <select id="yz-domain" style="width: 100%; padding: 5px; box-sizing: border-box;"> <option value="http://api.sqhyw.net:90" ${config.apiDomain === 'http://api.sqhyw.net:90' ? 'selected' : ''}>主域名</option> <option value="http://api.nnanx.com:90" ${config.apiDomain === 'http://api.nnanx.com:90' ? 'selected' : ''}>备用域名</option> </select> </div> <div style="margin-bottom: 10px;"> <div style="margin-bottom: 5px;">项目ID/对接码:</div> <input id="yz-project-id" type="text" value="${config.projectId}" style="width: 100%; padding: 5px; box-sizing: border-box;"> </div> <div style="margin-bottom: 10px;"> <label> <input id="yz-floating-btn" type="checkbox" ${config.floatingButton ? 'checked' : ''}> 显示悬浮按钮 </label> </div> <div style="margin-bottom: 10px;"> <label> <input id="yz-auto-sms-config" type="checkbox" ${config.autoGetSms ? 'checked' : ''}> 自动获取验证码 </label> </div> <div style="display: flex; justify-content: space-between;"> <button id="yz-save" style="padding: 5px 10px;">保存配置</button> <button id="yz-login" style="padding: 5px 10px;">登录</button> <button id="yz-cancel" style="padding: 5px 10px;">取消</button> </div> `; // 添加配置界面的事件监听 document.getElementById('yz-save').addEventListener('click', saveConfig); document.getElementById('yz-login').addEventListener('click', login); document.getElementById('yz-cancel').addEventListener('click', togglePanel); } // 保存配置 function saveConfig() { config.username = document.getElementById('yz-username').value; config.password = document.getElementById('yz-password').value; config.apiDomain = document.getElementById('yz-domain').value; config.projectId = document.getElementById('yz-project-id').value; config.floatingButton = document.getElementById('yz-floating-btn').checked; config.autoGetSms = document.getElementById('yz-auto-sms-config').checked; GM_setValue('username', config.username); GM_setValue('password', config.password); GM_setValue('apiDomain', config.apiDomain); GM_setValue('projectId', config.projectId); GM_setValue('floatingButton', config.floatingButton); GM_setValue('autoGetSms', config.autoGetSms); // 更新悬浮按钮状态 createFloatingButton(); showMessage('配置已保存'); // 切换到操作界面 switchToOperation(); } // 登录 function login() { if (!config.username || !config.password) { showMessage('请输入用户名和密码'); return; } const url = `${config.apiDomain}/api/logins?username=${encodeURIComponent(config.username)}&password=${encodeURIComponent(config.password)}`; GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.token) { config.token = data.token; GM_setValue('token', config.token); showMessage('登录成功'); // 登录成功后获取项目列表 getProjects(); switchToOperation(); } else { showMessage('登录失败: ' + (data.message || '未知错误')); } } catch (e) { showMessage('解析响应失败: ' + e.message); } }, onerror: function(error) { showMessage('请求错误: ' + error.statusText); } }); } // 获取余额 function getBalance() { if (!config.token) { showMessage('请先登录'); return; } const url = `${config.apiDomain}/api/get_myinfo?token=${encodeURIComponent(config.token)}`; GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.message === 'ok' && data.data && data.data.length > 0) { document.getElementById('yz-balance').textContent = data.data[0].money; } else { showMessage('获取余额失败: ' + (data.message || '未知错误')); } } catch (e) { showMessage('解析响应失败: ' + e.message); } }, onerror: function(error) { showMessage('请求错误: ' + error.statusText); } }); } // 获取手机号 function getMobile() { if (!config.token) { showMessage('请先登录'); return; } if (!config.projectId) { showMessage('请设置项目ID'); return; } // 停止之前的轮询 stopSmsPolling(); let url = `${config.apiDomain}/api/get_mobile?token=${encodeURIComponent(config.token)}&project_id=${encodeURIComponent(config.projectId)}`; GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.message === 'ok' && data.mobile) { config.currentMobile = data.mobile; document.getElementById('yz-mobile').textContent = data.mobile; // 复制到剪贴板 copyToClipboard(data.mobile); showMessage('获取号码成功,已复制到剪贴板'); // 清空之前的验证码 document.getElementById('yz-sms-content').textContent = ''; // 如果开启了自动获取验证码,启动轮询 if (config.autoGetSms) { startSmsPolling(); } } else { showMessage('获取号码失败: ' + (data.message || '未知错误')); } } catch (e) { showMessage('解析响应失败: ' + e.message); } }, onerror: function(error) { showMessage('请求错误: ' + error.statusText); } }); } // 复制文本到剪贴板 function copyToClipboard(text) { try { // 使用现代剪贴板API if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text) .then(() => { console.log('文本已成功复制到剪贴板'); }) .catch(err => { console.error('复制到剪贴板失败: ', err); // 如果现代API失败,回退到传统方法 fallbackCopyToClipboard(text); }); } else { // 回退到传统方法 fallbackCopyToClipboard(text); } } catch (e) { console.error('复制到剪贴板出错: ', e); // 最终回退方法 fallbackCopyToClipboard(text); } } // 传统的复制到剪贴板方法 function fallbackCopyToClipboard(text) { const textarea = document.createElement('textarea'); textarea.value = text; textarea.setAttribute('readonly', ''); textarea.style.position = 'absolute'; textarea.style.left = '-9999px'; document.body.appendChild(textarea); // 保存当前选中的内容 const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; textarea.select(); textarea.setSelectionRange(0, textarea.value.length); let success = false; try { success = document.execCommand('copy'); if (!success) { console.error('复制文本失败'); // 显示提示给用户 showMessage('无法自动复制验证码,请手动复制: ' + text); } } catch (err) { console.error('复制命令出错: ', err); // 显示提示给用户 showMessage('无法自动复制验证码,请手动复制: ' + text); } document.body.removeChild(textarea); // 恢复之前的选中内容 if (selected) { document.getSelection().removeAllRanges(); document.getSelection().addRange(selected); } return success; } // 获取短信 function getSms() { if (!config.token) { showMessage('请先登录'); return; } if (!config.projectId) { showMessage('请设置项目ID'); return; } if (!config.currentMobile) { showMessage('请先获取手机号'); return; } // 更新状态为等待中 updateSmsStatus('等待中'); let url = `${config.apiDomain}/api/get_message?token=${encodeURIComponent(config.token)}&project_id=${encodeURIComponent(config.projectId)}&phone_num=${encodeURIComponent(config.currentMobile)}`; GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.message === 'ok') { // 直接检查短信内容 if (data.data && data.data.length > 0 && data.data[0].content) { const smsContent = data.data[0].content; const smsContentElem = document.getElementById('yz-sms-content'); smsContentElem.textContent = smsContent; // 尝试从短信内容提取验证码 let verificationCode = extractVerificationCode(smsContent); if (verificationCode) { // 显示提取到的验证码 smsContentElem.innerHTML = `<strong style="color:#f00;">${verificationCode}</strong> - ${smsContent}`; // 复制验证码到剪贴板 copyToClipboard(verificationCode); showMessage(`验证码 ${verificationCode} 已复制到剪贴板`); updateSmsStatus('已获取'); // 停止轮询 stopSmsPolling(); } else if (data.code) { // 如果API返回了code字段,也使用它 smsContentElem.innerHTML = `<strong style="color:#f00;">${data.code}</strong> - ${smsContent}`; copyToClipboard(data.code); showMessage(`验证码 ${data.code} 已复制到剪贴板`); updateSmsStatus('已获取'); // 停止轮询 stopSmsPolling(); } else { updateSmsStatus('等待中'); if (!config.smsPollingTimer) { // 只在非轮询模式下显示 showMessage('短信已到达但未能提取验证码,请手动查看'); } } } else if (data.code) { // 如果API直接提供了验证码 const smsContent = document.getElementById('yz-sms-content'); smsContent.innerHTML = `<strong style="color:#f00;">${data.code}</strong> - ${(data.data && data.data.length > 0 ? data.data[0].modle : '')}`; // 复制验证码到剪贴板 copyToClipboard(data.code); showMessage(`验证码 ${data.code} 已复制到剪贴板`); updateSmsStatus('已获取'); // 停止轮询 stopSmsPolling(); } else { updateSmsStatus('等待中'); if (!config.smsPollingTimer) { // 只在非轮询模式下显示 showMessage('短信未到达,请稍后再试'); } } } else { // 不再显示获取失败提示 updateSmsStatus('等待中'); } } catch (e) { // 不再显示解析失败提示 updateSmsStatus('等待中'); console.error('解析响应失败: ' + e.message); } }, onerror: function(error) { // 不再显示请求错误提示 updateSmsStatus('等待中'); console.error('请求错误: ' + error.statusText); } }); } // 从短信内容提取验证码 function extractVerificationCode(content) { if (!content) return null; // 记录找到的验证码,用于调试 console.log('尝试从内容中提取验证码: ', content); // 尝试多种匹配模式 // 1. 匹配"验证码为:123456"或"验证码是123456"格式 let match = content.match(/验证码为[::]\s*([0-9]{4,6})/); if (match) { console.log('匹配到验证码为格式: ', match[1]); return match[1]; } match = content.match(/验证码是\s*([0-9]{4,6})/); if (match) { console.log('匹配到验证码是格式: ', match[1]); return match[1]; } // 2. 匹配"[动态码|验证码|校验码|码|code]:123456"格式 match = content.match(/(?:动态码|验证码|校验码|码|code)[::]\s*([0-9]{4,6})/i); if (match) { console.log('匹配到各种码格式: ', match[1]); return match[1]; } // 3. 匹配开头可能有数字-文字的格式,提取开头的数字 match = content.match(/^([0-9]{4,6})-/); if (match) { console.log('匹配到开头数字-: ', match[1]); return match[1]; } // 4. 直接匹配4-6位数字 match = content.match(/[^0-9]([0-9]{4,6})[^0-9]/); if (match) { console.log('匹配到4-6位数字: ', match[1]); return match[1]; } // 5. 最后尝试匹配任意独立的4-6位数字 match = content.match(/\b([0-9]{4,6})\b/); if (match) { console.log('匹配到独立数字: ', match[1]); return match[1]; } console.log('未能匹配到验证码'); return null; } // 开始轮询获取短信 function startSmsPolling() { // 先停止之前的轮询 stopSmsPolling(); // 更新状态 updateSmsStatus('等待中 (自动)'); // 重置计数 config.smsPollingCount = 0; // 启动轮询 config.smsPollingTimer = setInterval(() => { config.smsPollingCount++; // 最多轮询24次(2分钟) if (config.smsPollingCount > 24) { updateSmsStatus('超时'); showMessage('自动获取验证码超时,请手动获取或重试'); stopSmsPolling(); return; } // 更新状态 updateSmsStatus(`等待中 (自动 ${config.smsPollingCount}/24)`); // 获取短信 getSms(); }, 5000); // 每5秒查询一次 } // 停止轮询获取短信 function stopSmsPolling() { if (config.smsPollingTimer) { clearInterval(config.smsPollingTimer); config.smsPollingTimer = null; } } // 更新短信状态 function updateSmsStatus(status) { const statusElem = document.getElementById('yz-sms-status'); if (statusElem) { statusElem.textContent = status; } } // 释放号码 function freeMobile() { if (!config.token) { showMessage('请先登录'); return; } if (!config.currentMobile) { showMessage('没有需要释放的号码'); return; } // 停止轮询 stopSmsPolling(); let url = `${config.apiDomain}/api/free_mobile?token=${encodeURIComponent(config.token)}&phone_num=${encodeURIComponent(config.currentMobile)}`; if (config.projectId) { url += `&project_id=${encodeURIComponent(config.projectId)}`; } GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.message === 'ok') { config.currentMobile = ''; document.getElementById('yz-mobile').textContent = '无'; document.getElementById('yz-sms-content').textContent = ''; updateSmsStatus(''); showMessage('释放号码成功'); } else { showMessage('释放号码失败: ' + (data.message || '未知错误')); } } catch (e) { showMessage('解析响应失败: ' + e.message); } }, onerror: function(error) { showMessage('请求错误: ' + error.statusText); } }); } // 显示/隐藏面板 function togglePanel() { const panel = document.getElementById('yz-panel'); if (panel.style.display === 'none' || !panel.style.display) { panel.style.display = 'block'; // 如果已登录,显示操作界面 if (config.token) { switchToOperation(); // 检查是否有保存的手机号,并显示 if (config.currentMobile) { document.getElementById('yz-mobile').textContent = config.currentMobile; } } } else { // 关闭面板前停止轮询 stopSmsPolling(); panel.style.display = 'none'; } } // 显示消息 function showMessage(message) { const messageDiv = document.createElement('div'); messageDiv.textContent = message; messageDiv.style.cssText = ` position: fixed; top: 10px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.7); color: white; padding: 10px 20px; border-radius: 5px; z-index: 10000; `; document.body.appendChild(messageDiv); setTimeout(() => { document.body.removeChild(messageDiv); }, 3000); } // 获取项目列表 function getProjects() { if (!config.token) { showMessage('请先登录'); return Promise.reject('未登录'); } return new Promise((resolve, reject) => { const url = `${config.apiDomain}/api/get_projects?token=${encodeURIComponent(config.token)}`; GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.message === 'ok' && data.data) { config.projects = data.data; resolve(data.data); } else { showMessage('获取项目列表失败: ' + (data.message || '未知错误')); reject('获取失败'); } } catch (e) { showMessage('解析响应失败: ' + e.message); reject(e); } }, onerror: function(error) { showMessage('请求错误: ' + error.statusText); reject(error); } }); }); } // 搜索项目 function searchProjects() { if (!config.token) { showMessage('请先登录'); return; } // 获取项目列表 const loadingDiv = document.createElement('div'); loadingDiv.id = 'yz-loading'; loadingDiv.textContent = '正在加载项目列表...'; loadingDiv.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); color: white; display: flex; justify-content: center; align-items: center; z-index: 10001; `; document.body.appendChild(loadingDiv); const searchProject = () => { // 创建项目搜索面板 const projectPanel = document.createElement('div'); projectPanel.id = 'yz-project-panel'; projectPanel.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; max-width: 500px; max-height: 80vh; background: #fff; border: 1px solid #ccc; border-radius: 5px; padding: 10px; z-index: 10002; box-shadow: 0 0 10px rgba(0,0,0,0.3); display: flex; flex-direction: column; overflow: hidden; `; projectPanel.innerHTML = ` <div style="font-weight: bold; margin-bottom: 10px; text-align: center;">搜索项目</div> <div style="margin-bottom: 10px; display: flex;"> <input id="yz-project-search" type="text" placeholder="输入关键词搜索项目" style="flex: 1; padding: 5px; margin-right: 5px;"> <button id="yz-search-btn" style="padding: 5px 10px;">搜索</button> </div> <div id="yz-project-list" style="flex: 1; overflow-y: auto; max-height: 60vh;"></div> <div style="margin-top: 10px; text-align: right;"> <button id="yz-project-cancel" style="padding: 5px 10px;">取消</button> </div> `; document.body.appendChild(projectPanel); // 添加搜索事件 document.getElementById('yz-search-btn').addEventListener('click', () => { const keyword = document.getElementById('yz-project-search').value.toLowerCase(); displayProjectList(keyword); }); // 添加回车键搜索 document.getElementById('yz-project-search').addEventListener('keypress', (e) => { if (e.key === 'Enter') { const keyword = document.getElementById('yz-project-search').value.toLowerCase(); displayProjectList(keyword); } }); // 关闭面板 document.getElementById('yz-project-cancel').addEventListener('click', () => { document.body.removeChild(projectPanel); document.body.removeChild(loadingDiv); }); // 显示项目列表 function displayProjectList(keyword = '') { const filteredProjects = config.projects.filter(project => { // 包含关键词的项目 return project.name.toLowerCase().includes(keyword) || project.id.toString().includes(keyword) || (project.remark && project.remark.toLowerCase().includes(keyword)); }); const projectListDiv = document.getElementById('yz-project-list'); projectListDiv.innerHTML = ''; if (filteredProjects.length === 0) { projectListDiv.innerHTML = '<div style="text-align: center; padding: 20px;">没有找到匹配的项目</div>'; return; } // 创建项目列表 filteredProjects.forEach(project => { const projectItem = document.createElement('div'); projectItem.style.cssText = ` padding: 10px; border-bottom: 1px solid #eee; cursor: pointer; `; projectItem.innerHTML = ` <div style="font-weight: bold;">${project.name}</div> <div>ID: ${project.id}</div> <div>价格: ${project.price}</div> ${project.remark ? `<div>备注: ${project.remark}</div>` : ''} `; projectItem.addEventListener('click', () => { document.getElementById('yz-project-id').value = project.id; document.body.removeChild(projectPanel); document.body.removeChild(loadingDiv); }); projectListDiv.appendChild(projectItem); }); } // 初始显示所有项目 displayProjectList(); }; // 如果已有项目列表缓存,直接显示 if (config.projects.length > 0) { searchProject(); document.body.removeChild(loadingDiv); } else { // 否则先获取项目列表 getProjects() .then(() => { searchProject(); document.body.removeChild(loadingDiv); }) .catch(() => { document.body.removeChild(loadingDiv); }); } } // 快速搜索项目 function quickSearchProject() { const keyword = document.getElementById('yz-quick-search').value.trim(); if (!keyword) { showMessage('请输入搜索关键词'); return; } if (!config.token) { showMessage('请先登录'); return; } // 显示加载中 const loadingDiv = document.createElement('div'); loadingDiv.id = 'yz-loading'; loadingDiv.textContent = '正在搜索项目...'; loadingDiv.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); color: white; display: flex; justify-content: center; align-items: center; z-index: 10001; `; document.body.appendChild(loadingDiv); // 如果已有项目列表缓存,直接搜索 if (config.projects.length > 0) { showQuickSearchResults(keyword); document.body.removeChild(loadingDiv); } else { // 否则先获取项目列表 getProjects() .then(() => { showQuickSearchResults(keyword); document.body.removeChild(loadingDiv); }) .catch(() => { document.body.removeChild(loadingDiv); }); } } // 显示快速搜索结果 function showQuickSearchResults(keyword) { // 过滤项目 const filteredProjects = config.projects.filter(project => { return project.name.toLowerCase().includes(keyword.toLowerCase()) || project.id.toString().includes(keyword) || (project.remark && project.remark.toLowerCase().includes(keyword.toLowerCase())); }); if (filteredProjects.length === 0) { showMessage('没有找到匹配的项目'); return; } // 如果只有一个结果,直接选择 if (filteredProjects.length === 1) { selectProject(filteredProjects[0]); return; } // 创建结果选择面板 const resultPanel = document.createElement('div'); resultPanel.id = 'yz-quick-result'; resultPanel.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; max-width: 500px; max-height: 80vh; background: #fff; border: 1px solid #ccc; border-radius: 5px; padding: 10px; z-index: 10002; box-shadow: 0 0 10px rgba(0,0,0,0.3); display: flex; flex-direction: column; overflow: hidden; `; resultPanel.innerHTML = ` <div style="font-weight: bold; margin-bottom: 10px; text-align: center;">搜索结果 - 找到 ${filteredProjects.length} 个项目</div> <div id="yz-result-list" style="flex: 1; overflow-y: auto; max-height: 60vh;"></div> <div style="margin-top: 10px; text-align: right;"> <button id="yz-result-cancel" style="padding: 5px 10px;">取消</button> </div> `; document.body.appendChild(resultPanel); // 添加结果列表 const resultList = document.getElementById('yz-result-list'); filteredProjects.forEach(project => { const item = document.createElement('div'); item.style.cssText = ` padding: 10px; border-bottom: 1px solid #eee; cursor: pointer; `; item.innerHTML = ` <div style="font-weight: bold;">${project.name}</div> <div>ID: ${project.id}</div> <div>价格: ${project.price}</div> ${project.remark ? `<div>备注: ${project.remark}</div>` : ''} `; item.addEventListener('click', () => { selectProject(project); document.body.removeChild(resultPanel); }); resultList.appendChild(item); }); // 添加取消按钮事件 document.getElementById('yz-result-cancel').addEventListener('click', () => { document.body.removeChild(resultPanel); }); } // 选择项目 function selectProject(project) { config.projectId = project.id.toString(); GM_setValue('projectId', config.projectId); // 更新配置界面和操作界面的显示 const currentProjectElem = document.getElementById('yz-current-project'); if (currentProjectElem) { currentProjectElem.textContent = config.projectId; } showMessage(`已选择项目: ${project.name}`); } // 初始化 function init() { createUI(); } // 启动脚本 window.addEventListener('load', init); })();