feat: 帖/评待审与被拒状态对作者可见

详情页横幅与楼层标签,避免作者误以为内容未保存。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 22:43:20 +08:00
parent f10ba99012
commit 7f21ffb14e
6 changed files with 88 additions and 9 deletions

View File

@@ -63,7 +63,7 @@
- [x] 点赞切换;收藏切换;收藏列表 — SSR `/favorites` - [x] 点赞切换;收藏切换;收藏列表 — SSR `/favorites`
- [x] 浏览量 — 详情页计数 - [x] 浏览量 — 详情页计数
- [x] 举报帖子 — SSR 表单 + `/admin/reports` - [x] 举报帖子 — SSR 表单 + `/admin/reports`
- [ ] 内容审核状态展示(作者可见待审/被拒) - [x] 内容审核状态展示(作者可见待审/被拒)— SSR 帖详情横幅 + 评论楼层标签
### D.1 帖子类型 ### D.1 帖子类型
@@ -107,7 +107,7 @@
- [x] 评论举报 — SSR - [x] 评论举报 — SSR
- [x] @ 提及 → 通知 — SSR 发评/审通调用 `NotifyCommentMentions` - [x] @ 提及 → 通知 — SSR 发评/审通调用 `NotifyCommentMentions`
- [x] 回复提醒(站内信 + 可选邮件)— SSR 发评/审通调用 `NotifyCommentPublished` - [x] 回复提醒(站内信 + 可选邮件)— SSR 发评/审通调用 `NotifyCommentPublished`
- [ ] 审核中 / 被拒评论可见性规则 - [x] 审核中 / 被拒评论可见性规则 — 服务层已有SSR 楼层标签(作者/管理员)
- [x] 管理员:通过 / 拒绝待审评论 — SSR `/admin/moderation`(回收站/修订未迁) - [x] 管理员:通过 / 拒绝待审评论 — SSR `/admin/moderation`(回收站/修订未迁)
--- ---

View File

