document.addEventListener('DOMContentLoaded', function() { // DOM元素 const categoriesContainer = document.getElementById('categories'); const addSiteModal = document.getElementById('addSiteModal'); const loginModal = document.getElementById('loginModal'); const addSiteBtn = document.getElementById('addSiteBtn'); const loginBtn = document.getElementById('loginBtn'); const logoutBtn = document.getElementById('logoutBtn'); const exportBtn = document.getElementById('exportBtn'); const importBtn = document.getElementById('importBtn'); const closeButtons = document.querySelectorAll('.close'); const addSiteForm = document.getElementById('addSiteForm'); const loginForm = document.getElementById('loginForm'); const searchInput = document.getElementById('searchInput'); const themeBtn = document.getElementById('themeBtn'); const importFile = document.getElementById('importFile'); // 状态变量 let authToken = localStorage.getItem('authToken'); let isAdmin = false; let currentTheme = localStorage.getItem('theme') || 'light'; // 初始化 initTheme(); checkAuthStatus(); setupEventListeners(); loadSites(); // 初始化主题 function initTheme() { document.documentElement.setAttribute('data-theme', currentTheme); themeBtn.innerHTML = currentTheme === 'light' ? '' : ''; } // 检查认证状态 async function checkAuthStatus() { if (authToken) { try { const response = await fetchWithAuth('/api/validate'); if (response.ok) { isAdmin = true; setupAdminFeatures(); } } catch (error) { console.error('Auth check failed:', error); logout(); } } updateAuthUI(); } // 设置管理员功能 function setupAdminFeatures() { // 显示管理按钮 addSiteBtn.style.display = 'flex'; exportBtn.style.display = 'flex'; importBtn.style.display = 'flex'; // 设置拖拽排序 setupDragAndDrop(); } // 更新认证UI function updateAuthUI() { if (isAdmin) { loginBtn.style.display = 'none'; logoutBtn.style.display = 'flex'; } else { loginBtn.style.display = 'flex'; logoutBtn.style.display = 'none'; } } // 设置拖拽排序 function setupDragAndDrop() { let draggedItem = null; // 拖拽开始 const handleDragStart = (e) => { draggedItem = e.target; e.target.classList.add('dragging'); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/html', e.target.innerHTML); }; // 拖拽结束 const handleDragEnd = () => { draggedItem.classList.remove('dragging'); draggedItem = null; }; // 拖拽经过 const handleDragOver = (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; const afterElement = getDragAfterElement(categoriesContainer, e.clientY); if (afterElement == null) { categoriesContainer.appendChild(draggedItem); } else { categoriesContainer.insertBefore(draggedItem, afterElement); } }; // 获取拖拽后的元素 const getDragAfterElement = (container, y) => { const draggableElements = [...container.querySelectorAll('.site:not(.dragging)')]; return draggableElements.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = y - box.top - box.height / 2; if (offset < 0 && offset > closest.offset) { return { offset: offset, element: child }; } else { return closest; } }, { offset: Number.NEGATIVE_INFINITY }).element; }; // 为所有网站添加拖拽事件 document.querySelectorAll('.site').forEach(site => { site.draggable = isAdmin; site.addEventListener('dragstart', handleDragStart); site.addEventListener('dragend', handleDragEnd); }); categoriesContainer.addEventListener('dragover', handleDragOver); categoriesContainer.addEventListener('dragend', async () => { // 更新排序 const sites = []; document.querySelectorAll('.site').forEach((siteEl, index) => { sites.push({ id: parseInt(siteEl.dataset.id), order: index }); }); try { const response = await fetchWithAuth('/api/sites/order', { method: 'POST', body: JSON.stringify(sites) }); if (!response.ok) throw new Error('Failed to update order'); } catch (error) { console.error('Error updating order:', error); alert('排序更新失败'); } }); } // 设置事件监听器 function setupEventListeners() { // 主题切换 themeBtn.addEventListener('click', toggleTheme); // 打开添加网站模态框 addSiteBtn.addEventListener('click', () => addSiteModal.style.display = 'block'); // 打开登录模态框 loginBtn.addEventListener('click', () => loginModal.style.display = 'block'); // 注销 logoutBtn.addEventListener('click', logout); // 关闭模态框 closeButtons.forEach(btn => { btn.addEventListener('click', function() { this.closest('.modal').style.display = 'none'; }); }); // 点击模态框外部关闭 window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { e.target.style.display = 'none'; } }); // 添加网站表单提交 addSiteForm.addEventListener('submit', handleAddSite); // 登录表单提交 loginForm.addEventListener('submit', handleLogin); // 搜索功能 searchInput.addEventListener('input', handleSearch); // 导出数据 exportBtn.addEventListener('click', handleExport); // 导入数据 importBtn.addEventListener('click', () => importFile.click()); importFile.addEventListener('change', handleImport); } // 添加网站处理 async function handleAddSite(e) { e.preventDefault(); const name = document.getElementById('siteName').value.trim(); let url = document.getElementById('siteUrl').value.trim(); const category = document.getElementById('siteCategory').value.trim(); const icon = document.getElementById('siteIcon').value.trim(); // 确保URL有协议 if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } try { const response = await fetchWithAuth('/api/sites/add', { method: 'POST', body: JSON.stringify({ name, url, icon: icon || null, category, order: 0 }) }); if (!response.ok) throw new Error('添加失败'); // 清空表单并关闭模态框 addSiteForm.reset(); addSiteModal.style.display = 'none'; // 重新加载数据 loadSites(); } catch (error) { console.error('Error:', error); alert('添加网站时出错: ' + error.message); } } // 登录处理 async function handleLogin(e) { e.preventDefault(); const username = document.getElementById('username').value.trim(); const password = document.getElementById('password').value.trim(); try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password }) }); if (!response.ok) throw new Error('登录失败'); const data = await response.json(); authToken = data.token; localStorage.setItem('authToken', authToken); isAdmin = true; loginModal.style.display = 'none'; loginForm.reset(); setupAdminFeatures(); updateAuthUI(); loadSites(); } catch (error) { console.error('Login error:', error); alert('登录失败: ' + error.message); } } // 注销处理 function logout() { localStorage.removeItem('authToken'); authToken = null; isAdmin = false; updateAuthUI(); window.location.reload(); } // 搜索处理 function handleSearch(e) { const searchTerm = e.target.value.toLowerCase(); document.querySelectorAll('.site').forEach(site => { const name = site.getAttribute('data-name') || ''; const url = site.getAttribute('data-url') || ''; if (name.includes(searchTerm) || url.includes(searchTerm)) { site.style.display = 'flex'; } else { site.style.display = 'none'; } }); } // 导出处理 async function handleExport() { try { const response = await fetchWithAuth('/api/export'); if (!response.ok) throw new Error('导出失败'); const data = await response.json(); const dataStr = JSON.stringify(data, null, 2); const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); const exportFileDefaultName = `导航数据_${new Date().toISOString().slice(0,10)}.json`; const linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', exportFileDefaultName); linkElement.click(); } catch (error) { console.error('导出失败:', error); alert('导出数据时出错'); } } // 导入处理 async function handleImport(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async (e) => { try { const importedData = JSON.parse(e.target.result); if (confirm('确定要导入数据吗?这将覆盖当前所有导航数据。')) { const response = await fetchWithAuth('/api/import', { method: 'POST', body: JSON.stringify(importedData) }); if (!response.ok) throw new Error('导入失败'); alert('数据导入成功!'); loadSites(); } } catch (error) { console.error('导入失败:', error); alert('导入数据时出错: ' + error.message); } }; reader.readAsText(file); // 清空input,以便可以重复导入同一文件 e.target.value = ''; } // 切换主题 function toggleTheme() { currentTheme = currentTheme === 'light' ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', currentTheme); localStorage.setItem('theme', currentTheme); themeBtn.innerHTML = currentTheme === 'light' ? '' : ''; } // 带认证的fetch async function fetchWithAuth(url, options = {}) { const headers = { 'Content-Type': 'application/json', ...options.headers }; if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } return fetch(url, { ...options, headers }); } // 加载网站数据 async function loadSites() { categoriesContainer.innerHTML = '
加载中...
'; try { const response = await fetchWithAuth('/api/sites'); if (!response.ok) throw new Error('Network response was not ok'); const sites = await response.json(); renderSites(sites); } catch (error) { console.error('Error loading sites:', error); categoriesContainer.innerHTML = '
加载失败,请刷新重试
'; } } // 渲染网站 function renderSites(sites) { if (!sites || sites.length === 0) { categoriesContainer.innerHTML = '

暂无网站,请添加您的第一个网站。

'; return; } // 按分类分组 const categories = {}; sites.forEach(site => { if (!categories[site.category]) { categories[site.category] = []; } categories[site.category].push(site); }); // 清空容器 categoriesContainer.innerHTML = ''; // 渲染每个分类 for (const category in categories) { const categoryElement = document.createElement('div'); categoryElement.className = 'category'; const categoryTitle = document.createElement('h2'); categoryTitle.innerHTML = ` ${category}`; const sitesContainer = document.createElement('div'); sitesContainer.className = 'sites'; categories[category].forEach(site => { const siteElement = document.createElement('a'); siteElement.className = 'site'; siteElement.href = site.url; siteElement.target = '_blank'; siteElement.dataset.id = site.id; siteElement.dataset.name = site.name.toLowerCase(); siteElement.dataset.url = site.url.toLowerCase(); siteElement.draggable = isAdmin; const iconElement = document.createElement('div'); iconElement.className = 'site-icon'; if (site.icon) { const img = document.createElement('img'); img.src = site.icon; img.alt = site.name; iconElement.appendChild(img); } else { iconElement.textContent = site.name.charAt(0).toUpperCase(); } const nameElement = document.createElement('span'); nameElement.className = 'site-name'; nameElement.textContent = site.name; // 管理员功能:删除按钮 if (isAdmin) { const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-btn'; deleteBtn.innerHTML = '×'; deleteBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); if (confirm(`确定要删除 "${site.name}" 吗?`)) { try { const response = await fetchWithAuth('/api/sites/delete', { method: 'POST', body: JSON.stringify({ id: site.id }) }); if (response.ok) { loadSites(); } else { throw new Error('删除失败'); } } catch (error) { console.error('Error deleting site:', error); alert('删除网站时出错'); } } }); siteElement.appendChild(deleteBtn); } siteElement.appendChild(iconElement); siteElement.appendChild(nameElement); sitesContainer.appendChild(siteElement); }); categoryElement.appendChild(categoryTitle); categoryElement.appendChild(sitesContainer); categoriesContainer.appendChild(categoryElement); } // 如果是管理员,设置拖拽功能 if (isAdmin) { setupDragAndDrop(); } } });