feat(announcements,messaging,notifications): 实现所有长期问题 — SSE 实时推送 + 通知日志持久化 + 优先级/归档 + 消息星标/草稿 + 公告已读回执/置顶 + 分类筛选/桌面推送 + 测试覆盖
P1-8 通知实时推送(SSE): - 新增 /api/notifications/stream SSE 端点(15 秒推送,5 分钟超时) - 新增 useNotificationStream Hook(SSE + 轮询降级) - NotificationDropdown 改用 SSE 实时推送 P2-12 测试覆盖: - notifications/dispatcher.test.ts(6 个测试,渠道选择逻辑) - notifications/channels/in-app-channel.test.ts(9 个测试,类型映射) - messaging/schema.test.ts(34 个测试,Zod 校验) - tests/e2e/messages.spec.ts(消息模块 E2E 测试) - vitest.unit.config.ts 添加 server-only stub P2-13a 通知发送日志持久化: - 新增 notification_logs 表(userId/title/channel/status/messageId/error/sentAt) - logNotificationSend 改为 async 写入 DB(失败降级 console) - dispatcher 传递 payload 用于持久化 P2-13b 通知优先级和归档: - messageNotifications 表新增 priority(low/normal/high/urgent)和 isArchived 字段 - getNotifications 支持归档和优先级筛选 - 新增 archiveNotificationAction - NotificationList 显示优先级 Badge 和归档按钮 P2-13c 消息星标和草稿: - messages 表新增 isStarred 字段 - 新增 message_drafts 表 - 新增 toggleMessageStar + 草稿 CRUD Server Actions - 新增 5 个草稿 data-access 函数 P2-13d 公告已读回执和置顶: - announcements 表新增 isPinned 字段 - 新增 announcement_reads 表(唯一索引保证幂等) - 新增 toggleAnnouncementPinAction + markAnnouncementAsReadAction - getAnnouncements 排序置顶优先 P2-13e 通知分类筛选和桌面推送: - NotificationList 添加按类型筛选按钮组 - 新增 useDesktopNotifications Hook(浏览器 Notification API) - NotificationDropdown 集成桌面推送(新通知触发) 架构图同步: - 004 和 005 均已更新(新增表、Action、Hook、组件描述)
This commit is contained in:
@@ -51,7 +51,7 @@ export function AdminAnnouncementsView({
|
||||
announcements={announcements}
|
||||
canManage
|
||||
initialStatus={initialStatus}
|
||||
detailHrefBuilder={(id) => `/admin/announcements/${id}`}
|
||||
detailHrefPrefix="/admin/announcements"
|
||||
/>
|
||||
|
||||
<Dialog open={createOpen} onOpenChange={handleOpenChange}>
|
||||
|
||||
@@ -27,18 +27,24 @@ type Filter = "all" | AnnouncementStatus
|
||||
* - Select 切换时更新 URL `?status=`,触发 RSC 重新渲染
|
||||
* - 父页面根据 `?status=` 查询并传入 `announcements` prop
|
||||
* - 组件不再做客户端二次过滤,避免双重过滤逻辑冗余
|
||||
*
|
||||
* 详情链接构建:
|
||||
* - `detailHrefPrefix`:推荐方式,适用于 Server Component(前缀 + id 拼接)
|
||||
* - `detailHrefBuilder`:仅适用于 Client Component 之间的调用
|
||||
*/
|
||||
export function AnnouncementList({
|
||||
announcements,
|
||||
canManage,
|
||||
createHref,
|
||||
detailHrefBuilder,
|
||||
detailHrefPrefix,
|
||||
initialStatus,
|
||||
}: {
|
||||
announcements: Announcement[]
|
||||
canManage?: boolean
|
||||
createHref?: string
|
||||
detailHrefBuilder?: (id: string) => string
|
||||
detailHrefPrefix?: string
|
||||
initialStatus?: Filter
|
||||
}) {
|
||||
const t = useTranslations("announcements")
|
||||
@@ -59,6 +65,14 @@ export function AnnouncementList({
|
||||
router.replace(qs ? `?${qs}` : "?")
|
||||
}
|
||||
|
||||
// 构建详情链接:优先使用 detailHrefPrefix(Server Component 安全),
|
||||
// 其次使用 detailHrefBuilder(仅 Client Component 间调用)
|
||||
const buildDetailHref = (id: string): string | undefined => {
|
||||
if (detailHrefPrefix) return `${detailHrefPrefix}/${id}`
|
||||
if (detailHrefBuilder) return detailHrefBuilder(id)
|
||||
return undefined
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
@@ -101,7 +115,7 @@ export function AnnouncementList({
|
||||
<AnnouncementCard
|
||||
key={a.id}
|
||||
announcement={a}
|
||||
href={detailHrefBuilder ? detailHrefBuilder(a.id) : undefined}
|
||||
href={buildDetailHref(a.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user