This commit is contained in:
2026-02-23 23:50:04 +08:00
commit 084d3b0faf
45 changed files with 4090 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>评论管理 · lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0f0f1a; --surface: #1a1a2f; --surface-light: #252540; --border: #2d2d4a;
--text: #e0e0f0; --text-soft: #b0b0d0; --text-hint: #8080a0;
--primary: #c5a572; --gradient: linear-gradient(135deg, #c5a572, #9a7e5a);
--sidebar-width: 220px;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', sans-serif; }
.admin-wrapper { display: flex; min-height: 100vh; }
.sidebar { width: var(--sidebar-width); background: var(--surface); border-right: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; height: 100vh; }
.sidebar-header { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.sidebar-header .logo { font-size: 1.6rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.sidebar-header p { color: var(--text-soft); font-size: 0.85rem; }
.sidebar-menu { list-style: none; }
.sidebar-menu li { margin: 5px 0; }
.sidebar-menu a { display: block; padding: 10px 20px; color: var(--text-soft); text-decoration: none; border-left: 4px solid transparent; }
.sidebar-menu a:hover, .sidebar-menu a.active { background: var(--surface-light); border-left-color: var(--primary); color: var(--primary); }
.sidebar-menu .separator { height: 1px; background: var(--border); margin: 15px 20px; }
.main-content { flex: 1; padding: 20px 30px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.page-title { font-size: 1.8rem; font-weight: 600; color: var(--primary); }
.user-info span { background: var(--surface-light); padding: 6px 16px; border-radius: 30px; }
.message { background: var(--surface-light); border-left: 4px solid var(--primary); padding: 12px 20px; margin-bottom: 20px; border-radius: 8px; }
.table-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; min-width: 700px; }
th { background: var(--surface-light); padding: 12px 15px; text-align: left; font-weight: 600; color: var(--primary); border-bottom: 1px solid var(--border); }
td { padding: 12px 15px; border-bottom: 1px solid var(--border); color: var(--text-soft); }
tr:hover { background: var(--surface-light); }
.actions a { color: #ff6b6b; text-decoration: none; }
@media (max-width: 768px) { .admin-wrapper { flex-direction: column; } .sidebar { width: 100%; height: auto; position: static; } }
</style>
</head>
<body>
<div class="admin-wrapper">
<aside class="sidebar">
<div class="sidebar-header"><div class="logo">lv8girl</div><p>管理面板</p></div>
<ul class="sidebar-menu">
<li><a href="/admin">📊 仪表盘</a></li>
<li><a href="/admin/pending_posts">⏳ 待审核帖子</a></li>
<li><a href="/admin/pending_users">👥 待审核用户</a></li>
<li><a href="/admin/posts">📝 帖子管理</a></li>
<li><a href="/admin/users">👥 用户管理</a></li>
<li><a href="/admin/comments" class="active">💬 评论管理</a></li>
<li class="separator"></li>
<li><a href="/">🏠 返回首页</a></li>
</ul>
</aside>
<main class="main-content">
<div class="top-bar">
<h1 class="page-title">评论管理</h1>
<div class="user-info"><span>{{.Username}}</span></div>
</div>
{{if .Message}}<div class="message">{{.Message}}</div>{{end}}
<div class="table-card">
<table>
<thead><tr><th>ID</th><th>帖子</th><th>评论者</th><th>内容</th><th>时间</th><th>操作</th></tr></thead>
<tbody>
{{range .Comments}}
<tr>
<td>{{.ID}}</td>
<td><a href="/post/{{.PostID}}" target="_blank">{{slice .Post.Title 0 30}}...</a></td>
<td>{{.User.Username}}</td>
<td>{{slice .Content 0 50}}{{if gt (len .Content) 50}}...{{end}}</td>
<td>{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
<td class="actions">
<a href="/admin/comments/delete/{{.ID}}" onclick="return confirm('确定删除?')">删除</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理面板 · lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0f0f1a;
--surface: #1a1a2f;
--surface-light: #252540;
--border: #2d2d4a;
--text: #e0e0f0;
--text-soft: #b0b0d0;
--text-hint: #8080a0;
--primary: #c5a572;
--primary-light: #d4b78c;
--accent: #a58e6d;
--gradient: linear-gradient(135deg, #c5a572, #9a7e5a);
--sidebar-width: 220px;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 1.6; }
.admin-wrapper { display: flex; min-height: 100vh; }
.sidebar { width: var(--sidebar-width); background: var(--surface); border-right: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
.sidebar-header { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.sidebar-header .logo { font-size: 1.6rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 5px; }
.sidebar-header p { color: var(--text-soft); font-size: 0.85rem; }
.sidebar-menu { list-style: none; }
.sidebar-menu li { margin: 5px 0; }
.sidebar-menu a { display: block; padding: 10px 20px; color: var(--text-soft); text-decoration: none; transition: 0.2s; border-left: 4px solid transparent; }
.sidebar-menu a:hover, .sidebar-menu a.active { background: var(--surface-light); border-left-color: var(--primary); color: var(--primary); }
.sidebar-menu .separator { height: 1px; background: var(--border); margin: 15px 20px; }
.main-content { flex: 1; padding: 20px 30px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.page-title { font-size: 1.8rem; font-weight: 600; color: var(--primary); }
.user-info { display: flex; align-items: center; gap: 15px; }
.user-info span { background: var(--surface-light); padding: 6px 16px; border-radius: 30px; color: var(--text); }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; }
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
.stat-number { font-size: 2.5rem; font-weight: 700; color: var(--primary); line-height: 1.2; margin-bottom: 4px; }
.stat-label { color: var(--text-hint); font-size: 0.95rem; }
.table-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
table { width: 100%; border-collapse: collapse; }
th { background: var(--surface-light); padding: 12px 15px; text-align: left; font-weight: 600; color: var(--primary); border-bottom: 1px solid var(--border); }
td { padding: 12px 15px; border-bottom: 1px solid var(--border); color: var(--text-soft); }
tr:last-child td { border-bottom: none; }
tr:hover { background: var(--surface-light); }
.actions a { margin-right: 10px; color: var(--text-hint); text-decoration: none; }
.actions a:hover { color: var(--primary); }
.actions .delete { color: #ff6b6b; }
.actions .delete:hover { color: #ff4d4d; }
.actions .approve { color: var(--primary); }
.actions .approve:hover { color: var(--primary-light); }
.message { background: var(--surface-light); border-left: 4px solid var(--primary); padding: 12px 20px; margin-bottom: 20px; border-radius: 8px; color: var(--text); }
@media (max-width: 768px) { .admin-wrapper { flex-direction: column; } .sidebar { width: 100%; height: auto; position: static; } }
</style>
</head>
<body>
<div class="admin-wrapper">
<aside class="sidebar">
<div class="sidebar-header">
<div class="logo">lv8girl</div>
<p>管理面板</p>
</div>
<ul class="sidebar-menu">
<li><a href="/admin" class="{{if eq .Page "dashboard"}}active{{end}}">📊 仪表盘</a></li>
<li><a href="/admin/pending_posts" class="{{if eq .Page "pending_posts"}}active{{end}}">⏳ 待审核帖子</a></li>
<li><a href="/admin/pending_users" class="{{if eq .Page "pending_users"}}active{{end}}">👥 待审核用户</a></li>
<li><a href="/admin/posts" class="{{if eq .Page "posts"}}active{{end}}">📝 帖子管理</a></li>
<li><a href="/admin/users" class="{{if eq .Page "users"}}active{{end}}">👥 用户管理</a></li>
<li><a href="/admin/comments" class="{{if eq .Page "comments"}}active{{end}}">💬 评论管理</a></li>
<li class="separator"></li>
<li><a href="/">🏠 返回首页</a></li>
</ul>
</aside>
<main class="main-content">
<div class="top-bar">
<h1 class="page-title">仪表盘</h1>
<div class="user-info"><span>{{.Username}}</span></div>
</div>
{{if .Message}}<div class="message">{{.Message}}</div>{{end}}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number">{{.Stats.Posts}}</div>
<div class="stat-label">帖子总数</div>
</div>
<div class="stat-card">
<div class="stat-number">{{.Stats.Users}}</div>
<div class="stat-label">注册用户</div>
</div>
<div class="stat-card">
<div class="stat-number">{{.Stats.Comments}}</div>
<div class="stat-label">评论总数</div>
</div>
<div class="stat-card">
<div class="stat-number">{{.Stats.Likes}}</div>
<div class="stat-label">点赞总数</div>
</div>
<div class="stat-card">
<div class="stat-number">{{.Stats.Online}}</div>
<div class="stat-label">实时在线</div>
</div>
<div class="stat-card">
<div class="stat-number">{{.Stats.Pending}}</div>
<div class="stat-label">待审核帖子</div>
</div>
</div>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>待审核帖子 · lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0f0f1a; --surface: #1a1a2f; --surface-light: #252540; --border: #2d2d4a;
--text: #e0e0f0; --text-soft: #b0b0d0; --text-hint: #8080a0;
--primary: #c5a572; --gradient: linear-gradient(135deg, #c5a572, #9a7e5a);
--sidebar-width: 220px;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', sans-serif; }
.admin-wrapper { display: flex; min-height: 100vh; }
.sidebar { width: var(--sidebar-width); background: var(--surface); border-right: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; height: 100vh; }
.sidebar-header { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.sidebar-header .logo { font-size: 1.6rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.sidebar-header p { color: var(--text-soft); font-size: 0.85rem; }
.sidebar-menu { list-style: none; }
.sidebar-menu li { margin: 5px 0; }
.sidebar-menu a { display: block; padding: 10px 20px; color: var(--text-soft); text-decoration: none; border-left: 4px solid transparent; }
.sidebar-menu a:hover, .sidebar-menu a.active { background: var(--surface-light); border-left-color: var(--primary); color: var(--primary); }
.sidebar-menu .separator { height: 1px; background: var(--border); margin: 15px 20px; }
.main-content { flex: 1; padding: 20px 30px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.page-title { font-size: 1.8rem; font-weight: 600; color: var(--primary); }
.user-info span { background: var(--surface-light); padding: 6px 16px; border-radius: 30px; }
.message { background: var(--surface-light); border-left: 4px solid var(--primary); padding: 12px 20px; margin-bottom: 20px; border-radius: 8px; }
.table-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
table { width: 100%; border-collapse: collapse; }
th { background: var(--surface-light); padding: 12px 15px; text-align: left; font-weight: 600; color: var(--primary); border-bottom: 1px solid var(--border); }
td { padding: 12px 15px; border-bottom: 1px solid var(--border); color: var(--text-soft); }
tr:hover { background: var(--surface-light); }
.actions a { margin-right: 10px; color: var(--text-hint); text-decoration: none; }
.actions .approve { color: var(--primary); }
.actions .delete { color: #ff6b6b; }
@media (max-width: 768px) { .admin-wrapper { flex-direction: column; } .sidebar { width: 100%; height: auto; position: static; } }
</style>
</head>
<body>
<div class="admin-wrapper">
<aside class="sidebar">
<div class="sidebar-header"><div class="logo">lv8girl</div><p>管理面板</p></div>
<ul class="sidebar-menu">
<li><a href="/admin">📊 仪表盘</a></li>
<li><a href="/admin/pending_posts" class="active">⏳ 待审核帖子</a></li>
<li><a href="/admin/pending_users">👥 待审核用户</a></li>
<li><a href="/admin/posts">📝 帖子管理</a></li>
<li><a href="/admin/users">👥 用户管理</a></li>
<li><a href="/admin/comments">💬 评论管理</a></li>
<li class="separator"></li>
<li><a href="/">🏠 返回首页</a></li>
</ul>
</aside>
<main class="main-content">
<div class="top-bar">
<h1 class="page-title">待审核帖子</h1>
<div class="user-info"><span>{{.Username}}</span></div>
</div>
{{if .Message}}<div class="message">{{.Message}}</div>{{end}}
<div class="table-card">
<table>
<thead><tr><th>ID</th><th>标题</th><th>作者</th><th>发布时间</th><th>操作</th></tr></thead>
<tbody>
{{range .Posts}}
<tr>
<td>{{.ID}}</td>
<td><a href="/post/{{.ID}}" target="_blank">{{.Title}}</a></td>
<td>{{.User.Username}}</td>
<td>{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
<td class="actions">
<a href="/admin/pending_posts/approve/{{.ID}}" class="approve" onclick="return confirm('通过审核?')">通过</a>
<a href="/admin/pending_posts/reject/{{.ID}}" class="delete" onclick="return confirm('拒绝审核?')">拒绝</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>待审核用户 · lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0f0f1a; --surface: #1a1a2f; --surface-light: #252540; --border: #2d2d4a;
--text: #e0e0f0; --text-soft: #b0b0d0; --text-hint: #8080a0;
--primary: #c5a572; --gradient: linear-gradient(135deg, #c5a572, #9a7e5a);
--sidebar-width: 220px;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', sans-serif; }
.admin-wrapper { display: flex; min-height: 100vh; }
.sidebar { width: var(--sidebar-width); background: var(--surface); border-right: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; height: 100vh; }
.sidebar-header { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.sidebar-header .logo { font-size: 1.6rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.sidebar-header p { color: var(--text-soft); font-size: 0.85rem; }
.sidebar-menu { list-style: none; }
.sidebar-menu li { margin: 5px 0; }
.sidebar-menu a { display: block; padding: 10px 20px; color: var(--text-soft); text-decoration: none; border-left: 4px solid transparent; }
.sidebar-menu a:hover, .sidebar-menu a.active { background: var(--surface-light); border-left-color: var(--primary); color: var(--primary); }
.sidebar-menu .separator { height: 1px; background: var(--border); margin: 15px 20px; }
.main-content { flex: 1; padding: 20px 30px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.page-title { font-size: 1.8rem; font-weight: 600; color: var(--primary); }
.user-info span { background: var(--surface-light); padding: 6px 16px; border-radius: 30px; }
.message { background: var(--surface-light); border-left: 4px solid var(--primary); padding: 12px 20px; margin-bottom: 20px; border-radius: 8px; }
.table-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
table { width: 100%; border-collapse: collapse; }
th { background: var(--surface-light); padding: 12px 15px; text-align: left; font-weight: 600; color: var(--primary); border-bottom: 1px solid var(--border); }
td { padding: 12px 15px; border-bottom: 1px solid var(--border); color: var(--text-soft); }
tr:hover { background: var(--surface-light); }
.actions a { margin-right: 10px; text-decoration: none; }
.actions .approve { color: var(--primary); }
.actions .delete { color: #ff6b6b; }
@media (max-width: 768px) { .admin-wrapper { flex-direction: column; } .sidebar { width: 100%; height: auto; position: static; } }
</style>
</head>
<body>
<div class="admin-wrapper">
<aside class="sidebar">
<div class="sidebar-header"><div class="logo">lv8girl</div><p>管理面板</p></div>
<ul class="sidebar-menu">
<li><a href="/admin">📊 仪表盘</a></li>
<li><a href="/admin/pending_posts">⏳ 待审核帖子</a></li>
<li><a href="/admin/pending_users" class="active">👥 待审核用户</a></li>
<li><a href="/admin/posts">📝 帖子管理</a></li>
<li><a href="/admin/users">👥 用户管理</a></li>
<li><a href="/admin/comments">💬 评论管理</a></li>
<li class="separator"></li>
<li><a href="/">🏠 返回首页</a></li>
</ul>
</aside>
<main class="main-content">
<div class="top-bar">
<h1 class="page-title">待审核用户</h1>
<div class="user-info"><span>{{.Username}}</span></div>
</div>
{{if .Message}}<div class="message">{{.Message}}</div>{{end}}
<div class="table-card">
<table>
<thead><tr><th>ID</th><th>用户名</th><th>邮箱</th><th>注册时间</th><th>操作</th></tr></thead>
<tbody>
{{range .Users}}
<tr>
<td>{{.ID}}</td>
<td>{{.Username}}</td>
<td>{{.Email}}</td>
<td>{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
<td class="actions">
<a href="/admin/pending_users/approve/{{.ID}}" class="approve" onclick="return confirm('通过审核?')">通过</a>
<a href="/admin/pending_users/reject/{{.ID}}" class="delete" onclick="return confirm('拒绝审核?')">拒绝</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>帖子管理 · lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0f0f1a; --surface: #1a1a2f; --surface-light: #252540; --border: #2d2d4a;
--text: #e0e0f0; --text-soft: #b0b0d0; --text-hint: #8080a0;
--primary: #c5a572; --gradient: linear-gradient(135deg, #c5a572, #9a7e5a);
--sidebar-width: 220px;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', sans-serif; }
.admin-wrapper { display: flex; min-height: 100vh; }
.sidebar { width: var(--sidebar-width); background: var(--surface); border-right: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; height: 100vh; }
.sidebar-header { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.sidebar-header .logo { font-size: 1.6rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.sidebar-header p { color: var(--text-soft); font-size: 0.85rem; }
.sidebar-menu { list-style: none; }
.sidebar-menu li { margin: 5px 0; }
.sidebar-menu a { display: block; padding: 10px 20px; color: var(--text-soft); text-decoration: none; border-left: 4px solid transparent; }
.sidebar-menu a:hover, .sidebar-menu a.active { background: var(--surface-light); border-left-color: var(--primary); color: var(--primary); }
.sidebar-menu .separator { height: 1px; background: var(--border); margin: 15px 20px; }
.main-content { flex: 1; padding: 20px 30px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.page-title { font-size: 1.8rem; font-weight: 600; color: var(--primary); }
.user-info span { background: var(--surface-light); padding: 6px 16px; border-radius: 30px; }
.message { background: var(--surface-light); border-left: 4px solid var(--primary); padding: 12px 20px; margin-bottom: 20px; border-radius: 8px; }
.table-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; min-width: 800px; }
th { background: var(--surface-light); padding: 12px 15px; text-align: left; font-weight: 600; color: var(--primary); border-bottom: 1px solid var(--border); }
td { padding: 12px 15px; border-bottom: 1px solid var(--border); color: var(--text-soft); }
tr:hover { background: var(--surface-light); }
.actions a { margin-right: 10px; color: var(--text-hint); text-decoration: none; }
.actions .delete { color: #ff6b6b; }
.status-approved { color: var(--primary); }
.status-pending { color: #ffb347; }
.status-rejected { color: #ff6b6b; }
@media (max-width: 768px) { .admin-wrapper { flex-direction: column; } .sidebar { width: 100%; height: auto; position: static; } }
</style>
</head>
<body>
<div class="admin-wrapper">
<aside class="sidebar">
<div class="sidebar-header"><div class="logo">lv8girl</div><p>管理面板</p></div>
<ul class="sidebar-menu">
<li><a href="/admin">📊 仪表盘</a></li>
<li><a href="/admin/pending_posts">⏳ 待审核帖子</a></li>
<li><a href="/admin/pending_users">👥 待审核用户</a></li>
<li><a href="/admin/posts" class="active">📝 帖子管理</a></li>
<li><a href="/admin/users">👥 用户管理</a></li>
<li><a href="/admin/comments">💬 评论管理</a></li>
<li class="separator"></li>
<li><a href="/">🏠 返回首页</a></li>
</ul>
</aside>
<main class="main-content">
<div class="top-bar">
<h1 class="page-title">帖子管理</h1>
<div class="user-info"><span>{{.Username}}</span></div>
</div>
{{if .Message}}<div class="message">{{.Message}}</div>{{end}}
<div class="table-card">
<table>
<thead><tr><th>ID</th><th>标题</th><th>作者</th><th>状态</th><th>发布时间</th><th>阅读</th><th>点赞</th><th>评论</th><th>操作</th></tr></thead>
<tbody>
{{range .Posts}}
<tr>
<td>{{.ID}}</td>
<td><a href="/post/{{.ID}}" target="_blank">{{.Title}}</a></td>
<td>{{.User.Username}}</td>
<td><span class="status-{{.Status}}">{{if eq .Status "approved"}}已通过{{else if eq .Status "pending"}}待审核{{else}}已拒绝{{end}}</span></td>
<td>{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
<td>{{.Views}}</td>
<td>{{.LikeCount}}</td>
<td>{{.CommentCount}}</td>
<td class="actions">
<a href="/admin/posts/delete/{{.ID}}" class="delete" onclick="return confirm('确定删除?')">删除</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</main>
</div>
</body>
</html>

109
templates/admin_users.html Normal file
View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户管理 · lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0f0f1a; --surface: #1a1a2f; --surface-light: #252540; --border: #2d2d4a;
--text: #e0e0f0; --text-soft: #b0b0d0; --text-hint: #8080a0;
--primary: #c5a572; --gradient: linear-gradient(135deg, #c5a572, #9a7e5a);
--sidebar-width: 220px;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', sans-serif; }
.admin-wrapper { display: flex; min-height: 100vh; }
.sidebar { width: var(--sidebar-width); background: var(--surface); border-right: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; height: 100vh; }
.sidebar-header { padding: 0 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.sidebar-header .logo { font-size: 1.6rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.sidebar-header p { color: var(--text-soft); font-size: 0.85rem; }
.sidebar-menu { list-style: none; }
.sidebar-menu li { margin: 5px 0; }
.sidebar-menu a { display: block; padding: 10px 20px; color: var(--text-soft); text-decoration: none; border-left: 4px solid transparent; }
.sidebar-menu a:hover, .sidebar-menu a.active { background: var(--surface-light); border-left-color: var(--primary); color: var(--primary); }
.sidebar-menu .separator { height: 1px; background: var(--border); margin: 15px 20px; }
.main-content { flex: 1; padding: 20px 30px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.page-title { font-size: 1.8rem; font-weight: 600; color: var(--primary); }
.user-info span { background: var(--surface-light); padding: 6px 16px; border-radius: 30px; }
.message { background: var(--surface-light); border-left: 4px solid var(--primary); padding: 12px 20px; margin-bottom: 20px; border-radius: 8px; }
.table-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; min-width: 900px; }
th { background: var(--surface-light); padding: 12px 15px; text-align: left; font-weight: 600; color: var(--primary); border-bottom: 1px solid var(--border); }
td { padding: 12px 15px; border-bottom: 1px solid var(--border); color: var(--text-soft); }
tr:hover { background: var(--surface-light); }
.actions a { margin-right: 10px; color: var(--text-hint); text-decoration: none; }
.actions .delete { color: #ff6b6b; }
select { background: var(--surface-light); border: 1px solid var(--border); padding: 5px 10px; border-radius: 8px; color: var(--text); }
.status-approved { color: var(--primary); }
.status-pending { color: #ffb347; }
.status-rejected { color: #ff6b6b; }
@media (max-width: 768px) { .admin-wrapper { flex-direction: column; } .sidebar { width: 100%; height: auto; position: static; } }
</style>
</head>
<body>
<div class="admin-wrapper">
<aside class="sidebar">
<div class="sidebar-header"><div class="logo">lv8girl</div><p>管理面板</p></div>
<ul class="sidebar-menu">
<li><a href="/admin">📊 仪表盘</a></li>
<li><a href="/admin/pending_posts">⏳ 待审核帖子</a></li>
<li><a href="/admin/pending_users">👥 待审核用户</a></li>
<li><a href="/admin/posts">📝 帖子管理</a></li>
<li><a href="/admin/users" class="active">👥 用户管理</a></li>
<li><a href="/admin/comments">💬 评论管理</a></li>
<li class="separator"></li>
<li><a href="/">🏠 返回首页</a></li>
</ul>
</aside>
<main class="main-content">
<div class="top-bar">
<h1 class="page-title">用户管理</h1>
<div class="user-info"><span>{{.Username}}</span></div>
</div>
{{if .Message}}<div class="message">{{.Message}}</div>{{end}}
<div class="table-card">
<table>
<thead><tr><th>ID</th><th>用户名</th><th>邮箱</th><th>角色</th><th>状态</th><th>注册时间</th><th>最后活动</th><th>帖子</th><th>评论</th><th>操作</th></tr></thead>
<tbody>
{{range .Users}}
<tr>
<td>{{.ID}}</td>
<td>{{.Username}}</td>
<td>{{.Email}}</td>
<td>
{{if eq .ID $.CurrentUserID}}
{{if eq .Role "admin"}}管理员{{else if eq .Role "banned"}}封禁{{else}}用户{{end}}
{{else}}
<form method="post" action="/admin/users/role" style="display:inline;">
<input type="hidden" name="user_id" value="{{.ID}}">
<select name="new_role" onchange="this.form.submit()">
<option value="user" {{if eq .Role "user"}}selected{{end}}>用户</option>
<option value="admin" {{if eq .Role "admin"}}selected{{end}}>管理员</option>
<option value="banned" {{if eq .Role "banned"}}selected{{end}}>封禁</option>
</select>
</form>
{{end}}
</td>
<td><span class="status-{{.Status}}">{{if eq .Status "approved"}}已通过{{else if eq .Status "pending"}}待审核{{else}}已拒绝{{end}}</span></td>
<td>{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
<td>{{if .LastActive}}{{.LastActive.Format "2006-01-02 15:04"}}{{else}}从未{{end}}</td>
<td>{{.PostCount}}</td>
<td>{{.CommentCount}}</td>
<td class="actions">
{{if ne .ID $.CurrentUserID}}
<a href="/admin/users/delete/{{.ID}}" class="delete" onclick="return confirm('确定删除?')">删除</a>
{{else}}
<span style="color:var(--text-hint)">当前用户</span>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</main>
</div>
</body>
</html>

191
templates/index.html Normal file
View File

@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lv8girl · 绿坝娘二次元论坛</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--border-light: #e0e8e0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--primary-light: #6abf6e;
--accent: #ffb347;
--accent-dark: #d9a066;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--border-light: #3a4a3a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--primary-light: #8aad8a;
--accent: #ffb347;
--accent-dark: #d9a066;
--gradient: linear-gradient(135deg, #6b8e6b, #5a9cff);
}
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
transition: background 0.3s, color 0.3s;
}
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.header { margin-bottom: 30px; border-bottom: 1px solid var(--border); padding-bottom: 20px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.user-area { display: flex; align-items: center; gap: 15px; }
.user-area a { color: var(--text-soft); text-decoration: none; padding: 6px 16px; border-radius: 30px; background: var(--surface-light); transition: 0.2s; }
.user-area a:hover { background: var(--primary); color: white; }
.user-menu { position: relative; cursor: pointer; }
.user-name { display: flex; align-items: center; gap: 5px; background: var(--surface-light); padding: 6px 16px; border-radius: 30px; color: var(--text); }
.dropdown { position: absolute; top: 120%; right: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; min-width: 140px; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: 0.2s; z-index: 100; }
.user-menu:hover .dropdown { opacity: 1; visibility: visible; transform: translateY(0); }
.dropdown a { display: block; padding: 10px 16px; color: var(--text); text-decoration: none; border-bottom: 1px solid var(--border); background: transparent; }
.dropdown a:last-child { border-bottom: none; }
.dropdown a:hover { background: var(--surface-light); }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.welcome-message { background: var(--surface-light); padding: 15px 20px; border-radius: 12px; margin-bottom: 20px; border-left: 5px solid var(--accent); }
.welcome-message p { color: var(--text-soft); }
.welcome-message a { color: var(--accent); text-decoration: none; }
.qq-group { background: var(--surface-light); padding: 10px 20px; border-radius: 12px; display: inline-block; margin-bottom: 20px; border: 1px solid var(--border); }
.main-layout { display: flex; gap: 30px; }
.content-left { flex: 2; }
.content-right { flex: 1; }
.post-list { background: var(--surface); border-radius: 12px; border: 1px solid var(--border); overflow: hidden; }
.post-item { display: flex; padding: 20px; border-bottom: 1px solid var(--border); transition: 0.2s; }
.post-item:hover { background: var(--surface-light); }
.post-avatar { width: 50px; height: 50px; border-radius: 50%; background: var(--gradient); margin-right: 20px; flex-shrink: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; }
.post-avatar img { width: 100%; height: 100%; object-fit: cover; }
.post-content { flex: 1; }
.post-header { display: flex; align-items: center; gap: 15px; margin-bottom: 8px; flex-wrap: wrap; }
.post-author { font-weight: 600; color: var(--accent); }
.post-time { color: var(--text-hint); font-size: 0.85rem; }
.post-title { font-size: 1.3rem; font-weight: 700; margin-bottom: 8px; }
.post-title a { color: var(--text); text-decoration: none; }
.post-title a:hover { color: var(--accent); }
.post-excerpt { color: var(--text-soft); margin-bottom: 12px; font-size: 0.95rem; }
.post-meta { display: flex; gap: 20px; color: var(--text-hint); font-size: 0.85rem; }
.stats-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
.stats-header { margin-bottom: 15px; border-bottom: 1px solid var(--border); padding-bottom: 10px; }
.stats-title { font-size: 1.1rem; font-weight: 600; color: var(--accent); }
.stats-grid { display: flex; justify-content: space-around; text-align: center; }
.stat-item { flex: 1; }
.stat-number { font-size: 1.8rem; font-weight: 700; color: var(--primary); line-height: 1.2; }
.stat-label { color: var(--text-hint); font-size: 0.9rem; }
.footer { margin-top: 40px; padding: 20px 0; border-top: 1px solid var(--border); text-align: center; color: var(--text-hint); font-size: 0.9rem; }
.footer a { color: var(--text-hint); text-decoration: none; margin: 0 10px; }
.footer a:hover { color: var(--accent); }
@media (max-width: 800px) {
.main-layout { flex-direction: column; }
.top-bar { flex-direction: column; gap: 15px; align-items: flex-start; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="top-bar">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<div class="user-area">
{{if .IsLoggedIn}}
<div class="user-menu">
<span class="user-name">{{.Username}} ▼</span>
<div class="dropdown">
{{if eq .UserRole "admin"}}<a href="/admin">管理面板</a>{{end}}
<a href="/profile">个人主页</a>
<a href="/new-post">发表新帖</a>
<a href="/messages">私信{{if gt .UnreadCount 0}}<span style="background:#ff6b6b;color:white;border-radius:50%;padding:2px 6px;font-size:0.7rem;margin-left:5px;">{{.UnreadCount}}</span>{{end}}</a>
<a href="/logout">登出</a>
</div>
</div>
{{else}}
<a href="/login">登录</a>
<a href="/register">注册</a>
{{end}}
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
</div>
<div class="welcome-message">
<p>欢迎来到 lv8girl 论坛,一个 ACG 爱好者的聚集地。</p>
</div>
<div class="qq-group">🍀 绿坝娘 · 守护你的二次元</div>
</div>
<div class="main-layout">
<div class="content-left">
<div class="post-list">
{{if not .Posts}}
<div style="padding: 40px; text-align: center; color: var(--text-hint);">暂无帖子,快去发表第一篇吧!</div>
{{else}}
{{range .Posts}}
<div class="post-item">
<a href="/profile/{{.UserID}}" class="post-avatar">
{{if .Avatar}}<img src="{{.Avatar}}" alt="avatar">{{else}}<span>{{slice .Username 0 1}}</span>{{end}}
</a>
<div class="post-content">
<div class="post-header">
<span class="post-author">{{.Username}}</span>
<span class="post-time">{{.CreatedAt.Format "2006-01-02 15:04"}}</span>
</div>
<div class="post-title"><a href="/post/{{.ID}}">{{.Title}}</a></div>
<div class="post-excerpt">{{slice .Content 0 100}}{{if gt (len .Content) 100}}...{{end}}</div>
<div class="post-meta">
<span>👍 {{.LikeCount}}</span>
<span>💬 {{.CommentCount}}</span>
<span>👁️ {{.Views}}</span>
</div>
</div>
</div>
{{end}}
{{end}}
</div>
</div>
<div class="content-right">
<div class="stats-card">
<div class="stats-header"><span class="stats-title">📊 论坛统计</span></div>
<div class="stats-grid">
<div class="stat-item"><div class="stat-number">{{.PostCount}}</div><div class="stat-label">帖子总数</div></div>
<div class="stat-item"><div class="stat-number">{{.UserCount}}</div><div class="stat-label">注册用户</div></div>
<div class="stat-item"><div class="stat-number">{{.OnlineCount}}</div><div class="stat-label">实时在线</div></div>
</div>
</div>
</div>
</div>
<div class="footer">
<div>© 2025 lv8girl · 绿坝娘二次元论坛</div>
<div>
<a href="#">关于</a>
<a href="#">帮助</a>
<a href="#">隐私</a>
<a href="#">投稿</a>
<a href="https://icp.gov.moe/?keyword=20260911" target="_blank">萌ICP备20260911号</a>
</div>
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
</script>
</body>
</html>

84
templates/login.html Normal file
View File

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lv8girl · 登录</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--accent: #ffb347;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--accent: #ffb347;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; transition: background 0.3s, color 0.3s; }
.login-wrapper { max-width: 400px; width: 100%; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 30px 25px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); }
.card h2 { text-align: center; margin-bottom: 25px; color: var(--accent); font-weight: 600; }
.error-message { background: var(--surface-light); border-left: 4px solid #ff6b6b; color: var(--text-soft); padding: 12px 15px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9rem; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 0.9rem; }
input { width: 100%; padding: 12px 15px; background: var(--surface-light); border: 1px solid var(--border); border-radius: 30px; color: var(--text); font-size: 1rem; outline: none; transition: border 0.2s; }
input:focus { border-color: var(--accent); }
.btn { width: 100%; padding: 14px; background: var(--gradient); border: none; border-radius: 40px; color: white; font-weight: 600; font-size: 1rem; cursor: pointer; transition: transform 0.2s; margin-top: 10px; }
.btn:hover { transform: scale(1.02); }
.footer-text { text-align: center; margin-top: 25px; color: var(--text-hint); }
.footer-text a { color: var(--accent); text-decoration: none; font-weight: 500; }
.footer-text a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="login-wrapper">
<div class="header">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
<div class="card">
<h2>登录</h2>
{{if .Error}}<div class="error-message">{{.Error}}</div>{{end}}
<form method="post">
<div class="form-group">
<label>用户名 / 邮箱</label>
<input type="text" name="login" placeholder="请输入用户名或邮箱" required>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" placeholder="请输入密码" required>
</div>
<button type="submit" class="btn">登 录</button>
</form>
<div class="footer-text">还没有账号? <a href="/register">立即注册</a></div>
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
</script>
</body>
</html>

109
templates/messages.html Normal file
View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>私信列表 - lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--accent: #ffb347;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--accent: #ffb347;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 1.6; transition: background 0.3s, color 0.3s; }
.container { max-width: 800px; margin: 0 auto; padding: 20px; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; border-bottom: 1px solid var(--border); padding-bottom: 20px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.nav-right { display: flex; align-items: center; gap: 15px; }
.nav-right a { color: var(--text-soft); text-decoration: none; padding: 6px 16px; border-radius: 30px; background: var(--surface-light); transition: 0.2s; }
.nav-right a:hover { background: var(--primary); color: white; }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.page-title { font-size: 1.8rem; font-weight: 600; color: var(--accent); margin-bottom: 20px; }
.conversation-list { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.conversation-item { display: flex; align-items: center; padding: 15px 20px; border-bottom: 1px solid var(--border-light); text-decoration: none; color: var(--text); transition: 0.2s; }
.conversation-item:hover { background: var(--surface-light); }
.conversation-item:last-child { border-bottom: none; }
.avatar { width: 50px; height: 50px; border-radius: 50%; background: var(--gradient); margin-right: 15px; overflow: hidden; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; flex-shrink: 0; }
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.conversation-info { flex: 1; min-width: 0; }
.username { font-weight: 600; color: var(--accent); margin-bottom: 4px; }
.last-msg { color: var(--text-soft); font-size: 0.9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 300px; }
.time { color: var(--text-hint); font-size: 0.8rem; margin-left: 10px; white-space: nowrap; }
.unread-badge { background: #ff6b6b; color: white; border-radius: 50%; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; margin-left: 10px; flex-shrink: 0; }
.empty-message { padding: 40px; text-align: center; color: var(--text-hint); }
.footer { margin-top: 40px; padding: 20px 0; border-top: 1px solid var(--border); text-align: center; color: var(--text-hint); font-size: 0.9rem; }
.footer a { color: var(--text-hint); text-decoration: none; margin: 0 10px; }
.footer a:hover { color: var(--accent); }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<div class="nav-right">
<a href="/">首页</a>
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
</div>
<h1 class="page-title">私信列表</h1>
<div class="conversation-list">
{{if not .Conversations}}
<div class="empty-message">暂无对话,快去给感兴趣的用户发送私信吧~</div>
{{else}}
{{range .Conversations}}
<div class="conversation-item">
<div class="avatar">{{if .Avatar}}<img src="{{.Avatar}}" alt="">{{else}}<span>{{slice .Username 0 1}}</span>{{end}}</div>
<div class="conversation-info">
<div class="username">{{.Username}}</div>
<div class="last-msg">{{slice .LastMsg 0 50}}{{if gt (len .LastMsg) 50}}...{{end}}</div>
</div>
<div class="time">{{.Time.Format "01-02 15:04"}}</div>
{{if gt .Unread 0}}<div class="unread-badge">{{.Unread}}</div>{{end}}
</div>
{{end}}
{{end}}
</div>
<div class="footer">
<div>© 2025 lv8girl · 绿坝娘二次元论坛</div>
<div>
<a href="#">关于</a>
<a href="#">帮助</a>
<a href="#">隐私</a>
<a href="#">投稿</a>
<a href="https://icp.gov.moe/?keyword=20260911" target="_blank">萌ICP备20260911号</a>
</div>
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
</script>
</body>
</html>

228
templates/post.html Normal file
View File

@@ -0,0 +1,228 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Post.Title}} - lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--accent: #ffb347;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--accent: #ffb347;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 1.6; transition: background 0.3s, color 0.3s; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; border-bottom: 1px solid var(--border); padding-bottom: 20px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.nav-right { display: flex; align-items: center; gap: 15px; }
.nav-right a { color: var(--text-soft); text-decoration: none; padding: 6px 16px; border-radius: 30px; background: var(--surface-light); transition: 0.2s; }
.nav-right a:hover { background: var(--primary); color: white; }
.user-menu { position: relative; cursor: pointer; }
.user-name { display: flex; align-items: center; gap: 5px; background: var(--surface-light); padding: 6px 16px; border-radius: 30px; color: var(--text); }
.dropdown { position: absolute; top: 120%; right: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; min-width: 140px; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: 0.2s; z-index: 100; }
.user-menu:hover .dropdown { opacity: 1; visibility: visible; transform: translateY(0); }
.dropdown a { display: block; padding: 10px 16px; color: var(--text); text-decoration: none; border-bottom: 1px solid var(--border); background: transparent; }
.dropdown a:last-child { border-bottom: none; }
.dropdown a:hover { background: var(--surface-light); }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.main-layout { display: flex; gap: 30px; }
.content-left { flex: 2; }
.content-right { flex: 1; }
.post-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 25px; margin-bottom: 30px; }
.post-header { display: flex; align-items: center; gap: 15px; margin-bottom: 15px; }
.post-author-avatar { width: 50px; height: 50px; border-radius: 50%; background: var(--gradient); overflow: hidden; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; }
.post-author-avatar img { width: 100%; height: 100%; object-fit: cover; }
.post-meta { flex: 1; }
.post-author-name { font-size: 1.1rem; font-weight: 600; color: var(--accent); text-decoration: none; }
.post-author-name:hover { text-decoration: underline; }
.post-time { color: var(--text-hint); font-size: 0.85rem; }
.post-title { font-size: 2rem; font-weight: 700; margin-bottom: 15px; color: var(--text); }
.post-image { margin: 20px 0; max-width: 100%; border-radius: 12px; overflow: hidden; }
.post-image img { width: 100%; max-height: 500px; object-fit: contain; background: var(--surface-light); }
.post-content { color: var(--text); font-size: 1.1rem; line-height: 1.7; margin-bottom: 20px; white-space: pre-wrap; }
.post-actions { display: flex; align-items: center; gap: 20px; padding: 15px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.like-btn { background: var(--gradient); border: none; border-radius: 40px; padding: 8px 25px; color: white; font-weight: 600; cursor: pointer; transition: 0.2s; display: inline-flex; align-items: center; gap: 8px; text-decoration: none; }
.like-btn:hover { transform: scale(1.02); }
.like-btn.liked { background: #ff6b6b; }
.like-count { font-size: 1.1rem; font-weight: 600; color: var(--text); }
.view-count { margin-left: auto; color: var(--text-hint); font-size: 0.95rem; }
.author-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 20px; text-align: center; }
.author-avatar-large { width: 80px; height: 80px; border-radius: 50%; background: var(--gradient); margin: 0 auto 15px; overflow: hidden; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: 600; }
.author-avatar-large img { width: 100%; height: 100%; object-fit: cover; }
.author-name { font-size: 1.3rem; font-weight: 600; color: var(--accent); margin-bottom: 5px; }
.author-meta { color: var(--text-hint); font-size: 0.9rem; margin-bottom: 5px; }
.author-uid { color: var(--text-soft); font-size: 0.85rem; margin-bottom: 10px; }
.author-stats { display: flex; justify-content: center; gap: 20px; margin: 15px 0; padding-top: 15px; border-top: 1px solid var(--border); }
.author-stat-item { text-align: center; }
.author-stat-number { font-size: 1.3rem; font-weight: 700; color: var(--primary); }
.author-stat-label { color: var(--text-hint); font-size: 0.8rem; }
.view-profile { display: inline-block; background: var(--gradient); color: white; text-decoration: none; padding: 8px 20px; border-radius: 40px; font-weight: 600; transition: 0.2s; }
.view-profile:hover { transform: scale(1.02); }
.comments-section { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 25px; margin-bottom: 30px; }
.comments-title { font-size: 1.5rem; font-weight: 600; color: var(--accent); margin-bottom: 20px; }
.comment-form { margin-bottom: 30px; }
.comment-form textarea { width: 100%; background: var(--surface-light); border: 1px solid var(--border); border-radius: 12px; padding: 15px; font-size: 1rem; color: var(--text); resize: vertical; min-height: 100px; margin-bottom: 10px; }
.comment-form button { background: var(--gradient); border: none; border-radius: 40px; padding: 10px 30px; color: white; font-weight: 600; cursor: pointer; transition: 0.2s; }
.comment-form button:hover { transform: scale(1.02); }
.comment-item { display: flex; gap: 15px; padding: 20px 0; border-bottom: 1px solid var(--border); }
.comment-item:last-child { border-bottom: none; }
.comment-avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--gradient); overflow: hidden; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; }
.comment-avatar img { width: 100%; height: 100%; object-fit: cover; }
.comment-content { flex: 1; }
.comment-header { display: flex; align-items: center; gap: 15px; margin-bottom: 5px; }
.comment-author { font-weight: 600; color: var(--accent); text-decoration: none; }
.comment-author:hover { text-decoration: underline; }
.comment-time { color: var(--text-hint); font-size: 0.8rem; }
.comment-text { color: var(--text-soft); line-height: 1.5; }
.no-comments { text-align: center; color: var(--text-hint); padding: 30px 0; }
.login-prompt { text-align: center; color: var(--text-hint); margin-bottom: 20px; }
.login-prompt a { color: var(--accent); text-decoration: none; }
.footer { margin-top: 40px; padding: 20px 0; border-top: 1px solid var(--border); text-align: center; color: var(--text-hint); font-size: 0.9rem; }
.footer a { color: var(--text-hint); text-decoration: none; margin: 0 10px; }
.footer a:hover { color: var(--accent); }
@media (max-width: 800px) { .main-layout { flex-direction: column; } }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<div class="nav-right">
{{if .IsLoggedIn}}
<div class="user-menu">
<span class="user-name">{{.Username}} ▼</span>
<div class="dropdown">
{{if eq .UserRole "admin"}}<a href="/admin">管理面板</a>{{end}}
<a href="/profile">个人主页</a>
<a href="/new-post">发表新帖</a>
<a href="/messages">私信</a>
<a href="/logout">登出</a>
</div>
</div>
{{else}}
<a href="/login">登录</a>
<a href="/register">注册</a>
{{end}}
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
</div>
<div class="main-layout">
<div class="content-left">
<div class="post-card">
<div class="post-header">
<a href="/profile/{{.Post.UserID}}" class="post-author-avatar">
{{if .PostAvatar}}<img src="{{.PostAvatar}}" alt="avatar">{{else}}<span>{{slice .Post.User.Username 0 1}}</span>{{end}}
</a>
<div class="post-meta">
<a href="/profile/{{.Post.UserID}}" class="post-author-name">{{.Post.User.Username}}</a>
<div class="post-time">{{.Post.CreatedAt.Format "2006-01-02 15:04"}}</div>
</div>
</div>
<h1 class="post-title">{{.Post.Title}}</h1>
{{if .Post.ImagePath}}<div class="post-image"><img src="/{{.Post.ImagePath}}" alt="post image"></div>{{end}}
<div class="post-content">{{.Post.Content}}</div>
<div class="post-actions">
{{if .IsLoggedIn}}
<form method="post" action="/post/{{.Post.ID}}/like" style="display: inline;">
<button type="submit" class="like-btn {{if .UserLiked}}liked{{end}}">{{if .UserLiked}}✓ 已点赞{{else}}👍 点赞{{end}}</button>
</form>
{{else}}
<a href="/login" class="like-btn">👍 登录后点赞</a>
{{end}}
<span class="like-count">{{.LikeCount}} 人点赞</span>
<span class="view-count">👁️ {{.Post.Views}} 次阅读</span>
</div>
</div>
<div class="comments-section">
<h2 class="comments-title">评论 ({{len .Comments}})</h2>
{{if .IsLoggedIn}}
<form method="post" action="/post/{{.Post.ID}}/comment" class="comment-form">
<textarea name="content" placeholder="写下你的评论..." required></textarea>
<button type="submit">发表评论</button>
</form>
{{else}}
<div class="login-prompt"><a href="/login">登录</a> 后即可评论</div>
{{end}}
{{if not .Comments}}
<div class="no-comments">暂无评论,快来抢沙发吧~</div>
{{else}}
{{range .Comments}}
<div class="comment-item">
<a href="/profile/{{.UserID}}" class="comment-avatar">
{{if index $.CommentAvatars .UserID}}<img src="{{index $.CommentAvatars .UserID}}" alt="avatar">{{else}}<span>{{slice .User.Username 0 1}}</span>{{end}}
</a>
<div class="comment-content">
<div class="comment-header">
<a href="/profile/{{.UserID}}" class="comment-author">{{.User.Username}}</a>
<span class="comment-time">{{.CreatedAt.Format "2006-01-02 15:04"}}</span>
</div>
<div class="comment-text">{{.Content}}</div>
</div>
</div>
{{end}}
{{end}}
</div>
</div>
<div class="content-right">
<div class="author-card">
<div class="author-avatar-large">
{{if .PostAvatar}}<img src="{{.PostAvatar}}" alt="avatar">{{else}}<span>{{slice .Post.User.Username 0 1}}</span>{{end}}
</div>
<div class="author-name">{{.Post.User.Username}}</div>
<div class="author-meta">身份:{{if eq .Post.User.Role "admin"}}管理员{{else}}用户{{end}}</div>
<div class="author-uid">UID: {{.Post.UserID}}</div>
<div class="author-stats">
<div class="author-stat-item">
<div class="author-stat-number">{{.AuthorPostCount}}</div>
<div class="author-stat-label">帖子</div>
</div>
</div>
<a href="/profile/{{.Post.UserID}}" class="view-profile">查看个人主页</a>
</div>
</div>
</div>
<div class="footer">
<div>© 2025 lv8girl · 绿坝娘二次元论坛</div>
<div>
<a href="#">关于</a>
<a href="#">帮助</a>
<a href="#">隐私</a>
<a href="#">投稿</a>
<a href="https://icp.gov.moe/?keyword=20260911" target="_blank">萌ICP备20260911号</a>
</div>
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
</script>
</body>
</html>

View File

@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发表新帖 - lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--accent: #ffb347;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--accent: #ffb347;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 1.6; transition: background 0.3s, color 0.3s; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
.post-wrapper { max-width: 800px; width: 100%; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.post-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); }
.post-card h2 { font-size: 1.8rem; font-weight: 600; color: var(--accent); margin-bottom: 25px; text-align: center; }
.error-message { background: var(--surface-light); border-left: 4px solid #ff6b6b; color: var(--text-soft); padding: 12px 15px; border-radius: 8px; margin-bottom: 20px; }
.success-message { background: var(--surface-light); border-left: 4px solid var(--primary); color: var(--text-soft); padding: 12px 15px; border-radius: 8px; margin-bottom: 20px; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; color: var(--text-soft); font-weight: 500; }
input[type="text"], textarea { width: 100%; padding: 12px 15px; background: var(--surface-light); border: 1px solid var(--border); border-radius: 30px; color: var(--text); font-size: 1rem; outline: none; transition: border 0.2s; }
textarea { border-radius: 20px; resize: vertical; min-height: 150px; }
input:focus, textarea:focus { border-color: var(--accent); }
input[type="file"] { background: var(--surface-light); border: 1px solid var(--border); border-radius: 30px; padding: 10px 15px; width: 100%; color: var(--text); }
.file-note { color: var(--text-hint); font-size: 0.85rem; margin-top: 5px; }
.btn { background: var(--gradient); border: none; border-radius: 40px; padding: 14px 30px; color: white; font-weight: 600; font-size: 1rem; cursor: pointer; transition: transform 0.2s; width: 100%; margin-top: 10px; }
.btn:hover { transform: scale(1.02); }
.footer-links { margin-top: 20px; text-align: center; }
.footer-links a { color: var(--text-hint); text-decoration: none; margin: 0 10px; }
.footer-links a:hover { color: var(--accent); }
</style>
</head>
<body>
<div class="post-wrapper">
<div class="header">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
<div class="post-card">
<h2>发表新帖</h2>
{{if .Error}}<div class="error-message">{{.Error}}</div>{{end}}
{{if .Success}}
<div class="success-message">{{.Success}}</div>
{{else}}
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label>标题</label>
<input type="text" name="title" placeholder="请输入标题" required>
</div>
<div class="form-group">
<label>内容</label>
<textarea name="content" placeholder="请输入帖子内容..." required></textarea>
</div>
<div class="form-group">
<label>上传图片可选不超过2MB</label>
<input type="file" name="image" accept="image/*">
<div class="file-note">支持 JPEG、PNG、GIF、WEBP 格式</div>
</div>
<button type="submit" class="btn">发 布</button>
</form>
{{end}}
<div class="footer-links">
<a href="/">返回首页</a>
<a href="/profile">个人主页</a>
</div>
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
</script>
</body>
</html>

195
templates/profile.html Normal file
View File

@@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.User.Username}}的个人主页 - lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--accent: #ffb347;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--accent: #ffb347;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 1.6; transition: background 0.3s, color 0.3s; }
.container { max-width: 1000px; margin: 0 auto; padding: 20px; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; border-bottom: 1px solid var(--border); padding-bottom: 20px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.nav-right { display: flex; align-items: center; gap: 15px; }
.nav-right a { color: var(--text-soft); text-decoration: none; padding: 6px 16px; border-radius: 30px; background: var(--surface-light); transition: 0.2s; }
.nav-right a:hover { background: var(--primary); color: white; }
.user-menu { position: relative; cursor: pointer; }
.user-name { display: flex; align-items: center; gap: 5px; background: var(--surface-light); padding: 6px 16px; border-radius: 30px; color: var(--text); }
.dropdown { position: absolute; top: 120%; right: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; min-width: 160px; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: 0.2s; z-index: 100; }
.user-menu:hover .dropdown { opacity: 1; visibility: visible; transform: translateY(0); }
.dropdown a { display: block; padding: 10px 16px; color: var(--text); text-decoration: none; border-bottom: 1px solid var(--border); }
.dropdown a:last-child { border-bottom: none; }
.dropdown a:hover { background: var(--surface-light); }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.success-message, .error-message { padding: 12px 20px; border-radius: 8px; margin-bottom: 20px; }
.success-message { background: var(--primary); color: white; }
.error-message { background: #ff6b6b; color: white; }
.profile-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 25px; margin-bottom: 30px; display: flex; gap: 25px; flex-wrap: wrap; }
.profile-avatar { width: 100px; height: 100px; border-radius: 50%; background: var(--gradient); overflow: hidden; display: flex; align-items: center; justify-content: center; color: white; font-size: 2.5rem; font-weight: 600; flex-shrink: 0; }
.profile-avatar img { width: 100%; height: 100%; object-fit: cover; }
.profile-info { flex: 1; }
.profile-info h1 { font-size: 2rem; margin-bottom: 10px; color: var(--accent); }
.profile-info p { color: var(--text-soft); margin-bottom: 5px; }
.profile-stats { display: flex; gap: 30px; margin-top: 15px; }
.stat-item { text-align: center; }
.stat-number { font-size: 1.5rem; font-weight: 700; color: var(--primary); }
.stat-label { color: var(--text-hint); font-size: 0.85rem; }
.edit-btn { display: inline-block; background: var(--gradient); color: white; border: none; border-radius: 30px; padding: 8px 25px; font-weight: 600; cursor: pointer; transition: 0.2s; margin-top: 15px; text-decoration: none; }
.edit-btn:hover { transform: scale(1.02); }
.avatar-upload-form { margin-top: 15px; padding: 15px; background: var(--surface-light); border-radius: 12px; border: 1px dashed var(--border); }
.file-input { margin-bottom: 10px; }
.file-input input[type="file"] { background: var(--surface); border: 1px solid var(--border); border-radius: 30px; padding: 8px 16px; width: 100%; color: var(--text); }
.section-title { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.section-title h2 { font-size: 1.5rem; font-weight: 600; color: var(--accent); }
.post-list { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.post-item { display: flex; padding: 20px; border-bottom: 1px solid var(--border); transition: 0.2s; }
.post-item:hover { background: var(--surface-light); }
.post-avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--gradient); margin-right: 15px; flex-shrink: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; }
.post-avatar img { width: 100%; height: 100%; object-fit: cover; }
.post-content { flex: 1; }
.post-header { display: flex; align-items: center; gap: 15px; margin-bottom: 5px; flex-wrap: wrap; }
.post-author { font-weight: 600; color: var(--accent); }
.post-time { color: var(--text-hint); font-size: 0.85rem; }
.post-title { font-size: 1.2rem; font-weight: 600; margin-bottom: 5px; }
.post-title a { color: var(--text); text-decoration: none; }
.post-title a:hover { color: var(--accent); }
.post-excerpt { color: var(--text-soft); font-size: 0.95rem; margin-bottom: 10px; }
.no-posts { padding: 40px; text-align: center; color: var(--text-hint); }
.footer { margin-top: 40px; padding: 20px 0; border-top: 1px solid var(--border); text-align: center; color: var(--text-hint); font-size: 0.9rem; }
.footer a { color: var(--text-hint); text-decoration: none; margin: 0 10px; }
.footer a:hover { color: var(--accent); }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<div class="nav-right">
{{if .IsLoggedIn}}
<a href="/messages">私信{{if gt .UnreadCount 0}}<span style="background:#ff6b6b;color:white;border-radius:50%;padding:2px 6px;font-size:0.7rem;margin-left:5px;">{{.UnreadCount}}</span>{{end}}</a>
<div class="user-menu">
<span class="user-name">{{.CurrentUsername}} ▼</span>
<div class="dropdown">
{{if eq .CurrentUserRole "admin"}}<a href="/admin">管理面板</a>{{end}}
<a href="/profile">个人主页</a>
<a href="/new-post">发表新帖</a>
<a href="/messages">私信</a>
<a href="/logout">登出</a>
</div>
</div>
{{else}}
<a href="/login">登录</a>
<a href="/register">注册</a>
{{end}}
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
</div>
<div class="profile-card">
<div class="profile-avatar">
{{if .UserAvatar}}<img src="{{.UserAvatar}}" alt="avatar">{{else}}<span>{{slice .User.Username 0 1}}</span>{{end}}
</div>
<div class="profile-info">
<h1>{{.User.Username}}</h1>
<p>邮箱:{{.User.Email}}</p>
<p>注册时间:{{.User.CreatedAt.Format "2006-01-02"}}</p>
<div class="profile-stats">
<div class="stat-item">
<div class="stat-number">{{.PostCount}}</div>
<div class="stat-label">帖子</div>
</div>
</div>
{{if .IsOwner}}
<button class="edit-btn" onclick="toggleUpload()">修改头像</button>
<div id="uploadForm" style="display: none;" class="avatar-upload-form">
<form action="/upload-avatar" method="post" enctype="multipart/form-data">
<div class="file-input">
<input type="file" name="avatar" accept="image/*" required>
</div>
<button type="submit" class="edit-btn">上传新头像</button>
</form>
</div>
{{else}}
<a href="/send-message?to={{.User.ID}}" class="edit-btn" style="margin-top:10px;">📩 发送私信</a>
{{end}}
</div>
</div>
<div class="section-title">
<h2>{{if .IsOwner}}我的帖子{{else}}{{.User.Username}}的帖子{{end}}</h2>
</div>
{{if not .Posts}}
<div class="no-posts">
<p>暂无帖子</p>
{{if .IsOwner}}<p><a href="/new-post" style="color: var(--accent);">去发表第一篇</a></p>{{end}}
</div>
{{else}}
<div class="post-list">
{{range .Posts}}
<div class="post-item">
<div class="post-avatar">
{{if $.UserAvatar}}<img src="{{$.UserAvatar}}" alt="avatar">{{else}}<span>{{slice $.User.Username 0 1}}</span>{{end}}
</div>
<div class="post-content">
<div class="post-header">
<span class="post-author">{{$.User.Username}}</span>
<span class="post-time">{{.CreatedAt.Format "2006-01-02 15:04"}}</span>
</div>
<div class="post-title"><a href="/post/{{.ID}}">{{.Title}}</a></div>
<div class="post-excerpt">{{slice .Content 0 100}}{{if gt (len .Content) 100}}...{{end}}</div>
</div>
</div>
{{end}}
</div>
{{end}}
<div class="footer">
<div>© 2025 lv8girl · 绿坝娘二次元论坛</div>
<div>
<a href="#">关于</a>
<a href="#">帮助</a>
<a href="#">隐私</a>
<a href="#">投稿</a>
<a href="https://icp.gov.moe/?keyword=20260911" target="_blank">萌ICP备20260911号</a>
</div>
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
function toggleUpload() {
var form = document.getElementById('uploadForm');
form.style.display = form.style.display === 'none' ? 'block' : 'none';
}
</script>
</body>
</html>

105
templates/register.html Normal file
View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lv8girl · 注册</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--accent: #ffb347;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--accent: #ffb347;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; transition: background 0.3s, color 0.3s; }
.register-wrapper { max-width: 420px; width: 100%; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 30px 25px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); }
.card h2 { text-align: center; margin-bottom: 25px; color: var(--accent); font-weight: 600; }
.error-message { background: var(--surface-light); border-left: 4px solid #ff6b6b; color: var(--text-soft); padding: 12px 15px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9rem; }
.success-message { background: var(--surface-light); border-left: 4px solid var(--primary); color: var(--text-soft); padding: 12px 15px; border-radius: 8px; margin-bottom: 20px; font-size: 0.9rem; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 0.9rem; }
input { width: 100%; padding: 12px 15px; background: var(--surface-light); border: 1px solid var(--border); border-radius: 30px; color: var(--text); font-size: 1rem; outline: none; transition: border 0.2s; }
input:focus { border-color: var(--accent); }
.checkbox-group { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; }
.checkbox-group input[type="checkbox"] { width: 18px; height: 18px; accent-color: var(--primary); }
.checkbox-group label { margin-bottom: 0; font-size: 0.9rem; }
.checkbox-group a { color: var(--accent); text-decoration: none; }
.btn { width: 100%; padding: 14px; background: var(--gradient); border: none; border-radius: 40px; color: white; font-weight: 600; font-size: 1rem; cursor: pointer; transition: transform 0.2s; margin-top: 10px; }
.btn:hover { transform: scale(1.02); }
.footer-text { text-align: center; margin-top: 25px; color: var(--text-hint); }
.footer-text a { color: var(--accent); text-decoration: none; font-weight: 500; }
.footer-text a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="register-wrapper">
<div class="header">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
<div class="card">
<h2>注册</h2>
{{if .Error}}<div class="error-message">{{.Error}}</div>{{end}}
{{if .Success}}
<div class="success-message">{{.Success}}</div>
{{else}}
<form method="post">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username" placeholder="3-20个字符支持中文、字母、数字、下划线" required>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" name="email" placeholder="请输入有效邮箱" required>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" placeholder="至少6位" required>
</div>
<div class="form-group">
<label>确认密码</label>
<input type="password" name="confirm_password" placeholder="请再次输入密码" required>
</div>
<div class="checkbox-group">
<input type="checkbox" name="agree" id="agree" required>
<label for="agree">我已阅读并同意 <a href="#">用户协议</a><a href="#">隐私政策</a></label>
</div>
<button type="submit" class="btn">注 册</button>
</form>
<div class="footer-text">已有账号? <a href="/login">去登录</a></div>
{{end}}
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
</script>
</body>
</html>

View File

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发送私信 - lv8girl</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f0f7f0;
--surface: #ffffff;
--surface-light: #e8f0e8;
--border: #d0e0d0;
--text: #2c3e2c;
--text-soft: #5f6b5f;
--text-hint: #8f9f8f;
--primary: #3d9e4a;
--accent: #ffb347;
--gradient: linear-gradient(135deg, #3d9e4a, #5a9cff);
}
body.dark-mode {
--bg: #1a1e1a;
--surface: #1e261e;
--surface-light: #2a3a2a;
--border: #2a3a2a;
--text: #e0e8e0;
--text-soft: #b0bcb0;
--text-hint: #8a958a;
--primary: #6b8e6b;
--accent: #ffb347;
}
body { background: var(--bg); color: var(--text); font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; transition: background 0.3s, color 0.3s; }
.wrapper { max-width: 600px; width: 100%; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
.logo { font-size: 2rem; font-weight: 800; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.logo span { font-size: 0.9rem; background: var(--accent); color: var(--surface); padding: 4px 12px; border-radius: 30px; margin-left: 10px; -webkit-text-fill-color: var(--surface); }
.theme-toggle { background: var(--surface-light); border: none; color: var(--text); font-size: 1.3rem; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s; }
.theme-toggle:hover { background: var(--accent); color: var(--surface); }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 30px 25px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); }
.card h2 { text-align: center; margin-bottom: 25px; color: var(--accent); font-weight: 600; }
.error-message { background: #ff6b6b; color: white; padding: 10px; border-radius: 8px; margin-bottom: 20px; }
.success-message { background: var(--primary); color: white; padding: 10px; border-radius: 8px; margin-bottom: 20px; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; color: var(--text-soft); }
textarea { width: 100%; padding: 12px; background: var(--surface-light); border: 1px solid var(--border); border-radius: 12px; color: var(--text); min-height: 150px; resize: vertical; font-size: 1rem; }
.btn { background: var(--gradient); border: none; border-radius: 40px; padding: 12px 30px; color: white; font-weight: 600; cursor: pointer; transition: 0.2s; width: 100%; }
.btn:hover { transform: scale(1.02); }
.footer-links { margin-top: 20px; text-align: center; }
.footer-links a { color: var(--text-hint); text-decoration: none; margin: 0 10px; }
.footer-links a:hover { color: var(--accent); }
</style>
</head>
<body>
<div class="wrapper">
<div class="header">
<div class="logo">lv8girl<span>绿坝娘</span></div>
<button class="theme-toggle" id="themeToggle">🌓</button>
</div>
<div class="card">
<h2>发送私信给 {{.Receiver.Username}}</h2>
{{if .Error}}<div class="error-message">{{.Error}}</div>{{end}}
{{if .Success}}<div class="success-message">{{.Success}}</div>{{end}}
<form method="post">
<div class="form-group">
<label>内容</label>
<textarea name="content" placeholder="请输入私信内容..."></textarea>
</div>
<button type="submit" class="btn">发送</button>
</form>
<div class="footer-links">
<a href="/">返回首页</a>
<a href="/messages">私信列表</a>
</div>
</div>
</div>
<script>
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌓';
});
</script>
</body>
</html>