@@ -85,7 +85,7 @@
**Feed**:排序条(最新发帖 / 最新回复 / 热门)+ 搜索面板(关键词、标签、作者、仅标题;已迁 SSR GET+ 列表项(标题、作者、板块徽章、回复数、置顶/精华)。虚拟滚动 / `feed_list_style` 未迁。 **Feed**:排序条(最新发帖 / 最新回复 / 热门)+ 搜索面板(关键词、标签、作者、仅标题;已迁 SSR GET+ 列表项(标题、作者、板块徽章、回复数、置顶/精华)。虚拟滚动 / `feed_list_style` 未迁。
**帖子详情**:标题区操作(赞、藏、举报、编辑、管理操作)→ 特殊组件(投票卡 / 悬赏条 / 抽奖卡)→ 正文 `PostContent`(门控块 UI→ 文章目录 → 作者卡片 → 修订入口 → 评论线程 + 评论框。 **帖子详情**:标题区操作(赞、藏、举报、编辑、管理操作)→ 待审/被拒横幅(作者/管理员)→ 特殊组件(投票卡 / 悬赏条 / 抽奖卡)→ 正文 `PostContent`(门控块 UI→ 文章目录 → 作者卡片 → 修订入口 → 评论线程 + 评论框。
### 2.3 右栏 RightPanel ### 2.3 右栏 RightPanel

View File

@@ -298,6 +298,25 @@ body.j13-body {
font-size: 0.75rem; font-size: 0.75rem;
} }
.j13-tag--pin { background: #fef3c7; color: #92400e; } .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-tag--feat { background: #ede9fe; color: #5b21b6; }
.j13-empty { color: var(--j13-muted); } .j13-empty { color: var(--j13-muted); }

View File

@@ -43,6 +43,9 @@ type PostPageData struct {
CanViewRevisions bool CanViewRevisions bool
CanReportPost bool CanReportPost bool
IsAdmin bool IsAdmin bool
Status string
StatusLabel string
ShowModerationBanner bool
} }
// CommentView 评论 // CommentView 评论
@@ -63,6 +66,8 @@ type CommentView struct {
CanReport bool CanReport bool
CanEdit bool CanEdit bool
CanDelete bool CanDelete bool
Status string
StatusLabel string
} }
// PostView GET /post/:id // 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()), CanReport: ctx.IsSigned() && (cm.UserID == 0 || cm.UserID != ctx.UserID()),
CanEdit: !cm.ContentHidden && d.Comment.CanUserEditComment(&cm, ctx.UserID(), ctx.IsAdmin()), CanEdit: !cm.ContentHidden && d.Comment.CanUserEditComment(&cm, ctx.UserID(), ctx.IsAdmin()),
CanDelete: !cm.ContentHidden && d.Comment.CanUserDeleteComment(&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 { if cm.ReplyTarget != nil {
view.ReplyToID = cm.ReplyTarget.ID 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 := d.chrome(ctx, post.Title+" · "+d.Settings.SiteBranding().Name, "", "post/body")
chrome.ActiveBoard = post.BoardID 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{ ctx.HTML(http.StatusOK, "post", PostPageData{
PageChrome: chrome, PostID: post.ID, PageChrome: chrome, PostID: post.ID,
PostPath: url.QueryEscape(fmt.Sprintf("/post/%d", post.ID)), PostPath: url.QueryEscape(fmt.Sprintf("/post/%d", post.ID)),
@@ -147,9 +167,23 @@ func (d Deps) PostView(c *gin.Context) {
CanViewRevisions: canViewPostRevisions(post, ctx.UserID(), ctx.IsAdmin()), CanViewRevisions: canViewPostRevisions(post, ctx.UserID(), ctx.IsAdmin()),
CanReportPost: ctx.IsSigned() && post.UserID != ctx.UserID(), CanReportPost: ctx.IsSigned() && post.UserID != ctx.UserID(),
IsAdmin: ctx.IsAdmin(), 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 { func postTypeLabel(t string) string {
switch t { switch t {
case models.PostTypeQuestion: case models.PostTypeQuestion:

View File

@@ -2,7 +2,13 @@
<article class="j13-post" data-post-id="{{.PostID}}" data-csrf="{{.CSRF}}" data-logged-in="{{if .LoggedIn}}1{{else}}0{{end}}"> <article class="j13-post" data-post-id="{{.PostID}}" data-csrf="{{.CSRF}}" data-logged-in="{{if .LoggedIn}}1{{else}}0{{end}}">
<header class="j13-post__header"> <header class="j13-post__header">
<h1 class="j13-post__title">{{.PostTitle}}</h1> <h1 class="j13-post__title">{{.PostTitle}}</h1>
{{if .ShowModerationBanner}}
<p class="j13-mod-banner j13-mod-banner--{{.Status}}" role="status">
{{if eq .Status "pending"}}本帖正在审核中,仅你与管理员可见。{{else if eq .Status "rejected"}}本帖未通过审核,仅你与管理员可见。{{else}}{{.StatusLabel}}{{end}}
</p>
{{end}}
<div class="j13-post__meta"> <div class="j13-post__meta">
{{if .StatusLabel}}<span class="j13-tag j13-tag--{{.Status}}">{{.StatusLabel}}</span>{{end}}
{{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}} {{if .Pinned}}<span class="j13-tag j13-tag--pin">置顶</span>{{end}}
{{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}} {{if .Featured}}<span class="j13-tag j13-tag--feat">精华</span>{{end}}
{{if .EditLocked}}<span class="j13-tag">禁编</span>{{end}} {{if .EditLocked}}<span class="j13-tag">禁编</span>{{end}}
@@ -98,6 +104,7 @@
<span class="j13-comment__floor">#{{.Floor}}</span> <span class="j13-comment__floor">#{{.Floor}}</span>
{{if .AuthorID}}<a href="/user/{{.AuthorID}}">{{.AuthorName}}</a>{{else}}<span>{{.AuthorName}}</span>{{end}} {{if .AuthorID}}<a href="/user/{{.AuthorID}}">{{.AuthorName}}</a>{{else}}<span>{{.AuthorName}}</span>{{end}}
<span>{{.CreatedLabel}}</span> <span>{{.CreatedLabel}}</span>
{{if .StatusLabel}}<span class="j13-tag j13-tag--{{.Status}}">{{.StatusLabel}}</span>{{end}}
{{if .IsPrivate}}<span class="j13-tag">私密</span>{{end}} {{if .IsPrivate}}<span class="j13-tag">私密</span>{{end}}
{{if .ReplyToFloor}} {{if .ReplyToFloor}}
<span class="j13-comment__reply-to">回复 <a href="#floor-{{.ReplyToFloor}}">#{{.ReplyToFloor}}</a>{{if .ReplyToAuthor}} {{.ReplyToAuthor}}{{end}}</span> <span class="j13-comment__reply-to">回复 <a href="#floor-{{.ReplyToFloor}}">#{{.ReplyToFloor}}</a>{{if .ReplyToAuthor}} {{.ReplyToAuthor}}{{end}}</span>

View File

@@ -298,6 +298,25 @@ body.j13-body {
font-size: 0.75rem; font-size: 0.75rem;
} }
.j13-tag--pin { background: #fef3c7; color: #92400e; } .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-tag--feat { background: #ede9fe; color: #5b21b6; }
.j13-empty { color: var(--j13-muted); } .j13-empty { color: var(--j13-muted); }