部署在CF的轻量化导航页面,可移动卡片式书签,方便管理

部署在CF的轻量化导航页面,可移动卡片式书签,方便管理

wszx01
2024-09-08 / 0 评论 / 2 阅读

部署在CF的轻量化导航页面,可移动卡片式书签,方便管理
github项目

Card-Tab 书签卡片式管理,进入管理模式可以自由移动书签位置,添加和删除书签,支持自定义网站分类,支持切换黑暗色主题
一、cloudflare workes部署
新版本部署
2024.10.30 更新:
1)、增加了前端验证,并取消了在浏览器中保存日志。现在超过15分钟需重新登录,及时退出登录能让你的隐私更安全;
2)、进入设置之前会在自动备份书签,KV里将保存最近10次的备份;
3)、小幅更改了配色。
1、原workes,

const HTML_CONTENT = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Card Tab</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>⭐</text></svg>">
    <style>
    /* 全局样式 */
    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        background-color: #e8f4ea;
        transition: background-color 0.3s ease;
    }
    
    /* 固定元素样式 */
    .fixed-elements {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        background-color: #e8f4ea;
        z-index: 1000;
        padding: 10px;
        transition: background-color 0.3s ease;
        height: 130px;
    }
    
    .fixed-elements h3 {
        position: absolute;
        top: 10px;
        left: 20px;
        margin: 0;
    }
    
    /* 中心内容样式 */
    .center-content {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        max-width: 600px;
        text-align: center;
    }
    
    /* 管理员控制面板样式 */
    .admin-controls {
        position: fixed;
        top: 10px;
        right: 10px;
        font-size: 60%;
    }
    
    /* 添加/删除控制按钮样式 */
    .add-remove-controls {
        display: none;
        flex-direction: column;
        position: fixed;
        right: 20px;
        top: 50%;
        transform: translateY(-50%);
        align-items: center;
        gap: 10px;
    }
    
    .round-btn {
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
        margin: 5px 0;
    }
    
    .add-btn { order: 1; }
    .remove-btn { order: 2; }
    .category-btn { order: 3; }
    .remove-category-btn { order: 4; }
    
    /* 主要内容区域样式 */
    .content {
        margin-top: 140px;
        padding: 20px;
    }
    
    /* 搜索栏样式 */
    .search-container {
        margin-top: 10px;
    }
    
    .search-bar {
        display: flex;
        justify-content: center;
        margin-bottom: 10px;
    }
    
    .search-bar input {
        width: 70%;
        padding: 5px;
        border: 1px solid #ccc;
        border-radius: 5px 0 0 5px;
    }
    
    .search-bar button {
        padding: 5px 10px;
        border: 1px solid #ccc;
        border-left: none;
        background-color: #f8f8;
        border-radius: 0 5px 5px 0;
        cursor: pointer;
    }
    
    /* 搜索引擎按钮样式 */
    .search-engines {
        display: flex;
        justify-content: center;
        gap: 10px;
    }
    
    .search-engine {
        padding: 5px 10px;
        border: 1px solid #ccc;
        background-color: #f0f0f0;
        border-radius: 5px;
        cursor: pointer;
    }
    
    /* 主题切换按钮样式 */
    #theme-toggle {
        position: fixed;
        bottom: 50px;
        right: 20px;
        background-color: #b8c9d9;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        transition: background-color 0.3s ease;
    }

    #theme-toggle:hover {
        background-color: #007bff;
    }

    /* 对话框样式 */
    #dialog-overlay {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        justify-content: center;
        align-items: center;
    }
    
    #dialog-box {
        background-color: white;
        padding: 20px;
        border-radius: 5px;
        width: 300px;
    }
    
    #dialog-box input, #dialog-box select {
        width: 100%;
        margin-bottom: 10px;
        padding: 5px;
    }
    
    /* 分类和卡片样式 */
    .section {
        margin-bottom: 20px;
    }
    
    .section-title-container {
        display: flex;
        align-items: center;
        margin-bottom: 10px;
    }
    
    .section-title {
        font-size: 18px;
        font-weight: bold;
    }
    
    .delete-category-btn {
        background-color: #ff9800;
        color: white;
        border: none;
        padding: 5px 10px;
        border-radius: 5px;
        cursor: pointer;
    }
    
    .card-container {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }
    
    .card {
        background-color: #b8c9d9;
        border-radius: 5px;
        padding: 10px;
        width: 150px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        cursor: pointer;
        transition: transform 0.2s;
        position: relative;
        user-select: none;
    }
    
    .card:hover {
        transform: translateY(-5px);
    }
    
    .card-top {
        display: flex;
        align-items: center;
        margin-bottom: 5px;
    }
    
    .card-icon {
        width: 16px;
        height: 16px;
        margin-right: 5px;
    }
    
    .card-title {
        font-size: 14px;
        font-weight: bold;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    
    .card-url {
        font-size: 12px;
        color: #666;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .private-tag {
        background-color: #ff9800;
        color: white;
        font-size: 10px;
        padding: 2px 5px;
        border-radius: 3px;
        position: absolute;
        top: 5px;
        right: 5px;
    }
    
    .delete-btn {
        position: absolute;
        top: -10px;
        right: -10px;
        background-color: red;
        color: white;
        border: none;
        border-radius: 50%;
        width: 20px;
        height: 20px;
        text-align: center;
        font-size: 14px;
        line-height: 20px;
        cursor: pointer;
        display: none;
    }
    
    /* 版权信息样式 */
    #copyright {
        position: fixed;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 40px;
        background-color: rgba(255, 255, 255, 0.8);
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 14px;
        z-index: 1000;
        box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
    }
    
    #copyright p {
        margin: 0;
    }
    
    #copyright a {
        color: #007bff;
        text-decoration: none;
    }
    
    #copyright a:hover {
        text-decoration: underline;
    }
    
    /* 响应式设计 */
    @media (max-width: 480px) {
        .fixed-elements {
            position: relative;
            padding: 5px;
        }
        .content {
            margin-top: 10px;
        }

        .admin-controls input,
        .admin-controls button {
            height: 30%;
        }

        .card-container {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
        }
        .card {
            width: 80%;
            max-width: 100%;
            padding: 5px;
        }
        .card-title {
            font-size: 12px;
            white-space: nowrap; 
            overflow: hidden; 
            text-overflow: ellipsis; 
            max-width: 130px; 
        }
        .card-url {
            font-size: 10px;
            white-space: nowrap; 
            overflow: hidden; 
            text-overflow: ellipsis; 
            max-width: 130px;
        }
        
        .add-remove-controls {
            right: 2px;
          }

        .round-btn, 
        #theme-toggle {
            right: 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 30px;
            height: 30px;
            font-size: 24px;
        }
    }
    </style>
</head>

<body>
    <div class="fixed-elements">
        <h3>我的导航</h3>
        <div class="center-content">
            <!-- 一言模块 -->
            <p id="hitokoto">
                <a href="#" id="hitokoto_text"></a>
            </p>
            <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
            <!-- 搜索栏 -->
            <div class="search-container">
                <div class="search-bar">
                    <input type="text" id="search-input" placeholder="">
                    <button id="search-button">🔍</button>
                </div>
                <div class="search-engines">
                    <button class="search-engine" data-engine="baidu">百度</button>
                    <button class="search-engine" data-engine="bing">必应</button>
                    <button class="search-engine" data-engine="google">谷歌</button>
                </div>
            </div>
        </div>
        <!-- 管理员控制面板 -->
        <div class="admin-controls">
            <input type="password" id="admin-password" placeholder="输入密码">
            <button id="admin-mode-btn" onclick="toggleAdminMode()">设  置</button>
            <button id="secret-garden-btn" onclick="toggleSecretGarden()">登  录</button>
        </div>
    </div>
    <div class="content">
        <!-- 添加/删除控制按钮 -->
        <div class="add-remove-controls">
            <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
            <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
            <button class="round-btn category-btn" onclick="addCategory()">C+</button>
            <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>

        </div>

        <!-- 分类和卡片容器 -->
        <div id="sections-container"></div>
        <!-- 主题切换按钮 -->
        <button id="theme-toggle" onclick="toggleTheme()">◑</button>
        <!-- 添加链接对话框 -->
        <div id="dialog-overlay">
            <div id="dialog-box">
                <label for="name-input">名称</label>
                <input type="text" id="name-input">
                <label for="url-input">地址</label>
                <input type="text" id="url-input">
                <label for="category-select">选择分类</label>
                <select id="category-select"></select>
                <div class="private-link-container">
                    <label for="private-checkbox">私密链接</label>    
                    <input type="checkbox" id="private-checkbox">
                </div>
                <button onclick="addLink()">确定</button>
                <button onclick="hideAddDialog()">取消</button>
            </div>
        </div>
        <!-- 版权信息 -->
        <div id="copyright" class="copyright">
            <!--请不要删除-->
            <p>项目地址:<a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 如果喜欢,烦请点个star!</p>
        </div>
    </div>

    <script>
    // 搜索引擎配置
    const searchEngines = {
        baidu: "https://www.baidu.com/s?wd=",
        bing: "https://www.bing.com/search?q=",
        google: "https://www.google.com/search?q="
    };
    
    let currentEngine = "baidu";
    
    // 日志记录函数
    function logAction(action, details) {
        const timestamp = new Date().toISOString();
        const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
        console.log(logEntry); 
    }
    
    // 设置当前搜索引擎
    function setActiveEngine(engine) {
        currentEngine = engine;
        document.querySelectorAll('.search-engine').forEach(btn => {
            btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
        });
        logAction('设置搜索引擎', { engine });
    }
    
    // 搜索引擎按钮点击事件
    document.querySelectorAll('.search-engine').forEach(button => {
        button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
    });
    
    // 搜索按钮点击事件
    document.getElementById('search-button').addEventListener('click', () => {
        const query = document.getElementById('search-input').value;
        if (query) {
            logAction('执行搜索', { engine: currentEngine, query });
            window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
        }
    });
    
    // 搜索输入框回车事件
    document.getElementById('search-input').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('search-button').click();
        }
    });
    
    // 初始化搜索引擎
    setActiveEngine(currentEngine);
    
    // 全局变量
    let publicLinks = [];
    let privateLinks = [];
    let isAdmin = false;
    let isLoggedIn = false;
    let removeMode = false;
    let isRemoveCategoryMode = false;
    let isDarkTheme = false;
    let links = [];
    const categories = {};
    
    // 添加新分类
    async function addCategory() {
        if (!await validateToken()) {
            return; 
        }
        const categoryName = prompt('请输入新分类名称:');
        if (categoryName && !categories[categoryName]) {
            categories[categoryName] = [];
            updateCategorySelect();
            renderCategories();
            saveLinks();
            logAction('添加分类', { categoryName, currentLinkCount: links.length });
        } else if (categories[categoryName]) {
            alert('该分类已存在');
            logAction('添加分类失败', { categoryName, reason: '分类已存在' });
        }
    }

    // 删除分类
    async function deleteCategory(category) {
        if (!await validateToken()) {
            return; 
        }
        if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
            delete categories[category];
            links = links.filter(link => link.category !== category);
            publicLinks = publicLinks.filter(link => link.category !== category);
            privateLinks = privateLinks.filter(link => link.category !== category);
            updateCategorySelect();
            saveLinks();
            renderCategories();
            logAction('删除分类', { category });
        }
    }    

    // 渲染分类(不重新加载链接)
    function renderCategories() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            container.appendChild(section);
    
            const categoryLinks = links.filter(link => link.category === category);
            categoryLinks.forEach(link => {
                createCard(link, cardContainer);
            });
        });
    
        logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
    }    
    
    // 读取链接数据
    async function loadLinks() {
        const headers = {
            'Content-Type': 'application/json'
        };
        
        // 如果已登录,从 localStorage 获取 token 并添加到请求头
        if (isLoggedIn) {
            const token = localStorage.getItem('authToken');
            if (token) {
                headers['Authorization'] = token; 
            }
        }
        
        try {
            const response = await fetch('/api/getLinks?userId=testUser', {
                headers: headers
            });
            
            if (!response.ok) {
                throw new Error("HTTP error! status: " + response.status);
            }
            
            
            const data = await response.json();
            console.log('Received data:', data); 
            
            if (data.categories) {
                Object.assign(categories, data.categories);
            }
            
            publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
            privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
            links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;

            loadSections();
            updateCategorySelect();
            updateUIState();
            logAction('读取链接', { 
                publicCount: publicLinks.length, 
                privateCount: privateLinks.length,
                isLoggedIn: isLoggedIn,
                hasToken: !!localStorage.getItem('authToken')
            });
        } catch (error) {
            console.error('Error loading links:', error);
            alert('加载链接时出错,请刷新页面重试');
        }
    }
    
    
    // 更新UI状态
    function updateUIState() {
        const passwordInput = document.getElementById('admin-password');
        const adminBtn = document.getElementById('admin-mode-btn');
        const secretGardenBtn = document.getElementById('secret-garden-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
        secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
        secretGardenBtn.style.display = 'inline-block';
    
        if (isAdmin) {
            adminBtn.textContent = "离开设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'flex';
        } else if (isLoggedIn) {
            adminBtn.textContent = "设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'none';
        } else {
            adminBtn.style.display = 'none';
            addRemoveControls.style.display = 'none';
        }
    
        logAction('更新UI状态', { isAdmin, isLoggedIn });
    }
    
    // 登录状态显示(加载所有链接)
    function showSecretGarden() {
        if (isLoggedIn) {
            links = [...publicLinks, ...privateLinks];
            loadSections();
            // 显示所有私密标签
            document.querySelectorAll('.private-tag').forEach(tag => {
                tag.style.display = 'block';
            });
            logAction('显示私密花园');
        }
    }
    
    // 加载分类和链接
    function loadSections() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            let privateCount = 0;
            let linkCount = 0;
    
            links.forEach(link => {
                if (link.category === category) {
                    if (link.isPrivate) privateCount++;
                    linkCount++;
                    createCard(link, cardContainer);
                }
            });
    
            if (privateCount < linkCount || isLoggedIn) {
                container.appendChild(section);
            }
        });
    
        logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
    }
    
    // 创建卡片
    function createCard(link, container) {
        const card = document.createElement('div');
        card.className = 'card';
        card.setAttribute('draggable', isAdmin);
        card.dataset.isPrivate = link.isPrivate;
    
        const cardTop = document.createElement('div');
        cardTop.className = 'card-top';
    
        // 定义默认的 SVG 图标
        const defaultIconSVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
        '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>' +
        '<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>' +
        '</svg>';
        
        // 创建图标元素
        const icon = document.createElement('img');
        icon.className = 'card-icon';
        // icon.src = 'https://api.iowen.cn/favicon/' + extractDomain(link.url) + '.png';
        icon.src = 'https://www.faviconextractor.com/favicon/' + extractDomain(link.url);
        icon.alt = 'Website Icon';
        
        // 如果图片加载失败,使用默认的 SVG 图标
        icon.onerror = function() {
            const svgBlob = new Blob([defaultIconSVG], {type: 'image/svg+xml'});
            const svgUrl = URL.createObjectURL(svgBlob);
            this.src = svgUrl;
            
            this.onload = () => URL.revokeObjectURL(svgUrl);
        };
        
        function extractDomain(url) {
            let domain;
            try {
                domain = new URL(url).hostname;
            } catch (e) {
                domain = url;
            }
            return domain;
        }
    
        const title = document.createElement('div');
        title.className = 'card-title';
        title.textContent = link.name;
    
        cardTop.appendChild(icon);
        cardTop.appendChild(title);
    
        const url = document.createElement('div');
        url.className = 'card-url';
        url.textContent = link.url;
    
        card.appendChild(cardTop);
        card.appendChild(url);
    
        if (link.isPrivate) {
            const privateTag = document.createElement('div');
            privateTag.className = 'private-tag';
            privateTag.textContent = '私密';
            card.appendChild(privateTag);
        }
    
        const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
    
        if (!isAdmin) {
            card.addEventListener('click', () => {
                window.open(correctedUrl, '_blank');
                logAction('打开链接', { name: link.name, url: correctedUrl });
            });
        }
    
        const deleteBtn = document.createElement('button');
        deleteBtn.textContent = '–';
        deleteBtn.className = 'delete-btn';
        deleteBtn.onclick = function (event) {
            event.stopPropagation();
            removeCard(card);
        };
        card.appendChild(deleteBtn);
    
        updateCardStyle(card);
    
        card.addEventListener('dragstart', dragStart);
        card.addEventListener('dragover', dragOver);
        card.addEventListener('dragend', dragEnd);
        card.addEventListener('drop', drop);
        card.addEventListener('touchstart', touchStart, { passive: false });
    
        if (isAdmin && removeMode) {
            deleteBtn.style.display = 'block';
        }
    
        if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
            container.appendChild(card);
        }
        // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
    }
    
    // 更新卡片样式
    function updateCardStyle(card) {
        if (isDarkTheme) {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        } else {
            card.style.backgroundColor = '#b8c9d9';
            card.style.color = '#333';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
        }
    }
    
    // 更新分类选择下拉框
    function updateCategorySelect() {
        const categorySelect = document.getElementById('category-select');
        categorySelect.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const option = document.createElement('option');
            option.value = category;
            option.textContent = category;
            categorySelect.appendChild(option);
        });
    
        logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
    }
    
    // 保存链接数据
    async function saveLinks() {
        if (isAdmin && !(await validateToken())) {
            return;
        }

        let allLinks = [...publicLinks, ...privateLinks];
    
        try {
            await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 
                    'Content-Type': 'application/json',
                    'Authorization': localStorage.getItem('authToken')
                },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: allLinks,
                    categories: categories
                }),
            });
            logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
        } catch (error) {
            logAction('保存链接失败', { error: error.message });
            alert('保存链接失败,请重试');
        }
    }
    
    // 添加卡片弹窗
    async function addLink() {
        if (!await validateToken()) {
            return; 
        }
        const name = document.getElementById('name-input').value;
        const url = document.getElementById('url-input').value;
        const category = document.getElementById('category-select').value;
        const isPrivate = document.getElementById('private-checkbox').checked;
    
        if (name && url && category) {
            const newLink = { name, url, category, isPrivate };
    
            if (isPrivate) {
                privateLinks.push(newLink);
            } else {
                publicLinks.push(newLink);
            }
    
            links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
    
            if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
                const container = document.getElementById(category);
                if (container) {
                    createCard(newLink, container);
                } else {
                    categories[category] = [];
                    renderCategories();
                }
            }
    
            saveLinks();
    
            document.getElementById('name-input').value = '';
            document.getElementById('url-input').value = '';
            document.getElementById('private-checkbox').checked = false;
            hideAddDialog();

            logAction('添加卡片', { name, url, category, isPrivate });
        }
    }

    // 删除卡片
    async function removeCard(card) {
        if (!await validateToken()) {
            return; 
        }
        const name = card.querySelector('.card-title').textContent;
        const url = card.querySelector('.card-url').textContent;
        const isPrivate = card.dataset.isPrivate === 'true';
        
        links = links.filter(link => link.url !== url);
        if (isPrivate) {
            privateLinks = privateLinks.filter(link => link.url !== url);
        } else {
            publicLinks = publicLinks.filter(link => link.url !== url);
        }
    
        for (const key in categories) {
            categories[key] = categories[key].filter(link => link.url !== url);
        }
    
        card.remove();
    
        saveLinks();
    
        logAction('删除卡片', { name, url, isPrivate });
    }
    
    // 拖拽卡片
    let draggedCard = null;
    let touchStartX, touchStartY;
    
    // 触屏端拖拽卡片
    function touchStart(event) {
        if (!isAdmin) {
            return;
        }
        draggedCard = event.target.closest('.card');
        if (!draggedCard) return;
    
        event.preventDefault();
        const touch = event.touches[0];
        touchStartX = touch.clientX;
        touchStartY = touch.clientY;
    
        draggedCard.classList.add('dragging');
        
        document.addEventListener('touchmove', touchMove, { passive: false });
        document.addEventListener('touchend', touchEnd);
    
    }
    
    function touchMove(event) {
        if (!draggedCard) return;
        event.preventDefault();
    
        const touch = event.touches[0];
        const currentX = touch.clientX;
        const currentY = touch.clientY;

        const deltaX = currentX - touchStartX;
        const deltaY = currentY - touchStartY;
        draggedCard.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
    
        const target = findCardUnderTouch(currentX, currentY);
        if (target && target !== draggedCard) {
            const container = target.parentElement;
            const targetRect = target.getBoundingClientRect();
    
            if (currentX < targetRect.left + targetRect.width / 2) {
                container.insertBefore(draggedCard, target);
            } else {
                container.insertBefore(draggedCard, target.nextSibling);
            }
        }
    }
    
    function touchEnd(event) {
        if (!draggedCard) return;
    
        const card = draggedCard;
        const targetCategory = card.closest('.card-container').id;
    
        validateToken().then(isValid => {
            if (isValid && card) {
                updateCardCategory(card, targetCategory);
                saveCardOrder().catch(error => {
                    console.error('Save failed:', error);
                });
            }
            cleanupDragState();
        });
    }
    
    function findCardUnderTouch(x, y) {
        const cards = document.querySelectorAll('.card:not(.dragging)');
        return Array.from(cards).find(card => {
            const rect = card.getBoundingClientRect();
            return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
        });
    }

    // PC端拖拽卡片
    function dragStart(event) {
        if (!isAdmin) {
            event.preventDefault();
            return;
        }
        draggedCard = event.target.closest('.card');
        if (!draggedCard) return;
    
        draggedCard.classList.add('dragging');
        event.dataTransfer.effectAllowed = "move";
        logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
    }
    
    function dragOver(event) {
        if (!isAdmin) {
            event.preventDefault();
            return;
        }
        event.preventDefault();
        const target = event.target.closest('.card');
        if (target && target !== draggedCard) {
            const container = target.parentElement;
            const mousePositionX = event.clientX;
            const targetRect = target.getBoundingClientRect();
    
            if (mousePositionX < targetRect.left + targetRect.width / 2) {
                container.insertBefore(draggedCard, target);
            } else {
                container.insertBefore(draggedCard, target.nextSibling);
            }
        }
    }
    
    // 清理拖拽状态函数
    function cleanupDragState() {
        if (draggedCard) {
            draggedCard.classList.remove('dragging');
            draggedCard.style.transform = '';
            draggedCard = null;
        }
        
        document.removeEventListener('touchmove', touchMove);
        document.removeEventListener('touchend', touchEnd);
        
        touchStartX = null;
        touchStartY = null;
    }

    // PC端拖拽结束
    function drop(event) {
        if (!isAdmin) {
            event.preventDefault();
            return;
        }
        event.preventDefault();
        
        const card = draggedCard;
        const targetCategory = event.target.closest('.card-container').id;
        
        validateToken().then(isValid => {
            if (isValid && card) {
                updateCardCategory(card, targetCategory);
                saveCardOrder().catch(error => {
                    console.error('Save failed:', error);
                });
            }
            cleanupDragState();
        });
    }

    function dragEnd(event) {
        if (draggedCard) {
            draggedCard.classList.remove('dragging');
            logAction('拖拽卡片结束');
        }
    }
  
    // 更新卡片分类
    function updateCardCategory(card, newCategory) {
        const cardTitle = card.querySelector('.card-title').textContent;
        const cardUrl = card.querySelector('.card-url').textContent;
        const isPrivate = card.dataset.isPrivate === 'true';
    
        const linkIndex = links.findIndex(link => link.url === cardUrl);
        if (linkIndex !== -1) {
            links[linkIndex].category = newCategory;
        }
    
        const linkArray = isPrivate ? privateLinks : publicLinks;
        const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
        if (arrayIndex !== -1) {
            linkArray[arrayIndex].category = newCategory;
        }
    
        card.dataset.category = newCategory;
    }

    // 在页面加载完成后添加触摸事件监听器
    document.addEventListener('DOMContentLoaded', function() {
        const cardContainers = document.querySelectorAll('.card-container');
        cardContainers.forEach(container => {
            container.addEventListener('touchstart', touchStart, { passive: false });
        });
    });    
    
    // 保存卡片顺序
    async function saveCardOrder() {
        if (!await validateToken()) {
            return; 
        }
        const containers = document.querySelectorAll('.card-container');
        let newPublicLinks = [];
        let newPrivateLinks = [];
        let newCategories = {};
    
        containers.forEach(container => {
            const category = container.id;
            newCategories[category] = [];
    
            [...container.children].forEach(card => {
                const url = card.querySelector('.card-url').textContent;
                const name = card.querySelector('.card-title').textContent;
                const isPrivate = card.dataset.isPrivate === 'true';
                card.dataset.category = category;
                const link = { name, url, category, isPrivate };
                if (isPrivate) {
                    newPrivateLinks.push(link);
                } else {
                    newPublicLinks.push(link);
                }
                newCategories[category].push(link); 
            });
        });
    
        publicLinks.length = 0;
        publicLinks.push(...newPublicLinks);
        privateLinks.length = 0;
        privateLinks.push(...newPrivateLinks);
        Object.keys(categories).forEach(key => delete categories[key]);
        Object.assign(categories, newCategories);
    
        logAction('保存卡片顺序', { 
            publicCount: newPublicLinks.length, 
            privateCount: newPrivateLinks.length, 
            categoryCount: Object.keys(newCategories).length 
        });
    
        try {
            const response = await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 
                    'Content-Type': 'application/json',
                    'Authorization': localStorage.getItem('authToken')
                },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: [...newPublicLinks, ...newPrivateLinks],
                    categories: newCategories
                }),
            });
            const result = await response.json();
            if (!result.success) {
                throw new Error('Failed to save order');
            }
            logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
        } catch (error) {
            logAction('保存顺序失败', { error: error.message });
            alert('保存顺序失败,请重试');
        }
    }             
    
    // 设置状态重新加载卡片
    function reloadCardsAsAdmin() {
        document.querySelectorAll('.card-container').forEach(container => {
            container.innerHTML = '';
        });
        loadLinks().then(() => {
            if (isDarkTheme) {
                applyDarkTheme();
            }
        });
        logAction('重新加载卡片(管理员模式)');
    }
    
    // 密码输入框回车事件
    document.getElementById('admin-password').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            toggleSecretGarden();
        }
    });
    
    // 切换设置状态
    async function toggleAdminMode() {
        const adminBtn = document.getElementById('admin-mode-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        if (!isAdmin && isLoggedIn) {
            if (!await validateToken()) {
                return; 
            }

            // 在进入设置模式之前进行备份
            try {
                const response = await fetch('/api/backupData', {
                    method: 'POST',
                    headers: { 
                        'Content-Type': 'application/json',
                        'Authorization': localStorage.getItem('authToken')
                    },
                    body: JSON.stringify({ 
                        sourceUserId: 'testUser', 
                        backupUserId: 'backup' 
                    }),
                });
                const result = await response.json();
                if (result.success) {
                    logAction('数据备份成功');
                } else {
                    throw new Error('备份失败');
                }
            } catch (error) {
                logAction('数据备份失败', { error: error.message });
                if (!confirm('备份失败,是否仍要继续进入设置模式?')) {
                    return;
                }
            }

            isAdmin = true;
            adminBtn.textContent = "退出设置";
            addRemoveControls.style.display = 'flex';
            alert('准备设置分类和书签');
            reloadCardsAsAdmin();
            logAction('进入设置');
        } else if (isAdmin) {
            isAdmin = false;
            removeMode = false;
            adminBtn.textContent = "设  置";
            addRemoveControls.style.display = 'none';
            alert('设置已保存');
            reloadCardsAsAdmin();
            logAction('离开设置');
        }
    
        updateUIState();
    }
    
    // 切换到登录状态
    function toggleSecretGarden() {
        const passwordInput = document.getElementById('admin-password');
        if (!isLoggedIn) {
            verifyPassword(passwordInput.value)
                .then(result => {
                    if (result.valid) {
                        isLoggedIn = true;
                        localStorage.setItem('authToken', result.token);
                        console.log('Token saved:', result.token); 
                        loadLinks(); 
                        alert('登录成功!');
                        logAction('登录成功');
                    } else {
                        alert('密码错误');
                        logAction('登录失败', { reason: result.error || '密码错误' });
                    }
                    updateUIState();
                })
                .catch(error => {
                    console.error('Login error:', error);
                    alert('登录过程出错,请重试');
                });
        } else {
            isLoggedIn = false;
            isAdmin = false;
            localStorage.removeItem('authToken');
            links = publicLinks;
            loadSections();
            alert('退出登录!');
            updateUIState();
            passwordInput.value = '';
            logAction('退出登录');
        }
    }
    
    // 应用暗色主题
    function applyDarkTheme() {
        document.body.style.backgroundColor = '#121212';
        document.body.style.color = '#ffffff';
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        });
        logAction('应用暗色主题');
    }
    
    // 显示添加链接对话框
    function showAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'flex';
        logAction('显示添加链接对话框');
    }
    
    // 隐藏添加链接对话框
    function hideAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'none';
        logAction('隐藏添加链接对话框');
    }
    
    // 切换删除卡片模式
    function toggleRemoveMode() {
        removeMode = !removeMode;
        const deleteButtons = document.querySelectorAll('.delete-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = removeMode ? 'block' : 'none';
        });
        logAction('切换删除卡片模式', { removeMode });
    }
    
    //切换删除分类模式
    function toggleRemoveCategory() {
        isRemoveCategoryMode = !isRemoveCategoryMode;
        const deleteButtons = document.querySelectorAll('.delete-category-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
        });
        logAction('切换删除分类模式', { isRemoveCategoryMode });
    }
    
    // 切换主题
    function toggleTheme() {
        isDarkTheme = !isDarkTheme;
    
        document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#e8f4ea';
        document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#b8c9d9';
            card.style.color = isDarkTheme ? '#ffffff' : '#333';
            card.style.boxShadow = isDarkTheme
                ? '0 4px 8px rgba(0, 0, 0, 0.5)'
                : '0 4px 8px rgba(0, 0, 0, 0.1)';
        });
    
        const fixedElements = document.querySelectorAll('.fixed-elements');
        fixedElements.forEach(element => {
            element.style.backgroundColor = isDarkTheme ? '#121212' : '#e8f4ea';
            element.style.color = isDarkTheme ? '#ffffff' : '#333';
        });
    
        const dialogBox = document.getElementById('dialog-box');
        dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
        dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
        inputs.forEach(input => {
            input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
            input.style.color = isDarkTheme ? '#fff' : '#333';
            input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
        });
        
        logAction('切换主题', { isDarkTheme });
    }
    
    // 验证密码
    async function verifyPassword(inputPassword) {
        const response = await fetch('/api/verifyPassword', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ password: inputPassword }),
        });
        const result = await response.json();
        return result;
    }
    
    // 初始化加载
    document.addEventListener('DOMContentLoaded', async () => {
        await validateToken(); 
        loadLinks(); 
    });


    // 前端检查是否有 token
    async function validateToken() {
        const token = localStorage.getItem('authToken');
        if (!token) {
            isLoggedIn = false;
            updateUIState();
            return false;
        }

        try {
            const response = await fetch('/api/getLinks?userId=testUser', {
                headers: { 'Authorization': token }
            });
            
            if (response.status === 401) {
                await resetToLoginState('token已过期,请重新登录'); 
                return false;
            }
            
            isLoggedIn = true;
            updateUIState();
            return true;
        } catch (error) {
            console.error('Token validation error:', error);
            return false;
        }
    }

    // 重置状态
    async function resetToLoginState(message) {
        alert(message);
        
        cleanupDragState();
        
        localStorage.removeItem('authToken');
        isLoggedIn = false;
        isAdmin = false;
        removeMode = false;
        isRemoveCategoryMode = false;
        
        const passwordInput = document.getElementById('admin-password');
        if (passwordInput) {
            passwordInput.value = '';
        }
        
        updateUIState();
        links = publicLinks;
        loadSections();
        
        const addRemoveControls = document.querySelector('.add-remove-controls');
        if (addRemoveControls) {
            addRemoveControls.style.display = 'none';
        }
        
        document.querySelectorAll('.delete-btn').forEach(btn => {
            btn.style.display = 'none';
        });
        
        document.querySelectorAll('.delete-category-btn').forEach(btn => {
            btn.style.display = 'none';
        });
        
        const dialogOverlay = document.getElementById('dialog-overlay');
        if (dialogOverlay) {
            dialogOverlay.style.display = 'none';
        }
    }

    </script>
