NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name 测试环境快速登录
// @namespace http://tampermonkey.net/
// @version 1.3
// @description 支持邮箱/userId/obsId登录,并可一键复制历史内容。最终始终用userId(数字)登录
// @author tingbai
// @license MIT
// @match http://localhost/*
// @match http://localhost:*/*
// @match https://localhost/*
// @match https://localhost:*/*
// @match http://127.0.0.1/*
// @match http://127.0.0.1:*/*
// @match https://127.0.0.1/*
// @match https://127.0.0.1:*/*
// @match http://zhuzhan.feat.qunhequnhe.com/*
// @match https://sit.kujiale.com/*
// @match http://tobdev.feat.qunhequnhe.com/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
// 样式
const style = document.createElement('style');
style.innerHTML = `
.email-login-container {
position: fixed;
top: 100px;
right: 0;
z-index: 9999;
background: white;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px 0 0 5px;
box-shadow: -2px 2px 5px rgba(0,0,0,0.1);
transition: all 0.3s ease;
user-select: none;
}
.email-login-container.collapsed {
transform: translateX(100%);
padding: 0;
border: none;
box-shadow: none;
}
.email-login-input {
padding: 8px;
width: 200px;
margin-right: 5px;
border: 1px solid #ccc;
border-radius: 3px;
}
.email-login-btn {
padding: 8px 12px;
background: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
}
.email-login-btn:hover {
background: #45a049;
}
.email-login-message {
margin-top: 5px;
font-size: 12px;
color: #666;
}
.toggle-container {
position: absolute;
top: 50%;
left: -20px;
width: 20px;
height: 40px;
background: white;
border: 1px solid #ddd;
border-right: none;
border-radius: 5px 0 0 5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
transform: translateY(-50%);
box-shadow: -2px 2px 5px rgba(0,0,0,0.1);
}
.toggle-container:hover {
background: #f0f0f0;
}
.toggle-arrow {
width: 0;
height: 0;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-right: 6px solid #666;
transition: all 0.3s ease;
}
.collapsed .toggle-arrow {
transform: rotate(180deg);
}
.content-wrapper {
display: flex;
flex-direction: column;
min-width: 250px;
}
.history-section {
margin-top: 10px;
border-top: 1px solid #eee;
padding-top: 10px;
}
.history-title {
font-size: 12px;
color: #999;
margin-bottom: 5px;
}
.history-list {
display: flex;
flex-direction: column;
gap: 3px;
}
.history-item {
padding: 3px 5px;
font-size: 12px;
background: #f5f5f5;
border-radius: 3px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.history-item:hover {
background: #e9e9e9;
}
.history-item .delete-btn {
color: #999;
margin-left: 8px;
visibility: hidden;
cursor: pointer;
}
.history-item .copy-btn {
color: #199ed8;
margin-left: 8px;
font-size: 12px;
border: none;
background: transparent;
cursor: pointer;
visibility: hidden;
}
.history-item:hover .delete-btn,
.history-item:hover .copy-btn {
visibility: visible;
}
.history-item .copy-btn.copied {
color: #4CAF50;
}
.input-row {
display: flex;
align-items: center;
}
`;
document.head.appendChild(style);
// 容器
const container = document.createElement('div');
container.className = 'email-login-container';
container.innerHTML = `
<div class="toggle-container" title="显示登录面板">
<div class="toggle-arrow"></div>
</div>
<div class="content-wrapper">
<div class="input-row">
<input type="text" class="email-login-input" placeholder="输入邮箱/userId/obsId">
<button class="email-login-btn">登录</button>
</div>
<div class="email-login-message"></div>
<div class="history-section">
<div class="history-title">最近登录</div>
<div class="history-list"></div>
</div>
</div>
`;
document.body.appendChild(container);
const input = container.querySelector('.email-login-input');
const button = container.querySelector('.email-login-btn');
const message = container.querySelector('.email-login-message');
const toggleBtn = container.querySelector('.toggle-container');
const historyList = container.querySelector('.history-list');
const MAX_HISTORY = 5;
let loginHistory = GM_getValue('loginHistory', []);
function loginWithInput(val) {
if (!val) {
message.textContent = '请输入邮箱、userId或obsId';
return;
}
val = val.trim();
if (validateEmail(val)) {
loginWithEmail(val);
} else if (validatePureUserId(val)) {
loginByUserId(val, "userId登录");
} else if (validateObsId(val)) {
loginWithObsId(val);
} else {
message.textContent = '请输入有效的邮箱、userId或obsId';
}
}
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validatePureUserId(str) {
return /^\d{4,}$/.test(str);
}
function validateObsId(str) {
return /^[A-Z0-9]{8,20}$/.test(str) && /[A-Z]/.test(str);
}
function loginWithEmail(email) {
message.textContent = '正在查询用户ID...';
GM_xmlhttpRequest({
method: "POST",
url: "https://colorful-dev.qunhequnhe.com/commercialization/api/arc-acs/check/business-accounts",
headers: {
"priority": "u=1, i",
"Cookie": "pubinternalsso=xaYvBTv9Tji/vEBlYWjrxJizDv1rCcvUcsifAAqAJgw",
"content-type": "application/json"
},
data: JSON.stringify({email: email}),
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
if (data.d && data.d[0] && data.d[0].userInfo && data.d[0].userInfo.userId) {
const userId = data.d[0].userInfo.userId;
message.textContent = "查得userId: " + userId + ",正在登录...";
addToHistory({type:'email', value:email, show:`${email} (uid:${userId})`});
loginByUserId(userId, "邮箱登录");
} else {
message.textContent = '未找到用户ID';
}
} catch (e) {
message.textContent = '解析响应失败: ' + e.message;
}
},
onerror: function(error) {
message.textContent = '请求失败: ' + error.statusText;
}
});
}
function loginWithObsId(obsId) {
message.textContent = "正在通过 obsId 查询 userId...";
obsConvert(obsId, (userId, errMsg) => {
if (userId && validatePureUserId(userId)) {
addToHistory({type: 'obsId', value: obsId, show: `obsId:${obsId} (userId:${userId})`});
loginByUserId(userId, "obsId登录");
} else {
message.textContent = 'obsId 转换为 userId 失败!' + (errMsg ? " 【" + errMsg + "】" : "");
}
}, 'obsToUserId');
}
function loginByUserId(userId, debugDesc) {
if (!validatePureUserId(userId)) {
message.textContent = 'userId必须为数字,实际是' + userId;
return;
}
const currentDomain = window.location.protocol + "//" + window.location.host;
const loginUrl = currentDomain + "/uic/trust/login?userId=" + userId;
message.textContent = '跳转到登录页面...';
window.location.href = loginUrl;
}
function obsConvert(id, cb, mode) {
GM_xmlhttpRequest({
method: "GET",
url: "https://coops.qunhequnhe.com/api/dbv/common/obsconvert?id=" + encodeURIComponent(id),
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
if (data && data.c === "0" && data.d) {
cb(data.d);
} else {
cb(undefined, data && data.m ? data.m : "接口无效返回");
}
} catch (e) {
cb(undefined, e.message);
}
},
onerror: function(err) {
cb(undefined, err.statusText || "网络错误");
}
});
}
// -------- 新增 复制功能 ---------
function copyToClipboard(text) {
// 极速方案
if (navigator.clipboard) {
navigator.clipboard.writeText(text).catch(()=>{});
} else {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try { document.execCommand('copy'); }catch(e){}
document.body.removeChild(textarea);
}
}
function renderHistory() {
historyList.innerHTML = '';
loginHistory.forEach((item, index) => {
const dom = document.createElement('div');
dom.className = 'history-item';
dom.innerHTML = `
<span>${item.show || item.value}</span>
<button class="copy-btn" title="复制">复制</button>
<span class="delete-btn" data-index="${index}">×</span>
`;
historyList.appendChild(dom);
// 主体点击跳转
dom.addEventListener('click', (e) => {
if (
!e.target.classList.contains('delete-btn') &&
!e.target.classList.contains('copy-btn')
) {
loginWithInput(item.value);
}
});
// 删除
dom.querySelector('.delete-btn').addEventListener('click', (e) => {
e.stopPropagation();
loginHistory.splice(index, 1);
GM_setValue('loginHistory', loginHistory);
renderHistory();
});
// 复制
const copyBtn = dom.querySelector('.copy-btn');
copyBtn.addEventListener('click', (e) => {
e.stopPropagation();
copyToClipboard(item.value);
copyBtn.classList.add('copied');
copyBtn.textContent = "已复制";
setTimeout(() => {
copyBtn.classList.remove('copied');
copyBtn.textContent = "复制";
}, 1200);
});
});
}
renderHistory();
function addToHistory(newItem) {
loginHistory = loginHistory.filter(item => item.value !== newItem.value);
loginHistory.unshift(newItem);
if (loginHistory.length > MAX_HISTORY) {
loginHistory = loginHistory.slice(0, MAX_HISTORY);
}
GM_setValue('loginHistory', loginHistory);
renderHistory();
}
toggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
container.classList.toggle('collapsed');
toggleBtn.title = container.classList.contains('collapsed') ? '显示登录面板' : '隐藏登录面板';
});
button.addEventListener('click', () => {
loginWithInput(input.value.trim());
});
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
loginWithInput(input.value.trim());
}
});
container.classList.add('collapsed');
})();