UPhone 切号工具(账号管理+删除+刷新)

支持账号管理、切换、删除、确认切号、切号后刷新页面。仅在指定页面生效。

目前為 2025-05-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name         UPhone 切号工具(账号管理+删除+刷新)
// @namespace    http://tampermonkey.net/
// @license MIT
// @version      3.0
// @description  支持账号管理、切换、删除、确认切号、切号后刷新页面。仅在指定页面生效。
// @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;

        const container = document.createElement('div');
        container.style.position = 'fixed';
        container.style.top = '150px';
        container.style.right = '0';
        container.style.width = '10px';
        container.style.height = '40px';
        container.style.zIndex = '99999';
        container.style.backgroundColor = 'rgba(64,158,255,0.5)';
        container.style.borderTopLeftRadius = '6px';
        container.style.borderBottomLeftRadius = '6px';
        container.style.cursor = 'pointer';
        container.style.transition = 'width 0.3s ease';

        const panel = document.createElement('div');
        panel.style.display = 'none';
        panel.style.width = '240px';
        panel.style.backgroundColor = '#fff';
        panel.style.border = '1px solid #ccc';
        panel.style.borderRadius = '6px';
        panel.style.boxShadow = '0 0 5px rgba(0,0,0,0.2)';
        panel.style.padding = '10px';
        panel.style.position = 'absolute';
        panel.style.right = '70px';
        panel.style.top = '0';
        panel.style.fontSize = '14px';
        panel.style.color = '#333';

        const title = document.createElement('div');
        title.textContent = '账号列表';
        title.style.fontWeight = 'bold';
        title.style.marginBottom = '5px';
        panel.appendChild(title);

        const accountList = document.createElement('div');
        panel.appendChild(accountList);

        const refreshAccountList = () => {
            accountList.innerHTML = '';
            const accounts = getAccounts();
            if (Object.keys(accounts).length === 0) {
                accountList.innerHTML = '<div style="color:gray;">暂无账号</div>';
                return;
            }
            for (const [name, token] of Object.entries(accounts)) {
                const row = document.createElement('div');
                row.style.display = 'flex';
                row.style.justifyContent = 'space-between';
                row.style.alignItems = 'center';
                row.style.marginBottom = '5px';

                const btn = document.createElement('button');
                btn.textContent = name;
                btn.style.flex = '1';
                btn.style.marginRight = '5px';
                btn.style.padding = '4px 6px';
                btn.style.fontSize = '12px';
                btn.style.cursor = 'pointer';
                btn.onclick = () => {
                    if (!confirm(`确定切换到账号「${name}」吗?`)) return;

                    const baseInfoStr = localStorage.getItem('baseInfo');
                    if (!baseInfoStr) return alert('baseInfo 不存在');
                    try {
                        const baseInfo = JSON.parse(baseInfoStr);
                        baseInfo.data.token = token;
                        baseInfo.data.userInfo = {};
                        localStorage.setItem('baseInfo', JSON.stringify(baseInfo));
                        location.reload(); // 自动刷新页面
                    } catch (e) {
                        alert('baseInfo 解析失败');
                        console.error(e);
                    }
                };

                const del = document.createElement('button');
                del.textContent = '🗑️';
                del.style.padding = '3px 5px';
                del.style.fontSize = '12px';
                del.style.cursor = 'pointer';
                del.style.color = '#f00';
                del.onclick = () => {
                    if (confirm(`确定删除账号「${name}」?`)) {
                        delete accounts[name];
                        saveAccounts(accounts);
                        refreshAccountList();
                    }
                };

                row.appendChild(btn);
                row.appendChild(del);
                accountList.appendChild(row);
            }
        };

        const addBtn = document.createElement('button');
        addBtn.textContent = '+ 添加账号';
        addBtn.style.marginTop = '5px';
        addBtn.style.padding = '4px 8px';
        addBtn.style.fontSize = '12px';
        addBtn.style.cursor = 'pointer';
        addBtn.onclick = () => {
            const name = prompt('请输入账号名称:');
            if (!name) return;
            const token = prompt('请输入该账号的 token:');
            if (!token) return;
            const accounts = getAccounts();
            accounts[name] = token;
            saveAccounts(accounts);
            refreshAccountList();
        };
        panel.appendChild(addBtn);

        const toggleBtn = document.createElement('div');
        toggleBtn.textContent = '📱';
        toggleBtn.style.width = '40px';
        toggleBtn.style.height = '40px';
        toggleBtn.style.backgroundColor = '#409EFF';
        toggleBtn.style.color = '#fff';
        toggleBtn.style.fontSize = '22px';
        toggleBtn.style.textAlign = 'center';
        toggleBtn.style.lineHeight = '40px';
        toggleBtn.style.borderRadius = '50%';  // 圆形按钮
        toggleBtn.style.border = '2px solid #2c7cd1';
        toggleBtn.style.boxShadow = '0 2px 6px rgba(64,158,255,0.6)';
        toggleBtn.style.userSelect = 'none';
        toggleBtn.style.display = 'none';
        toggleBtn.style.cursor = 'pointer';
        toggleBtn.style.transition = 'background-color 0.3s ease, box-shadow 0.3s ease';



        toggleBtn.onclick = () => {
            isExpanded = !isExpanded;
            if (isExpanded) {
                refreshAccountList();
                panel.style.display = 'block';
                container.style.width = '70px';
            } else {
                panel.style.display = 'none';
                container.style.width = '10px';
            }
        };

        container.appendChild(toggleBtn);
        container.appendChild(panel);
        document.body.appendChild(container);

        // 悬停显示按钮(不触发面板)
        container.addEventListener('mouseenter', () => {
            if (!isExpanded) {
                container.style.width = '70px';
                toggleBtn.style.display = 'block';
            }
        });
        container.addEventListener('mouseleave', () => {
            if (!isExpanded) {
                container.style.width = '10px';
                toggleBtn.style.display = 'none';
            }
        });

        // 拖动吸附
        let dragging = false;
        let offsetX = 0, offsetY = 0;
        container.addEventListener('mousedown', (e) => {
            dragging = true;
            offsetX = e.clientX - container.getBoundingClientRect().left;
            offsetY = e.clientY - container.getBoundingClientRect().top;
            e.preventDefault();
        });
        document.addEventListener('mousemove', (e) => {
            if (dragging) {
                container.style.top = `${e.clientY - offsetY}px`;
                container.style.left = `${e.clientX - offsetX}px`;
                container.style.right = 'auto';
            }
        });
        document.addEventListener('mouseup', () => {
            if (dragging) {
                dragging = false;
                const winWidth = window.innerWidth;
                const winHeight = window.innerHeight;
                const rect = container.getBoundingClientRect();
                const snapLeft = rect.left < winWidth / 2;
                container.style.left = snapLeft ? '0px' : 'auto';
                container.style.right = snapLeft ? 'auto' : '0px';
                if (rect.top < 10) container.style.top = '10px';
                if (rect.top > winHeight - rect.height - 10)
                    container.style.top = (winHeight - rect.height - 10) + 'px';
            }
        });
    });
})();