后端 — 新建 app apps.notifications: - Notification model:type/title/content/link_url/is_read,索引 (recipient, is_read, -created_at) - 4 个 endpoint: - GET /api/v1/notifications/ (列表 + 总未读数,unread_only/page/page_size) - GET /api/v1/notifications/unread-count (轻量,前端 60s 轮询用) - PATCH /api/v1/notifications/<id>/read (标单条已读) - POST /api/v1/notifications/read-all (一键全部已读) - 严格守 user 隔离:所有查询都 filter(recipient=request.user) - INSTALLED_APPS 注册 + urls.py include - migration 0001_initial 应用成功 - MySQL 严格模式:所有 CharField 加 default=''(memory feedback_mysql_default) 后端 — anomaly_detector 集成: - _RULE_LABELS / _team_admin_recipients() / _notify_user_disabled() / _notify_team_disabled() helper - process_anomalies 里 _disable_user/_disable_team 之后调对应 notify - 接收人 = 同团队的主管+副管(is_team_admin OR is_team_owner) - 用 bulk_create 一次写多条 - try/except 保护:通知失败不阻断封禁主流程 前端: - types/index.ts:AppNotification / NotificationListResponse(避开浏览器 Web API Notification 冲突) - lib/api.ts:notificationApi (list/getUnreadCount/markRead/markAllRead) - store/notification.ts:Zustand store 乐观更新(markRead 先动 UI 再发请求) - pages/NotificationsPage.tsx:标题 + 全部标记已读按钮 + 未读蓝点 + 相对时间 + 点击跳 link_url + 分页 - App.tsx:/notifications 路由(ProtectedRoute 不限 role) - Sidebar.tsx(用户 76px):铃铛 SVG + 红点 + 60s 轮询 + visibilitychange 立即刷新 - AdminLayout.tsx(超管 220px):同步加铃铛(本来 sub-agent 只加了用户侧 sidebar,我补全 admin 侧) 测试: - 新建 web/test/v0.20.1-smoke.mjs:11 项 — 铃铛/红点/跳页/标题/100dvh/min-height:0/调试折叠/poster - 11/11 通过 + v2-smoke 25/25 + modal-interaction 8/8 全部基线 OK - 后端 4 endpoint 用 curl 验过:list / unread-count / PATCH read / POST read-all 都正常 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 lines
446 B
Python
12 lines
446 B
Python
from django.urls import path
|
|
|
|
from . import views
|
|
|
|
|
|
urlpatterns = [
|
|
path('', views.notifications_list_view, name='notifications_list'),
|
|
path('unread-count', views.notifications_unread_count_view, name='notifications_unread_count'),
|
|
path('<int:notification_id>/read', views.notification_mark_read_view, name='notification_mark_read'),
|
|
path('read-all', views.notifications_mark_all_read_view, name='notifications_mark_all_read'),
|
|
]
|