"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { Heart, MessageCircle, CheckCheck, Bell, CheckCircle2, XCircle, AtSign, ClipboardList, Award, AlertTriangle, UserPlus, ChevronRight } from "lucide-react"; import { apiMarkRead, apiMarkAllRead, type NotificationsResponse, type NotificationItem } from "@/lib/api"; import { useUrlStyle } from "@/components/SiteBrandProvider"; import { postPath, userPath, commentAnchor } from "@/lib/urlStyle"; /** * 通知列表交互层:数据由服务端 SSR 直出,这里只承担已读标记等客户端操作。 * 布局:左栏 sticky 概览(未读数/全部已读)+ 右列紧凑列表(单条两行内)。 */ export default function NotificationsClient({ initialData, initialUnread, children, }: { initialData: NotificationsResponse; initialUnread: number; children?: React.ReactNode; }) { const router = useRouter(); const urlStyle = useUrlStyle(); const [notifications, setNotifications] = useState(initialData.notifications || []); const [unreadCount, setUnreadCount] = useState(initialUnread); const handleClick = async (n: NotificationItem) => { if (!n.is_read) { apiMarkRead(n.id).catch(() => {}); setNotifications((list) => list.map((item) => (item.id === n.id ? { ...item, is_read: true } : item)) ); setUnreadCount((c) => Math.max(0, c - 1)); } // @提及通知跳到群聊房间;徽章通知无关联帖子(仅标已读);关注通知跳到关注者主页;其余跳到帖子/评论 if (n.type === "mention" && n.room_id) { router.push(`/chat/${n.room_id}`); } else if (n.type === "pending_review") { router.push(n.post_id ? postPath(urlStyle, n.post_id) : "/admin/content?status=pending"); } else if (n.type === "badge") { return; } else if (n.type === "follow") { router.push(userPath(urlStyle, n.actor_id)); } else if (n.comment_id > 0 && n.comment_floor) { // 评论/回复/点赞等:直达评论所在楼层(跨页由帖子页深链定位换页) router.push( `${postPath(urlStyle, n.post_id)}${commentAnchor(n.comment_floor, n.comment_is_root ?? true, n.comment_id)}` ); } else { router.push(postPath(urlStyle, n.post_id)); } }; const handleMarkAll = async () => { await apiMarkAllRead(); setNotifications((list) => list.map((item) => ({ ...item, is_read: true }))); setUnreadCount(0); // 同步刷新 Header 未读红点(layout 重新取 unread_count) router.refresh(); }; const formatTime = (dateStr: string) => { const diff = Date.now() - new Date(dateStr).getTime(); const min = Math.floor(diff / 60000); if (min < 1) return "刚刚"; if (min < 60) return `${min} 分钟前`; const hour = Math.floor(min / 60); if (hour < 24) return `${hour} 小时前`; return `${Math.floor(hour / 24)} 天前`; }; const iconOf = (n: NotificationItem) => { const t = n.type; const node = t === "approved" ? : t === "rejected" || t === "deleted" ? : t === "necro_reply" ? : t === "like" ? : t === "mention" ? : t === "pending_review" ? : t === "badge" ? : t === "follow" ? : ; const color = t === "approved" ? "var(--ok)" : t === "rejected" || t === "deleted" || t === "necro_reply" ? "var(--danger)" : t === "like" ? "var(--clay)" : t === "badge" ? "var(--gold)" : "var(--accent)"; const bg = t === "approved" ? "var(--ok-soft)" : t === "rejected" || t === "deleted" || t === "necro_reply" ? "var(--danger-soft)" : t === "like" ? "var(--clay-soft)" : t === "badge" ? "var(--gold-soft)" : "var(--accent-soft)"; return { node, color, bg }; }; return ( <> {/* 左栏:概览 + 全部已读(lg 起 sticky) */} {/* 右列:紧凑通知列表 + 分页 */}
{notifications.length === 0 ? (

还没有通知

当有人评论或点赞你的帖子时,会在这里提醒你

) : (
{notifications.map((n, i) => { const approval = n.type === "approved" || n.type === "rejected"; // 系统提醒(ActorID=0):不渲染操作者链接,仅展示标题与内容 const system = n.type === "necro_reply"; const icon = iconOf(n); return ( ); })}
)} {children}
); }