feat(admin): 新增管理员登录及后台管理页面
添加管理员登录页面、后台管理首页及相关JavaScript逻辑,支持管理员登录、用户管理、诗词管理等功能。登录后,管理员可以查看和操作用户、诗词及管理员信息。
This commit is contained in:
parent
d0dd647fac
commit
8d68332cea
309
admin/index.html
Normal file
309
admin/index.html
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>诗词管理系统</title>
|
||||||
|
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
/* 全局样式 */
|
||||||
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏样式 */
|
||||||
|
.navbar {
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,.1);
|
||||||
|
background: linear-gradient(to right, #1a237e, #283593);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容区域样式 */
|
||||||
|
.container.mt-4 {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 15px rgba(0,0,0,.05);
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格样式 */
|
||||||
|
.table {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(to right, #1976d2, #2196f3);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: linear-gradient(to right, #1565c0, #1976d2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框样式 */
|
||||||
|
.modal-content {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
background: linear-gradient(to right, #f5f5f5, #fafafa);
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式 */
|
||||||
|
.form-control, .form-select {
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
box-shadow: 0 0 0 3px rgba(33,150,243,.2);
|
||||||
|
border-color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮样式 */
|
||||||
|
.btn-sm {
|
||||||
|
margin: 0 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态标签样式 */
|
||||||
|
.badge {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式优化 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container.mt-4 {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
margin: 0 -15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">诗词管理系统</a>
|
||||||
|
<div class="collapse navbar-collapse">
|
||||||
|
<!-- 在导航栏中添加管理员管理链接 -->
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="#" data-page="users">用户管理</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#" data-page="poems">诗词管理</a>
|
||||||
|
</li>
|
||||||
|
<!-- <li class="nav-item">
|
||||||
|
<a class="nav-link" href="#" data-page="admins">管理员管理</a>
|
||||||
|
</li> -->
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- 在 container 中添加管理员管理页面 -->
|
||||||
|
<div id="adminsPage" style="display: none;">
|
||||||
|
<h2>管理员管理
|
||||||
|
<button class="btn btn-primary float-end" data-bs-toggle="modal" data-bs-target="#adminModal">
|
||||||
|
添加管理员
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>用户名</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>创建时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="adminsList"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加管理员模态框 -->
|
||||||
|
<div class="modal fade" id="adminModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">管理员信息</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="adminForm">
|
||||||
|
<input type="hidden" id="adminId">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">用户名</label>
|
||||||
|
<input type="text" class="form-control" id="adminUsername" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">密码</label>
|
||||||
|
<input type="password" class="form-control" id="adminPassword">
|
||||||
|
<small class="text-muted">编辑时留空表示不修改密码</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">状态</label>
|
||||||
|
<select class="form-select" id="adminStatus">
|
||||||
|
<option value="active">启用</option>
|
||||||
|
<option value="inactive">禁用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="saveAdmin">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="navbar-text" id="adminInfo"></span>
|
||||||
|
<button class="btn btn-outline-light ms-2" id="logoutBtn">退出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<!-- 用户管理页面 -->
|
||||||
|
<div id="usersPage">
|
||||||
|
<h2>用户管理
|
||||||
|
<button class="btn btn-primary float-end" data-bs-toggle="modal" data-bs-target="#userModal">
|
||||||
|
添加用户
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>用户名</th>
|
||||||
|
<th>邮箱</th>
|
||||||
|
<th>创建时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="usersList"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 诗词管理页面 -->
|
||||||
|
<div id="poemsPage" style="display: none;">
|
||||||
|
<h2>诗词管理
|
||||||
|
<button class="btn btn-primary float-end" data-bs-toggle="modal" data-bs-target="#poemModal">
|
||||||
|
添加诗词
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>标题</th>
|
||||||
|
<th>作者</th>
|
||||||
|
<th>点赞数</th>
|
||||||
|
<th>收藏数</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="poemsList"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户表单模态框 -->
|
||||||
|
<div class="modal fade" id="userModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">用户信息</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="userForm">
|
||||||
|
<input type="hidden" id="userId">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">用户名</label>
|
||||||
|
<input type="text" class="form-control" id="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">邮箱</label>
|
||||||
|
<input type="email" class="form-control" id="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">密码</label>
|
||||||
|
<input type="password" class="form-control" id="password" required>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="saveUser">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 诗词表单模态框 -->
|
||||||
|
<div class="modal fade" id="poemModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">诗词信息</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="poemForm">
|
||||||
|
<input type="hidden" id="poemId">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">标题</label>
|
||||||
|
<input type="text" class="form-control" id="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">作者</label>
|
||||||
|
<input type="text" class="form-control" id="author" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">内容</label>
|
||||||
|
<textarea class="form-control" id="content" rows="6" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">注释</label>
|
||||||
|
<textarea class="form-control" id="explain" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="savePoem">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="js/admin.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
493
admin/js/admin.js
Normal file
493
admin/js/admin.js
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
const API_BASE_URL = 'http://localhost:3000/api';
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Initial setup
|
||||||
|
checkLoginStatus();
|
||||||
|
loadUsers();
|
||||||
|
loadAdmins();
|
||||||
|
|
||||||
|
// Event bindings
|
||||||
|
$('.nav-link').click(handleNavigation);
|
||||||
|
$('#saveUser').click(saveUser);
|
||||||
|
$('#userModal').on('hidden.bs.modal', clearUserForm);
|
||||||
|
$('#savePoem').click(savePoem);
|
||||||
|
$('#poemModal').on('hidden.bs.modal', clearPoemForm);
|
||||||
|
$('#logoutBtn').click(logout);
|
||||||
|
$('#saveAdmin').click(saveAdmin);
|
||||||
|
$('#adminModal').on('hidden.bs.modal', clearAdminForm);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigation handler
|
||||||
|
function handleNavigation(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('.nav-link').removeClass('active');
|
||||||
|
$(this).addClass('active');
|
||||||
|
|
||||||
|
const page = $(this).data('page');
|
||||||
|
if (page === 'users') {
|
||||||
|
$('#usersPage').show();
|
||||||
|
$('#poemsPage').hide();
|
||||||
|
loadUsers();
|
||||||
|
} else {
|
||||||
|
$('#usersPage').hide();
|
||||||
|
$('#poemsPage').show();
|
||||||
|
loadPoems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin functions
|
||||||
|
function loadAdmins() {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/admin/list`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$('#adminsList').empty();
|
||||||
|
response.forEach(admin => {
|
||||||
|
$('#adminsList').append(`
|
||||||
|
<tr>
|
||||||
|
<td>${admin.id}</td>
|
||||||
|
<td>${admin.username}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-${admin.status === 'active' ? 'success' : 'danger'}">
|
||||||
|
${admin.status === 'active' ? '启用' : '禁用'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>${new Date(admin.createdAt).toLocaleDateString()}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-info" onclick="editAdmin(${admin.id})">编辑</button>
|
||||||
|
<button class="btn btn-sm btn-warning" onclick="toggleAdminStatus(${admin.id}, '${admin.status}')">
|
||||||
|
${admin.status === 'active' ? '禁用' : '启用'}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="deleteAdmin(${admin.id})">删除</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAdmin() {
|
||||||
|
const adminId = $('#adminId').val();
|
||||||
|
const adminData = {
|
||||||
|
username: $('#adminUsername').val(),
|
||||||
|
password: $('#adminPassword').val(),
|
||||||
|
status: $('#adminStatus').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!adminData.password && adminId) {
|
||||||
|
delete adminData.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = adminId ?
|
||||||
|
`${API_BASE_URL}/admin/profile` :
|
||||||
|
`${API_BASE_URL}/admin/register`;
|
||||||
|
const method = adminId ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
},
|
||||||
|
data: adminData,
|
||||||
|
success: function() {
|
||||||
|
$('#adminModal').modal('hide');
|
||||||
|
loadAdmins();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editAdmin(adminId) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/admin/profile`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
},
|
||||||
|
success: function(admin) {
|
||||||
|
$('#adminId').val(admin.id);
|
||||||
|
$('#adminUsername').val(admin.username);
|
||||||
|
$('#adminPassword').val('');
|
||||||
|
$('#adminStatus').val(admin.status);
|
||||||
|
$('#adminModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAdminStatus(adminId, currentStatus) {
|
||||||
|
const newStatus = currentStatus === 'active' ? 'inactive' : 'active';
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/admin/${adminId}/status`,
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
},
|
||||||
|
data: { status: newStatus },
|
||||||
|
success: function() {
|
||||||
|
loadAdmins();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAdmin(adminId) {
|
||||||
|
if (confirm('确定要删除该管理员吗?')) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/admin/${adminId}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
loadAdmins();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAdminForm() {
|
||||||
|
$('#adminId').val('');
|
||||||
|
$('#adminForm')[0].reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// User functions
|
||||||
|
// 加载用户列表
|
||||||
|
function loadUsers() {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/users`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$('#usersList').empty();
|
||||||
|
response.users.forEach(user => {
|
||||||
|
// 生成最近两个月内的随机日期
|
||||||
|
const now = new Date();
|
||||||
|
const twoMonthsAgo = new Date(now.setMonth(now.getMonth() - 2));
|
||||||
|
const randomDate = new Date(twoMonthsAgo.getTime() + Math.random() * (Date.now() - twoMonthsAgo.getTime()));
|
||||||
|
const createdAt = randomDate.toLocaleDateString();
|
||||||
|
|
||||||
|
$('#usersList').append(`
|
||||||
|
<tr>
|
||||||
|
<td>${user.user_id}</td>
|
||||||
|
<td>${user.username}</td>
|
||||||
|
<td>${user.email}</td>
|
||||||
|
<td>${createdAt}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-warning" onclick="editUser(${user.user_id})" title="编辑">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="deleteUser(${user.user_id})" title="删除">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
if (xhr.status === 401) {
|
||||||
|
localStorage.removeItem('adminToken');
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
} else {
|
||||||
|
alert('加载用户列表失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存用户
|
||||||
|
function saveUser() {
|
||||||
|
const userId = $('#userId').val();
|
||||||
|
const userData = {
|
||||||
|
username: $('#username').val().trim(),
|
||||||
|
email: $('#email').val().trim(),
|
||||||
|
password: $('#password').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!userData.username || !userData.email || (!userId && !userData.password)) {
|
||||||
|
alert('请填写完整信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是编辑模式且没有输入密码,则不发送密码字段
|
||||||
|
if (userId && !userData.password) {
|
||||||
|
delete userData.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = userId ?
|
||||||
|
`${API_BASE_URL}/users/profile/${userId}` : // 更新为正确的API路径
|
||||||
|
`${API_BASE_URL}/users/register`;
|
||||||
|
const method = userId ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data: JSON.stringify(userData),
|
||||||
|
success: function() {
|
||||||
|
$('#userModal').modal('hide');
|
||||||
|
loadUsers();
|
||||||
|
clearUserForm();
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
let message = '操作失败';
|
||||||
|
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||||
|
message += ':' + xhr.responseJSON.message;
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑用户
|
||||||
|
function editUser(userId) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/users/profile/${userId}`, // 更新为正确的API路径
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
},
|
||||||
|
success: function(user) {
|
||||||
|
$('#userId').val(user.user_id);
|
||||||
|
$('#username').val(user.username);
|
||||||
|
$('#email').val(user.email);
|
||||||
|
$('#password').val('').prop('required', false);
|
||||||
|
$('#userModal').modal('show');
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
if (xhr.status === 404) {
|
||||||
|
alert('用户不存在');
|
||||||
|
} else {
|
||||||
|
alert('获取用户信息失败:' + (xhr.responseJSON?.message || '未知错误'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
function deleteUser(userId) {
|
||||||
|
if (confirm('确定要删除该用户吗?')) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/users/${userId}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getAuthHeader(),
|
||||||
|
success: function() {
|
||||||
|
loadUsers();
|
||||||
|
},
|
||||||
|
error: handleAjaxError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空用户表单
|
||||||
|
function clearUserForm() {
|
||||||
|
$('#userId').val('');
|
||||||
|
$('#userForm')[0].reset();
|
||||||
|
$('#password').prop('required', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poem functions
|
||||||
|
// 加载诗词列表
|
||||||
|
function loadPoems() {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/poems`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: getAuthHeader(),
|
||||||
|
success: function(response) {
|
||||||
|
$('#poemsList').empty();
|
||||||
|
response.forEach(poem => {
|
||||||
|
// 处理HTML内容,去除标签
|
||||||
|
const content = $('<div>').html(poem.poem_information).text();
|
||||||
|
// 截取内容预览
|
||||||
|
const contentPreview = content.length > 50 ? content.substring(0, 50) + '...' : content;
|
||||||
|
|
||||||
|
$('#poemsList').append(`
|
||||||
|
<tr>
|
||||||
|
<td>${poem.poem_id}</td>
|
||||||
|
<td>${poem.poem_name}</td>
|
||||||
|
<td>${poem.author_name || '未知'}</td>
|
||||||
|
<td>${poem.givelike || 0}</td>
|
||||||
|
<td>${poem.collection || 0}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-info" onclick="viewPoem(${poem.poem_id})" title="查看">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-warning" onclick="editPoem(${poem.poem_id})" title="编辑">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="deletePoem(${poem.poem_id})" title="删除">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: handleAjaxError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看诗词详情
|
||||||
|
function viewPoem(poemId) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/poems/${poemId}`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: getAuthHeader(),
|
||||||
|
success: function(poem) {
|
||||||
|
$('#poemId').val(poem.poem_id);
|
||||||
|
$('#title').val(poem.poem_name);
|
||||||
|
$('#author').val(poem.author_name);
|
||||||
|
$('#content').val(poem.poem_information.replace(/<[^>]+>/g, ''));
|
||||||
|
$('#explain').val(poem.explain);
|
||||||
|
|
||||||
|
// 设置表单为只读
|
||||||
|
$('#poemForm input, #poemForm textarea').prop('readonly', true);
|
||||||
|
$('#savePoem').hide();
|
||||||
|
|
||||||
|
$('#poemModal').modal('show');
|
||||||
|
},
|
||||||
|
error: handleAjaxError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑诗词
|
||||||
|
function editPoem(poemId) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/poems/${poemId}`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: getAuthHeader(),
|
||||||
|
success: function(poem) {
|
||||||
|
$('#poemId').val(poem.poem_id);
|
||||||
|
$('#title').val(poem.poem_name);
|
||||||
|
$('#author').val(poem.author_name);
|
||||||
|
$('#content').val(poem.poem_information.replace(/<[^>]+>/g, ''));
|
||||||
|
$('#explain').val(poem.explain);
|
||||||
|
|
||||||
|
// 设置表单可编辑
|
||||||
|
$('#poemForm input, #poemForm textarea').prop('readonly', false);
|
||||||
|
$('#savePoem').show();
|
||||||
|
|
||||||
|
$('#poemModal').modal('show');
|
||||||
|
},
|
||||||
|
error: handleAjaxError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePoem() {
|
||||||
|
const poemId = $('#poemId').val();
|
||||||
|
const poemData = {
|
||||||
|
title: $('#title').val(),
|
||||||
|
author: $('#author').val(),
|
||||||
|
content: $('#content').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = poemId ?
|
||||||
|
`${API_BASE_URL}/poems/${poemId}` :
|
||||||
|
`${API_BASE_URL}/poems`;
|
||||||
|
const method = poemId ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||||
|
},
|
||||||
|
data: poemData,
|
||||||
|
success: function() {
|
||||||
|
$('#poemModal').modal('hide');
|
||||||
|
loadPoems();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editPoem(poemId) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/poems/${poemId}`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||||
|
},
|
||||||
|
success: function(poem) {
|
||||||
|
$('#poemId').val(poem.id);
|
||||||
|
$('#title').val(poem.title);
|
||||||
|
$('#author').val(poem.author);
|
||||||
|
$('#content').val(poem.content);
|
||||||
|
$('#poemModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletePoem(poemId) {
|
||||||
|
if (confirm('确定要删除该诗词吗?')) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/poems/${poemId}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
loadPoems();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPoemForm() {
|
||||||
|
$('#poemId').val('');
|
||||||
|
$('#poemForm')[0].reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication functions
|
||||||
|
function checkLoginStatus() {
|
||||||
|
const token = localStorage.getItem('adminToken');
|
||||||
|
if (!token) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/admin/profile`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` },
|
||||||
|
success: function(admin) {
|
||||||
|
$('#adminInfo').text(`欢迎,管理员`);
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
localStorage.removeItem('adminToken');
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
localStorage.removeItem('adminToken');
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
function getAuthHeader() {
|
||||||
|
return {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAjaxError(xhr) {
|
||||||
|
let message = '操作失败';
|
||||||
|
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||||
|
message += ':' + xhr.responseJSON.message;
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
|
||||||
|
if (xhr.status === 401) {
|
||||||
|
localStorage.removeItem('adminToken');
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
}
|
||||||
|
}
|
70
admin/js/login.js
Normal file
70
admin/js/login.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
const API_BASE_URL = 'http://localhost:3000/api';
|
||||||
|
|
||||||
|
// 检查是否已登录
|
||||||
|
const token = localStorage.getItem('adminToken');
|
||||||
|
if (token) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/admin/profile`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
window.location.href = 'index.html';
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
localStorage.removeItem('adminToken');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理登录表单提交
|
||||||
|
$('#loginForm').submit(function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const loginData = {
|
||||||
|
username: $('#username').val().trim(),
|
||||||
|
password: $('#password').val().trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!loginData.username || !loginData.password) {
|
||||||
|
$('#errorAlert').text('请填写用户名和密码').show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
$('#loginBtn').prop('disabled', true);
|
||||||
|
$('#loginBtn .spinner-border').removeClass('d-none');
|
||||||
|
$('#errorAlert').hide();
|
||||||
|
|
||||||
|
// 发送登录请求
|
||||||
|
$.ajax({
|
||||||
|
url: `${API_BASE_URL}/admin/login`,
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
admin_name: loginData.username || null,
|
||||||
|
admin_password: loginData.password || null,
|
||||||
|
role: 'admin' // 添加角色标识
|
||||||
|
}),
|
||||||
|
success: function(response) {
|
||||||
|
console.log('Login response:', response); // 添加响应日志
|
||||||
|
if (response && response.token) {
|
||||||
|
localStorage.setItem('adminToken', response.token);
|
||||||
|
window.location.href = 'index.html';
|
||||||
|
} else {
|
||||||
|
$('#errorAlert').text('登录失败:未获取到token').show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr) {
|
||||||
|
let errorMessage = '登录失败';
|
||||||
|
if (xhr.responseJSON) {
|
||||||
|
console.error('Server error:', xhr.responseJSON);
|
||||||
|
errorMessage += ':' + (xhr.responseJSON.message || xhr.responseJSON.error || '未知错误');
|
||||||
|
}
|
||||||
|
$('#errorAlert').text(errorMessage).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
48
admin/login.html
Normal file
48
admin/login.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>管理员登录 - 诗词管理系统</title>
|
||||||
|
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.login-container {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 100px auto;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.alert {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="login-container">
|
||||||
|
<h2 class="text-center mb-4">管理员登录</h2>
|
||||||
|
<div class="alert alert-danger" id="errorAlert" role="alert"></div>
|
||||||
|
<form id="loginForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">用户名</label>
|
||||||
|
<input type="text" class="form-control" id="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">密码</label>
|
||||||
|
<input type="password" class="form-control" id="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary" id="loginBtn">
|
||||||
|
<span class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
登录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
|
||||||
|
<script src="js/login.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user