Greasy Fork 支持简体中文。

Claude Session Key 管理器

Claude Session Key 管理工具,支持拖拽、测活、导入导出、WebDAV云备份等功能

// ==UserScript==
// @name         Claude Session Key 管理器
// @name:zh-CN   Claude Session Key 管理器
// @name:en      Claude Session Key Manager
// @version      1.0.1
// @description  Claude Session Key 管理工具,支持拖拽、测活、导入导出、WebDAV云备份等功能
// @description:zh-CN  Claude Session Key 管理工具,支持拖拽、测活、批量导入导出、WebDAV云备份等功能
// @description:en  Claude Session Key Manager with drag-and-drop, token validation, import/export, WebDAV backup and more
// @author       xiaoye6688
// @namespace    https://greasyfork.org/users/1317128-xiaoye6688
// @homepage     https://greasyfork.org/zh-CN/users/1317128-xiaoye6688
// @supportURL   https://greasyfork.org/zh-CN/users/1317128-xiaoye6688
// @license      MIT
// @date         2025-03-09
// @modified     2025-03-09

// @match        https://claude.ai/*
// @match        https://claude.asia/*
// @match        https://demo.fuclaude.com/*
// @include      https://*fuclaude*/*
//
// @icon         https://claude.ai/favicon.ico
// @run-at       document-end

// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_info

// @connect      ipapi.co
// @connect      api.claude.ai
// @connect      *
// ==/UserScript==