</body>

</html>
`;

// 服务端 token 验证
async function validateServerToken(authToken, env) {
    if (!authToken) {
        return {
            isValid: false,
            status: 401,
            response: { error: 'Unauthorized', message: '未登录或登录已过期' }
        };
    }

    try {
        const [timestamp, hash] = authToken.split('.');
        const tokenTimestamp = parseInt(timestamp);
        const now = Date.now();
        
        const FIFTEEN_MINUTES = 15 * 60 * 1000;
        if (now - tokenTimestamp > FIFTEEN_MINUTES) {
            return {
                isValid: false,
                status: 401,
                response: { 
                    error: 'Token expired',
                    tokenExpired: true,
                    message: '登录已过期,请重新登录'
                }
            };
        }
        
        const tokenData = timestamp + "_" + env.ADMIN_PASSWORD;
        const encoder = new TextEncoder();
        const data = encoder.encode(tokenData);
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        const expectedHash = btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
        
        if (hash !== expectedHash) {
            return {
                isValid: false,
                status: 401,
                response: { 
                    error: 'Invalid token',
                    tokenInvalid: true,
                    message: '登录状态无效,请重新登录'
                }
            };
        }

        return { isValid: true };
    } catch (error) {
        return {
            isValid: false,
            status: 401,
            response: { 
                error: 'Invalid token',
                tokenInvalid: true,
                message: '登录验证失败,请重新登录'
            }
        };
    }
}

export default {
    async fetch(request, env) {
      const url = new URL(request.url);
  
      if (url.pathname === '/') {
        return new Response(HTML_CONTENT, {
          headers: { 'Content-Type': 'text/html' }
        });
      }
  
      if (url.pathname === '/api/getLinks') {
        const userId = url.searchParams.get('userId');
        const authToken = request.headers.get('Authorization');
        const data = await env.CARD_ORDER.get(userId);
  
        if (data) {
            const parsedData = JSON.parse(data);
            
            // 验证 token
            if (authToken) {
                const validation = await validateServerToken(authToken, env);
                if (!validation.isValid) {
                    return new Response(JSON.stringify(validation.response), {
                        status: validation.status,
                        headers: { 'Content-Type': 'application/json' }
                    });
                }

                // Token 有效,返回完整数据
                return new Response(JSON.stringify(parsedData), {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' }
                });
            }
            
            // 未提供 token,只返回公开数据
            const filteredLinks = parsedData.links.filter(link => !link.isPrivate);
            const filteredCategories = {};
            Object.keys(parsedData.categories).forEach(category => {
                filteredCategories[category] = parsedData.categories[category].filter(link => !link.isPrivate);
            });
  
            return new Response(JSON.stringify({
                links: filteredLinks,
                categories: filteredCategories
            }), {
                status: 200,
                headers: { 'Content-Type': 'application/json' }
            });
        }
  
        return new Response(JSON.stringify({
            links: [],
            categories: {}
        }), {
            status: 200,
            headers: { 'Content-Type': 'application/json' }
        });
      }
  
      if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
        const authToken = request.headers.get('Authorization');
        const validation = await validateServerToken(authToken, env);
        
        if (!validation.isValid) {
            return new Response(JSON.stringify(validation.response), {
                status: validation.status,
                headers: { 'Content-Type': 'application/json' }
            });
        }

        const { userId, links, categories } = await request.json();
        await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories }));
        return new Response(JSON.stringify({ 
            success: true,
            message: '保存成功'
        }), { 
            status: 200,
            headers: { 'Content-Type': 'application/json' }
        });
      }
  
      if (url.pathname === '/api/verifyPassword' && request.method === 'POST') { 
        try {
            const { password } = await request.json();
            const isValid = password === env.ADMIN_PASSWORD;
            
            if (isValid) {
                // 生成包含时间戳的加密 token
                const timestamp = Date.now();
                const tokenData = timestamp + "_" + env.ADMIN_PASSWORD; 
                const encoder = new TextEncoder();
                const data = encoder.encode(tokenData);
                const hashBuffer = await crypto.subtle.digest('SHA-256', data);
                
                // 使用指定格式:timestamp.hash
                const token = timestamp + "." + btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
                
                return new Response(JSON.stringify({ 
                    valid: true,
                    token: token 
                }), {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' }
                });
            }
            
            return new Response(JSON.stringify({ 
                valid: false,
                error: 'Invalid password'
            }), {
                status: 403,
                headers: { 'Content-Type': 'application/json' }
            });
        } catch (error) {
            return new Response(JSON.stringify({ 
                valid: false,
                error: error.message 
            }), {
                status: 500,
                headers: { 'Content-Type': 'application/json' }
            });
        }
      }
  
      if (url.pathname === '/api/backupData' && request.method === 'POST') {
        const { sourceUserId } = await request.json();
        const result = await this.backupData(env, sourceUserId);
        return new Response(JSON.stringify(result), {
          status: result.success ? 200 : 404,
          headers: { 'Content-Type': 'application/json' }
        });
      }  
  
      return new Response('Not Found', { status: 404 });
    },
  
    async backupData(env, sourceUserId) {
        const MAX_BACKUPS = 10;
        const sourceData = await env.CARD_ORDER.get(sourceUserId);
        
        if (sourceData) {
            try {
                const currentDate = new Date().toLocaleString('zh-CN', {
                    timeZone: 'Asia/Shanghai',
                    year: 'numeric',
                    month: '2-digit',
                    day: '2-digit',
                    hour: '2-digit',
                    minute: '2-digit',
                    second: '2-digit',
                    hour12: false
                }).replace(/\//g, '-'); 
                
                const backupId = `backup_${currentDate}`;
                
                const backups = await env.CARD_ORDER.list({ prefix: 'backup_' });
                const backupKeys = backups.keys.map(key => key.name).sort((a, b) => {
                    const timeA = new Date(a.split('_')[1].replace(/-/g, '/')).getTime();
                    const timeB = new Date(b.split('_')[1].replace(/-/g, '/')).getTime();
                    return timeB - timeA;  // 降序排序,最新的在前
                });
                
                await env.CARD_ORDER.put(backupId, sourceData);
                
                const allBackups = [...backupKeys, backupId].sort((a, b) => {
                    const timeA = new Date(a.split('_')[1].replace(/-/g, '/')).getTime();
                    const timeB = new Date(b.split('_')[1].replace(/-/g, '/')).getTime();
                    return timeB - timeA;
                });
                
                const backupsToDelete = allBackups.slice(MAX_BACKUPS);
                
                if (backupsToDelete.length > 0) {
                    await Promise.all(
                        backupsToDelete.map(key => env.CARD_ORDER.delete(key))
                    );
                }
    
                return { 
                    success: true, 
                    backupId,
                    remainingBackups: MAX_BACKUPS,
                    deletedCount: backupsToDelete.length 
                };
            } catch (error) {
                return { 
                    success: false, 
                    error: 'Backup operation failed',
                    details: error.message 
                };
            }
        }
        return { success: false, error: 'Source data not found' };
    }
  };

2024.10.26 修复:
原获取网站图标的接口https://favicon.zhusl.com/ico?url= 好像失效了,现更换接口https://www.faviconextractor.com/favicon/ 项目地址:【Github】 ;另一个备份接口为https://api.iowen.cn/favicon 项目地址:【Github】 感谢上述接口作者提供的服务

const HTML_CONTENT = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Card Tab</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>💠</text></svg>">
    <style>
    /* 全局样式 */
    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        background-color: #d4d4d4;
        transition: background-color 0.3s ease;
    }

    ul {
        padding: 0;
        margin-block-start: 1em;
        margin-block-end: 1em;
        margin-inline-start: 0px;
        margin-inline-end: 0px;
        padding-inline-start: 40px;
        unicode-bidi: isolate;
    }
    li {
        display: list-item;
        text-align: -webkit-match-parent;
        unicode-bidi: isolate;
        margin: 0 8px;
    }
    
    .background {
              background-image: linear-gradient(#d4d4d4 1px, transparent 0), linear-gradient(90deg, #d4d4d4 1px, transparent 0);
              background-size: 32px 32px;
              background-color: #fffcf8;
          }

          .header {
              background-color: #fff;
              box-shadow: 0 0 5px rgba(0, 0, 0, .1);
              transition: background-color .5s;
          }
          .navbar {
              display: flex;
              width: 1280px;
              margin: auto;
              height: 40px;
          }
    
          .navbar .brand {
    
              display: flex;
              align-items: center;
              color: #555;
          }
    
          .brand .logo {
              max-width: 36px;
          }
    
          .brand .title {
              margin-left: 5px;
              font-family: helvetica neue, helvetica, arial, sans-serif;
              font-size: 24px;
              font-weight: 700;
          }
    
          .beta {
              color: #ccc;
              font-size: 11px;
              font-weight: 400;
              position: relative;
              top: -14px;
          }
    
          .category-list {
            list-style: none;
            display: flex;
            align-items: center;
        }
        
    .sites {
    width:1286px;
    background:;
    border:2px solid auto;
    margin:15px auto;
    padding:0px;
    text-align:center;
    }
    .sites1 {
    width:1286px;
    background:;
    border:2px solid auto;
    margin:15px auto;
    padding:0px;
    }
    
    .sites dl {
    height:36px;
    line-height:36px;
    display:block;
    margin:0;
    }
    
    .sites dl.alt {
    background:#d4d4d4;
    border-top:1px solid #ffffff;
    border-bottom:1px solid #ffffff;
    }
    
    .sites dl.alt2 {
    background:#d4d4d4;
    border-top:1px solid #ffffff;
    border-bottom:1px solid #ffffff;
    }
    
    .sites dt,.sites dd {
    text-align:center;
    display:block;
    float:left;
    }
    
    .sites dt {
    width:60px;
    }
    
    .sites dd {
    width:90px;
    margin:0;
    }
    .footer {
    width:580px;
    text-align:center;
    margin:5px auto;
    padding:2px;
    }    

    
    /* 固定元素样式 */
    .fixed-elements {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        background-color: #d4d4d4;
        z-index: 1000;
        padding: 10px;
        transition: background-color 0.3s ease;
        height: 130px;
    }
    
    .fixed-elements h3 {
        position: absolute;
        top: 10px;
        left: 20px;
        margin: 0;
    }
    
    /* 中心内容样式 */
    .center-content {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        max-width: 600px;
        text-align: center;
    }
    
    /* 管理员控制面板样式 */
    .admin-controls {
        position: fixed;
        top: 10px;
        right: 10px;
        font-size: 60%;
    }
    
    /* 添加/删除控制按钮样式 */
    .add-remove-controls {
        display: none;
        flex-direction: column;
        position: fixed;
        right: 20px;
        top: 50%;
        transform: translateY(-50%);
        align-items: center;
        gap: 10px;
    }
    
    .round-btn {
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
        margin: 5px 0;
    }
    
    .add-btn { order: 1; }
    .remove-btn { order: 2; }
    .category-btn { order: 3; }
    .remove-category-btn { order: 4; }
    
    /* 主要内容区域样式 */
    .content {
        margin-top: 140px;
        padding: 20px;
    }
    
    /* 搜索栏样式 */
    .search-container {
        margin-top: 10px;
    }
    
    .search-bar {
        display: flex;
        justify-content: center;
        margin-bottom: 10px;
    }
    
    .search-bar input {
        width: 50%;
        padding: 5px;
        border: 1px solid #ccc;
        border-radius: 5px 0 0 5px;
    }
    
    .search-bar button {
        padding: 5px 10px;
        border: 1px solid #ccc;
        border-left: none;
        background-color: #a0c9e5;
        border-radius: 0 5px 5px 0;
        cursor: pointer;
    }
    
    /* 搜索引擎按钮样式 */
    .search-engines {
        display: flex;
        justify-content: center;
        gap: 10px;
    }
    
    .search-engine {
        padding: 5px 10px;
        border: 1px solid #ccc;
        background-color: #f0f0;
        border-radius: 5px;
        cursor: pointer;
    }
    
    /* 主题切换按钮样式 */
    #theme-toggle {
        position: fixed;
        bottom: 50px;
        right: 20px;
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
    }

    /* 显示日志按钮样式 */
    #view-logs-btn {
        position: fixed;
        top: 100px; 
        right: 10px;
        z-index: 1000; 
    }    
    
    /* 对话框样式 */
    #dialog-overlay {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        justify-content: center;
        align-items: center;
    }
    
    #dialog-box {
        background-color: white;
        padding: 20px;
        border-radius: 5px;
        width: 300px;
    }
    
    #dialog-box input, #dialog-box select {
        width: 100%;
        margin-bottom: 10px;
        padding: 5px;
    }
    
    /* 分类和卡片样式 */
    .section {
        margin-bottom: 20px;
    }
    
    .section-title-container {
        display: flex;
        align-items: center;
        margin-bottom: 10px;
    }
    
    .section-title {
        font-size: 18px;
        font-weight: bold;
    }
    
    .delete-category-btn {
        background-color: pink;
        color: white;
        border: none;
        padding: 5px 10px;
        border-radius: 5px;
        cursor: pointer;
    }
    
    .card-container {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }
    
    .card {
        background-color: #a0c9e5;
        border-radius: 5px;
        padding: 10px;
        width: 132px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        cursor: pointer;
        transition: transform 0.2s;
        position: relative;
        user-select: none;
    }
    
    /* 中等屏幕 (平板等) */
@media (max-width: 1024px) {

}
/* 小屏幕 (手机) */
@media (max-width: 768px) {   

}

/* 超小屏幕 (更小手机) */
@media (max-width: 480px) {
    .card {
        padding: 6px; /* 进一步减小卡片内边距6 */
        width: 235px;
    }
}
    
    
    .card:hover {
        transform: translateY(-5px);
    }
    
    .card-top {
        display: flex;
        align-items: center;
        margin-bottom: 5px;
    }
    
    .card-icon {
        width: 20px;
        height: 20px;
        margin-right: 5px;
    }
    
    .card-title {
        font-size: 18px;
        font-weight: bold;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    
    .card-url {
        font-size: 12px;
        color: #666;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .private-tag {
        background-color: #ff9800;
        color: white;
        font-size: 10px;
        padding: 2px 5px;
        border-radius: 3px;
        position: absolute;
        top: 5px;
        right: 5px;
    }
    
    .delete-btn {
        position: absolute;
        top: -10px;
        right: -10px;
        background-color: red;
        color: white;
        border: none;
        border-radius: 50%;
        width: 20px;
        height: 20px;
        text-align: center;
        font-size: 14px;
        line-height: 20px;
        cursor: pointer;
        display: none;
    }
    
    /* 版权信息样式 */
    #copyright {
        position: fixed;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 40px;
        background-color: rgba(255, 255, 255, 0.8);
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 14px;
        z-index: 1000;
        box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
    }
    
    #copyright p {
        margin: 0;
    }
    
    #copyright a {
        color: #007bff;
        text-decoration: none;
    }
    
    #copyright a:hover {
        text-decoration: underline;
    }
    
    /* 响应式设计 */
    @media (max-width: 480px) {
        .fixed-elements {
            position: relative;
            padding: 5px;
        }
        .content {
            margin-top: 10px;
        }

        .admin-controls input,
        .admin-controls button {
            height: 30%;
        }

        .card-container {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
        }
        .card {
            width: 80%;
            max-width: 100%;
            padding: 5px;
        }
        .card-title {
            font-size: 12px;
            white-space: nowrap; 
            overflow: hidden; 
            text-overflow: ellipsis; 
            max-width: 130px; 
        }
        .card-url {
            font-size: 10px;
            white-space: nowrap; 
            overflow: hidden; 
            text-overflow: ellipsis; 
            max-width: 130px;
        }
        
        .add-remove-controls {
            right: 2px;
          }

        .round-btn, 
        #theme-toggle {
            right: 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 30px;
            height: 30px;
            font-size: 24px;
        }
    }
    </style>
    </head>
    <body class="background">
    
    </div>
    <div class="sites">  <!--  00   -->
    <header class="header">
      <nav class="navbar">
        <a href="https://www.199881.xyz/" class="brand">
          <img class="debug logo" src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
          <span class="debug title">WS01の主页</span>
          <span class="debug beta">beta</span>
          <!-- 一言模块 -->
          <p id="hitokoto">
              <a href="#" id="hitokoto_text"></a>
          </p>
          <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
          </a>
          </header>

          <!-- 搜索栏 -->
          <div class="search-container">
              <div class="search-bar">
                  <input type="text" id="search-input" placeholder="">
                  <button id="search-button">🔍</button>
              </div>
              <div class="search-engines">
                  <button class="search-engine" data-engine="baidu">百度</button>
                  <button class="search-engine" data-engine="bing">必应</button>
                  <button class="search-engine" data-engine="toutiao">头条</button>
                  <button class="search-engine" data-engine="sm">神马</button>
                  <button class="search-engine" data-engine="bilibili">哔哩</button>
                  <button class="search-engine" data-engine="github">github</button>
                  <button class="search-engine" data-engine="google">谷歌</button>
                  <button class="search-engine" data-engine="yandex">yandex</button>
              </div>
          </div>

    <div class="head">
        <!-- 添加/删除控制按钮 -->
        <div class="add-remove-controls">
            <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
            <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
            <button class="round-btn category-btn" onclick="addCategory()">C+</button>
            <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>

            </div>
            <div class="sites1">  <!--  00   -->
        <!-- 分类和卡片容器 -->
        <div id="sections-container"></div>
        <!-- 主题切换按钮 -->
        <button id="theme-toggle" onclick="toggleTheme()">◑</button>
        <!-- 显示日志按钮 用于调试-->
        <!--<button id="view-logs-btn" onclick="viewLogs()">显示日志</button>-->
        <!-- 添加链接对话框 -->
        <div id="dialog-overlay">
            <div id="dialog-box">
                <label for="name-input">名称</label>
                <input type="text" id="name-input">
                <label for="url-input">地址</label>
                <input type="text" id="url-input">
                <label for="category-select">选择分类</label>
                <select id="category-select"></select>
                <div class="private-link-container">
                    <label for="private-checkbox">私密链接</label>    
                    <input type="checkbox" id="private-checkbox">
                </div>
                <button onclick="addLink()">确定</button>
                <button onclick="hideAddDialog()">取消</button>
            </div>
        </div>

    <br />
    <!-- body   页脚    -->
    <div class="footer">
    <!-- 管理员控制面板 -->
    <input type="password" id="admin-password" placeholder="输入密码">
    <button id="admin-mode-btn" onclick="toggleAdminMode()">设  置</button>
    <button id="secret-garden-btn" onclick="toggleSecretGarden()">登  录</button>

    <br />
    <br />    <br />
    <!-- 版权信息 -->

    <div id="copyright" class="copyright">
        <!--请不要删除-->
        <p><a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 项目
        <!-- 开站时间开始 -->       
        <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span> <script language="javascript"> 
var now = new Date();
function createtime(){
   var grt= new Date("09/05/2024 00:00:00");/*---这里是网站的启用时间--*/
   now.setTime(now.getTime()+250);
   days = (now - grt ) / 1000 / 60 / 60 / 24;
   dnum = Math.floor(days);
   hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
   hnum = Math.floor(hours);
   if(String(hnum).length ==1 ){hnum = "0" + hnum;}
   minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
   mnum = Math.floor(minutes);
   if(String(mnum).length ==1 ){mnum = "0" + mnum;}
   seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
   snum = Math.round(seconds);
   if(String(snum).length ==1 ){snum = "0" + snum;}
   document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天";
   document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
}
setInterval("createtime()",250); 
</script> 
<!-- 开站时间结束 -->      
<!-- <script defer src="https://four-root-occupation.glitch.me/denglong.js"></script> 国庆快乐-->
        
        </p>
    </div>
</div>

    <script>
    // 搜索引擎配置
    const searchEngines = {
        baidu: "https://www.baidu.com/s?wd=",
        bing: "https://www.bing.com/search?q=",
        sm: "https://m.sm.cn/s?q=",
        toutiao: "https://so.toutiao.com/search?dvpf=pc&source=trending_card&keyword=",
        bilibili: "https://search.bilibili.com/all?keyword=",
        github: "https://github.com/search?q=",
        google: "https://www.google.com/search?q=",
        yandex: "https://www.yandex.com/search/?text="
    };
    
    let currentEngine = "baidu";
    
    // 日志记录函数
    function logAction(action, details) {
        const timestamp = new Date().toISOString();
        const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
        
        let logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
        logs.push(logEntry);
        
        // 保留最新的1000条日志
        if (logs.length > 1000) {
            logs = logs.slice(-1000);
        }
        
        localStorage.setItem('cardTabLogs', JSON.stringify(logs));
        console.log(logEntry); // 同时在控制台输出日志
    }
    
    // 查看日志的函数
    function viewLogs() {
        const logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
        console.log('Card Tab Logs:');
        logs.forEach(log => console.log(log));
        alert('日志已在控制台输出,请按F12打开开发者工具查看。');
    }
    
    // 设置当前搜索引擎
    function setActiveEngine(engine) {
        currentEngine = engine;
        document.querySelectorAll('.search-engine').forEach(btn => {
            btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
        });
        logAction('设置搜索引擎', { engine });
    }
    
    // 搜索引擎按钮点击事件
    document.querySelectorAll('.search-engine').forEach(button => {
        button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
    });
    
    // 搜索按钮点击事件
    document.getElementById('search-button').addEventListener('click', () => {
        const query = document.getElementById('search-input').value;
        if (query) {
            logAction('执行搜索', { engine: currentEngine, query });
            window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
        }
    });
    
    // 搜索输入框回车事件
    document.getElementById('search-input').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('search-button').click();
        }
    });
    
    // 初始化搜索引擎
    setActiveEngine(currentEngine);
    
    // 全局变量
    let publicLinks = [];
    let privateLinks = [];
    let isAdmin = false;
    let isLoggedIn = false;
    let removeMode = false;
    let isRemoveCategoryMode = false;
    let isDarkTheme = false;
    let links = [];
    const categories = {};
    
    // 添加新分类
    function addCategory() {
        const categoryName = prompt('请输入新分类名称:');
        if (categoryName && !categories[categoryName]) {
            categories[categoryName] = [];
            updateCategorySelect();
            renderCategories();
            saveLinks();
            logAction('添加分类', { categoryName, currentLinkCount: links.length });
        } else if (categories[categoryName]) {
            alert('该分类已存在');
            logAction('添加分类失败', { categoryName, reason: '分类已存在' });
        }
    }

    // 删除分类
    function deleteCategory(category) {
        if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
            delete categories[category];
            links = links.filter(link => link.category !== category);
            publicLinks = publicLinks.filter(link => link.category !== category);
            privateLinks = privateLinks.filter(link => link.category !== category);
            updateCategorySelect();
            saveLinks();
            renderCategories();
            logAction('删除分类', { category });
        }
    }    

    // 渲染分类(不重新加载链接)
    function renderCategories() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            container.appendChild(section);
    
            const categoryLinks = links.filter(link => link.category === category);
            categoryLinks.forEach(link => {
                createCard(link, cardContainer);
            });
        });
    
        logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
    }    
    
    // 读取链接数据
    async function loadLinks() {
        const response = await fetch('/api/getLinks?userId=testUser');
        const data = await response.json();
        if (data.categories) {
            Object.assign(categories, data.categories);
        }
        
        publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
        privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
        links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
    
        loadSections();
        updateCategorySelect();
        updateUIState();
        logAction('读取链接', { publicCount: publicLinks.length, privateCount: privateLinks.length });
    }
    
    
    // 更新UI状态
    function updateUIState() {
        const passwordInput = document.getElementById('admin-password');
        const adminBtn = document.getElementById('admin-mode-btn');
        const secretGardenBtn = document.getElementById('secret-garden-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
        secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
        secretGardenBtn.style.display = 'inline-block';
    
        if (isAdmin) {
            adminBtn.textContent = "离开设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'flex';
        } else if (isLoggedIn) {
            adminBtn.textContent = "设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'none';
        } else {
            adminBtn.style.display = 'none';
            addRemoveControls.style.display = 'none';
        }
    
        logAction('更新UI状态', { isAdmin, isLoggedIn });
    }
    
    // 登录状态显示(加载所有链接)
    function showSecretGarden() {
        if (isLoggedIn) {
            links = [...publicLinks, ...privateLinks];
            loadSections();
            // 显示所有私密标签
            document.querySelectorAll('.private-tag').forEach(tag => {
                tag.style.display = 'block';
            });
            logAction('显示私密花园');
        }
    }
    
    // 加载分类和链接
    function loadSections() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            let privateCount = 0;
            let linkCount = 0;
    
            links.forEach(link => {
                if (link.category === category) {
                    if (link.isPrivate) privateCount++;
                    linkCount++;
                    createCard(link, cardContainer);
                }
            });
    
            if (privateCount < linkCount || isLoggedIn) {
                container.appendChild(section);
            }
        });
    
        logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
    }
    
    // 创建卡片
    function createCard(link, container) {
        const card = document.createElement('div');
        card.className = 'card';
        card.setAttribute('draggable', isAdmin);
        card.dataset.isPrivate = link.isPrivate;
    
        const cardTop = document.createElement('div');
        cardTop.className = 'card-top';
    
        // const icon = document.createElement('img');
        // icon.className = 'card-icon';
        // icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
        // icon.alt = 'Website Icon';
        // 定义默认的 SVG 图标
        const defaultIconSVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
        '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>' +
        '<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>' +
        '</svg>';
        
        // 创建图标元素
        const icon = document.createElement('img');
        icon.className = 'card-icon';
        // icon.src = 'https://api.iowen.cn/favicon/' + extractDomain(link.url) + '.png';
        icon.src = 'https://www.faviconextractor.com/favicon/' + extractDomain(link.url);
        icon.alt = 'Website Icon';
        
        // 错误处理:如果图片加载失败,使用默认的 SVG 图标
        icon.onerror = function() {
            const svgBlob = new Blob([defaultIconSVG], {type: 'image/svg+xml'});
            const svgUrl = URL.createObjectURL(svgBlob);
            this.src = svgUrl;
            
            // 清理:当图片不再需要时,撤销对象 URL
            this.onload = () => URL.revokeObjectURL(svgUrl);
        };
        
        // 辅助函数:从 URL 中提取域名
        function extractDomain(url) {
            let domain;
            try {
                domain = new URL(url).hostname;
            } catch (e) {
                // 如果 URL 无效,返回原始输入
                domain = url;
            }
            return domain;
        }
    
        const title = document.createElement('div');
        title.className = 'card-title';
        title.textContent = link.name;
    
        cardTop.appendChild(icon);
        cardTop.appendChild(title);
    
        const url = document.createElement('div');
        url.className = 'card-url';
        url.textContent = link.url;
    
        card.appendChild(cardTop);
        card.appendChild(url);
    
        if (link.isPrivate) {
            const privateTag = document.createElement('div');
            privateTag.className = 'private-tag';
            privateTag.textContent = '私密';
            card.appendChild(privateTag);
        }
    
        const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
    
        if (!isAdmin) {
            card.addEventListener('click', () => {
                window.open(correctedUrl, '_blank');
                logAction('打开链接', { name: link.name, url: correctedUrl });
            });
        }
    
        const deleteBtn = document.createElement('button');
        deleteBtn.textContent = '–';
        deleteBtn.className = 'delete-btn';
        deleteBtn.onclick = function (event) {
            event.stopPropagation();
            removeCard(card);
        };
        card.appendChild(deleteBtn);
    
        updateCardStyle(card);
    
        card.addEventListener('dragstart', dragStart);
        card.addEventListener('dragover', dragOver);
        card.addEventListener('dragend', dragEnd);
        card.addEventListener('drop', drop);
        card.addEventListener('touchstart', touchStart, { passive: false });
    
        if (isAdmin && removeMode) {
            deleteBtn.style.display = 'block';
        }
    
        if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
            container.appendChild(card);
        }
        // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
    }
    
    // 更新卡片样式
    function updateCardStyle(card) {
        if (isDarkTheme) {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        } else {
            card.style.backgroundColor = '#a0c9e5';
            card.style.color = '#333';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
        }
    }
    
    // 更新分类选择下拉框
    function updateCategorySelect() {
        const categorySelect = document.getElementById('category-select');
        categorySelect.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const option = document.createElement('option');
            option.value = category;
            option.textContent = category;
            categorySelect.appendChild(option);
        });
    
        logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
    }
    
    // 保存链接数据
    async function saveLinks() {
        let allLinks = [...publicLinks, ...privateLinks];
    
        try {
            await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: allLinks,
                    categories: categories
                }),
            });
            logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
        } catch (error) {
            console.error('Error saving links:', error);
            logAction('保存链接失败', { error: error.message });
            alert('保存链接失败,请重试');
        }
    }
    
    // 添加卡片弹窗
    function addLink() {
        const name = document.getElementById('name-input').value;
        const url = document.getElementById('url-input').value;
        const category = document.getElementById('category-select').value;
        const isPrivate = document.getElementById('private-checkbox').checked;
    
        if (name && url && category) {
            const newLink = { name, url, category, isPrivate };
    
            if (isPrivate) {
                privateLinks.push(newLink);
            } else {
                publicLinks.push(newLink);
            }
    
            links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
    
            if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
                const container = document.getElementById(category);
                if (container) {
                    createCard(newLink, container);
                } else {
                    categories[category] = [];
                    renderCategories();
                }
            }
    
            saveLinks();
    
            document.getElementById('name-input').value = '';
            document.getElementById('url-input').value = '';
            document.getElementById('private-checkbox').checked = false;
            hideAddDialog();

            logAction('添加卡片', { name, url, category, isPrivate });
        }
    }

    // 删除卡片
    function removeCard(card) {
        const name = card.querySelector('.card-title').textContent;
        const url = card.querySelector('.card-url').textContent;
        const isPrivate = card.dataset.isPrivate === 'true';
        
        links = links.filter(link => link.url !== url);
        if (isPrivate) {
            privateLinks = privateLinks.filter(link => link.url !== url);
        } else {
            publicLinks = publicLinks.filter(link => link.url !== url);
        }
    
        for (const key in categories) {
            categories[key] = categories[key].filter(link => link.url !== url);
        }
    
        card.remove();
    
        saveLinks();
    
        logAction('删除卡片', { name, url, isPrivate });
    }
    
    // 拖拽卡片
    let draggedCard = null;
    let touchStartX, touchStartY;
    
    // 触屏端拖拽卡片
    function touchStart(event) {
        if (!isAdmin) return;
        draggedCard = event.target.closest('.card');
        if (!draggedCard) return;
    
        event.preventDefault();
        const touch = event.touches[0];
        touchStartX = touch.clientX;
        touchStartY = touch.clientY;
    
        draggedCard.classList.add('dragging');
        
        document.addEventListener('touchmove', touchMove, { passive: false });
        document.addEventListener('touchend', touchEnd);
    
    }
    
    function touchMove(event) {
        if (!draggedCard) return;
        event.preventDefault();
    
        const touch = event.touches[0];
        const currentX = touch.clientX;
        const currentY = touch.clientY;

        const deltaX = currentX - touchStartX;
        const deltaY = currentY - touchStartY;
        draggedCard.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
    
        const target = findCardUnderTouch(currentX, currentY);
        if (target && target !== draggedCard) {
            const container = target.parentElement;
            const targetRect = target.getBoundingClientRect();
    
            if (currentX < targetRect.left + targetRect.width / 2) {
                container.insertBefore(draggedCard, target);
            } else {
                container.insertBefore(draggedCard, target.nextSibling);
            }
        }
    }
    
    function touchEnd(event) {
        if (!draggedCard) return;
    
        document.removeEventListener('touchmove', touchMove);
        document.removeEventListener('touchend', touchEnd);
    
        draggedCard.classList.remove('dragging');
        draggedCard.style.transform = '';
    
        const targetCategory = draggedCard.closest('.card-container').id;
        updateCardCategory(draggedCard, targetCategory);
        saveCardOrder();
    
        draggedCard = null;
    }
    
    function findCardUnderTouch(x, y) {
        const cards = document.querySelectorAll('.card:not(.dragging)');
        return Array.from(cards).find(card => {
            const rect = card.getBoundingClientRect();
            return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
        });
    }

    // PC端拖拽卡片
    function dragStart(event) {
        if (!isAdmin) return;
        draggedCard = event.target.closest('.card');
        if (!draggedCard) return;
    
        draggedCard.classList.add('dragging');
        event.dataTransfer.effectAllowed = "move";
        logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
    }
    
    function dragOver(event) {
        if (!isAdmin || !draggedCard) return;
        event.preventDefault();
        const target = event.target.closest('.card');
        if (target && target !== draggedCard) {
            const container = target.parentElement;
            const mousePositionX = event.clientX;
            const targetRect = target.getBoundingClientRect();
    
            if (mousePositionX < targetRect.left + targetRect.width / 2) {
                container.insertBefore(draggedCard, target);
            } else {
                container.insertBefore(draggedCard, target.nextSibling);
            }
        }
    }
    
    function drop(event) {
        if (!isAdmin || !draggedCard) return;
        event.preventDefault();
        draggedCard.classList.remove('dragging');
        const targetCategory = event.target.closest('.card-container').id;
        updateCardCategory(draggedCard, targetCategory);
        logAction('放下卡片', { name: draggedCard.querySelector('.card-title').textContent, category: targetCategory });
        draggedCard = null;
        saveCardOrder();
    }
    
    function dragEnd(event) {
        if (draggedCard) {
            draggedCard.classList.remove('dragging');
            logAction('拖拽卡片结束');
            draggedCard = null;
        }
    }
  
    // 更新卡片分类
    function updateCardCategory(card, newCategory) {
        const cardTitle = card.querySelector('.card-title').textContent;
        const cardUrl = card.querySelector('.card-url').textContent;
        const isPrivate = card.dataset.isPrivate === 'true';
    
        const linkIndex = links.findIndex(link => link.url === cardUrl);
        if (linkIndex !== -1) {
            links[linkIndex].category = newCategory;
        }
    
        const linkArray = isPrivate ? privateLinks : publicLinks;
        const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
        if (arrayIndex !== -1) {
            linkArray[arrayIndex].category = newCategory;
        }
    
        card.dataset.category = newCategory;
    }

    // 在页面加载完成后添加触摸事件监听器
    document.addEventListener('DOMContentLoaded', function() {
        const cardContainers = document.querySelectorAll('.card-container');
        cardContainers.forEach(container => {
            container.addEventListener('touchstart', touchStart, { passive: false });
        });
    });    
    
    // 保存卡片顺序
    async function saveCardOrder() {
        if (!isAdmin) return;
        const containers = document.querySelectorAll('.card-container');
        let newPublicLinks = [];
        let newPrivateLinks = [];
        let newCategories = {};
    
        containers.forEach(container => {
            const category = container.id;
            newCategories[category] = [];
    
            [...container.children].forEach(card => {
                const url = card.querySelector('.card-url').textContent;
                const name = card.querySelector('.card-title').textContent;
                const isPrivate = card.dataset.isPrivate === 'true';
                card.dataset.category = category;
                const link = { name, url, category, isPrivate };
                if (isPrivate) {
                    newPrivateLinks.push(link);
                } else {
                    newPublicLinks.push(link);
                }
                newCategories[category].push(link); 
            });
        });
    
        publicLinks.length = 0;
        publicLinks.push(...newPublicLinks);
        privateLinks.length = 0;
        privateLinks.push(...newPrivateLinks);
        Object.keys(categories).forEach(key => delete categories[key]);
        Object.assign(categories, newCategories);
    
        logAction('保存卡片顺序', { 
            publicCount: newPublicLinks.length, 
            privateCount: newPrivateLinks.length, 
            categoryCount: Object.keys(newCategories).length 
        });
    
        try {
            const response = await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: [...newPublicLinks, ...newPrivateLinks],
                    categories: newCategories
                }),
            });
            const result = await response.json();
            if (!result.success) {
                throw new Error('Failed to save order');
            }
            logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
        } catch (error) {
            console.error('Error saving order:', error);
            logAction('保存顺序失败', { error: error.message });
            alert('保存顺序失败,请重试');
        }
    }             
    
    // 设置状态重新加载卡片
    function reloadCardsAsAdmin() {
        document.querySelectorAll('.card-container').forEach(container => {
            container.innerHTML = '';
        });
        loadLinks().then(() => {
            if (isDarkTheme) {
                applyDarkTheme();
            }
        });
        logAction('重新加载卡片(管理员模式)');
    }
    
    // 密码输入框回车事件
    document.getElementById('admin-password').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            toggleSecretGarden();
        }
    });
    
    // 切换设置状态
    function toggleAdminMode() {
        const adminBtn = document.getElementById('admin-mode-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        if (!isAdmin && isLoggedIn) {
            isAdmin = true;
            adminBtn.textContent = "退出设置";
            addRemoveControls.style.display = 'flex';
            alert('准备设置分类和书签');
            reloadCardsAsAdmin();
            logAction('进入设置');
        } else if (isAdmin) {
            isAdmin = false;
            removeMode = false;
            adminBtn.textContent = "设  置";
            addRemoveControls.style.display = 'none';
            alert('设置已保存');
            reloadCardsAsAdmin();
            logAction('离开设置');
        }
    
        updateUIState();
    }      
    
    // 切换到登录状态
    function toggleSecretGarden() {
        const passwordInput = document.getElementById('admin-password');
        if (!isLoggedIn) {
            verifyPassword(passwordInput.value).then(isValid => {
                if (isValid) {
                    isLoggedIn = true;
                    links = [...publicLinks, ...privateLinks];
                    loadSections();
                    alert('登录成功!');
                    logAction('登录成功');
                } else {
                    alert('密码错误');
                    logAction('登录失败', { reason: '密码错误' });
                }
                updateUIState();
            });
        } else {
            isLoggedIn = false;
            isAdmin = false;
            links = publicLinks;
            loadSections();
            alert('退出登录!');
            updateUIState();
            passwordInput.value = '';
            logAction('退出登录');
        }
    }
    
    // 应用暗色主题
    function applyDarkTheme() {
        document.body.style.backgroundColor = '#121212';
        document.body.style.color = '#ffffff';
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        });
        logAction('应用暗色主题');
    }
    
    // 显示添加链接对话框
    function showAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'flex';
        logAction('显示添加链接对话框');
    }
    
    // 隐藏添加链接对话框
    function hideAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'none';
        logAction('隐藏添加链接对话框');
    }
    
    // 切换删除卡片模式
    function toggleRemoveMode() {
        removeMode = !removeMode;
        const deleteButtons = document.querySelectorAll('.delete-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = removeMode ? 'block' : 'none';
        });
        logAction('切换删除卡片模式', { removeMode });
    }
    
    //切换删除分类模式
    function toggleRemoveCategory() {
        isRemoveCategoryMode = !isRemoveCategoryMode;
        const deleteButtons = document.querySelectorAll('.delete-category-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
        });
        logAction('切换删除分类模式', { isRemoveCategoryMode });
    }
    
    // 切换主题
    function toggleTheme() {
        isDarkTheme = !isDarkTheme;
    
        document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
        document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
            card.style.color = isDarkTheme ? '#ffffff' : '#333';
            card.style.boxShadow = isDarkTheme
                ? '0 4px 8px rgba(0, 0, 0, 0.5)'
                : '0 4px 8px rgba(0, 0, 0, 0.1)';
        });
    
        const fixedElements = document.querySelectorAll('.fixed-elements');
        fixedElements.forEach(element => {
            element.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
            element.style.color = isDarkTheme ? '#ffffff' : '#333';
        });
    
        const dialogBox = document.getElementById('dialog-box');
        dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
        dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
        inputs.forEach(input => {
            input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
            input.style.color = isDarkTheme ? '#fff' : '#333';
            input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
        });
        
        logAction('切换主题', { isDarkTheme });
    }
    
    // 验证密码
    async function verifyPassword(inputPassword) {
        const response = await fetch('/api/verifyPassword', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ password: inputPassword }),
        });
        const result = await response.json();
        return result.valid;
    }
    
    // 初始化加载链接
    loadLinks();

    </script>
</body>

</html>
`;

