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

支持账号管理、切换、删除、确认切号、切号后刷新页面。优化版UI更美观,交互更流畅。

// ==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);
    });
})();