feat: 已发布帖子编辑后进修订审核,通过前保持展示旧内容
- Post 模型新增 PendingTitle/PendingBody 字段,分离已发布内容与待审修订 - Update: approved 帖子编辑写入 Pending 字段,不覆盖正文,不改变发布状态 - Approve: 有 PendingBody 时复制到正式字段后清空,无 PendingBody 走首次审核 - Reject: 有 PendingBody 时仅清空待审修订(保持已发布),无 PendingBody 走普通退回 - 新增 ListPendingRevisions 查询,管理后台独立展示"修订待审核"区域 - 详情页展示"修订审核中"标识,编辑页加载待审内容 - 通过修订后直接覆盖,不保留历史版本
This commit is contained in:
@ -47,6 +47,9 @@ func (ctrl *AdminPostController) PostsPage(c *gin.Context) {
|
||||
nextPage = totalPages
|
||||
}
|
||||
|
||||
// 查询待审修订(approved 帖子有 PendingBody 的)
|
||||
revisions, _, _ := ctrl.postService.ListPendingRevisions(keyword, 1, 100)
|
||||
|
||||
c.HTML(http.StatusOK, "admin/posts/index.html", common.BuildAdminPageData(c, gin.H{
|
||||
"Title": "内容管理",
|
||||
"Posts": posts,
|
||||
@ -58,6 +61,7 @@ func (ctrl *AdminPostController) PostsPage(c *gin.Context) {
|
||||
"Keyword": keyword,
|
||||
"Status": status,
|
||||
"StatusNames": common.PostStatusDisplayNames,
|
||||
"Revisions": revisions,
|
||||
"ExtraCSS": "/admin/static/css/posts.css",
|
||||
}))
|
||||
}
|
||||
|
||||
@ -33,9 +33,10 @@ type auditStatusProvider interface {
|
||||
FindByID(id uint) (*model.User, error)
|
||||
}
|
||||
|
||||
// adminPostUseCase AdminPostController 对 PostService 的最小依赖(ISP:6 个方法)
|
||||
// adminPostUseCase AdminPostController 对 PostService 的最小依赖(ISP:7 个方法)
|
||||
type adminPostUseCase interface {
|
||||
ListAdmin(keyword, status string, page, pageSize int) ([]model.Post, int64, error)
|
||||
ListPendingRevisions(keyword string, page, pageSize int) ([]model.Post, int64, error)
|
||||
Approve(postID uint) error
|
||||
Reject(postID uint, reason string) error
|
||||
Lock(postID uint) error
|
||||
|
||||
@ -154,6 +154,12 @@ func (ctrl *PostController) EditPage(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 已通过帖子有待审修订时,编辑页展示待审内容
|
||||
if post.PendingBody != "" {
|
||||
post.Title = post.PendingTitle
|
||||
post.Body = post.PendingBody
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "posts/new.html", common.BuildPageData(c, gin.H{
|
||||
"Title": "编辑帖子",
|
||||
"Post": post,
|
||||
|
||||
@ -15,6 +15,8 @@ type Post struct {
|
||||
UserID uint `gorm:"index;not null" json:"user_id"`
|
||||
Status string `gorm:"type:varchar(20);index;default:draft;not null" json:"status"`
|
||||
IsLocked bool `gorm:"default:false" json:"is_locked"`
|
||||
PendingTitle string `gorm:"type:varchar(200);default:''" json:"pending_title,omitempty"`
|
||||
PendingBody string `gorm:"type:text" json:"pending_body,omitempty"`
|
||||
RejectReason string `gorm:"type:varchar(500);default:''" json:"reject_reason,omitempty"`
|
||||
AllowComment bool `gorm:"default:true" json:"allow_comment"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
@ -88,6 +88,13 @@ func (r *PostRepo) FindByUserIDAndStatus(userID uint, status string, offset, lim
|
||||
return r.pageResults(query, offset, limit)
|
||||
}
|
||||
|
||||
// FindPendingRevisions 查询有待审修订的帖子(approved 且 pending_body 不为空)
|
||||
func (r *PostRepo) FindPendingRevisions(keyword string, offset, limit int) ([]model.Post, int64, error) {
|
||||
query := r.buildQuery(keyword, model.PostStatusApproved).
|
||||
Where("posts.pending_body != ''")
|
||||
return r.pageResults(query, offset, limit)
|
||||
}
|
||||
|
||||
// pageResults 执行 Count + Offset/Limit + ORDER BY
|
||||
func (r *PostRepo) pageResults(query *gorm.DB, offset, limit int) ([]model.Post, int64, error) {
|
||||
var total int64
|
||||
|
||||
@ -217,11 +217,19 @@ func (s *PostService) Update(postID uint, title, body string) error {
|
||||
return common.ErrPostCannotEdit
|
||||
}
|
||||
|
||||
// 已通过帖子:编辑内容保存为待审修订,不影响当前展示
|
||||
if post.Status == model.PostStatusApproved {
|
||||
post.PendingTitle = title
|
||||
post.PendingBody = body
|
||||
post.RejectReason = "" // 清空旧退回理由
|
||||
return s.repo.Update(post)
|
||||
}
|
||||
|
||||
// 草稿/已退回:直接修改原文(现有逻辑)
|
||||
post.Title = title
|
||||
post.Body = body
|
||||
post.Excerpt = generateExcerpt(body)
|
||||
|
||||
// rejected 状态编辑后自动重置为 draft
|
||||
if post.Status == model.PostStatusRejected {
|
||||
post.Status = model.PostStatusDraft
|
||||
post.RejectReason = ""
|
||||
@ -248,12 +256,26 @@ func (s *PostService) SubmitForAudit(postID uint) error {
|
||||
return s.repo.Update(post)
|
||||
}
|
||||
|
||||
// Approve 审核通过:pending → approved
|
||||
// Approve 审核通过:pending → approved;或修订审核通过复制 Pending 到正式字段
|
||||
func (s *PostService) Approve(postID uint) error {
|
||||
post, err := s.findPost(postID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 修订审核:将待审内容覆盖到正式字段
|
||||
if post.PendingBody != "" {
|
||||
post.Title = post.PendingTitle
|
||||
post.Body = post.PendingBody
|
||||
post.Excerpt = generateExcerpt(post.PendingBody)
|
||||
post.PendingTitle = ""
|
||||
post.PendingBody = ""
|
||||
post.Status = model.PostStatusApproved
|
||||
post.RejectReason = ""
|
||||
return s.repo.Update(post)
|
||||
}
|
||||
|
||||
// 首次审核通过
|
||||
if post.Status != model.PostStatusPending {
|
||||
return common.ErrPostCannotApprove
|
||||
}
|
||||
@ -262,8 +284,7 @@ func (s *PostService) Approve(postID uint) error {
|
||||
return s.repo.Update(post)
|
||||
}
|
||||
|
||||
// Reject 退回:pending/approved → rejected
|
||||
// 独立于锁定:可单独退回(允许作者修改后重审),也可锁定+退回(禁止再投稿)
|
||||
// Reject 退回:pending/approved → rejected;修订退回则清空 Pending 保持 approved
|
||||
func (s *PostService) Reject(postID uint, reason string) error {
|
||||
post, err := s.findPost(postID)
|
||||
if err != nil {
|
||||
@ -272,6 +293,16 @@ func (s *PostService) Reject(postID uint, reason string) error {
|
||||
if post.Status != model.PostStatusPending && post.Status != model.PostStatusApproved {
|
||||
return common.ErrPostCannotReject
|
||||
}
|
||||
|
||||
// 修订退回:清空待审修订,保持已发布内容不变
|
||||
if post.PendingBody != "" {
|
||||
post.PendingTitle = ""
|
||||
post.PendingBody = ""
|
||||
post.RejectReason = reason
|
||||
return s.repo.Update(post)
|
||||
}
|
||||
|
||||
// 普通退回(首次审核)
|
||||
post.Status = model.PostStatusRejected
|
||||
post.RejectReason = reason
|
||||
return s.repo.Update(post)
|
||||
@ -316,6 +347,13 @@ func (s *PostService) Restore(postID uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListPendingRevisions 查询有待审修订的帖子(approved 且 PendingBody 不为空)
|
||||
func (s *PostService) ListPendingRevisions(keyword string, page, pageSize int) ([]model.Post, int64, error) {
|
||||
return s.listPageable(page, pageSize, func(offset, limit int) ([]model.Post, int64, error) {
|
||||
return s.repo.FindPendingRevisions(keyword, offset, limit)
|
||||
})
|
||||
}
|
||||
|
||||
// IsPostAccessible 检查用户是否有权限访问/操作帖子(作者或 moderator+)
|
||||
func (s *PostService) IsPostAccessible(userID uint, role string, postUserID uint) bool {
|
||||
return userID == postUserID || model.HasMinRole(role, model.RoleModerator)
|
||||
|
||||
@ -31,7 +31,7 @@ type userAdminStore interface {
|
||||
IncrementTokenVersion(userID uint) error
|
||||
}
|
||||
|
||||
// postStore PostService 所需的最小仓储接口(ISP:9 个方法)
|
||||
// postStore PostService 所需的最小仓储接口(ISP:10 个方法)
|
||||
type postStore interface {
|
||||
Create(post *model.Post) error
|
||||
FindByID(id uint) (*model.Post, error)
|
||||
@ -39,6 +39,7 @@ type postStore interface {
|
||||
FindPageable(keyword string, offset, limit int) ([]model.Post, int64, error)
|
||||
FindAdminPageable(keyword, status string, offset, limit int) ([]model.Post, int64, error)
|
||||
FindByUserIDAndStatus(userID uint, status string, offset, limit int) ([]model.Post, int64, error)
|
||||
FindPendingRevisions(keyword string, offset, limit int) ([]model.Post, int64, error)
|
||||
Update(post *model.Post) error
|
||||
SoftDelete(id uint) error
|
||||
Restore(id uint) error
|
||||
|
||||
Reference in New Issue
Block a user