首页
文章导航
留言板
友链
更多
关于
Search
1
常用安装脚本知识 [24年10月27日更新]
70 阅读
2
Win10怎么默认开启数字小键盘
70 阅读
3
网页制作常用代码 不断增加 [2025年7月8更新]
66 阅读
4
总结白嫖】DeepSeek R1 671B满血版-网页版+API版
57 阅读
5
自建不蒜子
56 阅读
默认
日常
学习
技术
登录
Search
标签搜索
cloudflare
白嫖
安装
CF
脚本
壁纸
图片
docker
Linux
Caddy
代码
哪吒
域名
节点
桌面壁纸
手机壁纸
NAT
LXC
优选
HTML
ws01
累计撰写
109
篇文章
累计收到
62
条评论
首页
栏目
默认
日常
学习
技术
页面
文章导航
留言板
友链
关于
搜索到
12
篇与
的结果
2025-07-16
cloudflare上免费部署随机地址生成器
cloudflare免费部署随机地址生成器github项目 这是一个基于 Cloudflare Workers 的随机地址生成器,可以生成全球多个国家的真实地址、姓名和电话号码。 本项目基于 Real-Address-Generator 做了一些样式和逻辑上的调整和优化。一、 主要功能 ,爱好者可以自己修改部署支持多个国家/地区的地址生成根据不同国家生成符合当地特色的姓名生成符合各国格式的电话号码实时地图预览地址保存和管理一键复制信息二、部署到 Cloudflare Workers1、原项目代码// 国家坐标数据 const countryCoordinates = { "US": [{ lat: 37.7749, lng: -122.4194 }, { lat: 34.0522, lng: -118.2437 }], "UK": [{ lat: 51.5074, lng: -0.1278 }, { lat: 53.4808, lng: -2.2426 }], "FR": [{ lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }], "DE": [{ lat: 52.5200, lng: 13.4050 }, { lat: 48.1351, lng: 11.5820 }], "CN": [{ lat: 39.9042, lng: 116.4074 }, { lat: 31.2304, lng: 121.4737 }], "TW": [{ lat: 25.0330, lng: 121.5654 }, { lat: 22.6273, lng: 120.3014 }], "HK": [{ lat: 22.3193, lng: 114.1694 },{ lat: 22.3964, lng: 114.1095 }], "JP": [{ lat: 35.6895, lng: 139.6917 }, { lat: 34.6937, lng: 135.5023 }], "IN": [{ lat: 28.6139, lng: 77.2090 }, { lat: 19.0760, lng: 72.8777 }], "AU": [{ lat: -33.8688, lng: 151.2093 }, { lat: -37.8136, lng: 144.9631 }], "BR": [{ lat: -23.5505, lng: -46.6333 }, { lat: -22.9068, lng: -43.1729 }], "CA": [{ lat: 43.651070, lng: -79.347015 }, { lat: 45.501690, lng: -73.567253 }], "RU": [{ lat: 55.7558, lng: 37.6173 }, { lat: 59.9343, lng: 30.3351 }], "ZA": [{ lat: -33.9249, lng: 18.4241 }, { lat: -26.2041, lng: 28.0473 }], "MX": [{ lat: 19.4326, lng: -99.1332 }, { lat: 20.6597, lng: -103.3496 }], "KR": [{ lat: 37.5665, lng: 126.9780 }, { lat: 35.1796, lng: 129.0756 }], "IT": [{ lat: 41.9028, lng: 12.4964 }, { lat: 45.4642, lng: 9.1900 }], "ES": [{ lat: 40.4168, lng: -3.7038 }, { lat: 41.3851, lng: 2.1734 }], "TR": [{ lat: 41.0082, lng: 28.9784 }, { lat: 39.9334, lng: 32.8597 }], "SA": [{ lat: 24.7136, lng: 46.6753 }, { lat: 21.3891, lng: 39.8579 }], "AR": [{ lat: -34.6037, lng: -58.3816 }, { lat: -31.4201, lng: -64.1888 }], "EG": [{ lat: 30.0444, lng: 31.2357 }, { lat: 31.2156, lng: 29.9553 }], "NG": [{ lat: 6.5244, lng: 3.3792 }, { lat: 9.0579, lng: 7.4951 }], "ID": [{ lat: -6.2088, lng: 106.8456 }, { lat: -7.7956, lng: 110.3695 }] }; // 姓名数据 const namesByCountry = { "CN": { first: ["Li", "Wang", "Zhang", "Liu", "Chen", "Yang", "Huang", "Zhao", "Wu", "Zhou", "Xu", "Sun", "Ma", "Zhu", "Hu", "Guo", "He", "Gao", "Lin", "Zheng"], last: ["Wei", "Fang", "Na", "Xiuying", "Min", "Jing", "Li", "Qiang", "Lei", "Jun", "Yang", "Yong", "Yan", "Jie", "Tao", "Ming", "Chao", "Xiulan", "Xia", "Ping"] }, "JP": { first: ["Sato", "Suzuki", "Takahashi", "Tanaka", "Watanabe", "Ito", "Yamamoto", "Nakamura", "Kobayashi", "Kato"], last: ["Shota", "Ren", "Hina", "Yui", "Hiroto", "Sota", "Yota", "Misaki", "Nanami", "Yuto"] }, "KR": { first: ["Kim", "Lee", "Park", "Choi", "Jung", "Kang", "Jo", "Yoon", "Jang", "Lim"], last: ["Minjun", "Seojun", "Doyun", "Jiho", "Jihun", "Seoyeon", "Seoyun", "Jiwoo", "Seohyun", "Minseo"] }, "TW": { first: ["Chen", "Lin", "Huang", "Chang", "Lee", "Wang", "Wu", "Liu", "Tsai", "Yang"], last: ["Zhiming", "Jianhong", "Junjie", "Yijun", "Shufen", "Meiling", "Yating", "Jiahao", "Zhihao", "Shuhui"] }, "HK": { first: ["Chan", "Lee", "Wong", "Cheung", "Lau", "Wang", "Ng", "Cheng", "Leung", "Ho"], last: ["Chiming", "Kayan", "Junjie", "Wingsze", "Kaming", "Meiling", "Kahao", "Winger", "Chihao", "Shukfan"] }, "US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "UK": { first: ["Smith", "Jones", "Williams", "Taylor", "Brown", "Davies", "Evans", "Wilson", "Thomas", "Roberts"], last: ["Oliver", "Jack", "Harry", "George", "Noah", "Charlie", "Jacob", "Oscar", "William", "Leo"] }, "FR": { first: ["Martin", "Bernard", "Dubois", "Thomas", "Robert", "Richard", "Petit", "Durand", "Leroy", "Moreau"], last: ["Lucas", "Louis", "Gabriel", "Arthur", "Jules", "Hugo", "Leo", "Adam", "Raphael", "Paul"] }, "DE": { first: ["Mueller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Schulz", "Hoffmann"], last: ["Ben", "Paul", "Leon", "Noah", "Luis", "Finn", "Felix", "Jonas", "Maximilian", "Henry"] }, "IT": { first: ["Rossi", "Ferrari", "Russo", "Bianchi", "Romano", "Gallo", "Costa", "Fontana", "Conti", "Esposito"], last: ["Leonardo", "Francesco", "Alessandro", "Lorenzo", "Matteo", "Andrea", "Gabriele", "Marco", "Antonio", "Giuseppe"] }, "ES": { first: ["Garcia", "Rodriguez", "Gonzalez", "Fernandez", "Lopez", "Martinez", "Sanchez", "Perez", "Martin", "Gomez"], last: ["Antonio", "Jose", "Manuel", "Francisco", "David", "Juan", "Miguel", "Javier", "Rafael", "Carlos"] }, "BR": { first: ["Silva", "Santos", "Oliveira", "Souza", "Rodrigues", "Ferreira", "Alves", "Pereira", "Lima", "Gomes"], last: ["Miguel", "Arthur", "Heitor", "Pedro", "Davi", "Gabriel", "Bernardo", "Lucas", "Matheus", "Rafael"] }, "RU": { first: ["Ivanov", "Smirnov", "Kuznetsov", "Popov", "Vasiliev", "Petrov", "Sokolov", "Mikhailov", "Fedorov", "Morozov"], last: ["Alexander", "Dmitry", "Maxim", "Ivan", "Andrey", "Mikhail", "Artem", "Daniel", "Roman", "Sergey"] }, "IN": { first: ["Kumar", "Singh", "Sharma", "Patel", "Gupta", "Shah", "Verma", "Rao", "Reddy", "Joshi"], last: ["Aarav", "Vihaan", "Vivaan", "Aditya", "Arjun", "Reyansh", "Ayaan", "Sai", "Krishna", "Ishaan"] }, "AU": { first: ["Smith", "Jones", "Williams", "Brown", "Wilson", "Taylor", "Johnson", "White", "Anderson", "Thompson"], last: ["Oliver", "William", "Jack", "Noah", "Thomas", "James", "Lucas", "Henry", "Ethan", "Alexander"] }, "CA": { first: ["Smith", "Brown", "Tremblay", "Martin", "Roy", "Wilson", "MacDonald", "Taylor", "Campbell", "Anderson"], last: ["Liam", "Noah", "Oliver", "William", "James", "Benjamin", "Lucas", "Henry", "Theodore", "Jack"] }, "MX": { first: ["Garcia", "Rodriguez", "Martinez", "Lopez", "Gonzalez", "Perez", "Sanchez", "Ramirez", "Torres", "Flores"], last: ["Santiago", "Mateo", "Sebastian", "Leonardo", "Diego", "Daniel", "Gabriel", "Adrian", "David", "Alexander"] }, "TR": { first: ["Yilmaz", "Kaya", "Demir", "Sahin", "Celik", "Yildiz", "Erdogan", "Ozturk", "Aydin", "Ozdemir"], last: ["Yusuf", "Eymen", "Ömer", "Mustafa", "Ali", "Mehmet", "Ahmet", "Emir", "Hamza", "Ibrahim"] }, "SA": { first: ["Al-Saud", "Al-Sheikh", "Al-Rashid", "Al-Qahtani", "Al-Ghamdi", "Al-Zahrani", "Al-Dossari", "Al-Shammari", "Al-Otaibi", "Al-Harbi"], last: ["Mohammed", "Abdullah", "Ahmed", "Ali", "Omar", "Ibrahim", "Khalid", "Hassan", "Fahad", "Abdul"] }, "AR": { first: ["Gonzalez", "Rodriguez", "Garcia", "Fernandez", "Lopez", "Martinez", "Perez", "Romero", "Sanchez", "Diaz"], last: ["Mateo", "Thiago", "Benjamin", "Valentino", "Santiago", "Juan", "Lucas", "Martin", "Nicolas", "Joaquin"] }, "EG": { first: ["Mohamed", "Ahmed", "Mahmoud", "Ibrahim", "Ali", "Hassan", "Hussein", "Mostafa", "Kamal", "Samir"], last: ["Omar", "Youssef", "Adam", "Malik", "Zain", "Hamza", "Kareem", "Hassan", "Ali", "Ibrahim"] }, "NG": { first: ["Okafor", "Adebayo", "Okonkwo", "Eze", "Oluwaseun", "Adegoke", "Afolabi", "Ogunleye", "Adeniyi", "Adesina"], last: ["Oluwadamilare", "Oluwatobiloba", "Ayomide", "Temitope", "Oluwaseun", "Adebayo", "Chibuike", "Chisom", "Chidi", "Obinna"] }, "ID": { first: ["Wijaya", "Kusuma", "Suryanto", "Halim", "Santoso", "Tanaka", "Wibowo", "Susanto", "Hidayat", "Putra"], last: ["Muhammad", "Ahmad", "Abdul", "Aditya", "Budi", "Dimas", "Eko", "Fajar", "Gading", "Hadi"] }, "ZA": { first: ["Nkosi", "Van der Merwe", "Botha", "Mkhize", "Khumalo", "Pretorius", "Venter", "Ndlovu", "Fourie", "Nel"], last: ["Bandile", "Themba", "Sipho", "Thabo", "Jabu", "Mandla", "Blessing", "Gift", "Lucky", "Precious"] } }; // 电话号码格式配置 const phoneFormats = { "US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "CN": { format: "+86 1XX-XXXX-XXXX", mobilePrefix: ["30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "50", "51", "52", "53", "55", "56", "57", "58", "59", "66", "70", "71", "72", "73", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89"] }, "JP": { format: "+81 XX-XXXX-XXXX", mobilePrefix: ["70", "80", "90"] }, "KR": { format: "+82 10-XXXX-XXXX" }, "UK": { format: "+44 7XXX XXXXXX", mobilePrefix: ["7"] }, "FR": { format: "+33 6 XX XX XX XX", mobilePrefix: ["6", "7"] }, "DE": { format: "+49 15X XXXXXXXX", mobilePrefix: ["15", "16", "17"] }, "TW": { format: "+886 9XX-XXX-XXX" }, "HK": { format: "+852 XXXX XXXX", mobilePrefix: ["5", "6", "9"] }, "AU": { format: "+61 4XX XXX XXX", mobilePrefix: ["4"] }, "CA": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[204, 989]] }, "MX": { format: "+52 1XX XXX XXXX" }, "TR": { format: "+90 5XX XXX XXXX", mobilePrefix: ["5"] }, "SA": { format: "+966 5XX XXX XXXX", mobilePrefix: ["5"] }, "AR": { format: "+54 9XX XXXX-XXXX" }, "EG": { format: "+20 1XX XXX XXXX", mobilePrefix: ["1"] }, "NG": { format: "+234 8XX XXX XXXX", mobilePrefix: ["7", "8", "9"] }, "ID": { format: "+62 8XX-XXXX-XXXX", mobilePrefix: ["8"] }, "ZA": { format: "+27 8X XXX XXXX", mobilePrefix: ["6", "7", "8"] } }; // 工具函数 function getRandomLocation(country) { const coordsArray = countryCoordinates[country]; const randomCity = coordsArray[Math.floor(Math.random() * coordsArray.length)]; const lat = randomCity.lat + (Math.random() - 0.5) * 0.1; const lng = randomCity.lng + (Math.random() - 0.5) * 0.1; return { lat, lng }; } function getRandomName(country) { if (!namesByCountry[country]) { return null; } const names = namesByCountry[country]; const firstName = names.first[Math.floor(Math.random() * names.first.length)]; const lastName = names.last[Math.floor(Math.random() * names.last.length)]; return `${firstName} ${lastName}`; } function generateAreaCode(ranges) { const range = ranges[Math.floor(Math.random() * ranges.length)]; const [min, max] = range; return Math.floor(min + Math.random() * (max - min + 1)); } function getRandomPhoneNumber(country) { const format = phoneFormats[country] || phoneFormats["US"]; let phone = format.format; if (format.areaCodeRanges) { const areaCode = generateAreaCode(format.areaCodeRanges); phone = phone.replace("XXX", areaCode); phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } else if (format.mobilePrefix) { const prefix = format.mobilePrefix[Math.floor(Math.random() * format.mobilePrefix.length)]; // 先替换前缀 if (prefix.length === 2) { phone = phone.replace(/XX/, prefix); } else { phone = phone.replace(/X/, prefix); } // 然后替换剩余的X phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } else { phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } return phone; } function isValidAddress(data) { return data && data.address && data.address.house_number && data.address.road && (data.address.city || data.address.town); } // 处理CORS请求的headers const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; // HTML 模板 const htmlContent = `<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>随机地址生成器</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet"> <script> tailwind.config = { theme: { extend: { animation: { 'gradient': 'gradient 8s linear infinite', }, keyframes: { gradient: { '0%, 100%': { 'background-size': '200% 200%', 'background-position': 'left center' }, '50%': { 'background-size': '200% 200%', 'background-position': 'right center' } } } } } } </script> </head> <body class="bg-gradient-to-br from-blue-50 via-white to-blue-50 text-gray-800 min-h-screen font-['Noto_Sans_SC']"> <!-- 头部 --> <header class="bg-gradient-to-r from-blue-500 via-sky-500 to-blue-500 animate-gradient w-full p-6 shadow-lg"> <div class="max-w-4xl mx-auto flex items-center justify-between"> <div class="flex items-center gap-3"> <img src="https://img.freepik.com/premium-vector/minimal-location-map-icon-logo-symbol-vector-design-transparent_965979-613.jpg?w=2000" alt="Logo" class="w-12 h-12 transform hover:scale-105 transition-transform"> <h1 class="text-2xl font-bold text-white">随机地址生成器</h1> </div> <a href="https://github.com/jiangnan1224/AddressGenerator/" target="_blank" class="flex items-center gap-2 text-white hover:text-gray-200 transition-colors"> <svg viewBox="0 0 16 16" class="w-6 h-6 fill-current" aria-hidden="true"> <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path> </svg> <span class="font-medium">GitHub</span> </a> </div> </header> <!-- 主要内容 --> <main class="container mx-auto px-4 py-8 max-w-5xl"> <!-- 加载动画 --> <div id="loading" class="hidden fixed inset-0 bg-white bg-opacity-75 backdrop-blur-sm flex items-center justify-center z-50"> <div class="bg-white rounded-2xl p-8 flex flex-col items-center shadow-2xl"> <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mb-4"></div> <div class="text-gray-800 text-lg font-medium">正在加载...</div> </div> </div> <div id="copied" class="hidden fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-40 transform transition-transform duration-300"> 已复制到剪贴板! </div> <!-- 国家选择 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200 mb-8"> <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> <div class="w-full sm:w-auto flex flex-col sm:flex-row items-start sm:items-center gap-2 flex-grow"> <label for="country" class="text-blue-600 font-bold whitespace-nowrap">选择国家/地区:</label> <select id="country" onchange="changeCountry(this.value)" class="w-full bg-gray-50 border border-gray-200 rounded-xl p-3 text-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"> </select> </div> <button onclick="generateNewAddress(document.getElementById('country').value)" class="w-full sm:w-auto bg-gradient-to-r from-blue-500 to-sky-500 hover:from-blue-600 hover:to-sky-600 text-white font-bold py-3 px-6 rounded-xl transition-all transform hover:scale-105 hover:shadow-lg focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"> 获取新地址 </button> </div> </div> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <!-- 左侧面板 --> <div class="space-y-6"> <!-- 信息卡片 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">个人信息</h2> <div class="space-y-4"> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">姓名:</strong><span id="name" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">性别:</strong><span id="gender" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">电话:</strong><span id="phone" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">地址:</strong><span id="address" class="ml-2"></span> </div> </div> </div> <!-- 保存按钮 --> <button onclick="saveAddress()" class="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white font-bold py-3 px-6 rounded-xl transition-all transform hover:scale-105 hover:shadow-lg focus:ring-2 focus:ring-green-500 focus:ring-opacity-50"> 保存地址 </button> </div> <!-- 右侧面板 --> <div class="space-y-6"> <!-- 地图 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">地图预览</h2> <iframe id="map" class="w-full h-[400px] rounded-xl border border-gray-200"></iframe> </div> </div> </div> <!-- 已保存的地址表格 --> <div class="mt-8 bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">已保存的地址</h2> <div class="overflow-x-auto"> <table class="w-full border-collapse" id="savedAddressesTable"> <thead> <tr class="bg-gradient-to-r from-blue-500 to-sky-500 text-white"> <th class="p-4 text-left rounded-tl-lg">姓名</th> <th class="p-4 text-left">性别</th> <th class="p-4 text-left">电话号码</th> <th class="p-4 text-left">地址</th> <th class="p-4 text-left">备注</th> <th class="p-4 text-left rounded-tr-lg">操作</th> </tr> </thead> <tbody></tbody> </table> </div> </div> </main> <!-- 页脚 --> <footer class="text-center py-8 text-gray-600 text-sm mt-8 bg-gray-50 border-t border-gray-200"> <p class="max-w-4xl mx-auto px-4"> All right reserved <a href="https://github.com/jiangnan1224/AddressGenerator/" target="_blank" class="inline-flex items-center hover:text-blue-600 transition-colors"> <img src="https://pic.imgdb.cn/item/66e7ab36d9c307b7e9cefd24.png" alt="GitHub" class="w-5 h-5 ml-1"> </a> </p> </footer> <script> // 国家数据 const countries = [ { name: "美国", code: "US" }, { name: "英国", code: "UK" }, { name: "法国", code: "FR" }, { name: "德国", code: "DE" }, { name: "中国", code: "CN" }, { name: "中国台湾", code: "TW" }, { name: "中国香港", code: "HK" }, { name: "日本", code: "JP" }, { name: "印度", code: "IN" }, { name: "澳大利亚", code: "AU" }, { name: "巴西", code: "BR" }, { name: "加拿大", code: "CA" }, { name: "俄罗斯", code: "RU" }, { name: "南非", code: "ZA" }, { name: "墨西哥", code: "MX" }, { name: "韩国", code: "KR" }, { name: "意大利", code: "IT" }, { name: "西班牙", code: "ES" }, { name: "土耳其", code: "TR" }, { name: "沙特阿拉伯", code: "SA" }, { name: "阿根廷", code: "AR" }, { name: "埃及", code: "EG" }, { name: "尼日利亚", code: "NG" }, { name: "印度尼西亚", code: "ID" } ]; // 初始化国家选择下拉框 function initCountrySelect() { const select = document.getElementById('country'); countries.forEach(country => { const option = document.createElement('option'); option.value = country.code; option.textContent = country.name; select.appendChild(option); }); } // 复制到剪贴板 function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { const copied = document.getElementById('copied'); copied.classList.remove('hidden'); copied.classList.add('translate-y-0'); copied.classList.remove('translate-y-[-100%]'); setTimeout(() => { copied.classList.add('translate-y-[-100%]'); copied.classList.remove('translate-y-0'); setTimeout(() => { copied.classList.add('hidden'); }, 300); }, 2000); }); } // 显示/隐藏加载动画 function toggleLoading(show) { const loading = document.getElementById('loading'); if (show) { loading.classList.remove('hidden'); } else { loading.classList.add('hidden'); } } // 更改国家 function changeCountry(country) { toggleLoading(true); generateNewAddress(country); } // 保存地址 function saveAddress() { const note = prompt('请输入备注(可以留空)') || ''; const savedAddresses = JSON.parse(localStorage.getItem('savedAddresses') || '[]'); const newEntry = { note: note, name: document.getElementById('name').textContent, gender: document.getElementById('gender').textContent, phone: document.getElementById('phone').textContent, address: document.getElementById('address').textContent }; savedAddresses.push(newEntry); localStorage.setItem('savedAddresses', JSON.stringify(savedAddresses)); renderSavedAddresses(); } // 渲染保存的地址 function renderSavedAddresses() { const savedAddresses = JSON.parse(localStorage.getItem('savedAddresses') || '[]'); const tbody = document.querySelector('#savedAddressesTable tbody'); tbody.innerHTML = ''; savedAddresses.forEach((entry, index) => { const row = tbody.insertRow(); row.className = 'border-t border-gray-200 hover:bg-gray-50 transition-colors'; const cells = ['name', 'gender', 'phone', 'address', 'note', '']; cells.forEach((cell, i) => { const td = row.insertCell(); td.className = 'p-4'; if (i === cells.length - 1) { const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.className = 'bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-all transform hover:scale-105 focus:ring-2 focus:ring-red-500 focus:ring-opacity-50'; deleteBtn.onclick = () => { if (confirm('确定要删除这条记录吗?')) { savedAddresses.splice(index, 1); localStorage.setItem('savedAddresses', JSON.stringify(savedAddresses)); renderSavedAddresses(); } }; td.appendChild(deleteBtn); } else { td.textContent = entry[cell]; } }); }); } // 生成新地址 async function generateNewAddress(country) { if (!country) { country = document.getElementById('country').value; } toggleLoading(true); try { const response = await fetch(\`\${window.location.href}api?country=\${country}\`); const data = await response.json(); if (data.error) { alert(data.error); return; } document.getElementById('name').textContent = data.name; document.getElementById('gender').textContent = data.gender; document.getElementById('phone').textContent = data.phone; document.getElementById('address').textContent = data.address; // 更新地图 document.getElementById('map').src = \`https://www.google.com/maps?q=\${encodeURIComponent(data.address)}&output=embed\`; } catch (error) { console.error('Error fetching address:', error); alert('获取地址时发生错误,请重试'); } finally { toggleLoading(false); } } // 页面加载时初始化 window.onload = function() { initCountrySelect(); generateNewAddress(); renderSavedAddresses(); }; </script> </body> </html>`; async function handleRequest(request) { const url = new URL(request.url); const path = url.pathname; // 处理 API 请求 if (path === '/api') { // 原有的 API 处理逻辑 return handleApiRequest(request); } // 处理根路径请求,返回 HTML 页面 return new Response(htmlContent, { headers: { 'Content-Type': 'text/html;charset=UTF-8', ...corsHeaders } }); } // 将原有的处理逻辑移到单独的函数中 async function handleApiRequest(request) { if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } if (request.method !== 'GET') { return new Response('Method not allowed', { status: 405 }); } const url = new URL(request.url); const country = url.searchParams.get('country') || 'US'; if (!countryCoordinates[country]) { return new Response(JSON.stringify({ error: 'Invalid country code' }), { status: 400, headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } let attempts = 0; const maxAttempts = 20; while (attempts < maxAttempts) { try { const location = getRandomLocation(country); const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${location.lat}&lon=${location.lng}&zoom=18&addressdetails=1`; const response = await fetch(apiUrl, { headers: { 'User-Agent': 'Cloudflare Worker Random Address Generator' } }); const data = await response.json(); if (isValidAddress(data)) { let city = data.address.city || data.address.town || ''; city = city.split(';')[0].trim(); const address = `${data.address.house_number} ${data.address.road}, ${city}, ${data.address.postcode || ''}, ${country}`.replace(/\s+/g, ' ').trim(); const name = getRandomName(country); const gender = Math.random() > 0.5 ? 'Male' : 'Female'; const phone = getRandomPhoneNumber(country); const result = { name, gender, phone, address, coordinates: { lat: location.lat, lng: location.lng } }; return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } attempts++; if (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); } } catch (error) { attempts++; if (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); } } } return new Response(JSON.stringify({ error: 'Failed to generate valid address after multiple attempts' }), { status: 500, headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } // 注册事件监听器 addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); }); {dotted startColor="#ff6c6c" endColor="#1989fa"/}2、修改后的代码,各有所好,当然也可以找一下其它人修改的代码2025年07月16日更新,界面调整,添加多个国家和地区// 国家坐标数据 const countryCoordinates = { "US": [{ lat: 37.7749, lng: -122.4194 }, { lat: 34.0522, lng: -118.2437 }, { lat: 32.7767, lng: -96.7970 }, { lat: 41.8781, lng: -87.6298 }, { lat: 29.7604, lng: -95.3698 }, { lat: 33.4484, lng: -112.0740 }, { lat: 39.9526, lng: -75.1652 }, { lat: 29.4241, lng: -98.4936 }, { lat: 32.7157, lng: -117.1611 }, { lat: 47.6062, lng: -122.3321 }], "NY US": [{ lat: 40.7128, lng: -74.0060 }], "California US": [{ lat: 34.0522, lng: -118.2437 }], "Texas US": [{ lat: 32.7767, lng: -96.7970 }], "CA US": [{ lat: 37.3382, lng: -121.8863 }], "Illinois US": [{ lat: 41.8781, lng: -87.6298 }], // 添加的城市坐标:休斯敦、菲尼克斯、费城、圣安东尼奥、圣地亚哥、西雅图 "TX US": [{ lat: 29.7604, lng: -95.3698 }], "Arizona US": [{ lat: 33.4484, lng: -112.0740 }], "Pennsylvania US": [{ lat: 39.9526, lng: -75.1652 }], "Tx US": [{ lat: 29.4241, lng: -98.4936 }], "Ca US": [{ lat: 32.7157, lng: -117.1611 }], "Washington US": [{ lat: 47.6062, lng: -122.3321 }], "UK": [{ lat: 51.5074, lng: -0.1278 }, { lat: 53.4808, lng: -2.2426 }], "FR": [{ lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }, { lat: 43.2965, lng: 5.3698 }], "DE": [{ lat: 52.5200, lng: 13.4050 }, { lat: 48.1351, lng: 11.5820 }], "CN": [{ lat: 39.9042, lng: 116.4074 }, { lat: 31.2304, lng: 121.4737 }], "TW": [{ lat: 25.0330, lng: 121.5654 }, { lat: 22.6273, lng: 120.3014 }], "HK": [{ lat: 22.3193, lng: 114.1694 },{ lat: 22.3964, lng: 114.1095 }], "JP": [{ lat: 35.6895, lng: 139.6917 }, { lat: 34.6937, lng: 135.5023 }], "IN": [{ lat: 28.6139, lng: 77.2090 }, { lat: 19.0760, lng: 72.8777 }], "AU": [{ lat: -33.8688, lng: 151.2093 }, { lat: -37.8136, lng: 144.9631 }], "BR": [{ lat: -23.5505, lng: -46.6333 }, { lat: -22.9068, lng: -43.1729 }], "CA": [{ lat: 43.651070, lng: -79.347015 }, { lat: 45.501690, lng: -73.567253 }], "RU": [{ lat: 55.7558, lng: 37.6173 }, { lat: 59.9343, lng: 30.3351 }], "ZA": [{ lat: -33.9249, lng: 18.4241 }, { lat: -26.2041, lng: 28.0473 }], "MX": [{ lat: 19.4326, lng: -99.1332 }, { lat: 20.6597, lng: -103.3496 }], "KR": [{ lat: 37.5665, lng: 126.9780 }, { lat: 35.1796, lng: 129.0756 }], "IT": [{ lat: 41.9028, lng: 12.4964 }, { lat: 45.4642, lng: 9.1900 }], "ES": [{ lat: 40.4168, lng: -3.7038 }, { lat: 41.3851, lng: 2.1734 }], "TR": [{ lat: 41.0082, lng: 28.9784 }, { lat: 39.9334, lng: 32.8597 }], "SA": [{ lat: 24.7136, lng: 46.6753 }, { lat: 21.3891, lng: 39.8579 }], "AR": [{ lat: -34.6037, lng: -58.3816 }, { lat: -31.4201, lng: -64.1888 }], "EG": [{ lat: 30.0444, lng: 31.2357 }, { lat: 31.2156, lng: 29.9553 }], "NG": [{ lat: 6.5244, lng: 3.3792 }, { lat: 9.0579, lng: 7.4951 }], "ID": [{ lat: -6.2088, lng: 106.8456 }, { lat: -7.7956, lng: 110.3695 }], "VN": [{ lat: 21.0285, lng: 105.8048 }, { lat: 10.7626, lng: 106.6602 }], // 添加越南坐标数据 // 新增城市坐标 "Berlin DE": [{ lat: 52.5200, lng: 13.4050 }], "Bavaria DE": [{ lat: 48.1351, lng: 11.5820 }], "Île-de-France FR": [{ lat: 48.8566, lng: 2.3522 }], "Provence-Alpes-Côte d'Azur FR": [{ lat: 43.2965, lng: 5.3698 }], "London UK": [{ lat: 51.5074, lng: -0.1278 }], "Seoul KR": [{ lat: 37.5665, lng: 126.9780 }], "Ontario CA": [{ lat: 43.651070, lng: -79.347015 }], "Comunidad de Madrid ES": [{ lat: 40.4168, lng: -3.7038 }], "Cataluña ES": [{ lat: 41.3851, lng: 2.1734 }], "Tokyo JP": [{ lat: 35.6895, lng: 139.6917 }], "Osaka JP": [{ lat: 34.6937, lng: 135.5023 }], }; // 姓名数据 const namesByCountry = { "US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "CN": { first: ["李", "王", "张", "刘", "陈", "杨", "黄", "赵", "吴", "周", "孙", "苏", "马", "朱", "候", "郭", "黑", "高", "林", "钱", "冯", "卫", "蒋", "许", "吕", "施", "曹", "严", "魏", "陶", "方", "雷"], last: ["力", "斌", "那", "真", "杰", "京", "丽", "晴", "来", "东", "勇", "仕", "云", "洁", "山", "伟", "明", "亮", "刚", "飞", "英", "兴", "浩", "曙光", "平", "志远", "志珍", "第一", "建国", "玉仙", "现红", "瑞山", "一山", "新丽", "思思", "青凤", "秀美"] }, "JP": { first: ["Sato", "Suzuki", "Takahashi", "Tanaka", "Watanabe", "Ito", "Yamamoto", "Nakamura", "Kobayashi", "Kato"], last: ["Shota", "Ren", "Hina", "Yui", "Hiroto", "Sota", "Yota", "Misaki", "Nanami", "Yuto"] }, "KR": { first: ["Kim", "Lee", "Park", "Choi", "Jung", "Kang", "Jo", "Yoon", "Jang", "Lim"], last: ["Minjun", "Seojun", "Doyun", "Jiho", "Jihun", "Seoyeon", "Seoyun", "Jiwoo", "Seohyun", "Minseo"] }, "TW": { first: ["Chen", "Lin", "Huang", "Chang", "Lee", "Wang", "Wu", "Liu", "Tsai", "Yang"], last: ["Zhiming", "Jianhong", "Junjie", "Yijun", "Shufen", "Meiling", "Yating", "Jiahao", "Zhihao", "Shuhui"] }, "HK": { first: ["Chan", "Lee", "Wong", "Cheung", "Lau", "Wang", "Ng", "Cheng", "Leung", "Ho"], last: ["Chiming", "Kayan", "Junjie", "Wingsze", "Kaming", "Meiling", "Kahao", "Winger", "Chihao", "Shukfan"] }, "UK": { first: ["Smith", "Jones", "Williams", "Taylor", "Brown", "Davies", "Evans", "Wilson", "Thomas", "Roberts"], last: ["Oliver", "Jack", "Harry", "George", "Noah", "Charlie", "Jacob", "Oscar", "William", "Leo"] }, "FR": { first: ["Martin", "Bernard", "Dubois", "Thomas", "Robert", "Richard", "Petit", "Durand", "Leroy", "Moreau"], last: ["Lucas", "Louis", "Gabriel", "Arthur", "Jules", "Hugo", "Leo", "Adam", "Raphael", "Paul"] }, "DE": { first: ["Mueller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Schulz", "Hoffmann"], last: ["Ben", "Paul", "Leon", "Noah", "Luis", "Finn", "Felix", "Jonas", "Maximilian", "Henry"] }, "IT": { first: ["Rossi", "Ferrari", "Russo", "Bianchi", "Romano", "Gallo", "Costa", "Fontana", "Conti", "Esposito"], last: ["Leonardo", "Francesco", "Alessandro", "Lorenzo", "Matteo", "Andrea", "Gabriele", "Marco", "Antonio", "Giuseppe"] }, "ES": { first: ["Garcia", "Rodriguez", "Gonzalez", "Fernandez", "Lopez", "Martinez", "Sanchez", "Perez", "Martin", "Gomez"], last: ["Antonio", "Jose", "Manuel", "Francisco", "David", "Juan", "Miguel", "Javier", "Rafael", "Carlos"] }, "BR": { first: ["Silva", "Santos", "Oliveira", "Souza", "Rodrigues", "Ferreira", "Alves", "Pereira", "Lima", "Gomes"], last: ["Miguel", "Arthur", "Heitor", "Pedro", "Davi", "Gabriel", "Bernardo", "Lucas", "Matheus", "Rafael"] }, "RU": { first: ["Ivanov", "Smirnov", "Kuznetsov", "Popov", "Vasiliev", "Petrov", "Sokolov", "Mikhailov", "Fedorov", "Morozov"], last: ["Alexander", "Dmitry", "Maxim", "Ivan", "Andrey", "Mikhail", "Artem", "Daniel", "Roman", "Sergey"] }, "IN": { first: ["Kumar", "Singh", "Sharma", "Patel", "Gupta", "Shah", "Verma", "Rao", "Reddy", "Joshi"], last: ["Aarav", "Vihaan", "Vivaan", "Aditya", "Arjun", "Reyansh", "Ayaan", "Sai", "Krishna", "Ishaan"] }, "AU": { first: ["Smith", "Jones", "Williams", "Brown", "Wilson", "Taylor", "Johnson", "White", "Anderson", "Thompson"], last: ["Oliver", "William", "Jack", "Noah", "Thomas", "James", "Lucas", "Henry", "Ethan", "Alexander"] }, "CA": { first: ["Smith", "Brown", "Tremblay", "Martin", "Roy", "Wilson", "MacDonald", "Taylor", "Campbell", "Anderson"], last: ["Liam", "Noah", "Oliver", "William", "James", "Benjamin", "Lucas", "Henry", "Theodore", "Jack"] }, "MX": { first: ["Garcia", "Rodriguez", "Martinez", "Lopez", "Gonzalez", "Perez", "Sanchez", "Ramirez", "Torres", "Flores"], last: ["Santiago", "Mateo", "Sebastian", "Leonardo", "Diego", "Daniel", "Gabriel", "Adrian", "David", "Alexander"] }, "TR": { first: ["Yilmaz", "Kaya", "Demir", "Sahin", "Celik", "Yildiz", "Erdogan", "Ozturk", "Aydin", "Ozdemir"], last: ["Yusuf", "Eymen", "Ömer", "Mustafa", "Ali", "Mehmet", "Ahmet", "Emir", "Hamza", "Ibrahim"] }, "SA": { first: ["Al-Saud", "Al-Sheikh", "Al-Rashid", "Al-Qahtani", "Al-Ghamdi", "Al-Zahrani", "Al-Dossari", "Al-Shammari", "Al-Otaibi", "Al-Harbi"], last: ["Mohammed", "Abdullah", "Ahmed", "Ali", "Omar", "Ibrahim", "Khalid", "Hassan", "Fahad", "Abdul"] }, "AR": { first: ["Gonzalez", "Rodriguez", "Garcia", "Fernandez", "Lopez", "Martinez", "Perez", "Romero", "Sanchez", "Diaz"], last: ["Mateo", "Thiago", "Benjamin", "Valentino", "Santiago", "Juan", "Lucas", "Martin", "Nicolas", "Joaquin"] }, "EG": { first: ["Mohamed", "Ahmed", "Mahmoud", "Ibrahim", "Ali", "Hassan", "Hussein", "Mostafa", "Kamal", "Samir"], last: ["Omar", "Youssef", "Adam", "Malik", "Zain", "Hamza", "Kareem", "Hassan", "Ali", "Ibrahim"] }, "NG": { first: ["Okafor", "Adebayo", "Okonkwo", "Eze", "Oluwaseun", "Adegoke", "Afolabi", "Ogunleye", "Adeniyi", "Adesina"], last: ["Oluwadamilare", "Oluwatobiloba", "Ayomide", "Temitope", "Oluwaseun", "Adebayo", "Chibuike", "Chisom", "Chidi", "Obinna"] }, "ID": { first: ["Wijaya", "Kusuma", "Suryanto", "Halim", "Santoso", "Tanaka", "Wibowo", "Susanto", "Hidayat", "Putra"], last: ["Muhammad", "Ahmad", "Abdul", "Aditya", "Budi", "Dimas", "Eko", "Fajar", "Gading", "Hadi"] }, "ZA": { first: ["Nkosi", "Van der Merwe", "Botha", "Mkhize", "Khumalo", "Pretorius", "Venter", "Ndlovu", "Fourie", "Nel"], last: ["Bandile", "Themba", "Sipho", "Thabo", "Jabu", "Mandla", "Blessing", "Gift", "Lucky", "Precious"] }, "VN": { first: ["Nguyen", "Tran", "Le", "Pham", "Hoang", "Vu", "Do", "Dao", "Bui", "Dang"], last: ["Van", "Minh", "Thanh", "Ngoc", "Huu", "Quoc", "Xuan", "Duc", "Tuan", "Khanh"] } }; // 电话号码格式配置 const phoneFormats = { "US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "CN": { format: "+86 1XX-XXXX-XXXX", mobilePrefix: ["30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "50", "51", "52", "53", "55", "56", "57", "58", "59", "66", "70", "71", "72", "73", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89"] }, "JP": { format: "+81 XX-XXXX-XXXX", mobilePrefix: ["70", "80", "90"] }, "KR": { format: "+82 10-XXXX-XXXX" }, "UK": { format: "+44 7XXX XXXXXX", mobilePrefix: ["7"] }, "FR": { format: "+33 6 XX XX XX XX", mobilePrefix: ["6", "7"] }, "DE": { format: "+49 15X XXXXXXXX", mobilePrefix: ["15", "16", "17"] }, "TW": { format: "+886 9XX-XXX-XXX" }, "HK": { format: "+852 XXXX XXXX", mobilePrefix: ["5", "6", "9"] }, "AU": { format: "+61 4XX XXX XXX", mobilePrefix: ["4"] }, "CA": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[204, 989]] }, "MX": { format: "+52 1XX XXX XXXX" }, "TR": { format: "+90 5XX XXX XXXX", mobilePrefix: ["5"] }, "SA": { format: "+966 5XX XXX XXXX", mobilePrefix: ["5"] }, "AR": { format: "+54 9XX XXXX-XXXX" }, "EG": { format: "+20 1XX XXX XXXX", mobilePrefix: ["1"] }, "NG": { format: "+234 8XX XXX XXXX", mobilePrefix: ["7", "8", "9"] }, "ID": { format: "+62 8XX-XXXX-XXXX", mobilePrefix: ["8"] }, "ZA": { format: "+27 8X XXX XXXX", mobilePrefix: ["6", "7", "8"] }, "VN": { format: "+84 9X XXX XXXX", mobilePrefix: ["9", "8"] } }; // 新增加的所有美国地区别名都指向同一个 US 数据 namesByCountry["NY US"] = namesByCountry["US"]; namesByCountry["California US"] = namesByCountry["US"]; namesByCountry["Texas US"] = namesByCountry["US"]; namesByCountry["CA US"] = namesByCountry["US"]; namesByCountry["Illinois US"] = namesByCountry["US"]; namesByCountry["TX US"] = namesByCountry["US"]; namesByCountry["Arizona US"] = namesByCountry["US"]; namesByCountry["Pennsylvania US"] = namesByCountry["US"]; namesByCountry["Tx US"] = namesByCountry["US"]; namesByCountry["Ca US"] = namesByCountry["US"]; namesByCountry["Washington US"] = namesByCountry["US"]; namesByCountry["Berlin DE"] = namesByCountry["DE"]; namesByCountry["Bavaria DE"] = namesByCountry["DE"]; namesByCountry["Île-de-France FR"] = namesByCountry["FR"]; namesByCountry["Provence-Alpes-Côte d'Azur FR"] = namesByCountry["FR"]; namesByCountry["London UK"] = namesByCountry["UK"]; namesByCountry["Seoul KR"] = namesByCountry["KR"]; namesByCountry["Ontario CA"] = namesByCountry["CA"]; namesByCountry["Comunidad de Madrid ES"] = namesByCountry["ES"]; namesByCountry["Cataluña ES"] = namesByCountry["ES"]; namesByCountry["Tokyo JP"] = namesByCountry["JP"]; namesByCountry["Osaka JP"] = namesByCountry["JP"]; // 新增加的美国所有电话别名都指向同一个 US 数据 phoneFormats["NY US"] = phoneFormats["US"]; phoneFormats["California US"] = phoneFormats["US"]; phoneFormats["Texas US"] = phoneFormats["US"]; phoneFormats["CA US"] = phoneFormats["US"]; phoneFormats["Illinois US"] = phoneFormats["US"]; phoneFormats["TX US"] = phoneFormats["US"]; phoneFormats["Arizona US"] = phoneFormats["US"]; phoneFormats["Pennsylvania US"] = phoneFormats["US"]; phoneFormats["Tx US"] = phoneFormats["US"]; phoneFormats["Ca US"] = phoneFormats["US"]; phoneFormats["Washington US"] = phoneFormats["US"]; phoneFormats["Berlin DE"] = phoneFormats["DE"]; phoneFormats["Bavaria DE"] = phoneFormats["DE"]; phoneFormats["Île-de-France FR"] = phoneFormats["FR"]; phoneFormats["Provence-Alpes-Côte d'Azur FR"] = phoneFormats["FR"]; phoneFormats["London UK"] = phoneFormats["UK"]; phoneFormats["Seoul KR"] = phoneFormats["KR"]; phoneFormats["Ontario CA"] = phoneFormats["CA"]; phoneFormats["Comunidad de Madrid ES"] = phoneFormats["ES"]; phoneFormats["Cataluña ES"] = phoneFormats["ES"]; phoneFormats["Tokyo JP"] = phoneFormats["JP"]; phoneFormats["Osaka JP"] = phoneFormats["JP"]; // 工具函数 function getRandomLocation(country) { const coordsArray = countryCoordinates[country]; const randomCity = coordsArray[Math.floor(Math.random() * coordsArray.length)]; const lat = randomCity.lat + (Math.random() - 0.5) * 0.1; const lng = randomCity.lng + (Math.random() - 0.5) * 0.1; return { lat, lng }; } function getRandomName(country) { if (!namesByCountry[country]) { return null; } const names = namesByCountry[country]; const firstName = names.first[Math.floor(Math.random() * names.first.length)]; const lastName = names.last[Math.floor(Math.random() * names.last.length)]; return `${firstName} ${lastName}`; } function generateAreaCode(ranges) { const range = ranges[Math.floor(Math.random() * ranges.length)]; const [min, max] = range; return Math.floor(min + Math.random() * (max - min + 1)); } function getRandomPhoneNumber(country) { const format = phoneFormats[country] || phoneFormats["US"]; let phone = format.format; if (format.areaCodeRanges) { const areaCode = generateAreaCode(format.areaCodeRanges); phone = phone.replace("XXX", areaCode); phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } else if (format.mobilePrefix) { const prefix = format.mobilePrefix[Math.floor(Math.random() * format.mobilePrefix.length)]; // 先替换前缀 if (prefix.length === 2) { phone = phone.replace(/XX/, prefix); } else { phone = phone.replace(/X/, prefix); } // 然后替换剩余的X phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } else { phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } return phone; } function isValidAddress(data) { return data && data.address && data.address.house_number && data.address.road && (data.address.city || data.address.town); } // 处理CORS请求的headers const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; // HTML 模板 const htmlContent = `<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>随机地址生成器</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet"> <script> tailwind.config = { theme: { extend: { animation: { 'gradient': 'gradient 8s linear infinite', }, keyframes: { gradient: { '0%, 100%': { 'background-size': '200% 200%', 'background-position': 'left center' }, '50%': { 'background-size': '200% 200%', 'background-position': 'right center' } } } } } } </script> </head> <link rel="shortcut 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>"> <body class="bg-gradient-to-br from-blue-50 via-white to-blue-50 text-gray-800 min-h-screen font-['Noto_Sans_SC']"> <!-- 头部 --> <header class="bg-gradient-to-r from-blue-500 via-sky-500 to-blue-500 animate-gradient w-full p-6 shadow-lg"> <div class="max-w-4xl mx-auto flex items-center justify-between"> <div class="flex items-center gap-3"> <h1 class="text-2xl font-bold text-white">随机地址生成器</h1> </div> <div class="flex items-center gap-3"> <a href="/" target="_blank" class="flex items-center gap-2 text-white hover:text-gray-200 transition-colors"> <svg viewBox="0 0 16 16" class="w-6 h-6 fill-current" aria-hidden="true"> <img src="https://img.freepik.com/premium-vector/minimal-location-map-icon-logo-symbol-vector-design-transparent_965979-613.jpg?w=2000" alt="Logo" class="w-8 h-8 transform hover:scale-105 transition-transform"> </svg> </a> <a href="/about" class="flex items-center gap-2 text-white hover:text-gray-200 transition-colors"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16"> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/> <path d="m8.93 6.588-2.29.287-.082.38.45.083c.296.07.366.177.246.475l-.136.283-.14.064c-.296.136-.494.397-.56.745-.107 1.044.44 1.43 1.234 1.43.46.002.832-.166 1.06-.432.246-.28.334-.677.256-1.176-.09-.564-.426-.977-.977-.977-.642 0-.962.514-.962 1.176 0 .242.047.466.126.652.126.309.34.564.626.652.296.09.662.034.962-.126.296-.16.494-.432.56-.744.107-.564-.246-1.098-.832-1.098-.426 0-.764.29-.962.652-.107.19-.186.408-.246.652-.06.244-.082.49-.082.744 0 1.134.732 2.042 1.634 2.042.902 0 1.634-.908 1.634-2.042 0-1.134-.732-2.042-1.634-2.042zm-.634 5.5c-.344 0-.6-.256-.6-.588 0-.332.256-.588.6-.588.344 0 .6.256.6.588 0 .332-.256.588-.6.588z"/> </svg> <span class="font-medium">关于</span> </a> </div> </div> </header> <!-- 关于页面内容 --> <div id="about" class="hidden max-w-4xl mx-auto px-4 py-8"> <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-200"> <h2 class="text-2xl font-bold mb-6 text-blue-600">关于本项目</h2> <div class="prose max-w-none"> <p class="mb-4">随机地址生成器是一个实用工具,可以帮助用户快速生成大多数常用国家和地区的随机地址信息【根据需要,国家和地区不断完善中】。该工具适用于测试、开发以及其他需要模拟地址数据的场景。</p> <p class="mb-4">本项目使用 Cloudflare Workers 技术构建,结合 OpenStreetMap 的地理编码服务,能够实时生成准确的地址信息。通过简单的界面操作,您可以轻松获取不同国家的地址、电话号码和个人信息。</p> <h3 class="text-xl font-semibold mt-6 mb-3 text-blue-600">主要功能</h3> <ul class="list-disc pl-6 space-y-2 mb-4"> <li>支持多个国家和地区的地址生成</li> <li>自动生成姓名、性别、电话号码等个人信息</li> <li>技术有限,英国、日本等一些国家的邮编,『地址中倒数第2项提取』</li> <li>集成地图预览功能</li> <li>支持保存常用地址</li> <li>响应式设计适配各种设备</li> </ul> <h3 class="text-xl font-semibold mt-6 mb-3 text-blue-600">技术特点</h3> <ul class="list-disc pl-6 space-y-2 mb-4"> <li>基于 Cloudflare Workers 构建,实现无服务器架构</li> <li>使用 Tailwind CSS 实现现代化 UI 设计</li> <li>集成 OpenStreetMap 地理编码 API</li> <li>前端完全静态化,无需后端数据库支持</li> <li>本地存储保存历史记录</li> </ul> <h3 class="text-xl font-semibold mt-6 mb-3 text-blue-600">使用说明</h3> <p class="mb-4">选择国家或地区后点击“获取新地址”按钮即可生成随机地址信息。您可以通过点击各项信息将其复制到剪贴板,也可以点击“保存地址”按钮将当前地址保存到本地浏览器。</p> <p class="mb-4">在保存的地址列表中,您可以查看所有已保存的地址,并可随时删除不需要的记录。</p> <h3 class="text-xl font-semibold mt-6 mb-3 text-blue-600">联系方式</h3> <p>如有任何问题或建议,请访问我们的<a href="https://www.199881.xyz/" target="_blank" class="text-blue-600 hover:underline">官方网站</a>或通过 GitHub 联系我们。</p> </div> </div> </div> <!-- 主要内容 --> <main id="main-content" class="container mx-auto px-4 py-8 max-w-5xl"> <!-- 加载动画 --> <div id="loading" class="hidden fixed inset-0 bg-white bg-opacity-75 backdrop-blur-sm flex items-center justify-center z-50"> <div class="bg-white rounded-2xl p-8 flex flex-col items-center shadow-2xl"> <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mb-4"></div> <div class="text-gray-800 text-lg font-medium">正在加载...</div> </div> </div> <div id="copied" class="hidden fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-40 transform transition-transform duration-300"> 已复制到剪贴板! </div> <!-- 国家选择 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200 mb-8"> <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> <div class="w-full sm:w-auto flex flex-col sm:flex-row items-start sm:items-center gap-2 flex-grow"> <label for="country" class="text-blue-600 font-bold whitespace-nowrap">选择国家/地区:</label> <select id="country" onchange="changeCountry(this.value)" class="w-full bg-gray-50 border border-gray-200 rounded-xl p-3 text-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"> </select> </div> <button onclick="generateNewAddress(document.getElementById('country').value)" class="w-full sm:w-auto bg-gradient-to-r from-blue-500 to-sky-500 hover:from-blue-600 hover:to-sky-600 text-white font-bold py-3 px-6 rounded-xl transition-all transform hover:scale-105 hover:shadow-lg focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"> 获取新地址 </button> </div> </div> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <!-- 左侧面板 --> <div class="space-y-2"> <!-- 信息卡片 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">个人信息</h2> <div class="space-y-2"> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">姓名:</strong><span id="name" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">性别:</strong><span id="gender" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">电话:</strong><span id="phone" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">邮编:</strong><span id="postcode" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">地址:</strong><span id="address" class="ml-2"></span> </div> </div> </div> <!-- 保存按钮 --> <button onclick="saveAddress()" class="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white font-bold py-3 px-6 rounded-xl transition-all transform hover:scale-105 hover:shadow-lg focus:ring-2 focus:ring-green-500 focus:ring-opacity-50"> 保存地址 </button> </div> <!-- 右侧面板 --> <div class="space-y-2"> <!-- 地图 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">地图预览</h2> <iframe id="map" class="w-full h-[372px] rounded-xl border border-gray-200"></iframe> </div> </div> </div> <!-- 已保存的地址表格 --> <div class="mt-8 bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">已保存的地址</h2> <div class="overflow-x-auto"> <table class="w-full border-collapse" id="savedAddressesTable"> <thead> <tr class="bg-gradient-to-r from-blue-500 to-sky-500 text-white"> <th class="p-4 text-left rounded-tl-lg">姓名</th> <th class="p-4 text-left">性别</th> <th class="p-4 text-left">电话</th> <th class="p-4 text-left">地址</th> <th class="p-4 text-left">备注</th> <th class="p-4 text-left rounded-tr-lg">操作</th> </tr> </thead> <tbody></tbody> </table> </div> </div> </main> <!-- 页脚 --> <footer class="text-center py-8 text-gray-600 text-sm mt-8 bg-gray-50 border-t border-gray-200"> <p class="max-w-4xl mx-auto px-4"> 总访问量 <span id="busuanzi_site_pv"></span> 次 | <span id="timeDate">载入天数...</span> | <a href="https://boke.199881.xyz/" target="_blank"> <span style="color: blue;">博客 | <a href="https://www.199881.xyz/" target="_blank"> <span style="color: green;">导航 <a href="https://github.com/jiangnan1224/AddressGenerator/" target="_blank" class="inline-flex items-center hover:text-blue-600 transition-colors"> <img src="https://pic.imgdb.cn/item/66e7ab36d9c307b7e9cefd24.png" alt="GitHub" class="w-5 h-5 ml-1"> </a> <script language="javascript"> var now = new Date(); function createtime(){ var grt= new Date("04/23/2025 00:00:00");/*---这里是网站的启用时间--*/ now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); document.getElementById("timeDate").innerHTML = "运行"+dnum+"天"; } setInterval("createtime()",250); </script> <script defer src="https://bsz.211119.xyz/js"></script> </p> </footer> <script> // 国家数据 const countries = [ { name: "美国", code: "US" }, { name: "美国纽约", code: "NY US" }, { name: "美国洛杉矶", code: "California US" }, { name: "美国达拉斯", code: "Texas US" }, { name: "美国圣何塞", code: "CA US" }, { name: "美国芝加哥", code: "Illinois US" }, { name: "美国休斯敦", code: "TX US" }, { name: "美国菲尼克斯", code: "Arizona US" }, { name: "美国费城", code: "Pennsylvania US" }, { name: "美国圣安东尼奥", code: "Tx US" }, { name: "美国圣地亚哥", code: "Ca US" }, { name: "美国西雅图", code: "Washington US" }, { name: "德国", code: "DE" }, { name: "德国柏林", code: "Berlin DE" }, { name: "德国慕尼黑", code: "Bavaria DE" }, { name: "法国", code: "FR" }, { name: "法国巴黎", code: "Île-de-France FR" }, { name: "法国马赛", code: "Provence-Alpes-Côte d'Azur FR" }, { name: "越南", code: "VN" }, { name: "巴西", code: "BR" }, { name: "墨西哥", code: "MX" }, { name: "韩国", code: "Seoul KR" }, { name: "韩国首尔", code: "KR" }, { name: "意大利", code: "IT" }, { name: "西班牙", code: "ES" }, { name: "西班牙马德里", code: "Comunidad de Madrid ES" }, { name: "西班牙马德里巴塞罗那", code: "Cataluña ES" }, { name: "土耳其", code: "TR" }, { name: "埃及", code: "EG" }, { name: "印度尼西亚", code: "ID" }, // 以下邮编末知 { name: "英国", code: "UK" }, { name: "英国伦敦", code: "London UK" }, { name: "中国", code: "CN" }, { name: "中国台湾", code: "TW" }, { name: "中国香港", code: "HK" }, { name: "日本", code: "JP" }, { name: "日本东京", code: "Tokyo JP" }, { name: "日本大坂", code: "Osaka JP" }, { name: "印度", code: "IN" }, { name: "澳大利亚", code: "AU" }, { name: "加拿大", code: "CA" }, { name: "加拿大多伦多", code: "Ontario CA" }, { name: "俄罗斯", code: "RU" }, { name: "南非", code: "ZA" }, { name: "沙特阿拉伯", code: "SA" }, { name: "阿根廷", code: "AR" }, { name: "尼日利亚", code: "NG" } ]; // 初始化国家选择下拉框 function initCountrySelect() { const select = document.getElementById('country'); countries.forEach(country => { const option = document.createElement('option'); option.value = country.code; option.textContent = country.name; select.appendChild(option); }); // 添加返回主页按钮的点击事件 document.querySelector('a[href="/about"]').addEventListener('click', function(e) { e.preventDefault(); showAbout(); }); // 添加返回主页按钮的点击事件(在页脚也添加了一个) document.querySelector('a[href="/"]').addEventListener('click', function(e) { e.preventDefault(); showHome(); }); } // 复制到剪贴板 function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { const copied = document.getElementById('copied'); copied.classList.remove('hidden'); copied.classList.add('translate-y-0'); copied.classList.remove('translate-y-[-100%]'); setTimeout(() => { copied.classList.add('translate-y-[-100%]'); copied.classList.remove('translate-y-0'); setTimeout(() => { copied.classList.add('hidden'); }, 300); }, 2000); }); } // 显示/隐藏加载动画 function toggleLoading(show) { const loading = document.getElementById('loading'); if (show) { loading.classList.remove('hidden'); } else { loading.classList.add('hidden'); } } // 更改国家 function changeCountry(country) { toggleLoading(true); generateNewAddress(country); } // 保存地址 function saveAddress() { const note = prompt('请输入备注(可以留空)') || ''; const savedAddresses = JSON.parse(localStorage.getItem('savedAddresses') || '[]'); const newEntry = { note: note, name: document.getElementById('name').textContent, gender: document.getElementById('gender').textContent, phone: document.getElementById('phone').textContent, address: document.getElementById('address').textContent }; savedAddresses.push(newEntry); localStorage.setItem('savedAddresses', JSON.stringify(savedAddresses)); renderSavedAddresses(); } // 渲染保存的地址 function renderSavedAddresses() { const savedAddresses = JSON.parse(localStorage.getItem('savedAddresses') || '[]'); const tbody = document.querySelector('#savedAddressesTable tbody'); tbody.innerHTML = ''; savedAddresses.forEach((entry, index) => { const row = tbody.insertRow(); row.className = 'border-t border-gray-200 hover:bg-gray-50 transition-colors'; const cells = ['name', 'gender', 'phone', 'address', 'note', '']; cells.forEach((cell, i) => { const td = row.insertCell(); td.className = 'p-4'; if (i === cells.length - 1) { const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.className = 'bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-all transform hover:scale-105 focus:ring-2 focus:ring-red-500 focus:ring-opacity-50'; deleteBtn.onclick = () => { if (confirm('确定要删除这条记录吗?')) { savedAddresses.splice(index, 1); localStorage.setItem('savedAddresses', JSON.stringify(savedAddresses)); renderSavedAddresses(); } }; td.appendChild(deleteBtn); } else { td.textContent = entry[cell]; } }); }); } // 生成新地址 async function generateNewAddress(country) { if (!country) { country = document.getElementById('country').value; } toggleLoading(true); try { const response = await fetch(\`\${window.location.href}api?country=\${country}\`); const data = await response.json(); if (data.error) { alert(data.error); return; } // 提取邮编逻辑 const addressText = data.address || ''; const postcodeMatch = addressText.match(/\\b\\d{5}(?:\\-\\d{4})?\\b/g); // 匹配国际通用的邮编格式 const postcode = postcodeMatch ? postcodeMatch[0] : '未知『地址中倒数第2项提取』'; document.getElementById('name').textContent = data.name; document.getElementById('gender').textContent = data.gender; document.getElementById('phone').textContent = data.phone; document.getElementById('address').textContent = addressText; // 显示完整地址 document.getElementById('postcode').textContent = postcode; // 显示邮编 // 更新地图 document.getElementById('map').src = \`https://www.google.com/maps?q=\${encodeURIComponent(data.address)}&output=embed\`; } catch (error) { console.error('Error fetching address:', error); showErrorModal('获取地址时发生错误,请检查网络后点击重试', () => { generateNewAddress(document.getElementById('country').value); }); } finally { toggleLoading(false); } } // 页面加载时初始化 window.onload = function() { initCountrySelect(); generateNewAddress(); renderSavedAddresses(); }; // 显示关于页面 function showAbout() { document.getElementById('main-content').classList.add('hidden'); document.getElementById('about').classList.remove('hidden'); } // 返回主页 function showHome() { document.getElementById('about').classList.add('hidden'); document.getElementById('main-content').classList.remove('hidden'); } </script> </body> </html>`; async function handleRequest(request) { const url = new URL(request.url); const path = url.pathname; // 处理 API 请求 if (path === '/api') { // 原有的 API 处理逻辑 return handleApiRequest(request); } // 处理根路径请求,返回 HTML 页面 return new Response(htmlContent, { headers: { 'Content-Type': 'text/html;charset=UTF-8', ...corsHeaders } }); } // 将原有的处理逻辑移到单独的函数中 async function handleApiRequest(request) { if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } if (request.method !== 'GET') { return new Response('Method not allowed', { status: 405 }); } const url = new URL(request.url); const country = url.searchParams.get('country') || 'US'; if (!countryCoordinates[country]) { return new Response(JSON.stringify({ error: 'Invalid country code' }), { status: 400, headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } let attempts = 0; const maxAttempts = 20; while (attempts < maxAttempts) { try { const location = getRandomLocation(country); const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${location.lat}&lon=${location.lng}&zoom=18&addressdetails=1`; const response = await fetch(apiUrl, { headers: { 'User-Agent': 'Cloudflare Worker Random Address Generator' } }); const data = await response.json(); if (isValidAddress(data)) { let city = data.address.city || data.address.town || ''; city = city.split(';')[0].trim(); const address = `${data.address.house_number} ${data.address.road}, ${city}, ${data.address.postcode || ''}, ${country}`.replace(/\s+/g, ' ').trim(); const name = getRandomName(country); const gender = Math.random() > 0.4 ? 'Male男' : 'Female女'; const phone = getRandomPhoneNumber(country); const result = { name, gender, phone, address, coordinates: { lat: location.lat, lng: location.lng } }; return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } attempts++; if (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); } } catch (error) { attempts++; if (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); } } } return new Response(JSON.stringify({ error: 'Failed to generate valid address after multiple attempts' }), { status: 500, headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } // 注册事件监听器 addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); }); 2025年07月15日更新,界面调整,添加多个国家和地区// 国家坐标数据 const countryCoordinates = { "US": [{ lat: 37.7749, lng: -122.4194 }, { lat: 34.0522, lng: -118.2437 }, { lat: 32.7767, lng: -96.7970 }, { lat: 41.8781, lng: -87.6298 }], "NY US": [{ lat: 40.7128, lng: -74.0060 }], "California US": [{ lat: 34.0522, lng: -118.2437 }], "Texas US": [{ lat: 32.7767, lng: -96.7970 }], "CA US": [{ lat: 37.3382, lng: -121.8863 }], "Illinois US": [{ lat: 41.8781, lng: -87.6298 }], "UK": [{ lat: 51.5074, lng: -0.1278 }, { lat: 53.4808, lng: -2.2426 }], "FR": [{ lat: 48.8566, lng: 2.3522 }, { lat: 45.7640, lng: 4.8357 }], "DE": [{ lat: 52.5200, lng: 13.4050 }, { lat: 48.1351, lng: 11.5820 }], "CN": [{ lat: 39.9042, lng: 116.4074 }, { lat: 31.2304, lng: 121.4737 }], "TW": [{ lat: 25.0330, lng: 121.5654 }, { lat: 22.6273, lng: 120.3014 }], "HK": [{ lat: 22.3193, lng: 114.1694 },{ lat: 22.3964, lng: 114.1095 }], "JP": [{ lat: 35.6895, lng: 139.6917 }, { lat: 34.6937, lng: 135.5023 }], "IN": [{ lat: 28.6139, lng: 77.2090 }, { lat: 19.0760, lng: 72.8777 }], "AU": [{ lat: -33.8688, lng: 151.2093 }, { lat: -37.8136, lng: 144.9631 }], "BR": [{ lat: -23.5505, lng: -46.6333 }, { lat: -22.9068, lng: -43.1729 }], "CA": [{ lat: 43.651070, lng: -79.347015 }, { lat: 45.501690, lng: -73.567253 }], "RU": [{ lat: 55.7558, lng: 37.6173 }, { lat: 59.9343, lng: 30.3351 }], "ZA": [{ lat: -33.9249, lng: 18.4241 }, { lat: -26.2041, lng: 28.0473 }], "MX": [{ lat: 19.4326, lng: -99.1332 }, { lat: 20.6597, lng: -103.3496 }], "KR": [{ lat: 37.5665, lng: 126.9780 }, { lat: 35.1796, lng: 129.0756 }], "IT": [{ lat: 41.9028, lng: 12.4964 }, { lat: 45.4642, lng: 9.1900 }], "ES": [{ lat: 40.4168, lng: -3.7038 }, { lat: 41.3851, lng: 2.1734 }], "TR": [{ lat: 41.0082, lng: 28.9784 }, { lat: 39.9334, lng: 32.8597 }], "SA": [{ lat: 24.7136, lng: 46.6753 }, { lat: 21.3891, lng: 39.8579 }], "AR": [{ lat: -34.6037, lng: -58.3816 }, { lat: -31.4201, lng: -64.1888 }], "EG": [{ lat: 30.0444, lng: 31.2357 }, { lat: 31.2156, lng: 29.9553 }], "NG": [{ lat: 6.5244, lng: 3.3792 }, { lat: 9.0579, lng: 7.4951 }], "ID": [{ lat: -6.2088, lng: 106.8456 }, { lat: -7.7956, lng: 110.3695 }], "VN": [{ lat: 21.0285, lng: 105.8048 }, { lat: 10.7626, lng: 106.6602 }], // 添加越南坐标数据 }; // 姓名数据 const namesByCountry = { "CN": { first: ["李", "王", "张", "刘", "陈", "杨", "黄", "赵", "吴", "周", "孙", "苏", "马", "朱", "候", "郭", "黑", "高", "林", "钱", "冯", "卫", "蒋", "许", "吕", "施", "曹", "严", "魏", "陶", "方", "雷"], last: ["力", "斌", "那", "真", "杰", "京", "丽", "晴", "来", "东", "勇", "仕", "云", "洁", "山", "伟", "明", "亮", "刚", "飞", "英", "兴", "浩", "曙光", "平", "志远", "志珍", "第一", "建国", "玉仙", "现红", "瑞山", "一山", "新丽", "思思", "青凤", "秀美"] }, "JP": { first: ["Sato", "Suzuki", "Takahashi", "Tanaka", "Watanabe", "Ito", "Yamamoto", "Nakamura", "Kobayashi", "Kato"], last: ["Shota", "Ren", "Hina", "Yui", "Hiroto", "Sota", "Yota", "Misaki", "Nanami", "Yuto"] }, "KR": { first: ["Kim", "Lee", "Park", "Choi", "Jung", "Kang", "Jo", "Yoon", "Jang", "Lim"], last: ["Minjun", "Seojun", "Doyun", "Jiho", "Jihun", "Seoyeon", "Seoyun", "Jiwoo", "Seohyun", "Minseo"] }, "TW": { first: ["Chen", "Lin", "Huang", "Chang", "Lee", "Wang", "Wu", "Liu", "Tsai", "Yang"], last: ["Zhiming", "Jianhong", "Junjie", "Yijun", "Shufen", "Meiling", "Yating", "Jiahao", "Zhihao", "Shuhui"] }, "HK": { first: ["Chan", "Lee", "Wong", "Cheung", "Lau", "Wang", "Ng", "Cheng", "Leung", "Ho"], last: ["Chiming", "Kayan", "Junjie", "Wingsze", "Kaming", "Meiling", "Kahao", "Winger", "Chihao", "Shukfan"] }, "US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "NY US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "California US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "Texas US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "CA US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "Illinois US": { first: ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"], last: ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Christopher"] }, "UK": { first: ["Smith", "Jones", "Williams", "Taylor", "Brown", "Davies", "Evans", "Wilson", "Thomas", "Roberts"], last: ["Oliver", "Jack", "Harry", "George", "Noah", "Charlie", "Jacob", "Oscar", "William", "Leo"] }, "FR": { first: ["Martin", "Bernard", "Dubois", "Thomas", "Robert", "Richard", "Petit", "Durand", "Leroy", "Moreau"], last: ["Lucas", "Louis", "Gabriel", "Arthur", "Jules", "Hugo", "Leo", "Adam", "Raphael", "Paul"] }, "DE": { first: ["Mueller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Schulz", "Hoffmann"], last: ["Ben", "Paul", "Leon", "Noah", "Luis", "Finn", "Felix", "Jonas", "Maximilian", "Henry"] }, "IT": { first: ["Rossi", "Ferrari", "Russo", "Bianchi", "Romano", "Gallo", "Costa", "Fontana", "Conti", "Esposito"], last: ["Leonardo", "Francesco", "Alessandro", "Lorenzo", "Matteo", "Andrea", "Gabriele", "Marco", "Antonio", "Giuseppe"] }, "ES": { first: ["Garcia", "Rodriguez", "Gonzalez", "Fernandez", "Lopez", "Martinez", "Sanchez", "Perez", "Martin", "Gomez"], last: ["Antonio", "Jose", "Manuel", "Francisco", "David", "Juan", "Miguel", "Javier", "Rafael", "Carlos"] }, "BR": { first: ["Silva", "Santos", "Oliveira", "Souza", "Rodrigues", "Ferreira", "Alves", "Pereira", "Lima", "Gomes"], last: ["Miguel", "Arthur", "Heitor", "Pedro", "Davi", "Gabriel", "Bernardo", "Lucas", "Matheus", "Rafael"] }, "RU": { first: ["Ivanov", "Smirnov", "Kuznetsov", "Popov", "Vasiliev", "Petrov", "Sokolov", "Mikhailov", "Fedorov", "Morozov"], last: ["Alexander", "Dmitry", "Maxim", "Ivan", "Andrey", "Mikhail", "Artem", "Daniel", "Roman", "Sergey"] }, "IN": { first: ["Kumar", "Singh", "Sharma", "Patel", "Gupta", "Shah", "Verma", "Rao", "Reddy", "Joshi"], last: ["Aarav", "Vihaan", "Vivaan", "Aditya", "Arjun", "Reyansh", "Ayaan", "Sai", "Krishna", "Ishaan"] }, "AU": { first: ["Smith", "Jones", "Williams", "Brown", "Wilson", "Taylor", "Johnson", "White", "Anderson", "Thompson"], last: ["Oliver", "William", "Jack", "Noah", "Thomas", "James", "Lucas", "Henry", "Ethan", "Alexander"] }, "CA": { first: ["Smith", "Brown", "Tremblay", "Martin", "Roy", "Wilson", "MacDonald", "Taylor", "Campbell", "Anderson"], last: ["Liam", "Noah", "Oliver", "William", "James", "Benjamin", "Lucas", "Henry", "Theodore", "Jack"] }, "MX": { first: ["Garcia", "Rodriguez", "Martinez", "Lopez", "Gonzalez", "Perez", "Sanchez", "Ramirez", "Torres", "Flores"], last: ["Santiago", "Mateo", "Sebastian", "Leonardo", "Diego", "Daniel", "Gabriel", "Adrian", "David", "Alexander"] }, "TR": { first: ["Yilmaz", "Kaya", "Demir", "Sahin", "Celik", "Yildiz", "Erdogan", "Ozturk", "Aydin", "Ozdemir"], last: ["Yusuf", "Eymen", "Ömer", "Mustafa", "Ali", "Mehmet", "Ahmet", "Emir", "Hamza", "Ibrahim"] }, "SA": { first: ["Al-Saud", "Al-Sheikh", "Al-Rashid", "Al-Qahtani", "Al-Ghamdi", "Al-Zahrani", "Al-Dossari", "Al-Shammari", "Al-Otaibi", "Al-Harbi"], last: ["Mohammed", "Abdullah", "Ahmed", "Ali", "Omar", "Ibrahim", "Khalid", "Hassan", "Fahad", "Abdul"] }, "AR": { first: ["Gonzalez", "Rodriguez", "Garcia", "Fernandez", "Lopez", "Martinez", "Perez", "Romero", "Sanchez", "Diaz"], last: ["Mateo", "Thiago", "Benjamin", "Valentino", "Santiago", "Juan", "Lucas", "Martin", "Nicolas", "Joaquin"] }, "EG": { first: ["Mohamed", "Ahmed", "Mahmoud", "Ibrahim", "Ali", "Hassan", "Hussein", "Mostafa", "Kamal", "Samir"], last: ["Omar", "Youssef", "Adam", "Malik", "Zain", "Hamza", "Kareem", "Hassan", "Ali", "Ibrahim"] }, "NG": { first: ["Okafor", "Adebayo", "Okonkwo", "Eze", "Oluwaseun", "Adegoke", "Afolabi", "Ogunleye", "Adeniyi", "Adesina"], last: ["Oluwadamilare", "Oluwatobiloba", "Ayomide", "Temitope", "Oluwaseun", "Adebayo", "Chibuike", "Chisom", "Chidi", "Obinna"] }, "ID": { first: ["Wijaya", "Kusuma", "Suryanto", "Halim", "Santoso", "Tanaka", "Wibowo", "Susanto", "Hidayat", "Putra"], last: ["Muhammad", "Ahmad", "Abdul", "Aditya", "Budi", "Dimas", "Eko", "Fajar", "Gading", "Hadi"] }, "ZA": { first: ["Nkosi", "Van der Merwe", "Botha", "Mkhize", "Khumalo", "Pretorius", "Venter", "Ndlovu", "Fourie", "Nel"], last: ["Bandile", "Themba", "Sipho", "Thabo", "Jabu", "Mandla", "Blessing", "Gift", "Lucky", "Precious"] }, "VN": { first: ["Nguyen", "Tran", "Le", "Pham", "Hoang", "Vu", "Do", "Dao", "Bui", "Dang"], last: ["Van", "Minh", "Thanh", "Ngoc", "Huu", "Quoc", "Xuan", "Duc", "Tuan", "Khanh"] } }; // 电话号码格式配置 const phoneFormats = { "US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "NY US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "California US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "Texas US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "CA US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "Illinois US": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[201, 989]] }, "CN": { format: "+86 1XX-XXXX-XXXX", mobilePrefix: ["30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "50", "51", "52", "53", "55", "56", "57", "58", "59", "66", "70", "71", "72", "73", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89"] }, "JP": { format: "+81 XX-XXXX-XXXX", mobilePrefix: ["70", "80", "90"] }, "KR": { format: "+82 10-XXXX-XXXX" }, "UK": { format: "+44 7XXX XXXXXX", mobilePrefix: ["7"] }, "FR": { format: "+33 6 XX XX XX XX", mobilePrefix: ["6", "7"] }, "DE": { format: "+49 15X XXXXXXXX", mobilePrefix: ["15", "16", "17"] }, "TW": { format: "+886 9XX-XXX-XXX" }, "HK": { format: "+852 XXXX XXXX", mobilePrefix: ["5", "6", "9"] }, "AU": { format: "+61 4XX XXX XXX", mobilePrefix: ["4"] }, "CA": { format: "+1 (XXX) XXX-XXXX", areaCodeRanges: [[204, 989]] }, "MX": { format: "+52 1XX XXX XXXX" }, "TR": { format: "+90 5XX XXX XXXX", mobilePrefix: ["5"] }, "SA": { format: "+966 5XX XXX XXXX", mobilePrefix: ["5"] }, "AR": { format: "+54 9XX XXXX-XXXX" }, "EG": { format: "+20 1XX XXX XXXX", mobilePrefix: ["1"] }, "NG": { format: "+234 8XX XXX XXXX", mobilePrefix: ["7", "8", "9"] }, "ID": { format: "+62 8XX-XXXX-XXXX", mobilePrefix: ["8"] }, "ZA": { format: "+27 8X XXX XXXX", mobilePrefix: ["6", "7", "8"] }, "VN": { format: "+84 9X XXX XXXX", mobilePrefix: ["9", "8"] } }; // 工具函数 function getRandomLocation(country) { const coordsArray = countryCoordinates[country]; const randomCity = coordsArray[Math.floor(Math.random() * coordsArray.length)]; const lat = randomCity.lat + (Math.random() - 0.5) * 0.1; const lng = randomCity.lng + (Math.random() - 0.5) * 0.1; return { lat, lng }; } function getRandomName(country) { if (!namesByCountry[country]) { return null; } const names = namesByCountry[country]; const firstName = names.first[Math.floor(Math.random() * names.first.length)]; const lastName = names.last[Math.floor(Math.random() * names.last.length)]; return `${firstName} ${lastName}`; } function generateAreaCode(ranges) { const range = ranges[Math.floor(Math.random() * ranges.length)]; const [min, max] = range; return Math.floor(min + Math.random() * (max - min + 1)); } function getRandomPhoneNumber(country) { const format = phoneFormats[country] || phoneFormats["US"]; let phone = format.format; if (format.areaCodeRanges) { const areaCode = generateAreaCode(format.areaCodeRanges); phone = phone.replace("XXX", areaCode); phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } else if (format.mobilePrefix) { const prefix = format.mobilePrefix[Math.floor(Math.random() * format.mobilePrefix.length)]; // 先替换前缀 if (prefix.length === 2) { phone = phone.replace(/XX/, prefix); } else { phone = phone.replace(/X/, prefix); } // 然后替换剩余的X phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } else { phone = phone.replace(/X/g, () => Math.floor(Math.random() * 10)); } return phone; } function isValidAddress(data) { return data && data.address && data.address.house_number && data.address.road && (data.address.city || data.address.town); } // 处理CORS请求的headers const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; // HTML 模板 const htmlContent = `<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>随机地址生成器</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet"> <script> tailwind.config = { theme: { extend: { animation: { 'gradient': 'gradient 8s linear infinite', }, keyframes: { gradient: { '0%, 100%': { 'background-size': '200% 200%', 'background-position': 'left center' }, '50%': { 'background-size': '200% 200%', 'background-position': 'right center' } } } } } } </script> </head> <link rel="shortcut 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>"> <body class="bg-gradient-to-br from-blue-50 via-white to-blue-50 text-gray-800 min-h-screen font-['Noto_Sans_SC']"> <!-- 头部 --> <header class="bg-gradient-to-r from-blue-500 via-sky-500 to-blue-500 animate-gradient w-full p-6 shadow-lg"> <div class="max-w-4xl mx-auto flex items-center justify-between"> <div class="flex items-center gap-3"> <img src="https://img.freepik.com/premium-vector/minimal-location-map-icon-logo-symbol-vector-design-transparent_965979-613.jpg?w=2000" alt="Logo" class="w-12 h-12 transform hover:scale-105 transition-transform"> <h1 class="text-2xl font-bold text-white">随机地址生成器</h1> </div> <a href="https://www.199881.xyz" target="_blank" class="flex items-center gap-2 text-white hover:text-gray-200 transition-colors"> <svg viewBox="0 0 16 16" class="w-6 h-6 fill-current" aria-hidden="true"> <img src="https://cdn.glitch.global/efdace30-a873-49c7-aaa9-4fa31679ee0c/logo04.png?1741698119024" alt="GitHub" class="w-5 h-5 ml-1"> </svg> <span class="font-medium">导航</span> </a> </div> </header> <!-- 主要内容 --> <main class="container mx-auto px-4 py-8 max-w-5xl"> <!-- 加载动画 --> <div id="loading" class="hidden fixed inset-0 bg-white bg-opacity-75 backdrop-blur-sm flex items-center justify-center z-50"> <div class="bg-white rounded-2xl p-8 flex flex-col items-center shadow-2xl"> <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mb-4"></div> <div class="text-gray-800 text-lg font-medium">正在加载...</div> </div> </div> <div id="copied" class="hidden fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-40 transform transition-transform duration-300"> 已复制到剪贴板! </div> <!-- 国家选择 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200 mb-8"> <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> <div class="w-full sm:w-auto flex flex-col sm:flex-row items-start sm:items-center gap-2 flex-grow"> <label for="country" class="text-blue-600 font-bold whitespace-nowrap">选择国家/地区:</label> <select id="country" onchange="changeCountry(this.value)" class="w-full bg-gray-50 border border-gray-200 rounded-xl p-3 text-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"> </select> </div> <button onclick="generateNewAddress(document.getElementById('country').value)" class="w-full sm:w-auto bg-gradient-to-r from-blue-500 to-sky-500 hover:from-blue-600 hover:to-sky-600 text-white font-bold py-3 px-6 rounded-xl transition-all transform hover:scale-105 hover:shadow-lg focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"> 获取新地址 </button> </div> </div> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <!-- 左侧面板 --> <div class="space-y-6"> <!-- 信息卡片 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">个人信息</h2> <div class="space-y-4"> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">姓名:</strong><span id="name" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">性别:</strong><span id="gender" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">电话:</strong><span id="phone" class="ml-2"></span> </div> <div class="bg-gray-50 p-4 rounded-xl cursor-pointer hover:bg-gray-100 transition-all transform hover:scale-[1.02] hover:shadow-lg" onclick="copyToClipboard(this.querySelector('span').textContent)"> <strong class="text-blue-600">地址:</strong><span id="address" class="ml-2"></span> </div> </div> </div> <!-- 保存按钮 --> <button onclick="saveAddress()" class="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white font-bold py-3 px-6 rounded-xl transition-all transform hover:scale-105 hover:shadow-lg focus:ring-2 focus:ring-green-500 focus:ring-opacity-50"> 保存地址 </button> </div> <!-- 右侧面板 --> <div class="space-y-6"> <!-- 地图 --> <div class="bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">地图预览</h2> <iframe id="map" class="w-full h-[360px] rounded-xl border border-gray-200"></iframe> </div> </div> </div> <!-- 已保存的地址表格 --> <div class="mt-8 bg-white rounded-2xl shadow-xl p-6 border border-gray-200"> <h2 class="text-xl font-bold mb-6 text-blue-600">已保存的地址</h2> <div class="overflow-x-auto"> <table class="w-full border-collapse" id="savedAddressesTable"> <thead> <tr class="bg-gradient-to-r from-blue-500 to-sky-500 text-white"> <th class="p-4 text-left rounded-tl-lg">姓名</th> <th class="p-4 text-left">性别</th> <th class="p-4 text-left">电话</th> <th class="p-4 text-left">地址</th> <th class="p-4 text-left">备注</th> <th class="p-4 text-left rounded-tr-lg">操作</th> </tr> </thead> <tbody></tbody> </table> </div> </div> </main> <!-- 页脚 --> <footer class="text-center py-8 text-gray-600 text-sm mt-8 bg-gray-50 border-t border-gray-200"> <p class="max-w-4xl mx-auto px-4"> 总访问量 <span id="busuanzi_site_pv"></span> 次 | <span id="timeDate">载入天数...</span> | <a href="https://boke.199881.xyz/" target="_blank"> <span style="color: blue;">博客 | <a href="https://www.199881.xyz/" target="_blank"> <span style="color: green;">导航 <a href="https://github.com/jiangnan1224/AddressGenerator/" target="_blank" class="inline-flex items-center hover:text-blue-600 transition-colors"> <img src="https://pic.imgdb.cn/item/66e7ab36d9c307b7e9cefd24.png" alt="GitHub" class="w-5 h-5 ml-1"> </a> <script language="javascript"> var now = new Date(); function createtime(){ var grt= new Date("04/23/2025 00:00:00");/*---这里是网站的启用时间--*/ now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); document.getElementById("timeDate").innerHTML = "运行"+dnum+"天"; } setInterval("createtime()",250); </script> <script defer src="https://bsz.211119.xyz/js"></script> </p> </footer> <script> // 国家数据 const countries = [ { name: "美国", code: "US" }, { name: "美国纽约", code: "NY US" }, { name: "美国洛杉矶", code: "California US" }, { name: "美国达拉斯", code: "Texas US" }, { name: "美国圣何塞", code: "CA US" }, { name: "美国芝加哥", code: "Illinois US" }, { name: "英国", code: "UK" }, { name: "法国", code: "FR" }, { name: "德国", code: "DE" }, { name: "中国", code: "CN" }, { name: "中国台湾", code: "TW" }, { name: "中国香港", code: "HK" }, { name: "日本", code: "JP" }, { name: "越南", code: "VN" }, { name: "印度", code: "IN" }, { name: "澳大利亚", code: "AU" }, { name: "巴西", code: "BR" }, { name: "加拿大", code: "CA" }, { name: "俄罗斯", code: "RU" }, { name: "南非", code: "ZA" }, { name: "墨西哥", code: "MX" }, { name: "韩国", code: "KR" }, { name: "意大利", code: "IT" }, { name: "西班牙", code: "ES" }, { name: "土耳其", code: "TR" }, { name: "沙特阿拉伯", code: "SA" }, { name: "阿根廷", code: "AR" }, { name: "埃及", code: "EG" }, { name: "尼日利亚", code: "NG" }, { name: "印度尼西亚", code: "ID" } ]; // 初始化国家选择下拉框 function initCountrySelect() { const select = document.getElementById('country'); countries.forEach(country => { const option = document.createElement('option'); option.value = country.code; option.textContent = country.name; select.appendChild(option); }); } // 复制到剪贴板 function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { const copied = document.getElementById('copied'); copied.classList.remove('hidden'); copied.classList.add('translate-y-0'); copied.classList.remove('translate-y-[-100%]'); setTimeout(() => { copied.classList.add('translate-y-[-100%]'); copied.classList.remove('translate-y-0'); setTimeout(() => { copied.classList.add('hidden'); }, 300); }, 2000); }); } // 显示/隐藏加载动画 function toggleLoading(show) { const loading = document.getElementById('loading'); if (show) { loading.classList.remove('hidden'); } else { loading.classList.add('hidden'); } } // 更改国家 function changeCountry(country) { toggleLoading(true); generateNewAddress(country); } // 保存地址 function saveAddress() { const note = prompt('请输入备注(可以留空)') || ''; const savedAddresses = JSON.parse(localStorage.getItem('savedAddresses') || '[]'); const newEntry = { note: note, name: document.getElementById('name').textContent, gender: document.getElementById('gender').textContent, phone: document.getElementById('phone').textContent, address: document.getElementById('address').textContent }; savedAddresses.push(newEntry); localStorage.setItem('savedAddresses', JSON.stringify(savedAddresses)); renderSavedAddresses(); } // 渲染保存的地址 function renderSavedAddresses() { const savedAddresses = JSON.parse(localStorage.getItem('savedAddresses') || '[]'); const tbody = document.querySelector('#savedAddressesTable tbody'); tbody.innerHTML = ''; savedAddresses.forEach((entry, index) => { const row = tbody.insertRow(); row.className = 'border-t border-gray-200 hover:bg-gray-50 transition-colors'; const cells = ['name', 'gender', 'phone', 'address', 'note', '']; cells.forEach((cell, i) => { const td = row.insertCell(); td.className = 'p-4'; if (i === cells.length - 1) { const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.className = 'bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-all transform hover:scale-105 focus:ring-2 focus:ring-red-500 focus:ring-opacity-50'; deleteBtn.onclick = () => { if (confirm('确定要删除这条记录吗?')) { savedAddresses.splice(index, 1); localStorage.setItem('savedAddresses', JSON.stringify(savedAddresses)); renderSavedAddresses(); } }; td.appendChild(deleteBtn); } else { td.textContent = entry[cell]; } }); }); } // 生成新地址 async function generateNewAddress(country) { if (!country) { country = document.getElementById('country').value; } toggleLoading(true); try { const response = await fetch(\`\${window.location.href}api?country=\${country}\`); const data = await response.json(); if (data.error) { alert(data.error); return; } document.getElementById('name').textContent = data.name; document.getElementById('gender').textContent = data.gender; document.getElementById('phone').textContent = data.phone; document.getElementById('address').textContent = data.address; // 更新地图 document.getElementById('map').src = \`https://www.google.com/maps?q=\${encodeURIComponent(data.address)}&output=embed\`; } catch (error) { console.error('Error fetching address:', error); alert('获取地址时发生错误,请重试'); } finally { toggleLoading(false); } } // 页面加载时初始化 window.onload = function() { initCountrySelect(); generateNewAddress(); renderSavedAddresses(); }; </script> </body> </html>`; async function handleRequest(request) { const url = new URL(request.url); const path = url.pathname; // 处理 API 请求 if (path === '/api') { // 原有的 API 处理逻辑 return handleApiRequest(request); } // 处理根路径请求,返回 HTML 页面 return new Response(htmlContent, { headers: { 'Content-Type': 'text/html;charset=UTF-8', ...corsHeaders } }); } // 将原有的处理逻辑移到单独的函数中 async function handleApiRequest(request) { if (request.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } if (request.method !== 'GET') { return new Response('Method not allowed', { status: 405 }); } const url = new URL(request.url); const country = url.searchParams.get('country') || 'US'; if (!countryCoordinates[country]) { return new Response(JSON.stringify({ error: 'Invalid country code' }), { status: 400, headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } let attempts = 0; const maxAttempts = 20; while (attempts < maxAttempts) { try { const location = getRandomLocation(country); const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${location.lat}&lon=${location.lng}&zoom=18&addressdetails=1`; const response = await fetch(apiUrl, { headers: { 'User-Agent': 'Cloudflare Worker Random Address Generator' } }); const data = await response.json(); if (isValidAddress(data)) { let city = data.address.city || data.address.town || ''; city = city.split(';')[0].trim(); const address = `${data.address.house_number} ${data.address.road}, ${city}, ${data.address.postcode || ''}, ${country}`.replace(/\s+/g, ' ').trim(); const name = getRandomName(country); const gender = Math.random() > 0.4 ? 'Male男' : 'Female女'; const phone = getRandomPhoneNumber(country); const result = { name, gender, phone, address, coordinates: { lat: location.lat, lng: location.lng } }; return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } attempts++; if (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); } } catch (error) { attempts++; if (attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); } } } return new Response(JSON.stringify({ error: 'Failed to generate valid address after multiple attempts' }), { status: 500, headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } // 注册事件监听器 addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); });
2025年07月16日
26 阅读
0 评论
0 点赞
2025-05-25
白嫖Lade io保姆级教程 从注册到搭建vmess节点应用,可开3个1C128MB实例,8个地区可选!
白嫖Lade io保姆级教程 从注册到搭建vmess节点应用,可开3个1C128MB实例,8个地区可选!本文转自: 科技共享 去看看 YTB 一、注册账号打开:https://www.lade.io/,开始注册。GitHub 绑定福利:GitHub 账号 ≥180 天,永久免费填写必须的信息,验证账号:去你的邮箱查收,并完成账号验证,重新登录,看到下面这个图代表登录成功了接下来进行github账号验证,按图中红色标识的三个操作(1. 创建gist, 2. 得到gist连接发给live chat, 3. 再次输入邮箱,约1分钟会给你审核结果), 具体看下面的截图(如下图在github创建gist)(创建成功后,复制地址栏中的连接)(回到Lade.io,点击live chat,将刚复制的连接发个chat)(chat会让你再次输入mail地址)最好是晚上操作(倒时差),是人工审核的(提交后等待约1分钟,如果你的github账号不足6个月,会是这个提示(不成功)(验证成功会返回这个消息){dotted startColor="#ff6c6c" endColor="#1989fa"/}二、部署应用打开https://github.com/eooce/Sing-box/tree/main/nodejs 下载图中三个文件修改start.sh文件(重点,也是唯一需要修改的文件)说明:需要修改的配置如下图红色标识(如果不需要扎针,关于nezha的可以留空)。隧道token和自定义域名。用文本编辑器打开start.sh编辑 argo 域名、json、端口、节点名称等信息,保存。可用fscarmen大佬的ArgoX Cloudflare Json 生成网轻松获取: https://fscarmen.cloudflare.now.cc下载官网的lade连接程序如果是windows则可将lade.exe扔进windows系统文件夹方便调用,如果觉得有安全风险,完成设置后删除即可输入:lade(检测)输入:lade login(登录)输入注册邮箱输入密码输入:cd (3个文件的目录)输入:lade apps create (自定义名字)输入:lade deploy --app (名字)下载安装输入:lade logs -a (名字)查看状态输入:lade apps show (名字)输入:lade apps remove (名字)#删除项目从日志中复制节点信息,导入V2ray N即可玩耍了。到此为止,lade的应用就算部署完成了。我这里部署的是nodejs应用,当然你也可以举一反三,部署python应用.一个账号可以3个1C128MB实例项目,每月100G出站流量,不会休眠,8个地区可选,能日本、新加坡、美国都开一个容器方便用,Vultr的服务器,直连速度一般,套CF隧道优选速度还挺快的{dotted startColor="#ff6c6c" endColor="#1989fa"/}三、有大佬打包成一键脚本MAC OS/Linux的一键脚本:(直接在终端中运行以下命令即可自动下载并执行脚本。)bash <(curl -l -s https://raw.githubusercontent.com/byJoey/ladefree/refs/heads/main/install.sh)Windows的一键脚本:以管理员身份运行 PowerShell: 为了确保 Lade CLI 能够正确安装到系统路径,并执行其他需要权限的操作,强烈建议以管理员身份运行 PowerShell。一行命令运行脚本: 重要安全提示: 以下命令使用了 -ExecutionPolicy Bypass 参数,它会绕过 PowerShell 的执行策略,允许运行任何脚本。请确保你完全信任此脚本的来源,否则可能存在安全风险。Invoke-WebRequest -Uri "https://raw.githubusercontent.com/byJoey/ladefree/main/install.ps1" -OutFile "$env:TEMP\install.ps1"; PowerShell -ExecutionPolicy Bypass -File "$env:TEMP\install.ps1"; Remove-Item "$env:TEMP\install.ps1" -ErrorAction SilentlyContinue
2025年05月25日
27 阅读
0 评论
0 点赞
2025-04-04
一段代码简单在谷歌colab内搭建vpn,完全免费科学上网工具,全网高速免费机场节点抓取
本文转载自科技共享 一段代码简单在谷歌colab内搭建vpn,完全免费科学上网工具,全网高速免费机场节点抓取支持v2ray/clash/singox能够自动爬取全网分享的订阅地址及代理节点,也能够自动注册/续期、订阅转换、节点存活检查等作者项目地址 【点击直达】 谷歌账号注册教程 【点击直达】 1、谷歌colab 【点击直达】 2、在新建的笔记本中,运行以下命令以克隆项目仓库:!git clone https://github.com/wzdnzd/aggregator.git3、安装依赖项:进入克隆的项目目录:%cd aggregator安装项目所需的 Python 依赖项:!pip install -r requirements.txt4、运行项目:在 Colab 中,使用以下命令运行 process.py 脚本:!python -u subscribe/collect.py -s5、查看节点文件【以clash.yaml为例,可改为v2或其它,下载时也类似修改】:clash.yaml!cat /content/aggregator/data/clash.yamlv2节点!cat /content/aggregator/data/v2ray.txt其它!cat /content/aggregator/data/singbox.json6、下载节点文件from google.colab import files files.download('/content/aggregator/data/clash.yaml')
2025年04月04日
32 阅读
1 评论
0 点赞
2024-09-08
部署在CF的轻量化导航页面,可移动卡片式书签,方便管理
部署在CF的轻量化导航页面,可移动卡片式书签,方便管理 github项目 Card-Tab 书签卡片式管理,进入管理模式可以自由移动书签位置,添加和删除书签,支持自定义网站分类,支持切换黑暗色主题一、cloudflare workes部署 现用代码 ,nodeseek风格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: 32px; 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; } /* 主题切换按钮样式 */ #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: 32px; 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: 182px; border: 2px solid gray; /* 添加2px灰色边框 */ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); cursor: pointer; transition: transform 0.2s; position: relative; } /* 中等屏幕 (平板等) */ @media (max-width: 1024px) { .card { width: 225px; } } /* 小屏幕 (手机) */ @media (max-width: 768px) { .card { width: 224px; } } .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: 24px; /* 进一步减小卡片内边距6 */ width: 368px; /* 手机上显示4列,235时显示6列 */ } .card-icon { width: 46px; height: 46px; } .card-title { font-size: 40px; } .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.211119.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> </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"> <span id="timeDate">载入天数...</span> <script language="javascript"> var now = new Date(); function createtime(){ var grt= new Date("01/05/2025 00:00:00");/*---这里是网站的启用时间--*/ now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天"; } setInterval("createtime()",250); </script> <span <p> | 本页总访问量 <span id="busuanzi_site_pv"></span> 次 | 开源于 <a href="https://github.com/hmhm2022/Card-Tab" target="_blank">@GitHub</a></p></span> <script defer src="https://bsz.211119.xyz/js"></script> </p> </div> </div> <script> // 日志记录函数 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打开开发者工具查看。'); } // 全局变量 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 = 'rgba(30, 30, 30, 0.5)'; 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 = 'rgba(30, 30, 30, 0.9)'; document.body.style.color = '#ffffff'; const cards = document.querySelectorAll('.card'); cards.forEach(card => { card.style.backgroundColor = 'rgba(30, 30, 30, 0.9)'; 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 ? 'rgba(30, 30, 30, 0.9)' : '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 ? 'rgba(30, 30, 30, 0.9)' : '#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.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 }); } };{dotted startColor="#ff6c6c" endColor="#1989fa"/}旧版本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()">◑</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()">◑</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、有域名的可邦定域名,完成。
2024年09月08日
31 阅读
1 评论
0 点赞
2024-07-24
一次可生成n加节点,YTB视频4K秒开!2024最新永久免费,订阅节点订阅搭建保姆教程
一次可生成n加节点,YTB视频4K秒开!2024最新永久免费,订阅节点订阅搭建保姆教程 本文转自: 科技共享 原项目地址 代码 创建邦定KV空间,命名:settings支持Windows、MacOS、Linux 优选IP使用,操作简单,速度快。创建好后, 第一次打开设置登录密码 第一次打开设置登录密码 第一次打开设置登录密码 其它根据需要再设置
2024年07月24日
25 阅读
0 评论
0 点赞
1
2
3