部署在CF的轻量化导航页面,可移动卡片式书签,方便管理
侧边栏壁纸
博主昵称
ws01

只是一个休闲小屋!许多好东西记不住,挂上自用而已,基本都是转载的,没太多自己的。

  • 累计撰写 81 篇文章
  • 累计收到 40 条评论

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

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

部署在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' };
  • }
  • };

修改后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="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 20px;
  • border: 1px solid #ccc;
  • border-left: none;
  • background-color: rgba(160, 201, 229, 0.6);
  • 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: rgba(160, 201, 229, 0.6);
  • 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) {
  • }
  • .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: 14px;
  • color: #666;
  • white-space: nowrap;
  • overflow: hidden;
  • text-overflow: ellipsis;
  • text-align: left; /* 添加左对齐 */
  • }
  • /* 超小屏幕 (更小手机) */
  • @media (max-width: 480px) {
  • .card {
  • padding: 6px; /* 进一步减小卡片内边距6 */
  • width: 300px; /* 手机上显示4列,235时显示6列 */
  • }
  • .card-icon {
  • width: 42px;
  • height: 42px;
  • }
  • .card-title {
  • font-size: 36px;
  • }
  • .card-url {
  • font-size: 22px;
  • }
  • }
  • .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>
  • <!-- 开站时间结束 -->
  • <!-- <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 = {};
  • // 添加新分类
  • 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 = 'rgba(160, 201, 229, 0.6)';
  • 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' : '#ffffff';
  • document.body.style.color = isDarkTheme ? '#ffffff' : '#333';
  • const cards = document.querySelectorAll('.card');
  • cards.forEach(card => {
  • card.style.backgroundColor = isDarkTheme ? '#1e1e1e' : 'rgba(160, 201, 229, 0.6)';
  • 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;
  • }
  • // 初始化加载
  • 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、有域名的可邦定域名,完成。

0

评论 (0)

取消