Files
2026-06-16 03:30:57 +08:00

726 lines
34 KiB
HTML
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.
<!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>