diff --git a/backend/internal/model/notification.go b/backend/internal/model/notification.go index bd5d2f7..9cfccfa 100644 --- a/backend/internal/model/notification.go +++ b/backend/internal/model/notification.go @@ -8,10 +8,11 @@ type Notification struct { Type string `gorm:"size:32;not null" json:"type"` Title string `gorm:"size:128;not null" json:"title"` Content string `json:"content"` - BizType string `gorm:"size:32;not null;default:''" json:"biz_type"` - BizID *uint64 `json:"biz_id"` - ReadAt *time.Time `json:"read_at"` - CreatedAt time.Time `json:"created_at"` + BizType string `gorm:"size:32;not null;default:''" json:"biz_type"` + BizID *uint64 `json:"biz_id"` + DistributionSiteID *uint64 `gorm:"index" json:"distribution_site_id"` + ReadAt *time.Time `json:"read_at"` + CreatedAt time.Time `json:"created_at"` } func (Notification) TableName() string { diff --git a/backend/internal/modules/notification/dto.go b/backend/internal/modules/notification/dto.go index cedd4af..e97ffad 100644 --- a/backend/internal/modules/notification/dto.go +++ b/backend/internal/modules/notification/dto.go @@ -8,10 +8,11 @@ type NotificationDTO struct { Type string `json:"type"` Title string `json:"title"` Content string `json:"content"` - BizType string `json:"biz_type"` - BizID *uint64 `json:"biz_id"` - ReadAt *time.Time `json:"read_at"` - CreatedAt time.Time `json:"created_at"` + BizType string `json:"biz_type"` + BizID *uint64 `json:"biz_id"` + DistributionSiteID *uint64 `json:"distribution_site_id,omitempty"` + ReadAt *time.Time `json:"read_at"` + CreatedAt time.Time `json:"created_at"` } type PaginatedResult struct { diff --git a/backend/internal/modules/notification/handler.go b/backend/internal/modules/notification/handler.go index d3c9be9..9b66cab 100644 --- a/backend/internal/modules/notification/handler.go +++ b/backend/internal/modules/notification/handler.go @@ -40,7 +40,8 @@ func (h *Handler) List(c *gin.Context) { return } page, pageSize := parsePagination(c) - result, err := h.service.List(c.Request.Context(), userID, page, pageSize) + siteCode := parseSiteCode(c) + result, err := h.service.List(c.Request.Context(), userID, page, pageSize, siteCode) if err != nil { writeNotificationError(c, err) return @@ -54,7 +55,8 @@ func (h *Handler) UnreadCount(c *gin.Context) { response.Unauthorized(c, "缺少用户上下文") return } - result, err := h.service.UnreadCount(c.Request.Context(), userID) + siteCode := parseSiteCode(c) + result, err := h.service.UnreadCount(c.Request.Context(), userID, siteCode) if err != nil { writeNotificationError(c, err) return @@ -68,7 +70,8 @@ func (h *Handler) MarkAllRead(c *gin.Context) { response.Unauthorized(c, "缺少用户上下文") return } - count, err := h.service.MarkAllRead(c.Request.Context(), userID) + siteCode := parseSiteCode(c) + count, err := h.service.MarkAllRead(c.Request.Context(), userID, siteCode) if err != nil { writeNotificationError(c, err) return @@ -87,13 +90,28 @@ func (h *Handler) MarkRead(c *gin.Context) { response.BadRequest(c, "ID 不正确") return } - if err := h.service.MarkRead(c.Request.Context(), userID, id); err != nil { + siteCode := parseSiteCode(c) + if err := h.service.MarkRead(c.Request.Context(), userID, id, siteCode); err != nil { writeNotificationError(c, err) return } response.OK(c, gin.H{"read": true}) } +func parseSiteCode(c *gin.Context) string { + siteCode := c.Query("distribution_site_code") + if siteCode == "" { + siteCode = c.Query("site_code") + } + if siteCode == "" { + siteCode = c.GetHeader("X-Distribution-Site-Code") + } + if siteCode == "" { + siteCode = c.GetHeader("X-Site-Code") + } + return siteCode +} + func currentUserID(c *gin.Context) (uint64, bool) { value, ok := c.Get(middleware.ContextUserID) if !ok { diff --git a/backend/internal/modules/notification/repository.go b/backend/internal/modules/notification/repository.go index 5b4cf99..6b69f07 100644 --- a/backend/internal/modules/notification/repository.go +++ b/backend/internal/modules/notification/repository.go @@ -2,6 +2,8 @@ package notification import ( "context" + "errors" + "strings" "time" "hfb_sys/backend/internal/model" @@ -14,26 +16,63 @@ type Repository struct { } type Entry struct { - UserID uint64 - Type string - Title string - Content string - BizType string - BizID *uint64 + UserID uint64 + Type string + Title string + Content string + BizType string + BizID *uint64 + DistributionSiteID *uint64 } func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } -func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int) (*PaginatedResult, error) { +func (r *Repository) resolveSite(ctx context.Context, siteCode string) (uint64, bool, error) { + siteCode = strings.TrimSpace(siteCode) + if siteCode == "" { + return 0, false, nil + } + var site model.DistributionSite + if err := r.db.WithContext(ctx).Where("site_code = ? AND status = 'active'", siteCode).First(&site).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, false, nil + } + return 0, false, err + } + return site.ID, true, nil +} + +func (r *Repository) applySiteFilter(db *gorm.DB, siteID uint64, isSite bool) *gorm.DB { + if isSite && siteID > 0 { + return db.Where("distribution_site_id = ?", siteID) + } + return db.Where("distribution_site_id IS NULL OR distribution_site_id = 0") +} + +func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int, siteCode ...string) (*PaginatedResult, error) { + var code string + if len(siteCode) > 0 { + code = siteCode[0] + } + siteID, isSite, err := r.resolveSite(ctx, code) + if err != nil { + return nil, err + } + + query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ?", userID) + query = r.applySiteFilter(query, siteID, isSite) + var total int64 - if err := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ?", userID).Count(&total).Error; err != nil { + if err := query.Count(&total).Error; err != nil { return nil, err } offset := (page - 1) * pageSize var rows []model.Notification - if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Order("id DESC").Offset(offset).Limit(pageSize).Find(&rows).Error; err != nil { + dataQuery := r.db.WithContext(ctx).Where("user_id = ?", userID) + dataQuery = r.applySiteFilter(dataQuery, siteID, isSite) + if err := dataQuery.Order("id DESC").Offset(offset).Limit(pageSize).Find(&rows).Error; err != nil { return nil, err } items := make([]NotificationDTO, 0, len(rows)) @@ -43,19 +82,29 @@ func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil } -func (r *Repository) MarkRead(ctx context.Context, userID uint64, id uint64) error { +func (r *Repository) MarkRead(ctx context.Context, userID uint64, id uint64, siteCode ...string) error { + var code string + if len(siteCode) > 0 { + code = siteCode[0] + } + siteID, isSite, err := r.resolveSite(ctx, code) + if err != nil { + return err + } + now := time.Now() - result := r.db.WithContext(ctx).Model(&model.Notification{}). - Where("id = ? AND user_id = ?", id, userID). - Update("read_at", now) + query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("id = ? AND user_id = ?", id, userID) + query = r.applySiteFilter(query, siteID, isSite) + + result := query.Update("read_at", now) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { + checkQuery := r.db.WithContext(ctx).Model(&model.Notification{}).Where("id = ? AND user_id = ?", id, userID) + checkQuery = r.applySiteFilter(checkQuery, siteID, isSite) var total int64 - if err := r.db.WithContext(ctx).Model(&model.Notification{}). - Where("id = ? AND user_id = ?", id, userID). - Count(&total).Error; err != nil { + if err := checkQuery.Count(&total).Error; err != nil { return err } if total == 0 { @@ -65,19 +114,39 @@ func (r *Repository) MarkRead(ctx context.Context, userID uint64, id uint64) err return nil } -func (r *Repository) UnreadCount(ctx context.Context, userID uint64) (int64, error) { +func (r *Repository) UnreadCount(ctx context.Context, userID uint64, siteCode ...string) (int64, error) { + var code string + if len(siteCode) > 0 { + code = siteCode[0] + } + siteID, isSite, err := r.resolveSite(ctx, code) + if err != nil { + return 0, err + } + + query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ? AND read_at IS NULL", userID) + query = r.applySiteFilter(query, siteID, isSite) + var total int64 - err := r.db.WithContext(ctx).Model(&model.Notification{}). - Where("user_id = ? AND read_at IS NULL", userID). - Count(&total).Error + err = query.Count(&total).Error return total, err } -func (r *Repository) MarkAllRead(ctx context.Context, userID uint64) (int64, error) { +func (r *Repository) MarkAllRead(ctx context.Context, userID uint64, siteCode ...string) (int64, error) { + var code string + if len(siteCode) > 0 { + code = siteCode[0] + } + siteID, isSite, err := r.resolveSite(ctx, code) + if err != nil { + return 0, err + } + now := time.Now() - result := r.db.WithContext(ctx).Model(&model.Notification{}). - Where("user_id = ? AND read_at IS NULL", userID). - Update("read_at", now) + query := r.db.WithContext(ctx).Model(&model.Notification{}).Where("user_id = ? AND read_at IS NULL", userID) + query = r.applySiteFilter(query, siteID, isSite) + + result := query.Update("read_at", now) return result.RowsAffected, result.Error } @@ -86,13 +155,30 @@ func Append(tx *gorm.DB, entries ...Entry) error { if entry.UserID == 0 || entry.Title == "" { continue } + siteID := entry.DistributionSiteID + if siteID == nil { + if (entry.BizType == "order" || entry.BizType == "order_handoff") && entry.BizID != nil && *entry.BizID > 0 { + var orderSiteID *uint64 + row := tx.Table("rental_orders").Where("id = ?", *entry.BizID).Select("distribution_site_id").Row() + if err := row.Scan(&orderSiteID); err == nil && orderSiteID != nil { + siteID = orderSiteID + } + } else if entry.BizType == "dispute" && entry.BizID != nil && *entry.BizID > 0 { + var orderSiteID *uint64 + row := tx.Table("order_disputes d").Joins("JOIN rental_orders o ON o.id = d.order_id").Where("d.id = ?", *entry.BizID).Select("o.distribution_site_id").Row() + if err := row.Scan(&orderSiteID); err == nil && orderSiteID != nil { + siteID = orderSiteID + } + } + } row := model.Notification{ - UserID: entry.UserID, - Type: entry.Type, - Title: entry.Title, - Content: entry.Content, - BizType: entry.BizType, - BizID: entry.BizID, + UserID: entry.UserID, + Type: entry.Type, + Title: entry.Title, + Content: entry.Content, + BizType: entry.BizType, + BizID: entry.BizID, + DistributionSiteID: siteID, } if row.Type == "" { row.Type = "system" @@ -106,14 +192,16 @@ func Append(tx *gorm.DB, entries ...Entry) error { func toDTO(row model.Notification) NotificationDTO { return NotificationDTO{ - ID: row.ID, - UserID: row.UserID, - Type: row.Type, - Title: row.Title, - Content: row.Content, - BizType: row.BizType, - BizID: row.BizID, - ReadAt: row.ReadAt, - CreatedAt: row.CreatedAt, + ID: row.ID, + UserID: row.UserID, + Type: row.Type, + Title: row.Title, + Content: row.Content, + BizType: row.BizType, + BizID: row.BizID, + DistributionSiteID: row.DistributionSiteID, + ReadAt: row.ReadAt, + CreatedAt: row.CreatedAt, } } + diff --git a/backend/internal/modules/notification/repository_test.go b/backend/internal/modules/notification/repository_test.go index a509532..b810397 100644 --- a/backend/internal/modules/notification/repository_test.go +++ b/backend/internal/modules/notification/repository_test.go @@ -63,3 +63,131 @@ func TestRepositoryMarkReadNotFound(t *testing.T) { t.Fatalf("MarkRead() error = %v, want ErrNotificationNotFound", err) } } + +func TestNotificationTenantIsolation(t *testing.T) { + db := database.NewTestDB() + if err := db.AutoMigrate(&model.Notification{}, &model.DistributionSite{}, &model.RentalOrder{}); err != nil { + t.Fatalf("AutoMigrate() error = %v", err) + } + repo := NewRepository(db) + ctx := context.Background() + + site := model.DistributionSite{ + ID: 101, + SiteCode: "branch_a", + SiteName: "分站A", + Status: "active", + } + if err := db.Create(&site).Error; err != nil { + t.Fatalf("create distribution site error: %v", err) + } + + siteID := site.ID + // 1. 创建主站通知 + mainNotif := model.Notification{ + UserID: 20, + Type: "order", + Title: "主站订单通知", + Content: "这是主站的订单通知", + BizType: "order", + DistributionSiteID: nil, + } + if err := db.Create(&mainNotif).Error; err != nil { + t.Fatalf("create main notif error: %v", err) + } + + // 2. 创建分站通知 + branchNotif := model.Notification{ + UserID: 20, + Type: "order", + Title: "分站A订单通知", + Content: "这是分站A的订单通知", + BizType: "order", + DistributionSiteID: &siteID, + } + if err := db.Create(&branchNotif).Error; err != nil { + t.Fatalf("create branch notif error: %v", err) + } + + // 3. 验证未读数隔离 + mainUnread, err := repo.UnreadCount(ctx, 20) + if err != nil || mainUnread != 1 { + t.Fatalf("mainUnread = %d, err = %v, want 1", mainUnread, err) + } + + branchUnread, err := repo.UnreadCount(ctx, 20, "branch_a") + if err != nil || branchUnread != 1 { + t.Fatalf("branchUnread = %d, err = %v, want 1", branchUnread, err) + } + + // 4. 验证列表查询隔离 + mainList, err := repo.List(ctx, 20, 1, 10) + if err != nil || mainList.Total != 1 { + t.Fatalf("mainList total = %d, err = %v, want 1", mainList.Total, err) + } + items := mainList.Items.([]NotificationDTO) + if items[0].Title != "主站订单通知" { + t.Fatalf("mainList item title = %s, want 主站订单通知", items[0].Title) + } + + branchList, err := repo.List(ctx, 20, 1, 10, "branch_a") + if err != nil || branchList.Total != 1 { + t.Fatalf("branchList total = %d, err = %v, want 1", branchList.Total, err) + } + branchItems := branchList.Items.([]NotificationDTO) + if branchItems[0].Title != "分站A订单通知" { + t.Fatalf("branchList item title = %s, want 分站A订单通知", branchItems[0].Title) + } + + // 5. 跨站点标记单条已读阻断 + err = repo.MarkRead(ctx, 20, mainNotif.ID, "branch_a") + if !errors.Is(err, ErrNotificationNotFound) { + t.Fatalf("cross-site MarkRead error = %v, want ErrNotificationNotFound", err) + } + + // 6. 分站全部已读不影响主站 + affected, err := repo.MarkAllRead(ctx, 20, "branch_a") + if err != nil || affected != 1 { + t.Fatalf("MarkAllRead affected = %d, err = %v, want 1", affected, err) + } + + branchUnreadAfter, err := repo.UnreadCount(ctx, 20, "branch_a") + if err != nil || branchUnreadAfter != 0 { + t.Fatalf("branchUnreadAfter = %d, want 0", branchUnreadAfter) + } + + mainUnreadAfter, err := repo.UnreadCount(ctx, 20) + if err != nil || mainUnreadAfter != 1 { + t.Fatalf("mainUnreadAfter = %d, want 1 (should remain unread)", mainUnreadAfter) + } + + // 7. 测试 Append 自动关联订单 distribution_site_id + order := model.RentalOrder{ + ID: 3001, + DistributionSiteID: &siteID, + OrderNo: "ORD3001", + } + if err := db.Create(&order).Error; err != nil { + t.Fatalf("create order error: %v", err) + } + orderID := order.ID + err = Append(db, Entry{ + UserID: 20, + Type: "order", + Title: "订单自动关联分站", + Content: "自动读取订单 site ID", + BizType: "order", + BizID: &orderID, + }) + if err != nil { + t.Fatalf("Append with auto lookup error: %v", err) + } + + var autoNotif model.Notification + if err := db.Where("biz_id = ? AND biz_type = 'order'", orderID).First(&autoNotif).Error; err != nil { + t.Fatalf("find autoNotif error: %v", err) + } + if autoNotif.DistributionSiteID == nil || *autoNotif.DistributionSiteID != siteID { + t.Fatalf("autoNotif.DistributionSiteID = %v, want %d", autoNotif.DistributionSiteID, siteID) + } +} diff --git a/backend/internal/modules/notification/service.go b/backend/internal/modules/notification/service.go index 089b6d8..f844471 100644 --- a/backend/internal/modules/notification/service.go +++ b/backend/internal/modules/notification/service.go @@ -16,34 +16,34 @@ func NewService(repo *Repository) *Service { return &Service{repo: repo} } -func (s *Service) List(ctx context.Context, userID uint64, page, pageSize int) (*PaginatedResult, error) { +func (s *Service) List(ctx context.Context, userID uint64, page, pageSize int, siteCode ...string) (*PaginatedResult, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - return s.repo.List(ctx, userID, page, pageSize) + return s.repo.List(ctx, userID, page, pageSize, siteCode...) } -func (s *Service) MarkRead(ctx context.Context, userID uint64, id uint64) error { +func (s *Service) MarkRead(ctx context.Context, userID uint64, id uint64, siteCode ...string) error { if s.repo == nil { return ErrDependencyUnavailable } - return s.repo.MarkRead(ctx, userID, id) + return s.repo.MarkRead(ctx, userID, id, siteCode...) } -func (s *Service) UnreadCount(ctx context.Context, userID uint64) (*UnreadCountDTO, error) { +func (s *Service) UnreadCount(ctx context.Context, userID uint64, siteCode ...string) (*UnreadCountDTO, error) { if s.repo == nil { return nil, ErrDependencyUnavailable } - count, err := s.repo.UnreadCount(ctx, userID) + count, err := s.repo.UnreadCount(ctx, userID, siteCode...) if err != nil { return nil, err } return &UnreadCountDTO{UnreadCount: count}, nil } -func (s *Service) MarkAllRead(ctx context.Context, userID uint64) (int64, error) { +func (s *Service) MarkAllRead(ctx context.Context, userID uint64, siteCode ...string) (int64, error) { if s.repo == nil { return 0, ErrDependencyUnavailable } - return s.repo.MarkAllRead(ctx, userID) + return s.repo.MarkAllRead(ctx, userID, siteCode...) } diff --git a/backend/migrations/000071_distribution_notification_isolation.sql b/backend/migrations/000071_distribution_notification_isolation.sql new file mode 100644 index 0000000..ebd2052 --- /dev/null +++ b/backend/migrations/000071_distribution_notification_isolation.sql @@ -0,0 +1,18 @@ +-- +goose Up + +-- 1. notifications 表新增 distribution_site_id 并添加联合索引 +ALTER TABLE notifications + ADD COLUMN distribution_site_id BIGINT UNSIGNED NULL AFTER biz_id, + ADD KEY idx_notifications_user_site (user_id, distribution_site_id, id); + +-- 2. 回填历史分销订单的站内信通知归属 +UPDATE notifications n +JOIN rental_orders o ON n.biz_id = o.id +SET n.distribution_site_id = o.distribution_site_id +WHERE n.biz_type = 'order' AND o.distribution_site_id IS NOT NULL AND o.distribution_site_id > 0; + +-- +goose Down + +ALTER TABLE notifications + DROP KEY idx_notifications_user_site, + DROP COLUMN distribution_site_id; diff --git a/frontend/src/features/auth/api/notifications.ts b/frontend/src/features/auth/api/notifications.ts index 0a705b9..c10057d 100644 --- a/frontend/src/features/auth/api/notifications.ts +++ b/frontend/src/features/auth/api/notifications.ts @@ -10,37 +10,78 @@ export interface NotificationItem { content: string biz_type: string biz_id?: number + distribution_site_id?: number | null read_at?: string created_at: string } -export async function fetchNotifications(page = 1, pageSize = 20) { +export async function fetchNotifications(page = 1, pageSize = 20, distributionSiteCode?: string) { + const siteCode = + distributionSiteCode || + (typeof window !== 'undefined' ? localStorage.getItem('hfb_site_code') : '') || + undefined + const params: Record = { page, page_size: pageSize } + if (siteCode) { + params.distribution_site_code = siteCode + } const { data } = await apiClient.get>>( '/notifications', { - params: { page, page_size: pageSize }, + params, } ) return data.data } -export async function markNotificationRead(id: number) { - const { data } = await apiClient.post>(`/notifications/${id}/read`) +export async function markNotificationRead(id: number, distributionSiteCode?: string) { + const siteCode = + distributionSiteCode || + (typeof window !== 'undefined' ? localStorage.getItem('hfb_site_code') : '') || + undefined + const params: Record = {} + if (siteCode) { + params.distribution_site_code = siteCode + } + const { data } = await apiClient.post>( + `/notifications/${id}/read`, + {}, + { params } + ) return data.data } -export async function fetchUnreadNotificationCount() { +export async function fetchUnreadNotificationCount(distributionSiteCode?: string) { + const siteCode = + distributionSiteCode || + (typeof window !== 'undefined' ? localStorage.getItem('hfb_site_code') : '') || + undefined + const params: Record = {} + if (siteCode) { + params.distribution_site_code = siteCode + } const { data } = await apiClient.get>( '/notifications/unread-count', { + params, silent: true, } ) return data.data.unread_count } -export async function markAllNotificationsRead() { - const { data } = - await apiClient.put>('/notifications/read-all') +export async function markAllNotificationsRead(distributionSiteCode?: string) { + const siteCode = + distributionSiteCode || + (typeof window !== 'undefined' ? localStorage.getItem('hfb_site_code') : '') || + undefined + const params: Record = {} + if (siteCode) { + params.distribution_site_code = siteCode + } + const { data } = await apiClient.put>( + '/notifications/read-all', + {}, + { params } + ) return data.data } diff --git a/frontend/src/features/auth/views/MobileNotificationsView.vue b/frontend/src/features/auth/views/MobileNotificationsView.vue index 122fd1f..a2c8ec1 100644 --- a/frontend/src/features/auth/views/MobileNotificationsView.vue +++ b/frontend/src/features/auth/views/MobileNotificationsView.vue @@ -11,8 +11,10 @@ import { } from '@/features/auth/api/notifications' import { notificationUnreadChangedEvent } from '@/features/auth/composables/useNotificationUnreadCount' import { formatDateMinute } from '@/shared/utils/time' +import { useDistributionSiteStore } from '@/stores/distributionSite' const router = useRouter() +const distributionStore = useDistributionSiteStore() const loading = ref(false) const refreshing = ref(false) const loadingMore = ref(false) @@ -36,7 +38,8 @@ async function loadNotifications(reset = false) { loadingMore.value = true } try { - const result = await fetchNotifications(currentPage.value, pageSize) + const siteCode = distributionStore.isDistribution ? distributionStore.siteCode : undefined + const result = await fetchNotifications(currentPage.value, pageSize, siteCode) notifications.value = reset ? result.items : [...notifications.value, ...result.items] total.value = result.total if (notifications.value.length < result.total && result.items.length > 0) { @@ -54,7 +57,8 @@ async function loadNotifications(reset = false) { async function markRead(item: NotificationItem) { if (item.read_at) return try { - await markNotificationRead(item.id) + const siteCode = distributionStore.isDistribution ? distributionStore.siteCode : undefined + await markNotificationRead(item.id, siteCode) item.read_at = new Date().toISOString() window.dispatchEvent(new Event(notificationUnreadChangedEvent)) showToast({ message: '已标记为已读', icon: 'passed' }) @@ -98,14 +102,33 @@ function typeLabel(type: string) {
-

站内信

-

{{ total > 0 ? `共 ${total} 条通知` : '订单、审核和申诉消息会出现在这里' }}

+

+ {{ + distributionStore.isDistribution ? `${distributionStore.siteName} · 站内信` : '站内信' + }} +

+

+ {{ + distributionStore.isDistribution + ? `【${distributionStore.siteName}】专属通知 · 共 ${total} 条` + : total > 0 + ? `共 ${total} 条通知` + : '订单、审核和申诉消息会出现在这里' + }} +

- +
([]) const currentPage = ref(1) @@ -21,7 +23,8 @@ onMounted(loadNotifications) async function loadNotifications() { loading.value = true try { - const result = await fetchNotifications(currentPage.value, currentPageSize.value) + const siteCode = distributionStore.isDistribution ? distributionStore.siteCode : undefined + const result = await fetchNotifications(currentPage.value, currentPageSize.value, siteCode) notifications.value = result.items total.value = result.total } finally { @@ -35,7 +38,8 @@ function handleSizeChange() { } async function markRead(id: number) { - await markNotificationRead(id) + const siteCode = distributionStore.isDistribution ? distributionStore.siteCode : undefined + await markNotificationRead(id, siteCode) window.dispatchEvent(new Event(notificationUnreadChangedEvent)) ElMessage.success('已标记为已读') await loadNotifications() @@ -45,12 +49,38 @@ async function markRead(id: number) { + + diff --git a/frontend/src/features/chats/views/MessagesView.vue b/frontend/src/features/chats/views/MessagesView.vue index ec6c7f7..f31043d 100644 --- a/frontend/src/features/chats/views/MessagesView.vue +++ b/frontend/src/features/chats/views/MessagesView.vue @@ -130,7 +130,8 @@ async function loadNotifications(reset = false) { if (reset) notiPage.value = 1 notiLoading.value = true try { - const result = await fetchNotifications(notiPage.value, notiPageSize) + const siteCode = distributionStore.isDistribution ? distributionStore.siteCode : undefined + const result = await fetchNotifications(notiPage.value, notiPageSize, siteCode) notifications.value = reset ? result.items : [...notifications.value, ...result.items] notiTotal.value = result.total if (notifications.value.length < result.total && result.items.length > 0) { @@ -146,7 +147,8 @@ async function loadNotifications(reset = false) { async function markNotiRead(item: NotificationItem) { if (item.read_at) return try { - await markNotificationRead(item.id) + const siteCode = distributionStore.isDistribution ? distributionStore.siteCode : undefined + await markNotificationRead(item.id, siteCode) item.read_at = new Date().toISOString() window.dispatchEvent(new Event(notificationUnreadChangedEvent)) ElMessage.success('已标记为已读') @@ -159,7 +161,8 @@ async function markAllNotiRead() { if (notiSubmitting.value) return notiSubmitting.value = true try { - const result = await markAllNotificationsRead() + const siteCode = distributionStore.isDistribution ? distributionStore.siteCode : undefined + const result = await markAllNotificationsRead(siteCode) window.dispatchEvent(new Event(notificationUnreadChangedEvent)) ElMessage.success(result.read_count > 0 ? `已标记 ${result.read_count} 条通知` : '暂无未读通知') await loadNotifications(true) @@ -301,10 +304,21 @@ function handleTabChange(tab: string) {
- - {{ notificationUnreadCount }} 条未读 - - 暂无未读通知 +
+ + {{ distributionStore.siteName }}专属通知 + + + {{ notificationUnreadCount }} 条未读 + + 暂无未读通知 +
- +
@@ -442,6 +462,16 @@ function handleTabChange(tab: string) { margin-bottom: 12px; } +.noti-toolbar-left { + display: flex; + align-items: center; + gap: 8px; +} + +.noti-site-tag { + font-weight: 500; +} + .noti-toolbar-text { color: #6b7280; font-size: 13px;