444 lines
12 KiB
JavaScript
444 lines
12 KiB
JavaScript
// 管理员密码(实际应用中应该使用更安全的方式)
|
|
const ADMIN_PASSWORD = 'JLUnaga2026';
|
|
|
|
// 全局变量存储当前数据和排序状态
|
|
let currentRecords = [];
|
|
let currentSortField = null;
|
|
let currentSortOrder = 'asc'; // 'asc' 或 'desc'
|
|
|
|
// 页面加载时初始化
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
checkAuth();
|
|
initAuthForm();
|
|
initEditForm();
|
|
initModal();
|
|
});
|
|
|
|
// 检查认证状态
|
|
function checkAuth() {
|
|
const isAuth = localStorage.getItem('adminAuth') === 'true';
|
|
if (isAuth) {
|
|
showAdminSection();
|
|
loadAdminData();
|
|
}
|
|
}
|
|
|
|
// 初始化认证表单
|
|
function initAuthForm() {
|
|
const authForm = document.getElementById('authForm');
|
|
authForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
const password = document.getElementById('adminPassword').value;
|
|
|
|
if (password === ADMIN_PASSWORD) {
|
|
localStorage.setItem('adminAuth', 'true');
|
|
showAdminSection();
|
|
loadAdminData();
|
|
} else {
|
|
alert('❌ 密码错误');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 显示管理员区域
|
|
function showAdminSection() {
|
|
document.getElementById('authSection').style.display = 'none';
|
|
document.getElementById('adminSection').style.display = 'block';
|
|
}
|
|
|
|
// 加载管理员数据
|
|
async function loadAdminData() {
|
|
try {
|
|
const response = await fetch('/api/list');
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
currentRecords = result.records;
|
|
renderAdminTable(currentRecords);
|
|
} else {
|
|
alert('加载数据失败: ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
console.error('加载数据出错:', error);
|
|
document.getElementById('adminTableBody').innerHTML = `
|
|
<tr><td colspan="10" class="loading">加载失败,请刷新重试</td></tr>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 渲染管理员表格
|
|
function renderAdminTable(records) {
|
|
const tbody = document.getElementById('adminTableBody');
|
|
|
|
if (records.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="10" class="loading">暂无数据</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = records.map((record, index) => {
|
|
const hasNaga = record.nagaLink && record.nagaLink.trim() !== '';
|
|
const nagaStatus = hasNaga
|
|
? '<span class="has-naga">✔ 有</span>'
|
|
: '<span class="no-naga">✘ 无</span>';
|
|
|
|
const nagaLinkDisplay = hasNaga
|
|
? `<a href="${escapeHtml(record.nagaLink)}" target="_blank">查看链接</a>`
|
|
: '-';
|
|
|
|
const nagaNoteDisplay = record.nagaNote && record.nagaNote.trim() !== ''
|
|
? escapeHtml(record.nagaNote)
|
|
: '-';
|
|
|
|
const imagesDisplay = record.images && record.images.length > 0
|
|
? `<div class="image-thumbs">
|
|
${record.images.map(img => `
|
|
<img src="${img}" class="thumb-img" onclick="showImages(${JSON.stringify(record.images).replace(/"/g, '"')})">
|
|
`).join('')}
|
|
</div>`
|
|
: '-';
|
|
|
|
return `
|
|
<tr>
|
|
<td>${index + 1}</td>
|
|
<td>${escapeHtml(record.submitTime)}</td>
|
|
<td>${escapeHtml(record.name)}</td>
|
|
<td class="link-cell"><a href="${escapeHtml(record.link)}" target="_blank">查看牌谱</a></td>
|
|
<td class="note-cell">${record.note ? escapeHtml(record.note) : '-'}</td>
|
|
<td>${nagaStatus}</td>
|
|
<td class="link-cell">${nagaLinkDisplay}</td>
|
|
<td class="note-cell">${nagaNoteDisplay}</td>
|
|
<td>${imagesDisplay}</td>
|
|
<td>
|
|
<button class="btn btn-edit" onclick="openEditModal(${record.id})">编辑</button>
|
|
<button class="btn btn-delete" onclick="deleteRecord(${record.id})">删除</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// 打开编辑模态框
|
|
async function openEditModal(id) {
|
|
try {
|
|
const response = await fetch('/api/list');
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
alert('获取数据失败');
|
|
return;
|
|
}
|
|
|
|
const record = result.records.find(r => r.id === id);
|
|
if (!record) {
|
|
alert('记录不存在');
|
|
return;
|
|
}
|
|
|
|
// 填充表单
|
|
document.getElementById('editId').value = record.id;
|
|
document.getElementById('editNagaLink').value = record.nagaLink || '';
|
|
document.getElementById('editNagaNote').value = record.nagaNote || '';
|
|
document.getElementById('nagaNoteCount').textContent = (record.nagaNote || '').length;
|
|
|
|
// 清空图片预览和已有图片
|
|
document.getElementById('imagePreview').innerHTML = '';
|
|
document.getElementById('editImages').value = '';
|
|
|
|
// 显示已有图片
|
|
const existingImagesDiv = document.getElementById('existingImages');
|
|
if (record.images && record.images.length > 0) {
|
|
existingImagesDiv.innerHTML = record.images.map(img => `
|
|
<div class="existing-image-item">
|
|
<img src="${img}" alt="已上传图片">
|
|
<button type="button" class="btn-remove-image" onclick="removeImage(${record.id}, '${img}')">✕</button>
|
|
</div>
|
|
`).join('');
|
|
} else {
|
|
existingImagesDiv.innerHTML = '<p class="no-images">暂无图片</p>';
|
|
}
|
|
|
|
// 显示只读信息
|
|
const readonlyInfo = document.getElementById('readonlyInfo');
|
|
readonlyInfo.innerHTML = `
|
|
<p><strong>人名:</strong> ${escapeHtml(record.name)}</p>
|
|
<p><strong>提交时间:</strong> ${escapeHtml(record.submitTime)}</p>
|
|
<p><strong>牌谱链接:</strong> <a href="${escapeHtml(record.link)}" target="_blank">查看</a></p>
|
|
<p><strong>备注:</strong> ${record.note ? escapeHtml(record.note) : '无'}</p>
|
|
`;
|
|
|
|
// 显示模态框
|
|
document.getElementById('editModal').style.display = 'block';
|
|
} catch (error) {
|
|
alert('打开编辑器失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 关闭编辑模态框
|
|
function closeEditModal() {
|
|
document.getElementById('editModal').style.display = 'none';
|
|
}
|
|
|
|
// 初始化编辑表单
|
|
function initEditForm() {
|
|
const editForm = document.getElementById('editForm');
|
|
const nagaNoteInput = document.getElementById('editNagaNote');
|
|
const nagaNoteCount = document.getElementById('nagaNoteCount');
|
|
const imageInput = document.getElementById('editImages');
|
|
const imagePreview = document.getElementById('imagePreview');
|
|
|
|
// 字数统计
|
|
nagaNoteInput.addEventListener('input', () => {
|
|
nagaNoteCount.textContent = nagaNoteInput.value.length;
|
|
});
|
|
|
|
// 图片预览
|
|
imageInput.addEventListener('change', (e) => {
|
|
imagePreview.innerHTML = '';
|
|
const files = e.target.files;
|
|
|
|
if (files.length > 10) {
|
|
alert('最多只能上传 10 张图片');
|
|
imageInput.value = '';
|
|
return;
|
|
}
|
|
|
|
Array.from(files).forEach(file => {
|
|
if (file.size > 5 * 1024 * 1024) {
|
|
alert(`文件 ${file.name} 超过 5MB`);
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
const div = document.createElement('div');
|
|
div.className = 'preview-item';
|
|
div.innerHTML = `<img src="${e.target.result}" alt="预览">`;
|
|
imagePreview.appendChild(div);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
});
|
|
|
|
// 表单提交
|
|
editForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await updateRecord();
|
|
});
|
|
}
|
|
|
|
// 更新记录
|
|
async function updateRecord() {
|
|
const id = document.getElementById('editId').value;
|
|
const nagaLink = document.getElementById('editNagaLink').value;
|
|
const nagaNote = document.getElementById('editNagaNote').value;
|
|
const imageInput = document.getElementById('editImages');
|
|
|
|
try {
|
|
// 获取已有图片列表
|
|
const response = await fetch('/api/list');
|
|
const result = await response.json();
|
|
const record = result.records.find(r => r.id === parseInt(id));
|
|
const existingImages = record ? record.images : [];
|
|
|
|
// 创建 FormData
|
|
const formData = new FormData();
|
|
formData.append('id', id);
|
|
formData.append('nagaLink', nagaLink.trim());
|
|
formData.append('nagaNote', nagaNote.trim());
|
|
formData.append('existingImages', JSON.stringify(existingImages));
|
|
|
|
// 添加新图片
|
|
if (imageInput.files.length > 0) {
|
|
Array.from(imageInput.files).forEach(file => {
|
|
formData.append('images', file);
|
|
});
|
|
}
|
|
|
|
const updateResponse = await fetch('/api/admin/update', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const updateResult = await updateResponse.json();
|
|
|
|
if (updateResult.success) {
|
|
alert('✅ 更新成功!');
|
|
closeEditModal();
|
|
loadAdminData();
|
|
} else {
|
|
alert('❌ ' + updateResult.message);
|
|
}
|
|
} catch (error) {
|
|
alert('❌ 更新失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 删除单张图片
|
|
async function removeImage(id, imagePath) {
|
|
if (!confirm('确定要删除这张图片吗?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/admin/delete-image', {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
id: id,
|
|
imagePath: imagePath
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
alert('✅ 图片删除成功!');
|
|
// 重新打开编辑框以刷新图片列表
|
|
openEditModal(id);
|
|
} else {
|
|
alert('❌ ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('❌ 删除图片失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 删除记录
|
|
async function deleteRecord(id) {
|
|
if (!confirm('确定要删除这条记录吗?此操作不可恢复!')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/admin/delete/${id}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
alert('✅ 删除成功!');
|
|
loadAdminData();
|
|
} else {
|
|
alert('❌ ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('❌ 删除失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 显示图片模态框
|
|
function showImages(images) {
|
|
const modal = document.getElementById('imageModal');
|
|
const modalImages = document.getElementById('modalImages');
|
|
|
|
modalImages.innerHTML = images.map(img => `
|
|
<img src="${img}" alt="图片">
|
|
`).join('');
|
|
|
|
modal.style.display = 'block';
|
|
}
|
|
|
|
// 初始化图片模态框
|
|
function initModal() {
|
|
const imageModal = document.getElementById('imageModal');
|
|
const editModal = document.getElementById('editModal');
|
|
|
|
// 图片模态框关闭
|
|
imageModal.querySelector('.close').onclick = () => {
|
|
imageModal.style.display = 'none';
|
|
};
|
|
|
|
// 点击模态框外部关闭
|
|
window.onclick = (event) => {
|
|
if (event.target === imageModal) {
|
|
imageModal.style.display = 'none';
|
|
}
|
|
if (event.target === editModal) {
|
|
closeEditModal();
|
|
}
|
|
};
|
|
}
|
|
|
|
// HTML 转义函数
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// 排序表格
|
|
function sortTable(field) {
|
|
// 如果点击同一列,切换排序方向
|
|
if (currentSortField === field) {
|
|
currentSortOrder = currentSortOrder === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
currentSortField = field;
|
|
currentSortOrder = 'asc';
|
|
}
|
|
|
|
// 复制数组进行排序
|
|
const sortedRecords = [...currentRecords];
|
|
|
|
sortedRecords.sort((a, b) => {
|
|
let valueA, valueB;
|
|
|
|
switch(field) {
|
|
case 'id':
|
|
valueA = a.id;
|
|
valueB = b.id;
|
|
break;
|
|
case 'submitTime':
|
|
valueA = a.submitTime;
|
|
valueB = b.submitTime;
|
|
break;
|
|
case 'name':
|
|
valueA = a.name.toLowerCase();
|
|
valueB = b.name.toLowerCase();
|
|
break;
|
|
case 'hasNaga':
|
|
valueA = (a.nagaLink && a.nagaLink.trim() !== '') ? 1 : 0;
|
|
valueB = (b.nagaLink && b.nagaLink.trim() !== '') ? 1 : 0;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (valueA < valueB) {
|
|
return currentSortOrder === 'asc' ? -1 : 1;
|
|
}
|
|
if (valueA > valueB) {
|
|
return currentSortOrder === 'asc' ? 1 : -1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
renderAdminTable(sortedRecords);
|
|
updateSortIcons();
|
|
}
|
|
|
|
// 更新排序图标
|
|
function updateSortIcons() {
|
|
// 移除所有排序图标的激活状态
|
|
document.querySelectorAll('.sort-icon').forEach(icon => {
|
|
icon.textContent = '⇅';
|
|
icon.classList.remove('sort-asc', 'sort-desc');
|
|
});
|
|
|
|
// 如果有当前排序字段,更新对应图标
|
|
if (currentSortField) {
|
|
const headers = document.querySelectorAll('th.sortable');
|
|
headers.forEach(th => {
|
|
const onclick = th.getAttribute('onclick');
|
|
if (onclick && onclick.includes(`'${currentSortField}'`)) {
|
|
const icon = th.querySelector('.sort-icon');
|
|
if (icon) {
|
|
icon.textContent = currentSortOrder === 'asc' ? '↑' : '↓';
|
|
icon.classList.add(currentSortOrder === 'asc' ? 'sort-asc' : 'sort-desc');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} |