export default {
async fetch(request, env) {
const url = new URL(request.url);

if (url.pathname === '/') {
return new Response(HTML_CONTENT, {
headers: { 'Content-Type': 'text/html' }
});
}

if (url.pathname === '/api/getLinks') {
const userId = url.searchParams.get('userId');
const links = await env.CARD_ORDER.get(userId); 
return new Response(links || JSON.stringify([]), { status: 200 });
}

if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
    const { userId, links, categories } = await request.json(); 
    await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories })); //保存链接和分类
    return new Response(JSON.stringify({ success: true }), { status: 200 });
}

if (url.pathname === '/api/verifyPassword' && request.method === 'POST') { 
const { password } = await request.json();
const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
return new Response(JSON.stringify({ valid: isValid }), {
status: isValid ? 200 : 403,
headers: { 'Content-Type': 'application/json' },
});
}

return new Response('Not Found', { status: 404 });
}
};

2024.09.09 更新:
1)、增加私密书签,登录后可见
2)、增加网站分类管理,现在你无需编辑代码,通过页面即可进行网站分类的添加和删除操作
3)、增加搜索框和一言接口
1、原workes, 效果

const HTML_CONTENT = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Card Tab</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2280%22>⭐</text></svg>">
    <style>
    /* 全局样式 */
    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        background-color: #d8eac4;
        transition: background-color 0.3s ease;
    }
    
    /* 固定元素样式 */
    .fixed-elements {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        background-color: #d8eac4;
        z-index: 1000;
        padding: 10px;
        transition: background-color 0.3s ease;
        height: 130px;
    }
    
    .fixed-elements h3 {
        position: absolute;
        top: 10px;
        left: 20px;
        margin: 0;
    }
    
    /* 中心内容样式 */
    .center-content {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        max-width: 600px;
        text-align: center;
    }
    
    /* 管理员控制面板样式 */
    .admin-controls {
        position: fixed;
        top: 10px;
        right: 10px;
        font-size: 60%;
    }
    
    /* 添加/删除控制按钮样式 */
    .add-remove-controls {
        display: none;
        flex-direction: column;
        position: fixed;
        right: 20px;
        top: 50%;
        transform: translateY(-50%);
        align-items: center;
        gap: 10px;
    }
    
    .round-btn {
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
        margin: 5px 0;
    }
    
    .add-btn { order: 1; }
    .remove-btn { order: 2; }
    .category-btn { order: 3; }
    .remove-category-btn { order: 4; }
    
    /* 主要内容区域样式 */
    .content {
        margin-top: 140px;
        padding: 20px;
    }
    
    /* 搜索栏样式 */
    .search-container {
        margin-top: 10px;
    }
    
    .search-bar {
        display: flex;
        justify-content: center;
        margin-bottom: 10px;
    }
    
    .search-bar input {
        width: 70%;
        padding: 5px;
        border: 1px solid #ccc;
        border-radius: 5px 0 0 5px;
    }
    
    .search-bar button {
        padding: 5px 10px;
        border: 1px solid #ccc;
        border-left: none;
        background-color: #f8f8;
        border-radius: 0 5px 5px 0;
        cursor: pointer;
    }
    
    /* 搜索引擎按钮样式 */
    .search-engines {
        display: flex;
        justify-content: center;
        gap: 10px;
    }
    
    .search-engine {
        padding: 5px 10px;
        border: 1px solid #ccc;
        background-color: #f0f0;
        border-radius: 5px;
        cursor: pointer;
    }
    
    /* 主题切换按钮样式 */
    #theme-toggle {
        position: fixed;
        bottom: 50px;
        right: 20px;
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
    }

    /* 显示日志按钮样式 */
    #view-logs-btn {
        position: fixed;
        top: 100px; 
        right: 10px;
        z-index: 1000; 
    }    
    
    /* 对话框样式 */
    #dialog-overlay {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        justify-content: center;
        align-items: center;
    }
    
    #dialog-box {
        background-color: white;
        padding: 20px;
        border-radius: 5px;
        width: 300px;
    }
    
    #dialog-box input, #dialog-box select {
        width: 100%;
        margin-bottom: 10px;
        padding: 5px;
    }
    
    /* 分类和卡片样式 */
    .section {
        margin-bottom: 20px;
    }
    
    .section-title-container {
        display: flex;
        align-items: center;
        margin-bottom: 10px;
    }
    
    .section-title {
        font-size: 18px;
        font-weight: bold;
    }
    
    .delete-category-btn {
        background-color: pink;
        color: white;
        border: none;
        padding: 5px 10px;
        border-radius: 5px;
        cursor: pointer;
    }
    
    .card-container {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }
    
    .card {
        background-color: #a0c9e5;
        border-radius: 5px;
        padding: 10px;
        width: 150px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        cursor: pointer;
        transition: transform 0.2s;
        position: relative;
        user-select: none;
    }
    
    .card:hover {
        transform: translateY(-5px);
    }
    
    .card-top {
        display: flex;
        align-items: center;
        margin-bottom: 5px;
    }
    
    .card-icon {
        width: 16px;
        height: 16px;
        margin-right: 5px;
    }
    
    .card-title {
        font-size: 14px;
        font-weight: bold;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    
    .card-url {
        font-size: 12px;
        color: #666;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .private-tag {
        background-color: #ff9800;
        color: white;
        font-size: 10px;
        padding: 2px 5px;
        border-radius: 3px;
        position: absolute;
        top: 5px;
        right: 5px;
    }
    
    .delete-btn {
        position: absolute;
        top: -10px;
        right: -10px;
        background-color: red;
        color: white;
        border: none;
        border-radius: 50%;
        width: 20px;
        height: 20px;
        text-align: center;
        font-size: 14px;
        line-height: 20px;
        cursor: pointer;
        display: none;
    }
    
    /* 版权信息样式 */
    #copyright {
        position: fixed;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 40px;
        background-color: rgba(255, 255, 255, 0.8);
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 14px;
        z-index: 1000;
        box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
    }
    
    #copyright p {
        margin: 0;
    }
    
    #copyright a {
        color: #007bff;
        text-decoration: none;
    }
    
    #copyright a:hover {
        text-decoration: underline;
    }
    
    /* 响应式设计 */
    @media (max-width: 480px) {
        .fixed-elements {
            position: relative;
            padding: 5px;
        }
        .content {
            margin-top: 10px;
        }

        .admin-controls input,
        .admin-controls button {
            height: 30%;
        }

        .card-container {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
        }
        .card {
            width: 80%;
            max-width: 100%;
            padding: 5px;
        }
        .card-title {
            font-size: 12px;
            white-space: nowrap; 
            overflow: hidden; 
            text-overflow: ellipsis; 
            max-width: 130px; 
        }
        .card-url {
            font-size: 10px;
            white-space: nowrap; 
            overflow: hidden; 
            text-overflow: ellipsis; 
            max-width: 130px;
        }
        
        .add-remove-controls {
            right: 2px;
          }

        .round-btn, 
        #theme-toggle {
            right: 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 30px;
            height: 30px;
            font-size: 24px;
        }
    }
    </style>
