// 后台管理 JavaScript(API 地址从 config.js 读取,token 仅从 URL ?token= 传入并由后端校验) const API_BASE = typeof window !== 'undefined' && window.API_BASE !== undefined ? window.API_BASE : ''; // 从 URL 读取 token,不在前端校验,直接交给后端;无 token 则不显示后台 const urlParams = new URLSearchParams(typeof location !== 'undefined' ? location.search : ''); const authToken = urlParams.get('token') || null; let categories = []; let allSites = []; // 显示提示消息 function showToast(message, type = 'success') { const toast = document.getElementById('toast'); toast.className = `toast ${type} show`; document.querySelector('.toast-message').textContent = message; setTimeout(() => { toast.classList.remove('show'); }, 3000); } // 显示无权限提示(无 token 或 token 错误) function showNoPermission() { const noPerm = document.getElementById('no-permission'); const adminEl = document.getElementById('admin-container'); if (noPerm) noPerm.style.display = 'block'; if (adminEl) adminEl.style.display = 'none'; } // 退出:跳转到当前页不带 query,下次进入需重新带 token document.getElementById('logout-btn').addEventListener('click', () => { if (typeof location !== 'undefined') location.href = location.pathname; }); // 显示管理面板(仅 token 正确时) function showAdminPanel() { const noPerm = document.getElementById('no-permission'); const adminEl = document.getElementById('admin-container'); if (noPerm) noPerm.style.display = 'none'; if (adminEl) adminEl.style.display = 'block'; loadSites(); loadCategories(); } // 进入页时先向后端校验 token,通过才显示后台 async function initAdmin() { if (!authToken) { showNoPermission(); return; } try { const response = await fetch(`${API_BASE}/api/auth/check`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.status !== 200) { showNoPermission(); return; } const data = await response.json().catch(() => ({})); if (!data || !data.ok) { showNoPermission(); return; } showAdminPanel(); } catch (_) { showNoPermission(); } } // 加载分类列表 async function loadCategories() { try { const response = await fetch(`${API_BASE}/api/categories`, { headers: authToken ? { 'Authorization': `Bearer ${authToken}` } : {} }); if (response.status === 401) { showNoPermission(); return; } categories = response.ok ? await response.json() : []; renderCategoryList(); updateCategorySelect(); updateSiteCategoryFilterOptions(); if (allSites.length) renderSites(allSites); } catch (error) { categories = []; renderCategoryList(); } } // 渲染分类标签 function renderCategoryList() { const container = document.getElementById('category-list'); container.innerHTML = ''; if (!categories.length) { const empty = document.createElement('div'); empty.style.color = '#64748b'; empty.textContent = '暂无分类'; container.appendChild(empty); return; } categories.forEach(name => { const item = document.createElement('div'); item.className = 'category-item'; item.innerHTML = ` ${name} `; container.appendChild(item); }); } // 更新分类下拉 function updateCategorySelect(selectedValue = '') { const select = document.getElementById('edit-site-category'); if (!select) { return; } const options = ['默认']; const merged = Array.from(new Set([...options, ...categories].filter(Boolean))); select.innerHTML = ''; merged.forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; if (name === selectedValue) { option.selected = true; } select.appendChild(option); }); } // 更新「网站列表」上方的分类筛选下拉 function updateSiteCategoryFilterOptions() { const select = document.getElementById('site-category-filter'); if (!select) return; const current = select.value; const fromSites = (allSites || []).map(s => s.category || '默认').filter(Boolean); const combined = Array.from(new Set(['默认', ...categories, ...fromSites])); select.innerHTML = ''; combined.forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; select.appendChild(option); }); select.value = current || ''; } // 根据当前筛选条件渲染网站表格 function renderSites(sites) { const tbody = document.getElementById('sites-tbody'); const filterEl = document.getElementById('site-category-filter'); const categoryFilter = filterEl ? filterEl.value : ''; const list = categoryFilter ? sites.filter(site => (site.category || '默认') === categoryFilter) : sites; tbody.innerHTML = ''; list.forEach(site => { const tr = document.createElement('tr'); const tagsHtml = site.tags && site.tags.length > 0 ? `
${site.tags.map(tag => `${tag}`).join('')}
` : '-'; tr.innerHTML = ` ${site.name} ${site.url} ${site.category} ${site.description || '-'} ${tagsHtml}
`; tbody.appendChild(tr); }); } // 加载网站列表 async function loadSites() { try { const response = await fetch(`${API_BASE}/api/sites`, { headers: authToken ? { 'Authorization': `Bearer ${authToken}` } : {} }); if (response.status === 401) { showNoPermission(); return; } const sites = await response.json(); allSites = sites || []; updateSiteCategoryFilterOptions(); renderSites(allSites); } catch (error) { showToast('加载网站列表失败', 'error'); } } // 网站列表按分类筛选 const siteCategoryFilterEl = document.getElementById('site-category-filter'); if (siteCategoryFilterEl) { siteCategoryFilterEl.addEventListener('change', () => { renderSites(allSites); }); } // 添加新网站 document.getElementById('add-new-site').addEventListener('click', () => { if (!authToken) return; document.getElementById('modal-title').textContent = '添加新网站'; document.getElementById('edit-site-id').value = ''; document.getElementById('edit-site-form').reset(); updateCategorySelect(); document.getElementById('edit-modal').classList.add('active'); }); // 编辑网站 async function editSite(id) { try { const response = await fetch(`${API_BASE}/api/sites/${id}`); const site = await response.json(); document.getElementById('modal-title').textContent = '编辑网站'; document.getElementById('edit-site-id').value = site.id; document.getElementById('edit-site-name').value = site.name; document.getElementById('edit-site-url').value = site.url; document.getElementById('edit-site-description').value = site.description || ''; updateCategorySelect(site.category); document.getElementById('edit-site-tags').value = site.tags ? site.tags.join(', ') : ''; document.getElementById('edit-modal').classList.add('active'); } catch (error) { showToast('加载网站信息失败', 'error'); } } // 删除网站 async function deleteSite(id) { if (!confirm('确定要删除这个网站吗?')) { return; } try { const response = await fetch(`${API_BASE}/api/sites/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { showToast('网站删除成功'); loadSites(); } else { showToast('删除失败', 'error'); } } catch (error) { showToast('删除请求失败', 'error'); } } // 保存网站(添加或更新) document.getElementById('edit-site-form').addEventListener('submit', async (e) => { e.preventDefault(); const id = document.getElementById('edit-site-id').value; const site = { name: document.getElementById('edit-site-name').value.trim(), url: document.getElementById('edit-site-url').value.trim(), description: document.getElementById('edit-site-description').value.trim(), category: document.getElementById('edit-site-category').value, tags: document.getElementById('edit-site-tags').value .split(',') .map(tag => tag.trim()) .filter(tag => tag) }; // 验证URL if (!site.url.startsWith('http://') && !site.url.startsWith('https://')) { site.url = 'https://' + site.url; } try { const url = id ? `${API_BASE}/api/sites/${id}` : `${API_BASE}/api/sites`; const method = id ? 'PUT' : 'POST'; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify(site) }); if (response.ok) { showToast(id ? '网站更新成功' : '网站添加成功'); document.getElementById('edit-modal').classList.remove('active'); loadSites(); } else { showToast('保存失败', 'error'); } } catch (error) { showToast('保存请求失败', 'error'); } }); // 关闭模态框 document.getElementById('close-edit-modal').addEventListener('click', () => { document.getElementById('edit-modal').classList.remove('active'); }); document.getElementById('edit-modal').addEventListener('click', (e) => { if (e.target.id === 'edit-modal') { document.getElementById('edit-modal').classList.remove('active'); } }); // 添加分类 document.getElementById('add-category-btn').addEventListener('click', async () => { const input = document.getElementById('new-category-name'); const name = input.value.trim(); if (!name) { return; } try { const response = await fetch(`${API_BASE}/api/categories`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ name }) }); if (response.ok) { input.value = ''; await loadCategories(); showToast('分类添加成功'); } else { showToast('分类添加失败', 'error'); } } catch (error) { showToast('分类添加失败', 'error'); } }); // 编辑分类 async function editCategory(oldName) { const newName = prompt('请输入新的分类名称', oldName); if (!newName || newName.trim() === oldName) { return; } try { const response = await fetch(`${API_BASE}/api/categories/${encodeURIComponent(oldName)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ name: newName.trim() }) }); if (response.ok) { await loadCategories(); await loadSites(); showToast('分类更新成功'); } else { showToast('分类更新失败', 'error'); } } catch (error) { showToast('分类更新失败', 'error'); } } // 删除分类 async function deleteCategory(name) { if (!confirm(`确定删除分类「${name}」吗?`)) { return; } try { const response = await fetch(`${API_BASE}/api/categories/${encodeURIComponent(name)}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.ok) { await loadCategories(); showToast('分类删除成功'); } else { showToast('分类删除失败', 'error'); } } catch (error) { showToast('分类删除失败', 'error'); } } // 初始化 initAdmin();