(function () {
    "use strict";

    const config = {
        storageKey: "claudeTokens",
        ipApiUrl: "https://ipapi.co/country_code",
        defaultToken: {
            name: "Token00",
            key: "sk-key",
        },
        currentTokenKey: "currentClaudeToken",
        testResultsKey: "claudeTokenTestResults",
        testResultExpiry: 1800000, // 30分钟过期
    };

    const theme = {
        light: {
            bgColor: "#fcfaf5",
            textColor: "#333",
            borderColor: "#ccc",
            buttonBg: "#f5f1e9",
            buttonHoverBg: "#e5e1d9",
            modalBg: "rgba(0, 0, 0, 0.5)",
        },
        dark: {
            bgColor: "#2c2b28",
            textColor: "#f5f4ef",
            borderColor: "#3f3f3c",
            buttonBg: "#3f3f3c",
            buttonHoverBg: "#4a4a47",
            modalBg: "rgba(0, 0, 0, 0.7)",
        },
    };

    const getStyles = (isDarkMode) => `
        :root {
            --bg-color: ${isDarkMode ? theme.dark.bgColor : theme.light.bgColor};
            --text-color: ${isDarkMode ? theme.dark.textColor : theme.light.textColor};
            --border-color: ${isDarkMode ? theme.dark.borderColor : theme.light.borderColor};
            --button-bg: ${isDarkMode ? theme.dark.buttonBg : theme.light.buttonBg};
            --button-hover-bg: ${isDarkMode ? theme.dark.buttonHoverBg : theme.light.buttonHoverBg};
            --modal-bg: ${isDarkMode ? theme.dark.modalBg : theme.light.modalBg};
        }
        
        /* 浮动按钮样式 */
        #claude-toggle-button {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background-color: var(--bg-color);
            color: #b3462f;
            cursor: move;
            position: fixed;
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            transition: background-color 0.3s ease, transform 0.2s ease;
            outline: none;
            padding: 0;
            user-select: none;
            touch-action: none;
            border: 1px solid var(--border-color);
            font-size: 18px;
        }
        
        #claude-toggle-button:hover {
            transform: scale(1.1);
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
        }
        
        /* 下拉容器样式 */
        .claude-dropdown-container {
            position: fixed;
            background-color: var(--bg-color);
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
            display: none;
            flex-direction: column;
            gap: 0; /* 移除flex布局产生的空隙 */
            width: 600px;
            max-height: 80vh;
            overflow-y: auto;
            z-index: 9999;
            border: 1px solid var(--border-color);
            opacity: 0;
            transform: scale(0.95);
            transition: opacity 0.3s ease, transform 0.3s ease;
            scrollbar-gutter: stable; /* 保持滚动条空间稳定 */
            scrollbar-width: thin; /* Firefox滚动条样式 */
            scrollbar-color: ${isDarkMode ? "rgba(255, 255, 255, 0.2) transparent" : "rgba(0, 0, 0, 0.2) transparent"};
        }
        
        /* 标题容器 */
        .claude-title-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-bottom: 10px;
        }
        
        .claude-title-container h2 {
            margin: 0;
            color: var(--text-color);
            font-size: 18px;
            font-weight: 600;
        }
        
        .claude-ip-display {
            font-size: 14px;
            color: var(--text-color);
            padding: 4px 10px;
            background-color: var(--button-bg);
            border-radius: 12px;
        }
        
        /* Token 网格容器 */
        .claude-token-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 12px;
            max-height: calc(2 * (90px + 12px) + 24px); /* 两行token的高度加上间隙和padding */
            overflow-y: auto;
            padding: 12px 0 12px 12px;
            scrollbar-gutter: stable; /* 保持滚动条空间稳定,防止出现时推动内容 */
            border: 1px solid var(--border-color);
            border-radius: 8px;
            background-color: ${isDarkMode ? "rgba(255, 255, 255, 0.03)" : "rgba(0, 0, 0, 0.02)"};
            /* Firefox滚动条样式支持 */
            scrollbar-width: thin;
            scrollbar-color: ${isDarkMode ? "rgba(255, 255, 255, 0.2) transparent" : "rgba(0, 0, 0, 0.2) transparent"};
        }
        
        /* Token 卡片样式 */
        .claude-token-item {
            padding: 15px;
            border-radius: 8px;
            background-color: var(--bg-color);
            border: 1px solid var(--border-color);
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            height: 90px; /* 固定高度 */
            box-sizing: border-box; /* 确保padding不会增加总高度 */
            display: flex;
            flex-direction: column;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
        }
        
        .claude-token-item:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            border-color: ${isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.2)"};
        }
        
        .claude-token-item.current-token {
            border: 2px solid #b3462f;
            background-color: ${isDarkMode ? "rgba(179, 70, 47, 0.1)" : "rgba(179, 70, 47, 0.05)"};
            position: relative;
        }
        
        .current-token-badge {
            position: absolute;
            top: -8px;
            left: 8px;
            background-color: #b3462f;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .current-token-badge::after {
            content: "";
            display: block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background-color: white;
        }
        
        /* Token 内容样式 */
        .token-info {
            display: flex;
            flex-direction: column;
            gap: 8px;
            flex: 1; /* 填充可用空间 */
            justify-content: space-between; /* 顶部行和底部行分别位于容器顶部和底部 */
        }
        
        .token-top-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
        
        .token-name-container {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .token-number {
            padding: 2px 8px;
            border-radius: 12px;
            font-size: 12px;
            background-color: var(--button-bg);
        }
        
        .token-name {
            font-weight: 500;
            font-size: 14px;
        }
        
        .token-actions {
            display: flex;
            gap: 8px;
        }
        
        .token-action-btn {
            background: transparent;
            border: none;
            cursor: pointer;
            padding: 4px;
            border-radius: 4px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: var(--text-color);
            transition: all 0.2s ease;
        }
        
        .token-action-btn:hover {
            background-color: var(--button-hover-bg);
            transform: scale(1.1);
        }
        
        .token-action-btn.delete-btn {
            color: #e24a4a;
        }
        
        .token-bottom-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
        
        .token-status {
            display: flex;
            align-items: center;
            gap: 6px;
            margin-left: auto;
        }
        
        .status-indicator {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background-color: #888;
        }
        
        .status-indicator.success {
            background-color: #48bb78;
        }
        
        .status-indicator.error {
            background-color: #e53e3e;
        }
        
        .status-indicator.loading {
            background-color: #888;
            animation: pulse 1.5s infinite;
        }
        
        @keyframes pulse {
            0% { opacity: 0.4; }
            50% { opacity: 1; }
            100% { opacity: 0.4; }
        }
        
        .token-time {
            font-size: 12px;
            color: var(--text-color);
            opacity: 0.7;
        }
        
        /* 按钮容器 */
        .claude-button-container {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 8px;
            padding-top: 12px;
        }
        
        .claude-button {
            padding: 8px 10px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s ease;
            font-size: 13px;
            font-weight: 500;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 4px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            position: relative;
        }
        
        .claude-button:hover {
            transform: translateY(-2px);
        }
        
        .claude-button.primary {
            background-color: #b3462f;
            color: white;
        }
        
        .claude-button.primary:hover {
            background-color: #a03d2a;
        }
        
        .claude-button.secondary {
            background-color: var(--button-bg);
            color: var(--text-color);
        }
        
        .claude-button.secondary:hover {
            background-color: var(--button-hover-bg);
        }
        
        /* 工具提示样式 */
        .claude-button[data-tooltip]:hover::after {
            content: attr(data-tooltip);
            position: absolute;
            bottom: 100%;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 12px;
            white-space: nowrap;
            z-index: 10001;
            margin-bottom: 5px;
            pointer-events: none;
            opacity: 0;
            animation: tooltip-fade-in 0.2s ease forwards;
        }
        
        @keyframes tooltip-fade-in {
            from { opacity: 0; transform: translate(-50%, 5px); }
            to { opacity: 1; transform: translate(-50%, 0); }
        }
        
        /* 模态框样式 */
        .claude-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: var(--modal-bg);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10001;
        }
        
        .claude-modal-content {
            background-color: var(--bg-color);
            padding: 20px;
            padding-right: 14px; /* 右侧padding稍微增加,为滚动条预留空间但不过多 */
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            width: 500px;
            max-width: 90%;
            overflow-y: auto;
            position: relative;
            scrollbar-gutter: stable; /* 保持滚动条空间稳定 */
            scrollbar-width: thin; /* Firefox滚动条样式 */
            scrollbar-color: ${isDarkMode ? "rgba(255, 255, 255, 0.2) transparent" : "rgba(0, 0, 0, 0.2) transparent"};
        }
        
        .claude-modal-content.narrow-modal {
            width: 400px;
            max-width: 80%;
        }
        
        .claude-modal h2 {
            margin-top: 0;
            margin-bottom: 15px;
            color: var(--text-color);
            font-size: 18px;
            font-weight: 600;
        }
        
        .claude-modal input, .claude-modal textarea {
            width: 100%;
            padding: 10px;
            margin-bottom: 15px;
            border: 1px solid var(--border-color);
            border-radius: 4px;
            font-size: 14px;
            background-color: var(--bg-color);
            color: var(--text-color);
        }
        
        .claude-modal-buttons {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 15px;
        }
        
        .claude-close-button {
            position: absolute;
            top: 10px;
            right: 10px;
            background: none;
            border: none;
            font-size: 20px;
            cursor: pointer;
            color: var(--text-color);
            padding: 5px;
            line-height: 1;
        }
        
        /* 自定义滚动条样式 */
        .claude-token-grid::-webkit-scrollbar {
            width: 6px;
            /* 初始状态下滚动条透明 */
            background-color: transparent;
        }
        
        .claude-token-grid::-webkit-scrollbar-track {
            background: transparent;
            margin: 4px 0;
        }
        
        .claude-token-grid::-webkit-scrollbar-thumb {
            background-color: ${isDarkMode ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.15)"};
            border-radius: 6px;
            transition: background-color 0.3s ease;
            /* 初始状态下滚动条半透明 */
            opacity: 0.6;
        }
        
        .claude-token-grid::-webkit-scrollbar-thumb:hover {
            background-color: ${isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.3)"};
            opacity: 1;
        }
        
        .claude-token-grid:hover::-webkit-scrollbar-thumb {
            background-color: ${isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.3)"};
            opacity: 1;
        }
        
        /* 滚动条样式 */
        .claude-dropdown-container::-webkit-scrollbar,
        .claude-modal-content::-webkit-scrollbar {
            width: 6px;
            background-color: transparent;
        }
        
        .claude-dropdown-container::-webkit-scrollbar-track,
        .claude-modal-content::-webkit-scrollbar-track {
            background: transparent;
            margin: 4px 0;
        }
        
        .claude-dropdown-container::-webkit-scrollbar-thumb,
        .claude-modal-content::-webkit-scrollbar-thumb {
            background-color: ${isDarkMode ? "rgba(255, 255, 255, 0.15)" : "rgba(0, 0, 0, 0.15)"};
            border-radius: 6px;
            transition: background-color 0.3s ease;
            opacity: 0.6;
        }
        
        .claude-dropdown-container::-webkit-scrollbar-thumb:hover,
        .claude-modal-content::-webkit-scrollbar-thumb:hover {
            background-color: ${isDarkMode ? "rgba(255, 255, 255, 0.3)" : "rgba(0, 0, 0, 0.3)"};
            opacity: 1;
        }
        
        .claude-dropdown-container:hover::-webkit-scrollbar-thumb,
        .claude-modal-content:hover::-webkit-scrollbar-thumb {
            opacity: 1;
        }
        
        /* 预览容器 */
        .claude-preview-container {
            margin-top: 15px;
            max-height: 200px;
            overflow-y: auto;
            border: 1px solid var(--border-color);
            border-radius: 8px;
            padding: 15px;
        }
        
        .claude-preview-title {
            font-size: 16px;
            margin-bottom: 10px;
            color: var(--text-color);
            border-bottom: 1px solid var(--border-color);
            padding-bottom: 5px;
        }
        
        .claude-preview-item {
            margin-bottom: 8px;
            font-size: 14px;
            padding: 8px;
            border-radius: 4px;
            background-color: var(--button-bg);
        }

        /* 滚动提示样式 */
        .scroll-indicator {
            grid-column: 1 / -1; /* 横跨所有列 */
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 8px;
            margin-top: 5px;
            color: ${isDarkMode ? "rgba(255, 255, 255, 0.6)" : "rgba(0, 0, 0, 0.5)"};
            font-size: 12px;
            background-color: ${isDarkMode ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.03)"};
            border-radius: 6px;
            gap: 8px;
        }

        .scroll-arrow {
            animation: bounce 1.5s infinite;
        }

        @keyframes bounce {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(3px); }
        }
    `;

    const UI = {
        createElem(tag, className = "", styles = {}) {
            const elem = document.createElement(tag);
            if (className) elem.className = className;
            Object.assign(elem.style, styles);
            return elem;
        },

        createButton(text, className, icon = "") {
            const button = this.createElem("button", className);
            if (icon) {
                button.innerHTML = `${icon} ${text}`;
            } else {
                button.textContent = text;
            }
            return button;
        },

        createModal(title, content, includeCloseButton = true) {
            const modal = this.createElem("div", "claude-modal");
            modal.setAttribute("aria-modal", "true");
            modal.setAttribute("role", "dialog");

            const modalContent = this.createElem("div", "claude-modal-content");

            const titleElem = this.createElem("h2");
            titleElem.textContent = title;
            modalContent.appendChild(titleElem);

            if (includeCloseButton) {
                const closeButton = this.createElem("button", "claude-close-button");
                closeButton.textContent = "×";
                closeButton.addEventListener("click", () => document.body.removeChild(modal));
                modalContent.appendChild(closeButton);
            }

            modalContent.appendChild(content);

            const buttonContainer = this.createElem("div", "claude-modal-buttons");
            modalContent.appendChild(buttonContainer);

            modal.appendChild(modalContent);
            document.body.appendChild(modal);

            return {
                modal,
                buttonContainer,
                close: () => document.body.removeChild(modal),
            };
        },
    };

    const App = {
        init() {
            this.isDarkMode = document.documentElement.getAttribute("data-mode") === "dark";
            this.injectStyles();
            this.tokens = this.loadTokens();
            this.createUI();
            this.setupEventListeners();
            this.observeThemeChanges();

            // 获取保存的位置或使用默认值
            const savedPosition = {
                left: GM_getValue("buttonLeft", 10),
                bottom: GM_getValue("buttonBottom", 10)
            };

            // 设置按钮位置
            this.toggleButton.style.left = `${savedPosition.left}px`;
            this.toggleButton.style.bottom = `${savedPosition.bottom}px`;

            // 初始化拖拽状态
            this.isDragging = false;
            this.buttonLeft = savedPosition.left;
            this.buttonBottom = savedPosition.bottom;

            // 获取IP信息
            this.fetchIPCountryCode();
        },

        injectStyles() {
            this.styleElem = document.createElement("style");
            this.styleElem.textContent = getStyles(this.isDarkMode);
            document.head.appendChild(this.styleElem);
        },

        updateStyles() {
            this.styleElem.textContent = getStyles(this.isDarkMode);
        },

        loadTokens() {
            try {
                const savedTokens = GM_getValue(config.storageKey);
                let tokens = savedTokens && savedTokens.length > 0
                    ? savedTokens
                    : [config.defaultToken];

                // 为没有创建时间的token添加默认值
                tokens = tokens.map(token => {
                    if (!token.createdAt) {
                        const now = new Date();
                        return {
                            ...token,
                            createdAt: now.toLocaleString("zh-CN", {
                                month: "2-digit",
                                day: "2-digit",
                                hour: "2-digit",
                                minute: "2-digit",
                            }),
                            timestamp: now.getTime()
                        };
                    }
                    return token;
                });

                return tokens;
            } catch (error) {
                console.error("加载 tokens 失败:", error);
                return [config.defaultToken];
            }
        },

        saveTokens() {
            try {
                GM_setValue(config.storageKey, this.tokens);
            } catch (error) {
                console.error("保存 tokens 失败:", error);
                alert("保存 tokens 失败,请重试。");
            }
        },

        createUI() {
            // 创建浮动按钮
            this.toggleButton = UI.createElem("button", "claude-toggle-button", {
                left: "10px",
                bottom: "10px"
            });
            this.toggleButton.id = "claude-toggle-button";
            this.toggleButton.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" pointer-events="none">
                    <path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" pointer-events="none"></path>
                </svg>
            `;
            document.body.appendChild(this.toggleButton);

            // 创建下拉容器
            this.dropdownContainer = UI.createElem("div", "claude-dropdown-container");
            document.body.appendChild(this.dropdownContainer);

            // 创建标题容器
            const titleContainer = UI.createElem("div", "claude-title-container");

            const title = UI.createElem("h2");
            title.textContent = "Claude Session Key 管理器";

            this.ipDisplay = UI.createElem("div", "claude-ip-display");
            this.ipDisplay.textContent = "IP: 加载中...";

            titleContainer.appendChild(title);
            titleContainer.appendChild(this.ipDisplay);
            this.dropdownContainer.appendChild(titleContainer);

            // 创建 Token 网格
            this.tokenGrid = UI.createElem("div", "claude-token-grid");
            this.dropdownContainer.appendChild(this.tokenGrid);

            // 更新 Token 网格
            this.updateTokenGrid();

            // 创建按钮容器
            const buttonContainer = UI.createElem("div", "claude-button-container");

            // 测试所有按钮
            const testAllButton = UI.createButton("测活", "claude-button primary", "🔍");
            testAllButton.setAttribute("data-tooltip", "测试所有Token是否有效");
            testAllButton.addEventListener("click", () => this.testAllTokens());
            buttonContainer.appendChild(testAllButton);

            // 清理无效按钮
            const cleanInvalidButton = UI.createButton("清理", "claude-button secondary", "🗑️");
            cleanInvalidButton.setAttribute("data-tooltip", "清理所有无效的Token");
            cleanInvalidButton.addEventListener("click", () => this.removeInvalidTokens());
            buttonContainer.appendChild(cleanInvalidButton);

            // 添加 Token 按钮
            const addTokenButton = UI.createButton("添加", "claude-button secondary", "➕");
            addTokenButton.setAttribute("data-tooltip", "添加新的Token");
            addTokenButton.addEventListener("click", () => this.showAddTokenModal());
            buttonContainer.appendChild(addTokenButton);

            // 批量导入按钮
            const importButton = UI.createButton("导入", "claude-button secondary", "📥");
            importButton.setAttribute("data-tooltip", "批量导入多个Token");
            importButton.addEventListener("click", () => this.showBulkImportModal());
            buttonContainer.appendChild(importButton);

            // 批量导出按钮
            const exportButton = UI.createButton("导出", "claude-button secondary", "📤");
            exportButton.setAttribute("data-tooltip", "导出所有Token");
            exportButton.addEventListener("click", () => this.exportTokens());
            buttonContainer.appendChild(exportButton);

            // WebDAV备份按钮
            const webdavButton = UI.createButton("云备份", "claude-button secondary", "☁️");
            webdavButton.setAttribute("data-tooltip", "WebDAV云备份与恢复");
            webdavButton.addEventListener("click", () => this.showWebDAVModal());
            buttonContainer.appendChild(webdavButton);

            this.dropdownContainer.appendChild(buttonContainer);

            // 添加信息提示
            const infoSection = UI.createElem("div", "claude-info-section", {
                marginTop: "10px",
                padding: "8px",
                backgroundColor: "#f8f9fa",
                borderRadius: "6px",
                fontSize: "11px",
                color: "#666",
                textAlign: "center"
            });
            infoSection.innerHTML = '悬停显示面板 • 拖拽按钮调整位置 • 支持WebDAV云备份';
            this.dropdownContainer.appendChild(infoSection);
        },

        updateTokenGrid() {
            this.tokenGrid.innerHTML = "";

            // 获取当前使用的 token
            const currentTokenName = GM_getValue(config.currentTokenKey);

            // 加载测试结果
            const testResults = this.loadTestResults();

            this.tokens.forEach((token, index) => {
                const tokenItem = UI.createElem("div", "claude-token-item");
                if (token.name === currentTokenName) {
                    tokenItem.classList.add("current-token");

                    // 添加选中标记
                    const currentBadge = UI.createElem("div", "current-token-badge");
                    tokenItem.appendChild(currentBadge);
                }

                // Token 信息容器
                const tokenInfo = UI.createElem("div", "token-info");

                // 顶部行:名称和操作按钮
                const topRow = UI.createElem("div", "token-top-row");

                // 名称容器
                const nameContainer = UI.createElem("div", "token-name-container");

                const numberBadge = UI.createElem("span", "token-number");
                numberBadge.textContent = `#${(index + 1).toString().padStart(2, "0")}`;

                const nameSpan = UI.createElem("span", "token-name");
                nameSpan.textContent = token.name;

                nameContainer.appendChild(numberBadge);
                nameContainer.appendChild(nameSpan);

                // 操作按钮
                const actions = UI.createElem("div", "token-actions");

                // 编辑按钮
                const editButton = UI.createElem("button", "token-action-btn edit-btn");
                editButton.innerHTML = `
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
                    </svg>
                `;
                editButton.dataset.index = index;
                editButton.addEventListener("click", (e) => {
                    e.stopPropagation();
                    this.showEditTokenModal(index);
                });

                // 删除按钮
                const deleteButton = UI.createElem("button", "token-action-btn delete-btn");
                deleteButton.innerHTML = `
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <path d="M3 6h18"></path>
                        <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path>
                        <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                    </svg>
                `;
                deleteButton.dataset.index = index;
                deleteButton.addEventListener("click", (e) => {
                    e.stopPropagation();
                    this.confirmDeleteToken(index);
                });

                actions.appendChild(editButton);
                actions.appendChild(deleteButton);

                topRow.appendChild(nameContainer);
                topRow.appendChild(actions);

                // 底部行:状态和时间
                const bottomRow = UI.createElem("div", "token-bottom-row");

                // 添加时间戳(使用token的创建时间)
                const timeSpan = UI.createElem("span", "token-time");
                timeSpan.textContent = token.createdAt || "";
                bottomRow.appendChild(timeSpan);

                // 状态指示器
                const status = UI.createElem("div", "token-status");
                const statusIndicator = UI.createElem("div", "status-indicator");

                // 检查缓存的测试结果
                const testResult = testResults[token.key];
                if (testResult) {
                    statusIndicator.classList.add(testResult.status);
                }

                status.appendChild(statusIndicator);
                status.addEventListener("click", async (e) => {
                    e.stopPropagation();
                    await this.testSingleToken(token, statusIndicator, bottomRow);
                });

                bottomRow.appendChild(status);

                // 将行添加到信息容器
                tokenInfo.appendChild(topRow);
                tokenInfo.appendChild(bottomRow);

                // 将信息容器添加到 token 项
                tokenItem.appendChild(tokenInfo);

                // 点击切换 token
                tokenItem.addEventListener("click", () => this.switchToToken(token));

                // 将 token 项添加到网格
                this.tokenGrid.appendChild(tokenItem);
            });

            // 如果token数量超过4个(两行),添加滚动提示
            if (this.tokens.length > 4) {
                const scrollIndicator = UI.createElem("div", "scroll-indicator");
                scrollIndicator.innerHTML = `
                    <div class="scroll-text">向下滚动查看更多 (${this.tokens.length - 4})</div>
                    <div class="scroll-arrow">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M7 13l5 5 5-5"></path>
                            <path d="M7 6l5 5 5-5"></path>
                        </svg>
                    </div>
                `;
                this.tokenGrid.appendChild(scrollIndicator);
            }
        },

        async switchToToken(token) {
            // 检查是否有缓存的测试结果
            const cachedResult = this.getTestResult(token.key);

            // 如果有缓存的测试结果且为无效,提示用户并询问是否继续
            if (cachedResult && cachedResult.status === "error") {
                const confirmResult = await this.showConfirmDialog(
                    "警告",
                    `该 Token "${token.name}" 已被标记为无效,是否仍要切换到该 Token?`
                );

                if (!confirmResult) {
                    return;
                }
            }

            // 应用 token
            this.applyToken(token.key);
            GM_setValue(config.currentTokenKey, token.name);

            // 隐藏下拉菜单
            this.hideDropdown();
        },

        applyToken(token) {
            const currentURL = window.location.href;

            if (currentURL.startsWith("https://claude.ai/")) {
                document.cookie = `sessionKey=${token}; path=/; domain=.claude.ai`;
                window.location.reload();
            } else {
                let loginUrl;
                const hostname = new URL(currentURL).hostname;
                if (hostname !== "claude.ai") {
                    loginUrl = `https://${hostname}/login_token?session_key=${token}`;
                }

                if (loginUrl) {
                    window.location.href = loginUrl;
                }
            }
        },

        async testSingleToken(token, statusIndicator, bottomRow) {
            // 显示加载状态
            statusIndicator.className = "status-indicator loading";

            // 测试 token
            const result = await this.testToken(token.key);

            // 保存测试结果
            this.saveTestResult(token.key, result);

            // 更新状态指示器
            statusIndicator.className = `status-indicator ${result.status}`;

            // 不再更新时间戳,保持显示token的创建时间
        },

        async testAllTokens() {
            // 获取所有 token 项
            const tokenItems = this.tokenGrid.querySelectorAll(".claude-token-item");

            // 禁用测试按钮
            const testButton = this.dropdownContainer.querySelector(".claude-button.primary");
            testButton.disabled = true;
            testButton.textContent = "测试中...";

            // 清除所有缓存的测试结果
            GM_setValue(config.testResultsKey, {});

            const tokens = Array.from(tokenItems);

            // 按4个一组处理所有tokens
            for (let i = 0; i < tokens.length; i += 4) {
                // 取出当前4个(或更少)token
                const currentChunk = tokens.slice(i, Math.min(i + 4, tokens.length));

                // 并行处理这最多4个token
                await Promise.all(
                    currentChunk.map(async (tokenItem) => {
                        const index = Array.from(tokenItems).indexOf(tokenItem);
                        const token = this.tokens[index];
                        const statusIndicator = tokenItem.querySelector(".status-indicator");
                        const bottomRow = tokenItem.querySelector(".token-bottom-row");

                        await this.testSingleToken(token, statusIndicator, bottomRow);
                    })
                );
            }

            // 恢复测试按钮
            testButton.disabled = false;
            testButton.innerHTML = "🔍 测活";
        },

        async testToken(key) {
            return new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: "https://api.claude.ai/api/organizations",
                    headers: {
                        accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
                        "accept-language": "en-US,en;q=0.9",
                        "cache-control": "max-age=0",
                        cookie: `sessionKey=${key}`,
                        "user-agent": "Mozilla/5.0 (X11; Linux x86_64)",
                        "sec-fetch-mode": "navigate",
                        "sec-fetch-site": "none",
                    },
                    onload: (response) => {
                        try {
                            if (response.status !== 200) {
                                resolve({ status: "error", message: "无效" });
                                return;
                            }

                            const responseText = response.responseText;

                            if (responseText.toLowerCase().includes("unauthorized")) {
                                resolve({ status: "error", message: "无效" });
                                return;
                            }

                            if (responseText.trim() === "") {
                                resolve({ status: "error", message: "无响应" });
                                return;
                            }

                            try {
                                const objects = JSON.parse(responseText);
                                if (objects && objects.length > 0) {
                                    resolve({ status: "success", message: "有效" });
                                    return;
                                }
                            } catch (e) {
                                resolve({ status: "error", message: "解析失败" });
                                return;
                            }

                            resolve({ status: "error", message: "无效数据" });
                        } catch (error) {
                            console.error("解析响应时发生错误:", error);
                            resolve({ status: "error", message: "测试失败" });
                        }
                    },
                    onerror: (error) => {
                        console.error("请求发生错误:", error);
                        resolve({ status: "error", message: "网络错误" });
                    },
                    ontimeout: () => {
                        resolve({ status: "error", message: "超时" });
                    },
                });
            });
        },

        loadTestResults() {
            try {
                const cached = GM_getValue(config.testResultsKey, {});
                const now = Date.now();
                // 清理过期的测试结果
                const filtered = Object.entries(cached).reduce((acc, [key, value]) => {
                    if (now - value.timestamp < config.testResultExpiry) {
                        acc[key] = value;
                    }
                    return acc;
                }, {});
                return filtered;
            } catch (error) {
                console.error("加载测试结果缓存失败:", error);
                return {};
            }
        },

        saveTestResult(key, result) {
            try {
                const testResults = this.loadTestResults();
                const now = new Date();
                // 统一使用简短时间格式
                const formattedTime = now.toLocaleString("zh-CN", {
                    month: "2-digit",
                    day: "2-digit",
                    hour: "2-digit",
                    minute: "2-digit",
                });

                testResults[key] = {
                    status: result.status,
                    message: result.message,
                    timestamp: now.getTime(),
                    testTime: formattedTime, // 保存简短格式的时间
                };
                GM_setValue(config.testResultsKey, testResults);
            } catch (error) {
                console.error("保存测试结果失败:", error);
            }
        },

        getTestResult(key) {
            const testResults = this.loadTestResults();
            return testResults[key];
        },

        async removeInvalidTokens() {
            const confirmResult = await this.showConfirmDialog(
                "确认清理",
                "是否删除所有无效的 Tokens?此操作不可撤销。"
            );

            if (!confirmResult) return;

            const testResults = this.loadTestResults();
            const validTokens = this.tokens.filter((token) => {
                const result = testResults[token.key];
                return !result || result.status === "success";
            });

            if (validTokens.length === this.tokens.length) {
                alert("没有发现无效的 Tokens");
                return;
            }

            this.tokens = validTokens;
            this.saveTokens();
            this.updateTokenGrid();
        },

        showAddTokenModal() {
            const content = UI.createElem("div", "claude-add-token-form");

            const nameInput = UI.createElem("input");
            nameInput.placeholder = "Token 名称";
            nameInput.setAttribute("aria-label", "Token 名称");

            const keyInput = UI.createElem("input");
            keyInput.placeholder = "Token 密钥";
            keyInput.setAttribute("aria-label", "Token 密钥");

            content.appendChild(nameInput);
            content.appendChild(keyInput);

            const { modal, buttonContainer, close } = UI.createModal("添加 Token", content);
            modal.querySelector(".claude-modal-content").classList.add("narrow-modal");

            const addButton = UI.createButton("添加", "claude-button primary");
            addButton.addEventListener("click", () => {
                if (this.validateInput(nameInput.value, keyInput.value)) {
                    // 获取当前时间并格式化
                    const now = new Date();
                    const formattedTime = now.toLocaleString("zh-CN", {
                        month: "2-digit",
                        day: "2-digit",
                        hour: "2-digit",
                        minute: "2-digit",
                    });

                    this.tokens.push({
                        name: nameInput.value,
                        key: keyInput.value,
                        createdAt: formattedTime, // 添加创建时间
                        timestamp: now.getTime() // 添加时间戳用于排序
                    });

                    this.saveTokens();
                    this.updateTokenGrid();
                    close();
                }
            });

            const cancelButton = UI.createButton("取消", "claude-button secondary");
            cancelButton.addEventListener("click", close);

            buttonContainer.appendChild(cancelButton);
            buttonContainer.appendChild(addButton);
        },

        showEditTokenModal(index) {
            const token = this.tokens[index];
            const content = UI.createElem("div", "claude-edit-token-form");

            const nameInput = UI.createElem("input");
            nameInput.value = token.name;
            nameInput.placeholder = "Token 名称";

            const keyInput = UI.createElem("input");
            keyInput.value = token.key;
            keyInput.placeholder = "Token 密钥";

            content.appendChild(nameInput);
            content.appendChild(keyInput);

            const { modal, buttonContainer, close } = UI.createModal("编辑 Token", content);
            modal.querySelector(".claude-modal-content").classList.add("narrow-modal");

            const saveButton = UI.createButton("保存", "claude-button primary");
            saveButton.addEventListener("click", () => {
                if (this.validateInput(nameInput.value, keyInput.value)) {
                    // 保留原有的创建时间和时间戳
                    this.tokens[index] = {
                        name: nameInput.value,
                        key: keyInput.value,
                        createdAt: token.createdAt || "", // 保留原有的创建时间
                        timestamp: token.timestamp || Date.now() // 保留原有的时间戳
                    };

                    this.saveTokens();
                    this.updateTokenGrid();
                    close();
                }
            });

            const cancelButton = UI.createButton("取消", "claude-button secondary");
            cancelButton.addEventListener("click", close);

            buttonContainer.appendChild(cancelButton);
            buttonContainer.appendChild(saveButton);
        },

        confirmDeleteToken(index) {
            const token = this.tokens[index];
            const content = UI.createElem("div", "claude-delete-confirm");

            content.innerHTML = `
                <div style="font-size: 48px; text-align: center; margin-bottom: 16px;">⚠️</div>
                <div style="font-size: 18px; font-weight: 600; text-align: center; margin-bottom: 12px;">删除确认</div>
                <div style="text-align: center; margin-bottom: 24px;">
                    您确定要删除 Token "${token.name}" 吗?<br>
                    此操作无法撤销。
                </div>
            `;

            const { modal, buttonContainer, close } = UI.createModal("", content);
            modal.querySelector(".claude-modal-content").classList.add("narrow-modal");

            const deleteButton = UI.createButton("删除", "claude-button primary");
            deleteButton.style.backgroundColor = "#e53e3e";
            deleteButton.addEventListener("click", () => {
                this.deleteToken(index);
                close();
            });

            const cancelButton = UI.createButton("取消", "claude-button secondary");
            cancelButton.addEventListener("click", close);

            buttonContainer.appendChild(cancelButton);
            buttonContainer.appendChild(deleteButton);
        },

        deleteToken(index) {
            this.tokens.splice(index, 1);
            this.saveTokens();
            this.updateTokenGrid();
        },

        showBulkImportModal() {
            const content = UI.createElem("div", "claude-bulk-import-form");

            // 文本区域标签
            const textareaLabel = UI.createElem("label");
            textareaLabel.innerHTML = "<strong>1️⃣ Tokens 粘贴区:</strong><br>在这里粘贴您需要导入的 Tokens,每行一个!";
            content.appendChild(textareaLabel);

            // 文本区域
            const textarea = UI.createElem("textarea");
            textarea.rows = 10;
            content.appendChild(textarea);

            // 命名规则容器
            const namingRuleContainer = UI.createElem("div", "claude-naming-rule");

            // 命名规则标签
            const namingRuleLabel = UI.createElem("label");
            namingRuleLabel.innerHTML = "<strong>2️⃣ Tokens 命名规则:</strong>";
            namingRuleContainer.appendChild(namingRuleLabel);

            // 名称前缀
            const prefixLabel = UI.createElem("label");
            prefixLabel.textContent = "名称前缀:";
            namingRuleContainer.appendChild(prefixLabel);

            const prefixInput = UI.createElem("input");
            prefixInput.value = "token";
            namingRuleContainer.appendChild(prefixInput);

            // 起始编号
            const startNumberLabel = UI.createElem("label");
            startNumberLabel.textContent = "名称起始编号:";
            namingRuleContainer.appendChild(startNumberLabel);

            const startNumberInput = UI.createElem("input");
            startNumberInput.type = "number";
            startNumberInput.value = "1";
            namingRuleContainer.appendChild(startNumberInput);

            content.appendChild(namingRuleContainer);

            // 预览容器
            const previewLabel = UI.createElem("label");
            previewLabel.innerHTML = "<strong>3️⃣ Tokens 导入结果预览:</strong>";
            content.appendChild(previewLabel);

            const previewContainer = UI.createElem("div", "claude-preview-container");
            content.appendChild(previewContainer);

            const { modal, buttonContainer, close } = UI.createModal("批量导入 Tokens", content);

            const importButton = UI.createButton("导入", "claude-button primary");
            importButton.addEventListener("click", () => {
                this.performBulkImport(
                    textarea.value,
                    prefixInput.value,
                    startNumberInput.value
                );
                close();
            });

            const cancelButton = UI.createButton("取消", "claude-button secondary");
            cancelButton.addEventListener("click", close);

            buttonContainer.appendChild(cancelButton);
            buttonContainer.appendChild(importButton);

            // 更新预览
            const updatePreview = () => {
                this.previewBulkImport(
                    textarea.value,
                    prefixInput.value,
                    startNumberInput.value,
                    previewContainer
                );
            };

            [textarea, prefixInput, startNumberInput].forEach((elem) => {
                elem.addEventListener("input", updatePreview);
            });

            // 初始化预览
            updatePreview();
        },

        previewBulkImport(input, namePrefix, startNumber, previewContainer) {
            previewContainer.innerHTML = "";

            const tokens = this.parseTokens(input);
            const namedTokens = this.applyNamingRule(
                tokens,
                namePrefix,
                parseInt(startNumber)
            );

            const previewTitle = UI.createElem("div", "claude-preview-title");
            previewTitle.textContent = "请核对下方导入结果:";
            previewContainer.appendChild(previewTitle);

            if (namedTokens.length === 0) {
                const emptyMessage = UI.createElem("div", "claude-preview-item");
                emptyMessage.textContent = "等待输入...";
                previewContainer.appendChild(emptyMessage);
                return;
            }

            namedTokens.forEach((token) => {
                const previewItem = UI.createElem("div", "claude-preview-item");
                previewItem.innerHTML = `
                    <strong>${token.name}:</strong>
                    <span style="font-family: monospace; word-break: break-all;">${token.key}</span>
                `;
                previewContainer.appendChild(previewItem);
            });
        },

        performBulkImport(input, namePrefix, startNumber) {
            const tokens = this.parseTokens(input);
            const namedTokens = this.applyNamingRule(
                tokens,
                namePrefix,
                parseInt(startNumber)
            );

            if (namedTokens.length === 0) {
                alert("没有有效的 Tokens 可导入");
                return;
            }

            this.tokens = [...this.tokens, ...namedTokens];
            this.saveTokens();
            this.updateTokenGrid();
        },

        parseTokens(input) {
            return input
                .split("\n")
                .map((line) => line.trim())
                .filter((line) => this.validateTokenKey(line))
                .map((key) => ({ key }));
        },

        applyNamingRule(tokens, namePrefix, startNumber) {
            return tokens.map((token, index) => {
                const number = startNumber + index;
                const name = `${namePrefix}${number.toString().padStart(2, "0")}`;
                return { ...token, name };
            });
        },

        exportTokens() {
            const testResults = this.loadTestResults();
            const exportData = this.tokens.map((token) => {
                const testResult = testResults[token.key] || {};
                return {
                    name: token.name,
                    sessionKey: token.key,
                    isValid: testResult.status === "success" ? true : testResult.status === "error" ? false : null,
                    testTime: testResult.testTime || null,
                    testMessage: testResult.message || null,
                };
            });

            // 创建并下载 JSON 文件
            const blob = new Blob([JSON.stringify(exportData, null, 2)], {
                type: "application/json",
            });
            const url = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = url;
            a.download = `claude_tokens_${new Date().toISOString().split("T")[0]}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
        },

        showWebDAVModal() {
            const content = UI.createElem("div", "claude-webdav-form");
            content.style.cssText = "width: 100%; max-width: 600px;";

            // 添加帮助信息
            const helpInfo = UI.createElem("div", "claude-webdav-help");
            helpInfo.style.cssText = `
                margin-bottom: 10px;
                padding: 12px;
                background-color: var(--bg-color);
                border: 1px solid var(--border-color);
                border-radius: 8px;
                font-size: 13px;
                color: var(--text-color);
                box-shadow: 0 2px 4px rgba(0,0,0,0.05);
            `;
            helpInfo.innerHTML = `
                <p style="margin: 0 0 10px 0; font-weight: 600; color: var(--text-color);">📝 WebDAV服务器设置说明:</p>
                <ul style="margin: 0; padding-left: 20px; line-height: 1.6;">
                    <li>URL必须是完整的WebDAV路径,例如:https://dav.jianguoyun.com/dav/Claude/</li>
                    <li>确保路径末尾有斜杠"/"</li>
                    <li>如果遇到404错误,请确认路径是否存在</li>
                    <li>坚果云用户请使用应用专用密码</li>
                </ul>
            `;
            content.appendChild(helpInfo);

            // 创建表单容器
            const formContainer = UI.createElem("div", "claude-webdav-form-container");
            formContainer.style.cssText = `
                display: grid;
                gap: 8px;
                margin-bottom: 15px;
            `;

            // WebDAV服务器URL输入
            const urlGroup = UI.createElem("div", "input-group");
            urlGroup.style.cssText = "display: flex; align-items: center; gap: 10px;";

            const urlLabel = UI.createElem("label");
            urlLabel.textContent = "WebDAV URL:";
            urlLabel.style.cssText = "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

            const urlInput = UI.createElem("input");
            urlInput.type = "text";
            urlInput.placeholder = "https://dav.jianguoyun.com/dav/Claude/";
            urlInput.value = GM_getValue("webdav_url", "");
            urlInput.style.cssText = `
                flex: 1;
                padding: 10px;
                border: 1px solid var(--border-color);
                border-radius: 6px;
                font-size: 14px;
                background-color: var(--bg-color);
                color: var(--text-color);
                transition: all 0.3s ease;
            `;

            urlGroup.appendChild(urlLabel);
            urlGroup.appendChild(urlInput);
            formContainer.appendChild(urlGroup);

            // 用户名输入
            const usernameGroup = UI.createElem("div", "input-group");
            usernameGroup.style.cssText = "display: flex; align-items: center; gap: 10px;";

            const usernameLabel = UI.createElem("label");
            usernameLabel.textContent = "用户名:";
            usernameLabel.style.cssText = "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

            const usernameInput = UI.createElem("input");
            usernameInput.type = "text";
            usernameInput.placeholder = "用户名";
            usernameInput.value = GM_getValue("webdav_username", "");
            usernameInput.style.cssText = `
                flex: 1;
                padding: 10px;
                border: 1px solid var(--border-color);
                border-radius: 6px;
                font-size: 14px;
                background-color: var(--bg-color);
                color: var(--text-color);
                transition: all 0.3s ease;
            `;

            usernameGroup.appendChild(usernameLabel);
            usernameGroup.appendChild(usernameInput);
            formContainer.appendChild(usernameGroup);

            // 密码输入
            const passwordGroup = UI.createElem("div", "input-group");
            passwordGroup.style.cssText = "display: flex; align-items: center; gap: 10px;";

            const passwordLabel = UI.createElem("label");
            passwordLabel.textContent = "密码:";
            passwordLabel.style.cssText = "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

            const passwordInput = UI.createElem("input");
            passwordInput.type = "password";
            passwordInput.placeholder = "密码";
            passwordInput.value = GM_getValue("webdav_password", "");
            passwordInput.style.cssText = `
                flex: 1;
                padding: 10px;
                border: 1px solid var(--border-color);
                border-radius: 6px;
                font-size: 14px;
                background-color: var(--bg-color);
                color: var(--text-color);
                transition: all 0.3s ease;
            `;

            passwordGroup.appendChild(passwordLabel);
            passwordGroup.appendChild(passwordInput);
            formContainer.appendChild(passwordGroup);

            // 文件名输入
            const filenameGroup = UI.createElem("div", "input-group");
            filenameGroup.style.cssText = "display: flex; align-items: center; gap: 10px;";

            const filenameLabel = UI.createElem("label");
            filenameLabel.textContent = "文件名:";
            filenameLabel.style.cssText = "font-weight: 500; color: var(--text-color); min-width: 120px; text-align: right;";

            const filenameInput = UI.createElem("input");
            filenameInput.type = "text";
            filenameInput.placeholder = "claude_tokens.json";
            filenameInput.value = GM_getValue("webdav_filename", "claude_tokens.json");
            filenameInput.style.cssText = `
                flex: 1;
                padding: 10px;
                border: 1px solid var(--border-color);
                border-radius: 6px;
                font-size: 14px;
                background-color: var(--bg-color);
                color: var(--text-color);
                transition: all 0.3s ease;
            `;

            filenameGroup.appendChild(filenameLabel);
            filenameGroup.appendChild(filenameInput);
            formContainer.appendChild(filenameGroup);

            content.appendChild(formContainer);

            // 测试连接按钮
            const testConnectionButton = UI.createButton("测试连接", "claude-button secondary");
            testConnectionButton.style.cssText = `
                width: 100%;
                margin: 10px 0;
                padding: 10px;
                font-size: 14px;
                font-weight: 500;
                border-radius: 6px;
                background-color: var(--button-bg);
                color: var(--text-color);
                border: 1px solid var(--border-color);
                cursor: pointer;
                transition: all 0.3s ease;
            `;
            testConnectionButton.addEventListener("click", async () => {
                this.saveWebDAVSettings(urlInput.value, usernameInput.value, passwordInput.value, filenameInput.value);

                statusDisplay.style.display = "block";
                statusDisplay.textContent = "正在测试连接...";
                statusDisplay.style.backgroundColor = "var(--bg-color)";

                try {
                    await this.checkWebDAVDirectory(urlInput.value, usernameInput.value, passwordInput.value);
                    statusDisplay.textContent = "✅ 连接成功!目录存在且可访问。";
                    statusDisplay.style.backgroundColor = "#d4edda";
                } catch (error) {
                    statusDisplay.textContent = `❌ 连接失败: ${error.message}`;
                    statusDisplay.style.backgroundColor = "#f8d7da";
                }
            });
            content.appendChild(testConnectionButton);

            // 状态显示
            const statusDisplay = UI.createElem("div", "claude-webdav-status");
            statusDisplay.style.cssText = `
                margin: 10px 0;
                padding: 10px;
                border-radius: 6px;
                font-size: 14px;
                text-align: center;
                display: none;
                transition: all 0.3s ease;
            `;
            content.appendChild(statusDisplay);

            // 操作按钮容器
            const actionsContainer = UI.createElem("div", "claude-webdav-actions");
            actionsContainer.style.cssText = `
                display: grid;
                grid-template-columns: repeat(3, 1fr);
                gap: 10px;
                margin-top: 15px;
            `;

            // 备份按钮
            const backupButton = UI.createButton("备份到WebDAV", "claude-button primary");
            backupButton.style.cssText = `
                padding: 12px;
                font-size: 14px;
                font-weight: 500;
                border-radius: 6px;
                background-color: #b3462f;
                color: white;
                border: none;
                cursor: pointer;
                transition: all 0.3s ease;
            `;
            backupButton.addEventListener("click", async () => {
                this.saveWebDAVSettings(urlInput.value, usernameInput.value, passwordInput.value, filenameInput.value);

                statusDisplay.style.display = "block";
                statusDisplay.textContent = "正在备份...";
                statusDisplay.style.backgroundColor = "var(--bg-color)";

                try {
                    await this.backupToWebDAV(urlInput.value, usernameInput.value, passwordInput.value, filenameInput.value);
                    statusDisplay.textContent = "✅ 备份成功!";
                    statusDisplay.style.backgroundColor = "#d4edda";
                } catch (error) {
                    statusDisplay.textContent = `❌ 备份失败: ${error.message}`;
                    statusDisplay.style.backgroundColor = "#f8d7da";
                }
            });

            // 恢复按钮
            const restoreButton = UI.createButton("从WebDAV恢复", "claude-button secondary");
            restoreButton.style.cssText = `
                padding: 12px;
                font-size: 14px;
                font-weight: 500;
                border-radius: 6px;
                background-color: var(--button-bg);
                color: var(--text-color);
                border: 1px solid var(--border-color);
                cursor: pointer;
                transition: all 0.3s ease;
            `;
            restoreButton.addEventListener("click", async () => {
                this.saveWebDAVSettings(urlInput.value, usernameInput.value, passwordInput.value, filenameInput.value);

                statusDisplay.style.display = "block";
                statusDisplay.textContent = "正在恢复...";
                statusDisplay.style.backgroundColor = "var(--bg-color)";

                try {
                    await this.restoreFromWebDAV(urlInput.value, usernameInput.value, passwordInput.value, filenameInput.value);
                    statusDisplay.textContent = "✅ 恢复成功!";
                    statusDisplay.style.backgroundColor = "#d4edda";
                    this.updateTokenGrid();
                } catch (error) {
                    statusDisplay.textContent = `❌ 恢复失败: ${error.message}`;
                    statusDisplay.style.backgroundColor = "#f8d7da";
                }
            });

            // 关闭按钮
            const closeButton = UI.createButton("关闭", "claude-button secondary");
            closeButton.style.cssText = `
                padding: 12px;
                font-size: 14px;
                font-weight: 500;
                border-radius: 6px;
                background-color: var(--button-bg);
                color: var(--text-color);
                border: 1px solid var(--border-color);
                cursor: pointer;
                transition: all 0.3s ease;
            `;
            closeButton.addEventListener("click", () => {
                modal.remove();
            });

            actionsContainer.appendChild(backupButton);
            actionsContainer.appendChild(restoreButton);
            actionsContainer.appendChild(closeButton);
            content.appendChild(actionsContainer);

            // 创建模态框
            const { modal, buttonContainer } = UI.createModal("☁️ WebDAV备份与恢复", content, true);
            document.body.appendChild(modal);

            // 添加关闭按钮事件监听
            const closeBtn = modal.querySelector(".claude-close-button");
            if (closeBtn) {
                closeBtn.addEventListener("click", () => {
                    document.body.removeChild(modal);
                });
            }
        },

        saveWebDAVSettings(url, username, password, filename) {
            GM_setValue("webdav_url", url);
            GM_setValue("webdav_username", username);
            GM_setValue("webdav_password", password);
            GM_setValue("webdav_filename", filename);
        },

        async backupToWebDAV(url, username, password, filename) {
            // 准备备份数据
            const testResults = this.loadTestResults();
            const exportData = this.tokens.map((token) => {
                const testResult = testResults[token.key] || {};
                return {
                    name: token.name,
                    sessionKey: token.key,
                    isValid: testResult.status === "success" ? true : testResult.status === "error" ? false : null,
                    testTime: testResult.testTime || null,
                    testMessage: testResult.message || null,
                };
            });

            const jsonData = JSON.stringify(exportData, null, 2);

            // 确保URL以/结尾
            if (!url.endsWith('/')) {
                url += '/';
            }

            // 先检查目录是否存在
            try {
                await this.checkWebDAVDirectory(url, username, password);
            } catch (error) {
                // 如果是404错误,尝试创建目录
                if (error.message.includes("404")) {
                    try {
                        // 尝试创建父目录
                        const parentUrl = url.substring(0, url.lastIndexOf('/', url.length - 2) + 1);
                        if (parentUrl !== url) {
                            await this.createWebDAVDirectory(parentUrl, username, password, url.substring(parentUrl.length, url.length - 1));
                        } else {
                            throw new Error("无法确定父目录");
                        }
                    } catch (createError) {
                        throw new Error(`目录不存在且无法创建: ${createError.message}`);
                    }
                } else {
                    throw error;
                }
            }

            // 发送到WebDAV服务器
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "PUT",
                    url: url + filename,
                    headers: {
                        "Content-Type": "application/json",
                        "Authorization": "Basic " + btoa(username + ":" + password)
                    },
                    data: jsonData,
                    onload: function (response) {
                        if (response.status >= 200 && response.status < 300) {
                            resolve();
                        } else {
                            console.error("WebDAV备份失败:", response);
                            reject(new Error(`HTTP错误: ${response.status} ${response.statusText || ''}\n响应: ${response.responseText || '无响应内容'}`));
                        }
                    },
                    onerror: function (error) {
                        console.error("WebDAV备份网络错误:", error);
                        reject(new Error(`网络错误: ${error.statusText || '连接失败'}`));
                    }
                });
            });
        },

        // 检查WebDAV目录是否存在
        checkWebDAVDirectory(url, username, password) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "PROPFIND",
                    url: url,
                    headers: {
                        "Depth": "0",
                        "Authorization": "Basic " + btoa(username + ":" + password)
                    },
                    onload: function (response) {
                        if (response.status >= 200 && response.status < 300) {
                            resolve();
                        } else {
                            reject(new Error(`HTTP错误: ${response.status} ${response.statusText || ''}`));
                        }
                    },
                    onerror: function (error) {
                        reject(new Error(`网络错误: ${error.statusText || '连接失败'}`));
                    }
                });
            });
        },

        // 创建WebDAV目录
        createWebDAVDirectory(parentUrl, username, password, dirName) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "MKCOL",
                    url: parentUrl + dirName,
                    headers: {
                        "Authorization": "Basic " + btoa(username + ":" + password)
                    },
                    onload: function (response) {
                        if (response.status >= 200 && response.status < 300) {
                            resolve();
                        } else {
                            reject(new Error(`无法创建目录: HTTP错误 ${response.status} ${response.statusText || ''}`));
                        }
                    },
                    onerror: function (error) {
                        reject(new Error(`网络错误: ${error.statusText || '连接失败'}`));
                    }
                });
            });
        },

        async restoreFromWebDAV(url, username, password, filename) {
            // 确保URL以/结尾
            if (!url.endsWith('/')) {
                url += '/';
            }

            // 从WebDAV服务器获取数据
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url + filename,
                    headers: {
                        "Authorization": "Basic " + btoa(username + ":" + password)
                    },
                    onload: (response) => {
                        if (response.status >= 200 && response.status < 300) {
                            try {
                                const data = JSON.parse(response.responseText);

                                // 转换数据格式
                                const tokens = data.map(item => ({
                                    name: item.name,
                                    key: item.sessionKey,
                                    createdAt: new Date().toLocaleString("zh-CN", {
                                        month: "2-digit",
                                        day: "2-digit",
                                        hour: "2-digit",
                                        minute: "2-digit",
                                    }),
                                    timestamp: Date.now()
                                }));

                                // 更新tokens
                                this.tokens = tokens;
                                this.saveTokens();

                                resolve();
                            } catch (error) {
                                console.error("解析WebDAV数据失败:", error, response.responseText);
                                reject(new Error(`解析数据失败: ${error.message}`));
                            }
                        } else {
                            console.error("WebDAV恢复失败:", response);
                            reject(new Error(`HTTP错误: ${response.status} ${response.statusText || ''}\n响应: ${response.responseText || '无响应内容'}`));
                        }
                    },
                    onerror: function (error) {
                        console.error("WebDAV恢复网络错误:", error);
                        reject(new Error(`网络错误: ${error.statusText || '连接失败'}`));
                    }
                });
            });
        },

        validateInput(name, key) {
            if (!name || !key) {
                alert("Token 名称和密钥都要填写!");
                return false;
            }
            // 移除对token名称的严格限制,允许更多字符,包括@和.
            // if (!/^[a-zA-Z0-9-_]+$/.test(name)) {
            //     alert("Token 名称只能包含字母、数字、下划线和连字符!");
            //     return false;
            // }
            if (!this.validateTokenKey(key)) {
                alert("无效的 Token 密钥格式!");
                return false;
            }
            return true;
        },

        validateTokenKey(key) {
            return /^sk-ant-sid\d{2}-[A-Za-z0-9_-]*$/.test(key);
        },

        showConfirmDialog(title, message) {
            return new Promise((resolve) => {
                const content = UI.createElem("div", "claude-confirm-dialog");

                content.innerHTML = `
                    <div style="font-size: 48px; text-align: center; margin-bottom: 16px;">⚠️</div>
                    <div style="font-size: 18px; font-weight: 600; text-align: center; margin-bottom: 12px;">${title}</div>
                    <div style="text-align: center; margin-bottom: 24px;">${message}</div>
                `;

                const { modal, buttonContainer, close } = UI.createModal("", content);
                modal.querySelector(".claude-modal-content").classList.add("narrow-modal");

                const confirmButton = UI.createButton("确认", "claude-button primary");
                confirmButton.addEventListener("click", () => {
                    close();
                    resolve(true);
                });

                const cancelButton = UI.createButton("取消", "claude-button secondary");
                cancelButton.addEventListener("click", () => {
                    close();
                    resolve(false);
                });

                buttonContainer.appendChild(cancelButton);
                buttonContainer.appendChild(confirmButton);
            });
        },

        fetchIPCountryCode() {
            this.ipDisplay.textContent = "IP: 加载中...";

            GM_xmlhttpRequest({
                method: "GET",
                url: config.ipApiUrl,
                onload: (response) => {
                    if (response.status === 200) {
                        this.ipDisplay.textContent = "IP: " + response.responseText.trim();
                    } else {
                        this.ipDisplay.textContent = "IP: 获取失败";
                    }
                },
                onerror: () => {
                    this.ipDisplay.textContent = "IP: 获取失败";
                },
            });
        },

        setupEventListeners() {
            // 拖拽相关事件
            this.toggleButton.addEventListener("mousedown", this.onMouseDown.bind(this));
            document.addEventListener("mousemove", this.onMouseMove.bind(this));
            document.addEventListener("mouseup", this.onMouseUp.bind(this));

            // 状态管理对象
            this.state = {
                isButtonHovered: false,
                isDropdownHovered: false,
                isDropdownVisible: false,
                isDragging: false,
                isProcessingClick: false,  // 新增:处理点击状态
                isClosing: false,  // 新增:窗口正在关闭的状态

                // 检查当前是否应该保持弹窗显示
                shouldKeepOpen() {
                    // 修改逻辑,即使在拖动过程中,也要考虑鼠标悬停状态
                    return this.isButtonHovered || this.isDropdownHovered;
                }
            };

            // 定时器
            this.hoverTimeout = null;
            this.closeTimeout = null;

            // 鼠标进入按钮
            this.toggleButton.addEventListener("mouseenter", (e) => {
                if (this.state.isProcessingClick) return;  // 如果正在处理点击,忽略hover

                this.state.isButtonHovered = true;
                clearTimeout(this.closeTimeout);
                clearTimeout(this.hoverTimeout);  // 确保清除之前的hover定时器

                // 如果下拉窗口未显示或正在关闭中,则显示窗口
                if (!this.state.isDropdownVisible || this.state.isClosing) {
                    // 如果窗口正在关闭,立即显示
                    if (this.state.isClosing) {
                        this.state.isClosing = false;
                        this.showDropdown();
                    } else {
                        this.hoverTimeout = setTimeout(() => {
                            this.showDropdown();
                        }, 300);
                    }
                }
            });

            // 鼠标离开按钮
            this.toggleButton.addEventListener("mouseleave", (e) => {
                if (this.state.isProcessingClick) return;  // 如果正在处理点击,忽略hover

                this.state.isButtonHovered = false;
                clearTimeout(this.hoverTimeout);

                // 检查是否应该关闭弹窗
                if (!this.state.shouldKeepOpen()) {
                    this.scheduleHideDropdown();
                }
            });

            // 按钮点击事件
            this.toggleButton.addEventListener("click", (e) => {
                if (this.state.isDragging) return;  // 如果正在拖拽,忽略点击

                this.state.isProcessingClick = true;
                clearTimeout(this.hoverTimeout);
                clearTimeout(this.closeTimeout);

                if (this.state.isDropdownVisible) {
                    this.hideDropdown();
                } else {
                    this.showDropdown();
                }

                setTimeout(() => {
                    this.state.isProcessingClick = false;
                }, 100);
            });

            // 鼠标进入弹窗
            this.dropdownContainer.addEventListener("mouseenter", () => {
                if (this.state.isProcessingClick) return;

                this.state.isDropdownHovered = true;
                clearTimeout(this.closeTimeout);

                // 如果弹窗在淡出过程中,恢复显示
                if (this.state.isDropdownVisible && this.dropdownContainer.style.opacity !== "1") {
                    this.dropdownContainer.style.opacity = "1";
                    this.dropdownContainer.style.transform = "scale(1)";
                }
            });

            // 鼠标离开弹窗
            this.dropdownContainer.addEventListener("mouseleave", () => {
                if (this.state.isProcessingClick) return;

                this.state.isDropdownHovered = false;

                // 检查是否应该关闭弹窗
                if (!this.state.shouldKeepOpen()) {
                    this.scheduleHideDropdown();
                }
            });

            // 点击其他区域关闭下拉菜单
            document.addEventListener("click", (e) => {
                if (
                    this.dropdownContainer.style.display === "flex" &&
                    !this.dropdownContainer.contains(e.target) &&
                    e.target !== this.toggleButton
                ) {
                    this.state.isDropdownHovered = false;
                    this.state.isButtonHovered = false;
                    this.hideDropdown();
                }
            });
        },

        onMouseDown(e) {
            if (e.button !== 0) return; // 只处理左键点击

            this.isDragging = true;
            this.state.isDragging = true;
            this.startX = e.clientX;
            this.startY = e.clientY;

            const rect = this.toggleButton.getBoundingClientRect();
            this.offsetX = this.startX - rect.left;
            this.offsetY = this.startY - rect.top;

            this.toggleButton.style.cursor = "grabbing";

            // 不再清除悬停定时器,允许在拖动过程中显示窗口
            // clearTimeout(this.hoverTimeout);
            clearTimeout(this.closeTimeout);

            // 如果鼠标在按钮上,立即显示下拉窗口
            if (this.state.isButtonHovered && !this.state.isDropdownVisible) {
                this.showDropdown();
            }

            // 阻止默认行为和事件冒泡
            e.preventDefault();
            e.stopPropagation();
        },

        onMouseMove(e) {
            // 检查鼠标是否在按钮上,用于处理拖动过程中的悬停
            const buttonRect = this.toggleButton.getBoundingClientRect();
            const isOverButton =
                e.clientX >= buttonRect.left &&
                e.clientX <= buttonRect.right &&
                e.clientY >= buttonRect.top &&
                e.clientY <= buttonRect.bottom;

            // 更新悬停状态
            if (isOverButton && !this.state.isButtonHovered) {
                this.state.isButtonHovered = true;
                // 如果下拉窗口未显示或正在关闭中,则显示窗口
                if (!this.state.isDropdownVisible || this.state.isClosing) {
                    clearTimeout(this.hoverTimeout);
                    // 如果窗口正在关闭或正在拖动,立即显示
                    if (this.state.isClosing || this.isDragging) {
                        this.state.isClosing = false;
                        this.showDropdown();
                    } else {
                        this.hoverTimeout = setTimeout(() => {
                            this.showDropdown();
                        }, 300);
                    }
                }
            } else if (!isOverButton && this.state.isButtonHovered) {
                this.state.isButtonHovered = false;
                clearTimeout(this.hoverTimeout);
                if (!this.state.shouldKeepOpen()) {
                    this.scheduleHideDropdown();
                }
            }

            if (!this.isDragging) return;

            const x = e.clientX - this.offsetX;
            const y = e.clientY - this.offsetY;

            // 计算底部位置
            const bottom = window.innerHeight - y - this.toggleButton.offsetHeight;

            // 确保按钮在窗口范围内
            const maxX = window.innerWidth - this.toggleButton.offsetWidth;
            const maxBottom = window.innerHeight - this.toggleButton.offsetHeight;

            this.buttonLeft = Math.max(0, Math.min(x, maxX));
            this.buttonBottom = Math.max(0, Math.min(bottom, maxBottom));

            // 更新按钮位置
            this.toggleButton.style.left = `${this.buttonLeft}px`;
            this.toggleButton.style.bottom = `${this.buttonBottom}px`;
            this.toggleButton.style.top = "auto";

            // 如果下拉窗口可见,更新其位置
            if (this.state.isDropdownVisible) {
                this.updateDropdownPosition();
            }

            e.preventDefault();
        },

        // 添加新方法用于更新下拉窗口位置
        updateDropdownPosition() {
            const buttonRect = this.toggleButton.getBoundingClientRect();
            const dropdownWidth = 600; // 下拉窗口宽度

            // 计算下拉窗口位置
            let left = buttonRect.right + 10;
            let top = buttonRect.top;

            // 检查是否超出右边界
            if (left + dropdownWidth > window.innerWidth) {
                // 如果右边放不下,则放到左边
                left = buttonRect.left - dropdownWidth - 10;
            }

            // 如果左边也放不下,则居中显示
            if (left < 0) {
                left = Math.max(0, (window.innerWidth - dropdownWidth) / 2);
            }

            // 检查是否超出底部边界
            const dropdownHeight = Math.min(this.dropdownContainer.scrollHeight, window.innerHeight * 0.8);
            if (top + dropdownHeight > window.innerHeight) {
                // 如果超出底部,则向上显示,确保完全可见
                top = Math.max(0, window.innerHeight - dropdownHeight - 10);
            }

            // 应用新位置
            this.dropdownContainer.style.left = `${left}px`;
            this.dropdownContainer.style.top = `${top}px`;
        },

        onMouseUp(e) {
            if (!this.isDragging) return;

            this.isDragging = false;
            this.state.isDragging = false;
            this.toggleButton.style.cursor = "move";

            // 保存位置
            GM_setValue("buttonLeft", this.buttonLeft);
            GM_setValue("buttonBottom", this.buttonBottom);

            // 检查鼠标是否在按钮上
            const buttonRect = this.toggleButton.getBoundingClientRect();
            const isOverButton =
                e.clientX >= buttonRect.left &&
                e.clientX <= buttonRect.right &&
                e.clientY >= buttonRect.top &&
                e.clientY <= buttonRect.bottom;

            // 更新悬停状态
            this.state.isButtonHovered = isOverButton;

            // 添加短暂延迟,避免与点击事件冲突
            setTimeout(() => {
                // 如果鼠标在按钮上且下拉窗口未显示,则显示下拉窗口
                if (isOverButton && !this.state.isDropdownVisible) {
                    this.showDropdown();
                }
                // 否则检查是否应该关闭弹窗
                else if (!this.state.shouldKeepOpen()) {
                    this.scheduleHideDropdown();
                }
            }, 100);

            e.preventDefault();
        },

        scheduleHideDropdown() {
            if (this.state.isProcessingClick) return;

            clearTimeout(this.closeTimeout);
            this.closeTimeout = setTimeout(() => {
                if (!this.state.shouldKeepOpen()) {
                    this.hideDropdown();
                }
            }, 300);
        },

        showDropdown() {
            // 立即更新状态
            this.state.isDropdownVisible = true;
            this.state.isClosing = false;

            // 计算下拉菜单位置
            const buttonRect = this.toggleButton.getBoundingClientRect();
            const dropdownWidth = 600;

            // 先显示容器但设为透明,以便获取实际高度
            this.dropdownContainer.style.opacity = "0";
            this.dropdownContainer.style.display = "flex";

            // 更新下拉窗口位置
            this.updateDropdownPosition();

            // 淡入效果
            setTimeout(() => {
                this.dropdownContainer.style.opacity = "1";
                this.dropdownContainer.style.transform = "scale(1)";
                this.toggleButton.style.transform = "scale(1.1)";
            }, 10);
        },

        hideDropdown() {
            // 设置正在关闭状态
            this.state.isClosing = true;

            // 添加动画
            this.dropdownContainer.style.opacity = "0";
            this.dropdownContainer.style.transform = "scale(0.95)";
            this.toggleButton.style.transform = "scale(1)";

            // 等待动画完成后隐藏
            this.closeTimeout = setTimeout(() => {
                if (!this.state.shouldKeepOpen()) {
                    this.dropdownContainer.style.display = "none";
                    this.state.isDropdownVisible = false;
                    this.state.isClosing = false;  // 重置关闭状态
                } else {
                    // 如果此时应该保持打开,则恢复显示
                    this.state.isClosing = false;  // 重置关闭状态
                    this.dropdownContainer.style.opacity = "1";
                    this.dropdownContainer.style.transform = "scale(1)";
                }
            }, 300);
        },

        observeThemeChanges() {
            const observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    if (
                        mutation.type === "attributes" &&
                        mutation.attributeName === "data-mode"
                    ) {
                        this.isDarkMode =
                            document.documentElement.getAttribute("data-mode") === "dark";
                        this.updateStyles();
                    }
                });
            });

            observer.observe(document.documentElement, {
                attributes: true,
                attributeFilter: ["data-mode"],
            });
        },
    };

    // 初始化应用
    App.init();
})();