feat: complete local version to overwrite remote

This commit is contained in:
DelLevin-Home
2026-06-16 03:30:57 +08:00
parent 1735c19f48
commit 3c78293f4d
129 changed files with 22814 additions and 0 deletions

View File

@@ -0,0 +1,725 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>评论后台管理</title>
<style>
:root {
--el-color-primary: #409eff;
--el-color-success: #67c23a;
--el-color-danger: #f56c6c;
--el-color-info: #909399;
--el-text-primary: #303133;
--el-text-regular: #606266;
--el-text-secondary: #909399;
--el-border-color: #dcdfe6;
--el-border-light: #e4e7ed;
--el-fill-color: #f5f7fa;
--el-header-height: 60px;
--el-aside-width: 200px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif; background-color: #f0f2f5; color: var(--el-text-regular); }
/* ========== 顶部 Header ========== */
.header {
position: fixed; top: 0; left: 0; right: 0; height: var(--el-header-height);
background: #fff; border-bottom: 1px solid var(--el-border-light);
display: flex; align-items: center; justify-content: space-between; padding: 0 20px;
z-index: 1000; box-shadow: 0 1px 4px rgba(0,0,0,.08);
}
.header-brand { font-size: 18px; font-weight: 600; color: var(--el-text-primary); letter-spacing: 1px; }
.header-right { display: flex; align-items: center; gap: 20px; font-size: 14px; }
.header-right a { color: var(--el-text-secondary); text-decoration: none; transition: color .3s; }
.header-right a:hover { color: var(--el-color-primary); }
/* ========== 左侧 Aside ========== */
.aside {
position: fixed; top: var(--el-header-height); left: 0; bottom: 0;
width: var(--el-aside-width); background: #304156;
z-index: 1000; overflow-y: auto;
}
.menu-item {
display: block; padding: 0 20px; height: 56px; line-height: 56px;
color: #bfcbd9; font-size: 14px; cursor: pointer; transition: all .3s;
text-decoration: none; border-left: 3px solid transparent;
}
.menu-item:hover, .menu-item.active {
background: #263445; color: var(--el-color-primary); border-left-color: var(--el-color-primary);
}
.menu-icon { display: inline-block; width: 20px; margin-right: 10px; text-align: center; }
/* 左侧状态筛选 */
.aside-status-group {
padding: 10px 0;
border-top: 1px solid rgba(255,255,255,0.1);
margin-top: 10px;
}
.aside-menu-item {
display: block; padding: 0 20px; height: 48px; line-height: 48px;
color: #bfcbd9; font-size: 14px; cursor: pointer; transition: all .3s;
text-decoration: none; border-left: 3px solid transparent;
}
.aside-menu-item:hover, .aside-menu-item.active {
background: #263445; color: var(--el-color-primary); border-left-color: var(--el-color-primary);
}
/* ========== 右侧 Main ========== */
.main {
margin-left: var(--el-aside-width); margin-top: var(--el-header-height);
padding: 20px; min-height: calc(100vh - var(--el-header-height));
}
.main-card {
background: #fff; border-radius: 4px; padding: 20px;
box-shadow: 0 1px 4px rgba(0,0,0,.08);
}
.page-title { font-size: 20px; font-weight: 500; color: var(--el-text-primary); margin-bottom: 20px; }
/* 筛选区 */
.filters { display: flex; gap: 10px; margin-bottom: 20px; align-items: center; flex-wrap: wrap; padding: 15px; background: var(--el-fill-color); border-radius: 4px; }
input[type="text"] {
appearance: none; background-color: #fff; border-radius: 4px; border: 1px solid var(--el-border-color);
color: var(--el-text-regular); height: 36px; line-height: 36px; outline: none; padding: 0 15px;
transition: border-color .2s; width: 220px;
}
input[type="text"]:focus { border-color: var(--el-color-primary); }
/* 状态单选按钮组 */
.status-group { display: inline-flex; border: 1px solid var(--el-border-color); border-radius: 4px; overflow: hidden; background: #fff; }
.radio-label {
cursor: pointer; padding: 0 16px; height: 34px; line-height: 34px; font-size: 14px;
transition: all .3s; user-select: none; background: #fff; color: var(--el-text-regular); border-right: 1px solid var(--el-border-color);
}
.radio-label:last-child { border-right: none; }
.radio-label:hover { color: var(--el-color-primary); }
.radio-label.active { background-color: var(--el-color-primary); border-color: var(--el-color-primary); color: #fff; }
input[type="radio"] { display: none; }
button.el-button {
display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #fff;
border: 1px solid var(--el-border-color); color: var(--el-text-regular); text-align: center;
outline: none; transition: .1s; font-weight: 500; padding: 9px 20px; font-size: 14px; border-radius: 4px;
}
button.el-button:hover { color: var(--el-color-primary); border-color: #c6e2ff; background-color: #ecf5ff; }
button.el-button--primary { color: #fff; background-color: var(--el-color-primary); border-color: var(--el-color-primary); }
button.el-button--primary:hover { background: #66b1ff; border-color: #66b1ff; }
/* 表格 */
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { background-color: var(--el-fill-color); color: var(--el-text-secondary); font-weight: 500; text-align: left; padding: 12px 10px; border-bottom: 1px solid var(--el-border-light); }
td { padding: 12px 10px; border-bottom: 1px solid var(--el-border-light); color: var(--el-text-regular); }
tr:hover td { background-color: var(--el-fill-color); }
.status-badge { padding: 0 10px; height: 22px; line-height: 22px; border-radius: 4px; font-size: 12px; display: inline-block; }
.status-approved { background-color: #f0f9eb; color: var(--el-color-success); border: 1px solid #e1f3d8; }
.status-spam { background-color: #fef0f0; color: var(--el-color-danger); border: 1px solid #fde2e2; }
.status-deleted { background-color: #f4f4f5; color: var(--el-color-info); border: 1px solid #e9e9eb; }
.actions button {
margin-right: 8px; padding: 0; background: none; border: none; cursor: pointer;
color: var(--el-color-primary); font-size: 14px; transition: .3s;
}
.actions button:hover { color: #66b1ff; }
.actions button.text-danger { color: var(--el-color-danger); }
.actions button.text-danger:hover { color: #f78989; }
/* 弹窗 */
.modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); display: none; justify-content: center; align-items: center;
z-index: 2000; backdrop-filter: blur(2px);
}
.modal-box {
background: white; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
width: 420px; animation: dialog-fade-in .3s;
}
@keyframes dialog-fade-in {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.modal-header {
padding: 20px; font-size: 16px; color: var(--el-text-primary);
font-weight: 500; border-bottom: 1px solid var(--el-border-light);
}
.modal-body { padding: 20px; color: var(--el-text-regular); font-size: 14px; line-height: 1.6; }
.modal-footer {
padding: 15px 20px; text-align: right;
border-top: 1px solid var(--el-border-light);
}
.modal-btn {
border-radius: 4px; border: 1px solid var(--el-border-color); background: #fff;
color: var(--el-text-regular); min-width: 80px; height: 36px; line-height: 34px;
padding: 0 20px; cursor: pointer; font-size: 14px; transition: all .3s;
}
.modal-btn:hover { color: var(--el-color-primary); border-color: #c6e2ff; background-color: #ecf5ff; }
.modal-btn.primary { color: #fff; background-color: var(--el-color-primary); border-color: var(--el-color-primary); }
.modal-btn.primary:hover { background: #66b1ff; border-color: #66b1ff; }
/* 详情弹窗样式优化 */
.detail-modal-content { width: 700px; max-width: 90%; }
.detail-grid {
display: grid; grid-template-columns: 100px 1fr;
gap: 12px 20px; align-items: start;
}
.detail-label {
color: var(--el-text-secondary); font-size: 14px;
text-align: right; padding-top: 2px;
}
.detail-value {
word-break: break-all; line-height: 1.8;
color: var(--el-text-primary); font-size: 14px;
}
.comment-html {
background: linear-gradient(to bottom, #fafafa, #f5f7fa);
padding: 20px; border-radius: 6px;
border: 1px solid var(--el-border-light);
box-shadow: inset 0 1px 2px rgba(0,0,0,0.05);
}
.detail-divider {
grid-column: 1 / -1; height: 1px;
background: var(--el-border-light); margin: 8px 0;
}
/* 分页 - Element Pagination 风格 */
.pagination {
display: flex; justify-content: flex-end; align-items: center; gap: 8px;
margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--el-border-light);
flex-wrap: wrap;
}
.page-info { color: var(--el-text-secondary); font-size: 14px; margin-right: 10px; }
.page-btn {
min-width: 32px; height: 32px; line-height: 30px; padding: 0 6px;
border: 1px solid var(--el-border-color); background: #fff; border-radius: 4px;
cursor: pointer; font-size: 14px; color: var(--el-text-regular); transition: all .3s;
}
.page-btn:hover:not(.disabled):not(.active) { color: var(--el-color-primary); border-color: var(--el-color-primary); }
.page-btn.active { background-color: var(--el-color-primary); border-color: var(--el-color-primary); color: #fff; }
.page-btn.disabled { cursor: not-allowed; color: var(--el-text-secondary); background: #f5f7fa; }
/* 暂无数据样式 */
.empty-state {
text-align: center !important;
padding: 40px 0 !important;
color: var(--el-text-secondary) !important;
font-size: 14px !important;
background-color: #fff !important;
}
/* ========== 移动端适配 ========== */
@media screen and (max-width: 768px) {
:root {
--el-aside-width: 0px;
}
/* 顶部 Header */
.header { padding: 0 15px; }
.header-brand { font-size: 16px; }
.header-right span { display: none; }
/* 左侧侧边栏 - 隐藏 */
.aside { display: none; }
/* 右侧 Main */
.main { margin-left: 0; padding: 10px; }
.main-card { padding: 15px; }
.page-title { font-size: 18px; margin-bottom: 15px; }
/* 移动端底部状态筛选 */
.mobile-status-bar {
display: flex !important;
gap: 8px; margin-bottom: 15px; overflow-x: auto;
padding-bottom: 5px;
}
.mobile-status-item {
flex-shrink: 0; padding: 6px 14px; border-radius: 20px;
border: 1px solid var(--el-border-color); background: #fff;
color: var(--el-text-regular); font-size: 13px; cursor: pointer;
transition: all .3s;
}
.mobile-status-item.active {
background: var(--el-color-primary); color: #fff; border-color: var(--el-color-primary);
}
/* 筛选区 */
.filters {
flex-direction: column; align-items: stretch; gap: 10px;
padding: 12px;
}
input[type="text"] { width: 100%; }
button.el-button { width: 100%; }
/* 表格横向滚动 */
.table-wrapper { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { min-width: 600px; }
th, td { padding: 10px 8px; font-size: 13px; }
th:first-child, td:first-child { padding-left: 8px; }
th:last-child, td:last-child { padding-right: 8px; }
/* 操作按钮调整 */
.actions button { font-size: 13px; margin-right: 6px; }
/* 分页 */
.pagination { justify-content: center; }
.page-info { width: 100%; text-align: center; margin-bottom: 8px; }
/* 弹窗 */
.modal-box { width: 90% !important; max-width: 400px; }
.detail-modal-content { width: 92% !important; }
.detail-grid { grid-template-columns: 70px 1fr; gap: 8px 12px; }
.detail-label { font-size: 13px; }
.detail-value { font-size: 13px; }
}
/* 小于 480px 的屏幕 */
@media screen and (max-width: 480px) {
.main { padding: 8px; }
.main-card { padding: 12px; border-radius: 8px; }
.page-title { font-size: 16px; }
th, td { padding: 8px 6px; font-size: 12px; }
.status-badge { padding: 0 6px; height: 20px; line-height: 20px; font-size: 11px; }
.actions button { font-size: 12px; }
.modal-header { padding: 15px; font-size: 15px; }
.modal-body { padding: 15px; }
.modal-footer { padding: 12px 15px; }
.modal-btn { min-width: 70px; height: 34px; line-height: 32px; padding: 0 15px; font-size: 13px; }
.detail-grid { grid-template-columns: 60px 1fr; gap: 6px 10px; }
}
</style>
</head>
<body>
<!-- 顶部 Header -->
<header class="header">
<div class="header-brand">评论后台管理系统</div>
<div class="header-right">
<span>欢迎admin</span>
<a href="/logout">退出登录</a>
</div>
</header>
<!-- 左侧 Aside -->
<aside class="aside">
<div class="aside-status-group">
<div class="aside-menu-item active" onclick="selectStatus(this, '')">全部</div>
<div class="aside-menu-item" onclick="selectStatus(this, 'approved')">通过</div>
<div class="aside-menu-item" onclick="selectStatus(this, 'spam')">垃圾</div>
<div class="aside-menu-item" onclick="selectStatus(this, 'deleted')">删除</div>
</div>
</aside>
<!-- 右侧 Main -->
<main class="main">
<div class="main-card">
<div class="page-title">评论列表</div>
<!-- 确认弹窗 -->
<div id="confirmModal" class="modal-overlay">
<div class="modal-box">
<div class="modal-header">提示</div>
<div class="modal-body" id="modalTitle"></div>
<div class="modal-footer">
<button class="modal-btn" onclick="closeModal(false)">取消</button>
<button class="modal-btn primary" onclick="closeModal(true)">确定</button>
</div>
</div>
</div>
<!-- 详情弹窗 -->
<div id="detailModal" class="modal-overlay" onclick="if(event.target === this) closeDetailModal()">
<div class="modal-box detail-modal-content">
<div class="modal-header">评论详情</div>
<div class="modal-body" id="detailContent"></div>
<div class="modal-footer">
<button class="modal-btn primary" onclick="closeDetailModal()">关闭</button>
</div>
</div>
</div>
<!-- 新增留言弹窗 -->
<div id="addCommentModal" class="modal-overlay" onclick="if(event.target === this) closeAddCommentModal()">
<div class="modal-box" style="width: 500px;">
<div class="modal-header">新增留言</div>
<div class="modal-body">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: var(--el-text-secondary); font-size: 14px;">昵称:</label>
<input type="text" id="addNick" placeholder="请输入昵称" style="width: 100%; height: 36px; padding: 0 12px; border: 1px solid var(--el-border-color); border-radius: 4px; outline: none; box-sizing: border-box;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: var(--el-text-secondary); font-size: 14px;">邮箱:</label>
<input type="email" id="addMail" placeholder="请输入邮箱(选填)" style="width: 100%; height: 36px; padding: 0 12px; border: 1px solid var(--el-border-color); border-radius: 4px; outline: none; box-sizing: border-box;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: var(--el-text-secondary); font-size: 14px;">网站:</label>
<input type="url" id="addLink" placeholder="请输入网站链接(选填)" style="width: 100%; height: 36px; padding: 0 12px; border: 1px solid var(--el-border-color); border-radius: 4px; outline: none; box-sizing: border-box;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: var(--el-text-secondary); font-size: 14px;">评论内容:</label>
<textarea id="addComment" placeholder="请输入评论内容" rows="5" style="width: 100%; padding: 10px 12px; border: 1px solid var(--el-border-color); border-radius: 4px; outline: none; resize: vertical; font-family: inherit; font-size: 14px; box-sizing: border-box;"></textarea>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: var(--el-text-secondary); font-size: 14px;">状态:</label>
<select id="addStatus" style="width: 100%; height: 36px; padding: 0 12px; border: 1px solid var(--el-border-color); border-radius: 4px; outline: none; background: #fff; box-sizing: border-box;">
<option value="approved">通过</option>
<option value="spam">垃圾</option>
</select>
</div>
</div>
<div class="modal-footer">
<button class="modal-btn" onclick="closeAddCommentModal()">取消</button>
<button class="modal-btn primary" onclick="submitNewComment()">提交</button>
</div>
</div>
</div>
<div class="filters">
<!-- 移动端状态筛选(仅在手机显示) -->
<div class="mobile-status-bar" style="display: none;">
<div class="mobile-status-item active" onclick="selectStatusMobile(this, '')">全部</div>
<div class="mobile-status-item" onclick="selectStatusMobile(this, 'approved')">通过</div>
<div class="mobile-status-item" onclick="selectStatusMobile(this, 'spam')">垃圾</div>
<div class="mobile-status-item" onclick="selectStatusMobile(this, 'deleted')">删除</div>
</div>
<input type="text" id="searchKeyword" placeholder="搜索昵称或评论内容...">
<button class="el-button el-button--primary" onclick="loadComments()">搜索</button>
<button class="el-button" id="addCommentBtn" onclick="showAddCommentModal()" style="margin-left: auto;">+ 新增留言</button>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>昵称</th>
<th>评论内容</th>
<th>浏览器/系统</th>
<th>时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="commentTableBody">
<tr><td colspan="6" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination" id="pagination">
<span class="page-info"><strong id="totalCount">0</strong></span>
<button class="page-btn" id="prevPage" onclick="changePage(-1)">&lt;</button>
<div id="pageNumbers" style="display: inline-flex; gap: 4px;"></div>
<button class="page-btn" id="nextPage" onclick="changePage(1)">&gt;</button>
</div>
</div>
</main>
<script>
let modalResolve = null;
let currentStatusFilter = '';
let currentPage = 1;
let totalPages = 0;
let totalCount = 0;
function selectStatus(element, status) {
// 更新左侧菜单高亮
document.querySelectorAll('.aside-menu-item').forEach(el => el.classList.remove('active'));
if (element.classList.contains('aside-menu-item')) {
element.classList.add('active');
}
currentStatusFilter = status;
// 在“删除”状态下隐藏新增按钮
const addBtn = document.getElementById('addCommentBtn');
if (addBtn) {
addBtn.style.display = status === 'deleted' ? 'none' : 'inline-block';
}
loadComments(); // 点击状态后自动搜索
}
function selectStatusMobile(element, status) {
document.querySelectorAll('.mobile-status-item').forEach(el => el.classList.remove('active'));
element.classList.add('active');
currentStatusFilter = status;
loadComments();
}
function showConfirm(message) {
return new Promise((resolve) => {
document.getElementById('modalTitle').textContent = message;
document.getElementById('confirmModal').style.display = 'flex';
modalResolve = resolve;
});
}
function closeModal(result) {
document.getElementById('confirmModal').style.display = 'none';
if (modalResolve) modalResolve(result);
}
function showDetail(comment) {
const date = new Date(comment.time).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
const content = `
<div class="detail-grid">
<div class="detail-label">ID</div>
<div class="detail-value">${comment.id}</div>
<div class="detail-label">昵称:</div>
<div class="detail-value">${comment.nick || '匿名'}</div>
<div class="detail-label">邮箱:</div>
<div class="detail-value">${comment.mail || '-'}</div>
<div class="detail-label">网站:</div>
<div class="detail-value">${comment.link ? `<a href="${comment.link}" target="_blank" style="color: var(--el-color-primary); text-decoration: none;">${comment.link}</a>` : '-'}</div>
<div class="detail-label">浏览器:</div>
<div class="detail-value">${comment.browser || '-'} / ${comment.os || '-'}</div>
<div class="detail-divider"></div>
<div class="detail-label">评论内容:</div>
<div class="detail-value comment-html">${comment.comment}</div>
<div class="detail-divider"></div>
<div class="detail-label">发布时间:</div>
<div class="detail-value">${date}</div>
<div class="detail-label">User Agent</div>
<div class="detail-value" style="font-size: 12px; color: var(--el-text-secondary); font-family: monospace;">${comment.ua || '-'}</div>
</div>
`;
document.getElementById('detailContent').innerHTML = content;
document.getElementById('detailModal').style.display = 'flex';
}
function closeDetailModal() {
document.getElementById('detailModal').style.display = 'none';
}
function showAddCommentModal() {
// 清空表单
document.getElementById('addNick').value = '';
document.getElementById('addMail').value = '';
document.getElementById('addLink').value = '';
document.getElementById('addComment').value = '';
document.getElementById('addStatus').value = 'approved';
document.getElementById('addCommentModal').style.display = 'flex';
}
function closeAddCommentModal() {
document.getElementById('addCommentModal').style.display = 'none';
}
function submitNewComment() {
const nick = document.getElementById('addNick').value.trim();
const mail = document.getElementById('addMail').value.trim();
const link = document.getElementById('addLink').value.trim();
const comment = document.getElementById('addComment').value.trim();
const status = document.getElementById('addStatus').value;
if (!comment) {
showConfirm('请输入评论内容').then(() => {});
return;
}
const data = {
nick: nick || '匿名',
mail: mail,
link: link,
comment: comment,
ua: navigator.userAgent,
status: status
};
fetch('/api/comment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(res => res.json())
.then(result => {
if (result.errno === 0) {
closeAddCommentModal();
loadComments();
} else {
showConfirm('添加失败: ' + result.errmsg).then(() => {});
}
})
.catch(err => {
showConfirm('网络错误,请重试').then(() => {});
});
}
function selectStatusMobile(element, status) {
document.querySelectorAll('.mobile-status-item').forEach(el => el.classList.remove('active'));
element.classList.add('active');
currentStatusFilter = status;
loadComments();
}
function changePage(delta) {
const newPage = currentPage + delta;
if (newPage >= 1 && newPage <= totalPages) {
loadComments(newPage);
}
}
function goToPage(page) {
if (page >= 1 && page <= totalPages && page !== currentPage) {
loadComments(page);
}
}
function renderPagination(total, page, pages) {
totalCount = total;
totalPages = pages;
document.getElementById('totalCount').textContent = total;
const paginationDiv = document.getElementById('pagination');
const prevBtn = document.getElementById('prevPage');
const nextBtn = document.getElementById('nextPage');
const pageNumbers = document.getElementById('pageNumbers');
// 更新上一页/下一页按钮状态
prevBtn.classList.toggle('disabled', page <= 1);
nextBtn.classList.toggle('disabled', page >= pages);
// 生成页码按钮
pageNumbers.innerHTML = '';
if (pages <= 1 && total === 0) {
// 如果没有数据,完全隐藏分页器
paginationDiv.style.display = 'none';
} else if (pages <= 1) {
// 如果只有一页但有数据,只显示总数,隐藏翻页按钮
prevBtn.style.display = 'none';
nextBtn.style.display = 'none';
pageNumbers.style.display = 'none';
paginationDiv.style.display = 'flex';
} else {
// 多页时显示完整分页器
prevBtn.style.display = 'inline-block';
nextBtn.style.display = 'inline-block';
pageNumbers.style.display = 'inline-flex';
paginationDiv.style.display = 'flex';
// 显示最多 5 个页码
let start = Math.max(1, page - 2);
let end = Math.min(pages, start + 4);
if (end - start < 4) {
start = Math.max(1, end - 4);
}
for (let i = start; i <= end; i++) {
const btn = document.createElement('button');
btn.className = `page-btn ${i === page ? 'active' : ''}`;
btn.textContent = i;
btn.onclick = () => goToPage(i);
pageNumbers.appendChild(btn);
}
}
}
function loadComments(page = 1) {
const keyword = document.getElementById('searchKeyword').value;
currentPage = page;
fetch(`/api/admin/comments?keyword=${encodeURIComponent(keyword)}&status=${currentStatusFilter}&page=${page}&pageSize=10`)
.then(res => res.json())
.then(result => {
if (result.errno === 0) {
renderTable(result.data.list);
renderPagination(result.data.total, result.data.page, result.data.totalPages);
} else {
showConfirm('加载失败: ' + result.errmsg).then(() => {});
}
})
.catch(err => console.error('Error:', err));
}
function renderTable(comments) {
const tbody = document.getElementById('commentTableBody');
if (!comments || comments.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-state">暂无数据</td></tr>';
return;
}
let html = '';
comments.forEach(c => {
const date = new Date(c.time).toLocaleString();
let statusClass = 'status-approved';
let statusText = '通过';
if (c.status === 'spam') { statusClass = 'status-spam'; statusText = '垃圾'; }
if (c.status === 'deleted') { statusClass = 'status-deleted'; statusText = '删除'; }
let actionButtons = '';
if (currentStatusFilter === 'deleted') {
actionButtons = `
<button onclick="updateStatus(${c.id}, 'approved')">通过</button>
<button onclick="showDetail(${JSON.stringify(c).replace(/"/g, '&quot;')})">详情</button>
`;
} else if (currentStatusFilter === 'spam') {
actionButtons = `
<button onclick="updateStatus(${c.id}, 'approved')">通过</button>
<button class="text-danger" onclick="updateStatus(${c.id}, 'deleted')">删除</button>
<button onclick="showDetail(${JSON.stringify(c).replace(/"/g, '&quot;')})">详情</button>
`;
} else {
actionButtons = `
<button onclick="updateStatus(${c.id}, 'approved')">通过</button>
<button class="text-danger" onclick="updateStatus(${c.id}, 'spam')">垃圾</button>
<button class="text-danger" onclick="updateStatus(${c.id}, 'deleted')">删除</button>
<button onclick="showDetail(${JSON.stringify(c).replace(/"/g, '&quot;')})">详情</button>
`;
}
html += `<tr>
<td>${c.nick || '匿名'}</td>
<td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${c.comment.replace(/<[^>]*>/g, '')}</td>
<td>${c.browser} / ${c.os}</td>
<td>${date}</td>
<td><span class="status-badge ${statusClass}">${statusText}</span></td>
<td class="actions">
${actionButtons}
</td>
</tr>`;
});
tbody.innerHTML = html;
}
async function updateStatus(id, status) {
const confirmed = await showConfirm(`确定要将此评论标记为 "${status}" 吗?`);
if (!confirmed) return;
fetch(`/api/admin/comment/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: status })
})
.then(res => res.json())
.then(result => {
if (result.errno === 0) {
loadComments(); // 重新加载列表
} else {
showConfirm('更新失败: ' + result.errmsg).then(() => {});
}
});
}
// 初始加载
loadComments();
</script>
</body>
</html>