feat: SSR 个人闭环(profile / user / favorites)

登录用户可改资料与头像、浏览收藏;公开主页不含邮箱;改密吊销并重建本端 session。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 05:53:56 +08:00
parent fde5f628ec
commit 126e3bae98
19 changed files with 599 additions and 20 deletions

View File

@@ -11,6 +11,7 @@
<nav class="j13-nav">
{{if .LoggedIn}}
<a href="/compose">发帖</a>
<a href="/favorites">收藏</a>
<a href="/profile">{{.ViewerName}}</a>
{{if .IsAdmin}}<a href="/admin/dashboard">后台</a>{{end}}
<form class="j13-inline-form" method="post" action="/logout">

View File

@@ -2,5 +2,5 @@ package templates
import "embed"
//go:embed *.tmpl base/*.tmpl home/*.tmpl post/*.tmpl shared/*.tmpl status/*.tmpl auth/*.tmpl admin/*.tmpl
//go:embed *.tmpl base/*.tmpl home/*.tmpl post/*.tmpl shared/*.tmpl status/*.tmpl auth/*.tmpl admin/*.tmpl profile/*.tmpl user/*.tmpl favorites/*.tmpl
var FS embed.FS

View File

@@ -0,0 +1,30 @@
{{define "favorites/list"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-profile">
<h1>我的收藏</h1>
{{template "base/alert" .}}
<p class="j13-muted">共 {{.Total}} 条 · <a href="/profile">个人中心</a></p>
{{if .Items}}
<ul class="j13-post-list">
{{range .Items}}
<li class="j13-post-item">
<a class="j13-post-item__title" href="/post/{{.PostID}}">{{.Title}}</a>
<div class="j13-post-item__meta">
{{if .AuthorName}}<span>{{.AuthorName}}</span>{{end}}
{{if .BoardName}}<span>{{.BoardName}}</span>{{end}}
<span>{{.CreatedLabel}}</span>
</div>
</li>
{{end}}
</ul>
<nav class="j13-pager">
{{if .HasPrev}}<a href="/favorites?page={{.PrevPage}}">上一页</a>{{end}}
{{if .HasMore}}<a href="/favorites?page={{.NextPage}}">下一页</a>{{end}}
</nav>
{{else}}
<p class="j13-empty">还没有收藏。在帖子详情页可点击收藏。</p>
{{end}}
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -7,7 +7,7 @@
{{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}}
{{if .PostTypeLabel}}<span class="j13-tag">{{.PostTypeLabel}}</span>{{end}}
{{if .BoardName}}<a class="j13-tag" href="/board/{{.BoardID}}">{{.BoardName}}</a>{{end}}
<span>{{.AuthorName}}</span>
{{if .AuthorID}}<a href="/user/{{.AuthorID}}">{{.AuthorName}}</a>{{else}}<span>{{.AuthorName}}</span>{{end}}
<span>{{.CreatedLabel}}</span>
<span>{{.ViewCount}} 阅读</span>
</div>
@@ -38,7 +38,7 @@
<li class="j13-comment" id="floor-{{.Floor}}">
<div class="j13-comment__meta">
<span class="j13-comment__floor">#{{.Floor}}</span>
<span>{{.AuthorName}}</span>
{{if .AuthorID}}<a href="/user/{{.AuthorID}}">{{.AuthorName}}</a>{{else}}<span>{{.AuthorName}}</span>{{end}}
<span>{{.CreatedLabel}}</span>
</div>
{{if .ContentHidden}}

View File

@@ -0,0 +1,72 @@
{{define "profile/view"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-profile">
<h1>个人中心</h1>
{{template "base/alert" .}}
<p class="j13-muted">
<a href="{{.PublicURL}}">查看公开主页</a>
· <a href="/favorites">我的收藏</a>
</p>
<ul class="j13-admin-stats">
<li><strong>{{.PostCount}}</strong><span>帖子</span></li>
<li><strong>{{.CommentCount}}</strong><span>评论</span></li>
<li><strong>{{.FavoriteCount}}</strong><span>收藏</span></li>
<li><strong>{{.LikeReceived}}</strong><span>获赞</span></li>
</ul>
<section class="j13-profile__card">
<h2>账号</h2>
<p>用户名 <strong>{{.Username}}</strong></p>
<p class="j13-muted">邮箱 {{.Email}}</p>
<p class="j13-muted">等级 Lv.{{.Level}} · 经验 {{.Exp}} · 积分 {{.Points}}</p>
{{if .Avatar}}
<p><img class="j13-avatar" src="{{.Avatar}}" alt="头像"/></p>
{{end}}
</section>
<section>
<h2>头像</h2>
<form method="post" action="/profile/avatar" enctype="multipart/form-data" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>图片(最大 {{.AvatarMaxMB}} MB
<input type="file" name="avatar" accept="image/*" required/>
</label>
<button type="submit">上传头像</button>
</form>
</section>
<section>
<h2>昵称</h2>
<form method="post" action="/profile/nickname" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>昵称 <input name="nickname" value="{{.Nickname}}" maxlength="32" required/></label>
<button type="submit">保存昵称</button>
</form>
</section>
<section>
<h2>签名</h2>
<form method="post" action="/profile/signature" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>签名
<textarea name="signature" rows="3" maxlength="{{.SignatureMax}}">{{.Signature}}</textarea>
</label>
<button type="submit">保存签名</button>
</form>
</section>
<section>
<h2>修改密码</h2>
<form method="post" action="/profile/password" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>当前密码 <input type="password" name="old_password" required autocomplete="current-password"/></label>
<label>新密码 <input type="password" name="new_password" required minlength="6" autocomplete="new-password"/></label>
<label>确认新密码 <input type="password" name="new_password2" required minlength="6" autocomplete="new-password"/></label>
<button type="submit">更新密码</button>
</form>
</section>
</main>
{{template "base/footer" .}}
{{end}}

48
templates/user/view.tmpl Normal file
View File

@@ -0,0 +1,48 @@
{{define "user/view"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-profile">
<header class="j13-user-hero">
{{if .Avatar}}
<img class="j13-avatar j13-avatar--lg" src="{{.Avatar}}" alt=""/>
{{else}}
<span class="j13-avatar j13-avatar--lg j13-avatar--placeholder" aria-hidden="true">·</span>
{{end}}
<div>
<h1>{{if .Nickname}}{{.Nickname}}{{else}}{{.Username}}{{end}}</h1>
<p class="j13-muted">@{{.Username}} · Lv.{{.Level}} · 积分 {{.Points}}</p>
{{if .Signature}}<p class="j13-user-sig">{{.Signature}}</p>{{end}}
{{if .IsSelf}}<p><a href="/profile">编辑资料</a></p>{{end}}
</div>
</header>
{{template "base/alert" .}}
<ul class="j13-admin-stats">
<li><strong>{{.PostCount}}</strong><span>帖子</span></li>
<li><strong>{{.CommentCount}}</strong><span>评论</span></li>
<li><strong>{{.LikeReceived}}</strong><span>获赞</span></li>
</ul>
<h2>最近帖子</h2>
{{if .Posts}}
<ul class="j13-post-list">
{{range .Posts}}
<li class="j13-post-item">
<a class="j13-post-item__title" href="/post/{{.ID}}">{{.Title}}</a>
<div class="j13-post-item__meta">
{{if .BoardName}}<span>{{.BoardName}}</span>{{end}}
<span>{{.CreatedLabel}}</span>
</div>
</li>
{{end}}
</ul>
<nav class="j13-pager">
{{if .HasPrev}}<a href="/user/{{.UserID}}?page={{.PrevPage}}">上一页</a>{{end}}
{{if .HasMore}}<a href="/user/{{.UserID}}?page={{.NextPage}}">下一页</a>{{end}}
</nav>
{{else}}
<p class="j13-empty">暂无公开帖子。</p>
{{end}}
</main>
{{template "base/footer" .}}
{{end}}