功能: 优化油猴脚本 v3.0

主要改进:
1. 修复核心Bug - 正确检测API响应状态(检查success字段)
2. 配置界面化 - 使用GM_setValue/GM_getValue持久化存储
3. 添加可视化配置面板 - 无需修改代码
4. 增强错误处理 - 显示详细的错误信息
5. API兼容性验证 - 与后端完全兼容

提供两个版本:
- clients/userscript-template.js (v3.0增强版, 608行)
- client/userscript-template.js (v2.0简化版, 431行)

新增文档:
- USERSCRIPT_CHANGELOG.md - 更新日志
- USERSCRIPT_FIX_SUMMARY.md - 修复总结
- USERSCRIPT_USAGE.md - 使用指南

由Claude Code完成。
This commit is contained in:
2026-08-14 10:15:31 +08:00
parent 9271bf62b2
commit a3c23970d7
5 changed files with 1103 additions and 41 deletions
+234 -31
View File
@@ -1,37 +1,62 @@
// ==UserScript==
// @name 通用数据同步脚本
// @namespace http://tampermonkey.net/
// @version 2.0
// @description 通用数据同步服务客户端,支持自动检测项目和账号
// @version 3.0
// @description 通用数据同步服务客户端,支持自动检测项目和账号,可视化配置管理
// @author Hermes
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_notification
// @grant GM_setValue
// @grant GM_getValue
// @connect 47.122.126.244
// @connect *
// ==/UserScript==
(function() {
'use strict';
// ==================== 配置 ====================
const CONFIG = {
// ==================== 配置管理 ====================
const DEFAULT_CONFIG = {
serverUrl: 'http://47.122.126.244:5001',
password: 'admin123123123',
// 自动检测配置
autoDetect: true, // 是否自动检测项目名和账号
tag: 'auto', // 'auto' 表示自动检测,或手动指定如 'tingwu:ykaayk@qq.com'
type: 'cookie', // cookie/token/credential/session/custom
// 过期配置
expiresIn: 604800, // 7天(秒)
// 其他配置
autoSync: false, // 是否自动同步(页面加载后自动上传)
syncInterval: 0, // 定时同步间隔(秒,0 表示不定时同步)
};
// 加载配置
function loadConfig() {
const saved = GM_getValue('sync_config', null);
if (saved) {
try {
return {...DEFAULT_CONFIG, ...JSON.parse(saved)};
} catch (e) {
console.error('配置解析失败,使用默认配置', e);
}
}
return {...DEFAULT_CONFIG};
}
// 保存配置
function saveConfig(config) {
GM_setValue('sync_config', JSON.stringify(config));
console.log('✅ 配置已保存');
}
// 全局配置对象
let CONFIG = loadConfig();
// ==================== 项目检测规则 ====================
const PROJECT_RULES = [
{
@@ -134,19 +159,34 @@
},
data: JSON.stringify(payload),
onload: function(response) {
if (response.status === 200) {
const result = JSON.parse(response.responseText);
let result;
try {
result = JSON.parse(response.responseText);
} catch (e) {
console.error('❌ 响应解析失败:', response.responseText);
GM_notification({
title: '同步失败',
text: '服务器响应格式错误',
timeout: 5000
});
return;
}
// 检查是否成功:HTTP 200 且响应体包含 success: true
if (response.status === 200 && result.success === true) {
console.log('✅ 数据同步成功:', result);
GM_notification({
title: '数据同步成功',
text: `Tag: ${tag}\n时间: ${result.timestamp}`,
text: `Tag: ${tag}\n时间: ${result.timestamp || '未知'}`,
timeout: 3000
});
} else {
console.error('❌ 数据同步失败:', response.responseText);
// 失败:可能是 HTTP 错误码,或响应包含 error 字段
const errorMsg = result.error || result.message || response.statusText || '未知错误';
console.error('❌ 数据同步失败:', errorMsg, response);
GM_notification({
title: '数据同步失败',
text: `状态码: ${response.status}`,
text: `状态码: ${response.status}\n错误: ${errorMsg}`,
timeout: 5000
});
}
@@ -275,32 +315,42 @@
*/
function showSyncPanel() {
const detected = autoDetectTag();
const panel = document.createElement('div');
panel.id = 'data-sync-panel';
panel.innerHTML = `
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); max-width: 400px;">
<h3 style="margin: 0 0 15px 0;">数据同步</h3>
<div style="margin-bottom: 10px;">
<strong>项目:</strong> ${detected.project}
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); max-width: 450px;">
<h3 style="margin: 0 0 15px 0; color: #333;">数据同步控制面板</h3>
<div style="margin-bottom: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<div style="margin-bottom: 8px;">
<strong>项目:</strong> ${detected.project}
</div>
<div style="margin-bottom: 8px;">
<strong>账号:</strong> ${detected.account}
</div>
<div style="margin-bottom: 8px;">
<strong>Tag:</strong> ${detected.tag}
</div>
<div>
<strong>类型:</strong> ${CONFIG.type}
</div>
</div>
<div style="margin-bottom: 10px;">
<strong>账号:</strong> ${detected.account}
</div>
<div style="margin-bottom: 10px;">
<strong>Tag:</strong> ${detected.tag}
</div>
<div style="margin-bottom: 15px;">
<strong>类型:</strong> ${CONFIG.type}
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
<button id="sync-now-btn" style="flex: 1; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
🔄 立即同步
</button>
<button id="copy-config-btn" style="flex: 1; padding: 10px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
📋 复制配置
</button>
</div>
<div style="display: flex; gap: 10px;">
<button id="sync-now-btn" style="flex: 1; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">
立即同步
<button id="settings-btn" style="flex: 1; padding: 10px; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
⚙️ 配置设置
</button>
<button id="copy-config-btn" style="flex: 1; padding: 10px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">
复制配置
</button>
<button id="close-panel-btn" style="padding: 10px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;">
<button id="close-panel-btn" style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
关闭
</button>
</div>
@@ -340,10 +390,163 @@
copyConfig();
});
document.getElementById('settings-btn').addEventListener('click', () => {
closePanel();
showSettingsPanel();
});
document.getElementById('close-panel-btn').addEventListener('click', closePanel);
overlay.addEventListener('click', closePanel);
}
/**
* 显示设置面板
*/
function showSettingsPanel() {
const panel = document.createElement('div');
panel.id = 'data-sync-settings-panel';
panel.innerHTML = `
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); max-width: 500px; max-height: 80vh; overflow-y: auto;">
<h3 style="margin: 0 0 15px 0; color: #333;">配置设置</h3>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">服务器地址</label>
<input type="text" id="setting-serverUrl" value="${CONFIG.serverUrl}"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">密码</label>
<input type="password" id="setting-password" value="${CONFIG.password}"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">Tag(留空自动检测)</label>
<input type="text" id="setting-tag" value="${CONFIG.tag === 'auto' ? '' : CONFIG.tag}" placeholder="auto"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
<small style="color: #888;">格式:项目名:账号,如 tingwu:user@email.com</small>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">数据类型</label>
<select id="setting-type" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
<option value="cookie" ${CONFIG.type === 'cookie' ? 'selected' : ''}>Cookie</option>
<option value="token" ${CONFIG.type === 'token' ? 'selected' : ''}>Token</option>
<option value="credential" ${CONFIG.type === 'credential' ? 'selected' : ''}>Credential</option>
<option value="session" ${CONFIG.type === 'session' ? 'selected' : ''}>Session</option>
<option value="custom" ${CONFIG.type === 'custom' ? 'selected' : ''}>Custom</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">过期时间(秒)</label>
<input type="number" id="setting-expiresIn" value="${CONFIG.expiresIn}"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
<small style="color: #888;">默认 6048007天)</small>
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="setting-autoDetect" ${CONFIG.autoDetect ? 'checked' : ''}
style="margin-right: 8px;">
<span style="font-weight: bold; color: #555;">自动检测项目和账号</span>
</label>
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="setting-autoSync" ${CONFIG.autoSync ? 'checked' : ''}
style="margin-right: 8px;">
<span style="font-weight: bold; color: #555;">页面加载后自动同步</span>
</label>
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">定时同步间隔(秒,0=禁用)</label>
<input type="number" id="setting-syncInterval" value="${CONFIG.syncInterval}"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="display: flex; gap: 10px;">
<button id="save-settings-btn" style="flex: 1; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
✅ 保存配置
</button>
<button id="reset-settings-btn" style="flex: 1; padding: 10px; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
🔄 恢复默认
</button>
<button id="close-settings-btn" style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
关闭
</button>
</div>
</div>
`;
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10001;
`;
// 添加背景遮罩
const overlay = document.createElement('div');
overlay.id = 'data-sync-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 10000;
`;
document.body.appendChild(overlay);
document.body.appendChild(panel);
// 绑定事件
document.getElementById('save-settings-btn').addEventListener('click', () => {
const newConfig = {
serverUrl: document.getElementById('setting-serverUrl').value.trim(),
password: document.getElementById('setting-password').value,
tag: document.getElementById('setting-tag').value.trim() || 'auto',
type: document.getElementById('setting-type').value,
expiresIn: parseInt(document.getElementById('setting-expiresIn').value) || 604800,
autoDetect: document.getElementById('setting-autoDetect').checked,
autoSync: document.getElementById('setting-autoSync').checked,
syncInterval: parseInt(document.getElementById('setting-syncInterval').value) || 0,
};
CONFIG = newConfig;
saveConfig(newConfig);
GM_notification({
title: '配置已保存',
text: '设置将在下次页面加载时生效',
timeout: 3000
});
closePanel();
});
document.getElementById('reset-settings-btn').addEventListener('click', () => {
if (confirm('确定要恢复默认配置吗?')) {
CONFIG = {...DEFAULT_CONFIG};
saveConfig(CONFIG);
GM_notification({
title: '配置已重置',
text: '已恢复为默认配置',
timeout: 3000
});
closePanel();
}
});
document.getElementById('close-settings-btn').addEventListener('click', closePanel);
overlay.addEventListener('click', closePanel);
}
/**
* 关闭面板
*/