// ==UserScript==
// @name UPhone 切号工具优化版(账号管理+删除+刷新)
// @namespace http://tampermonkey.net/
// @license MIT
// @version 3.3
// @description 支持账号管理、切换、删除、确认切号、切号后刷新页面。优化版UI更美观,交互更流畅。
// @author GPT
// @match https://uphone.wo-adv.cn/cloudphone/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// 等待页面加载完成
window.addEventListener('load', () => {
const hash = location.hash;
if (!['#/personal', '#/discover', '#/home'].includes(hash)) return;
// 存储相关函数
const storageKey = '__uphone_token_accounts__';
const getAccounts = () => JSON.parse(localStorage.getItem(storageKey) || '{}');
const saveAccounts = (obj) => localStorage.setItem(storageKey, JSON.stringify(obj));
// 状态变量
let isExpanded = false;
let isDragging = false;
let dragOffsetX = 0, dragOffsetY = 0;
// 创建主容器
const container = document.createElement('div');
Object.assign(container.style, {
position: 'fixed',
top: '150px',
right: '0',
width: '50px',
height: '50px',
zIndex: '99999',
cursor: 'pointer',
transition: 'all 0.3s ease',
userSelect: 'none'
});
// 创建浮动按钮
const floatBtn = document.createElement('div');
let isHovering = false;
Object.assign(floatBtn.style, {
width: '100%',
height: '100%',
backgroundColor: '#409EFF',
color: '#fff',
fontSize: '24px',
textAlign: 'center',
lineHeight: '50px',
borderRadius: '50%', // 保持圆形
border: '2px solid rgba(255,255,255,0.3)',
boxShadow: '0 4px 12px rgba(64,158,255,0.4)',
transition: 'all 0.3s ease',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
right: '0' // 初始位置在容器最右侧
});
// 修改鼠标事件
floatBtn.addEventListener('mouseenter', () => {
container.style.right = '0'; // 完全显示
floatBtn.style.paddingLeft = '0';
floatBtn.style.marginLeft = '0';
floatBtn.style.borderRadius = '50%'; // 恢复圆形
// floatBtn.style.border = '2px solid rgba(255,255,255,0.3)';
});
floatBtn.addEventListener('mouseleave', () => {
if (isExpanded) return; // 如果面板展开则不隐藏
container.style.right = '-60px'; // 容器向右隐藏一半
floatBtn.style.paddingLeft = '25px';
floatBtn.style.marginLeft = '-25px';
floatBtn.style.borderRadius = '10%';
// floatBtn.style.borderRadius = '25px 0 0 25px';
floatBtn.style.border = '2px solid rgba(255,255,255,0.3)';
floatBtn.style.borderRight = 'none';
});
floatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect><line x1="12" y1="18" x2="12" y2="18"></line></svg>';
// 创建控制面板
const panel = document.createElement('div');
Object.assign(panel.style, {
width: '280px',
backgroundColor: '#fff',
border: 'none',
borderRadius: '12px',
boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
padding: '16px',
position: 'absolute',
right: '60px',
top: '0',
opacity: '0',
transform: 'translateX(20px)',
pointerEvents: 'none',
transition: 'all 0.3s ease',
fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif'
});
// 面板标题
const title = document.createElement('div');
Object.assign(title.style, {
fontSize: '16px',
fontWeight: '600',
color: '#333',
marginBottom: '16px',
display: 'flex',
alignItems: 'center',
gap: '8px'
});
title.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg> 账号管理';
// 账号列表容器
const accountList = document.createElement('div');
accountList.style.maxHeight = '300px';
accountList.style.overflowY = 'auto';
accountList.style.marginBottom = '16px';
// 刷新账号列表函数
const refreshAccountList = () => {
accountList.innerHTML = '';
const accounts = getAccounts();
if (Object.keys(accounts).length === 0) {
const emptyState = document.createElement('div');
Object.assign(emptyState.style, {
color: '#999',
textAlign: 'center',
padding: '16px',
fontSize: '14px'
});
emptyState.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-bottom: 8px;"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg><div>暂无保存的账号</div>';
accountList.appendChild(emptyState);
return;
}
Object.entries(accounts).forEach(([name, token]) => {
const accountItem = document.createElement('div');
Object.assign(accountItem.style, {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 12px',
marginBottom: '8px',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
transition: 'all 0.2s ease'
});
// 账号名称
const accountName = document.createElement('div');
accountName.textContent = name;
accountName.style.flex = '1';
accountName.style.fontSize = '14px';
accountName.style.fontWeight = '500';
accountName.style.overflow = 'hidden';
accountName.style.textOverflow = 'ellipsis';
accountName.style.whiteSpace = 'nowrap';
// 操作按钮容器
const actionButtons = document.createElement('div');
actionButtons.style.display = 'flex';
actionButtons.style.gap = '6px';
// 切换按钮
const switchBtn = document.createElement('button');
Object.assign(switchBtn.style, {
padding: '4px 8px',
fontSize: '12px',
backgroundColor: '#409EFF',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
transition: 'all 0.2s ease'
});
switchBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>';
switchBtn.title = '切换到此账号';
switchBtn.onmouseenter = () => switchBtn.style.backgroundColor = '#337ecc';
switchBtn.onmouseleave = () => switchBtn.style.backgroundColor = '#409EFF';
switchBtn.onclick = () => {
if (confirm(`确定切换到账号「${name}」吗?`)) {
const baseInfoStr = localStorage.getItem('baseInfo');
if (!baseInfoStr) return alert('账号信息不存在');
try {
// 自定义位置逻辑
const baseInfo = JSON.parse(baseInfoStr);
baseInfo.data.token = token;
baseInfo.data.userInfo = {};
localStorage.setItem('baseInfo', JSON.stringify(baseInfo));
showLoading('切换中...');
setTimeout(() => location.reload(), 800);
} catch (e) {
alert('切换失败:账号信息解析错误');
console.error(e);
}
}
};
// 删除按钮
const deleteBtn = document.createElement('button');
Object.assign(deleteBtn.style, {
padding: '4px 8px',
fontSize: '12px',
backgroundColor: '#ff4d4f',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
transition: 'all 0.2s ease'
});
deleteBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>';
deleteBtn.title = '删除此账号';
deleteBtn.onmouseenter = () => deleteBtn.style.backgroundColor = '#d9363e';
deleteBtn.onmouseleave = () => deleteBtn.style.backgroundColor = '#ff4d4f';
deleteBtn.onclick = () => {
if (confirm(`确定删除账号「${name}」?此操作不可恢复!`)) {
const accounts = getAccounts();
delete accounts[name];
saveAccounts(accounts);
refreshAccountList();
}
};
// 组装元素
actionButtons.appendChild(switchBtn);
actionButtons.appendChild(deleteBtn);
accountItem.appendChild(accountName);
accountItem.appendChild(actionButtons);
accountList.appendChild(accountItem);
// 悬停效果
accountItem.onmouseenter = () => {
accountItem.style.backgroundColor = '#ebf3ff';
};
accountItem.onmouseleave = () => {
accountItem.style.backgroundColor = '#f8f9fa';
};
});
};
// 按钮组容器
const buttonGroup = document.createElement('div');
buttonGroup.style.display = 'flex';
buttonGroup.style.gap = '10px';
buttonGroup.style.marginTop = '16px';
// 添加账号按钮
const addBtn = document.createElement('button');
Object.assign(addBtn.style, {
flex: '1',
padding: '8px 12px',
fontSize: '13px',
fontWeight: '500',
backgroundColor: '#f0f2f5',
color: '#409EFF',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '6px',
transition: 'all 0.2s ease'
});
addBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg> 添加账号';
addBtn.onmouseenter = () => {
addBtn.style.backgroundColor = '#e6f1ff';
};
addBtn.onmouseleave = () => {
addBtn.style.backgroundColor = '#f0f2f5';
};
addBtn.onclick = () => {
const name = prompt('请输入账号名称:');
if (!name) return;
const token = prompt('请输入该账号的 token:');
if (!token) return;
const accounts = getAccounts();
if (accounts[name]) {
if (!confirm(`账号「${name}」已存在,是否覆盖?`)) return;
}
accounts[name] = token;
saveAccounts(accounts);
refreshAccountList();
};
// 退出登录按钮
const logoutBtn = document.createElement('button');
Object.assign(logoutBtn.style, {
flex: '1',
padding: '8px 12px',
fontSize: '13px',
fontWeight: '500',
backgroundColor: '#fff0f0',
color: '#ff4d4f',
border: '1px solid #ffd6d6',
borderRadius: '6px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '6px',
transition: 'all 0.2s ease'
});
logoutBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg> 退出登录';
logoutBtn.onmouseenter = () => {
logoutBtn.style.backgroundColor = '#ffebeb';
};
logoutBtn.onmouseleave = () => {
logoutBtn.style.backgroundColor = '#fff0f0';
};
logoutBtn.onclick = () => {
if (confirm('确定要退出当前账号吗?')) {
const baseInfoStr = localStorage.getItem('baseInfo');
if (!baseInfoStr) return alert('账号信息不存在');
try {
const baseInfo = JSON.parse(baseInfoStr);
baseInfo.data.token = "";
baseInfo.data.userInfo = {};
localStorage.setItem('baseInfo', JSON.stringify(baseInfo));
showLoading('退出中...');
setTimeout(() => location.reload(), 800);
} catch (e) {
alert('退出失败:账号信息解析错误');
console.error(e);
}
}
};
// 加载状态提示
const showLoading = (text) => {
const loading = document.createElement('div');
Object.assign(loading.style, {
position: 'fixed',
top: '0',
left: '0',
right: '0',
bottom: '0',
backgroundColor: 'rgba(0,0,0,0.5)',
zIndex: '100000',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: '16px',
gap: '16px'
});
loading.innerHTML = `
<div class="spinner" style="width: 40px; height: 40px; border: 4px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: #fff; animation: spin 1s ease-in-out infinite;"></div>
<div>${text}</div>
`;
document.body.appendChild(loading);
// 添加动画
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
// 3秒后自动移除
setTimeout(() => {
if (loading.parentNode) {
loading.parentNode.removeChild(loading);
}
if (style.parentNode) {
style.parentNode.removeChild(style);
}
}, 3000);
};
// 组装面板
buttonGroup.appendChild(addBtn);
buttonGroup.appendChild(logoutBtn);
panel.appendChild(title);
panel.appendChild(accountList);
panel.appendChild(buttonGroup);
// 主容器组装
container.appendChild(floatBtn);
container.appendChild(panel);
// 添加到页面
document.body.appendChild(container);
// 修改togglePanel函数(确保展开时完全显示)
const togglePanel = () => {
isExpanded = !isExpanded;
if (isExpanded) {
refreshAccountList();
panel.style.opacity = '1';
panel.style.transform = 'translateX(0)';
panel.style.pointerEvents = 'auto';
floatBtn.style.transform = 'rotate(90deg)';
floatBtn.style.opacity = '1'; // 展开时强制不透明
floatBtn.style.backgroundColor = '#409EFF';
} else {
panel.style.opacity = '0';
panel.style.transform = 'translateX(20px)';
panel.style.pointerEvents = 'none';
floatBtn.style.transform = 'rotate(0)';
if (!isHovering) { // 收起后如果鼠标不在按钮上则恢复半透明
floatBtn.style.opacity = '1';
// floatBtn.style.backgroundColor = 'rgba(64,158,255,0.2)';
}
}
};
// 点击事件
floatBtn.addEventListener('click', togglePanel);
setTimeout(() => {
if (!isExpanded && !isHovering) {
container.style.right = '-40px'; // 执行隐藏
floatBtn.style.borderRadius = '10%';
}
}, 2000);
});
})();