feat: 添加通知功能,包括UI页面、状态管理Hook和API服务集成。
This commit is contained in:
@@ -1,132 +1,84 @@
|
||||
|
||||
import { useState, useEffect, useCallback, createContext, useContext } from 'react';
|
||||
|
||||
export type NotificationType = 'info' | 'success' | 'warning' | 'error' | 'system';
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
type: NotificationType;
|
||||
title: string;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
read: boolean;
|
||||
link?: string;
|
||||
}
|
||||
import React, { useState, useEffect, useCallback, createContext, useContext } from 'react';
|
||||
import { notificationService, Notification } from '../services/notificationService';
|
||||
|
||||
interface NotificationContextType {
|
||||
notifications: Notification[];
|
||||
unreadCount: number;
|
||||
markAsRead: (id: string) => void;
|
||||
markAllAsRead: () => void;
|
||||
deleteNotification: (id: string) => void;
|
||||
addNotification: (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => void;
|
||||
loading: boolean;
|
||||
markAsRead: (id: number) => Promise<void>;
|
||||
markAllAsRead: () => Promise<void>;
|
||||
refreshNotifications: () => Promise<void>;
|
||||
}
|
||||
|
||||
const NotificationContext = createContext<NotificationContextType | undefined>(undefined);
|
||||
|
||||
const STORAGE_KEY = 'novault_notifications';
|
||||
|
||||
const MOCK_NOTIFICATIONS: Notification[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'system',
|
||||
title: '系统升级通知',
|
||||
message: 'Novault 已成功升级至 v2.3.0 版本,新增了专注模式和高级报表功能。',
|
||||
timestamp: Date.now() - 1000 * 60 * 30, // 30 mins ago
|
||||
read: false,
|
||||
link: '/settings'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'warning',
|
||||
title: '预算预警',
|
||||
message: '您的 "餐饮" 类别预算已使用 85%,请注意控制支出。',
|
||||
timestamp: Date.now() - 1000 * 60 * 60 * 2, // 2 hours ago
|
||||
read: false,
|
||||
link: '/budget'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'success',
|
||||
title: '数据同步完成',
|
||||
message: '您的本地数据已成功备份至云端。',
|
||||
timestamp: Date.now() - 1000 * 60 * 60 * 24, // 1 day ago
|
||||
read: true,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'info',
|
||||
title: '新功能推荐',
|
||||
message: '试试新的 "专注模式",点击右上角头像即可切换。',
|
||||
timestamp: Date.now() - 1000 * 60 * 60 * 48, // 2 days ago
|
||||
read: true,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'error',
|
||||
title: '连接失败',
|
||||
message: '无法连接到汇率服务器,请检查您的网络设置。',
|
||||
timestamp: Date.now() - 1000 * 60 * 60 * 72, // 3 days ago
|
||||
read: true,
|
||||
}
|
||||
];
|
||||
|
||||
export const NotificationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
const [unreadCount, setUnreadCount] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
setNotifications(JSON.parse(stored));
|
||||
} catch (e) {
|
||||
console.error('Failed to parse notifications', e);
|
||||
setNotifications(MOCK_NOTIFICATIONS);
|
||||
const fetchNotifications = useCallback(async () => {
|
||||
try {
|
||||
// Fetch unread count
|
||||
const countRes = await notificationService.getUnreadCount();
|
||||
if (countRes.success) {
|
||||
setUnreadCount(countRes.data.count);
|
||||
}
|
||||
} else {
|
||||
setNotifications(MOCK_NOTIFICATIONS);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(MOCK_NOTIFICATIONS));
|
||||
|
||||
// Fetch latest notifications (page 1, 10 items)
|
||||
// You might want to load more on demand, but this is initial state
|
||||
const listRes = await notificationService.getNotifications(1, 10);
|
||||
if (listRes.success) {
|
||||
setNotifications(listRes.data.notifications);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch notifications:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const refreshNotifications = useCallback(async () => {
|
||||
setLoading(true);
|
||||
await fetchNotifications();
|
||||
setLoading(false);
|
||||
}, [fetchNotifications]);
|
||||
|
||||
// Initial fetch and polling
|
||||
useEffect(() => {
|
||||
if (notifications.length > 0) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(notifications));
|
||||
fetchNotifications();
|
||||
const interval = setInterval(fetchNotifications, 60000); // Poll every minute
|
||||
return () => clearInterval(interval);
|
||||
}, [fetchNotifications]);
|
||||
|
||||
const markAsRead = useCallback(async (id: number) => {
|
||||
try {
|
||||
await notificationService.markAsRead(id);
|
||||
// Optimistic update
|
||||
setNotifications(prev => prev.map(n => n.id === id ? { ...n, is_read: true } : n));
|
||||
setUnreadCount(prev => Math.max(0, prev - 1));
|
||||
} catch (error) {
|
||||
console.error('Failed to mark as read:', error);
|
||||
}
|
||||
}, [notifications]);
|
||||
|
||||
const unreadCount = notifications.filter(n => !n.read).length;
|
||||
|
||||
const markAsRead = useCallback((id: string) => {
|
||||
setNotifications(prev => prev.map(n => n.id === id ? { ...n, read: true } : n));
|
||||
}, []);
|
||||
|
||||
const markAllAsRead = useCallback(() => {
|
||||
setNotifications(prev => prev.map(n => ({ ...n, read: true })));
|
||||
}, []);
|
||||
|
||||
const deleteNotification = useCallback((id: string) => {
|
||||
setNotifications(prev => prev.filter(n => n.id !== id));
|
||||
}, []);
|
||||
|
||||
const addNotification = useCallback((n: Omit<Notification, 'id' | 'timestamp' | 'read'>) => {
|
||||
const newNotification: Notification = {
|
||||
...n,
|
||||
id: Date.now().toString(),
|
||||
timestamp: Date.now(),
|
||||
read: false,
|
||||
};
|
||||
setNotifications(prev => [newNotification, ...prev]);
|
||||
const markAllAsRead = useCallback(async () => {
|
||||
try {
|
||||
await notificationService.markAllAsRead();
|
||||
// Optimistic update
|
||||
setNotifications(prev => prev.map(n => ({ ...n, is_read: true })));
|
||||
setUnreadCount(0);
|
||||
} catch (error) {
|
||||
console.error('Failed to mark all as read:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={{
|
||||
notifications,
|
||||
unreadCount,
|
||||
loading,
|
||||
markAsRead,
|
||||
markAllAsRead,
|
||||
deleteNotification,
|
||||
addNotification
|
||||
refreshNotifications
|
||||
}}>
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user