feat(web): migrate notification inbox to React

Add the React notification route with cursor-based inbox and sent-message views, read receipts, filters, unread badges, and safe fallback links for functionality that remains in the legacy frontend.
This commit is contained in:
biss authored and biss committed 2026-10-03 21:38:14 +08:00
1 parent 850eb6b05c
commit 338006c51e
6 files changed
+232 -5

No files matched your search

+12 -3
View File
@@ -8,8 +8,10 @@ import {
MenuUnfoldOutlined,
SafetyCertificateOutlined,
} from '@ant-design/icons'
import { Avatar, Button, Layout, Menu, Tag } from 'antd'
import { Avatar, Badge, Button, Layout, Menu, Tag } from 'antd'
import { useQuery } from '@tanstack/react-query'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import http from '../api/http'
import { branding } from '../config/branding'
import { useAuthStore } from '../stores/auth'
import BrandMark from '../components/BrandMark'
@@ -26,13 +28,18 @@ export default function AppShell() {
const roles = user?.roles ?? []
const isStudentHome = roles.includes('Student') && !roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor'].includes(role))
const isTeacherHome = roles.includes('Teacher') && !roles.some((role) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Student'].includes(role))
const legacyBase = (import.meta.env.VITE_LEGACY_APP_URL ?? 'http://localhost:5255').replace(/\/$/, '')
const unreadQuery = useQuery({
queryKey: ['notification-unread-count'],
queryFn: async () => (await http.get<{ count: number }>('/notifications/unread-count')).data.count,
refetchInterval: 60_000,
})
useEffect(() => { void refreshUser().catch(() => undefined) }, [refreshUser])
const items = useMemo(() => [
{ key: '/dashboard', icon: <AppstoreOutlined />, label: isStudentHome ? '学习首页' : isTeacherHome ? '教学工作台' : '教务总览' },
{ key: '/account', icon: <IdcardOutlined />, label: '个人账户' },
{ key: '/notifications', icon: <BellOutlined />, label: '通知中心' },
...(user?.roles.includes('SuperAdmin')
? [{ key: '/users', icon: <SafetyCertificateOutlined />, label: '用户与权限' }]
: []),
@@ -87,7 +94,9 @@ export default function AppShell() {
<div className="shell-context"><span>当前工作区</span><strong>{pageTitle}</strong></div>
</div>
<div className="shell-user">
<Button type="text" aria-label="通知中心" icon={<BellOutlined />} onClick={() => window.location.assign(`${legacyBase}/notifications`)} />
<Badge count={unreadQuery.data ?? 0} size="small" overflowCount={99}>
<Button type="text" aria-label="通知中心" icon={<BellOutlined />} onClick={() => navigate('/notifications')} />
</Badge>
<Tag color="geekblue">预览</Tag>
<div className="shell-user__copy"><strong>{user?.displayName ?? '用户'}</strong><small>{user?.roles.join(' · ') || '正在读取权限'}</small></div>
<Avatar style={{ background: '#54cfbd', color: '#163f43' }}>{user?.displayName?.slice(0, 1) ?? '明'}</Avatar>