From 67444434b91f30f6cd8d6d52bdd6d577fe4d735e Mon Sep 17 00:00:00 2001 From: Victor_Jay Date: Sat, 30 May 2026 21:24:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E6=8E=A5=E7=A8=BF=E4=BB=B6?= =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E9=80=9A=E7=9F=A5=E7=B3=BB=E7=BB=9F=20+=20?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=A1=B5=E9=9D=A2=E7=B3=BB=E7=BB=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8C=BA=E5=9F=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增通知类型 post_approved / post_rejected - PostService.Approve/Reject 完成后通知作者(通知失败不影响主流程) - NotificationService 新增 NotifyPostApproved/NotifyPostRejected 方法 - 消息页面顶部增加系统消息标识和分隔线,之下为全部消息列表 - 稿件审核通过通知卡片点击跳转到 /posts/{id} 页面 - 稿件审核拒绝通知卡片点击跳转到 /studio/posts 创作中心 - 新增 postNotifier 接口遵循 ISP 原则 --- internal/model/notification.go | 12 +++-- internal/router/deps_extra.go | 2 +- internal/service/notification_service.go | 17 ++++++ internal/service/post_service.go | 52 +++++++++++++++---- internal/service/repository.go | 6 +++ .../MetaLab-2026/html/messages/index.html | 39 +++++++++++++- .../MetaLab-2026/static/css/messages.css | 37 +++++++++++++ 7 files changed, 148 insertions(+), 17 deletions(-) diff --git a/internal/model/notification.go b/internal/model/notification.go index d6efce2..f96ada3 100644 --- a/internal/model/notification.go +++ b/internal/model/notification.go @@ -2,8 +2,10 @@ package model // 消息/通知类型常量 const ( - NotifyAuditApproved = "audit_approved" - NotifyAuditRejected = "audit_rejected" + NotifyAuditApproved = "audit_approved" + NotifyAuditRejected = "audit_rejected" + NotifyPostApproved = "post_approved" + NotifyPostRejected = "post_rejected" ) // Notification 消息/通知模型 @@ -20,8 +22,10 @@ type Notification struct { // NotifyTypeNames 通知类型 → 中文名 var NotifyTypeNames = map[string]string{ - NotifyAuditApproved: "系统通知", - NotifyAuditRejected: "系统通知", + NotifyAuditApproved: "资料审核", + NotifyAuditRejected: "资料审核", + NotifyPostApproved: "稿件审核", + NotifyPostRejected: "稿件审核", } // NotificationListResult 消息列表查询结果 diff --git a/internal/router/deps_extra.go b/internal/router/deps_extra.go index 8c673b0..f773890 100644 --- a/internal/router/deps_extra.go +++ b/internal/router/deps_extra.go @@ -32,7 +32,7 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting ) postRepo := repository.NewPostRepo(db) - postService := service.NewPostService(postRepo, siteSettings) + postService := service.NewPostService(postRepo, siteSettings, notificationService) shortcodeSvc := service.NewShortcodeService() postCtrl := controller.NewPostController(postService, shortcodeSvc) adminPostCtrl := adminCtrl.NewAdminPostController(postService) diff --git a/internal/service/notification_service.go b/internal/service/notification_service.go index 4d62dad..1356fd4 100644 --- a/internal/service/notification_service.go +++ b/internal/service/notification_service.go @@ -110,3 +110,20 @@ func (s *NotificationService) NotifyAuditRejected(userID uint, auditType, reason content, &relatedID) } + +// NotifyPostApproved 稿件审核通过通知 +func (s *NotificationService) NotifyPostApproved(userID uint, postTitle string, postID uint) { + title := "稿件审核通过" + content := "你的稿件《" + postTitle + "》已通过审核,现已公开发布" + _ = s.Create(userID, model.NotifyPostApproved, title, content, &postID) +} + +// NotifyPostRejected 稿件审核拒绝通知 +func (s *NotificationService) NotifyPostRejected(userID uint, postTitle, reason string, postID uint) { + title := "稿件审核未通过" + content := "你的稿件《" + postTitle + "》未通过审核" + if reason != "" { + content += ",理由:" + reason + } + _ = s.Create(userID, model.NotifyPostRejected, title, content, &postID) +} diff --git a/internal/service/post_service.go b/internal/service/post_service.go index 2a55835..6ce2d3c 100644 --- a/internal/service/post_service.go +++ b/internal/service/post_service.go @@ -75,15 +75,17 @@ func generateExcerpt(md string) string { // PostService 帖子业务逻辑 type PostService struct { - repo postStore - ss *config.SiteSettings + repo postStore + ss *config.SiteSettings + notifier postNotifier } // NewPostService 构造函数 -func NewPostService(repo postStore, ss *config.SiteSettings) *PostService { +func NewPostService(repo postStore, ss *config.SiteSettings, notifier postNotifier) *PostService { return &PostService{ - repo: repo, - ss: ss, + repo: repo, + ss: ss, + notifier: notifier, } } @@ -263,8 +265,10 @@ func (s *PostService) Approve(postID uint) error { return err } + wasRevision := post.PendingBody != "" + // 修订审核:将待审内容覆盖到正式字段 - if post.PendingBody != "" { + if wasRevision { post.Title = post.PendingTitle post.Body = post.PendingBody post.Excerpt = generateExcerpt(post.PendingBody) @@ -272,7 +276,14 @@ func (s *PostService) Approve(postID uint) error { post.PendingBody = "" post.Status = model.PostStatusApproved post.RejectReason = "" - return s.repo.Update(post) + if err := s.repo.Update(post); err != nil { + return err + } + // 通知作者修订审核通过 + if s.notifier != nil { + s.notifier.NotifyPostApproved(post.UserID, post.Title, post.ID) + } + return nil } // 首次审核通过 @@ -281,7 +292,14 @@ func (s *PostService) Approve(postID uint) error { } post.Status = model.PostStatusApproved post.RejectReason = "" - return s.repo.Update(post) + if err := s.repo.Update(post); err != nil { + return err + } + // 通知作者审核通过 + if s.notifier != nil { + s.notifier.NotifyPostApproved(post.UserID, post.Title, post.ID) + } + return nil } // Reject 退回:pending/approved → rejected;修订退回则清空 Pending 保持 approved @@ -299,13 +317,27 @@ func (s *PostService) Reject(postID uint, reason string) error { post.PendingTitle = "" post.PendingBody = "" post.RejectReason = reason - return s.repo.Update(post) + if err := s.repo.Update(post); err != nil { + return err + } + // 通知作者修订被退回 + if s.notifier != nil { + s.notifier.NotifyPostRejected(post.UserID, post.Title, reason, post.ID) + } + return nil } // 普通退回(首次审核) post.Status = model.PostStatusRejected post.RejectReason = reason - return s.repo.Update(post) + if err := s.repo.Update(post); err != nil { + return err + } + // 通知作者审核被退回 + if s.notifier != nil { + s.notifier.NotifyPostRejected(post.UserID, post.Title, reason, post.ID) + } + return nil } // Lock 锁定:设置 IsLocked 标志,禁止编辑,不改变帖子发布状态 diff --git a/internal/service/repository.go b/internal/service/repository.go index 96edfd3..6854966 100644 --- a/internal/service/repository.go +++ b/internal/service/repository.go @@ -53,4 +53,10 @@ type spaceUserStore interface { // spacePostStore SpaceService 所需的最小帖子仓储接口(ISP:1 个方法) type spacePostStore interface { FindByUserID(userID uint, offset, limit int) ([]model.Post, int64, error) +} + +// postNotifier PostService 所需的通知服务接口(ISP:2 个方法) +type postNotifier interface { + NotifyPostApproved(userID uint, postTitle string, postID uint) + NotifyPostRejected(userID uint, postTitle, reason string, postID uint) } \ No newline at end of file diff --git a/templates/MetaLab-2026/html/messages/index.html b/templates/MetaLab-2026/html/messages/index.html index 4dd171a..4ddf8db 100644 --- a/templates/MetaLab-2026/html/messages/index.html +++ b/templates/MetaLab-2026/html/messages/index.html @@ -21,6 +21,15 @@
+ +
+
+ + 系统消息 +
+
+
+ {{if .Messages}}
{{if .UnreadCount}} @@ -29,7 +38,18 @@
{{range $i, $m := .Messages}} -
+ {{$link := ""}} + {{if or (eq $m.NotifyType "post_approved") (eq $m.NotifyType "post_rejected")}} + {{if eq $m.NotifyType "post_approved"}} + {{$link = printf "/posts/%d" $m.RelatedID}} + {{else}} + {{$link = "/studio/posts"}} + {{end}} + {{end}} + {{if $link}} + + {{end}} +
@@ -45,6 +65,9 @@ {{end}}
+ {{if $link}} +
+ {{end}} {{end}}
@@ -82,7 +105,10 @@ var cards = document.querySelectorAll('.msg-card.unread'); for (var i = 0; i < cards.length; i++) { (function (card) { - card.addEventListener('click', function () { + card.addEventListener('click', function (e) { + var link = card.getAttribute('data-link'); + // 如果是可跳转的通知(post 审核),不阻止默认行为,只标记已读 + // 标记已读通过 fetch 发送,不阻止链接跳转 var id = card.getAttribute('data-id'); if (!id) return; var xhr = new XMLHttpRequest(); @@ -96,6 +122,15 @@ } }; xhr.send(); + + // 如果有链接,在标记已读后跳转(非 post 通知直接标记已读) + if (link) { + e.preventDefault(); + var self = this; + setTimeout(function () { + window.location.href = link; + }, 200); + } }); })(cards[i]); } diff --git a/templates/MetaLab-2026/static/css/messages.css b/templates/MetaLab-2026/static/css/messages.css index 97a7659..390887e 100644 --- a/templates/MetaLab-2026/static/css/messages.css +++ b/templates/MetaLab-2026/static/css/messages.css @@ -100,6 +100,43 @@ body { min-width: 0; } +/* ---------- System Message Section ---------- */ +.msg-system-section { + margin-bottom: 1.25rem; +} + +.msg-system-header { + display: flex; + align-items: center; + gap: .5rem; + font-size: .95rem; + font-weight: 600; + color: var(--color-primary); + padding: 0 0 .5rem 0; +} + +.msg-system-icon { + color: var(--color-accent); + flex-shrink: 0; +} + +.msg-system-divider { + height: 1px; + background: linear-gradient(to right, var(--color-border), transparent); + margin: 0; +} + +/* ---------- Message Card Link ---------- */ +.msg-card-link { + text-decoration: none; + color: inherit; + display: block; +} + +.msg-card-link:hover { + text-decoration: none; +} + /* ---------- Toolbar ---------- */ .msg-toolbar { display: flex;