</head>

<body>
    <div class="fixed-elements">
        <h3>我的导航</h3>
        <div class="center-content">
            <!-- 一言模块 -->
            <p id="hitokoto">
                <a href="#" id="hitokoto_text"></a>
            </p>
            <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
            <!-- 搜索栏 -->
            <div class="search-container">
                <div class="search-bar">
                    <input type="text" id="search-input" placeholder="">
                    <button id="search-button">🔍</button>
                </div>
                <div class="search-engines">
                    <button class="search-engine" data-engine="baidu">百度</button>
                    <button class="search-engine" data-engine="bing">必应</button>
                    <button class="search-engine" data-engine="google">谷歌</button>
                </div>
            </div>
        </div>
        <!-- 管理员控制面板 -->
        <div class="admin-controls">
            <input type="password" id="admin-password" placeholder="输入密码">
            <button id="admin-mode-btn" onclick="toggleAdminMode()">设  置</button>
            <button id="secret-garden-btn" onclick="toggleSecretGarden()">登  录</button>
        </div>
    </div>
    <div class="content">
        <!-- 添加/删除控制按钮 -->
        <div class="add-remove-controls">
            <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
            <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
            <button class="round-btn category-btn" onclick="addCategory()">C+</button>
            <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>

        </div>

        <!-- 分类和卡片容器 -->
        <div id="sections-container"></div>
        <!-- 主题切换按钮 -->
        <button id="theme-toggle" onclick="toggleTheme()">◑</button>
        <!-- 显示日志按钮 用于调试-->
        <!--<button id="view-logs-btn" onclick="viewLogs()">显示日志</button>-->
        <!-- 添加链接对话框 -->
        <div id="dialog-overlay">
            <div id="dialog-box">
                <label for="name-input">名称</label>
                <input type="text" id="name-input">
                <label for="url-input">地址</label>
                <input type="text" id="url-input">
                <label for="category-select">选择分类</label>
                <select id="category-select"></select>
                <div class="private-link-container">
                    <label for="private-checkbox">私密链接</label>    
                    <input type="checkbox" id="private-checkbox">
                </div>
                <button onclick="addLink()">确定</button>
                <button onclick="hideAddDialog()">取消</button>
            </div>
        </div>
        <!-- 版权信息 -->
        <div id="copyright" class="copyright">
            <!--请不要删除-->
            <p>项目地址:<a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 如果喜欢,烦请点个star!</p>
        </div>
    </div>

    <script>
    // 搜索引擎配置
    const searchEngines = {
        baidu: "https://www.baidu.com/s?wd=",
        bing: "https://www.bing.com/search?q=",
        google: "https://www.google.com/search?q="
    };
    
    let currentEngine = "baidu";
    
    // 日志记录函数
    function logAction(action, details) {
        const timestamp = new Date().toISOString();
        const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
        
        let logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
        logs.push(logEntry);
        
        // 保留最新的1000条日志
        if (logs.length > 1000) {
            logs = logs.slice(-1000);
        }
        
        localStorage.setItem('cardTabLogs', JSON.stringify(logs));
        console.log(logEntry); // 同时在控制台输出日志
    }
    
    // 查看日志的函数
    function viewLogs() {
        const logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
        console.log('Card Tab Logs:');
        logs.forEach(log => console.log(log));
        alert('日志已在控制台输出,请按F12打开开发者工具查看。');
    }
    
    // 设置当前搜索引擎
    function setActiveEngine(engine) {
        currentEngine = engine;
        document.querySelectorAll('.search-engine').forEach(btn => {
            btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
        });
        logAction('设置搜索引擎', { engine });
    }
    
    // 搜索引擎按钮点击事件
    document.querySelectorAll('.search-engine').forEach(button => {
        button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
    });
    
    // 搜索按钮点击事件
    document.getElementById('search-button').addEventListener('click', () => {
        const query = document.getElementById('search-input').value;
        if (query) {
            logAction('执行搜索', { engine: currentEngine, query });
            window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
        }
    });
    
    // 搜索输入框回车事件
    document.getElementById('search-input').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('search-button').click();
        }
    });
    
    // 初始化搜索引擎
    setActiveEngine(currentEngine);
    
    // 全局变量
    let publicLinks = [];
    let privateLinks = [];
    let isAdmin = false;
    let isLoggedIn = false;
    let removeMode = false;
    let isRemoveCategoryMode = false;
    let isDarkTheme = false;
    let links = [];
    const categories = {};
    
    // 添加新分类
    function addCategory() {
        const categoryName = prompt('请输入新分类名称:');
        if (categoryName && !categories[categoryName]) {
            categories[categoryName] = [];
            updateCategorySelect();
            renderCategories();
            saveLinks();
            logAction('添加分类', { categoryName, currentLinkCount: links.length });
        } else if (categories[categoryName]) {
            alert('该分类已存在');
            logAction('添加分类失败', { categoryName, reason: '分类已存在' });
        }
    }

    // 删除分类
    function deleteCategory(category) {
        if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
            delete categories[category];
            links = links.filter(link => link.category !== category);
            publicLinks = publicLinks.filter(link => link.category !== category);
            privateLinks = privateLinks.filter(link => link.category !== category);
            updateCategorySelect();
            saveLinks();
            renderCategories();
            logAction('删除分类', { category });
        }
    }    

    // 渲染分类(不重新加载链接)
    function renderCategories() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            container.appendChild(section);
    
            const categoryLinks = links.filter(link => link.category === category);
            categoryLinks.forEach(link => {
                createCard(link, cardContainer);
            });
        });
    
        logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
    }    
    
    // 读取链接数据
    async function loadLinks() {
        const response = await fetch('/api/getLinks?userId=testUser');
        const data = await response.json();
        if (data.categories) {
            Object.assign(categories, data.categories);
        }
        
        publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
        privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
        links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
    
        loadSections();
        updateCategorySelect();
        updateUIState();
        logAction('读取链接', { publicCount: publicLinks.length, privateCount: privateLinks.length });
    }
    
    
    // 更新UI状态
    function updateUIState() {
        const passwordInput = document.getElementById('admin-password');
        const adminBtn = document.getElementById('admin-mode-btn');
        const secretGardenBtn = document.getElementById('secret-garden-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
        secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
        secretGardenBtn.style.display = 'inline-block';
    
        if (isAdmin) {
            adminBtn.textContent = "离开设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'flex';
        } else if (isLoggedIn) {
            adminBtn.textContent = "设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'none';
        } else {
            adminBtn.style.display = 'none';
            addRemoveControls.style.display = 'none';
        }
    
        logAction('更新UI状态', { isAdmin, isLoggedIn });
    }
    
    // 登录状态显示(加载所有链接)
    function showSecretGarden() {
        if (isLoggedIn) {
            links = [...publicLinks, ...privateLinks];
            loadSections();
            // 显示所有私密标签
            document.querySelectorAll('.private-tag').forEach(tag => {
                tag.style.display = 'block';
            });
            logAction('显示私密花园');
        }
    }
    
    // 加载分类和链接
    function loadSections() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            let privateCount = 0;
            let linkCount = 0;
    
            links.forEach(link => {
                if (link.category === category) {
                    if (link.isPrivate) privateCount++;
                    linkCount++;
                    createCard(link, cardContainer);
                }
            });
    
            if (privateCount < linkCount || isLoggedIn) {
                container.appendChild(section);
            }
        });
    
        logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
    }
    
    // 创建卡片
    function createCard(link, container) {
        const card = document.createElement('div');
        card.className = 'card';
        card.setAttribute('draggable', isAdmin);
        card.dataset.isPrivate = link.isPrivate;
    
        const cardTop = document.createElement('div');
        cardTop.className = 'card-top';
    
        const icon = document.createElement('img');
        icon.className = 'card-icon';
        icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
        icon.alt = 'Website Icon';
    
        const title = document.createElement('div');
        title.className = 'card-title';
        title.textContent = link.name;
    
        cardTop.appendChild(icon);
        cardTop.appendChild(title);
    
        const url = document.createElement('div');
        url.className = 'card-url';
        url.textContent = link.url;
    
        card.appendChild(cardTop);
        card.appendChild(url);
    
        if (link.isPrivate) {
            const privateTag = document.createElement('div');
            privateTag.className = 'private-tag';
            privateTag.textContent = '私密';
            card.appendChild(privateTag);
        }
    
        const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
    
        if (!isAdmin) {
            card.addEventListener('click', () => {
                window.open(correctedUrl, '_blank');
                logAction('打开链接', { name: link.name, url: correctedUrl });
            });
        }
    
        const deleteBtn = document.createElement('button');
        deleteBtn.textContent = '–';
        deleteBtn.className = 'delete-btn';
        deleteBtn.onclick = function (event) {
            event.stopPropagation();
            removeCard(card);
        };
        card.appendChild(deleteBtn);
    
        updateCardStyle(card);
    
        card.addEventListener('dragstart', dragStart);
        card.addEventListener('dragover', dragOver);
        card.addEventListener('dragend', dragEnd);
        card.addEventListener('drop', drop);
        card.addEventListener('touchstart', touchStart, { passive: false });
    
        if (isAdmin && removeMode) {
            deleteBtn.style.display = 'block';
        }
    
        if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
            container.appendChild(card);
        }
        // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
    }
    
    // 更新卡片样式
    function updateCardStyle(card) {
        if (isDarkTheme) {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        } else {
            card.style.backgroundColor = '#a0c9e5';
            card.style.color = '#333';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
        }
    }
    
    // 更新分类选择下拉框
    function updateCategorySelect() {
        const categorySelect = document.getElementById('category-select');
        categorySelect.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const option = document.createElement('option');
            option.value = category;
            option.textContent = category;
            categorySelect.appendChild(option);
        });
    
        logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
    }
    
    // 保存链接数据
    async function saveLinks() {
        let allLinks = [...publicLinks, ...privateLinks];
    
        try {
            await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: allLinks,
                    categories: categories
                }),
            });
            logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
        } catch (error) {
            console.error('Error saving links:', error);
            logAction('保存链接失败', { error: error.message });
            alert('保存链接失败,请重试');
        }
    }
    
    // 添加卡片弹窗
    function addLink() {
        const name = document.getElementById('name-input').value;
        const url = document.getElementById('url-input').value;
        const category = document.getElementById('category-select').value;
        const isPrivate = document.getElementById('private-checkbox').checked;
    
        if (name && url && category) {
            const newLink = { name, url, category, isPrivate };
    
            if (isPrivate) {
                privateLinks.push(newLink);
            } else {
                publicLinks.push(newLink);
            }
    
            links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
    
            if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
                const container = document.getElementById(category);
                if (container) {
                    createCard(newLink, container);
                } else {
                    categories[category] = [];
                    renderCategories();
                }
            }
    
            saveLinks();
    
            document.getElementById('name-input').value = '';
            document.getElementById('url-input').value = '';
            document.getElementById('private-checkbox').checked = false;
            hideAddDialog();

            logAction('添加卡片', { name, url, category, isPrivate });
        }
    }

    // 删除卡片
    function removeCard(card) {
        const name = card.querySelector('.card-title').textContent;
        const url = card.querySelector('.card-url').textContent;
        const isPrivate = card.dataset.isPrivate === 'true';
        
        links = links.filter(link => link.url !== url);
        if (isPrivate) {
            privateLinks = privateLinks.filter(link => link.url !== url);
        } else {
            publicLinks = publicLinks.filter(link => link.url !== url);
        }
    
        for (const key in categories) {
            categories[key] = categories[key].filter(link => link.url !== url);
        }
    
        card.remove();
    
        saveLinks();
    
        logAction('删除卡片', { name, url, isPrivate });
    }
    
    // 拖拽卡片
    let draggedCard = null;
    let touchStartX, touchStartY;
    
    // 触屏端拖拽卡片
    function touchStart(event) {
        if (!isAdmin) return;
        draggedCard = event.target.closest('.card');
        if (!draggedCard) return;
    
        event.preventDefault();
        const touch = event.touches[0];
        touchStartX = touch.clientX;
        touchStartY = touch.clientY;
    
        draggedCard.classList.add('dragging');
        
        document.addEventListener('touchmove', touchMove, { passive: false });
        document.addEventListener('touchend', touchEnd);
    
    }
    
    function touchMove(event) {
        if (!draggedCard) return;
        event.preventDefault();
    
        const touch = event.touches[0];
        const currentX = touch.clientX;
        const currentY = touch.clientY;

        const deltaX = currentX - touchStartX;
        const deltaY = currentY - touchStartY;
        draggedCard.style.transform = "translate(" + deltaX + "px, " + deltaY + "px)";
    
        const target = findCardUnderTouch(currentX, currentY);
        if (target && target !== draggedCard) {
            const container = target.parentElement;
            const targetRect = target.getBoundingClientRect();
    
            if (currentX < targetRect.left + targetRect.width / 2) {
                container.insertBefore(draggedCard, target);
            } else {
                container.insertBefore(draggedCard, target.nextSibling);
            }
        }
    }
    
    function touchEnd(event) {
        if (!draggedCard) return;
    
        document.removeEventListener('touchmove', touchMove);
        document.removeEventListener('touchend', touchEnd);
    
        draggedCard.classList.remove('dragging');
        draggedCard.style.transform = '';
    
        const targetCategory = draggedCard.closest('.card-container').id;
        updateCardCategory(draggedCard, targetCategory);
        saveCardOrder();
    
        draggedCard = null;
    }
    
    function findCardUnderTouch(x, y) {
        const cards = document.querySelectorAll('.card:not(.dragging)');
        return Array.from(cards).find(card => {
            const rect = card.getBoundingClientRect();
            return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
        });
    }

    // PC端拖拽卡片
    function dragStart(event) {
        if (!isAdmin) return;
        draggedCard = event.target.closest('.card');
        if (!draggedCard) return;
    
        draggedCard.classList.add('dragging');
        event.dataTransfer.effectAllowed = "move";
        logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
    }
    
    function dragOver(event) {
        if (!isAdmin || !draggedCard) return;
        event.preventDefault();
        const target = event.target.closest('.card');
        if (target && target !== draggedCard) {
            const container = target.parentElement;
            const mousePositionX = event.clientX;
            const targetRect = target.getBoundingClientRect();
    
            if (mousePositionX < targetRect.left + targetRect.width / 2) {
                container.insertBefore(draggedCard, target);
            } else {
                container.insertBefore(draggedCard, target.nextSibling);
            }
        }
    }
    
    function drop(event) {
        if (!isAdmin || !draggedCard) return;
        event.preventDefault();
        draggedCard.classList.remove('dragging');
        const targetCategory = event.target.closest('.card-container').id;
        updateCardCategory(draggedCard, targetCategory);
        logAction('放下卡片', { name: draggedCard.querySelector('.card-title').textContent, category: targetCategory });
        draggedCard = null;
        saveCardOrder();
    }
    
    function dragEnd(event) {
        if (draggedCard) {
            draggedCard.classList.remove('dragging');
            logAction('拖拽卡片结束');
            draggedCard = null;
        }
    }
  
    // 更新卡片分类
    function updateCardCategory(card, newCategory) {
        const cardTitle = card.querySelector('.card-title').textContent;
        const cardUrl = card.querySelector('.card-url').textContent;
        const isPrivate = card.dataset.isPrivate === 'true';
    
        const linkIndex = links.findIndex(link => link.url === cardUrl);
        if (linkIndex !== -1) {
            links[linkIndex].category = newCategory;
        }
    
        const linkArray = isPrivate ? privateLinks : publicLinks;
        const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
        if (arrayIndex !== -1) {
            linkArray[arrayIndex].category = newCategory;
        }
    
        card.dataset.category = newCategory;
    }

    // 在页面加载完成后添加触摸事件监听器
    document.addEventListener('DOMContentLoaded', function() {
        const cardContainers = document.querySelectorAll('.card-container');
        cardContainers.forEach(container => {
            container.addEventListener('touchstart', touchStart, { passive: false });
        });
    });    
    
    // 保存卡片顺序
    async function saveCardOrder() {
        if (!isAdmin) return;
        const containers = document.querySelectorAll('.card-container');
        let newPublicLinks = [];
        let newPrivateLinks = [];
        let newCategories = {};
    
        containers.forEach(container => {
            const category = container.id;
            newCategories[category] = [];
    
            [...container.children].forEach(card => {
                const url = card.querySelector('.card-url').textContent;
                const name = card.querySelector('.card-title').textContent;
                const isPrivate = card.dataset.isPrivate === 'true';
                card.dataset.category = category;
                const link = { name, url, category, isPrivate };
                if (isPrivate) {
                    newPrivateLinks.push(link);
                } else {
                    newPublicLinks.push(link);
                }
                newCategories[category].push(link); 
            });
        });
    
        publicLinks.length = 0;
        publicLinks.push(...newPublicLinks);
        privateLinks.length = 0;
        privateLinks.push(...newPrivateLinks);
        Object.keys(categories).forEach(key => delete categories[key]);
        Object.assign(categories, newCategories);
    
        logAction('保存卡片顺序', { 
            publicCount: newPublicLinks.length, 
            privateCount: newPrivateLinks.length, 
            categoryCount: Object.keys(newCategories).length 
        });
    
        try {
            const response = await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: [...newPublicLinks, ...newPrivateLinks],
                    categories: newCategories
                }),
            });
            const result = await response.json();
            if (!result.success) {
                throw new Error('Failed to save order');
            }
            logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
        } catch (error) {
            console.error('Error saving order:', error);
            logAction('保存顺序失败', { error: error.message });
            alert('保存顺序失败,请重试');
        }
    }             
    
    // 设置状态重新加载卡片
    function reloadCardsAsAdmin() {
        document.querySelectorAll('.card-container').forEach(container => {
            container.innerHTML = '';
        });
        loadLinks().then(() => {
            if (isDarkTheme) {
                applyDarkTheme();
            }
        });
        logAction('重新加载卡片(管理员模式)');
    }
    
    // 密码输入框回车事件
    document.getElementById('admin-password').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            toggleSecretGarden();
        }
    });
    
    // 切换设置状态
    function toggleAdminMode() {
        const adminBtn = document.getElementById('admin-mode-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        if (!isAdmin && isLoggedIn) {
            isAdmin = true;
            adminBtn.textContent = "退出设置";
            addRemoveControls.style.display = 'flex';
            alert('准备设置分类和书签');
            reloadCardsAsAdmin();
            logAction('进入设置');
        } else if (isAdmin) {
            isAdmin = false;
            removeMode = false;
            adminBtn.textContent = "设  置";
            addRemoveControls.style.display = 'none';
            alert('设置已保存');
            reloadCardsAsAdmin();
            logAction('离开设置');
        }
    
        updateUIState();
    }      
    
    // 切换到登录状态
    function toggleSecretGarden() {
        const passwordInput = document.getElementById('admin-password');
        if (!isLoggedIn) {
            verifyPassword(passwordInput.value).then(isValid => {
                if (isValid) {
                    isLoggedIn = true;
                    links = [...publicLinks, ...privateLinks];
                    loadSections();
                    alert('登录成功!');
                    logAction('登录成功');
                } else {
                    alert('密码错误');
                    logAction('登录失败', { reason: '密码错误' });
                }
                updateUIState();
            });
        } else {
            isLoggedIn = false;
            isAdmin = false;
            links = publicLinks;
            loadSections();
            alert('退出登录!');
            updateUIState();
            passwordInput.value = '';
            logAction('退出登录');
        }
    }
    
    // 应用暗色主题
    function applyDarkTheme() {
        document.body.style.backgroundColor = '#121212';
        document.body.style.color = '#ffffff';
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        });
        logAction('应用暗色主题');
    }
    
    // 显示添加链接对话框
    function showAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'flex';
        logAction('显示添加链接对话框');
    }
    
    // 隐藏添加链接对话框
    function hideAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'none';
        logAction('隐藏添加链接对话框');
    }
    
    // 切换删除卡片模式
    function toggleRemoveMode() {
        removeMode = !removeMode;
        const deleteButtons = document.querySelectorAll('.delete-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = removeMode ? 'block' : 'none';
        });
        logAction('切换删除卡片模式', { removeMode });
    }
    
    //切换删除分类模式
    function toggleRemoveCategory() {
        isRemoveCategoryMode = !isRemoveCategoryMode;
        const deleteButtons = document.querySelectorAll('.delete-category-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
        });
        logAction('切换删除分类模式', { isRemoveCategoryMode });
    }
    
    // 切换主题
    function toggleTheme() {
        isDarkTheme = !isDarkTheme;
    
        document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#d8eac4';
        document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
            card.style.color = isDarkTheme ? '#ffffff' : '#333';
            card.style.boxShadow = isDarkTheme
                ? '0 4px 8px rgba(0, 0, 0, 0.5)'
                : '0 4px 8px rgba(0, 0, 0, 0.1)';
        });
    
        const fixedElements = document.querySelectorAll('.fixed-elements');
        fixedElements.forEach(element => {
            element.style.backgroundColor = isDarkTheme ? '#121212' : '#d8eac4';
            element.style.color = isDarkTheme ? '#ffffff' : '#333';
        });
    
        const dialogBox = document.getElementById('dialog-box');
        dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
        dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
        inputs.forEach(input => {
            input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
            input.style.color = isDarkTheme ? '#fff' : '#333';
            input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
        });
        
        logAction('切换主题', { isDarkTheme });
    }
    
    // 验证密码
    async function verifyPassword(inputPassword) {
        const response = await fetch('/api/verifyPassword', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ password: inputPassword }),
        });
        const result = await response.json();
        return result.valid;
    }
    
    // 初始化加载链接
    loadLinks();

    </script>
