feat: opaque session、安装/发帖 SSR 与最小 Admin 后台

浏览器登录改为 DB sessions(可吊销);敏感词与 OIDC PEM 入 settings;
落地安装向导、注册发帖与 /admin 仪表盘/板块/审核/设置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 05:44:16 +08:00
parent 3f50316ad0
commit fde5f628ec
80 changed files with 4148 additions and 1849 deletions

View File

@@ -0,0 +1,47 @@
{{define "admin/boards"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>板块管理</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<h2>新建板块</h2>
<form method="post" action="/admin/boards" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>名称 <input name="name" required value="{{.Form.Name}}"/></label>
<label>简介 <input name="description" value="{{.Form.Description}}"/></label>
<label>图标(可选) <input name="icon" value="{{.Form.Icon}}"/></label>
<label>色号 <input name="color_index" type="number" value="{{.Form.ColorIndex}}"/></label>
<label>排序 <input name="sort_order" type="number" value="{{.Form.SortOrder}}"/></label>
<button type="submit">创建</button>
</form>
<h2>已有板块</h2>
{{if .Boards}}
<ul class="j13-admin-list">
{{range .Boards}}
<li class="j13-admin-card">
<form method="post" action="/admin/boards/{{.ID}}" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<label>名称 <input name="name" required value="{{.Name}}"/></label>
<label>简介 <input name="description" value="{{.Description}}"/></label>
<label>图标 <input name="icon" value="{{.Icon}}"/></label>
<label>色号 <input name="color_index" type="number" value="{{.ColorIndex}}"/></label>
<label>排序 <input name="sort_order" type="number" value="{{.SortOrder}}"/></label>
<p class="j13-muted">已发布帖 {{.PostCount}}</p>
<button type="submit">保存</button>
</form>
<form method="post" action="/admin/boards/{{.ID}}/delete" class="j13-inline-form" onsubmit="return confirm('确认删除该板块?');">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<button type="submit" class="j13-linkbtn">删除</button>
</form>
</li>
{{end}}
</ul>
{{else}}
<p class="j13-empty">暂无板块</p>
{{end}}
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -0,0 +1,18 @@
{{define "admin/dashboard"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>管理后台</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<ul class="j13-admin-stats">
<li><strong>{{.UserCount}}</strong><span>用户</span></li>
<li><strong>{{.PostCount}}</strong><span>已发布帖</span></li>
<li><strong>{{.BoardCount}}</strong><span>板块</span></li>
<li><strong>{{.PendingPosts}}</strong><span>待审帖</span></li>
<li><strong>{{.PendingComments}}</strong><span>待审评</span></li>
</ul>
<p class="j13-muted">有待审内容时请前往 <a href="/admin/moderation">内容审核</a>。</p>
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -0,0 +1,60 @@
{{define "admin/moderation"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>内容审核</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<h2>待审帖子({{len .Posts}}</h2>
{{if .Posts}}
<ul class="j13-admin-list">
{{range .Posts}}
<li class="j13-admin-card">
<p><a href="/post/{{.ID}}">{{.Title}}</a></p>
<p class="j13-muted">{{.AuthorName}} · {{.BoardName}} · {{.CreatedAt}}</p>
<div class="j13-admin-actions">
<form method="post" action="/admin/posts/{{.ID}}/approve" class="j13-inline-form">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<button type="submit">通过</button>
</form>
<form method="post" action="/admin/posts/{{.ID}}/reject" class="j13-form j13-admin-reject">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<input name="reason" required placeholder="拒绝原因" />
<button type="submit" class="j13-btn-secondary">拒绝</button>
</form>
</div>
</li>
{{end}}
</ul>
{{else}}
<p class="j13-empty">无待审帖子</p>
{{end}}
<h2>待审评论({{len .Comments}}</h2>
{{if .Comments}}
<ul class="j13-admin-list">
{{range .Comments}}
<li class="j13-admin-card">
<p class="j13-muted">#{{.Floor}} · {{.AuthorName}} · {{.CreatedAt}} · <a href="/post/{{.PostID}}">{{.PostTitle}}</a></p>
<p>{{.Excerpt}}</p>
<div class="j13-admin-actions">
<form method="post" action="/admin/comments/{{.ID}}/approve" class="j13-inline-form">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<button type="submit">通过</button>
</form>
<form method="post" action="/admin/comments/{{.ID}}/reject" class="j13-form j13-admin-reject">
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
<input name="reason" required placeholder="拒绝原因" />
<button type="submit" class="j13-btn-secondary">拒绝</button>
</form>
</div>
</li>
{{end}}
</ul>
{{else}}
<p class="j13-empty">无待审评论</p>
{{end}}
</main>
{{template "base/footer" .}}
{{end}}

9
templates/admin/nav.tmpl Normal file
View File

@@ -0,0 +1,9 @@
{{define "admin/nav"}}
<nav class="j13-admin-nav">
<a href="/admin/dashboard"{{if eq .NavActive "dashboard"}} class="is-active"{{end}}>仪表盘</a>
<a href="/admin/boards"{{if eq .NavActive "boards"}} class="is-active"{{end}}>板块</a>
<a href="/admin/moderation"{{if eq .NavActive "moderation"}} class="is-active"{{end}}>审核</a>
<a href="/admin/settings"{{if eq .NavActive "settings"}} class="is-active"{{end}}>设置</a>
<a href="/">返回前台</a>
</nav>
{{end}}

View File

@@ -0,0 +1,43 @@
{{define "admin/settings"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-admin">
<h1>站点设置</h1>
{{template "admin/nav" .}}
{{template "base/alert" .}}
<h2>品牌</h2>
<form method="post" action="/admin/settings/brand" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>站点名称 <input name="name" required value="{{.Brand.Name}}"/></label>
<label>标语 <input name="slogan" value="{{.Brand.Slogan}}"/></label>
<label>简介 <textarea name="description" rows="3">{{.Brand.Description}}</textarea></label>
<label>关键词 <input name="keywords" value="{{.Brand.Keywords}}"/></label>
<label>Logo 字标(单字) <input name="logo_mark" maxlength="4" value="{{.Brand.LogoMark}}"/></label>
<label>ICP 备案号 <input name="icp_beian" value="{{.Brand.ICPBeian}}"/></label>
<label>ICP 链接 <input name="icp_beian_url" value="{{.Brand.ICPBeianURL}}"/></label>
<button type="submit">保存品牌</button>
</form>
<h2>基础限流</h2>
<p class="j13-muted">窗口内允许的操作次数(秒)。</p>
<form method="post" action="/admin/settings/limits" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>发帖 <input name="rate_limit_post" type="number" min="1" value="{{.RatePost}}"/></label>
<label>评论 <input name="rate_limit_comment" type="number" min="1" value="{{.RateComment}}"/></label>
<label>注册 <input name="rate_limit_register" type="number" min="1" value="{{.RateReg}}"/></label>
<label>登录 <input name="rate_limit_login" type="number" min="1" value="{{.RateLogin}}"/></label>
<label>窗口秒数 <input name="rate_limit_window_sec" type="number" min="10" value="{{.RateWindow}}"/></label>
<button type="submit">保存限流</button>
</form>
<h2>敏感词(有效 {{.FilterCount}} 个)</h2>
<p class="j13-muted">每行一个词,# 开头为注释。保存后立即热更新。</p>
<form method="post" action="/admin/settings/filter-words" class="j13-form j13-admin-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>词表 <textarea name="filter_words" rows="12">{{.FilterWords}}</textarea></label>
<button type="submit">保存敏感词</button>
</form>
</main>
{{template "base/footer" .}}
{{end}}

20
templates/auth/login.tmpl Normal file
View File

@@ -0,0 +1,20 @@
{{define "auth/login"}}
{{template "base/head" .}}
<main class="j13-main j13-main--solo j13-auth">
<h1>登录</h1>
{{template "base/alert" .}}
<form method="post" action="/login" class="j13-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<input type="hidden" name="redirect" value="{{.Redirect}}"/>
<label>用户名
<input name="username" required autocomplete="username" value="{{.Username}}"/>
</label>
<label>密码
<input type="password" name="password" required autocomplete="current-password"/>
</label>
<button type="submit">登录</button>
</form>
<p><a href="/register">没有账号?注册</a> · <a href="/">返回首页</a></p>
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -0,0 +1,39 @@
{{define "auth/register"}}
{{template "base/head" .}}
<main class="j13-main j13-main--solo j13-auth">
<h1>注册</h1>
{{template "base/alert" .}}
<form method="post" action="/register" class="j13-form" id="register-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>用户名
<input name="username" required autocomplete="username" value="{{.Username}}" maxlength="32"/>
</label>
<label>昵称
<input name="nickname" autocomplete="nickname" value="{{.Nickname}}" maxlength="32" placeholder="可选,默认与用户名相同"/>
</label>
<label>邮箱
<input type="email" name="email" required autocomplete="email" value="{{.Email}}"/>
</label>
{{if .RequireEmailCode}}
<label>邮箱验证码
<div class="j13-form__row">
<input name="email_code" required inputmode="numeric" autocomplete="one-time-code" maxlength="8"/>
<button type="submit" formaction="/register/send-code" formnovalidate class="j13-btn-secondary">发送验证码</button>
</div>
</label>
<p class="j13-muted">邮件服务已启用,注册需验证邮箱。</p>
{{else}}
<p class="j13-muted">邮件服务未配置,可直接注册(管理员可稍后在后台启用 SMTP。</p>
{{end}}
<label>密码
<input type="password" name="password" required autocomplete="new-password" minlength="6"/>
</label>
<label>确认密码
<input type="password" name="password2" required autocomplete="new-password" minlength="6"/>
</label>
<button type="submit">注册</button>
</form>
<p><a href="/login">已有账号?登录</a> · <a href="/">返回首页</a></p>
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -1,51 +0,0 @@
{{define "base"}}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{{.Title}}</title>
{{if .Description}}<meta name="description" content="{{.Description}}"/>{{end}}
<link rel="stylesheet" href="/ssr-assets/site.css"/>
</head>
<body class="j13-body">
<header class="j13-header">
<div class="j13-header__inner">
<a class="j13-brand" href="/">
<span class="j13-brand__mark">{{.LogoMark}}</span>
<span class="j13-brand__text">
<strong>{{.SiteName}}</strong>
{{if .Slogan}}<span class="j13-brand__slogan">{{.Slogan}}</span>{{end}}
</span>
</a>
<nav class="j13-nav">
{{if .LoggedIn}}
<a href="/compose">发帖</a>
<a href="/messages">消息</a>
<a href="/profile">{{.ViewerName}}</a>
{{if .IsAdmin}}<a href="/admin/dashboard">后台</a>{{end}}
{{else}}
<a href="/login">登录</a>
<a href="/register">注册</a>
{{end}}
</nav>
</div>
</header>
<div class="j13-layout">
<aside class="j13-aside j13-aside--left">
<a class="j13-aside__link{{if eq .ActiveBoard 0}} is-active{{end}}" href="/">全部帖子</a>
{{range .Boards}}
<a class="j13-aside__link{{if eq $.ActiveBoard .ID}} is-active{{end}}" href="/board/{{.ID}}">{{.Name}}</a>
{{end}}
</aside>
<main class="j13-main">
{{template "content" .}}
</main>
</div>
<footer class="j13-footer">
<p>{{.SiteName}} · Gitea 式 SSR 骨架</p>
</footer>
<script src="/ssr-assets/site.js" defer></script>
</body>
</html>
{{end}}

View File

@@ -0,0 +1,4 @@
{{define "base/alert"}}
{{if .Error}}<div class="j13-alert j13-alert--error" role="alert">{{.Error}}</div>{{end}}
{{if .Flash}}<div class="j13-flash" role="status">{{.Flash}}</div>{{end}}
{{end}}

View File

@@ -0,0 +1,8 @@
{{define "base/footer"}}
<footer class="j13-footer">
<p>{{.SiteName}}</p>
</footer>
<script src="/ssr-assets/site.js" defer></script>
</body>
</html>
{{end}}

12
templates/base/head.tmpl Normal file
View File

@@ -0,0 +1,12 @@
{{define "base/head"}}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{{.Title}}</title>
{{if .Description}}<meta name="description" content="{{.Description}}"/>{{end}}
<link rel="stylesheet" href="/ssr-assets/site.css"/>
</head>
<body class="j13-body">
{{end}}

View File

@@ -0,0 +1,27 @@
{{define "base/navbar"}}
<header class="j13-header">
<div class="j13-header__inner">
<a class="j13-brand" href="/">
<span class="j13-brand__mark">{{.LogoMark}}</span>
<span class="j13-brand__text">
<strong>{{.SiteName}}</strong>
{{if .Slogan}}<span class="j13-brand__slogan">{{.Slogan}}</span>{{end}}
</span>
</a>
<nav class="j13-nav">
{{if .LoggedIn}}
<a href="/compose">发帖</a>
<a href="/profile">{{.ViewerName}}</a>
{{if .IsAdmin}}<a href="/admin/dashboard">后台</a>{{end}}
<form class="j13-inline-form" method="post" action="/logout">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<button type="submit" class="j13-linkbtn">退出</button>
</form>
{{else}}
<a href="/register">注册</a>
<a href="/login">登录</a>
{{end}}
</nav>
</div>
</header>
{{end}}

39
templates/compose.tmpl Normal file
View File

@@ -0,0 +1,39 @@
{{define "compose"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
{{if .Flash}}<div class="j13-flash" role="status">{{.Flash}}</div>{{end}}
<main class="j13-main j13-main--solo j13-compose">
<h1>{{if .IsEdit}}编辑帖子{{else}}发帖{{end}}</h1>
{{template "base/alert" .}}
<form method="post" action="{{.FormAction}}" class="j13-form" id="compose-form" data-csrf="{{.CSRF}}" data-upload="/compose/upload">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label>板块
<select name="board_id" required>
<option value="">请选择</option>
{{range .Boards}}
<option value="{{.ID}}" {{if eq $.BoardID .ID}}selected{{end}}>{{.Name}}</option>
{{end}}
</select>
</label>
<label>标题
<input name="title" required maxlength="{{.TitleMax}}" value="{{.Title}}"/>
</label>
<label>标签
<input name="tags" maxlength="{{.TagsMax}}" value="{{.Tags}}" placeholder="逗号分隔,可选"/>
</label>
<label>正文
<textarea name="content" id="compose-content" rows="16" required maxlength="{{.ContentMax}}" placeholder="支持纯文本;图片上传后插入 Markdown 图片语法">{{.Content}}</textarea>
</label>
<div class="j13-compose__tools">
<label class="j13-btn-secondary j13-filebtn">
插入图片
<input type="file" id="compose-image" accept="image/*" hidden/>
</label>
<span class="j13-muted" id="compose-upload-status"></span>
</div>
<p class="j13-muted">本版仅发普通讨论帖;投票 / 悬赏 / 抽奖后续迭代。</p>
<button type="submit">{{if .IsEdit}}保存{{else}}发布{{end}}</button>
</form>
</main>
{{template "base/footer" .}}
{{end}}

View File

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

View File

@@ -1,6 +1,4 @@
{{define "home"}}{{template "base" .}}{{end}}
{{define "content"}}
{{define "home/feed"}}
<section class="j13-feed">
<header class="j13-feed__header">
<h1 class="j13-feed__title">{{if .BoardName}}{{.BoardName}}{{else}}全部帖子{{end}}</h1>
@@ -10,14 +8,13 @@
<a class="{{if eq .Sort "hot"}}is-active{{end}}" href="{{sortURL .ActiveBoard "hot"}}">热门讨论</a>
</div>
</header>
{{if not .Posts}}
<p class="j13-empty">暂无帖子。登录后可以发第一帖。</p>
<p class="j13-empty">暂无帖子。</p>
{{else}}
<ul class="j13-post-list">
{{range .Posts}}
<li class="j13-post-item">
<a class="j13-post-item__title" href="/post/{{.ID}}">{{.Title}}</a>
<a class="j13-post-item__title" href="{{postURL .ID}}">{{.Title}}</a>
<div class="j13-post-item__meta">
{{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}}
{{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}}
@@ -30,7 +27,6 @@
{{end}}
</ul>
{{end}}
{{if or .HasPrev .HasMore}}
<nav class="j13-pager">
{{if .HasPrev}}<a href="{{pageURL .ActiveBoard .Sort .PrevPage}}">上一页</a>{{end}}

12
templates/home/home.tmpl Normal file
View File

@@ -0,0 +1,12 @@
{{define "home"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
{{if .Flash}}<div class="j13-flash" role="status">{{.Flash}}</div>{{end}}
<div class="j13-layout">
{{template "shared/board_aside" .}}
<main class="j13-main">
{{template "home/feed" .}}
</main>
</div>
{{template "base/footer" .}}
{{end}}

37
templates/install.tmpl Normal file
View File

@@ -0,0 +1,37 @@
{{define "install"}}
{{template "base/head" .}}
<main class="j13-main j13-main--solo j13-install">
<h1>安装姜十三论坛</h1>
<p class="j13-muted">首次运行配置站点与管理员账号。</p>
{{template "base/alert" .}}
<form method="post" action="/install" class="j13-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<fieldset>
<legend>站点</legend>
<label>站点名称
<input name="site_name" required value="{{.SiteName}}" maxlength="64"/>
</label>
</fieldset>
<fieldset>
<legend>管理员</legend>
<label>用户名
<input name="admin_username" required autocomplete="username" value="{{.AdminUsername}}" pattern="[a-zA-Z0-9_]{3,32}"/>
</label>
<label>邮箱
<input type="email" name="admin_email" required autocomplete="email" value="{{.AdminEmail}}"/>
</label>
<label>昵称
<input name="admin_nickname" value="{{.AdminNickname}}" maxlength="32"/>
</label>
<label>密码
<input type="password" name="admin_password" required autocomplete="new-password" minlength="6"/>
</label>
<label>确认密码
<input type="password" name="admin_password2" required autocomplete="new-password" minlength="6"/>
</label>
</fieldset>
<button type="submit">安装</button>
</form>
</main>
{{template "base/footer" .}}
{{end}}

View File

@@ -0,0 +1,10 @@
{{define "post-install"}}
{{template "base/head" .}}
<main class="j13-main j13-main--solo j13-install">
<h1>安装完成</h1>
<p>站点已就绪,正在进入论坛…</p>
<p><a href="/">立即进入</a></p>
<meta http-equiv="refresh" content="2;url=/"/>
</main>
{{template "base/footer" .}}
{{end}}

69
templates/post/body.tmpl Normal file
View File

@@ -0,0 +1,69 @@
{{define "post/body"}}
<article class="j13-post">
<header class="j13-post__header">
<h1 class="j13-post__title">{{.PostTitle}}</h1>
<div class="j13-post__meta">
{{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}}
{{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>
<span>{{.CreatedLabel}}</span>
<span>{{.ViewCount}} 阅读</span>
</div>
{{if .LoggedIn}}
<div class="j13-post__actions">
<form method="post" action="/post/{{.PostID}}/like" class="j13-inline-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<button type="submit">{{if .Liked}}取消赞{{else}}赞{{end}} ({{.LikeCount}})</button>
</form>
<form method="post" action="/post/{{.PostID}}/favorite" class="j13-inline-form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<button type="submit">{{if .Favorited}}取消收藏{{else}}收藏{{end}}</button>
</form>
{{if .CanEdit}}<a class="j13-linkbtn" href="/post/{{.PostID}}/edit">编辑</a>{{end}}
</div>
{{end}}
</header>
<div class="j13-post__content post-detail-content">{{safeHTML .BodyHTML}}</div>
</article>
<section class="j13-comments" id="comments">
<h2>评论 ({{.CommentCount}})</h2>
{{if not .Comments}}
<p class="j13-empty">暂无评论。</p>
{{else}}
<ul class="j13-comment-list">
{{range .Comments}}
<li class="j13-comment" id="floor-{{.Floor}}">
<div class="j13-comment__meta">
<span class="j13-comment__floor">#{{.Floor}}</span>
<span>{{.AuthorName}}</span>
<span>{{.CreatedLabel}}</span>
</div>
{{if .ContentHidden}}
<p class="j13-muted">(私密评论不可见)</p>
{{else}}
<div class="j13-comment__body">{{safeHTML .Content}}</div>
{{end}}
</li>
{{end}}
</ul>
{{end}}
{{if .LoggedIn}}
{{if .CommentsLocked}}
<p class="j13-muted">评论已锁定。</p>
{{else}}
<form class="j13-comment-form" method="post" action="/post/{{.PostID}}/comments">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<label for="comment-content">发表评论</label>
<textarea id="comment-content" name="content" rows="4" required maxlength="8000"></textarea>
<button type="submit">提交</button>
</form>
{{end}}
{{else}}
<p><a href="/login?redirect={{.PostPath}}">登录</a> 后回复。</p>
{{end}}
</section>
{{end}}

12
templates/post/view.tmpl Normal file
View File

@@ -0,0 +1,12 @@
{{define "post"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
{{if .Flash}}<div class="j13-flash" role="status">{{.Flash}}</div>{{end}}
<div class="j13-layout">
{{template "shared/board_aside" .}}
<main class="j13-main">
{{template "post/body" .}}
</main>
</div>
{{template "base/footer" .}}
{{end}}

View File

@@ -0,0 +1,8 @@
{{define "shared/board_aside"}}
<aside class="j13-aside j13-aside--left">
<a class="j13-aside__link{{if eq .ActiveBoard 0}} is-active{{end}}" href="/">全部帖子</a>
{{range .Boards}}
<a class="j13-aside__link{{if eq $.ActiveBoard .ID}} is-active{{end}}" href="/board/{{.ID}}">{{.Name}}</a>
{{end}}
</aside>
{{end}}

21
templates/status/404.tmpl Normal file
View File

@@ -0,0 +1,21 @@
{{define "status/404"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-main--solo">
<h1>页面不存在</h1>
<p>您访问的页面不存在或已删除。</p>
<p><a href="/">返回首页</a></p>
</main>
{{template "base/footer" .}}
{{end}}
{{define "status/pending"}}
{{template "base/head" .}}
{{template "base/navbar" .}}
<main class="j13-main j13-main--solo">
<h1>{{.Heading}}</h1>
<p>{{.Message}}</p>
<p><a href="/">返回首页</a></p>
</main>
{{template "base/footer" .}}
{{end}}