feat: 新增隐私模式、全面的交易管理、预算与储蓄罐功能,并优化了整体用户界面和页面结构。

This commit is contained in:
2026-01-27 00:29:00 +08:00
parent 91054dd670
commit 0d9fd58bc7
67 changed files with 5418 additions and 875 deletions

View File

@@ -0,0 +1,142 @@
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;
}
interface NotificationContextType {
notifications: Notification[];
unreadCount: number;
markAsRead: (id: string) => void;
markAllAsRead: () => void;
deleteNotification: (id: string) => void;
addNotification: (notification: Omit<Notification, 'id' | 'timestamp' | 'read'>) => 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[]>([]);
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);
}
} else {
setNotifications(MOCK_NOTIFICATIONS);
localStorage.setItem(STORAGE_KEY, JSON.stringify(MOCK_NOTIFICATIONS));
}
}, []);
useEffect(() => {
if (notifications.length > 0) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(notifications));
}
}, [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]);
}, []);
return (
<NotificationContext.Provider value={{
notifications,
unreadCount,
markAsRead,
markAllAsRead,
deleteNotification,
addNotification
}}>
{children}
</NotificationContext.Provider>
);
};
export const useNotifications = () => {
const context = useContext(NotificationContext);
if (!context) {
throw new Error('useNotifications must be used within a NotificationProvider');
}
return context;
};