Files
navigation-app/frontend/script.js
2025-03-26 01:57:10 +08:00

367 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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' ? '<i class="fas fa-moon"></i>' : '<i class="fas fa-sun"></i>';
// 加载数据
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 = '<div class="loading">加载中...</div>';
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 = '<div class="error">加载失败,请刷新重试</div>';
}
}
// 渲染网站
function renderSites(sites) {
if (!sites || sites.length === 0) {
categoriesContainer.innerHTML = '<p>暂无网站,请添加您的第一个网站。</p>';
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 = `<i class="fas fa-folder"></i> ${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 renderCategoryList() {
categoryList.innerHTML = '<div class="loading">加载中...</div>';
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 = '<i class="fas fa-trash"></i> 删除';
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 = '<div class="error">加载分类失败</div>';
}
}
// 切换主题
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' ? '<i class="fas fa-moon"></i>' : '<i class="fas fa-sun"></i>';
}
});