【更新】GPT Team 账户管理面板 - Tampermonkey 脚本

一个简洁实用的油猴脚本,帮助你更高效地管理 ChatGPT Team 成员。不适用于bugTeam

:sparkles: 核心功能

  • :bar_chart: 实时统计:总邮箱数、ChatGPT 席位数、今日已用席位
  • :envelope: 邮箱管理:添加/删除邮箱,一键移出、邀请
  • :counterclockwise_arrows_button: 自动同步:每 5 秒自动同步成员状态,无需手动刷新
  • :artist_palette: 智能标识:已加入成员绿色背景,待加入成员灰色背景,自动排序
  • :warning: 席位提醒:ChatGPT 席位数 ≥2 时邀请会弹窗提醒
  • :link: 跨页面标记:复制邮箱后,在 auth.openai.com 页面自动标记该邮箱(黄色边框)
  • :date: 今日统计:自动记录当日使用的 ChatGPT 席位数(去重)

:package: 安装方法

  1. 安装 Tampermonkey 浏览器扩展
  2. 点击下方链接安装脚本
  3. 访问 https://chatgpt.com/admin/members 即可看到右侧管理面板

:bullseye: 使用场景

适合场景

  • 管理多个 ChatGPT Team 成员邮箱
  • 需要控制 ChatGPT 席位使用量
  • 频繁邀请新成员加入团队


