import React, { useState, useEffect } from 'react'; import { List, Typography, Menu, Layout, Button, Empty, Spin, Space, Grid, Select } from 'antd'; import { BellOutlined, DollarOutlined, HomeOutlined, TeamOutlined, SettingOutlined, } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import api from '../../api'; import { message } from '../../ui/app-message'; import { formatNotificationText } from '../../utils/notification-display'; const { Sider, Content } = Layout; const { useBreakpoint } = Grid; interface NotificationItem { id: number; type: string; title: string; content: string; link: string | null; isRead: boolean; createdAt: string; } const typeMap: Record = { bill_generated: { label: '账单', icon: }, bill_paid: { label: '账单', icon: }, check_in: { label: '入住', icon: }, check_out: { label: '退宿', icon: }, deposit_due: { label: '押金', icon: }, deposit_refunded: { label: '押金', icon: }, class_change: { label: '班级', icon: }, schedule_conflict: { label: '排课', icon: }, announcement: { label: '公告', icon: }, }; function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return '刚刚'; if (mins < 60) return `${mins}分钟前`; const hours = Math.floor(mins / 60); if (hours < 24) return `${hours}小时前`; const days = Math.floor(hours / 24); if (days < 7) return `${days}天前`; return new Date(dateStr).toLocaleDateString('zh-CN'); } const FILTER_ITEMS: Array<{ key: string; icon: React.ReactNode; label: string }> = [ { key: 'all', icon: , label: '全部' }, { key: 'bill_generated', icon: , label: '账单' }, { key: 'check_in', icon: , label: '入住' }, { key: 'class_change', icon: , label: '班级' }, { key: 'announcement', icon: , label: '公告' }, ]; const NotificationsPage: React.FC = () => { const screens = useBreakpoint(); const isMobile = !screens.sm; const [notifications, setNotifications] = useState([]); const [filter, setFilter] = useState('all'); const [loading, setLoading] = useState(false); const navigate = useNavigate(); const fetchData = async () => { setLoading(true); try { const data = (await api.get('/notifications?limit=50')) as unknown as NotificationItem[]; setNotifications(data); } catch (e: any) { console.error('加载通知失败', e); message.error(e?.message || '加载通知失败'); } setLoading(false); }; useEffect(() => { fetchData(); }, []); const handleClick = async (item: NotificationItem) => { if (!item.isRead) { try { await api.put(`/notifications/${item.id}/read`); setNotifications((prev) => prev.map((n) => (n.id === item.id ? { ...n, isRead: true } : n)), ); } catch (e: any) { console.error('标记已读失败', e); message.error(e?.message || '标记已读失败'); } } if (item.link) navigate(item.link); }; const handleMarkAll = async () => { try { await api.put('/notifications/read-all'); setNotifications((prev) => prev.map((n) => ({ ...n, isRead: true }))); } catch (e: any) { console.error('全部已读失败', e); message.error(e?.message || '操作失败'); } }; const filtered = filter === 'all' ? notifications : notifications.filter((n) => n.type === filter); return ( {!isMobile && ( setFilter(key)} items={FILTER_ITEMS} /> )}
通知中心
{isMobile && (