feat: SSR 帖运营标记与回收站
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -58,7 +58,7 @@
|
||||
- [ ] TipTap 富文本能力(见 [06-pages-ux.md](06-pages-ux.md) 编辑器节)— 本分支改用 Markdown textarea 渐进增强
|
||||
- [ ] Markdown 编辑模式(与富文本互转/双模)
|
||||
- [x] 编辑帖子(时限、锁帖约束)— SSR `/post/:id/edit`
|
||||
- [ ] 删除帖子 → 软删进回收站
|
||||
- [x] 删除帖子 → 软删进回收站 — SSR Admin(帖详情 + `/admin/trash`)
|
||||
- [ ] 修订历史列表与单条详情(可做 diff)
|
||||
- [x] 点赞切换;收藏切换;收藏列表 — SSR `/favorites`
|
||||
- [x] 浏览量 — 详情页计数
|
||||
@@ -75,13 +75,13 @@
|
||||
|
||||
### D.2 运营标记(管理员)
|
||||
|
||||
- [ ] 全局置顶 / 取消
|
||||
- [ ] 版内置顶 / 取消(仅板块列表抬升)
|
||||
- [ ] 精华 / 取消
|
||||
- [ ] 禁止编辑(edit lock)
|
||||
- [ ] 禁止评论 / 结贴(comments lock)
|
||||
- [x] 全局置顶 / 取消 — SSR 帖详情
|
||||
- [x] 版内置顶 / 取消(仅板块列表抬升)— SSR 帖详情
|
||||
- [x] 精华 / 取消 — SSR 帖详情
|
||||
- [x] 禁止编辑(edit lock)— SSR 帖详情
|
||||
- [x] 禁止评论 / 结贴(comments lock)— SSR 帖详情
|
||||
- [x] 审核通过 / 拒绝(拒绝可通知作者)— SSR `/admin/moderation`
|
||||
- [ ] 回收站:恢复 / 彻底删除
|
||||
- [x] 回收站:恢复 / 彻底删除 — SSR `/admin/trash`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
| `/admin/friend-links` | 品牌友链、申请审核、入口开关 | 已迁 |
|
||||
| `/admin/pages` | 站点单页 CRUD / 发布 | 已迁 |
|
||||
| `/admin/reports` | 举报处理 | 已迁 |
|
||||
| `/admin/trash` | 帖回收站 | 已迁 |
|
||||
| `/admin/login` | 重定向前台登录 | 已迁 |
|
||||
|
||||
未迁(原 SPA):users / badges / media / 完整 Limits 等。
|
||||
@@ -188,8 +189,8 @@
|
||||
| Boards | 拖拽或数字排序;图标/色板选择;增删改 |
|
||||
| Pages | 列表发布开关;编辑正文(HTML textarea);nav/footer 勾选 — SSR `/admin/pages` |
|
||||
| Links | 品牌友链增删;申请通过/拒绝;回链检测开关;nav/footer/aside 开关 — SSR `/admin/friend-links`(完整侧栏组件编辑 / 复检按钮未迁) |
|
||||
| Posts | 按状态筛;通过/拒绝;置顶/版顶/精华/锁编/锁评;进回收站恢复/清除 |
|
||||
| Comments | 审核;修订查看;回收站 |
|
||||
| Posts | 审核通过/拒绝 — SSR `/admin/moderation`;置顶/版顶/精华/锁编/锁评/软删 — 帖详情 Admin 条;回收站恢复/清除 — SSR `/admin/trash` |
|
||||
| Comments | 审核 — SSR `/admin/moderation`;修订查看未迁 |
|
||||
| Reports | 处理动作:dismiss / resolve / reject_post / reject_comment — SSR `/admin/reports` |
|
||||
| Users | 搜索;禁言;认证;设等级;调积分;授徽章 |
|
||||
| Badges | 定义自动/限定徽章 |
|
||||
|
||||
@@ -100,4 +100,6 @@ Feed 搜索:`/` / `/board/:id` 面板(`keyword` / `tag` / `author` / `title_
|
||||
|
||||
举报:帖/评 SSR 表单;Admin `/admin/reports`(dismiss / resolve / reject_post / reject_comment)。
|
||||
|
||||
Admin:`/admin/dashboard`、`/admin/boards`、`/admin/moderation`、`/admin/settings`(品牌/限流/敏感词/SMTP)、`/admin/friend-links`、`/admin/pages`、`/admin/reports`。
|
||||
运营标记:帖详情 Admin 条(全局/版内置顶、精华、禁编、锁评、软删);回收站 `/admin/trash`(恢复/彻底删除)。
|
||||
|
||||
Admin:`/admin/dashboard`、`/admin/boards`、`/admin/moderation`、`/admin/settings`(品牌/限流/敏感词/SMTP)、`/admin/friend-links`、`/admin/pages`、`/admin/reports`、`/admin/trash`。
|
||||
@@ -890,9 +890,13 @@ points-only[data-locked="false"] {
|
||||
.j13-admin-filters a { color: var(--j13-muted); text-decoration: none; }
|
||||
.j13-admin-filters a.is-active,
|
||||
.j13-admin-filters a:hover { color: var(--j13-accent); font-weight: 600; }
|
||||
.j13-report-handle .j13-form__row {
|
||||
.j13-post__admin {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
gap: 0.35rem 0.65rem;
|
||||
margin-top: 0.65rem;
|
||||
padding-top: 0.55rem;
|
||||
border-top: 1px dashed var(--j13-border);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
|
||||
285
routers/web/admin_post_ops.go
Normal file
285
routers/web/admin_post_ops.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/modules/webctx"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (d Deps) adminPostRedirect(postID uint) string {
|
||||
return fmt.Sprintf("/post/%d", postID)
|
||||
}
|
||||
|
||||
func (d Deps) parseAdminPostID(c *gin.Context) (uint, string, bool) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
return 0, "/", false
|
||||
}
|
||||
return uint(id), d.adminPostRedirect(uint(id)), true
|
||||
}
|
||||
|
||||
func (d Deps) requireAdminPostCSRF(ctx *webctx.Context, redir string) bool {
|
||||
if !ctx.CheckCSRF() {
|
||||
ctx.SetFlash("无效请求,请重试")
|
||||
ctx.Redirect(redir)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func formBoolEnable(c *gin.Context) bool {
|
||||
v := strings.TrimSpace(c.PostForm("enable"))
|
||||
return v == "1" || strings.EqualFold(v, "true") || v == "on"
|
||||
}
|
||||
|
||||
// AdminPostPinPost 全局置顶切换
|
||||
func (d Deps) AdminPostPinPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
id, redir, ok := d.parseAdminPostID(c)
|
||||
if !ok {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
if !d.requireAdminPostCSRF(ctx, redir) {
|
||||
return
|
||||
}
|
||||
enable := formBoolEnable(c)
|
||||
if err := d.Post.SetPinned(id, enable); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect(redir)
|
||||
return
|
||||
}
|
||||
if enable {
|
||||
ctx.SetFlash("已全局置顶")
|
||||
} else {
|
||||
ctx.SetFlash("已取消全局置顶")
|
||||
}
|
||||
ctx.Redirect(redir)
|
||||
}
|
||||
|
||||
// AdminPostBoardPinPost 版内置顶切换
|
||||
func (d Deps) AdminPostBoardPinPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
id, redir, ok := d.parseAdminPostID(c)
|
||||
if !ok {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
if !d.requireAdminPostCSRF(ctx, redir) {
|
||||
return
|
||||
}
|
||||
enable := formBoolEnable(c)
|
||||
if err := d.Post.SetBoardPinned(id, enable); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect(redir)
|
||||
return
|
||||
}
|
||||
if enable {
|
||||
ctx.SetFlash("已版内置顶")
|
||||
} else {
|
||||
ctx.SetFlash("已取消版内置顶")
|
||||
}
|
||||
ctx.Redirect(redir)
|
||||
}
|
||||
|
||||
// AdminPostFeaturePost 精华切换
|
||||
func (d Deps) AdminPostFeaturePost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
id, redir, ok := d.parseAdminPostID(c)
|
||||
if !ok {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
if !d.requireAdminPostCSRF(ctx, redir) {
|
||||
return
|
||||
}
|
||||
enable := formBoolEnable(c)
|
||||
if err := d.Post.SetFeatured(id, enable); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect(redir)
|
||||
return
|
||||
}
|
||||
if enable {
|
||||
ctx.SetFlash("已设为精华")
|
||||
} else {
|
||||
ctx.SetFlash("已取消精华")
|
||||
}
|
||||
ctx.Redirect(redir)
|
||||
}
|
||||
|
||||
// AdminPostEditLockPost 禁止编辑切换
|
||||
func (d Deps) AdminPostEditLockPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
id, redir, ok := d.parseAdminPostID(c)
|
||||
if !ok {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
if !d.requireAdminPostCSRF(ctx, redir) {
|
||||
return
|
||||
}
|
||||
enable := formBoolEnable(c)
|
||||
if err := d.Post.SetEditLocked(id, enable); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect(redir)
|
||||
return
|
||||
}
|
||||
if enable {
|
||||
ctx.SetFlash("已禁止编辑")
|
||||
} else {
|
||||
ctx.SetFlash("已允许编辑")
|
||||
}
|
||||
ctx.Redirect(redir)
|
||||
}
|
||||
|
||||
// AdminPostCommentsLockPost 禁止评论切换
|
||||
func (d Deps) AdminPostCommentsLockPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
id, redir, ok := d.parseAdminPostID(c)
|
||||
if !ok {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
if !d.requireAdminPostCSRF(ctx, redir) {
|
||||
return
|
||||
}
|
||||
enable := formBoolEnable(c)
|
||||
if err := d.Post.SetCommentsLocked(id, enable); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect(redir)
|
||||
return
|
||||
}
|
||||
if enable {
|
||||
ctx.SetFlash("已锁定评论")
|
||||
} else {
|
||||
ctx.SetFlash("已解锁评论")
|
||||
}
|
||||
ctx.Redirect(redir)
|
||||
}
|
||||
|
||||
// AdminPostDeletePost 软删进回收站
|
||||
func (d Deps) AdminPostDeletePost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
id, redir, ok := d.parseAdminPostID(c)
|
||||
if !ok {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
if !d.requireAdminPostCSRF(ctx, redir) {
|
||||
return
|
||||
}
|
||||
if err := d.Post.Delete(ctx.UserID(), id, true); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect(redir)
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("帖子已移入回收站")
|
||||
ctx.Redirect("/admin/trash")
|
||||
}
|
||||
|
||||
type adminTrashRow struct {
|
||||
ID uint
|
||||
Title string
|
||||
AuthorName string
|
||||
BoardName string
|
||||
DeletedLabel string
|
||||
}
|
||||
|
||||
type adminTrashData struct {
|
||||
AdminChrome
|
||||
Items []adminTrashRow
|
||||
Page int
|
||||
HasPrev bool
|
||||
HasMore bool
|
||||
PrevPage int
|
||||
NextPage int
|
||||
Keyword string
|
||||
}
|
||||
|
||||
// AdminTrashGet 回收站
|
||||
func (d Deps) AdminTrashGet(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
d.renderAdminTrash(ctx, "")
|
||||
}
|
||||
|
||||
func (d Deps) renderAdminTrash(ctx *webctx.Context, errMsg string) {
|
||||
chrome := d.adminChrome(ctx, "回收站", "trash")
|
||||
chrome.Error = errMsg
|
||||
page, _ := strconv.Atoi(ctx.C.DefaultQuery("page", "1"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
keyword := strings.TrimSpace(ctx.C.Query("keyword"))
|
||||
size := d.Settings.PageSizeDefault()
|
||||
data := adminTrashData{
|
||||
AdminChrome: chrome,
|
||||
Page: page,
|
||||
PrevPage: page - 1,
|
||||
NextPage: page + 1,
|
||||
HasPrev: page > 1,
|
||||
Keyword: keyword,
|
||||
}
|
||||
list, total, err := d.Post.ListTrash(page, size, keyword)
|
||||
if err != nil {
|
||||
data.Error = err.Error()
|
||||
ctx.HTML(http.StatusOK, "admin/trash", data)
|
||||
return
|
||||
}
|
||||
data.HasMore = int64(page*size) < total
|
||||
data.Items = make([]adminTrashRow, 0, len(list))
|
||||
for _, it := range list {
|
||||
author := strings.TrimSpace(it.User.Nickname)
|
||||
if author == "" {
|
||||
author = it.User.Username
|
||||
}
|
||||
bname := ""
|
||||
if it.Board.ID > 0 {
|
||||
bname = it.Board.Name
|
||||
}
|
||||
data.Items = append(data.Items, adminTrashRow{
|
||||
ID: it.ID, Title: it.Title, AuthorName: author, BoardName: bname,
|
||||
DeletedLabel: it.DeletedAt.Local().Format("2006-01-02 15:04"),
|
||||
})
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "admin/trash", data)
|
||||
}
|
||||
|
||||
// AdminTrashRestorePost 恢复
|
||||
func (d Deps) AdminTrashRestorePost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
ctx.SetFlash("无效请求,请重试")
|
||||
ctx.Redirect("/admin/trash")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := d.Post.Restore(uint(id)); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect("/admin/trash")
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("已恢复")
|
||||
ctx.Redirect(fmt.Sprintf("/post/%d", id))
|
||||
}
|
||||
|
||||
// AdminTrashPurgePost 彻底删除
|
||||
func (d Deps) AdminTrashPurgePost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
ctx.SetFlash("无效请求,请重试")
|
||||
ctx.Redirect("/admin/trash")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := d.Post.Purge(uint(id)); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect("/admin/trash")
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("已彻底删除")
|
||||
ctx.Redirect("/admin/trash")
|
||||
}
|
||||
@@ -29,6 +29,12 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
g.POST("/post/:id/unlock", authMW.RequireAuth(), deps.PostUnlock)
|
||||
g.POST("/post/:id/report", authMW.RequireAuth(), deps.PostReportPost)
|
||||
g.POST("/post/:id/comments/:cid/report", authMW.RequireAuth(), deps.CommentReportPost)
|
||||
g.POST("/post/:id/admin/pin", authMW.RequireAuth(), authMW.RequireAdmin(), deps.AdminPostPinPost)
|
||||
g.POST("/post/:id/admin/board-pin", authMW.RequireAuth(), authMW.RequireAdmin(), deps.AdminPostBoardPinPost)
|
||||
g.POST("/post/:id/admin/feature", authMW.RequireAuth(), authMW.RequireAdmin(), deps.AdminPostFeaturePost)
|
||||
g.POST("/post/:id/admin/edit-lock", authMW.RequireAuth(), authMW.RequireAdmin(), deps.AdminPostEditLockPost)
|
||||
g.POST("/post/:id/admin/comments-lock", authMW.RequireAuth(), authMW.RequireAdmin(), deps.AdminPostCommentsLockPost)
|
||||
g.POST("/post/:id/admin/delete", authMW.RequireAuth(), authMW.RequireAdmin(), deps.AdminPostDeletePost)
|
||||
g.GET("/login", deps.LoginGet)
|
||||
g.POST("/login", deps.LoginPost)
|
||||
g.POST("/logout", deps.LogoutPost)
|
||||
@@ -76,6 +82,9 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
admin.POST("/pages/:id/publish", deps.AdminPagePublishPost)
|
||||
admin.GET("/reports", deps.AdminReportsGet)
|
||||
admin.POST("/reports/:id/handle", deps.AdminReportHandlePost)
|
||||
admin.GET("/trash", deps.AdminTrashGet)
|
||||
admin.POST("/trash/:id/restore", deps.AdminTrashRestorePost)
|
||||
admin.POST("/trash/:id/purge", deps.AdminTrashPurgePost)
|
||||
}
|
||||
|
||||
g.GET("/user/:id", deps.UserPublic)
|
||||
|
||||
@@ -23,8 +23,11 @@ type PostPageData struct {
|
||||
AuthorID uint
|
||||
BoardID uint
|
||||
BoardName string
|
||||
Pinned bool
|
||||
Pinned bool // 展示用:全局或版内置顶
|
||||
GlobalPinned bool
|
||||
BoardPinned bool
|
||||
Featured bool
|
||||
EditLocked bool
|
||||
PostTypeLabel string
|
||||
CreatedLabel string
|
||||
ViewCount int
|
||||
@@ -37,6 +40,7 @@ type PostPageData struct {
|
||||
CommentsLocked bool
|
||||
CanEdit bool
|
||||
CanReportPost bool
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
// CommentView 评论
|
||||
@@ -126,7 +130,8 @@ func (d Deps) PostView(c *gin.Context) {
|
||||
PostPath: url.QueryEscape(fmt.Sprintf("/post/%d", post.ID)),
|
||||
PostTitle: post.Title, AuthorName: author, AuthorID: post.UserID,
|
||||
BoardID: post.BoardID, BoardName: boardName,
|
||||
Pinned: post.Pinned || post.BoardPinned, Featured: post.Featured,
|
||||
Pinned: post.Pinned || post.BoardPinned, GlobalPinned: post.Pinned, BoardPinned: post.BoardPinned,
|
||||
Featured: post.Featured, EditLocked: post.EditLocked,
|
||||
PostTypeLabel: postTypeLabel(post.PostType), CreatedLabel: formatTime(post.CreatedAt),
|
||||
ViewCount: post.ViewCount, LikeCount: post.LikeCount,
|
||||
Liked: d.Post.IsLiked(ctx.UserID(), post.ID), Favorited: d.Post.IsFavorited(ctx.UserID(), post.ID),
|
||||
@@ -134,6 +139,7 @@ func (d Deps) PostView(c *gin.Context) {
|
||||
CommentsLocked: post.CommentsLocked,
|
||||
CanEdit: d.Post.CanUserEdit(post, ctx.UserID(), ctx.IsAdmin()),
|
||||
CanReportPost: ctx.IsSigned() && post.UserID != ctx.UserID(),
|
||||
IsAdmin: ctx.IsAdmin(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<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/reports"{{if eq .NavActive "reports"}} class="is-active"{{end}}>举报</a>
|
||||
<a href="/admin/trash"{{if eq .NavActive "trash"}} class="is-active"{{end}}>回收站</a>
|
||||
<a href="/admin/friend-links"{{if eq .NavActive "friend-links"}} class="is-active"{{end}}>友链</a>
|
||||
<a href="/admin/pages"{{if eq .NavActive "pages"}} class="is-active"{{end}}>单页</a>
|
||||
<a href="/admin/settings"{{if eq .NavActive "settings"}} class="is-active"{{end}}>设置</a>
|
||||
|
||||
52
templates/admin/trash.tmpl
Normal file
52
templates/admin/trash.tmpl
Normal file
@@ -0,0 +1,52 @@
|
||||
{{define "admin/trash"}}
|
||||
{{template "base/head" .}}
|
||||
{{template "base/navbar" .}}
|
||||
<main class="j13-main j13-admin">
|
||||
<h1>回收站</h1>
|
||||
{{template "admin/nav" .}}
|
||||
{{template "base/alert" .}}
|
||||
|
||||
<form method="get" action="/admin/trash" class="j13-form j13-admin-form j13-inline-form">
|
||||
<label>关键词 <input name="keyword" value="{{.Keyword}}" placeholder="标题"/></label>
|
||||
<button type="submit">搜索</button>
|
||||
</form>
|
||||
|
||||
{{if .Items}}
|
||||
<table class="j13-admin-table">
|
||||
<thead>
|
||||
<tr><th>标题</th><th>作者</th><th>板块</th><th>删除时间</th><th>操作</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Items}}
|
||||
<tr>
|
||||
<td>{{.Title}}</td>
|
||||
<td>{{.AuthorName}}</td>
|
||||
<td>{{if .BoardName}}{{.BoardName}}{{else}}—{{end}}</td>
|
||||
<td>{{.DeletedLabel}}</td>
|
||||
<td>
|
||||
<form method="post" action="/admin/trash/{{.ID}}/restore" class="j13-inline-form">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
|
||||
<button type="submit" class="j13-linkbtn">恢复</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/trash/{{.ID}}/purge" class="j13-inline-form" onsubmit="return confirm('确认彻底删除?不可恢复');">
|
||||
<input type="hidden" name="_csrf" value="{{$.CSRF}}"/>
|
||||
<button type="submit" class="j13-linkbtn">彻底删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{if or .HasPrev .HasMore}}
|
||||
<nav class="j13-pager">
|
||||
{{if .HasPrev}}<a href="/admin/trash?page={{.PrevPage}}{{if $.Keyword}}&keyword={{queryEscape $.Keyword}}{{end}}">上一页</a>{{end}}
|
||||
<span>第 {{.Page}} 页</span>
|
||||
{{if .HasMore}}<a href="/admin/trash?page={{.NextPage}}{{if $.Keyword}}&keyword={{queryEscape $.Keyword}}{{end}}">下一页</a>{{end}}
|
||||
</nav>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p class="j13-empty">回收站为空。</p>
|
||||
{{end}}
|
||||
</main>
|
||||
{{template "base/footer" .}}
|
||||
{{end}}
|
||||
@@ -5,6 +5,8 @@
|
||||
<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 .EditLocked}}<span class="j13-tag">禁编</span>{{end}}
|
||||
{{if .CommentsLocked}}<span class="j13-tag">已结贴</span>{{end}}
|
||||
{{if .PostTypeLabel}}<span class="j13-tag">{{.PostTypeLabel}}</span>{{end}}
|
||||
{{if .BoardName}}<a class="j13-tag" href="/board/{{.BoardID}}">{{.BoardName}}</a>{{end}}
|
||||
{{if .AuthorID}}<a href="/user/{{.AuthorID}}">{{.AuthorName}}</a>{{else}}<span>{{.AuthorName}}</span>{{end}}
|
||||
@@ -46,6 +48,39 @@
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .IsAdmin}}
|
||||
<div class="j13-post__admin" role="group" aria-label="管理操作">
|
||||
<form method="post" action="/post/{{.PostID}}/admin/pin" class="j13-inline-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="enable" value="{{if .GlobalPinned}}0{{else}}1{{end}}"/>
|
||||
<button type="submit" class="j13-linkbtn">{{if .GlobalPinned}}取消全局置顶{{else}}全局置顶{{end}}</button>
|
||||
</form>
|
||||
<form method="post" action="/post/{{.PostID}}/admin/board-pin" class="j13-inline-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="enable" value="{{if .BoardPinned}}0{{else}}1{{end}}"/>
|
||||
<button type="submit" class="j13-linkbtn">{{if .BoardPinned}}取消版内置顶{{else}}版内置顶{{end}}</button>
|
||||
</form>
|
||||
<form method="post" action="/post/{{.PostID}}/admin/feature" class="j13-inline-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="enable" value="{{if .Featured}}0{{else}}1{{end}}"/>
|
||||
<button type="submit" class="j13-linkbtn">{{if .Featured}}取消精华{{else}}设为精华{{end}}</button>
|
||||
</form>
|
||||
<form method="post" action="/post/{{.PostID}}/admin/edit-lock" class="j13-inline-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="enable" value="{{if .EditLocked}}0{{else}}1{{end}}"/>
|
||||
<button type="submit" class="j13-linkbtn">{{if .EditLocked}}允许编辑{{else}}禁止编辑{{end}}</button>
|
||||
</form>
|
||||
<form method="post" action="/post/{{.PostID}}/admin/comments-lock" class="j13-inline-form">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<input type="hidden" name="enable" value="{{if .CommentsLocked}}0{{else}}1{{end}}"/>
|
||||
<button type="submit" class="j13-linkbtn">{{if .CommentsLocked}}解锁评论{{else}}锁定评论{{end}}</button>
|
||||
</form>
|
||||
<form method="post" action="/post/{{.PostID}}/admin/delete" class="j13-inline-form" onsubmit="return confirm('确认移入回收站?');">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
|
||||
<button type="submit" class="j13-linkbtn">移入回收站</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</header>
|
||||
<div class="j13-post__content post-detail-content">{{safeHTML .BodyHTML}}</div>
|
||||
</article>
|
||||
|
||||
@@ -890,9 +890,13 @@ points-only[data-locked="false"] {
|
||||
.j13-admin-filters a { color: var(--j13-muted); text-decoration: none; }
|
||||
.j13-admin-filters a.is-active,
|
||||
.j13-admin-filters a:hover { color: var(--j13-accent); font-weight: 600; }
|
||||
.j13-report-handle .j13-form__row {
|
||||
.j13-post__admin {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
gap: 0.35rem 0.65rem;
|
||||
margin-top: 0.65rem;
|
||||
padding-top: 0.55rem;
|
||||
border-top: 1px dashed var(--j13-border);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user