diff --git a/docs/rebuild-spec/02-features.md b/docs/rebuild-spec/02-features.md index 0be4f3f..c450715 100644 --- a/docs/rebuild-spec/02-features.md +++ b/docs/rebuild-spec/02-features.md @@ -63,7 +63,7 @@ - [x] 点赞切换;收藏切换;收藏列表 — SSR `/favorites` - [x] 浏览量 — 详情页计数 - [x] 举报帖子 — SSR 表单 + `/admin/reports` -- [ ] 内容审核状态展示(作者可见待审/被拒) +- [x] 内容审核状态展示(作者可见待审/被拒)— SSR 帖详情横幅 + 评论楼层标签 ### D.1 帖子类型 @@ -107,7 +107,7 @@ - [x] 评论举报 — SSR - [x] @ 提及 → 通知 — SSR 发评/审通调用 `NotifyCommentMentions` - [x] 回复提醒(站内信 + 可选邮件)— SSR 发评/审通调用 `NotifyCommentPublished` -- [ ] 审核中 / 被拒评论可见性规则 +- [x] 审核中 / 被拒评论可见性规则 — 服务层已有;SSR 楼层标签(作者/管理员) - [x] 管理员:通过 / 拒绝待审评论 — SSR `/admin/moderation`(回收站/修订未迁) --- diff --git a/docs/rebuild-spec/06-pages-ux.md b/docs/rebuild-spec/06-pages-ux.md index a07b832..2bceb90 100644 --- a/docs/rebuild-spec/06-pages-ux.md +++ b/docs/rebuild-spec/06-pages-ux.md @@ -85,7 +85,7 @@ **Feed**:排序条(最新发帖 / 最新回复 / 热门)+ 搜索面板(关键词、标签、作者、仅标题;已迁 SSR GET)+ 列表项(标题、作者、板块徽章、回复数、置顶/精华)。虚拟滚动 / `feed_list_style` 未迁。 -**帖子详情**:标题区操作(赞、藏、举报、编辑、管理操作)→ 特殊组件(投票卡 / 悬赏条 / 抽奖卡)→ 正文 `PostContent`(门控块 UI)→ 文章目录 → 作者卡片 → 修订入口 → 评论线程 + 评论框。 +**帖子详情**:标题区操作(赞、藏、举报、编辑、管理操作)→ 待审/被拒横幅(作者/管理员)→ 特殊组件(投票卡 / 悬赏条 / 抽奖卡)→ 正文 `PostContent`(门控块 UI)→ 文章目录 → 作者卡片 → 修订入口 → 评论线程 + 评论框。 ### 2.3 右栏 RightPanel diff --git a/public/assets/site.css b/public/assets/site.css index 73441e2..aab75f7 100644 --- a/public/assets/site.css +++ b/public/assets/site.css @@ -298,6 +298,25 @@ body.j13-body { font-size: 0.75rem; } .j13-tag--pin { background: #fef3c7; color: #92400e; } +.j13-tag--pending { background: #fff7ed; color: #9a3412; } +.j13-tag--rejected { background: #fef2f2; color: #991b1b; } +.j13-mod-banner { + margin: 0.5rem 0 0.75rem; + padding: 0.65rem 0.85rem; + border-radius: var(--j13-radius); + font-size: 0.9rem; + line-height: 1.4; +} +.j13-mod-banner--pending { + background: #fff7ed; + color: #9a3412; + border: 1px solid #fed7aa; +} +.j13-mod-banner--rejected { + background: #fef2f2; + color: #991b1b; + border: 1px solid #fecaca; +} .j13-tag--feat { background: #ede9fe; color: #5b21b6; } .j13-empty { color: var(--j13-muted); } diff --git a/routers/web/post.go b/routers/web/post.go index 603d94a..7fa04dc 100644 --- a/routers/web/post.go +++ b/routers/web/post.go @@ -39,10 +39,13 @@ type PostPageData struct { CommentCount int Comments []CommentView CommentsLocked bool - CanEdit bool + CanEdit bool CanViewRevisions bool - CanReportPost bool - IsAdmin bool + CanReportPost bool + IsAdmin bool + Status string + StatusLabel string + ShowModerationBanner bool } // CommentView 评论 @@ -63,6 +66,8 @@ type CommentView struct { CanReport bool CanEdit bool CanDelete bool + Status string + StatusLabel string } // PostView GET /post/:id @@ -101,6 +106,12 @@ func (d Deps) PostView(c *gin.Context) { CanReport: ctx.IsSigned() && (cm.UserID == 0 || cm.UserID != ctx.UserID()), CanEdit: !cm.ContentHidden && d.Comment.CanUserEditComment(&cm, ctx.UserID(), ctx.IsAdmin()), CanDelete: !cm.ContentHidden && d.Comment.CanUserDeleteComment(&cm, ctx.UserID(), ctx.IsAdmin()), + Status: cm.Status, + } + if cm.Status == models.ContentStatusPending || cm.Status == models.ContentStatusRejected { + if ctx.IsAdmin() || (cm.UserID > 0 && cm.UserID == ctx.UserID()) { + view.StatusLabel = contentStatusLabel(cm.Status) + } } if cm.ReplyTarget != nil { view.ReplyToID = cm.ReplyTarget.ID @@ -131,6 +142,15 @@ func (d Deps) PostView(c *gin.Context) { chrome := d.chrome(ctx, post.Title+" · "+d.Settings.SiteBranding().Name, "", "post/body") chrome.ActiveBoard = post.BoardID + statusLabel := "" + showBanner := false + if post.Status == models.ContentStatusPending || post.Status == models.ContentStatusRejected { + if ctx.IsAdmin() || post.UserID == ctx.UserID() { + statusLabel = contentStatusLabel(post.Status) + showBanner = true + } + } + ctx.HTML(http.StatusOK, "post", PostPageData{ PageChrome: chrome, PostID: post.ID, PostPath: url.QueryEscape(fmt.Sprintf("/post/%d", post.ID)), @@ -143,13 +163,27 @@ func (d Deps) PostView(c *gin.Context) { Liked: d.Post.IsLiked(ctx.UserID(), post.ID), Favorited: d.Post.IsFavorited(ctx.UserID(), post.ID), BodyHTML: body, CommentCount: len(cv), Comments: cv, CommentsLocked: post.CommentsLocked, - CanEdit: d.Post.CanUserEdit(post, ctx.UserID(), ctx.IsAdmin()), + CanEdit: d.Post.CanUserEdit(post, ctx.UserID(), ctx.IsAdmin()), CanViewRevisions: canViewPostRevisions(post, ctx.UserID(), ctx.IsAdmin()), - CanReportPost: ctx.IsSigned() && post.UserID != ctx.UserID(), - IsAdmin: ctx.IsAdmin(), + CanReportPost: ctx.IsSigned() && post.UserID != ctx.UserID(), + IsAdmin: ctx.IsAdmin(), + Status: post.Status, + StatusLabel: statusLabel, + ShowModerationBanner: showBanner, }) } +func contentStatusLabel(status string) string { + switch status { + case models.ContentStatusPending: + return "审核中" + case models.ContentStatusRejected: + return "已拒绝" + default: + return "" + } +} + func postTypeLabel(t string) string { switch t { case models.PostTypeQuestion: diff --git a/templates/post/body.tmpl b/templates/post/body.tmpl index df9bb9d..c7ea1a3 100644 --- a/templates/post/body.tmpl +++ b/templates/post/body.tmpl @@ -2,7 +2,13 @@

{{.PostTitle}}

+ {{if .ShowModerationBanner}} +

+ {{if eq .Status "pending"}}本帖正在审核中,仅你与管理员可见。{{else if eq .Status "rejected"}}本帖未通过审核,仅你与管理员可见。{{else}}{{.StatusLabel}}{{end}} +

+ {{end}}