geo/frontend/lib/stores/notification-store.ts

134 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 全局通知/Toast 状态 Store (Zustand)
*
* 管理应用全局的 toast / 通知队列:
* - 添加通知(支持 success / error / warning / info
* - 自动过期清除
* - 手动移除
*/
import { create } from "zustand";
// ── 类型定义 ────────────────────────────────────────────────────────────
export type NotificationType = "success" | "error" | "warning" | "info";
export interface Notification {
/** 通知唯一 ID */
id: string;
/** 通知类型 */
type: NotificationType;
/** 通知消息 */
message: string;
/** 通知标题(可选) */
title?: string;
/** 创建时间戳 */
createdAt: number;
/** 自动过期时间毫秒null 表示不自动过期 */
duration: number | null;
}
export interface NotificationState {
/** 当前通知队列 */
notifications: Notification[];
}
export interface NotificationActions {
/** 添加一条通知 */
addNotification: (payload: {
type: NotificationType;
message: string;
title?: string;
/** 自定义过期时间(毫秒),默认按 type 自动设置 */
duration?: number | null;
}) => string;
/** 移除一条通知 */
removeNotification: (id: string) => void;
/** 清空所有通知 */
clearAll: () => void;
}
// ── 默认过期时间 ────────────────────────────────────────────────────────
const DEFAULT_DURATION_BY_TYPE: Record<NotificationType, number> = {
success: 3000,
error: 5000,
warning: 4000,
info: 3000,
};
// ── 辅助 ────────────────────────────────────────────────────────────────────
/** 生成通知 ID */
function generateNotificationId(): string {
return `notif-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
}
/** 定时器映射,避免重复 */
const timers = new Map<string, ReturnType<typeof setTimeout>>();
// ── 默认值 ──────────────────────────────────────────────────────────────────
const INITIAL_STATE: NotificationState = {
notifications: [],
};
// ── Store ───────────────────────────────────────────────────────────────────
export const useNotificationStore = create<NotificationState & NotificationActions>()(
(set, get) => ({
...INITIAL_STATE,
addNotification: ({ type, message, title, duration }) => {
const id = generateNotificationId();
const effectiveDuration = duration ?? DEFAULT_DURATION_BY_TYPE[type];
const notification: Notification = {
id,
type,
message,
title,
createdAt: Date.now(),
duration: effectiveDuration,
};
set((state) => ({
notifications: [...state.notifications, notification],
}));
// 设置自动过期
if (effectiveDuration !== null) {
const timer = setTimeout(() => {
get().removeNotification(id);
timers.delete(id);
}, effectiveDuration);
timers.set(id, timer);
}
return id;
},
removeNotification: (id) => {
// 清除定时器
const timer = timers.get(id);
if (timer) {
clearTimeout(timer);
timers.delete(id);
}
set((state) => ({
notifications: state.notifications.filter((n) => n.id !== id),
}));
},
clearAll: () => {
// 清除所有定时器
timers.forEach((timer) => clearTimeout(timer));
timers.clear();
set({ notifications: [] });
},
})
);