refactor: 前端应用骨架迁移至 zustand 并统一路由权限壳

This commit is contained in:
2026-08-05 17:10:30 +08:00
parent e21f0de427
commit d53bbd8176
33 changed files with 1055 additions and 687 deletions

View File

@@ -1,7 +1,9 @@
import React, { useState, useEffect, useRef } from 'react';
import { Badge, Popover, Button, List, Typography, Empty } from 'antd';
import { BellOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import dayjs from 'dayjs';
import { useInterval } from 'usehooks-ts';
import api from '../api';
import { formatNotificationText, notificationTypeLabels } from '../utils/notification-display';
import { useUserStore } from '../store/user/userStore';
@@ -17,25 +19,19 @@ interface NotificationItem {
}
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);
return `${days}天前`;
return dayjs(dateStr).fromNow();
}
const NotificationBell: React.FC = () => {
const [unreadCount, setUnreadCount] = useState(0);
const [notifications, setNotifications] = useState<NotificationItem[]>([]);
const [open, setOpen] = useState(false);
const [sseDown, setSseDown] = useState(false);
const navigate = useNavigate();
const fetchNotifications = async () => {
try {
const data = (await api.get('/notifications?limit=20')) as unknown as NotificationItem[];
const data = await api.get<NotificationItem[]>('/notifications?limit=20');
setNotifications(data);
} catch {
/* ignore */
@@ -44,7 +40,7 @@ const NotificationBell: React.FC = () => {
const fetchUnread = async () => {
try {
const data = (await api.get('/notifications/unread-count')) as unknown as { count: number };
const data = await api.get<{ count: number }>('/notifications/unread-count');
setUnreadCount(data.count);
} catch {
/* ignore */
@@ -52,7 +48,9 @@ const NotificationBell: React.FC = () => {
};
const openRef = useRef(open);
openRef.current = open;
const retryRef = useRef<number | null>(null);
useInterval(() => {
void fetchUnread();
}, sseDown ? 60_000 : null);
// SSE connection — decoupled from popover open state
useEffect(() => {
@@ -71,13 +69,11 @@ const NotificationBell: React.FC = () => {
};
es.onerror = () => {
es.close();
if (retryRef.current !== null) clearInterval(retryRef.current);
retryRef.current = window.setInterval(fetchUnread, 60_000);
setSseDown(true);
};
return () => {
es.close();
clearInterval(retryRef.current ?? undefined);
retryRef.current = null;
setSseDown(false);
};
}, []);