// ==UserScript==
// @name         ChatGPT Team 账户管理面板
// @namespace    http://tampermonkey.net/
// @version      3.5.0
// @description  功能增强版 - 点击邮箱复制、邀请成员、角色过滤、紧凑布局、一键移出成员、全站注入仅members显示、tab检测、批量添加、搜索过滤、批量删除、备注功能、自动同步、跨页面标记
// @author       xcg
// @match        https://chatgpt.com/*
// @match        https://auth.openai.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 检测当前页面
    const isAuthPage = window.location.hostname === 'auth.openai.com';
    const isChatGPTSite = window.location.hostname === 'chatgpt.com';

    // auth.openai.com 页面:只标记邮箱行
    if (isAuthPage) {
        initAuthPageMarker();
        return;
    }

    // chatgpt.com 其他页面:不处理
    if (!isChatGPTSite) {
        return;
    }

    // ==================== Auth 页面标记功能 ====================
    function initAuthPageMarker() {
        console.log('Auth 页面标记功能已启动');

        // 添加标记样式
        GM_addStyle(`
            .last-copied-email-row {
                border: 3px solid #ffc107 !important;
                border-radius: 4px;
            }
        `);

        let isMarked = false; // 标记是否已完成
        let intervalId = null;

        // 定时检测并标记
        function markLastCopiedEmail() {
            // 如果已经标记成功,停止定时器
            if (isMarked) {
                if (intervalId) {
                    clearInterval(intervalId);
                    console.log('已找到并标记邮箱,停止检测');
                }
                return;
            }

            const lastCopiedEmail = GM_getValue('last_copied_email', '');
            if (!lastCopiedEmail) {
                return;
            }

            console.log('查找邮箱:', lastCopiedEmail);

            // 移除之前的标记
            document.querySelectorAll('.last-copied-email-row').forEach(el => {
                el.classList.remove('last-copied-email-row');
            });

            // 查找包含该邮箱的元素
            const emailRegex = new RegExp(lastCopiedEmail.replace(/[.*+?^$()|[\]\\]/g, '\\$&'), 'i');
            let found = false;

            // 方法1:查找所有包含 @ 的元素
            document.querySelectorAll('*').forEach(el => {
                if (el.children.length === 0 && el.textContent.includes('@')) {
                    if (emailRegex.test(el.textContent)) {
                        // 找到包含邮箱的元素,标记其父级行
                        const row = el.closest('tr, div[role="row"], li, [class*="row"], [class*="item"]');
                        if (row && !row.classList.contains('last-copied-email-row')) {
                            row.classList.add('last-copied-email-row');
                            console.log('✓ 已标记邮箱:', lastCopiedEmail);
                            found = true;
                            isMarked = true; // 标记成功,设置标志
                        }
                    }
                }
            });

            if (!found) {
                console.log('未找到邮箱:', lastCopiedEmail);
            }
        }

        // 初始标记
        setTimeout(markLastCopiedEmail, 1000);

        // 每 1 秒检测一次,找到后自动停止
        intervalId = setInterval(markLastCopiedEmail, 1000);
    }

    // ==================== Members 页面管理面板 ====================

    // 样式定义
    GM_addStyle(`
        #account-panel {
            position: fixed;
            right: 20px;
            top: 80px;
            width: 380px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border-radius: 16px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
            z-index: 999999;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            overflow: hidden;
        }

        #account-panel.collapsed {
            width: 180px;
        }

        #account-panel.collapsed .panel-body {
            display: none;
        }

        .panel-header {
            padding: 16px 20px;
            cursor: move;
            display: flex;
            justify-content: space-between;
            align-items: center;
            user-select: none;
            color: white;
        }

        .panel-title {
            font-size: 16px;
            font-weight: 700;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .panel-toggle {
            background: rgba(255, 255, 255, 0.2);
            border: none;
            color: white;
            width: 32px;
            height: 32px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 18px;
            transition: all 0.2s;
        }

        .panel-toggle:hover {
            background: rgba(255, 255, 255, 0.3);
        }

        .panel-body {
            padding: 14px;
            background: white;
            max-height: calc(85vh - 70px);
            overflow-y: auto;
        }

        .panel-body::-webkit-scrollbar {
            width: 8px;
        }

        .panel-body::-webkit-scrollbar-track {
            background: #f1f1f1;
        }

        .panel-body::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 4px;
        }

        .stats-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 6px;
            margin-bottom: 10px;
        }

        .stat-card {
            background: rgba(255, 255, 255, 0.95);
            padding: 6px 8px;
            border-radius: 8px;
            text-align: center;
            border: 1px solid rgba(102, 126, 234, 0.2);
        }

        .stat-value {
            font-size: 18px;
            font-weight: 700;
            color: #667eea;
            margin-bottom: 2px;
            line-height: 1;
        }

        .stat-label {
            font-size: 9px;
            color: #666;
            font-weight: 500;
        }

        .section {
            margin-bottom: 10px;
        }

        .section-title {
            font-size: 12px;
            font-weight: 600;
            color: #333;
            margin-bottom: 6px;
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .search-box {
            margin-bottom: 10px;
        }

        .search-input {
            width: 100%;
            padding: 6px 10px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 12px;
            outline: none;
            transition: all 0.2s;
            background: white;
            color: #333;
        }

        .search-input:focus {
            border-color: #667eea;
        }

        .batch-actions {
            display: flex;
            gap: 6px;
            margin-bottom: 10px;
        }

        .btn-batch {
            flex: 1;
            padding: 6px 10px;
            background: #ef4444;
            border: none;
            border-radius: 6px;
            color: white;
            font-size: 11px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-batch:hover {
            background: #dc2626;
        }

        .btn-batch.disabled {
            background: #ccc;
            cursor: not-allowed;
        }

        .input-group {
            display: flex;
            gap: 8px;
            margin-bottom: 10px;
        }

        .email-input {
            flex: 1;
            padding: 8px 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 13px;
            outline: none;
            transition: all 0.2s;
            background: white;
            color: #333;
            resize: vertical;
            min-height: 34px;
            max-height: 100px;
            font-family: inherit;
        }

        .email-input:focus {
            border-color: #667eea;
        }

        .email-input::placeholder {
            color: #999;
        }

        .btn-add {
            padding: 8px 16px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 8px;
            color: white;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-add:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
        }

        .email-list {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }

        .email-item {
            background: #f9f9f9;
            padding: 6px 8px;
            border-radius: 6px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border: 1px solid #e0e0e0;
            transition: all 0.2s;
        }

        .email-item:hover {
            border-color: #667eea;
        }

        .email-item.joined {
            background: #d1fae5;
            border-color: #6ee7b7;
        }

        .email-item.selected {
            border-color: #667eea;
            background: #e0e7ff;
        }

        .email-item.selected.joined {
            background: #a7f3d0;
        }

        .email-checkbox {
            margin-right: 8px;
            cursor: pointer;
            width: 16px;
            height: 16px;
        }

        .email-info {
            flex: 1;
            min-width: 0;
        }

        .email-text {
            font-size: 12px;
            color: #333;
            word-break: break-all;
            margin-bottom: 2px;
            cursor: pointer;
            user-select: text;
        }

        .email-text:hover {
            color: #667eea;
        }

        .email-text:active {
            color: #059669;
        }

        .email-meta {
            font-size: 9px;
            color: #666;
            display: flex;
            gap: 4px;
            align-items: center;
            flex-wrap: wrap;
        }

        .email-note {
            font-size: 10px;
            color: #888;
            font-style: italic;
            margin-top: 2px;
        }

        .btn-note {
            padding: 3px 6px;
            background: #8b5cf6;
            border: none;
            border-radius: 4px;
            color: white;
            font-size: 10px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-note:hover {
            background: #7c3aed;
        }

        .badge {
            display: inline-flex;
            align-items: center;
            padding: 3px 8px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: 600;
        }

        .badge-joined {
            background: #10b981;
            color: white;
        }

        .badge-pending {
            background: #fbbf24;
            color: white;
        }

        .btn-invite {
            padding: 4px 10px;
            background: #3b82f6;
            border: none;
            border-radius: 6px;
            color: white;
            font-size: 11px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            margin-right: 4px;
        }

        .btn-invite:hover {
            background: #2563eb;
        }

        .btn-invite.disabled {
            background: #ccc;
            cursor: not-allowed;
        }

        .btn-remove {
            padding: 4px 10px;
            background: #f59e0b;
            border: none;
            border-radius: 6px;
            color: white;
            font-size: 11px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            margin-right: 4px;
        }

        .btn-remove:hover {
            background: #d97706;
        }

        .btn-remove.disabled {
            background: #ccc;
            cursor: not-allowed;
        }

        .btn-delete {
            padding: 4px 10px;
            background: #ef4444;
            border: none;
            border-radius: 6px;
            color: white;
            font-size: 11px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-delete:hover {
            background: #dc2626;
        }

        .btn-copy {
            padding: 6px 12px;
            background: #10b981;
            border: none;
            border-radius: 6px;
            color: white;
            font-size: 12px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            margin-right: 4px;
        }

        .btn-copy:hover {
            background: #059669;
        }

        .email-actions {
            display: flex;
            gap: 4px;
        }

        .btn-sync {
            width: 100%;
            padding: 8px;
            background: #667eea;
            border: none;
            border-radius: 8px;
            color: white;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-sync:hover {
            background: #5568d3;
        }

        .btn-fill {
            width: 100%;
            padding: 10px;
            background: #10b981;
            border: none;
            border-radius: 8px;
            color: white;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            margin-bottom: 12px;
        }

        .btn-fill:hover {
            background: #059669;
        }

        .empty-state {
            text-align: center;
            padding: 40px 20px;
            color: #999;
        }

        .empty-icon {
            font-size: 48px;
            margin-bottom: 12px;
        }

        .loading-overlay {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(255, 255, 255, 0.9);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 10;
            border-radius: 8px;
        }

        .loading-spinner {
            width: 40px;
            height: 40px;
            border: 4px solid #e0e0e0;
            border-top-color: #667eea;
            border-radius: 50%;
            animation: spin 0.8s linear infinite;
        }

        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        .loading-text {
            margin-top: 12px;
            color: #666;
            font-size: 14px;
        }

        /* 自定义弹框 */
        .custom-modal {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 1000000;
            animation: fadeIn 0.2s ease;
        }

        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        .modal-content {
            background: white;
            border-radius: 16px;
            padding: 24px;
            min-width: 320px;
            max-width: 400px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            animation: slideUp 0.3s ease;
        }

        @keyframes slideUp {
            from {
                transform: translateY(20px);
                opacity: 0;
            }
            to {
                transform: translateY(0);
                opacity: 1;
            }
        }

        .modal-title {
            font-size: 18px;
            font-weight: 700;
            color: #333;
            margin-bottom: 12px;
        }

        .modal-message {
            font-size: 14px;
            color: #666;
            line-height: 1.6;
            margin-bottom: 20px;
            white-space: pre-wrap;
        }

        .modal-buttons {
            display: flex;
            gap: 10px;
            justify-content: flex-end;
        }

        .modal-btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }

        .modal-btn-cancel {
            background: #e0e0e0;
            color: #666;
        }

        .modal-btn-cancel:hover {
            background: #d0d0d0;
        }

        .modal-btn-confirm {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }

        .modal-btn-confirm:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
        }

        .modal-btn-ok {
            background: #10b981;
            color: white;
        }

        .modal-btn-ok:hover {
            background: #059669;
        }

        .modal-btn-warning {
            background: #ffc107;
            color: #000;
        }

        .modal-btn-warning:hover {
            background: #ffb300;
        }
    `);

    // 自定义弹框组件
    class CustomModal {
        // 确认对话框
        static confirm(title, message) {
            return new Promise((resolve) => {
                const modal = document.createElement('div');
                modal.className = 'custom-modal';
                modal.innerHTML = `
                    <div class="modal-content">
                        <div class="modal-title">${title}</div>
                        <div class="modal-message">${message}</div>
                        <div class="modal-buttons">
                            <button class="modal-btn modal-btn-cancel" id="modal-cancel">取消</button>
                            <button class="modal-btn modal-btn-confirm" id="modal-confirm">确定</button>
                        </div>
                    </div>
                `;

                document.body.appendChild(modal);

                const handleConfirm = () => {
                    modal.remove();
                    resolve(true);
                };

                const handleCancel = () => {
                    modal.remove();
                    resolve(false);
                };

                modal.querySelector('#modal-confirm').addEventListener('click', handleConfirm);
                modal.querySelector('#modal-cancel').addEventListener('click', handleCancel);
                modal.addEventListener('click', (e) => {
                    if (e.target === modal) handleCancel();
                });
            });
        }

        // 警告对话框
        static alert(title, message, type = 'warning') {
            return new Promise((resolve) => {
                const modal = document.createElement('div');
                modal.className = 'custom-modal';
                const btnClass = type === 'warning' ? 'modal-btn-warning' : 'modal-btn-ok';
                modal.innerHTML = `
                    <div class="modal-content">
                        <div class="modal-title">${title}</div>
                        <div class="modal-message">${message}</div>
                        <div class="modal-buttons">
                            <button class="modal-btn ${btnClass}" id="modal-ok">确定</button>
                        </div>
                    </div>
                `;

                document.body.appendChild(modal);

                const handleOk = () => {
                    modal.remove();
                    resolve();
                };

                modal.querySelector('#modal-ok').addEventListener('click', handleOk);
                modal.addEventListener('click', (e) => {
                    if (e.target === modal) handleOk();
                });
            });
        }
    }

    // 数据管理
    class AccountManager {
        constructor() {
            this.storageKey = 'chatgpt_accounts';
            this.accounts = this.load();
        }

        load() {
            try {
                return JSON.parse(GM_getValue(this.storageKey, '[]'));
            } catch (e) {
                return [];
            }
        }

        save() {
            GM_setValue(this.storageKey, JSON.stringify(this.accounts));
        }

        // 获取今日日期字符串 YYYY-MM-DD
        getTodayKey() {
            const now = new Date();
            return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
        }

        // 获取今日已使用的 ChatGPT 席位数(基于 lastGptSeatAt)
        getTodayChatGPTUsage() {
            try {
                const todayKey = this.getTodayKey();
                return this.accounts.filter(account => {
                    if (!account.lastGptSeatAt) return false;
                    const lastDate = new Date(account.lastGptSeatAt);
                    const dateKey = `${lastDate.getFullYear()}-${String(lastDate.getMonth() + 1).padStart(2, '0')}-${String(lastDate.getDate()).padStart(2, '0')}`;
                    return dateKey === todayKey;
                }).length;
            } catch (e) {
                return 0;
            }
        }

        add(email) {
            email = email.trim().toLowerCase();
            if (!this.validate(email)) {
                return { success: false, message: '邮箱格式不正确' };
            }
            if (this.accounts.some(a => a.email === email)) {
                return { success: false, message: '邮箱已存在' };
            }
            this.accounts.push({
                email: email,
                addedAt: new Date().toISOString(),
                joinedAt: null,
                status: 'pending',
                seatType: null,
                lastGptSeatAt: null,
                role: null,
                note: ''
            });
            this.save();
            return { success: true };
        }

        // 批量添加邮箱
        addBatch(emailsText) {
            const emails = emailsText
                .split(/[\n,;,;]+/)  // 支持换行、逗号、分号分隔
                .map(e => e.trim().toLowerCase())
                .filter(e => e);  // 过滤空字符串

            const results = {
                success: [],
                failed: [],
                duplicate: []
            };

            emails.forEach(email => {
                if (!this.validate(email)) {
                    results.failed.push(email);
                } else if (this.accounts.some(a => a.email === email)) {
                    results.duplicate.push(email);
                } else {
                    this.accounts.push({
                        email: email,
                        addedAt: new Date().toISOString(),
                        joinedAt: null,
                        status: 'pending',
                        seatType: null,
                        lastGptSeatAt: null,
                        role: null,
                        note: ''
                    });
                    results.success.push(email);
                }
            });

            this.save();
            return results;
        }

        remove(email) {
            this.accounts = this.accounts.filter(a => a.email !== email);
            this.save();
        }

        // 批量删除
        removeBatch(emails) {
            this.accounts = this.accounts.filter(a => !emails.includes(a.email));
            this.save();
        }

        update(email, updates) {
            const account = this.accounts.find(a => a.email === email);
            if (account) {
                Object.assign(account, updates);
                this.save();
            }
        }

        // 更新备注
        updateNote(email, note) {
            this.update(email, { note: note });
        }

        validate(email) {
            return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
        }

        getAll() {
            return this.accounts;
        }

        getStats() {
            const total = this.accounts.length;
            const joined = this.accounts.filter(a => a.status === 'joined').length;
            // 修复:检查席位类型是否包含 ChatGPT
            const chatgptSeats = this.accounts.filter(a =>
                    a.seatType && (
                        a.seatType === 'ChatGPT' ||
                        a.seatType.includes('ChatGPT') ||
                        a.seatType === 'chatgpt'
                    )
            ).length;
            const todayUsed = this.getTodayChatGPTUsage();
            return { total, joined, pending: total - joined, chatgptSeats, todayUsed };
        }
    }

    // 页面扫描
    class PageScanner {
        scanMembers() {
            const members = new Map();
            const emailRegex = /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g;

            // 扫描所有 title 属性
            document.querySelectorAll('[title*="@"]').forEach(el => {
                const title = el.getAttribute('title');
                const matches = title?.match(emailRegex);
                if (matches) {
                    matches.forEach(email => {
                        const row = el.closest('tr');
                        if (row) {
                            // 查找席位类型
                            let seatType = null;
                            let role = null;

                            const cells = row.querySelectorAll('td');
                            cells.forEach((cell, index) => {
                                const text = cell.textContent.trim();

                                // 查找角色列(通常包含 "所有者"/"Owner"/"成员"/"Member")
                                if (text === '所有者' || text === 'Owner') {
                                    role = '所有者';
                                    console.log('找到所有者角色:', email);
                                } else if (text === '成员' || text === 'Member') {
                                    role = '成员';
                                }

                                // 查找席位类型
                                if (text === 'ChatGPT' || text === 'ChatGPT Plus' ||
                                    text.includes('ChatGPT') || text === 'API' ||
                                    text === 'chatgpt' || text.toLowerCase().includes('chatgpt')) {
                                    seatType = text;
                                    console.log('找到席位类型:', email, '→', text);
                                }
                            });

                            // 如果没找到席位,尝试从按钮查找
                            if (!seatType) {
                                const buttons = row.querySelectorAll('button');
                                buttons.forEach(btn => {
                                    const text = btn.textContent.trim();
                                    if (text === 'ChatGPT' || text === 'ChatGPT Plus' ||
                                        text.includes('ChatGPT') || text === 'API' ||
                                        text === 'chatgpt' || text.toLowerCase().includes('chatgpt')) {
                                        seatType = text;
                                    }
                                });
                            }

                            members.set(email.toLowerCase(), { seatType, role });
                        }
                    });
                }
            });

            console.log('扫描完成,共找到', members.size, '个成员');
            members.forEach((info, email) => {
                console.log('  -', email, '席位:', info.seatType || '未知', '角色:', info.role || '未知');
            });

            return members;
        }
    }

    // UI 管理
    class PanelUI {
        constructor(manager, scanner) {
            this.manager = manager;
            this.scanner = scanner;
            this.panel = null;
            this.isCollapsed = false;
            this.dragState = { isDragging: false, startX: 0, startY: 0, initialX: 0, initialY: 0 };
            this.copiedEmails = new Set(); // 记录已复制的邮箱
            this.hasSynced = false; // 是否已同步过
            this.searchKeyword = ''; // 搜索关键词
            this.selectedEmails = new Set(); // 批量选中的邮箱
            this.init();
        }

        init() {
            this.createPanel();
            this.bindEvents();
            this.render();
            this.startAutoSync();
            this.watchUrlChange(); // 监听 URL 变化
        }

        watchUrlChange() {
            // 监听 URL 变化(SPA 应用)
            let lastPath = window.location.pathname;
            setInterval(() => {
                const currentPath = window.location.pathname;
                if (currentPath !== lastPath) {
                    lastPath = currentPath;
                    const isMembersPage = currentPath.includes('/admin/members');
                    if (this.panel) {
                        this.panel.style.display = isMembersPage ? 'block' : 'none';
                    }
                }
            }, 500);
        }

        createPanel() {
            this.panel = document.createElement('div');
            this.panel.id = 'account-panel';

            // 根据当前页面决定是否显示
            const isMembersPage = window.location.pathname.includes('/admin/members');
            this.panel.style.display = isMembersPage ? 'block' : 'none';

            this.panel.innerHTML = `
                <div class="panel-header">
                    <div class="panel-title">GPTeam v3.5.0</div>
                    <button class="panel-toggle" id="toggle-btn">−</button>
                </div>
                <div class="panel-body">
                    <div class="stats-grid">
                        <div class="stat-card">
                            <div class="stat-value" id="stat-total">0</div>
                            <div class="stat-label">总邮箱</div>
                        </div>
                        <div class="stat-card">
                            <div class="stat-value" id="stat-chatgpt">0</div>
                            <div class="stat-label">ChatGPT 席位</div>
                        </div>
                        <div class="stat-card">
                            <div class="stat-value" id="stat-today">0</div>
                            <div class="stat-label">今日已用</div>
                        </div>
                    </div>

                    <div class="section">
                        <div class="section-title">✉️ 添加邮箱</div>
                        <div class="input-group">
                            <textarea class="email-input" id="email-input" placeholder="输入邮箱(支持多行或逗号分隔)..." rows="1"></textarea>
                            <button class="btn-add" id="add-btn">添加</button>
                        </div>
                    </div>

                    <div class="section">
                        <div class="section-title">📋 邮箱列表</div>
                        <div class="search-box">
                            <input type="text" class="search-input" id="search-input" placeholder="🔍 搜索邮箱或备注...">
                        </div>
                        <div class="batch-actions">
                            <button class="btn-batch" id="batch-delete-btn">删除选中</button>
                        </div>
                        <div style="position: relative;">
                            <div class="email-list" id="email-list"></div>
                            <div class="loading-overlay" id="loading-overlay" style="display: none;">
                                <div class="loading-spinner"></div>
                                <div class="loading-text">正在同步...</div>
                            </div>
                        </div>
                    </div>
                </div>
            `;
            document.body.appendChild(this.panel);
        }

        bindEvents() {
            // 折叠
            document.getElementById('toggle-btn').addEventListener('click', () => {
                this.isCollapsed = !this.isCollapsed;
                this.panel.classList.toggle('collapsed');
                document.getElementById('toggle-btn').textContent = this.isCollapsed ? '+' : '−';
            });

            // 添加邮箱
            document.getElementById('add-btn').addEventListener('click', () => this.handleAdd());
            document.getElementById('email-input').addEventListener('keypress', (e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    this.handleAdd();
                }
            });

            // 搜索
            document.getElementById('search-input').addEventListener('input', (e) => {
                this.searchKeyword = e.target.value.toLowerCase();
                this.render();
            });

            // 批量删除
            document.getElementById('batch-delete-btn').addEventListener('click', () => this.handleBatchDelete());

            // 拖拽
            const header = this.panel.querySelector('.panel-header');
            header.addEventListener('mousedown', (e) => this.startDrag(e));
            document.addEventListener('mousemove', (e) => this.onDrag(e));
            document.addEventListener('mouseup', () => this.endDrag());
        }

        handleAdd() {
            const input = document.getElementById('email-input');
            const emailsText = input.value.trim();
            if (!emailsText) return;

            // 检测是否为批量添加(包含换行或分隔符)
            if (emailsText.includes('\n') || emailsText.includes(',') || emailsText.includes(',') || emailsText.includes(';') || emailsText.includes(';')) {
                // 批量添加
                const results = this.manager.addBatch(emailsText);

                let message = '';
                if (results.success.length > 0) {
                    message += `✓ 成功添加 ${results.success.length} 个邮箱\n`;
                }
                if (results.duplicate.length > 0) {
                    message += `⚠ ${results.duplicate.length} 个邮箱已存在\n`;
                }
                if (results.failed.length > 0) {
                    message += `✗ ${results.failed.length} 个邮箱格式错误\n`;
                }

                if (results.success.length > 0) {
                    input.value = '';
                    this.render();
                }

                if (message) {
                    CustomModal.alert('批量添加结果', message.trim(), results.failed.length > 0 ? 'warning' : 'ok');
                }
            } else {
                // 单个添加
                const result = this.manager.add(emailsText);
                if (result.success) {
                    input.value = '';
                    this.render();
                } else {
                    CustomModal.alert('添加失败', result.message, 'warning');
                }
            }
        }

        handleDelete(email) {
            CustomModal.confirm('删除邮箱', `确定要删除 ${email} 吗?`).then(confirmed => {
                if (confirmed) {
                    this.manager.remove(email);
                    this.selectedEmails.delete(email);
                    this.render();
                }
            });
        }

        handleBatchDelete() {
            if (this.selectedEmails.size === 0) {
                CustomModal.alert('批量删除', '请先选择要删除的邮箱', 'warning');
                return;
            }

            CustomModal.confirm('批量删除', `确定要删除选中的 ${this.selectedEmails.size} 个邮箱吗?`).then(confirmed => {
                if (confirmed) {
                    this.manager.removeBatch([...this.selectedEmails]);
                    this.selectedEmails.clear();
                    this.render();
                }
            });
        }

        handleNote(email, currentNote) {
            const note = prompt('输入备注:', currentNote || '');
            if (note !== null) {
                this.manager.updateNote(email, note.trim());
                this.render();
            }
        }

        handleRemove(email) {
            CustomModal.confirm('移出成员', `确定要从团队移出 ${email} 吗?`).then(confirmed => {
                if (confirmed) {
                    this.clickMenuAndRemoveByEmail(email);
                }
            });
        }

        handleInvite(email) {
            // 检查 ChatGPT 席位数
            const stats = this.manager.getStats();
            if (stats.chatgptSeats >= 2) {
                // 席位数 >= 2,弹窗警告并确认
                CustomModal.confirm('⚠️ 席位警告', `当前已有 ${stats.chatgptSeats} 个 ChatGPT 席位,确定要邀请 ${email} 吗?`).then(confirmed => {
                    if (confirmed) {
                        // 先复制邮箱
                        this.handleCopy(email).then(() => {
                            this.clickInviteButton(email);
                        });
                    }
                });
            } else {
                // 席位数 < 2,直接邀请,不需要确认
                // 先复制邮箱
                this.handleCopy(email).then(() => {
                    this.clickInviteButton(email);
                });
            }
        }

        clickInviteButton(email) {
            // 查找"邀请成员"按钮
            const inviteBtn = Array.from(document.querySelectorAll('button'))
                .find(btn => {
                    const text = btn.textContent.trim();
                    return text === '邀请成员' || text === 'Invite member';
                });

            if (!inviteBtn) {
                console.warn('未找到"邀请成员"按钮');
                CustomModal.alert('邀请失败', '页面上未找到"邀请成员"按钮', 'warning');
                return;
            }

            console.log('找到邀请按钮:', inviteBtn);
            inviteBtn.click();
            console.log('已点击"邀请成员"按钮');

            // 等待弹窗出现,填充邮箱
            setTimeout(() => {
                const emailInput = document.querySelector('input#email[type="email"]');
                if (!emailInput) {
                    console.warn('未找到邮箱输入框');
                    CustomModal.alert('邀请失败', '未找到邮箱输入框', 'warning');
                    return;
                }

                console.log('找到邮箱输入框:', emailInput);

                // React 受控组件需要设置 nativeValue
                const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
                nativeInputValueSetter.call(emailInput, email);

                // 触发 React 事件
                const inputEvent = new Event('input', { bubbles: true });
                emailInput.dispatchEvent(inputEvent);

                const changeEvent = new Event('change', { bubbles: true });
                emailInput.dispatchEvent(changeEvent);

                console.log('已填充邮箱:', email);
                console.log('当前输入框值:', emailInput.value);

                // 等待一下,自动点击发送按钮
                setTimeout(() => {
                    // 排除我们自己面板的按钮
                    const sendBtn = Array.from(document.querySelectorAll('button'))
                        .filter(btn => !btn.closest('#account-panel'))
                        .find(btn => {
                            const text = btn.textContent.trim();
                            return text === '发送邀请' || text === 'Send invite' || text.includes('发送') || text.includes('Send');
                        });

                    if (sendBtn) {
                        console.log('找到发送按钮:', sendBtn);
                        console.log('发送按钮 outerHTML:', sendBtn.outerHTML);
                        sendBtn.click();
                        console.log('已点击发送按钮');

                        // 邀请成功,1秒后同步状态
                        setTimeout(() => {
                            this.syncStatus();
                        }, 1000);
                    } else {
                        console.log('未找到发送按钮');
                    }
                }, 800);
            }, 800);
        }

        clickMenuAndRemoveByEmail(email) {
            // 找到包含指定邮箱的元素
            const emailCell = Array.from(document.querySelectorAll('*'))
                .find(el => el.textContent.trim() === email);

            console.log('匹配到的邮箱元素:', emailCell);

            if (!emailCell) {
                console.warn('页面上没有找到该邮箱:', email);
                CustomModal.alert('移除失败', '页面上未找到该邮箱,请确保在用户tab', 'warning');
                return;
            }

            // 找到该行
            const row = emailCell.closest('tr') || emailCell.closest('.member-row');
            console.log('匹配到的行元素:', row);

            if (!row) {
                console.warn('未找到包含邮箱的行');
                CustomModal.alert('移除失败', '未找到邮箱所在行', 'warning');
                return;
            }

            // 查找按钮
            let button = row.querySelector('button[aria-haspopup="menu"]');
            if (!button) {
                button = row.querySelector('.ellipsis, .more-options, button');
            }

            console.log('匹配到的按钮:', button);
            console.log('按钮 outerHTML:', button?.outerHTML);

            if (!button) {
                console.warn('未找到该行的菜单按钮');
                CustomModal.alert('移除失败', '未找到菜单按钮', 'warning');
                return;
            }

            // 点击按钮,触发菜单
            const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
            ['pointerdown','mousedown','mouseup','click'].forEach(type => {
                const event = new MouseEvent(type, {
                    bubbles: true,
                    cancelable: true,
                    view: win
                });
                button.dispatchEvent(event);
                console.log(`已触发 ${type} 事件`);
            });

            // 等待菜单出现并点击"移除成员"
            let elapsed = 0;
            const interval = setInterval(() => {
                elapsed += 500;
                const menu = document.querySelector('div[role="menu"], div[aria-expanded="true"], body > div:nth-of-type(6) div');
                if (menu) {
                    console.log('检测到菜单:', menu);

                    const removeBtn = Array.from(menu.querySelectorAll('div[role="menuitem"], button, div'))
                        .find(el => {
                            const text = el.textContent.trim();
                            return text === '移除成员' || text === 'Remove member';
                        });

                    if (removeBtn) {
                        console.log('匹配到移除成员按钮:', removeBtn);
                        console.log('移除按钮 outerHTML:', removeBtn.outerHTML);

                        // 如果匹配到的是外层 group,找到内层的 menuitem
                        let actualBtn = removeBtn;
                        if (removeBtn.getAttribute('role') === 'group') {
                            actualBtn = removeBtn.querySelector('div[role="menuitem"]');
                            console.log('找到内层 menuitem:', actualBtn);
                        }

                        if (!actualBtn) {
                            console.warn('未找到实际可点击的元素');
                            return;
                        }

                        const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
                        const clickEvent = new MouseEvent('click', {
                            bubbles: true,
                            cancelable: true,
                            view: win
                        });
                        actualBtn.dispatchEvent(clickEvent);
                        console.log('已点击"移除成员"按钮');
                        clearInterval(interval);

                        // 等待确认对话框并点击删除按钮
                        setTimeout(() => {
                            // 排除我们自己面板内的按钮
                            const confirmBtn = Array.from(document.querySelectorAll('button.btn-danger, button'))
                                .filter(btn => !btn.closest('#account-panel')) // 排除面板内的按钮
                                .find(btn => {
                                    const text = btn.textContent.trim();
                                    return text === '删除' || text === 'Delete' || text === '移除' || text === 'Remove';
                                });
                            if (confirmBtn) {
                                console.log('找到删除确认按钮:', confirmBtn);
                                console.log('删除按钮 outerHTML:', confirmBtn.outerHTML);
                                confirmBtn.click();
                                console.log('已点击删除确认按钮');

                                // 移除成功,1秒后同步状态
                                setTimeout(() => {
                                    this.syncStatus();
                                }, 1000);
                            } else {
                                console.log('未找到删除确认按钮');
                            }
                        }, 500);
                    }
                }

                if (elapsed >= 5000) {
                    console.warn('5 秒内未检测到菜单或移除按钮,可能触发失败');
                    CustomModal.alert('移除失败', '未能找到移除按钮,操作超时', 'warning');
                    clearInterval(interval);
                }
            }, 500); // 每 500ms 检查一次
        }

        async handleCopy(email) {
            // 先同步状态
            this.syncStatus();
            await this.sleep(500);

            // 复制邮箱到剪贴板(不检查席位数)
            try {
                await navigator.clipboard.writeText(email);
                console.log('已复制:', email);

                // 保存最后复制的邮箱(跨页面共享)
                GM_setValue('last_copied_email', email);
                console.log('已保存最后复制的邮箱:', email);

                // 标记为已复制
                this.copiedEmails.add(email);

                // 立即更新按钮显示
                this.render();

            } catch (err) {
                console.error('复制失败:', err);
                // 备用方案:使用旧方法
                const textarea = document.createElement('textarea');
                textarea.value = email;
                textarea.style.position = 'fixed';
                textarea.style.opacity = '0';
                document.body.appendChild(textarea);
                textarea.select();
                document.execCommand('copy');
                document.body.removeChild(textarea);

                // 保存最后复制的邮箱
                GM_setValue('last_copied_email', email);
                console.log('已保存最后复制的邮箱:', email);

                // 标记为已复制
                this.copiedEmails.add(email);

                // 立即更新按钮显示
                this.render();
            }
        }

        sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        fillInviteInput() {
            const pending = this.manager.getAll().filter(a => a.status === 'pending');
            if (pending.length === 0) {
                alert('没有待加入的邮箱');
                return;
            }

            // 查找邀请输入框
            const inviteInput = document.querySelector('input#email[type="email"]') ||
                document.querySelector('input[aria-label="电子邮件"]') ||
                document.querySelector('input[placeholder*="电子邮件"]') ||
                document.querySelector('input[placeholder*="email" i]');

            if (!inviteInput) {
                alert('未找到邀请输入框\n\n请先打开"邀请成员"弹框');
                return;
            }

            // 填充第一个待加入的邮箱
            const email = pending[0].email;
            inviteInput.value = email;
            inviteInput.focus();

            // 触发 input 事件
            inviteInput.dispatchEvent(new Event('input', { bubbles: true }));
            inviteInput.dispatchEvent(new Event('change', { bubbles: true }));

            console.log('已填充邮箱:', email);
            alert(`已填充邮箱:${email}\n\n请点击"发送邀请"按钮`);
        }

        syncStatus() {
            // 检查当前 URL 是否是 members 页面
            if (!window.location.pathname.includes('/admin/members')) {
                console.log('不在 members 页面,跳过同步');
                return;
            }

            // 检查是否在"用户"tab
            // 方法:检测页面上是否有"所有者/Owner"关键词,这是用户tab的列标题
            const pageText = document.body.innerText;
            const isUserTab = pageText.includes('所有者') || pageText.includes('Owner');

            if (!isUserTab) {
                console.log('不在用户 tab,跳过同步');
                return;
            }

            const pageMembers = this.scanner.scanMembers();
            const accounts = this.manager.getAll();

            // 1. 先将页面上的成员自动添加到列表(排除所有者)
            pageMembers.forEach((memberInfo, email) => {
                // 跳过所有者角色
                if (memberInfo.role === '所有者') {
                    console.log('跳过所有者:', email);
                    return;
                }

                const exists = accounts.find(a => a.email === email);
                if (!exists) {
                    // 自动添加页面上的成员
                    const isChatGPTSeat = memberInfo.seatType && (
                        memberInfo.seatType === 'ChatGPT' ||
                        memberInfo.seatType.includes('ChatGPT') ||
                        memberInfo.seatType === 'chatgpt'
                    );
                    this.manager.accounts.push({
                        email: email,
                        addedAt: new Date().toISOString(),
                        joinedAt: new Date().toISOString(),
                        status: 'joined',
                        seatType: memberInfo.seatType,
                        lastGptSeatAt: isChatGPTSeat ? new Date().toISOString() : null,
                        role: memberInfo.role,
                        note: ''
                    });
                    console.log('自动添加成员:', email);
                }
            });

            // 保存一次
            this.manager.save();

            // 2. 更新现有账户的状态
            const updatedAccounts = this.manager.getAll();
            updatedAccounts.forEach(account => {
                const pageMember = pageMembers.get(account.email);
                if (pageMember) {
                    // 跳过所有者角色
                    if (pageMember.role === '所有者') {
                        console.log('检测到所有者,从列表移除:', account.email);
                        this.manager.remove(account.email);
                        return;
                    }

                    // 已在团队中
                    const isChatGPTSeat = pageMember.seatType && (
                        pageMember.seatType === 'ChatGPT' ||
                        pageMember.seatType.includes('ChatGPT') ||
                        pageMember.seatType === 'chatgpt'
                    );

                    if (account.status !== 'joined') {
                        this.manager.update(account.email, {
                            status: 'joined',
                            joinedAt: new Date().toISOString(),
                            seatType: pageMember.seatType,
                            role: pageMember.role,
                            lastGptSeatAt: isChatGPTSeat ? new Date().toISOString() : account.lastGptSeatAt
                        });
                    } else {
                        // 更新席位类型和角色
                        const updates = {};
                        if (account.seatType !== pageMember.seatType) {
                            updates.seatType = pageMember.seatType;
                        }
                        if (account.role !== pageMember.role) {
                            updates.role = pageMember.role;
                        }
                        // 如果是 ChatGPT 席位,更新最后时间
                        if (isChatGPTSeat) {
                            updates.lastGptSeatAt = new Date().toISOString();
                        }
                        if (Object.keys(updates).length > 0) {
                            this.manager.update(account.email, updates);
                        }
                    }
                } else {
                    // 不在团队中(已移除)
                    if (account.status === 'joined') {
                        this.manager.update(account.email, {
                            status: 'pending',
                            joinedAt: null,
                            seatType: null
                            // 注意:不清空 lastGptSeatAt,保留最后时间
                        });
                    }
                }
            });

            // 3. 清空已复制标记(下次同步后按钮恢复)
            this.copiedEmails.clear();

            // 4. 只有获取到至少一个账户才标记为已同步
            const finalAccounts = this.manager.getAll();
            if (finalAccounts.length > 0) {
                this.hasSynced = true;
                const loadingOverlay = document.getElementById('loading-overlay');
                if (loadingOverlay) {
                    loadingOverlay.style.display = 'none';
                }
                console.log('同步完成,共', finalAccounts.length, '个账户');
            } else {
                console.log('同步完成,但未找到任何账户');
            }

            this.render();
        }

        render() {
            const accounts = this.manager.getAll();
            const stats = this.manager.getStats();
            const listEl = document.getElementById('email-list');
            const loadingOverlay = document.getElementById('loading-overlay');

            // 更新统计
            document.getElementById('stat-total').textContent = stats.total;
            document.getElementById('stat-chatgpt').textContent = stats.chatgptSeats;
            document.getElementById('stat-today').textContent = stats.todayUsed;

            // 如果还没同步过,显示 loading
            if (!this.hasSynced) {
                if (loadingOverlay) {
                    loadingOverlay.style.display = 'flex';
                }
                listEl.innerHTML = '<div class="empty-state"><div class="empty-icon">📭</div><div>等待同步...</div></div>';
                return;
            }

            // 隐藏 loading
            if (loadingOverlay) {
                loadingOverlay.style.display = 'none';
            }

            // 过滤搜索
            let filteredAccounts = accounts;
            if (this.searchKeyword) {
                filteredAccounts = accounts.filter(a =>
                    a.email.includes(this.searchKeyword) ||
                    (a.note && a.note.toLowerCase().includes(this.searchKeyword))
                );
            }

            // 渲染列表
            if (filteredAccounts.length === 0) {
                listEl.innerHTML = '<div class="empty-state"><div class="empty-icon">📭</div><div>' + (this.searchKeyword ? '无匹配结果' : '暂无邮箱') + '</div></div>';
                return;
            }

            // 排序:已加入的排在前面
            const sortedAccounts = [...filteredAccounts].sort((a, b) => {
                if (a.status === 'joined' && b.status !== 'joined') return -1;
                if (a.status !== 'joined' && b.status === 'joined') return 1;
                return 0;
            });

            // 计算已移除时长的辅助函数
            const getRemovalDuration = (lastGptSeatAt) => {
                if (!lastGptSeatAt) return null;
                const now = new Date();
                const lastTime = new Date(lastGptSeatAt);
                const diffMs = now - lastTime;
                const hours = Math.floor(diffMs / (1000 * 60 * 60));
                const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
                return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`;
            };

            listEl.innerHTML = sortedAccounts.map(account => {
                const isJoined = account.status === 'joined';
                const isCopied = this.copiedEmails.has(account.email);
                const isSelected = this.selectedEmails.has(account.email);
                const removalDuration = !isJoined && account.lastGptSeatAt ? getRemovalDuration(account.lastGptSeatAt) : null;

                return `
                    <div class="email-item ${isJoined ? 'joined' : ''} ${isSelected ? 'selected' : ''}" data-email="${account.email}">
                        <input type="checkbox" class="email-checkbox" data-email="${account.email}" ${isSelected ? 'checked' : ''}>
                        <div class="email-info">
                            <div class="email-text" data-email="${account.email}">${account.email}</div>
                            ${account.note ? `<div class="email-note">📝 ${account.note}</div>` : ''}
                            <div class="email-meta">
                                ${account.seatType ? `<span>席位: ${account.seatType}</span>` : ''}
                                ${removalDuration ? `<span>已移除 ${removalDuration}</span>` : ''}
                            </div>
                        </div>
                        <div class="email-actions">
                            <button class="btn-note" data-email="${account.email}" title="添加备注">📝</button>
                            ${isJoined ? `<button class="btn-remove" data-email="${account.email}">移出</button>` : `<button class="btn-invite" data-email="${account.email}">邀请</button>`}
                            <button class="btn-delete" data-email="${account.email}">删除</button>
                        </div>
                    </div>
                `;
            }).join('');

            // 绑定复选框
            listEl.querySelectorAll('.email-checkbox').forEach(checkbox => {
                checkbox.addEventListener('change', (e) => {
                    const email = e.target.dataset.email;
                    if (e.target.checked) {
                        this.selectedEmails.add(email);
                    } else {
                        this.selectedEmails.delete(email);
                    }
                    this.render();
                });
            });

            // 绑定备注按钮
            listEl.querySelectorAll('.btn-note').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const email = e.target.dataset.email;
                    const account = this.manager.getAll().find(a => a.email === email);
                    this.handleNote(email, account ? account.note : '');
                });
            });

            // 绑定邮箱文本点击复制
            listEl.querySelectorAll('.email-text').forEach(el => {
                el.addEventListener('click', (e) => {
                    const email = e.target.dataset.email;
                    this.handleCopy(email);
                });
            });

            // 绑定移出按钮
            listEl.querySelectorAll('.btn-remove').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const email = e.target.dataset.email;
                    this.handleRemove(email);
                });
            });

            // 绑定邀请按钮
            listEl.querySelectorAll('.btn-invite').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const email = e.target.dataset.email;
                    this.handleInvite(email);
                });
            });

            // 绑定删除按钮
            listEl.querySelectorAll('.btn-delete').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const email = e.target.dataset.email;
                    this.handleDelete(email);
                });
            });
        }

        startDrag(e) {
            if (e.target.closest('.panel-toggle')) return;
            this.dragState.isDragging = true;
            this.dragState.startX = e.clientX;
            this.dragState.startY = e.clientY;
            const rect = this.panel.getBoundingClientRect();
            this.dragState.initialX = rect.left;
            this.dragState.initialY = rect.top;
        }

        onDrag(e) {
            if (!this.dragState.isDragging) return;
            e.preventDefault();
            const deltaX = e.clientX - this.dragState.startX;
            const deltaY = e.clientY - this.dragState.startY;
            this.panel.style.left = (this.dragState.initialX + deltaX) + 'px';
            this.panel.style.top = (this.dragState.initialY + deltaY) + 'px';
            this.panel.style.right = 'auto';
        }

        endDrag() {
            this.dragState.isDragging = false;
        }

        startAutoSync() {
            // 初始同步
            setTimeout(() => this.syncStatus(), 2000);

            // 每 5 秒自动同步(仅在 members 页面)
            setInterval(() => {
                if (!this.isCollapsed && window.location.pathname.includes('/admin/members')) {
                    this.syncStatus();
                }
            }, 5000);
        }
    }

    // 初始化
    function init() {
        const manager = new AccountManager();
        const scanner = new PageScanner();
        const ui = new PanelUI(manager, scanner);
        console.log('ChatGPT Team 账户管理面板已加载');
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        setTimeout(init, 1000);
    }
})();



3 个赞

新增 一键移出、邀请
新增 已移除时间显示

感谢分享脚本

感谢大佬的教程

感謝分享