diff --git a/frontend/index.html b/frontend/index.html index 5758a22..03ce399 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,34 +5,31 @@ 我的导航网站 - +
-

我的导航网站

+

我的导航

-
- -
-
- - - - - -
+ + + + + +
+
加载中...
@@ -51,9 +48,7 @@
- +
@@ -80,7 +75,6 @@
- \ No newline at end of file diff --git a/frontend/script.js b/frontend/script.js index 2614ba2..b13a4fe 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -1,35 +1,367 @@ -// 修改原有的数据获取方式 -async function fetchSites() { - try { - const response = await fetch('/api/sites'); - if (!response.ok) throw new Error('Network response was not ok'); - return await response.json(); - } catch (error) { - console.error('Error fetching sites:', error); - return {}; +document.addEventListener('DOMContentLoaded', function() { + // DOM元素 + const categoriesContainer = document.getElementById('categories'); + const addSiteModal = document.getElementById('addSiteModal'); + const manageCategoriesModal = document.getElementById('manageCategoriesModal'); + const addSiteBtn = document.getElementById('addSiteBtn'); + const manageCategoriesBtn = document.getElementById('manageCategoriesBtn'); + const closeButtons = document.querySelectorAll('.close'); + const addSiteForm = document.getElementById('addSiteForm'); + const exportBtn = document.getElementById('exportBtn'); + const importBtn = document.getElementById('importBtn'); + const importFile = document.getElementById('importFile'); + const searchInput = document.getElementById('searchInput'); + const themeBtn = document.getElementById('themeBtn'); + const categoryList = document.getElementById('categoryList'); + const newCategoryName = document.getElementById('newCategoryName'); + const addCategoryBtn = document.getElementById('addCategoryBtn'); + + // 当前主题 + const currentTheme = localStorage.getItem('theme') || 'light'; + document.documentElement.setAttribute('data-theme', currentTheme); + themeBtn.innerHTML = currentTheme === 'light' ? '' : ''; + + // 加载数据 + loadSites(); + + // 打开添加网站模态框 + addSiteBtn.addEventListener('click', () => { + addSiteModal.style.display = 'block'; + }); + + // 打开管理分类模态框 + manageCategoriesBtn.addEventListener('click', () => { + renderCategoryList(); + manageCategoriesModal.style.display = 'block'; + }); + + // 关闭模态框 + 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', async (e) => { + e.preventDefault(); + + const name = document.getElementById('siteName').value.trim(); + const url = document.getElementById('siteUrl').value.trim(); + const category = document.getElementById('siteCategory').value.trim(); + const icon = document.getElementById('siteIcon').value.trim(); + + try { + const response = await fetch('/api/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name, + url, + icon: icon || null, + category + }) + }); + + if (!response.ok) throw new Error('添加失败'); + + // 清空表单并关闭模态框 + addSiteForm.reset(); + addSiteModal.style.display = 'none'; + + // 重新加载数据 + loadSites(); + } catch (error) { + console.error('Error:', error); + alert('添加网站时出错: ' + error.message); + } + }); + + // 导出数据 + exportBtn.addEventListener('click', async () => { + try { + const response = await fetch('/api/sites'); + 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('导出数据时出错'); + } + }); + + // 导入数据 + importBtn.addEventListener('click', () => { + importFile.click(); + }); + + importFile.addEventListener('change', async (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 fetch('/api/import', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + 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 = ''; + }); + + // 添加新分类 + addCategoryBtn.addEventListener('click', async () => { + const categoryName = newCategoryName.value.trim(); + if (!categoryName) return; + + try { + // 这里需要根据你的Go后端API进行调整 + const response = await fetch('/api/categories', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ name: categoryName }) + }); + + if (!response.ok) throw new Error('添加分类失败'); + + newCategoryName.value = ''; + renderCategoryList(); + loadSites(); + } catch (error) { + console.error('添加分类失败:', error); + alert('添加分类时出错: ' + error.message); + } + }); + + // 搜索功能 + searchInput.addEventListener('input', (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'; + } + }); + }); + + // 主题切换 + themeBtn.addEventListener('click', toggleTheme); + + // 加载网站数据 + async function loadSites() { + categoriesContainer.innerHTML = '
加载中...
'; + + try { + const response = await fetch('/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 = '
加载失败,请刷新重试
'; + } } -} -// 修改添加网站的函数 -async function addSite(site) { - const response = await fetch('/api/add', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(site) - }); - return await response.json(); -} + // 渲染网站 + 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.setAttribute('data-name', site.name.toLowerCase()); + siteElement.setAttribute('data-url', site.url.toLowerCase()); + + 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; + + 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 fetch(`/api/delete`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + 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(iconElement); + siteElement.appendChild(nameElement); + siteElement.appendChild(deleteBtn); + sitesContainer.appendChild(siteElement); + }); + + categoryElement.appendChild(categoryTitle); + categoryElement.appendChild(sitesContainer); + categoriesContainer.appendChild(categoryElement); + } + } -// 修改删除网站的函数 -async function deleteSite(id) { - const response = await fetch('/api/delete', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ id }) - }); - return await response.json(); -} \ No newline at end of file + // 渲染分类列表 + async function renderCategoryList() { + categoryList.innerHTML = '
加载中...
'; + + try { + const response = await fetch('/api/sites'); + if (!response.ok) throw new Error('Network response was not ok'); + + const sites = await response.json(); + + // 获取所有分类 + const categories = new Set(); + sites.forEach(site => categories.add(site.category)); + + categoryList.innerHTML = ''; + + categories.forEach(category => { + const categoryItem = document.createElement('div'); + categoryItem.className = 'category-item'; + + const categoryName = document.createElement('span'); + categoryName.textContent = category; + + const deleteBtn = document.createElement('button'); + deleteBtn.innerHTML = ' 删除'; + deleteBtn.addEventListener('click', async () => { + if (confirm(`确定要删除分类 "${category}" 吗?这将删除该分类下的所有网站。`)) { + try { + // 这里需要根据你的Go后端API进行调整 + const response = await fetch(`/api/categories/${encodeURIComponent(category)}`, { + method: 'DELETE' + }); + + if (response.ok) { + renderCategoryList(); + loadSites(); + } else { + throw new Error('删除分类失败'); + } + } catch (error) { + console.error('删除分类失败:', error); + alert('删除分类时出错: ' + error.message); + } + } + }); + + categoryItem.appendChild(categoryName); + categoryItem.appendChild(deleteBtn); + categoryList.appendChild(categoryItem); + }); + } catch (error) { + console.error('Error loading categories:', error); + categoryList.innerHTML = '
加载分类失败
'; + } + } + + // 切换主题 + function toggleTheme() { + const currentTheme = document.documentElement.getAttribute('data-theme'); + const newTheme = currentTheme === 'light' ? 'dark' : 'light'; + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + themeBtn.innerHTML = newTheme === 'light' ? '' : ''; + } +}); \ No newline at end of file diff --git a/frontend/styles.css b/frontend/styles.css index 00c45de..a2eab7b 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -59,6 +59,9 @@ header { h1 { font-size: 2rem; color: var(--primary-color); + display: flex; + align-items: center; + gap: 10px; } .search-box { @@ -84,13 +87,7 @@ h1 { opacity: 0.7; } -.actions { - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.actions button { +button { padding: 8px 16px; background-color: var(--primary-color); color: white; @@ -102,21 +99,22 @@ h1 { gap: 5px; } -.actions button:hover { +button:hover { opacity: 0.9; } -.actions button i { +button i { font-size: 0.9rem; } -.theme-switcher button { - background: none; - border: none; - color: var(--text-color); - font-size: 1.2rem; - cursor: pointer; - padding: 5px; +.loading, .error { + text-align: center; + padding: 20px; + color: var(--primary-color); +} + +.error { + color: var(--danger-color); } .categories { @@ -200,12 +198,12 @@ h1 { font-size: 12px; cursor: pointer; display: none; + align-items: center; + justify-content: center; } .site:hover .delete-btn { display: flex; - align-items: center; - justify-content: center; } /* 模态框样式 */ @@ -269,8 +267,7 @@ h1 { gap: 8px; } -.form-group input, -.form-group select { +.form-group input { width: 100%; padding: 10px; border: 1px solid var(--border-color); @@ -326,6 +323,9 @@ form button:hover { border-radius: 4px; padding: 5px 10px; cursor: pointer; + display: flex; + align-items: center; + gap: 5px; } .add-category { @@ -351,51 +351,4 @@ form button:hover { cursor: pointer; display: flex; align-items: center; - gap: 5px; -} - -/* 拖拽样式 */ -.sortable-ghost { - opacity: 0.5; - background: var(--primary-color); -} - -.sortable-chosen { - box-shadow: 0 0 10px var(--shadow-color); -} - -/* 响应式设计 */ -@media (max-width: 768px) { - header { - flex-direction: column; - align-items: stretch; - gap: 15px; - } - - .header-left, .header-right { - flex-direction: column; - align-items: stretch; - gap: 15px; - } - - .search-box { - width: 100%; - } - - .actions { - justify-content: space-between; - } - - .actions button { - flex-grow: 1; - justify-content: center; - } - - .sites { - grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); - } - - .add-category { - flex-direction: column; - } -} \ No newline at end of file + gap: 5px; \ No newline at end of file