新增 错误提示显示错误详情;

新增 错误提示现在在鼠标悬停时不会消失;
This commit is contained in:
2025-09-14 04:35:11 +08:00
parent fd35325c52
commit 50124d1acb
4 changed files with 130 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
import React, { createContext, useContext, useReducer, useState, useEffect } from 'react';
import React, { createContext, useContext, useReducer, useState, useEffect, useRef } from 'react';
import { Toast } from './Toast';
type ToastType = 'success' | 'error' | 'warning' | 'info';
@@ -8,10 +8,11 @@ export interface ToastMessage {
message: string;
type: ToastType;
duration?: number;
details?: string;
}
export interface ToastContextType {
addToast: (message: string, type: ToastType, duration?: number) => void;
addToast: (message: string, type: ToastType, duration?: number, details?: string) => void;
removeToast: (id: string) => void;
}
@@ -19,14 +20,21 @@ const ToastContext = createContext<ToastContextType | undefined>(undefined);
type ToastAction =
| { type: 'ADD_TOAST'; payload: ToastMessage }
| { type: 'REMOVE_TOAST'; payload: string };
| { type: 'REMOVE_TOAST'; payload: string }
| { type: 'SET_HOVERED_TOAST', payload: { id: string, hovered: boolean } };
const toastReducer = (state: ToastMessage[], action: ToastAction): ToastMessage[] => {
switch (action.type) {
case 'ADD_TOAST':
return [...state, action.payload];
return [...state, { ...action.payload, hovered: false }];
case 'REMOVE_TOAST':
return state.filter(toast => toast.id !== action.payload);
case 'SET_HOVERED_TOAST':
return state.map(toast =>
toast.id === action.payload.id
? { ...toast, hovered: action.payload.hovered }
: toast
);
default:
return state;
}
@@ -34,28 +42,52 @@ const toastReducer = (state: ToastMessage[], action: ToastAction): ToastMessage[
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [toasts, dispatch] = useReducer(toastReducer, []);
const hoverTimeouts = useRef<Record<string, NodeJS.Timeout>>({});
const addToast = (message: string, type: ToastType, duration: number = 5000) => {
const addToast = (message: string, type: ToastType, duration: number = 5000, details?: string) => {
const id = Date.now().toString();
dispatch({ type: 'ADD_TOAST', payload: { id, message, type, duration } });
dispatch({ type: 'ADD_TOAST', payload: { id, message, type, duration, details } });
};
const removeToast = (id: string) => {
dispatch({ type: 'REMOVE_TOAST', payload: id });
};
// Auto remove toasts after duration
const setToastHovered = (id: string, hovered: boolean) => {
dispatch({ type: 'SET_HOVERED_TOAST', payload: { id, hovered } });
};
// Auto remove toasts after duration, but respect hover state
useEffect(() => {
const timers = toasts.map(toast => {
if (toast.duration === 0) return; // 0 means persistent
return setTimeout(() => {
// Clear any existing timeout for this toast
if (hoverTimeouts.current[toast.id]) {
clearTimeout(hoverTimeouts.current[toast.id]);
delete hoverTimeouts.current[toast.id];
}
// If toast is hovered, don't set a timer
if (toast.hovered) {
return null;
}
// If duration is 0, it's persistent
if (toast.duration === 0) {
return null;
}
// Set timeout to remove toast
const timeout = setTimeout(() => {
removeToast(toast.id);
}, toast.duration);
return { id: toast.id, timeout };
});
// Cleanup function
return () => {
timers.forEach(timer => {
if (timer) clearTimeout(timer);
if (timer) clearTimeout(timer.timeout);
});
};
}, [toasts]);
@@ -70,7 +102,9 @@ export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ childre
id={toast.id}
message={toast.message}
type={toast.type}
details={toast.details}
onClose={removeToast}
onHoverChange={(hovered) => setToastHovered(toast.id, hovered)}
/>
))}
</div>