</body>

</html>
`;

export default {
async fetch(request, env) {
const url = new URL(request.url);

if (url.pathname === '/') {
return new Response(HTML_CONTENT, {
headers: { 'Content-Type': 'text/html' }
});
}

if (url.pathname === '/api/getLinks') {
const userId = url.searchParams.get('userId');
const links = await env.CARD_ORDER.get(userId); 
return new Response(links || JSON.stringify([]), { status: 200 });
}

if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
    const { userId, links, categories } = await request.json(); 
    await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories })); //保存链接和分类
    return new Response(JSON.stringify({ success: true }), { status: 200 });
}

if (url.pathname === '/api/verifyPassword' && request.method === 'POST') { 
const { password } = await request.json();
const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
return new Response(JSON.stringify({ valid: isValid }), {
status: isValid ? 200 : 403,
headers: { 'Content-Type': 'application/json' },
});
}

return new Response('Not Found', { status: 404 });
}
};

修改后workes, 效果

const HTML_CONTENT = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ws01主页</title>
    <link rel="icon" href="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
    <style>
    /* 全局样式 */
    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        background-color: #d4d4d4;
        transition: background-color 0.3s ease;
    }

    ul {
        padding: 0;
        margin-block-start: 1em;
        margin-block-end: 1em;
        margin-inline-start: 0px;
        margin-inline-end: 0px;
        padding-inline-start: 40px;
        unicode-bidi: isolate;
    }
    li {
        display: list-item;
        text-align: -webkit-match-parent;
        unicode-bidi: isolate;
        margin: 0 8px;
    }
    
    .background {
              background-image: linear-gradient(#d4d4d4 1px, transparent 0), linear-gradient(90deg, #d4d4d4 1px, transparent 0);
              background-size: 32px 32px;
              background-color: #fffcf8;
          }
        
          .header {
              background-color: #fff;
              box-shadow: 0 0 5px rgba(0, 0, 0, .1);
              transition: background-color .5s;
          }
          .navbar {
              display: flex;
              width: 1280px;
              margin: auto;
              height: 40px;
          }
    
          .navbar .brand {
    
              display: flex;
              align-items: center;
              color: #555;
          }
    
          .brand .logo {
              max-width: 36px;
          }
    
          .brand .title {
              margin-left: 5px;
              font-family: helvetica neue, helvetica, arial, sans-serif;
              font-size: 24px;
              font-weight: 700;
          }
    
          .beta {
              color: #ccc;
              font-size: 11px;
              font-weight: 400;
              position: relative;
              top: -14px;
          }
    
          .category-list {
            list-style: none;
            display: flex;
            align-items: center;
        }
        
    .sites {
    width:1286px;
    background:;
    border:2px solid auto;
    margin:15px auto;
    padding:0px;
    text-align:center;
    }
    .sites1 {
    width:1286px;
    background:;
    border:2px solid auto;
    margin:15px auto;
    padding:0px;
    }
    
    .sites dl {
    height:36px;
    line-height:36px;
    display:block;
    margin:0;
    }
    
    .sites dl.alt {
    background:#d4d4d4;
    border-top:1px solid #ffffff;
    border-bottom:1px solid #ffffff;
    }
    
    .sites dl.alt2 {
    background:#d4d4d4;
    border-top:1px solid #ffffff;
    border-bottom:1px solid #ffffff;
    }
    
    .sites dt,.sites dd {
    text-align:center;
    display:block;
    float:left;
    }
    
    .sites dt {
    width:60px;
    }
    
    .sites dd {
    width:90px;
    margin:0;
    }
    .footer {
    width:580px;
    text-align:center;
    margin:5px auto;
    padding:2px;
    }    
    


    /* 固定元素样式 */
    .fixed-elements {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        background-color: #d4d4d4;
        z-index: 1000;
        padding: 10px;
        transition: background-color 0.3s ease;
        height: 130px;
    }
    
    .fixed-elements h3 {
        position: absolute;
        top: 10px;
        left: 20px;
        margin: 0;
    }
    
    /* 中心内容样式 */
    .center-content {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        max-width: 600px;
        text-align: center;
    }
    
    /* 管理员控制面板样式 */
    .admin-controls {
        position: fixed;
        top: 10px;
        right: 10px;
        font-size: 60%;
    }
    
    /* 添加/删除控制按钮样式 */
    .add-remove-controls {
        display: none;
        flex-direction: column;
        position: fixed;
        right: 20px;
        top: 50%;
        transform: translateY(-50%);
        align-items: center;
        gap: 10px;
    }
    
    .round-btn {
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
        margin: 5px 0;
    }
    
    .add-btn { order: 1; }
    .remove-btn { order: 2; }
    .category-btn { order: 3; }
    .remove-category-btn { order: 4; }
    
    /* 主要内容区域样式 */
    .content {
        margin-top: 140px;
        padding: 20px;
    }
    
    /* 搜索栏样式 */
    .search-container {
        margin-top: 10px;
    }
    
    .search-bar {
        display: flex;
        justify-content: center;
        margin-bottom: 10px;
    }
    
    .search-bar input {
        width: 50%;
        padding: 5px;
        border: 1px solid #ccc;
        border-radius: 5px 0 0 5px;
    }
    
    .search-bar button {
        padding: 5px 20px;
        border: 1px solid #ccc;
        border-left: none;
        background-color: #a0c9e5;
        border-radius: 0 5px 5px 0;
        cursor: pointer;
    }
    
    /* 搜索引擎按钮样式 */
    .search-engines {
        display: flex;
        justify-content: center;
        gap: 10px;
    }
    
    .search-engine {
        padding: 5px 10px;
        border: 1px solid #ccc;
        background-color: #f0f0;
        border-radius: 5px;
        cursor: pointer;
    }
    
    /* 主题切换按钮样式 */
    #theme-toggle {
        position: fixed;
        bottom: 50px;
        right: 20px;
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        text-align: center;
        font-size: 24px;
        line-height: 40px;
        cursor: pointer;
    }

    /* 显示日志按钮样式 */
    #view-logs-btn {
        position: fixed;
        top: 100px; 
        right: 10px;
        z-index: 1000; 
    }    
    
    /* 对话框样式 */
    #dialog-overlay {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        justify-content: center;
        align-items: center;
    }
    
    #dialog-box {
        background-color: white;
        padding: 20px;
        border-radius: 5px;
        width: 300px;
    }
    
    #dialog-box input, #dialog-box select {
        width: 100%;
        margin-bottom: 10px;
        padding: 5px;
    }
    
    /* 分类和卡片样式 */
    .section {
        margin-bottom: 20px;
    }
    
    .section-title-container {
        display: flex;
        align-items: center;
        margin-bottom: 10px;
    }
    
    .section-title {
        font-size: 18px;
        font-weight: bold;
        margin-left: 20px; /* 分类标签右移添加这一行 */
        color: green; /* 分类标签字体颜色添加这一行 */
    }
    
    .delete-category-btn {
        background-color: pink;
        color: white;
        border: none;
        padding: 5px 10px;
        border-radius: 5px;
        cursor: pointer;
    }
    
    .card-container {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }
    
    .card {
        background-color: #a0c9e5;
        border-radius: 5px;
        padding: 10px;
        width: 132px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        cursor: pointer;
        transition: transform 0.2s;
        position: relative;
    }

/* 中等屏幕 (平板等) */
@media (max-width: 1024px) {

}
/* 小屏幕 (手机) */
@media (max-width: 768px) {   

}

/* 超小屏幕 (更小手机) */
@media (max-width: 480px) {
    .card {
        padding: 6px; /* 进一步减小卡片内边距6 */
        width: 235px;
    }
}

    .card:hover {
        transform: translateY(-5px);
    }
    
    .card-top {
        display: flex;
        align-items: center;
        margin-bottom: 5px;
    }
    
    .card-icon {
        width: 20px;
        height: 20px;
        margin-right: 5px;
    }
    
    .card-title {
        font-size: 18px;
        font-weight: bold;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    
    .card-url {
        font-size: 12px;
        color: #666;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    
    .private-tag {
        background-color: #ff9800;
        color: white;
        font-size: 10px;
        padding: 2px 5px;
        border-radius: 3px;
        position: absolute;
        top: 5px;
        right: 5px;
    }
    
    .delete-btn {
        position: absolute;
        top: -10px;
        right: -10px;
        background-color: red;
        color: white;
        border: none;
        border-radius: 50%;
        width: 20px;
        height: 20px;
        text-align: center;
        font-size: 14px;
        line-height: 20px;
        cursor: pointer;
        display: none;
    }
    
    /* 版权信息样式 */
    #copyright {
        position: fixed;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 40px;
        background-color: rgba(255, 255, 255, 0.8);
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 14px;
        z-index: 1000;
        box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
    }
    
    #copyright p {
        margin: 0;
    }
    
    #copyright a {
        color: #007bff;
        text-decoration: none;
    }
    
    #copyright a:hover {
        text-decoration: underline;
    }
        
    </style>
    </head>
    <body class="background">
    
    </div>
    <div class="sites">  <!--  00   -->
    <header class="header">
      <nav class="navbar">
        <a href="https://www.199881.xyz/" class="brand">
          <img class="debug logo" src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
          <span class="debug title">WS01の主页</span>
          <span class="debug beta">beta</span>
          <!-- 一言模块 -->
          <p id="hitokoto">
              <a href="#" id="hitokoto_text"></a>
          </p>
          <script src="https://v1.hitokoto.cn/?encode=js&select=%23hitokoto" defer></script>
          </a>
          </header>

          <!-- 搜索栏 -->
          <div class="search-container">
              <div class="search-bar">
                  <input type="text" id="search-input" placeholder="">
                  <button id="search-button">🔍</button>
              </div>
              <div class="search-engines">
                  <button class="search-engine" data-engine="baidu">百度</button>
                  <button class="search-engine" data-engine="bing">必应</button>
                  <button class="search-engine" data-engine="toutiao">头条</button>
                  <button class="search-engine" data-engine="sm">神马</button>
                  <button class="search-engine" data-engine="bilibili">哔哩</button>
                  <button class="search-engine" data-engine="github">github</button>
                  <button class="search-engine" data-engine="google">谷歌</button>
                  <button class="search-engine" data-engine="yandex">yandex</button>
              </div>
          </div>

    <div class="head">
        <!-- 添加/删除控制按钮 -->
        <div class="add-remove-controls">
            <button class="round-btn add-btn" onclick="showAddDialog()">+</button>
            <button class="round-btn remove-btn" onclick="toggleRemoveMode()">-</button>
            <button class="round-btn category-btn" onclick="addCategory()">C+</button>
            <button class="round-btn remove-category-btn" onclick="toggleRemoveCategory()">C-</button>

            </div>
            <div class="sites1">  <!--  00   -->
        <!-- 分类和卡片容器 -->
        <div id="sections-container"></div>
        <!-- 主题切换按钮 -->
        <button id="theme-toggle" onclick="toggleTheme()">◑</button>
        <!-- 显示日志按钮 用于调试-->
        <!--<button id="view-logs-btn" onclick="viewLogs()">显示日志</button>-->
        <!-- 添加链接对话框 -->
        <div id="dialog-overlay">
            <div id="dialog-box">
                <label for="name-input">名称</label>
                <input type="text" id="name-input">
                <label for="url-input">地址</label>
                <input type="text" id="url-input">
                <label for="category-select">选择分类</label>
                <select id="category-select"></select>
                <div class="private-link-container">
                    <label for="private-checkbox">私密链接</label>    
                    <input type="checkbox" id="private-checkbox">
                </div>
                <button onclick="addLink()">确定</button>
                <button onclick="hideAddDialog()">取消</button>
            </div>
        </div>

    <br />
    <!-- body   页脚    -->
    <div class="footer">
    <!-- 管理员控制面板 -->
    <input type="password" id="admin-password" placeholder="输入密码">
    <button id="admin-mode-btn" onclick="toggleAdminMode()">设  置</button>
    <button id="secret-garden-btn" onclick="toggleSecretGarden()">登  录</button>

    <br />
    <br />    <br />
    <!-- 版权信息 -->

    <div id="copyright" class="copyright">
        <!--请不要删除-->
        <p><a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 项目
        <!-- 开站时间开始 -->       
        <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span> <script language="javascript"> 
var now = new Date();
function createtime(){
   var grt= new Date("09/05/2024 00:00:00");/*---这里是网站的启用时间--*/
   now.setTime(now.getTime()+250);
   days = (now - grt ) / 1000 / 60 / 60 / 24;
   dnum = Math.floor(days);
   hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
   hnum = Math.floor(hours);
   if(String(hnum).length ==1 ){hnum = "0" + hnum;}
   minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
   mnum = Math.floor(minutes);
   if(String(mnum).length ==1 ){mnum = "0" + mnum;}
   seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
   snum = Math.round(seconds);
   if(String(snum).length ==1 ){snum = "0" + snum;}
   document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天";
   document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
}
setInterval("createtime()",250); 
</script> 
<!-- 开站时间结束 -->      
        
        
        </p>
    </div>
</div>

    <script>
    // 搜索引擎配置
    const searchEngines = {
        baidu: "https://www.baidu.com/s?wd=",
        bing: "https://www.bing.com/search?q=",
        sm: "https://m.sm.cn/s?q=",
        toutiao: "https://so.toutiao.com/search?dvpf=pc&source=trending_card&keyword=",
        bilibili: "https://search.bilibili.com/all?keyword=",
        github: "https://github.com/search?q=",
        google: "https://www.google.com/search?q=",
        yandex: "https://www.yandex.com/search/?text="
    };
    
    let currentEngine = "baidu";
    
    // 日志记录函数
    function logAction(action, details) {
        const timestamp = new Date().toISOString();
        const logEntry = timestamp + ': ' + action + ' - ' + JSON.stringify(details);
        
        let logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
        logs.push(logEntry);
        
        // 保留最新的1000条日志
        if (logs.length > 1000) {
            logs = logs.slice(-1000);
        }
        
        localStorage.setItem('cardTabLogs', JSON.stringify(logs));
        console.log(logEntry); // 同时在控制台输出日志
    }
    
    // 查看日志的函数
    function viewLogs() {
        const logs = JSON.parse(localStorage.getItem('cardTabLogs') || '[]');
        console.log('Card Tab Logs:');
        logs.forEach(log => console.log(log));
        alert('日志已在控制台输出,请按F12打开开发者工具查看。');
    }
    
    // 设置当前搜索引擎
    function setActiveEngine(engine) {
        currentEngine = engine;
        document.querySelectorAll('.search-engine').forEach(btn => {
            btn.style.backgroundColor = btn.dataset.engine === engine ? '#c0c0c0' : '#f0f0f0';
        });
        logAction('设置搜索引擎', { engine });
    }
    
    // 搜索引擎按钮点击事件
    document.querySelectorAll('.search-engine').forEach(button => {
        button.addEventListener('click', () => setActiveEngine(button.dataset.engine));
    });
    
    // 搜索按钮点击事件
    document.getElementById('search-button').addEventListener('click', () => {
        const query = document.getElementById('search-input').value;
        if (query) {
            logAction('执行搜索', { engine: currentEngine, query });
            window.open(searchEngines[currentEngine] + encodeURIComponent(query), '_blank');
        }
    });
    
    // 搜索输入框回车事件
    document.getElementById('search-input').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('search-button').click();
        }
    });
    
    // 初始化搜索引擎
    setActiveEngine(currentEngine);
    
    // 全局变量
    let publicLinks = [];
    let privateLinks = [];
    let isAdmin = false;
    let isLoggedIn = false;
    let removeMode = false;
    let isRemoveCategoryMode = false;
    let isDarkTheme = false;
    let links = [];
    const categories = {};
    
    // 添加新分类
    function addCategory() {
        const categoryName = prompt('请输入新分类名称:');
        if (categoryName && !categories[categoryName]) {
            categories[categoryName] = [];
            updateCategorySelect();
            renderCategories();
            saveLinks();
            logAction('添加分类', { categoryName, currentLinkCount: links.length });
        } else if (categories[categoryName]) {
            alert('该分类已存在');
            logAction('添加分类失败', { categoryName, reason: '分类已存在' });
        }
    }

    // 删除分类
    function deleteCategory(category) {
        if (confirm('确定要删除 "' + category + '" 分类吗?这将删除该分类下的所有链接。')) {
            delete categories[category];
            links = links.filter(link => link.category !== category);
            publicLinks = publicLinks.filter(link => link.category !== category);
            privateLinks = privateLinks.filter(link => link.category !== category);
            updateCategorySelect();
            saveLinks();
            renderCategories();
            logAction('删除分类', { category });
        }
    }    

    // 渲染分类(不重新加载链接)
    function renderCategories() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            container.appendChild(section);
    
            // 只渲染属于当前分类的链接
            const categoryLinks = links.filter(link => link.category === category);
            categoryLinks.forEach(link => {
                createCard(link, cardContainer);
            });
        });
    
        logAction('渲染分类', { categoryCount: Object.keys(categories).length, linkCount: links.length });
    }    
    
    // 读取链接数据
    async function loadLinks() {
        const response = await fetch('/api/getLinks?userId=testUser');
        const data = await response.json();
        if (data.categories) {
            Object.assign(categories, data.categories);
        }
        
        publicLinks = data.links ? data.links.filter(link => !link.isPrivate) : [];
        privateLinks = data.links ? data.links.filter(link => link.isPrivate) : [];
        links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
    
        loadSections();
        updateCategorySelect();
        updateUIState();
        logAction('读取链接', { publicCount: publicLinks.length, privateCount: privateLinks.length });
    }
    
    
    // 更新UI状态
    function updateUIState() {
        const passwordInput = document.getElementById('admin-password');
        const adminBtn = document.getElementById('admin-mode-btn');
        const secretGardenBtn = document.getElementById('secret-garden-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        passwordInput.style.display = isLoggedIn ? 'none' : 'inline-block';
        secretGardenBtn.textContent = isLoggedIn ? "退出" : "登录";
        secretGardenBtn.style.display = 'inline-block';
    
        if (isAdmin) {
            adminBtn.textContent = "离开设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'flex';
        } else if (isLoggedIn) {
            adminBtn.textContent = "设置";
            adminBtn.style.display = 'inline-block';
            addRemoveControls.style.display = 'none';
        } else {
            adminBtn.style.display = 'none';
            addRemoveControls.style.display = 'none';
        }
    
        logAction('更新UI状态', { isAdmin, isLoggedIn });
    }
    
    // 登录状态显示(加载所有链接)
    function showSecretGarden() {
        if (isLoggedIn) {
            links = [...publicLinks, ...privateLinks];
            loadSections();
            // 显示所有私密标签
            document.querySelectorAll('.private-tag').forEach(tag => {
                tag.style.display = 'block';
            });
            logAction('显示私密花园');
        }
    }
    
    // 加载分类和链接
    function loadSections() {
        const container = document.getElementById('sections-container');
        container.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const section = document.createElement('div');
            section.className = 'section';
    
            const titleContainer = document.createElement('div');
            titleContainer.className = 'section-title-container';
    
            const title = document.createElement('div');
            title.className = 'section-title';
            title.textContent = category;
    
            titleContainer.appendChild(title);
    
            if (isAdmin) {
                const deleteBtn = document.createElement('button');
                deleteBtn.textContent = '删除分类';
                deleteBtn.className = 'delete-category-btn';
                deleteBtn.style.display = 'none'; 
                deleteBtn.onclick = () => deleteCategory(category);
                titleContainer.appendChild(deleteBtn);
            }
    
            const cardContainer = document.createElement('div');
            cardContainer.className = 'card-container';
            cardContainer.id = category;
    
            section.appendChild(titleContainer);
            section.appendChild(cardContainer);
    
            let privateCount = 0;
            let linkCount = 0;
    
            links.forEach(link => {
                if (link.category === category) {
                    if (link.isPrivate) privateCount++;
                    linkCount++;
                    createCard(link, cardContainer);
                }
            });
    
            if (privateCount < linkCount || isLoggedIn) {
                container.appendChild(section);
            }
        });
    
        logAction('加载分类和链接', { isAdmin: isAdmin, linkCount: links.length, categoryCount: Object.keys(categories).length });
    }
    
    // 创建卡片
    function createCard(link, container) {
        const card = document.createElement('div');
        card.className = 'card';
        card.setAttribute('draggable', isAdmin);
        card.dataset.isPrivate = link.isPrivate;
    
        const cardTop = document.createElement('div');
        cardTop.className = 'card-top';
    
        const icon = document.createElement('img');
        icon.className = 'card-icon';
        icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
        icon.alt = 'Website Icon';
    
        const title = document.createElement('div');
        title.className = 'card-title';
        title.textContent = link.name;
    
        cardTop.appendChild(icon);
        cardTop.appendChild(title);
    
        const url = document.createElement('div');
        url.className = 'card-url';
        url.textContent = link.url;
    
        card.appendChild(cardTop);
        card.appendChild(url);
    
        if (link.isPrivate) {
            const privateTag = document.createElement('div');
            privateTag.className = 'private-tag';
            privateTag.textContent = '私密';
            card.appendChild(privateTag);
        }
    
        const correctedUrl = link.url.startsWith('http://') || link.url.startsWith('https://') ? link.url : 'http://' + link.url;
    
        if (!isAdmin) {
            card.addEventListener('click', () => {
                window.open(correctedUrl, '_blank');
                logAction('打开链接', { name: link.name, url: correctedUrl });
            });
        }
    
        const deleteBtn = document.createElement('button');
        deleteBtn.textContent = '–';
        deleteBtn.className = 'delete-btn';
        deleteBtn.onclick = function (event) {
            event.stopPropagation();
            removeCard(card);
        };
        card.appendChild(deleteBtn);
    
        updateCardStyle(card);
    
        card.addEventListener('dragstart', dragStart);
        card.addEventListener('dragover', dragOver);
        card.addEventListener('dragend', dragEnd);
        card.addEventListener('drop', drop);
    
        if (isAdmin && removeMode) {
            deleteBtn.style.display = 'block';
        }
    
        if (isAdmin || (link.isPrivate && isLoggedIn) || !link.isPrivate) {
            container.appendChild(card);
        }
        // logAction('创建卡片', { name: link.name, isPrivate: link.isPrivate });
    }
    
    // 更新卡片样式
    function updateCardStyle(card) {
        if (isDarkTheme) {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        } else {
            card.style.backgroundColor = '#a0c9e5';
            card.style.color = '#333';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
        }
    }
    
    // 更新分类选择下拉框
    function updateCategorySelect() {
        const categorySelect = document.getElementById('category-select');
        categorySelect.innerHTML = '';
    
        Object.keys(categories).forEach(category => {
            const option = document.createElement('option');
            option.value = category;
            option.textContent = category;
            categorySelect.appendChild(option);
        });
    
        logAction('更新分类选择', { categoryCount: Object.keys(categories).length });
    }
    
    // 保存链接数据
    async function saveLinks() {
        let allLinks = [...publicLinks, ...privateLinks];
    
        try {
            await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: allLinks,
                    categories: categories
                }),
            });
            logAction('保存链接', { linkCount: allLinks.length, categoryCount: Object.keys(categories).length });
        } catch (error) {
            console.error('Error saving links:', error);
            logAction('保存链接失败', { error: error.message });
            alert('保存链接失败,请重试');
        }
    }
    
    // 添加卡片弹窗
    function addLink() {
        const name = document.getElementById('name-input').value;
        const url = document.getElementById('url-input').value;
        const category = document.getElementById('category-select').value;
        const isPrivate = document.getElementById('private-checkbox').checked;
    
        if (name && url && category) {
            const newLink = { name, url, category, isPrivate };
    
            if (isPrivate) {
                privateLinks.push(newLink);
            } else {
                publicLinks.push(newLink);
            }
    
            links = isLoggedIn ? [...publicLinks, ...privateLinks] : publicLinks;
    
            if (isAdmin || (isPrivate && isLoggedIn) || !isPrivate) {
                const container = document.getElementById(category);
                if (container) {
                    createCard(newLink, container);
                } else {
                    categories[category] = [];
                    renderCategories();
                }
            }
    
            saveLinks();
    
            document.getElementById('name-input').value = '';
            document.getElementById('url-input').value = '';
            document.getElementById('private-checkbox').checked = false;
            hideAddDialog();

            logAction('添加卡片', { name, url, category, isPrivate });
        }
    }

    // 删除卡片
    function removeCard(card) {
        const name = card.querySelector('.card-title').textContent;
        const url = card.querySelector('.card-url').textContent;
        const isPrivate = card.dataset.isPrivate === 'true';
        
        links = links.filter(link => link.url !== url);
        if (isPrivate) {
            privateLinks = privateLinks.filter(link => link.url !== url);
        } else {
            publicLinks = publicLinks.filter(link => link.url !== url);
        }
    
        for (const key in categories) {
            categories[key] = categories[key].filter(link => link.url !== url);
        }
    
        card.remove();
    
        saveLinks();
    
        logAction('删除卡片', { name, url, isPrivate });
    }
    
    // 拖拽卡片
    let draggedCard = null;
    
    function dragStart(event) {
        if (!isAdmin) return;
        draggedCard = event.target;
        draggedCard.classList.add('dragging');
        event.dataTransfer.effectAllowed = "move";
        logAction('开始拖拽卡片', { name: draggedCard.querySelector('.card-title').textContent });
    }
    
    function dragOver(event) {
        if (!isAdmin) return;
        event.preventDefault();
        const target = event.target.closest('.card');
        if (target && target !== draggedCard) {
            const container = target.parentElement;
            const mousePositionX = event.clientX;
            const targetRect = target.getBoundingClientRect();
    
            if (mousePositionX < targetRect.left + targetRect.width /2) {
                container.insertBefore(draggedCard, target);
            } else {
                container.insertBefore(draggedCard, target.nextSibling);
            }
        }
        //logAction('拖拽卡片中', { over: target ? target.querySelector('.card-title').textContent : 'unknown' });
    }
    
    function drop(event) {
        if (!isAdmin) return;
        event.preventDefault();
        draggedCard.classList.remove('dragging');
        const targetCategory = event.target.closest('.card-container').id;
        // 更新卡片的分类信息
        const cardTitle = draggedCard.querySelector('.card-title').textContent;
        const cardUrl = draggedCard.querySelector('.card-url').textContent;
        const isPrivate = draggedCard.dataset.isPrivate === 'true';
        // 在 links 数组中更新卡片信息
        const linkIndex = links.findIndex(link => link.url === cardUrl);
        if (linkIndex !== -1) {
            links[linkIndex].category = targetCategory;
        }
        
        // 在 publicLinks 或 privateLinks 中更新卡片信息
        const linkArray = isPrivate ? privateLinks : publicLinks;
        const arrayIndex = linkArray.findIndex(link => link.url === cardUrl);
        if (arrayIndex !== -1) {
            linkArraycategory = targetCategory;
        }
        
        draggedCard.dataset.category = targetCategory;
        logAction('放下卡片', { name: cardTitle, category: targetCategory });
        draggedCard = null;
        saveCardOrder();
    }        
    
    function dragEnd(event) {
        if (draggedCard) {
            draggedCard.classList.remove('dragging');
            logAction('拖拽卡片结束');
        }
    }
    
    // 保存卡片顺序
    async function saveCardOrder() {
        if (!isAdmin) return;
        const containers = document.querySelectorAll('.card-container');
        let newPublicLinks = [];
        let newPrivateLinks = [];
        let newCategories = {};
    
        containers.forEach(container => {
            const category = container.id;
            newCategories[category] = [];
    
            [...container.children].forEach(card => {
                const url = card.querySelector('.card-url').textContent;
                const name = card.querySelector('.card-title').textContent;
                const isPrivate = card.dataset.isPrivate === 'true';
                card.dataset.category = category;
                const link = { name, url, category, isPrivate };
                if (isPrivate) {
                    newPrivateLinks.push(link);
                } else {
                    newPublicLinks.push(link);
                }
                newCategories[category].push(link); 
            });
        });
    
        publicLinks.length = 0;
        publicLinks.push(...newPublicLinks);
        privateLinks.length = 0;
        privateLinks.push(...newPrivateLinks);
        Object.keys(categories).forEach(key => delete categories[key]);
        Object.assign(categories, newCategories);
    
        logAction('保存卡片顺序', { 
            publicCount: newPublicLinks.length, 
            privateCount: newPrivateLinks.length, 
            categoryCount: Object.keys(newCategories).length 
        });
    
        try {
            const response = await fetch('/api/saveOrder', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    userId: 'testUser', 
                    links: [...newPublicLinks, ...newPrivateLinks],
                    categories: newCategories
                }),
            });
            const result = await response.json();
            if (!result.success) {
                throw new Error('Failed to save order');
            }
            logAction('保存卡片顺序', { publicCount: newPublicLinks.length, privateCount: newPrivateLinks.length, categoryCount: Object.keys(newCategories).length });
        } catch (error) {
            console.error('Error saving order:', error);
            logAction('保存顺序失败', { error: error.message });
            alert('保存顺序失败,请重试');
        }
    }             
    
    // 设置状态重新加载卡片
    function reloadCardsAsAdmin() {
        document.querySelectorAll('.card-container').forEach(container => {
            container.innerHTML = '';
        });
        loadLinks().then(() => {
            if (isDarkTheme) {
                applyDarkTheme();
            }
        });
        logAction('重新加载卡片(管理员模式)');
    }
    
    // 密码输入框回车事件
    document.getElementById('admin-password').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            toggleSecretGarden();
        }
    });
    
    // 切换设置状态
    function toggleAdminMode() {
        const adminBtn = document.getElementById('admin-mode-btn');
        const addRemoveControls = document.querySelector('.add-remove-controls');
    
        if (!isAdmin && isLoggedIn) {
            isAdmin = true;
            adminBtn.textContent = "退出设置";
            addRemoveControls.style.display = 'flex';
            alert('准备设置分类和书签');
            reloadCardsAsAdmin();
            logAction('进入设置');
        } else if (isAdmin) {
            isAdmin = false;
            removeMode = false;
            adminBtn.textContent = "设  置";
            addRemoveControls.style.display = 'none';
            alert('设置已保存');
            reloadCardsAsAdmin();
            logAction('离开');
        }
    
        updateUIState();
    }      
    
    // 切换到登录状态
    function toggleSecretGarden() {
        const passwordInput = document.getElementById('admin-password');
        if (!isLoggedIn) {
            verifyPassword(passwordInput.value).then(isValid => {
                if (isValid) {
                    isLoggedIn = true;
                    links = [...publicLinks, ...privateLinks];
                    loadSections();
                    alert('登录成功!');
                    logAction('登录成功');
                } else {
                    alert('密码错误');
                    logAction('登录失败', { reason: '密码错误' });
                }
                updateUIState();
            });
        } else {
            isLoggedIn = false;
            isAdmin = false;
            links = publicLinks;
            loadSections();
            alert('退出登录!');
            updateUIState();
            passwordInput.value = '';
            logAction('退出登录');
        }
    }
    
    // 应用暗色主题
    function applyDarkTheme() {
        document.body.style.backgroundColor = '#121212';
        document.body.style.color = '#ffffff';
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = '#1e1e1e';
            card.style.color = '#ffffff';
            card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
        });
        logAction('应用暗色主题');
    }
    
    // 显示添加链接对话框
    function showAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'flex';
        logAction('显示添加链接对话框');
    }
    
    // 隐藏添加链接对话框
    function hideAddDialog() {
        document.getElementById('dialog-overlay').style.display = 'none';
        logAction('隐藏添加链接对话框');
    }
    
    // 切换删除卡片模式
    function toggleRemoveMode() {
        removeMode = !removeMode;
        const deleteButtons = document.querySelectorAll('.delete-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = removeMode ? 'block' : 'none';
        });
        logAction('切换删除卡片模式', { removeMode });
    }
    
    //切换删除分类模式
    function toggleRemoveCategory() {
        isRemoveCategoryMode = !isRemoveCategoryMode;
        const deleteButtons = document.querySelectorAll('.delete-category-btn');
        deleteButtons.forEach(btn => {
            btn.style.display = isRemoveCategoryMode ? 'inline-block' : 'none';
        });
        logAction('切换删除分类模式', { isRemoveCategoryMode });
    }
    
    // 切换主题
    function toggleTheme() {
        isDarkTheme = !isDarkTheme;
    
        document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
        document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const cards = document.querySelectorAll('.card');
        cards.forEach(card => {
            card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
            card.style.color = isDarkTheme ? '#ffffff' : '#333';
            card.style.boxShadow = isDarkTheme
                ? '0 4px 8px rgba(0, 0, 0, 0.5)'
                : '0 4px 8px rgba(0, 0, 0, 0.1)';
        });
    
        const fixedElements = document.querySelectorAll('.fixed-elements');
        fixedElements.forEach(element => {
            element.style.backgroundColor = isDarkTheme ? '#121212' : '#ffffff';
            element.style.color = isDarkTheme ? '#ffffff' : '#333';
        });
    
        const dialogBox = document.getElementById('dialog-box');
        dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
        dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';
    
        const inputs = document.querySelectorAll('input[type="text"], input[type="password"], select');
        inputs.forEach(input => {
            input.style.backgroundColor = isDarkTheme ? '#444' : '#fff';
            input.style.color = isDarkTheme ? '#fff' : '#333';
            input.style.borderColor = isDarkTheme ? '#555' : '#ccc';
        });
        
        logAction('切换主题', { isDarkTheme });
    }
    
    // 验证密码
    async function verifyPassword(inputPassword) {
        const response = await fetch('/api/verifyPassword', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ password: inputPassword }),
        });
        const result = await response.json();
        return result.valid;
    }
    
    // 初始化加载链接
    loadLinks();

    </script>
</body>

</html>
`;

