Files
2026-03-11 20:46:24 +08:00

421 lines
14 KiB
JavaScript
Raw Permalink 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.
// 后台管理 JavaScriptAPI 地址从 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 = `
<span>${name}</span>
<span class="category-actions">
<button onclick="editCategory('${name.replace(/'/g, "\\'")}')">编辑</button>
<button onclick="deleteCategory('${name.replace(/'/g, "\\'")}')">删除</button>
</span>
`;
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 = '<option value="">选择分类</option>';
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 = '<option value="">全部</option>';
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
? `<div class="tag-display">${site.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}</div>`
: '-';
tr.innerHTML = `
<td><strong>${site.name}</strong></td>
<td><a href="${site.url}" target="_blank" style="color: #3b82f6;">${site.url}</a></td>
<td>${site.category}</td>
<td>${site.description || '-'}</td>
<td>${tagsHtml}</td>
<td>
<div class="action-btns">
<button class="btn-edit" onclick="editSite('${site.id}')">编辑</button>
<button class="btn-delete" onclick="deleteSite('${site.id}')">删除</button>
</div>
</td>
`;
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();