08-12-周三_16-44-56

This commit is contained in:
AaronXu
2026-08-12 16:44:57 +08:00
parent 56dd5b5adf
commit 460e9c7393
12 changed files with 1047 additions and 304 deletions
@@ -1,47 +0,0 @@
{
"models": [
{
"id": "kimi-for-coding",
"name": "kimi-for-coding",
"vendor": "user",
"url": "https://api.kimi.com/coding/v1",
"apiKey": "sk-kimi-OLoO20UJ8D3hPtywZqitU6zQO6edvWXhrRcWynoQFWBsmf38slzCG3ZbhvhLZOeS",
"supportsToolCall": true,
"supportsImages": true,
"supportsReasoning": false,
"temperature": 1
},
{
"id": "kimicode",
"name": "kimicode",
"vendor": "user",
"url": "https://api.kimi.com/coding/v1",
"apiKey": "sk-kimi-1sl2dMlwHcDAM5epuy4EHXhlehH38tcZxzqyOXAK7Og81HofBAcmYnW86KOd9Hdr",
"supportsToolCall": true,
"supportsImages": true,
"supportsReasoning": true,
"temperature": 1
},
{
"id": "mimo-v2.5-pro",
"name": "mimo-v2.5-pro",
"vendor": "user",
"url": "https://token-plan-cn.xiaomimimo.com/v1",
"apiKey": "tpqqgst8uxezjxw070kyv57vo4ayjm97go9jqk7fh",
"supportsToolCall": true,
"supportsImages": true,
"supportsReasoning": false
},
{
"id": "deepseek-v4-pro",
"name": "DeepSeek-V4 Pro",
"vendor": "user",
"url": "https://api.deepseek.com/chat/completions",
"apiKey": "sk-4456a067934f4a2a9582d0f579c7b231",
"supportsToolCall": true,
"supportsImages": false,
"supportsReasoning": true
}
]
}
@@ -1,249 +0,0 @@
<!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 rel="stylesheet" href="css/style.css">
</head>
<body>
<nav class="navbar">
<a class="navbar-brand" href="index.html"><span class="logo-icon">📝</span> 笔记小站</a>
<div class="navbar-actions" id="navbarActions"></div>
</nav>
<div class="container" id="app">
<div class="admin-header">
<h1>⚙️ 后台管理</h1>
</div>
<div class="admin-tabs">
<button class="admin-tab" data-tab="categories">📂 分类管理</button>
<button class="admin-tab" data-tab="notes">📝 笔记管理</button>
<button class="admin-tab" data-tab="comments">💬 评论管理</button>
<button class="admin-tab" data-tab="users">👥 用户管理</button>
</div>
<div id="tabContent"></div>
</div>
<div class="toast-container" id="toastContainer"></div>
<script src="js/data.js"></script>
<script src="js/common.js"></script>
<script>
(function () {
var activeTab = getUrlParam('tab') || 'categories';
var tabs = document.querySelectorAll('.admin-tab');
tabs.forEach(function (t) {
if (t.dataset.tab === activeTab) t.classList.add('active');
t.addEventListener('click', function () {
history.replaceState(null, '', '?tab=' + this.dataset.tab);
activeTab = this.dataset.tab;
tabs.forEach(function (x) { x.classList.remove('active'); });
this.classList.add('active');
renderTab();
});
});
renderTab();
function renderTab() {
var content = document.getElementById('tabContent');
if (activeTab === 'categories') content.innerHTML = renderCategories();
else if (activeTab === 'notes') content.innerHTML = renderNotes();
else if (activeTab === 'comments') content.innerHTML = renderComments();
else if (activeTab === 'users') content.innerHTML = renderUsers();
}
/* ============ Categories ============ */
function renderCategories() {
var h = '<div class="section-header">';
h += '<h2>分类列表</h2>';
h += '<button class="btn btn-primary" onclick="openCategoryModal()">+ 新增分类</button>';
h += '</div>';
h += '<div class="data-table">';
h += '<table><thead><tr><th>ID</th><th>图标</th><th>名称</th><th>颜色</th><th>操作</th></tr></thead><tbody>';
DATA.categories.forEach(function (c) {
h += '<tr><td>' + c.id + '</td><td>' + c.icon + '</td><td>' + c.name + '</td>';
h += '<td><span class="color-dot" style="background:' + c.color + '"></span> ' + c.color + '</td>';
h += '<td><button class="btn btn-sm" onclick="openCategoryModal(' + c.id + ')">编辑</button> ';
h += '<button class="btn btn-sm btn-danger" onclick="deleteCategory(' + c.id + ')">删除</button></td></tr>';
});
h += '</tbody></table></div>';
return h;
}
window.openCategoryModal = function (id) {
var cat = id ? findById(DATA.categories, id) : null;
var title = cat ? '编辑分类' : '新增分类';
var h = '<div class="form-group"><label>名称</label><input type="text" id="fname" value="' + (cat ? cat.name : '') + '"></div>';
h += '<div class="form-group"><label>图标</label><input type="text" id="ficon" value="' + (cat ? cat.icon : '📁') + '"></div>';
h += '<div class="form-group"><label>颜色</label><input type="color" id="fcolor" value="' + (cat ? cat.color : '#6366f1') + '"></div>';
h += '<div class="form-actions"><button class="btn" onclick="closeModal()">取消</button>';
h += '<button class="btn btn-primary" onclick="saveCategory(' + (cat ? cat.id : 0) + ')">保存</button></div>';
renderModal(title, h);
};
window.saveCategory = function (id) {
var name = document.getElementById('fname').value.trim();
var icon = document.getElementById('ficon').value.trim();
var color = document.getElementById('fcolor').value.trim();
if (!name) { showToast('请输入名称', 'error'); return; }
if (id) {
var c = findById(DATA.categories, id);
if (c) { c.name = name; c.icon = icon; c.color = color; }
} else {
DATA.categories.push({ id: DATA.categories.length + 1, name: name, icon: icon, color: color });
}
closeModal(); renderTab(); showToast('保存成功', 'success');
};
window.deleteCategory = function (id) {
DATA.categories = DATA.categories.filter(function (c) { return c.id !== id; });
renderTab(); showToast('已删除', 'info');
};
/* ============ Notes ============ */
function renderNotes() {
var h = '<div class="section-header">';
h += '<h2>笔记列表</h2>';
h += '<button class="btn btn-primary" onclick="openNoteModal()">+ 新增笔记</button>';
h += '</div><div class="data-table"><table><thead><tr><th>ID</th><th>标题</th><th>分类</th><th>作者</th><th>时间</th><th>操作</th></tr></thead><tbody>';
DATA.notes.forEach(function (n) {
var cat = findById(DATA.categories, n.categoryId);
var author = findById(DATA.users, n.userId);
h += '<tr><td>' + n.id + '</td><td>' + n.title + '</td>';
h += '<td>' + (cat ? cat.icon + ' ' + cat.name : '-') + '</td>';
h += '<td>' + (author ? author.nickname : '-') + '</td><td>' + n.createdAt + '</td>';
h += '<td><button class="btn btn-sm" onclick="openNoteModal(' + n.id + ')">编辑</button> ';
h += '<button class="btn btn-sm btn-danger" onclick="deleteNote(' + n.id + ')">删除</button></td></tr>';
});
h += '</tbody></table></div>';
return h;
}
window.openNoteModal = function (id) {
var note = id ? findById(DATA.notes, id) : null;
var title = note ? '编辑笔记' : '新增笔记';
var h = '<div class="form-group"><label>标题</label><input type="text" id="ftitle" value="' + (note ? note.title : '') + '"></div>';
h += '<div class="form-group"><label>分类</label><select id="fcat">';
DATA.categories.forEach(function (c) {
h += '<option value="' + c.id + '"' + (note && note.categoryId === c.id ? ' selected' : '') + '>' + c.icon + ' ' + c.name + '</option>';
});
h += '</select></div>';
h += '<div class="form-group"><label>内容</label><textarea id="fcontent" rows="6">' + (note ? note.content : '') + '</textarea></div>';
h += '<div class="form-actions"><button class="btn" onclick="closeModal()">取消</button>';
h += '<button class="btn btn-primary" onclick="saveNote(' + (note ? note.id : 0) + ')">保存</button></div>';
renderModal(title, h);
};
window.saveNote = function (id) {
var title = document.getElementById('ftitle').value.trim();
var catId = parseInt(document.getElementById('fcat').value);
var content = document.getElementById('fcontent').value.trim();
if (!title || !content) { showToast('请填写标题和内容', 'error'); return; }
if (id) {
var n = findById(DATA.notes, id);
if (n) { n.title = title; n.categoryId = catId; n.content = content; }
} else {
DATA.notes.push({
id: DATA.notes.length + 1,
title: title,
categoryId: catId,
content: content,
userId: 1,
createdAt: new Date().toISOString().slice(0, 10),
summary: content.slice(0, 100) + (content.length > 100 ? '...' : '')
});
}
closeModal(); renderTab(); showToast('保存成功', 'success');
};
window.deleteNote = function (id) {
DATA.notes = DATA.notes.filter(function (n) { return n.id !== id; });
DATA.comments = DATA.comments.filter(function (c) { return c.noteId !== id; });
renderTab(); showToast('已删除', 'info');
};
/* ============ Comments ============ */
function renderComments() {
var h = '<div class="section-header"><h2>评论列表</h2></div><div class="data-table"><table><thead><tr><th>ID</th><th>笔记</th><th>用户</th><th>内容</th><th>时间</th><th>操作</th></tr></thead><tbody>';
DATA.comments.forEach(function (c) {
var note = findById(DATA.notes, c.noteId);
var user = findById(DATA.users, c.userId);
h += '<tr><td>' + c.id + '</td><td>' + (note ? note.title : '已删除') + '</td>';
h += '<td>' + (user ? user.nickname : (c.author || '游客')) + '</td><td>' + c.content + '</td><td>' + c.createdAt + '</td>';
h += '<td><button class="btn btn-sm btn-danger" onclick="deleteComment(' + c.id + ')">删除</button></td></tr>';
});
h += '</tbody></table></div>';
return h;
}
window.deleteComment = function (id) {
DATA.comments = DATA.comments.filter(function (c) { return c.id !== id; });
renderTab(); showToast('已删除', 'info');
};
/* ============ Users ============ */
function renderUsers() {
var h = '<div class="section-header">';
h += '<h2>用户列表</h2>';
h += '<button class="btn btn-primary" onclick="openUserModal()">+ 新增用户</button>';
h += '</div><div class="data-table"><table><thead><tr><th>ID</th><th>头像</th><th>用户名</th><th>昵称</th><th>角色</th><th>时间</th><th>操作</th></tr></thead><tbody>';
DATA.users.forEach(function (u) {
h += '<tr><td>' + u.id + '</td><td>' + u.avatar + '</td><td>' + u.username + '</td><td>' + u.nickname + '</td>';
h += '<td><span class="badge badge-' + u.role + '">' + (u.role === 'admin' ? '管理员' : '普通用户') + '</span></td>';
h += '<td>' + u.createdAt + '</td>';
h += '<td><button class="btn btn-sm" onclick="openUserModal(' + u.id + ')">编辑</button> ';
h += '<button class="btn btn-sm btn-danger" onclick="deleteUser(' + u.id + ')">删除</button></td></tr>';
});
h += '</tbody></table></div>';
return h;
}
window.openUserModal = function (id) {
var u = id ? findById(DATA.users, id) : null;
var title = u ? '编辑用户' : '新增用户';
var h = '<div class="form-group"><label>用户名</label><input type="text" id="fname" value="' + (u ? u.username : '') + '"></div>';
h += '<div class="form-group"><label>昵称</label><input type="text" id="fnick" value="' + (u ? u.nickname : '') + '"></div>';
h += '<div class="form-group"><label>角色</label><select id="frole">';
h += '<option value="user"' + (u && u.role === 'user' ? ' selected' : '') + '>普通用户</option>';
h += '<option value="admin"' + (u && u.role === 'admin' ? ' selected' : '') + '>管理员</option>';
h += '</select></div>';
h += '<div class="form-actions"><button class="btn" onclick="closeModal()">取消</button>';
h += '<button class="btn btn-primary" onclick="saveUser(' + (u ? u.id : 0) + ')">保存</button></div>';
renderModal(title, h);
};
window.saveUser = function (id) {
var username = document.getElementById('fname').value.trim();
var nickname = document.getElementById('fnick').value.trim();
var role = document.getElementById('frole').value;
if (!username || !nickname) { showToast('请填写用户名和昵称', 'error'); return; }
if (id) {
var u = findById(DATA.users, id);
if (u) { u.username = username; u.nickname = nickname; u.role = role; }
} else {
DATA.users.push({
id: DATA.users.length + 1,
username: username, password: '123456',
nickname: nickname, avatar: '😊', role: role,
createdAt: new Date().toISOString().slice(0, 10)
});
}
closeModal(); renderTab(); showToast('保存成功', 'success');
};
window.deleteUser = function (id) {
DATA.users = DATA.users.filter(function (u) { return u.id !== id; });
renderTab(); showToast('已删除', 'info');
};
/* Helper */
function findById(arr, id) {
for (var i = 0; i < arr.length; i++) { if (arr[i].id === id) return arr[i]; }
return null;
}
})();
</script>
</body>
</html>
@@ -0,0 +1,217 @@
<!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 rel="stylesheet" href="css/style.css">
</head>
<body>
<nav class="navbar">
<a class="navbar-brand" href="index.html"><span class="logo-icon">📝</span> 笔记小站</a>
<div class="navbar-actions" id="navbarActions"></div>
</nav>
<div class="admin-layout">
<aside class="admin-sidebar">
<nav class="admin-nav">
<a class="admin-nav-item active" href="admin_categories.php">
<span>📂</span> 分类管理
</a>
<a class="admin-nav-item" href="admin_notes.php">
<span>📝</span> 笔记管理
</a>
<a class="admin-nav-item" href="admin_comments.php">
<span>💬</span> 评论管理
</a>
<a class="admin-nav-item" href="admin_users.php">
<span>👥</span> 用户管理
</a>
</nav>
</aside>
<main class="admin-main">
<div class="admin-panel">
<div class="admin-panel-header">
<h3>📂 分类管理</h3>
<button class="btn btn-primary btn-sm" onclick="openCategoryModal(0)">+ 新增分类</button>
</div>
<div class="admin-panel-body">
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>图标</th>
<th>名称</th>
<th>颜色</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!--
========================================
后端循环输出 <tr>,示例:
<tr>
<td>1</td>
<td>📁</td>
<td>技术笔记</td>
<td><span class="color-dot" style="background:#6366f1"></span>#6366f1</td>
<td class="actions">
<button class="btn btn-sm" onclick="openCategoryModal(1)">编辑</button>
<button class="btn btn-sm btn-danger" onclick="deleteCategory(1)">删除</button>
</td>
</tr>
========================================
-->
</tbody>
</table>
</div>
</div>
</main>
</div>
<!-- ==================== 新增 / 编辑分类 模态框 ==================== -->
<div class="modal-overlay" id="catModal" style="display:none;">
<div class="form-modal" onclick="event.stopPropagation()">
<h3 id="catModalTitle">新增分类</h3>
<input type="hidden" id="fcid" value="0">
<div class="form-group">
<label>名称</label>
<input type="text" id="fcname" placeholder="请输入分类名称">
</div>
<div class="form-group">
<label>图标</label>
<input type="text" id="fcicon" placeholder="📁">
</div>
<div class="form-group">
<label>颜色</label>
<input type="color" id="fccolor" value="#6366f1">
</div>
<div class="form-actions">
<button class="btn" onclick="closeCatModal()">取消</button>
<button class="btn btn-primary" onclick="saveCategory()">保存</button>
</div>
</div>
</div>
<!-- ==================== 删除确认模态框 ==================== -->
<div class="modal-overlay" id="deleteModal" style="display:none;">
<div class="form-modal" onclick="event.stopPropagation()">
<h3>⚠️ 确认删除</h3>
<input type="hidden" id="deleteId" value="">
<p style="margin-bottom:16px;">确定要删除该分类吗?此操作不可恢复。</p>
<div class="form-actions">
<button class="btn" onclick="closeDeleteModal()">取消</button>
<button class="btn btn-danger" onclick="confirmDeleteCategory()">确认删除</button>
</div>
</div>
</div>
<div class="toast-container" id="toastContainer"></div>
<script src="js/common.js"></script>
<script>
/* ==================== 分类模态框 ==================== */
/**
* 打开新增 / 编辑分类弹窗
* @param {number} id - 分类 ID0 = 新增,>0 = 编辑
*/
function openCategoryModal(id) {
var titleEl = document.getElementById('catModalTitle');
var cidEl = document.getElementById('fcid');
var nameEl = document.getElementById('fcname');
var iconEl = document.getElementById('fcicon');
var colorEl = document.getElementById('fccolor');
if (id === 0) {
titleEl.textContent = '新增分类';
cidEl.value = '0';
nameEl.value = '';
iconEl.value = '📁';
colorEl.value = '#6366f1';
} else {
titleEl.textContent = '编辑分类';
cidEl.value = id;
// TODO: 开发者自行拉取分类数据并填入
// nameEl.value = data.name;
// iconEl.value = data.icon;
// colorEl.value = data.color;
}
document.getElementById('catModal').style.display = 'flex';
}
/** 关闭分类模态框 */
function closeCatModal() {
document.getElementById('catModal').style.display = 'none';
document.getElementById('fcid').value = '0';
}
/** 保存分类 */
function saveCategory() {
var id = document.getElementById('fcid').value;
var name = document.getElementById('fcname').value.trim();
var icon = document.getElementById('fcicon').value.trim();
var color = document.getElementById('fccolor').value.trim();
if (!name) { showToast('请输入分类名称', 'error'); return; }
// TODO: 发送请求到后端保存
// fetch('/function/admin.php', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({
// action: id === '0' ? 'addCategory' : 'editCategory',
// id: id === '0' ? null : parseInt(id),
// name: name,
// icon: icon,
// color: color
// })
// }).then(...)
console.log('保存分类:', { id, name, icon, color });
closeCatModal();
showToast('保存成功', 'success');
// window.location.reload();
}
/* ==================== 删除确认 ==================== */
function deleteCategory(id) {
document.getElementById('deleteId').value = id;
document.getElementById('deleteModal').style.display = 'flex';
}
function closeDeleteModal() {
document.getElementById('deleteModal').style.display = 'none';
}
function confirmDeleteCategory() {
var id = document.getElementById('deleteId').value;
// TODO: 发送删除请求
console.log('删除分类, id:', id);
closeDeleteModal();
showToast('已删除', 'info');
// window.location.reload();
}
/* ==================== 点击遮罩关闭 ==================== */
document.getElementById('catModal').addEventListener('click', function(e) {
if (e.target === this) closeCatModal();
});
document.getElementById('deleteModal').addEventListener('click', function(e) {
if (e.target === this) closeDeleteModal();
});
</script>
</body>
</html>
@@ -0,0 +1,120 @@
<!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 rel="stylesheet" href="css/style.css">
</head>
<body>
<nav class="navbar">
<a class="navbar-brand" href="index.html"><span class="logo-icon">📝</span> 笔记小站</a>
<div class="navbar-actions" id="navbarActions"></div>
</nav>
<div class="admin-layout">
<aside class="admin-sidebar">
<nav class="admin-nav">
<a class="admin-nav-item" href="admin_categories.php">
<span>📂</span> 分类管理
</a>
<a class="admin-nav-item" href="admin_notes.php">
<span>📝</span> 笔记管理
</a>
<a class="admin-nav-item active" href="admin_comments.php">
<span>💬</span> 评论管理
</a>
<a class="admin-nav-item" href="admin_users.php">
<span>👥</span> 用户管理
</a>
</nav>
</aside>
<main class="admin-main">
<div class="admin-panel">
<div class="admin-panel-header">
<h3>💬 评论管理</h3>
</div>
<div class="admin-panel-body">
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>笔记</th>
<th>用户</th>
<th>内容</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!--
========================================
后端循环输出 <tr>,示例:
<tr>
<td>1</td>
<td>如何搭建个人博客</td>
<td>test</td>
<td>写的真好!</td>
<td>2026-01-12</td>
<td class="actions">
<button class="btn btn-sm btn-danger" onclick="deleteComment(1)">删除</button>
</td>
</tr>
========================================
-->
</tbody>
</table>
</div>
</div>
</main>
</div>
<!-- ==================== 删除确认模态框 ==================== -->
<div class="modal-overlay" id="deleteModal" style="display:none;">
<div class="form-modal" onclick="event.stopPropagation()">
<h3>⚠️ 确认删除</h3>
<input type="hidden" id="deleteId" value="">
<p style="margin-bottom:16px;">确定要删除该评论吗?此操作不可恢复。</p>
<div class="form-actions">
<button class="btn" onclick="closeDeleteModal()">取消</button>
<button class="btn btn-danger" onclick="confirmDeleteComment()">确认删除</button>
</div>
</div>
</div>
<div class="toast-container" id="toastContainer"></div>
<script src="js/common.js"></script>
<script>
/* ==================== 删除评论 ==================== */
function deleteComment(id) {
document.getElementById('deleteId').value = id;
document.getElementById('deleteModal').style.display = 'flex';
}
function closeDeleteModal() {
document.getElementById('deleteModal').style.display = 'none';
}
function confirmDeleteComment() {
var id = document.getElementById('deleteId').value;
// TODO: 发送删除请求
console.log('删除评论, id:', id);
closeDeleteModal();
showToast('已删除', 'info');
// window.location.reload();
}
/* ==================== 点击遮罩关闭 ==================== */
document.getElementById('deleteModal').addEventListener('click', function(e) {
if (e.target === this) closeDeleteModal();
});
</script>
</body>
</html>
@@ -0,0 +1,227 @@
<!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 rel="stylesheet" href="css/style.css">
</head>
<body>
<nav class="navbar">
<a class="navbar-brand" href="index.html"><span class="logo-icon">📝</span> 笔记小站</a>
<div class="navbar-actions" id="navbarActions"></div>
</nav>
<div class="admin-layout">
<aside class="admin-sidebar">
<nav class="admin-nav">
<a class="admin-nav-item" href="admin_categories.php">
<span>📂</span> 分类管理
</a>
<a class="admin-nav-item active" href="admin_notes.php">
<span>📝</span> 笔记管理
</a>
<a class="admin-nav-item" href="admin_comments.php">
<span>💬</span> 评论管理
</a>
<a class="admin-nav-item" href="admin_users.php">
<span>👥</span> 用户管理
</a>
</nav>
</aside>
<main class="admin-main">
<div class="admin-panel">
<div class="admin-panel-header">
<h3>📝 笔记管理</h3>
<button class="btn btn-primary btn-sm" onclick="openNoteModal(0)">+ 新增笔记</button>
</div>
<div class="admin-panel-body">
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>分类</th>
<th>作者</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!--
========================================
后端循环输出 <tr>,示例:
<tr>
<td>1</td>
<td>如何搭建个人博客</td>
<td>📂 技术笔记</td>
<td>admin</td>
<td>2026-01-10</td>
<td class="actions">
<button class="btn btn-sm" onclick="openNoteModal(1)">编辑</button>
<button class="btn btn-sm btn-danger" onclick="deleteNote(1)">删除</button>
</td>
</tr>
========================================
-->
</tbody>
</table>
</div>
</div>
</main>
</div>
<!-- ==================== 新增 / 编辑笔记 模态框 ==================== -->
<div class="modal-overlay" id="noteModal" style="display:none;">
<div class="form-modal" onclick="event.stopPropagation()" style="width:580px;max-width:95vw;">
<h3 id="noteModalTitle">新增笔记</h3>
<input type="hidden" id="fnid" value="0">
<div class="form-group">
<label>标题</label>
<input type="text" id="fntitle" placeholder="请输入笔记标题">
</div>
<div class="form-group">
<label>分类</label>
<select id="fncat">
<!--
========================================
后端循环输出 <option>,示例:
<option value="1">📂 技术笔记</option>
<option value="2">📂 生活随笔</option>
========================================
-->
</select>
</div>
<div class="form-group">
<label>内容</label>
<textarea id="fncontent" rows="8" placeholder="请输入笔记内容"></textarea>
</div>
<div class="form-actions">
<button class="btn" onclick="closeNoteModal()">取消</button>
<button class="btn btn-primary" onclick="saveNote()">保存</button>
</div>
</div>
</div>
<!-- ==================== 删除确认模态框 ==================== -->
<div class="modal-overlay" id="deleteModal" style="display:none;">
<div class="form-modal" onclick="event.stopPropagation()">
<h3>⚠️ 确认删除</h3>
<input type="hidden" id="deleteId" value="">
<p style="margin-bottom:16px;">确定要删除该笔记吗?此操作不可恢复。</p>
<div class="form-actions">
<button class="btn" onclick="closeDeleteModal()">取消</button>
<button class="btn btn-danger" onclick="confirmDeleteNote()">确认删除</button>
</div>
</div>
</div>
<div class="toast-container" id="toastContainer"></div>
<script src="js/common.js"></script>
<script>
/* ==================== 笔记模态框 ==================== */
/**
* 打开新增 / 编辑笔记弹窗
* @param {number} id - 笔记 ID0 = 新增,>0 = 编辑
*/
function openNoteModal(id) {
var titleEl = document.getElementById('noteModalTitle');
var nidEl = document.getElementById('fnid');
var titleVal = document.getElementById('fntitle');
var catVal = document.getElementById('fncat');
var contentVal = document.getElementById('fncontent');
if (id === 0) {
titleEl.textContent = '新增笔记';
nidEl.value = '0';
titleVal.value = '';
catVal.value = '';
contentVal.value = '';
} else {
titleEl.textContent = '编辑笔记';
nidEl.value = id;
// TODO: 开发者自行拉取笔记数据并填入
// titleVal.value = data.title;
// catVal.value = data.categoryId;
// contentVal.value = data.content;
}
document.getElementById('noteModal').style.display = 'flex';
}
/** 关闭笔记模态框 */
function closeNoteModal() {
document.getElementById('noteModal').style.display = 'none';
document.getElementById('fnid').value = '0';
}
/** 保存笔记 */
function saveNote() {
var id = document.getElementById('fnid').value;
var title = document.getElementById('fntitle').value.trim();
var catId = document.getElementById('fncat').value;
var content = document.getElementById('fncontent').value.trim();
if (!title || !content) { showToast('请填写标题和内容', 'error'); return; }
// TODO: 发送请求到后端保存
// fetch('/function/admin.php', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({
// action: id === '0' ? 'addNote' : 'editNote',
// id: id === '0' ? null : parseInt(id),
// title: title,
// categoryId: parseInt(catId),
// content: content
// })
// }).then(...)
console.log('保存笔记:', { id, title, catId, content: content.slice(0, 50) + '...' });
closeNoteModal();
showToast('保存成功', 'success');
// window.location.reload();
}
/* ==================== 删除确认 ==================== */
function deleteNote(id) {
document.getElementById('deleteId').value = id;
document.getElementById('deleteModal').style.display = 'flex';
}
function closeDeleteModal() {
document.getElementById('deleteModal').style.display = 'none';
}
function confirmDeleteNote() {
var id = document.getElementById('deleteId').value;
// TODO: 发送删除请求
console.log('删除笔记, id:', id);
closeDeleteModal();
showToast('已删除', 'info');
// window.location.reload();
}
/* ==================== 点击遮罩关闭 ==================== */
document.getElementById('noteModal').addEventListener('click', function(e) {
if (e.target === this) closeNoteModal();
});
document.getElementById('deleteModal').addEventListener('click', function(e) {
if (e.target === this) closeDeleteModal();
});
</script>
</body>
</html>
@@ -0,0 +1,18 @@
<?php
include 'function/admin.php';
$current = basename($_SERVER['SCRIPT_NAME']);
?>
<nav class="admin-nav">
<a class="admin-nav-item <?php echo $current == 'admin_categories.php' ? 'active' : ''; ?>" href="admin_categories.php">
<span>📂</span> 分类管理
</a>
<a class="admin-nav-item <?php echo $current == 'admin_notes.php' ? 'active' : ''; ?>" href="admin_notes.php">
<span>📝</span> 笔记管理
</a>
<a class="admin-nav-item <?php echo $current == 'admin_comments.php' ? 'active' : ''; ?>" href="admin_comments.php">
<span>💬</span> 评论管理
</a>
<a class="admin-nav-item <?php echo $current == 'admin_users.php' ? 'active' : ''; ?>" href="admin_users.php">
<span>👥</span> 用户管理
</a>
</nav>
@@ -0,0 +1,316 @@
<!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 rel="stylesheet" href="css/style.css">
</head>
<body>
<nav class="navbar">
<a class="navbar-brand" href="index.html"><span class="logo-icon">📝</span> 笔记小站</a>
<div class="navbar-actions" id="navbarActions"></div>
</nav>
<div class="admin-layout">
<aside class="admin-sidebar">
<nav class="admin-nav">
<a class="admin-nav-item" href="admin_categories.php">
<span>📂</span> 分类管理
</a>
<a class="admin-nav-item" href="admin_notes.php">
<span>📝</span> 笔记管理
</a>
<a class="admin-nav-item" href="admin_comments.php">
<span>💬</span> 评论管理
</a>
<a class="admin-nav-item active" href="admin_users.php">
<span>👥</span> 用户管理
</a>
</nav>
</aside>
<main class="admin-main">
<div class="admin-panel">
<div class="admin-panel-header">
<h3>👥 用户管理</h3>
<button class="btn btn-primary btn-sm" onclick="openUserModal(0)">+ 新增用户</button>
</div>
<div class="admin-panel-body">
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>头像</th>
<th>用户名</th>
<th>邮箱</th>
<th>角色</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php
require 'function/db.php';
$sql = 'SELECT * FROM users where status = 1';
$result = mysqli_query($conn, $sql);
while ($row = mysqli_fetch_assoc($result)) {
$uid = $row['id'];
$username = $row['username'];
$email = $row['email'];
$avatar = !empty($row['avatar']) ? $row['avatar'] : '😊';
$role = !empty($row['role']) ? $row['role'] : 'user';
$roleText = ($role === 'admin') ? '管理员' : '普通用户';
$created = !empty($row['created_at']) ? $row['created_at'] : '—';
$password = $row['password'];
?>
<tr>
<td><?= $uid ?></td>
<td><?= $avatar ?></td>
<td><?= $username ?></td>
<td><?= $email ?></td>
<td><span class="badge badge-<?= $role ?>"><?= $roleText ?></span></td>
<td><?= $created ?></td>
<td class="actions">
<button class="btn btn-sm" onclick="openUserModal(<?= $uid ?>,'<?= $username ?>','<?= $email ?>','<?= $password ?>','<?= $role ?>')">编辑</button>
<button class="btn btn-sm btn-danger" onclick="deleteUser(<?= $uid ?>)">删除</button>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</main>
</div>
<!-- ==================== 新增 / 编辑用户 模态框 ==================== -->
<div class="modal-overlay" id="userModal" style="display:none;">
<div class="form-modal" onclick="event.stopPropagation()">
<h3 id="userModalTitle">新增用户</h3>
<!-- 隐藏域:编辑时存放用户 ID,新增时为 0 -->
<input type="hidden" id="fid" value="0">
<div class="form-group">
<label>用户名</label>
<input type="text" id="fname" placeholder="请输入用户名">
</div>
<div class="form-group">
<label>邮箱</label>
<input type="text" id="femail" placeholder="请输入邮箱">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" id="fpassword" placeholder="新增时必填,编辑时留空则不修改">
</div>
<div class="form-group">
<label>角色</label>
<select id="frole">
<option value="user">普通用户</option>
<option value="admin">管理员</option>
</select>
</div>
<div class="form-actions">
<button class="btn" onclick="closeUserModal()">取消</button>
<button class="btn btn-primary" onclick="saveUser()">保存</button>
</div>
</div>
</div>
<!-- ==================== 删除确认模态框 ==================== -->
<div class="modal-overlay" id="deleteModal" style="display:none;">
<div class="form-modal" onclick="event.stopPropagation()">
<h3>⚠️ 确认删除</h3>
<input type="hidden" id="deleteId" value="">
<p style="margin-bottom:16px;">确定要删除该用户吗?此操作不可恢复。</p>
<div class="form-actions">
<button class="btn" onclick="closeDeleteModal()">取消</button>
<button class="btn btn-danger" onclick="confirmDeleteUser()">确认删除</button>
</div>
</div>
</div>
<div class="toast-container" id="toastContainer"></div>
<script src="js/common.js"></script>
<script>
/* ==================== 用户模态框 ==================== */
/**
* 打开新增/编辑用户的模态框
* @param {number} id - 用户 ID,传 0 表示新增
*
* 编辑时请在此函数中拉取用户数据并填入表单:
* document.getElementById('fid').value = 用户ID
* document.getElementById('fname').value = 用户名
* document.getElementById('femail').value = 邮箱
* document.getElementById('fpassword').value = '' (编辑时密码留空)
* document.getElementById('frole').value = 角色 (user / admin)
*/
function openUserModal(id,username='',email='',password='', role='') {
var titleEl = document.getElementById('userModalTitle');
var fidEl = document.getElementById('fid');
var nameEl = document.getElementById('fname');
var emailEl = document.getElementById('femail');
var pwdEl = document.getElementById('fpassword');
var roleEl = document.getElementById('frole');
if (id === 0) {
// ---- 新增模式 ----
titleEl.textContent = '新增用户';
fidEl.value = '0';
nameEl.value = '';
emailEl.value = '';
pwdEl.value = '';
pwdEl.placeholder = '请设置密码';
roleEl.value = 'user';
} else {
// ---- 编辑模式 ----
titleEl.textContent = '编辑用户';
fidEl.value = id;
pwdEl.value = '';
pwdEl.placeholder = '留空则不修改密码';
// TODO: 开发者自行拉取用户数据并填入以下字段
// 示例:
nameEl.value = username;
emailEl.value = email;
roleEl.value = role;
}
document.getElementById('userModal').style.display = 'flex';
}
/** 关闭用户模态框 */
function closeUserModal() {
document.getElementById('userModal').style.display = 'none';
document.getElementById('fid').value = '0';
}
/**
* 保存用户(新增或编辑)
* 开发者在此处实现提交逻辑(Ajax / 表单提交)
*/
function saveUser() {
var id = document.getElementById('fid').value;
var username = document.getElementById('fname').value.trim();
var email = document.getElementById('femail').value.trim();
var password = document.getElementById('fpassword').value;
var role = document.getElementById('frole').value;
// 前端校验
if (!username || !email) {
showToast('请填写用户名和邮箱', 'error');
return;
}
// 新增时密码必填
if (id === '0' && !password) {
showToast('请设置密码', 'error');
return;
}
// 判断是新增还是编辑
if(id === '0'){
var action = "register";
}else{
var action = "update";
}
fetch('/function/user.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'action': action,
'id': id,
'username': username,
'email': email,
'password': password,
'role' : role,
})
})
.then(res => res.json())
.then(data => {
if (data.code === 0) {
showToast(data.message, 'success');
setTimeout(() => {
location.href = location.href;
}, 1000);
} else {
showToast(data.message, 'error');
}
})
.catch(err => {
showToast(err.message, 'error');
});
closeUserModal();
}
/* ==================== 删除确认模态框 ==================== */
/** 打开删除确认弹窗 */
function deleteUser(id) {
document.getElementById('deleteId').value = id;
document.getElementById('deleteModal').style.display = 'flex';
}
/** 关闭删除弹窗 */
function closeDeleteModal() {
document.getElementById('deleteModal').style.display = 'none';
}
/** 确认删除 */
function confirmDeleteUser() {
var id = document.getElementById('deleteId').value;
fetch('/function/user.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'action': 'delete',
'id': id
})
})
.then(res => res.json())
.then(data => {
if (data.code === 0) {
showToast('删除成功', 'success');
setTimeout(() => {
location.href = location.href;
}, 1000);
} else {
showToast(data.message, 'error');
}
})
.catch(err => {
showToast(err.message, 'error');
});
closeDeleteModal();
// 删除成功后刷新列表:
// window.location.reload();
}
/* ==================== 点击遮罩关闭模态框 ==================== */
document.getElementById('userModal').addEventListener('click', function(e) {
if (e.target === this) closeUserModal();
});
document.getElementById('deleteModal').addEventListener('click', function(e) {
if (e.target === this) closeDeleteModal();
});
</script>
</body>
</html>
@@ -543,7 +543,78 @@ input, textarea, select { font-family: inherit; outline: none; }
.badge-user { background: #e0e7ff; color: #4f46e5; }
.badge-category { background: #d1fae5; color: #059669; }
/* ==================== Admin Tabs ==================== */
/* ==================== Admin Sidebar Layout ==================== */
.admin-layout {
display: flex; min-height: calc(100vh - 64px);
max-width: 1280px; margin: 0 auto; padding: 1.5rem;
gap: 1.5rem;
}
.admin-sidebar {
width: 220px; flex-shrink: 0;
background: var(--bg-card); border-radius: var(--radius-lg);
border: 1px solid var(--border); padding: 1rem;
height: fit-content; position: sticky; top: 80px;
animation: fadeInUp 0.4s ease both;
}
.admin-nav { display: flex; flex-direction: column; gap: 0.25rem; }
.admin-nav-item {
display: flex; align-items: center; gap: 0.65rem;
padding: 0.75rem 1rem; border-radius: var(--radius-sm);
font-size: 0.9rem; font-weight: 600; color: var(--text-secondary);
transition: all var(--transition); text-decoration: none;
}
.admin-nav-item:hover { background: var(--bg-hover); color: var(--text); }
.admin-nav-item.active {
background: linear-gradient(135deg, var(--primary), #8b5cf6);
color: white; box-shadow: var(--shadow-sm);
}
.admin-nav-item span { font-size: 1.1rem; }
.admin-main {
flex: 1; min-width: 0;
animation: fadeInUp 0.4s 0.05s ease both;
}
.admin-panel {
background: var(--bg-card); border-radius: var(--radius-lg);
border: 1px solid var(--border); overflow: hidden;
}
.admin-panel-header {
display: flex; align-items: center; justify-content: space-between;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.admin-panel-header h3 {
font-size: 1.1rem; font-weight: 700;
display: flex; align-items: center; gap: 0.4rem;
}
.admin-panel-body { padding: 0; }
.admin-table { width: 100%; border-collapse: collapse; }
.admin-table thead { background: var(--bg); }
.admin-table th {
text-align: left; padding: 0.75rem 1.25rem;
font-size: 0.78rem; font-weight: 700; color: var(--text-muted);
text-transform: uppercase; letter-spacing: 0.05em;
border-bottom: 2px solid var(--border);
}
.admin-table td {
padding: 0.85rem 1.25rem; font-size: 0.9rem;
border-bottom: 1px solid var(--border-light);
color: var(--text);
}
.admin-table tbody tr { transition: var(--transition); }
.admin-table tbody tr:hover { background: var(--bg-hover); }
.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table td.actions { white-space: nowrap; }
.admin-table td.actions .btn + .btn { margin-left: 0.35rem; }
/* ==================== Admin Tabs (legacy) ==================== */
.admin-header {
text-align: center; padding: 2rem 0 1.5rem;
animation: fadeInUp 0.5s ease both;
@@ -0,0 +1,33 @@
<?php
// 接受数据,并且提取出action
$data = json_decode(file_get_contents('php://input'), true);
$action = $data['action'] ?? '';
include 'db.php';
// 如果数据库连接失败,则返回错误信息
/** @var mysqli $conn */
if (!isset($conn)) {
echo json_encode(['code' => 10004, 'status' => 'error', 'message' => '数据库连接失败']);
exit;
}
// 判断是否admin登录
session_start();
if (!isset($_SESSION['username'])) {
echo "<script>alert('请登录');setTimeout(function(){location.href='login.html';},1000);</script>";
exit;
}
if ($_SESSION['username'] != 'admin') {
echo "<script>alert('请登录管理员账号');setTimeout(function(){location.href='login.html';},1000);</script>";
exit;
}
// 获取所有的用户信息
if($action=='getUsers'){
$sql = "select * from users";
$result = mysqli_query($conn, $sql);
$users = mysqli_fetch_all($result, MYSQLI_ASSOC);
echo json_encode($users);
}
?>
@@ -5,6 +5,7 @@ $action = $data['action'] ?? '';
// 处理与用户相关的操作
include 'db.php';
session_start();
// 如果数据库连接失败,则返回错误信息
/** @var mysqli $conn */
@@ -16,7 +17,7 @@ if (!isset($conn)) {
// 测试用户名是否已存在
function testUsername(mysqli $conn, string $username): bool
{
$sql = "select * from users where username = '$username'"; // sql注入风险
$sql = "select * from users where username = '$username' and status = 1"; // sql注入风险
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
return true;
@@ -30,15 +31,16 @@ if ($action == 'register') {
$username = $data['username'];
$password = $data['password'];
$email = $data['email'];
$role = isset($data['role']) ? $data['role'] : 'user';
if (testUsername($conn, $username)) {
echo json_encode(['code' => 10001, 'status' => 'error', 'message' => '用户名已存在']);
} else {
$sql = "insert into users (username, password, email) values ('$username', '$password', '$email')";
$sql = "insert into users (username, password, email, role) values ('$username', '$password', '$email', '$role')";
$result = mysqli_query($conn, $sql);
if ($result) {
echo json_encode(['code' => 0, 'status' => 'success', 'message' => '注册成功']);
} else {
echo json_encode(['code' => 10002, 'status' => 'error', 'message' => '注册失败']);
echo json_encode(['code' => 10002, 'status' => 'error', 'message' => '注册失败', 'sql'=>$sql]);
}
}
}
@@ -51,7 +53,36 @@ if ($action == 'login') {
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
echo json_encode(['code' => 0, 'status' => 'success', 'message' => '登录成功']);
$_SESSION['username'] = $username;
} else {
echo json_encode(['code' => 10003, 'status' => 'error', 'message' => '用户名或密码错误']);
}
}
// delete的逻辑
if ($action == 'delete') {
$id = $data['id'];
$sql = "update users set status = 0 where id = $id";
$result = mysqli_query($conn, $sql);
if ($result) {
echo json_encode(['code' => 0, 'status' => 'success', 'message' => '删除成功']);
} else {
echo json_encode(['code' => 10005, 'status' => 'error', 'message' => '删除失败']);
}
}
// update的逻辑
if ($action == 'update') {
$id = $data['id'];
$username = $data['username'];
$email = $data['email'];
$role = $data['role'];
$sql = "update users set username = '$username', email = '$email', role = '$role' where id = $id";
$result = mysqli_query($conn, $sql);
if ($result) {
echo json_encode(['code' => 0, 'status' => 'success', 'message' => '更新成功']);
} else {
echo json_encode(['code' => 10006, 'status' => 'error', 'message' => '更新失败']);
}
}
@@ -25,7 +25,7 @@ function renderNavbar() {
if (!nav) return;
nav.innerHTML =
'<a href="index.html" class="btn btn-ghost">🏠 首页</a>' +
'<a href="admin.html" class="btn btn-outline btn-sm">⚙️ 后台管理</a>' +
'<a href="admin_categories.php" class="btn btn-outline btn-sm">⚙️ 后台管理</a>' +
'<a href="register.html" class="btn btn-ghost btn-sm">✍️ 注册</a>' +
'<a href="login.html" class="btn btn-primary btn-sm">🔑 登录</a>';
}
@@ -62,9 +62,15 @@
}).then(res => res.json()).then(data => {
if (data.code == 0) {
showToast('登录成功', 'success');
if (username == 'admin') {
setTimeout(() => {
window.location.href = '/';
window.location.href = '/admin_categories.php';
}, 1000);
}else{
setTimeout(() => {
history.go(-1);
}, 1000);
}
return
}
showToast(data.message, 'error'); return;