feat(core): notification inbox infinite scroll + command palette fix (+ pending WIP)
消息中心:全量渲染 → 真·后端分页滚动加载 - backend(ops/views): NotificationPagination(10/页,page_size 可覆盖)+ 响应回 type_counts(按收件人绝对计数,不受分页/搜索影响) - frontend(messages): 自管分页,滚到底加载下一批;tab/搜索走服务端并重置到第1页; 代号作废在途旧请求防切换卡空白;乐观标已读;「已加载 X / Y」分母用当前筛选总数 - api/App/types: listNotifications 支持 page/page_size/search;allNotifications 携带 type_counts 命令面板(侧边栏搜索):修复点开后 UI 错位 - app-shell: 遮罩 className 漏了基类 shell-command-bg(只有 .show)致无定位塌到左下; 补回基类 + header 类名对齐 .shell-command-h - messages-page.css: 工作台收进视口高度,收件箱在面板内滚动 本次提交一并带入此前若干未提交 WIP(account/ai-tools/library/pipeline/products/settings + accounts/ai/assets/billing/projects 后端),按用户要求整体推 dev。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,17 @@ from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
|
||||
class NotificationPagination(PageNumberPagination):
|
||||
# 收件箱滚动加载:每批 10 条,前端可用 ?page_size= 覆盖(上限 100)
|
||||
page_size = 10
|
||||
page_size_query_param = "page_size"
|
||||
max_page_size = 100
|
||||
|
||||
from apps.assets.models import Asset
|
||||
from apps.billing.models import CreditAccount
|
||||
from apps.common.api import TeamScopedViewSetMixin
|
||||
@@ -109,14 +117,19 @@ def ensure_team_notifications(team, user):
|
||||
class NotificationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
serializer_class = NotificationSerializer
|
||||
queryset = Notification.objects.select_related("team", "recipient", "project").all()
|
||||
pagination_class = NotificationPagination
|
||||
search_fields = ["title", "brief", "body", "source", "stage"]
|
||||
ordering_fields = ["created_at", "updated_at"]
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def get_queryset(self):
|
||||
# 团队 + 收件人 + 未归档:分类计数的基准集(不含 tab/未读/搜索过滤)
|
||||
def _recipient_scope(self):
|
||||
queryset = super().get_queryset().filter(archived_at__isnull=True)
|
||||
user = self.request.user
|
||||
queryset = queryset.filter(Q(recipient=user) | Q(recipient__isnull=True))
|
||||
return queryset.filter(Q(recipient=user) | Q(recipient__isnull=True))
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self._recipient_scope()
|
||||
notification_type = self.request.query_params.get("type")
|
||||
if notification_type and notification_type not in {"all", "unread"}:
|
||||
queryset = queryset.filter(notification_type=notification_type)
|
||||
@@ -128,9 +141,19 @@ class NotificationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
ensure_team_notifications(self.get_team(), request.user)
|
||||
response = super().list(request, *args, **kwargs)
|
||||
data = response.data
|
||||
unread_count = self.get_queryset().filter(is_read=False).count()
|
||||
if isinstance(data, dict):
|
||||
# 分类 chip 计数取绝对总数(忽略当前 tab/搜索),与设计稿一致
|
||||
base = self._recipient_scope()
|
||||
unread_count = base.filter(is_read=False).count()
|
||||
data["unread_count"] = unread_count
|
||||
data["type_counts"] = {
|
||||
"all": base.count(),
|
||||
"unread": unread_count,
|
||||
"task": base.filter(notification_type="task").count(),
|
||||
"team": base.filter(notification_type="team").count(),
|
||||
"billing": base.filter(notification_type="billing").count(),
|
||||
"system": base.filter(notification_type="system").count(),
|
||||
}
|
||||
return response
|
||||
|
||||
def perform_create(self, serializer):
|
||||
|
||||
Reference in New Issue
Block a user