addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request) { const url = new URL(request.url); const { pathname } = url; try { // 登录请求处理 if (pathname === '/login' && request.method === 'POST') { const formData = new URLSearchParams(await request.text()); const username = formData.get('username'); const password = formData.get('password'); if (username === envusername && password === envpassword) { return new Response(await renderNavigationPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8' }, }); } // 登录失败时直接返回登录页面并显示错误 return new Response(await renderLoginPage(true), { headers: { 'Content-Type': 'text/html; charset=utf-8' } }); } // 路由处理 switch (pathname) { case '/': case '/login.html': return new Response(await renderLoginPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8' } }); case '/privacy': return serveStaticFile('privacy.html'); case '/data': return fetchNavigationData(); case '/add-category': if (request.method === 'POST') return addCategory(request); break; case '/add-site': if (request.method === 'POST') return addSite(request); break; case '/delete-category': if (request.method === 'POST') return deleteCategory(request); break; case '/delete-site': if (request.method === 'POST') return deleteSite(request); break; case '/edit-site': if (request.method === 'POST') return editSite(request); break; case '/edit-category': if (request.method === 'POST') return editCategory(request); break; // 在handleRequest中添加切换分类状态的路由 case '/toggle-category': if (request.method === 'POST') return toggleCategory(request); break; // 在handleRequest的switch语句中添加新路由 case '/reorder-site': if (request.method === 'POST') return reorderSite(request); break; case '/load-notification': return loadNotification(); case '/export': return exportData(); // 导出接口 case '/import': if (request.method === 'POST') return importData(request); break; } // 未匹配的路由返回404 return serveStaticFile('NotFound.html', 404); } catch (error) { console.error('Error handling request:', error); return new Response('Internal Server Error', { status: 500 }); } } // KV 数据操作函数 async function getNavigationData() { const data = await NAVIGATION_DATA.get('data'); const parsedData = data ? JSON.parse(data) : { categories: [] }; // 初始化折叠状态 parsedData.categories.forEach(category => { if (typeof category.collapsed === 'undefined') { category.collapsed = false; // 默认展开状态 } }); return parsedData; } // 导出导航数据为 JSON 文件 async function exportData() { const navigationData = await getNavigationData(); // 复用已有函数 const jsonStr = JSON.stringify(navigationData, null, 2); return new Response(jsonStr, { headers: { 'Content-Type': 'application/json', 'Content-Disposition': 'attachment; filename="navigation_backup.json"', }, }); } // 从上传的 JSON 文件导入导航数据 async function importData(request) { try { const formData = await request.formData(); const file = formData.get('file'); if (!file) { return new Response(JSON.stringify({ error: '请选择要导入的文件' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 读取文件内容 const fileText = await file.text(); const importedData = JSON.parse(fileText); // 简单验证格式:必须包含 categories 数组 if (!importedData || !Array.isArray(importedData.categories)) { return new Response(JSON.stringify({ error: '无效的备份文件格式,缺少 categories 数组' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 可选:进一步验证每个分类和站点结构(这里略,可根据需要补充) // 写入 KV await NAVIGATION_DATA.put('data', JSON.stringify(importedData)); return new Response(JSON.stringify({ message: '数据导入成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' }, }); } catch (error) { return new Response(JSON.stringify({ error: '导入失败:' + error.message }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } } // 添加站点重新排序功能 async function reorderSite(request) { const requestBody = await request.json(); const { categoryIndex, oldIndex, newIndex } = requestBody; const navigationData = await getNavigationData(); // 验证分类索引 if (categoryIndex < 0 || categoryIndex >= navigationData.categories.length) { return new Response(JSON.stringify({ error: '无效分类索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const category = navigationData.categories[categoryIndex]; // 验证站点索引 if (oldIndex < 0 || oldIndex >= category.sites.length || newIndex < 0 || newIndex >= category.sites.length) { return new Response(JSON.stringify({ error: '无效站点索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } // 移动站点位置 const siteToMove = category.sites.splice(oldIndex, 1)[0]; category.sites.splice(newIndex, 0, siteToMove); await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '站点顺序已更新' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } // 添加编辑分类的API处理函数 async function editCategory(request) { const requestBody = await request.json(); const navigationData = await getNavigationData(); // 验证索引有效性 if (requestBody.categoryIndex < 0 || requestBody.categoryIndex >= navigationData.categories.length) { return new Response(JSON.stringify({ error: '无效分类索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } // 检查新名称是否已存在 const newName = requestBody.newName.trim(); const categoryExists = navigationData.categories.some( (cat, index) => index !== requestBody.categoryIndex && cat.name === newName ); if (categoryExists) { return new Response(JSON.stringify({ error: '分类名称已存在' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } // 更新分类名称 navigationData.categories[requestBody.categoryIndex].name = newName; await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '分类名称修改成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } // 添加切换分类折叠状态的API处理函数 async function toggleCategory(request) { const requestBody = await request.json(); const navigationData = await getNavigationData(); // 验证索引有效性 if (requestBody.categoryIndex < 0 || requestBody.categoryIndex >= navigationData.categories.length) { return new Response(JSON.stringify({ error: '无效分类索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } // 切换折叠状态 const category = navigationData.categories[requestBody.categoryIndex]; category.collapsed = !category.collapsed; await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '状态更新成功', collapsed: category.collapsed }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } // 切换分类折叠状态 async function toggleCategoryState(categoryIndex) { const response = await fetch('/toggle-category', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ categoryIndex }) }); if (response.ok) { const result = await response.json(); const categoryElement = document.getElementById(`category-sites-${categoryIndex}`); const toggleIcon = document.querySelector(`.toggle-btn[onclick="toggleCategoryState(${categoryIndex})"] .iconify`); if (result.collapsed) { categoryElement.style.display = 'none'; toggleIcon.setAttribute('data-icon', 'mdi:chevron-right'); } else { categoryElement.style.display = 'flex'; toggleIcon.setAttribute('data-icon', 'mdi:chevron-down'); } } } async function setNavigationData(data) { await NAVIGATION_DATA.put('data', JSON.stringify(data)); } // 静态文件服务 async function serveStaticFile(filename, status = 200) { const content = await STATIC_FILES.get(filename); if (content) { return new Response(content, { status, headers: { 'Content-Type': 'text/html; charset=utf-8' } }); } return new Response(`${filename} not found`, { status: 404 }); } // API 端点处理函数 async function fetchNavigationData() { const navigationData = await getNavigationData(); return new Response(JSON.stringify(navigationData), { headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-cache' } }); } async function addCategory(request) { const requestBody = await request.json(); const navigationData = await getNavigationData(); // 检查分类是否已存在 const categoryExists = navigationData.categories.some( cat => cat.name === requestBody.name ); if (categoryExists) { return new Response(JSON.stringify({ error: '分类已存在' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } navigationData.categories.push({ name: requestBody.name, sites: [] }); await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '分类添加成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } async function addSite(request) { const requestBody = await request.json(); const navigationData = await getNavigationData(); // 验证索引有效性 if (requestBody.categoryIndex < 0 || requestBody.categoryIndex >= navigationData.categories.length) { return new Response(JSON.stringify({ error: '无效分类索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } // 添加站点描述字段 navigationData.categories[requestBody.categoryIndex].sites.push({ name: requestBody.siteName, url: requestBody.siteUrl, icon: requestBody.siteIcon, info: requestBody.siteInfo || '' // 添加站点描述 }); await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '站点添加成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } async function editSite(request) { const requestBody = await request.json(); const navigationData = await getNavigationData(); // 获取原始分类索引和新分类索引 const originalCategoryIndex = requestBody.originalCategoryIndex; const newCategoryIndex = requestBody.newCategoryIndex; const siteIndex = requestBody.siteIndex; // 验证索引有效性 if (originalCategoryIndex < 0 || originalCategoryIndex >= navigationData.categories.length || newCategoryIndex < 0 || newCategoryIndex >= navigationData.categories.length) { return new Response(JSON.stringify({ error: '无效分类索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const originalCategory = navigationData.categories[originalCategoryIndex]; if (siteIndex < 0 || siteIndex >= originalCategory.sites.length) { return new Response(JSON.stringify({ error: '无效站点索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } // 获取站点对象 const site = originalCategory.sites[siteIndex]; // 更新站点信息 site.name = requestBody.siteName; site.url = requestBody.siteUrl; site.icon = requestBody.siteIcon; site.info = requestBody.siteInfo || ''; // 如果分类发生变化,移动站点到新分类 if (originalCategoryIndex !== newCategoryIndex) { // 从原分类移除 originalCategory.sites.splice(siteIndex, 1); // 添加到新分类 navigationData.categories[newCategoryIndex].sites.push(site); } await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '站点更新成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } async function deleteCategory(request) { const requestBody = await request.json(); const navigationData = await getNavigationData(); // 验证索引有效性 if (requestBody.categoryIndex < 0 || requestBody.categoryIndex >= navigationData.categories.length) { return new Response(JSON.stringify({ error: '无效分类索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } navigationData.categories.splice(requestBody.categoryIndex, 1); await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '分类删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } async function deleteSite(request) { const requestBody = await request.json(); const navigationData = await getNavigationData(); // 验证索引有效性 if (requestBody.categoryIndex < 0 || requestBody.categoryIndex >= navigationData.categories.length) { return new Response(JSON.stringify({ error: '无效分类索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const category = navigationData.categories[requestBody.categoryIndex]; if (requestBody.siteIndex < 0 || requestBody.siteIndex >= category.sites.length) { return new Response(JSON.stringify({ error: '无效站点索引' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } category.sites.splice(requestBody.siteIndex, 1); await setNavigationData(navigationData); return new Response(JSON.stringify({ message: '站点删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } async function loadNotification() { const notification = await NAVIGATION_DATA.get('info'); return new Response(notification || '', { headers: { 'Cache-Control': 'no-cache' } }); } // 页面渲染函数 async function renderLoginPage(showError = false) { return `