export default {
async fetch(request, env) {
const url = new URL(request.url);

if (url.pathname === '/') {
return new Response(HTML_CONTENT, {
headers: { 'Content-Type': 'text/html' }
});
}

if (url.pathname === '/api/getLinks') {
const userId = url.searchParams.get('userId');
const links = await env.CARD_ORDER.get(userId); 
return new Response(links || JSON.stringify([]), { status: 200 });
}

if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
    const { userId, links, categories } = await request.json(); 
    await env.CARD_ORDER.put(userId, JSON.stringify({ links, categories })); //保存链接和分类
    return new Response(JSON.stringify({ success: true }), { status: 200 });
}

if (url.pathname === '/api/verifyPassword' && request.method === 'POST') { 
const { password } = await request.json();
const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
return new Response(JSON.stringify({ valid: isValid }), {
status: isValid ? 200 : 403,
headers: { 'Content-Type': 'application/json' },
});
}

return new Response('Not Found', { status: 404 });
}
};

旧版本
1、原workes, 效果

const HTML_CONTENT = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Card Tab</title>
<style>
body {
font-family: Arial, sans-serif;
// background-color: #f4f4f4;
background-color: #d8eac4;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
transition: background-color 0.3s ease;
}
.card-container {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 10px;
}
.card {
display: flex;
flex-direction: column;
position: relative;
background-color: #a0c9e5;
padding: 10px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
cursor: grab;
transition: transform 0.2s ease, box-shadow 0.2s ease;
width: 200px;
height: auto;
}

