init: 我的博客项目

This commit is contained in:
Your Name
2026-09-09 19:30:18 +08:00
commit 76c241f032
34 changed files with 3420 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
<?php
require_once __DIR__ . "/../includes/config.php";
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
require_admin();
$adminPage = isset($adminPage) ? $adminPage : "";
?>
<!DOCTYPE html>
<html lang="zh-CN" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo isset($pageTitle) ? e($pageTitle) . " - 后台" : "后台管理"; ?></title>
<link rel="stylesheet" href="<?php echo e(SITE_URL); ?>assets/css/style.css">
<link rel="stylesheet" href="<?php echo e(SITE_URL); ?>assets/css/admin.css">
</head>
<body class="admin-body">
<div class="bg"></div>
<nav class="glass-nav">
<div class="nav-brand"><a href="<?php echo e(SITE_URL); ?>admin/index.php">我的博客 后台</a></div>
<div class="nav-links">
<a class="nav-link" href="<?php echo e(SITE_URL); ?>index.php" target="_blank">查看前台</a>
<a class="glass-btn primary" href="<?php echo e(SITE_URL); ?>logout.php">退出</a>
</div>
</nav>
<div class="container admin-layout">
<aside class="admin-aside">
<div class="side-panel glass">
<nav class="admin-menu">
<div class="menu-title">内容管理</div>
<a class="menu-item<?php echo $adminPage==="articles"?" active":""; ?>" href="<?php echo e(SITE_URL); ?>admin/article_list.php">文章管理</a>
<a class="menu-item<?php echo $adminPage==="comments"?" active":""; ?>" href="<?php echo e(SITE_URL); ?>admin/comment.php">评论管理</a>
<div class="menu-sep"></div>
<div class="menu-title">分类体系</div>
<a class="menu-item<?php echo $adminPage==="categories"?" active":""; ?>" href="<?php echo e(SITE_URL); ?>admin/category.php">分类管理</a>
<a class="menu-item<?php echo $adminPage==="tags"?" active":""; ?>" href="<?php echo e(SITE_URL); ?>admin/tag.php">标签管理</a>
<div class="menu-sep"></div>
<div class="menu-title">总览</div>
<a class="menu-item<?php echo $adminPage==="dash"?" active":""; ?>" href="<?php echo e(SITE_URL); ?>admin/index.php">仪表盘</a>
</nav>
</div>
</aside>
<main class="admin-main">
+18
View File
@@ -0,0 +1,18 @@
</main>
</div>
<div class="footnote">
<?php echo e(SITE_NAME); ?> 后台管理系统
</div>
<div class="modal-mask" id="confirmModal" style="display:none;">
<div class="modal glass">
<div class="modal-title">确认操作</div>
<div class="modal-text" id="confirmText"></div>
<div class="modal-actions">
<button class="glass-btn" id="confirmCancel" type="button">取消</button>
<button class="glass-btn primary" id="confirmOk" type="button">确认</button>
</div>
</div>
</div>
<script src="<?php echo e(SITE_URL); ?>assets/js/main.js"></script>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
require_admin();
$id = isset($_GET["id"]) ? (int)$_GET["id"] : 0;
if ($id) {
db()->prepare("DELETE FROM article_tags WHERE article_id=?")->execute([$id]);
db()->prepare("DELETE FROM comments WHERE article_id=?")->execute([$id]);
db()->prepare("DELETE FROM articles WHERE id=?")->execute([$id]);
}
header("Location: article_list.php?deleted=1"); exit;
+104
View File
@@ -0,0 +1,104 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
$adminPage = "articles";
$pageTitle = "编辑文章";
require __DIR__ . "/_layout.php";
$id = isset($_GET["id"]) ? (int)$_GET["id"] : 0;
$article = null;
if ($id) {
$stmt = db()->prepare("SELECT * FROM articles WHERE id=?");
$stmt->execute([$id]);
$article = $stmt->fetch();
if (!$article) { echo '<div class="msg">文章不存在或已删除</div>'; echo '<p><a href="article_list.php">返回列表</a></p>'; require __DIR__ . "/_tail.php"; exit; }
}
$cats = get_categories();
$allTags = get_all_tags();
$curTags = $article ? array_column(get_article_tags($article["id"]), "id") : [];
$msg = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$title = trim($_POST["title"] ?? "");
$slug = trim($_POST["slug"] ?? "");
$category_id = (int)($_POST["category_id"] ?? 0);
$excerpt = trim($_POST["excerpt"] ?? "");
$content = $_POST["content"] ?? "";
$status = ($_POST["status"] ?? "published") === "draft" ? "draft" : "published";
$tags = $_POST["tags"] ?? []; if (!is_array($tags)) { $tags = []; }
if ($title === "" || $content === "") {
$msg = '<div class="msg">标题和内容不能为空</div>';
} else {
if ($slug === "") { $slug = slugify($title); }
if ($msg === "") {
$user_id = current_user()["id"];
if ($id) {
$s = db()->prepare("UPDATE articles SET title=?, slug=?, category_id=?, excerpt=?, content=?, status=? WHERE id=?");
$s->execute([$title, $slug, $category_id ?: null, $excerpt, $content, $status, $id]);
} else {
$s = db()->prepare("INSERT INTO articles (user_id, title, slug, category_id, excerpt, content, status, views) VALUES (?,?,?,?,?,?,?,0)");
$s->execute([$user_id, $title, $slug, $category_id ?: null, $excerpt, $content, $status]);
$id = (int)db()->lastInsertId();
}
db()->prepare("DELETE FROM article_tags WHERE article_id=?")->execute([$id]);
if ($tags) {
$si = db()->prepare("INSERT IGNORE INTO article_tags (article_id, tag_id) VALUES (?,?)");
foreach ($tags as $tid) { $si->execute([$id, (int)$tid]); }
}
header("Location: article_list.php?ok=1"); exit;
}
}
}
?>
<div class="admin-head"><h1><?php echo $id ? "编辑文章" : "写文章"; ?></h1></div>
<?php echo $msg; ?>
<div class="card glass form-card">
<form method="post">
<div class="field">
<label>标题</label>
<input class="input" type="text" name="title" value="<?php echo e($article["title"] ?? ""); ?>" required>
</div>
<div class="field">
<label>别名(slug,留空自动生成)</label>
<input class="input" type="text" name="slug" value="<?php echo e($article["slug"] ?? ""); ?>" placeholder="english-slug">
</div>
<div class="form-row">
<div class="field">
<label>分类</label>
<select class="input" name="category_id">
<option value="0">未分类</option>
<?php foreach($cats as $c): ?><option value="<?php echo $c["id"]; ?>" <?php echo ($article["category_id"] ?? 0)==$c["id"]?"selected":""; ?>><?php echo e($c["name"]); ?></option><?php endforeach; ?>
</select>
</div>
<div class="field">
<label>状态</label>
<select class="input" name="status">
<option value="published" <?php echo ($article["status"] ?? "published")==="published"?"selected":""; ?>>发布</option>
<option value="draft" <?php echo ($article["status"] ?? "")==="draft"?"selected":""; ?>>草稿</option>
</select>
</div>
</div>
<div class="field">
<label>摘要</label>
<textarea class="input" name="excerpt" rows="2"><?php echo e($article["excerpt"] ?? ""); ?></textarea>
</div>
<div class="field">
<label>标签</label>
<div>
<?php foreach($allTags as $t): ?>
<label class="tag-check"><input type="checkbox" name="tags[]" value="<?php echo $t["id"]; ?>" <?php echo in_array($t["id"],$curTags)?"checked":""; ?>> <?php echo e($t["name"]); ?></label>
<?php endforeach; ?>
<?php if(!$allTags): ?><span class="text-muted">暂无标签,可先在标签管理中创建</span><?php endif; ?>
</div>
</div>
<div class="field">
<label>内容(支持 Markdown)</label>
<textarea class="input code" name="content" rows="16" required><?php echo e($article["content"] ?? ""); ?></textarea>
</div>
<button class="glass-btn primary" type="submit">保存</button>
<a class="glass-btn" href="article_list.php">返回</a>
</form>
</div>
<?php require __DIR__ . "/_tail.php"; ?>
+48
View File
@@ -0,0 +1,48 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
$adminPage = "articles";
$pageTitle = "文章管理";
require __DIR__ . "/_layout.php";
$page = isset($_GET["page"]) ? max(1,(int)$_GET["page"]) : 1;
$per = 15;
$total = (int)db()->query("SELECT COUNT(*) FROM articles")->fetchColumn();
$pages = max(1,(int)ceil($total/$per));
$page = min($page,$pages);
$off = ($page-1)*$per;
$stmt = db()->prepare("SELECT a.id,a.title,a.views,a.created_at,c.name AS cat, COUNT(t.id) AS tagcount FROM articles a LEFT JOIN categories c ON a.category_id=c.id LEFT JOIN article_tags at ON at.article_id=a.id LEFT JOIN tags t ON t.id=at.tag_id GROUP BY a.id ORDER BY a.created_at DESC LIMIT ? OFFSET ?");
$stmt->bindValue(1,$per,PDO::PARAM_INT);
$stmt->bindValue(2,$off,PDO::PARAM_INT);
$stmt->execute();
$list = $stmt->fetchAll();
?>
<div class="admin-head">
<h1>文章管理</h1>
<a class="glass-btn primary" href="article_edit.php">写文章</a>
</div>
<?php if (isset($_GET["deleted"])) { ?><div class="msg ok">删除成功</div><?php } ?>
<div class="card glass">
<table class="admin-table">
<thead><tr><th style="width:40px">ID</th><th class="title">标题</th><th>分类</th><th>标签</th><th>阅读</th><th>日期</th><th>操作</th></tr></thead>
<tbody>
<?php if (!$list): ?><tr><td colspan="7" class="text-muted">暂无文章</td></tr><?php endif; ?>
<?php foreach ($list as $a): ?>
<tr>
<td><?php echo $a["id"]; ?></td>
<td class="title"><?php echo e($a["title"]); ?></td>
<td class="text-muted"><?php echo e($a["cat"] ?: "未分类"); ?></td>
<td><span class="badge-count"><?php echo $a["tagcount"]; ?></span></td>
<td><?php echo $a["views"]; ?></td>
<td class="text-muted"><?php echo e(substr($a["created_at"],0,10)); ?></td>
<td><div class="actions"><a href="article_edit.php?id=<?php echo $a["id"]; ?>">编辑</a><a href="article_delete.php?id=<?php echo $a["id"]; ?>" data-confirm="确定删除该文章?">删除</a></div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="pagination">
<?php for($i=1;$i<=$pages;$i++): ?><a class="<?php echo $i===$page?"current":""; ?>" href="?page=<?php echo $i; ?>"><?php echo $i; ?></a><?php endfor; ?>
</div>
</div>
<?php require __DIR__ . "/_tail.php"; ?>
+55
View File
@@ -0,0 +1,55 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
$adminPage = "categories";
$pageTitle = "分类管理";
require __DIR__ . "/_layout.php";
$msg = "";
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["name"])) {
$name = trim($_POST["name"]);
$slug = slugify($_POST["slug"] ?? "");
if ($name === "") { $msg = '<div class="msg">分类名不能为空</div>'; }
else {
$s = db()->prepare("INSERT INTO categories (name, slug) VALUES (?,?)");
$s->execute([$name, $slug]);
header("Location: category.php?ok=1"); exit;
}
}
if (isset($_GET["del"])) {
$cid = (int)$_GET["del"];
db()->prepare("UPDATE articles SET category_id=NULL WHERE category_id=?")->execute([$cid]);
db()->prepare("DELETE FROM categories WHERE id=?")->execute([$cid]);
header("Location: category.php?deleted=1"); exit;
}
$list = db()->query("SELECT c.*, (SELECT COUNT(*) FROM articles a WHERE a.category_id=c.id) AS cnt FROM categories c ORDER BY c.id DESC")->fetchAll();
?>
<div class="admin-head"><h1>分类管理</h1></div>
<?php echo $msg; ?><?php if (isset($_GET["deleted"])) { ?><div class="msg ok">删除成功</div><?php } ?>
<div class="card glass form-card" style="margin-bottom:20px">
<h3>新增分类</h3>
<form method="post">
<div class="form-row">
<div class="field"><label>名称</label><input class="input" type="text" name="name" required></div>
<div class="field"><label>别名(slug,可选)</label><input class="input" type="text" name="slug" placeholder="english-slug"></div>
</div>
<button class="glass-btn primary" type="submit">添加</button>
</form>
</div>
<div class="card glass">
<table class="admin-table">
<thead><tr><th>ID</th><th>名称</th><th>别名</th><th>文章数</th><th>操作</th></tr></thead>
<tbody>
<?php foreach($list as $c): ?>
<tr>
<td><?php echo $c["id"]; ?></td>
<td><?php echo e($c["name"]); ?></td>
<td class="text-muted"><?php echo e($c["slug"]); ?></td>
<td><?php echo $c["cnt"]; ?></td>
<td><div class="actions"><a href="?del=<?php echo $c["id"]; ?>" data-confirm="删除分类不会删除其下文章,仅取消关联,继续?">删除</a></div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require __DIR__ . "/_tail.php"; ?>
+48
View File
@@ -0,0 +1,48 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
$adminPage = "comments";
$pageTitle = "评论管理";
require __DIR__ . "/_layout.php";
if (isset($_GET["del"])) {
$cid = (int)$_GET["del"];
db()->prepare("DELETE FROM comments WHERE id=?")->execute([$cid]);
header("Location: comment.php?deleted=1"); exit;
}
$page = isset($_GET["page"]) ? max(1,(int)$_GET["page"]) : 1;
$per = 15;
$total = (int)db()->query("SELECT COUNT(*) FROM comments")->fetchColumn();
$pages = max(1,(int)ceil($total/$per));
$page = min($page,$pages);
$off = ($page-1)*$per;
$stmt = db()->prepare("SELECT cm.*, a.title AS atitle FROM comments cm LEFT JOIN articles a ON a.id=cm.article_id ORDER BY cm.created_at DESC LIMIT ? OFFSET ?");
$stmt->bindValue(1,$per,PDO::PARAM_INT);
$stmt->bindValue(2,$off,PDO::PARAM_INT);
$stmt->execute();
$list = $stmt->fetchAll();
?>
<div class="admin-head"><h1>评论管理</h1></div>
<?php if (isset($_GET["deleted"])) { ?><div class="msg ok">删除成功</div><?php } ?>
<div class="card glass">
<table class="admin-table">
<thead><tr><th style="width:40px">ID</th><th>评论内容</th><th>昵称</th><th>文章</th><th>时间</th><th>操作</th></tr></thead>
<tbody>
<?php if(!$list): ?><tr><td colspan="6" class="text-muted">暂无评论</td></tr><?php endif; ?>
<?php foreach($list as $c): ?>
<tr>
<td><?php echo $c["id"]; ?></td>
<td><?php echo e(mb_strimwidth($c["content"],0,60,"...")); ?></td>
<td><?php echo e($c["author"]); ?></td>
<td class="text-muted"><?php echo e(mb_strimwidth($c["atitle"] ?? "已删除",0,20,"...")); ?></td>
<td class="text-muted"><?php echo e($c["created_at"]); ?></td>
<td><div class="actions"><a href="?del=<?php echo $c["id"]; ?>" data-confirm="确定删除该评论?">删除</a></div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="pagination">
<?php for($i=1;$i<=$pages;$i++): ?><a class="<?php echo $i===$page?"current":""; ?>" href="?page=<?php echo $i; ?>"><?php echo $i; ?></a><?php endfor; ?>
</div>
</div>
<?php require __DIR__ . "/_tail.php"; ?>
+46
View File
@@ -0,0 +1,46 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
$adminPage = "dash";
$pageTitle = "仪表盘";
require __DIR__ . "/_layout.php";
$articles = (int)db()->query("SELECT COUNT(*) FROM articles")->fetchColumn();
$comments = (int)db()->query("SELECT COUNT(*) FROM comments")->fetchColumn();
$categories = (int)db()->query("SELECT COUNT(*) FROM categories")->fetchColumn();
$tags = (int)db()->query("SELECT COUNT(*) FROM tags")->fetchColumn();
$views = (int)db()->query("SELECT COALESCE(SUM(views),0) FROM articles")->fetchColumn();
$recent = db()->query("SELECT a.id, a.title, a.created_at, c.name AS cat FROM articles a LEFT JOIN categories c ON a.category_id=c.id ORDER BY a.created_at DESC LIMIT 8")->fetchAll();
?>
<div class="admin-head">
<h1>仪表盘</h1>
<a class="glass-btn primary" href="article_edit.php">写文章</a>
</div>
<div class="stat-grid">
<div class="card glass stat-card"><div class="num"><?php echo $articles; ?></div><div class="label">文章</div></div>
<div class="card glass stat-card"><div class="num"><?php echo $comments; ?></div><div class="label">评论</div></div>
<div class="card glass stat-card"><div class="num"><?php echo $views; ?></div><div class="label">总阅读</div></div>
<div class="card glass stat-card"><div class="num"><?php echo $categories; ?></div><div class="label">分类</div></div>
<div class="card glass stat-card"><div class="num"><?php echo $tags; ?></div><div class="label">标签</div></div>
</div>
<div class="card glass">
<h3>最近文章</h3>
<table class="admin-table">
<thead><tr><th>标题</th><th>分类</th><th>发布时间</th><th>操作</th></tr></thead>
<tbody>
<?php if (!$recent): ?><tr><td colspan="4" class="text-muted">暂无文章</td></tr><?php endif; ?>
<?php foreach ($recent as $a): ?>
<tr>
<td><?php echo e($a["title"]); ?></td>
<td class="text-muted"><?php echo e($a["cat"] ?: "未分类"); ?></td>
<td class="text-muted"><?php echo e($a["created_at"]); ?></td>
<td><div class="actions"><a href="article_edit.php?id=<?php echo $a["id"]; ?>">编辑</a><a href="article_delete.php?id=<?php echo $a["id"]; ?>" data-confirm="确定删除该文章?">删除</a></div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require __DIR__ . "/_tail.php"; ?>
+55
View File
@@ -0,0 +1,55 @@
<?php
require_once __DIR__ . "/../includes/functions.php";
require_once __DIR__ . "/../includes/auth.php";
$adminPage = "tags";
$pageTitle = "标签管理";
require __DIR__ . "/_layout.php";
$msg = "";
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["name"])) {
$name = trim($_POST["name"]);
$slug = slugify($_POST["slug"] ?? "");
if ($name === "") { $msg = '<div class="msg">标签名不能为空</div>'; }
else {
$s = db()->prepare("INSERT INTO tags (name, slug) VALUES (?,?)");
$s->execute([$name, $slug]);
header("Location: tag.php?ok=1"); exit;
}
}
if (isset($_GET["del"])) {
$tid = (int)$_GET["del"];
db()->prepare("DELETE FROM article_tags WHERE tag_id=?")->execute([$tid]);
db()->prepare("DELETE FROM tags WHERE id=?")->execute([$tid]);
header("Location: tag.php?deleted=1"); exit;
}
$list = db()->query("SELECT t.*, (SELECT COUNT(*) FROM article_tags at WHERE at.tag_id=t.id) AS cnt FROM tags t ORDER BY t.id DESC")->fetchAll();
?>
<div class="admin-head"><h1>标签管理</h1></div>
<?php echo $msg; ?><?php if (isset($_GET["deleted"])) { ?><div class="msg ok">删除成功</div><?php } ?>
<div class="card glass form-card" style="margin-bottom:20px">
<h3>新增标签</h3>
<form method="post">
<div class="form-row">
<div class="field"><label>名称</label><input class="input" type="text" name="name" required></div>
<div class="field"><label>别名(slug,可选)</label><input class="input" type="text" name="slug" placeholder="english-slug"></div>
</div>
<button class="glass-btn primary" type="submit">添加</button>
</form>
</div>
<div class="card glass">
<table class="admin-table">
<thead><tr><th>ID</th><th>名称</th><th>别名</th><th>使用次数</th><th>操作</th></tr></thead>
<tbody>
<?php foreach($list as $t): ?>
<tr>
<td><?php echo $t["id"]; ?></td>
<td><?php echo e($t["name"]); ?></td>
<td class="text-muted"><?php echo e($t["slug"]); ?></td>
<td><?php echo $t["cnt"]; ?></td>
<td><div class="actions"><a href="?del=<?php echo $t["id"]; ?>" data-confirm="删除标签将解除所有文章的该标签关联,继续?">删除</a></div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require __DIR__ . "/_tail.php"; ?>