椰子云验证码助手
// ==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);
})();