后端 — 新建 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>
37 lines
1.9 KiB
Python
37 lines
1.9 KiB
Python
# Generated by Django 4.2.29 on 2026-05-12 18:24
|
|
|
|
from django.conf import settings
|
|
from django.db import migrations, models
|
|
import django.db.models.deletion
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
|
|
initial = True
|
|
|
|
dependencies = [
|
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
]
|
|
|
|
operations = [
|
|
migrations.CreateModel(
|
|
name='Notification',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('type', models.CharField(choices=[('anomaly_disabled_user', '账号因异常被自动封禁'), ('anomaly_disabled_team', '团队因异常被自动封禁'), ('quota_warning', '额度即将耗尽'), ('system', '系统通知')], default='system', max_length=30, verbose_name='类型')),
|
|
('title', models.CharField(blank=True, default='', max_length=200, verbose_name='标题')),
|
|
('content', models.TextField(blank=True, default='', verbose_name='内容')),
|
|
('link_url', models.CharField(blank=True, default='', max_length=500, verbose_name='跳转链接')),
|
|
('is_read', models.BooleanField(db_index=True, default=False, verbose_name='已读')),
|
|
('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
|
('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='接收人')),
|
|
],
|
|
options={
|
|
'verbose_name': '站内通知',
|
|
'verbose_name_plural': '站内通知',
|
|
'ordering': ['-created_at'],
|
|
'indexes': [models.Index(fields=['recipient', 'is_read', '-created_at'], name='notificatio_recipie_684eac_idx')],
|
|
},
|
|
),
|
|
]
|