您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
实现UPhone账号管理,实现多账号切换
// ==UserScript== // @name UPhone 切号工具 // @namespace http://tampermonkey.net/ // @license MIT // @version 3.6 // @description 实现UPhone账号管理,实现多账号切换 // @author kkkkkba // @match https://uphone.wo-adv.cn/cloudphone/* // @grant none // ==/UserScript== (function () { 'use strict'; // —— 可配置常量 —— const STORAGE_ACCOUNTS = '__uphone_token_accounts__'; const STORAGE_FLOATPOS = '__uphone_float_position__'; const FLOAT_SIZE = 50; // 浮球尺寸 const PEEK_OFFSET = 25; // 半隐藏露出像素 const DRAG_THRESHOLD = 5; // 判定拖动的最小移动距离(像素) const getAccounts = () => JSON.parse(localStorage.getItem(STORAGE_ACCOUNTS) || '{}'); const saveAccounts = (obj) => localStorage.setItem(STORAGE_ACCOUNTS, JSON.stringify(obj)); // 帮助:节流 const raf = (fn) => requestAnimationFrame(fn); // —— 初始化 —— window.addEventListener('load', () => { // ===== 根容器(承载浮球与面板) ===== const container = document.createElement('div'); Object.assign(container.style, { position: 'fixed', top: '150px', right: '0', width: FLOAT_SIZE + 'px', height: FLOAT_SIZE + 'px', zIndex: '99999', userSelect: 'none', transition: 'transform .25s ease, right .25s ease, left .25s ease, top .25s ease', transform: 'translateX(' + PEEK_OFFSET + 'px)', // 初始半隐藏在右侧 }); // 恢复浮球停靠边与纵向位置 const savedPos = (() => { try { return JSON.parse(localStorage.getItem(STORAGE_FLOATPOS) || '{}'); } catch (_) { return {}; } })(); let dockEdge = savedPos.edge === 'left' ? 'left' : 'right'; // 默认右侧 if (dockEdge === 'left') { container.style.left = '0px'; container.style.right = 'auto'; container.style.transform = 'translateX(-' + PEEK_OFFSET + 'px)'; } else { container.style.right = '0px'; container.style.left = 'auto'; container.style.transform = 'translateX(' + PEEK_OFFSET + 'px)'; } if (savedPos.top) container.style.top = savedPos.top; // ===== 浮动按钮(悬浮球) ===== const floatBtn = document.createElement('div'); Object.assign(floatBtn.style, { width: '100%', height: '100%', background: '#409EFF', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '50%', boxShadow: '0 4px 12px rgba(64,158,255,.4)', border: '2px solid rgba(255,255,255,.35)', cursor: 'grab', transition: 'background .2s ease, opacity .2s ease, transform .2s ease', fontSize: '22px', lineHeight: 1, userSelect: 'none', }); floatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" 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>'; // ===== 全局toast管理 ===== let toastQueue = []; let isShowingToast = false; // 改进的toast函数 function showToast(message, duration = 2000) { toastQueue.push({ message, duration }); if (!isShowingToast) { processToastQueue(); } } // 处理toast队列 function processToastQueue() { if (toastQueue.length === 0) { isShowingToast = false; return; } isShowingToast = true; const { message, duration } = toastQueue.shift(); // 显示toast(假设您已有toast函数) toast(message); setTimeout(() => { processToastQueue(); }, duration); } // ===== 控制面板 ===== const panel = document.createElement('div'); Object.assign(panel.style, { position: 'absolute', top: '0', right: dockEdge === 'right' ? (FLOAT_SIZE + 10) + 'px' : 'auto', left: dockEdge === 'left' ? (FLOAT_SIZE + 10) + 'px' : 'auto', width: '250px', background: '#fff', borderRadius: '12px', boxShadow: '0 8px 24px rgba(0,0,0,.15)', padding: '14px', fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, PingFang SC, Microsoft YaHei, sans-serif', opacity: '0', transform: 'translateX(12px)', pointerEvents: 'none', transition: 'opacity .2s ease, transform .2s ease', }); const section = (titleText) => { const wrap = document.createElement('div'); wrap.style.marginBottom = '12px'; const head = document.createElement('div'); Object.assign(head.style, { fontSize: '14px', fontWeight: '900', color: '#333', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '6px' }); head.textContent = titleText; wrap.appendChild(head); return { wrap, head }; }; // —— 账号管理 —— const divider = document.createElement('div'); Object.assign(divider.style, { height: '1px', background: '#f1f5f9'}); panel.appendChild(divider); const { wrap: accWrap, head: accHead } = section('账号管理'); const list = document.createElement('div'); Object.assign(list.style, { maxHeight: '260px', overflowY: 'auto', marginBottom: '10px' }); const refreshAccountList = () => { list.innerHTML = ''; const accounts = getAccounts(); const names = Object.keys(accounts); if (!names.length) { const empty = document.createElement('div'); empty.textContent = '暂无保存的账号'; Object.assign(empty.style, { color: '#94a3b8', textAlign: 'center', padding: '12px', fontSize: '13px' }); list.appendChild(empty); return; } names.forEach((name) => { const token = accounts[name]; const item = document.createElement('div'); Object.assign(item.style, { display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: '#EDF2FA', padding: '8px 10px', borderRadius: '10px', marginBottom: '8px' }); const nm = document.createElement('div'); nm.textContent = name; Object.assign(nm.style, { flex: '1', minWidth: 0, fontSize: '14px', fontWeight: '400', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', }); const actions = document.createElement('div'); Object.assign(actions.style, { display: 'flex', gap: '6px' }); const btn = (txt, bg, color, border) => { const b = document.createElement('button'); b.textContent = txt; Object.assign(b.style, { padding: '4px 8px', fontSize: '12px', borderRadius: '8px', cursor: 'pointer', background: bg, color, border: border || 'none' }); return b; }; const bSwitch = btn('切换', '#409EFF', '#fff'); const bDel = btn('', '#FC886F', '#fff'); bDel.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>'; // bSwitch nm.onclick = async () => { if (!confirm(`确定切换到账号「${name}」吗?`)) return; const baseInfoStr = localStorage.getItem('baseInfo'); if (!baseInfoStr) return alert('账号信息不存在'); try { const baseInfo = JSON.parse(baseInfoStr); baseInfo.data.token = token; baseInfo.data.userInfo = {}; // 添加请求接口获取用户信息的逻辑 try { showToast('获取用户信息中...', 1500); const response = await fetch('https://uphone.wo-adv.cn/bucp/servers/system/user/getAppUserInfo', { method: 'GET', headers: { 'Accept': '*/*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Authorization': baseInfo.data.token, 'Connection': 'keep-alive', 'Referer': 'https://uphone.wo-adv.cn/cloudphone/', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/139.0.0.0', 'channel': 'bucp-master', 'channelCode': 'bucp-master', 'deviceId': 'c5c62d1dc02086a283f71c63197131da', 'os': 'H5', 'source': '4' } }); if (response.ok) { const result = await response.json(); if (result.code === 200 && result.data) { baseInfo.data.userInfo = result.data; showToast('用户信息获取成功', 1000); } else { console.warn('获取用户信息失败:', result.message || '未知错误'); showToast('获取用户信息失败,使用空用户信息', 1500); } } else { throw new Error(`HTTP错误: ${response.status}`); } } catch (error) { console.error('请求用户信息失败:', error); showToast('获取用户信息失败,使用空用户信息', 1500); } // 保存更新后的baseInfo localStorage.setItem('baseInfo', JSON.stringify(baseInfo)); showToast('切换中...', 800); setTimeout(() => location.reload(), 800); } catch (e) { console.error(e); alert('切换失败:baseInfo 解析错误'); } }; bDel.onclick = () => { if (!confirm(`确定删除账号「${name}」?此操作不可恢复!`)) return; const a = getAccounts(); delete a[name]; saveAccounts(a); refreshAccountList(); }; // actions.appendChild(bSwitch); actions.appendChild(bDel); item.appendChild(nm); item.appendChild(actions); list.appendChild(item); }); }; const controls = document.createElement('div'); Object.assign(controls.style, { display: 'flex', gap: '10px' }); const btnAdd = document.createElement('button'); btnAdd.textContent = '添加账号'; Object.assign(btnAdd.style, { flex: '1', padding: '8px 12px', fontSize: '13px', background: '#e6f0ff', color: '#2563eb', border: '1px solid #bfdbfe', borderRadius: '10px', cursor: 'pointer' }); btnAdd.onclick = () => { const name = prompt('请输入账号名称:'); if (!name) return; const token = prompt('请输入该账号的 token:'); if (!token) return; const a = getAccounts(); if (a[name] && !confirm(`账号「${name}」已存在,是否覆盖?`)) return; a[name] = token; saveAccounts(a); refreshAccountList(); }; const btnLogout = document.createElement('button'); btnLogout.textContent = '退出登录'; Object.assign(btnLogout.style, { flex: '1', padding: '8px 12px', fontSize: '13px', background: '#fff0f0', color: '#ef4444', border: '1px solid #fecaca', borderRadius: '10px', cursor: 'pointer' }); btnLogout.onclick = () => { if (!confirm('确定要退出当前账号吗?')) return; 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)); toast('退出中...'); setTimeout(() => location.reload(), 800); } catch (e) { console.error(e); alert('退出失败:baseInfo 解析错误'); } }; controls.appendChild(btnAdd); controls.appendChild(btnLogout); accWrap.appendChild(list); accWrap.appendChild(controls); panel.appendChild(accWrap); // —— 轻提示 —— function toast(text) { const el = document.createElement('div'); el.textContent = text; Object.assign(el.style, { position: 'fixed', left: '50%', top: '12%', transform: 'translateX(-50%)', background: 'rgba(0,0,0,.8)', color: '#fff', padding: '8px 12px', borderRadius: '999px', fontSize: '12px', zIndex: '100000', opacity: '0', transition: 'opacity .2s ease' }); document.body.appendChild(el); requestAnimationFrame(() => el.style.opacity = '1'); setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 200); }, 1600); } // ===== 交互:展开/收起 & 半隐藏 ===== let isExpanded = false; // 面板是否展开 let isHovering = false; // 鼠标是否悬停容器 let isDragging = false; // 是否拖动中 let dragStartX = 0, dragStartY = 0, moved = false; function applyPeekHidden(hidden) { if (isExpanded) { container.style.transform = 'translateX(0)'; return; } const tx = hidden ? (dockEdge === 'right' ? PEEK_OFFSET : -PEEK_OFFSET) : 0; container.style.transform = `translateX(${tx}px)`; } function togglePanel(force) { const willExpand = typeof force === 'boolean' ? force : !isExpanded; isExpanded = willExpand; if (willExpand) { refreshAccountList(); // 面板在当前停靠边的反方向展开 panel.style.left = dockEdge === 'left' ? (FLOAT_SIZE + 10) + 'px' : 'auto'; panel.style.right = dockEdge === 'right' ? (FLOAT_SIZE + 10) + 'px' : 'auto'; panel.style.opacity = '1'; panel.style.transform = 'translateX(0)'; panel.style.pointerEvents = 'auto'; applyPeekHidden(false); } else { panel.style.opacity = '0'; panel.style.transform = 'translateX(12px)'; panel.style.pointerEvents = 'none'; applyPeekHidden(true); } } container.addEventListener('mouseenter', () => { isHovering = true; applyPeekHidden(false); }); container.addEventListener('mouseleave', () => { isHovering = false; setTimeout(() => { if (!isHovering && !isExpanded && !isDragging) applyPeekHidden(true); }, 10); }); // 点击与拖动判定 const onMouseDown = (e) => { isDragging = true; moved = false; dragStartX = e.clientX; dragStartY = e.clientY; floatBtn.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; }; const onMouseMove = (e) => { if (!isDragging) return; const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; if (!moved && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)) moved = true; // 临时跟随指针(解除停靠) container.style.left = (e.clientX - FLOAT_SIZE / 2) + 'px'; container.style.top = (e.clientY - FLOAT_SIZE / 2) + 'px'; container.style.right = 'auto'; applyPeekHidden(true); // 拖拽中保持完全显示 }; const onMouseUp = (e) => { if (!isDragging) return; isDragging = false; floatBtn.style.cursor = 'grab'; document.body.style.userSelect = ''; // 是否当作点击? if (!moved) { togglePanel(); return; } // 结束时吸附到最近边 & 规范位置 const rect = container.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; dockEdge = centerX < window.innerWidth / 2 ? 'left' : 'right'; const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); const topPx = clamp(rect.top, 0, window.innerHeight - FLOAT_SIZE); container.style.top = topPx + 'px'; if (dockEdge === 'left') { container.style.left = '0px'; container.style.right = 'auto'; } else { container.style.right = '0px'; container.style.left = 'auto'; } // 记忆停靠边与纵向位置 localStorage.setItem(STORAGE_FLOATPOS, JSON.stringify({ edge: dockEdge, top: container.style.top })); // 根据停靠边应用半隐藏 raf(() => applyPeekHidden(true)); }; // 绑定鼠标事件(容器与按钮都能拖) // floatBtn.addEventListener('mousedown', onMouseDown); // container.addEventListener('mousedown', (e) => { if (e.target === container) onMouseDown(e); }); // window.addEventListener('mousemove', onMouseMove); // window.addEventListener('mouseup', onMouseUp); floatBtn.addEventListener('pointerdown', onMouseDown); window.addEventListener('pointermove', onMouseMove); window.addEventListener('pointerup', onMouseUp); // 触摸支持 floatBtn.addEventListener('touchstart', (e) => onMouseDown(e.touches[0]), { passive: true }); window.addEventListener('touchmove', (e) => onMouseMove(e.touches[0]), { passive: true }); window.addEventListener('touchend', (e) => onMouseUp(e.changedTouches?.[0] || e)); // ESC 关闭面板 window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isExpanded) togglePanel(false); }); // 窗口尺寸变化,确保不超出屏幕 window.addEventListener('resize', () => { const rect = container.getBoundingClientRect(); const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); container.style.top = clamp(rect.top, 0, window.innerHeight - FLOAT_SIZE) + 'px'; // 重新应用停靠位置与半隐藏 if (dockEdge === 'left') { container.style.left = '0px'; container.style.right = 'auto'; } else { container.style.right = '0px'; container.style.left = 'auto'; } applyPeekHidden(!isExpanded && !isHovering); }); // 将元素添加到页面 container.appendChild(floatBtn); container.appendChild(panel); document.body.appendChild(container); // 初次渲染:列表 & 初始半隐藏 refreshAccountList(); setTimeout(() => { if (!isExpanded) applyPeekHidden(true); }, 10); }); })();