.card-top {
display: flex;
align-items: center;
margin-bottom: 5px;
}

.card-icon {
width: 24px;
height: 24px;
margin-right: 10px;
}

.card-title {
font-size: 16px;
font-weight: bold;
}

.card-url {
color: #555;
font-size: 12px;
word-break: break-all;
}

.card.dragging {
opacity: 0.8;
transform: scale(1.05);
cursor: grabbing;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.delete-btn {
position: absolute;
top: -10px;
right: -10px;
background-color: red;
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
text-align: center;
font-size: 14px;
line-height: 20px;
cursor: pointer;
display: none;
}
.admin-controls {
position: fixed;
top: 10px;
right: 10px;
font-size: 60%;
}
.admin-controls input {
padding: 5px;
font-size: 60%;
}
.admin-controls button {
padding: 5px 10px;
font-size: 60%;
margin-left: 10px;
}
.add-remove-controls {
display: none;
margin-top: 10px;
}
.round-btn {
background-color: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
text-align: center;
font-size: 24px;
line-height: 40px;
cursor: pointer;
margin: 0 10px;
}
#theme-toggle {
position: fixed;
bottom: 10px;
left: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
text-align: center;
font-size: 24px;
line-height: 40px;
cursor: pointer;
}
#dialog-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
#dialog-box {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
#dialog-box label {
display: block;
margin-bottom: 5px;
}
#dialog-box input, #dialog-box select {
width: 100%;
padding: 5px;
margin-bottom: 10px;
}
#dialog-box button {
padding: 5px 10px;
margin-right: 10px;
}

.section {
margin-bottom: 20px;
}

.section-title {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
</style>
</head>
<body>
<h1>我的导航</h1>

<div class="admin-controls">
<input type="password" id="admin-password" placeholder="输入密码">
<button id="admin-mode-btn" onclick="toggleAdminMode()">进入管理模式</button>
</div>

<div class="add-remove-controls">
<button class="round-btn" onclick="showAddDialog()">+</button>
<button class="round-btn" onclick="toggleRemoveMode()">-</button>
</div>

<div id="sections-container">
<!-- 分类将在这里动态生成 -->
</div>

<button id="theme-toggle" onclick="toggleTheme()">&#9681;</button>

<div id="dialog-overlay">
<div id="dialog-box">
<label for="name-input">名称</label>
<input type="text" id="name-input">
<label for="url-input">地址</label>
<input type="text" id="url-input">
<label for="category-select">选择分类</label>
<select id="category-select">
<!-- 分类选项将在这里动态生成 -->
</select>
<button onclick="addLink()">确定</button>
<button onclick="hideAddDialog()">取消</button>
</div>
</div>
<div class="copyright">
    <!--    请不要删除 -->
    <p>   项目地址: <a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a>   烦请点个star!
</div>

<script>
let isAdmin = false; 
let removeMode = false; 
let isDarkTheme = false; 
let links = []; 
const categories = {
"常用网站": [],                   // **编辑自己的网站分类**
"工具导航": [],
"游戏娱乐": [],
"影音视听": [],
"技术论坛": []
};

async function loadLinks() {
const response = await fetch('/api/getLinks?userId=testUser');
links = await response.json();

Object.keys(categories).forEach(key => {
categories[key] = [];
});

links.forEach(link => {
if (categories[link.category]) {
categories[link.category].push(link);
}
});

loadSections();
updateCategorySelect();
// applyTheme();
}

function loadSections() {
const container = document.getElementById('sections-container');
container.innerHTML = '';

Object.keys(categories).forEach(category => {
const section = document.createElement('div');
section.className = 'section';

const title = document.createElement('div');
title.className = 'section-title';
title.textContent = category;

const cardContainer = document.createElement('div');
cardContainer.className = 'card-container';
cardContainer.id = category;

section.appendChild(title);
section.appendChild(cardContainer);

categories[category].forEach(link => {
createCard(link, cardContainer);
});

container.appendChild(section);
});
}

function createCard(link, container) {
const card = document.createElement('div');
card.className = 'card';
card.setAttribute('draggable', isAdmin);

const cardTop = document.createElement('div');
cardTop.className = 'card-top';

const icon = document.createElement('img');
icon.className = 'card-icon';
// icon.src = 'https://www.google.com/s2/favicons?domain=' + link.url;
icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
icon.alt = 'Website Icon';

const title = document.createElement('div');
title.className = 'card-title';
title.textContent = link.name;

cardTop.appendChild(icon);
cardTop.appendChild(title);

const url = document.createElement('div');
url.className = 'card-url';
url.textContent = link.url;

card.appendChild(cardTop);
card.appendChild(url);

//  URL 检查和修正
function correctUrl(url) {
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
} else {
return 'http://' + url;
}
}

let correctedUrl = correctUrl(link.url);

if (!isAdmin) {
card.addEventListener('click', () => {
window.open(correctedUrl, '_blank');
});
}

const deleteBtn = document.createElement('button');
deleteBtn.textContent = '–';
deleteBtn.className = 'delete-btn';
deleteBtn.onclick = function (event) {
event.stopPropagation();
removeCard(card);
};
card.appendChild(deleteBtn);

if (isDarkTheme) {
card.style.backgroundColor = '#1e1e1e';
card.style.color = '#ffffff';
card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
} else {
card.style.backgroundColor = '#a0c9e5';
card.style.color = '#333';
card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
}

card.addEventListener('dragstart', dragStart);
card.addEventListener('dragover', dragOver);
card.addEventListener('dragend', dragEnd);
card.addEventListener('drop', drop);

if (isAdmin && removeMode) {
deleteBtn.style.display = 'block';
}

container.appendChild(card);
}

function updateCategorySelect() {
const categorySelect = document.getElementById('category-select');
categorySelect.innerHTML = '';

Object.keys(categories).forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
categorySelect.appendChild(option);
});
}

async function saveLinks() {
let links = [];
for (const category in categories) {
links = links.concat(categories[category]);
}

await fetch('/api/saveOrder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'testUser', links }),
});
}

function addLink() {
const name = document.getElementById('name-input').value;
const url = document.getElementById('url-input').value;
const category = document.getElementById('category-select').value;

if (name && url && category) {
const newLink = { name, url, category };

if (!categories[category]) {
categories[category] = [];
}
categories[category].push(newLink);

const container = document.getElementById(category);
createCard(newLink, container);

saveLinks();

document.getElementById('name-input').value = '';
document.getElementById('url-input').value = '';
hideAddDialog();
}
}

function removeCard(card) {
const url = card.querySelector('.card-url').textContent;
let category;
for (const key in categories) {
const index = categories[key].findIndex(link => link.url === url);
if (index !== -1) {
categories[key].splice(index, 1);
category = key;
break;
}
}
card.remove();

saveLinks();
}

let draggedCard = null;

function dragStart(event) {
if (!isAdmin) return;
draggedCard = event.target;
draggedCard.classList.add('dragging');
event.dataTransfer.effectAllowed = "move";
}

function dragOver(event) {
if (!isAdmin) return;
event.preventDefault();
const target = event.target.closest('.card');
if (target && target !== draggedCard) {
const container = target.parentElement;
const mousePositionX = event.clientX;
const targetRect = target.getBoundingClientRect();

if (mousePositionX < targetRect.left + targetRect.width / 2) {
container.insertBefore(draggedCard, target);
} else {
container.insertBefore(draggedCard, target.nextSibling);
}
}
}

function drop(event) {
if (!isAdmin) return;
event.preventDefault();
draggedCard.classList.remove('dragging');
draggedCard = null;
saveCardOrder();
}

// function dragEnd(event) {
// draggedCard.classList.remove('dragging');
function dragEnd(event) {
if (draggedCard) {
draggedCard.classList.remove('dragging');
}
}

async function saveCardOrder() {
if (!isAdmin) return;
const containers = document.querySelectorAll('.card-container');
let newLinks = [];

containers.forEach(container => {
const category = container.id;
categories[category] = [];
[...container.children].forEach(card => {
const url = card.querySelector('.card-url').textContent;
const name = card.querySelector('.card-title').textContent;
const link = { name, url, category };
categories[category].push(link);
newLinks.push(link);
});
});

links = newLinks;

await fetch('/api/saveOrder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'testUser', links: newLinks }),
});
}


function toggleAdminMode() {
const passwordInput = document.getElementById('admin-password');
const adminBtn = document.getElementById('admin-mode-btn');
const addRemoveControls = document.querySelector('.add-remove-controls');

if (!isAdmin) {
verifyPassword(passwordInput.value)
.then(isValid => {
if (isValid) {
isAdmin = true;
adminBtn.textContent = "退出管理模式";
alert('已进入管理模式');
addRemoveControls.style.display = 'block';
reloadCardsAsAdmin();
} else {
alert('密码错误');
}
});
} else {
isAdmin = false;
removeMode = false;
adminBtn.textContent = "进入管理模式";
alert('已退出管理模式');
addRemoveControls.style.display = 'none';
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(btn => btn.style.display = 'none');
reloadCardsAsAdmin();
}

passwordInput.value = '';
}

function reloadCardsAsAdmin() {
document.querySelectorAll('.card-container').forEach(container => {
container.innerHTML = '';
});
loadLinks().then(() => {
if (isDarkTheme) {
applyDarkTheme();
}
});
}

function applyDarkTheme() {
document.body.style.backgroundColor = '#121212';
document.body.style.color = '#ffffff';
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
card.style.backgroundColor = '#1e1e1e';
card.style.color = '#ffffff';
card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
});
}

function showAddDialog() {
document.getElementById('dialog-overlay').style.display = 'flex';
}

function hideAddDialog() {
document.getElementById('dialog-overlay').style.display = 'none';
}


function toggleRemoveMode() {
removeMode = !removeMode;
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(btn => {
btn.style.display = removeMode ? 'block' : 'none';
});
}

function toggleTheme() {
isDarkTheme = !isDarkTheme;
// 设置暗色主题和亮色主题的背景色
document.body.style.backgroundColor = isDarkTheme ? '#121212' : '#d8eac4';
// 设置暗色主题和亮色主题的文本颜色
document.body.style.color = isDarkTheme ? '#ffffff' : '#333';

const cards = document.querySelectorAll('.card');
cards.forEach(card => {
// 卡片背景和文本颜色设置
card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#a0c9e5';
card.style.color = isDarkTheme ? '#ffffff' : '#333';
// 卡片阴影的设置,增强暗色主题的阴影
card.style.boxShadow = isDarkTheme
? '0 4px 8px rgba(0, 0, 0, 0.5)'
: '0 4px 8px rgba(0, 0, 0, 0.1)';
});

const dialogBox = document.getElementById('dialog-box');
// 对话框背景和文本颜色设置
dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';

const inputs = dialogBox.querySelectorAll('input, select');
inputs.forEach(input => {
// 输入框背景和文本颜色设置
input.style.backgroundColor = isDarkTheme ? '#333333' : '#ffffff';
input.style.color = isDarkTheme ? '#ffffff' : '#333';
});
}


async function verifyPassword(inputPassword) {
const response = await fetch('/api/verifyPassword', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: inputPassword }),
});
const result = await response.json();
return result.valid;
}

loadLinks();
</script>
</body>
</html>
`;

export default {
async fetch(request, env) {
const url = new URL(request.url);

if (url.pathname === '/') {
return new Response(HTML_CONTENT, {
headers: { 'Content-Type': 'text/html' }
});
}

if (url.pathname === '/api/getLinks') {
const userId = url.searchParams.get('userId');
const links = await env.CARD_ORDER.get(userId); 
return new Response(links || JSON.stringify([]), { status: 200 });
}

if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
const { userId, links } = await request.json();
await env.CARD_ORDER.put(userId, JSON.stringify(links));
return new Response(JSON.stringify({ success: true }), { status: 200 });
}

if (url.pathname === '/api/verifyPassword' && request.method === 'POST') { 
const { password } = await request.json();
const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
return new Response(JSON.stringify({ valid: isValid }), {
status: isValid ? 200 : 403,
headers: { 'Content-Type': 'application/json' },
});
}

return new Response('Not Found', { status: 404 });
}
};

2、修改后的workes, 效果

const HTML_CONTENT = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WS01の主页</title>
<style>
body {
font-family: Arial, sans-serif;
// background-color: #f4f4f4;
background-color: #d4d4d4;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
transition: background-color 0.3s ease;
}
ul {
    padding: 0;
    margin-block-start: 1em;
    margin-block-end: 1em;
    margin-inline-start: 0px;
    margin-inline-end: 0px;
    padding-inline-start: 40px;
    unicode-bidi: isolate;
}
li {
    display: list-item;
    text-align: -webkit-match-parent;
    unicode-bidi: isolate;
    margin: 0 8px;
}

.background {
          background-image: linear-gradient(#d4d4d4 1px, transparent 0), linear-gradient(90deg, #d4d4d4 1px, transparent 0);
          background-size: 32px 32px;
          background-color: #fffcf8;
      }


      .header {
          background-color: #fff;
          box-shadow: 0 0 5px rgba(0, 0, 0, .1);
          transition: background-color .5s;
      }
      .navbar {
          display: flex;
          width: 1280px;
          margin: auto;
          height: 40px;
      }

      .navbar .brand {

          display: flex;
          align-items: center;
          color: #555;
      }

      .brand .logo {
          max-width: 36px;
      }

      .brand .title {
          margin-left: 5px;
          font-family: helvetica neue, helvetica, arial, sans-serif;
          font-size: 24px;
          font-weight: 700;
      }

      .beta {
          color: #ccc;
          font-size: 11px;
          font-weight: 400;
          position: relative;
          top: -14px;
      }

      .category-list {
        list-style: none;
        display: flex;
        align-items: center;
    }



.sites {
width:1280px;
background:;
border:2px solid auto;
margin:15px auto;
padding:0px;
text-align:center;
}
.sites1 {
width:1280px;
background:;
border:2px solid auto;
margin:15px auto;
padding:0px;
}

.sites dl {
height:36px;
line-height:36px;
display:block;
margin:0;
}

.sites dl.alt {
background:#d4d4d4;
border-top:1px solid #ffffff;
border-bottom:1px solid #ffffff;
}

.sites dl.alt2 {
background:#d4d4d4;
border-top:1px solid #ffffff;
border-bottom:1px solid #ffffff;
}

.sites dt,.sites dd {
text-align:center;
display:block;
float:left;
}

.sites dt {
width:60px;
}

.sites dd {
width:90px;
margin:0;
}
.footer {
width:580px;
text-align:center;
margin:5px auto;
padding:2px;
}


.card-container {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 6px;
width: 100%; /* 宽度设为100%,以适应不同设备 */
}
.card {
display: flex;
flex-direction: column;
position: relative;
background-color: #d4d4d4;
padding: 10px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
cursor: grab;
transition: transform 0.2s ease, box-shadow 0.2s ease;
word-break: break-word; /* 防止超出边界 */
}
/* 中等屏幕 (平板等) */
@media (max-width: 1024px) {
    .card-container {
        grid-template-columns: repeat(8, 1fr); /* 中等屏幕显示4列 */
    }
}

/* 小屏幕 (手机) */
@media (max-width: 768px) {
    .card-container {
        grid-template-columns: repeat(6, 1fr); /* 小屏幕显示2列 */
    }
    
    .card {
        padding: 8px; /* 减小卡片内边距 */
        font-size: 0.9rem; /* 调整字体大小0.9 */
    }

    .admin-controls {
        top: 5px;
        right: 5px;
    }

    .round-btn, #theme-toggle {
        width: 30px;
        height: 30px;
        font-size: 18px;
        line-height: 30px;
    }
}

/* 超小屏幕 (更小手机) */
@media (max-width: 480px) {
    .card-container {
        grid-template-columns: repeat(5, 1fr); /* 超小屏幕显示1列 */
    }

    .card {
        padding: 6px; /* 进一步减小卡片内边距5 */
        font-size: 0.8rem; /* 再次缩小字体0.8 */
    }

    .round-btn, #theme-toggle {
        width: 25px;
        height: 25px;
        font-size: 16px;
        line-height: 25px;
    }
}


.card-top {
display: flex;
align-items: center;
margin-bottom: 5px;
}

.card-icon {
width: 20px;
height: 20px;
margin-right: 8px;
}

.card-title {
font-size: 16px;
font-weight: bold;
}

.card-url {
color: #555;
font-size: 12px;
word-break: break-all;
}

.card.dragging {
opacity: 0.8;
transform: scale(1.05);
cursor: grabbing;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.delete-btn {
position: absolute;
top: -10px;
right: -10px;
background-color: red;
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
text-align: center;
font-size: 14px;
line-height: 20px;
cursor: pointer;
display: none;
}
.admin-controls {
position: fixed;
top: 10px;
right: 10px;
font-size: 60%;
}
.admin-controls input {
padding: 5px;
font-size: 60%;
}
.admin-controls button {
padding: 5px 10px;
font-size: 60%;
margin-left: 10px;
}
.add-remove-controls {
display: none;
margin-top: 10px;
}
.round-btn {
background-color: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
text-align: center;
font-size: 24px;
line-height: 40px;
cursor: pointer;
margin: 0 10px;
}
#theme-toggle {
position: fixed;
bottom: 10px;
left: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
text-align: center;
font-size: 24px;
line-height: 40px;
cursor: pointer;
}
#dialog-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
#dialog-box {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
#dialog-box label {
display: block;
margin-bottom: 5px;
}
#dialog-box input, #dialog-box select {
width: 100%;
padding: 5px;
margin-bottom: 10px;
}
#dialog-box button {
padding: 5px 10px;
margin-right: 10px;
}

.section {
margin-bottom: 20px;
}

.section-title {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
</style>

</head>
<body class="background">

</div>
<div class="sites">  <!--  00   -->
<header class="header">
  <nav class="navbar">
    <a href="https://ddo.us.kg/" class="brand">
      <img class="debug logo" src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/thumbnails%2F%E5%9B%BE%E6%A0%8701.jpg?1692046715299">
      <span class="debug title">WS01の主页</span>
      <span class="debug beta">beta</span>
      </a>

      </header>


      </div>
    </div>
    <div class="add-remove-controls">
    <button class="round-btn" onclick="showAddDialog()">+</button>
    <button class="round-btn" onclick="toggleRemoveMode()">-</button>    
</div>
<div class="sites1">  <!--  00   -->
<div id="sections-container">
<!-- 分类将在这里动态生成 -->
</div>

<button id="theme-toggle" onclick="toggleTheme()">&#9681;</button>

<div id="dialog-overlay">
<div id="dialog-box">
<label for="name-input">名称</label>
<input type="text" id="name-input">
<label for="url-input">地址</label>
<input type="text" id="url-input">
<label for="category-select">选择分类</label>
<select id="category-select">
<!-- 分类选项将在这里动态生成 -->
</select>
<button onclick="addLink()">确定</button>
<button onclick="hideAddDialog()">取消</button>
</div>
</div>
<div class="copyright">
    <!--    请不要删除 -->
<br />
<!-- body   页脚    -->
<div class="footer">

<input type="password" id="admin-password" placeholder="输入密码">
<button id="admin-mode-btn" onclick="toggleAdminMode()">进入管理模式</button>
    <p>   <a href="https://github.com/hmhm2022/Card-Tab" target="_blank">GitHub</a> 项目

                <!-- 开站时间开始 -->       
             <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span> <script language="javascript"> 
    var now = new Date();
    function createtime(){
        var grt= new Date("09/05/2024 00:00:00");/*---这里是网站的启用时间--*/
        now.setTime(now.getTime()+250);
        days = (now - grt ) / 1000 / 60 / 60 / 24;
        dnum = Math.floor(days);
        hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum);
        hnum = Math.floor(hours);
        if(String(hnum).length ==1 ){hnum = "0" + hnum;}
        minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
        mnum = Math.floor(minutes);
        if(String(mnum).length ==1 ){mnum = "0" + mnum;}
        seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
        snum = Math.round(seconds);
        if(String(snum).length ==1 ){snum = "0" + snum;}
        document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天";
        document.getElementById("times").innerHTML = hnum + "小时" + mnum + "分" + snum + "秒";
    }
    setInterval("createtime()",250); 
</script> 
<!-- 开站时间结束 -->  
</div>

<script>
let isAdmin = false; 
let removeMode = false; 
let isDarkTheme = false; 
let links = []; 
const categories = {
"常·用": [],                   // **编辑自己的网站分类**
"工·具": [],
"影·音": [],
"N·B·A": [],
"论·坛": [],
"主·页": [],
"玩·具": [],
"v·p·s": [],
"下·载": [],
"商·城": [],
"搜·译": [],
"学·习": [],
"其·它": []
};

async function loadLinks() {
const response = await fetch('/api/getLinks?userId=testUser');
links = await response.json();

Object.keys(categories).forEach(key => {
categories[key] = [];
});

links.forEach(link => {
if (categories[link.category]) {
categories[link.category].push(link);
}
});

loadSections();
updateCategorySelect();
// applyTheme();
}

function loadSections() {
const container = document.getElementById('sections-container');
container.innerHTML = '';

Object.keys(categories).forEach(category => {
const section = document.createElement('div');
section.className = 'section';

const title = document.createElement('div');
title.className = 'section-title';
title.textContent = category;

const cardContainer = document.createElement('div');
cardContainer.className = 'card-container';
cardContainer.id = category;

section.appendChild(title);
section.appendChild(cardContainer);

categories[category].forEach(link => {
createCard(link, cardContainer);
});

container.appendChild(section);
});
}

function createCard(link, container) {
const card = document.createElement('div');
card.className = 'card';
card.setAttribute('draggable', isAdmin);

const cardTop = document.createElement('div');
cardTop.className = 'card-top';

const icon = document.createElement('img');
icon.className = 'card-icon';
// icon.src = 'https://www.google.com/s2/favicons?domain=' + link.url;
icon.src = 'https://favicon.zhusl.com/ico?url=' + link.url;
icon.alt = 'Website Icon';

const title = document.createElement('div');
title.className = 'card-title';
title.textContent = link.name;

cardTop.appendChild(icon);
cardTop.appendChild(title);

const url = document.createElement('div');
url.className = 'card-url';
url.textContent = link.url;

card.appendChild(cardTop);
card.appendChild(url);

//  URL 检查和修正
function correctUrl(url) {
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
} else {
return 'http://' + url;
}
}

let correctedUrl = correctUrl(link.url);

if (!isAdmin) {
card.addEventListener('click', () => {
window.open(correctedUrl, '_blank');
});
}

const deleteBtn = document.createElement('button');
deleteBtn.textContent = '–';
deleteBtn.className = 'delete-btn';
deleteBtn.onclick = function (event) {
event.stopPropagation();
removeCard(card);
};
card.appendChild(deleteBtn);

if (isDarkTheme) {
card.style.backgroundColor = '#1e1e1e';
card.style.color = '#ffffff';
card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
} else {
card.style.backgroundColor = '#d4d4d4';
card.style.color = '#333';
card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
}

card.addEventListener('dragstart', dragStart);
card.addEventListener('dragover', dragOver);
card.addEventListener('dragend', dragEnd);
card.addEventListener('drop', drop);

if (isAdmin && removeMode) {
deleteBtn.style.display = 'block';
}

container.appendChild(card);
}

function updateCategorySelect() {
const categorySelect = document.getElementById('category-select');
categorySelect.innerHTML = '';

Object.keys(categories).forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
categorySelect.appendChild(option);
});
}

async function saveLinks() {
let links = [];
for (const category in categories) {
links = links.concat(categories[category]);
}

await fetch('/api/saveOrder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'testUser', links }),
});
}

function addLink() {
const name = document.getElementById('name-input').value;
const url = document.getElementById('url-input').value;
const category = document.getElementById('category-select').value;

if (name && url && category) {
const newLink = { name, url, category };

if (!categories[category]) {
categories[category] = [];
}
categories[category].push(newLink);

const container = document.getElementById(category);
createCard(newLink, container);

saveLinks();

document.getElementById('name-input').value = '';
document.getElementById('url-input').value = '';
hideAddDialog();
}
}

function removeCard(card) {
const url = card.querySelector('.card-url').textContent;
let category;
for (const key in categories) {
const index = categories[key].findIndex(link => link.url === url);
if (index !== -1) {
categories[key].splice(index, 1);
category = key;
break;
}
}
card.remove();

saveLinks();
}

let draggedCard = null;

function dragStart(event) {
if (!isAdmin) return;
draggedCard = event.target;
draggedCard.classList.add('dragging');
event.dataTransfer.effectAllowed = "move";
}

function dragOver(event) {
if (!isAdmin) return;
event.preventDefault();
const target = event.target.closest('.card');
if (target && target !== draggedCard) {
const container = target.parentElement;
const mousePositionX = event.clientX;
const targetRect = target.getBoundingClientRect();

if (mousePositionX < targetRect.left + targetRect.width / 2) {
container.insertBefore(draggedCard, target);
} else {
container.insertBefore(draggedCard, target.nextSibling);
}
}
}

function drop(event) {
if (!isAdmin) return;
event.preventDefault();
draggedCard.classList.remove('dragging');
draggedCard = null;
saveCardOrder();
}

// function dragEnd(event) {
// draggedCard.classList.remove('dragging');
function dragEnd(event) {
if (draggedCard) {
draggedCard.classList.remove('dragging');
}
}

async function saveCardOrder() {
if (!isAdmin) return;
const containers = document.querySelectorAll('.card-container');
let newLinks = [];

containers.forEach(container => {
const category = container.id;
categories[category] = [];
[...container.children].forEach(card => {
const url = card.querySelector('.card-url').textContent;
const name = card.querySelector('.card-title').textContent;
const link = { name, url, category };
categories[category].push(link);
newLinks.push(link);
});
});

links = newLinks;

await fetch('/api/saveOrder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'testUser', links: newLinks }),
});
}


function toggleAdminMode() {
const passwordInput = document.getElementById('admin-password');
const adminBtn = document.getElementById('admin-mode-btn');
const addRemoveControls = document.querySelector('.add-remove-controls');

if (!isAdmin) {
verifyPassword(passwordInput.value)
.then(isValid => {
if (isValid) {
isAdmin = true;
adminBtn.textContent = "退出管理模式";
alert('已进入管理模式');
addRemoveControls.style.display = 'block';
reloadCardsAsAdmin();
} else {
alert('密码错误');
}
});
} else {
isAdmin = false;
removeMode = false;
adminBtn.textContent = "进入管理模式";
alert('已退出管理模式');
addRemoveControls.style.display = 'none';
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(btn => btn.style.display = 'none');
reloadCardsAsAdmin();
}

passwordInput.value = '';
}

function reloadCardsAsAdmin() {
document.querySelectorAll('.card-container').forEach(container => {
container.innerHTML = '';
});
loadLinks().then(() => {
if (isDarkTheme) {
applyDarkTheme();
}
});
}

function applyDarkTheme() {
document.body.style.backgroundColor = '#121212';
document.body.style.color = '#ffffff';
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
card.style.backgroundColor = '#1e1e1e';
card.style.color = '#ffffff';
card.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)';
});
}

function showAddDialog() {
document.getElementById('dialog-overlay').style.display = 'flex';
}

function hideAddDialog() {
document.getElementById('dialog-overlay').style.display = 'none';
}


function toggleRemoveMode() {
removeMode = !removeMode;
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(btn => {
btn.style.display = removeMode ? 'block' : 'none';
});
}

function toggleTheme() {
isDarkTheme = !isDarkTheme;
// 设置暗色主题和亮色主题的背景色
document.body.style.backgroundColor = isDarkTheme ? '#696969' : '#FFFFFF';
// 设置暗色主题和亮色主题的文本颜色
document.body.style.color = isDarkTheme ? '#ffffff' : '#333';

const cards = document.querySelectorAll('.card');
cards.forEach(card => {
// 卡片背景和文本颜色设置
card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#d4d4d4';
card.style.color = isDarkTheme ? '#ffffff' : '#333';
// 卡片阴影的设置,增强暗色主题的阴影
card.style.boxShadow = isDarkTheme
? '0 4px 8px rgba(0, 0, 0, 0.5)'
: '0 4px 8px rgba(0, 0, 0, 0.1)';
});

const dialogBox = document.getElementById('dialog-box');
// 对话框背景和文本颜色设置
dialogBox.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
dialogBox.style.color = isDarkTheme ? '#ffffff' : '#333';

const inputs = dialogBox.querySelectorAll('input, select');
inputs.forEach(input => {
// 输入框背景和文本颜色设置
input.style.backgroundColor = isDarkTheme ? '#333333' : '#ffffff';
input.style.color = isDarkTheme ? '#ffffff' : '#333';
});
}


async function verifyPassword(inputPassword) {
const response = await fetch('/api/verifyPassword', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: inputPassword }),
});
const result = await response.json();
return result.valid;
}

loadLinks();
</script>
</body>
</html>
`;

export default {
async fetch(request, env) {
const url = new URL(request.url);

if (url.pathname === '/') {
return new Response(HTML_CONTENT, {
headers: { 'Content-Type': 'text/html' }
});
}

if (url.pathname === '/api/getLinks') {
const userId = url.searchParams.get('userId');
const links = await env.CARD_ORDER.get(userId); 
return new Response(links || JSON.stringify([]), { status: 200 });
}

if (url.pathname === '/api/saveOrder' && request.method === 'POST') {
const { userId, links } = await request.json();
await env.CARD_ORDER.put(userId, JSON.stringify(links));
return new Response(JSON.stringify({ success: true }), { status: 200 });
}

if (url.pathname === '/api/verifyPassword' && request.method === 'POST') { 
const { password } = await request.json();
const isValid = password === env.ADMIN_PASSWORD; // 从环境变量中获取密码
return new Response(JSON.stringify({ valid: isValid }), {
status: isValid ? 200 : 403,
headers: { 'Content-Type': 'application/json' },
});
}

return new Response('Not Found', { status: 404 });
}
};

二、添加变量
1、先建立一个kv空间,名字为: CARD_ORDER ,再在 变量-KV 命名空间绑定 刚才建立的kv空间。

2、变量-环境变量 中添加 ADMIN_PASSWORD ,值是你的后台管理员密码

3、有域名的可邦定域名,完成。

01

0

评论 (0)

取消
您是第 31265 位访客