feat: 集中实现全局Toast通知功能

This commit is contained in:
gujiangjiang committed 2025-10-10 05:36:19 +08:00
1 parent 3192f046ed
commit 68e7d17924
4 files changed
+90 -232

No files matched your search

+11 -56
View File
@@ -21,35 +21,16 @@ interface Attachment {
const EmailDetail: React.FC<EmailDetailProps> = ({ emailId, onClose }) => {
const { t } = useTranslation();
const { emailCache, addToEmailCache, handleMailboxNotFound } = useContext(MailboxContext);
// fix: 从 context 中获取全局通知函数
const { emailCache, addToEmailCache, handleMailboxNotFound, showErrorMessage, showSuccessMessage } = useContext(MailboxContext);
const [email, setEmail] = useState<Email | null>(null);
const [attachments, setAttachments] = useState<Attachment[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isLoadingAttachments, setIsLoadingAttachments] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const errorTimeoutRef = useRef<number | null>(null);
const successTimeoutRef = useRef<number | null>(null);
// 清除提示的定时器
useEffect(() => {
return () => {
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
if (successTimeoutRef.current) {
window.clearTimeout(successTimeoutRef.current);
}
};
}, []);
useEffect(() => {
const fetchEmail = async () => {
try {
// 清除之前的错误和成功信息
setErrorMessage(null);
setSuccessMessage(null);
// 首先检查缓存中是否有该邮件
if (emailCache[emailId]) {
setEmail(emailCache[emailId].email);
@@ -86,22 +67,15 @@ const EmailDetail: React.FC<EmailDetailProps> = ({ emailId, onClose }) => {
throw new Error(data.error || 'Unknown error');
}
} catch (error) {
setErrorMessage(t('email.fetchFailed'));
// 3秒后清除错误信息
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
// fix: 使用全局通知函数
showErrorMessage(t('email.fetchFailed'));
} finally {
setIsLoading(false);
}
};
fetchEmail();
}, [emailId, t, emailCache, addToEmailCache, handleMailboxNotFound, onClose]);
}, [emailId, t, emailCache, addToEmailCache, handleMailboxNotFound, onClose, showErrorMessage]);
const fetchAttachments = async (emailId: string, emailData?: Email) => {
try {
@@ -138,10 +112,6 @@ const EmailDetail: React.FC<EmailDetailProps> = ({ emailId, onClose }) => {
const handleDelete = async () => {
try {
// 清除之前的错误和成功信息
setErrorMessage(null);
setSuccessMessage(null);
const response = await fetch(`${API_BASE_URL}/api/emails/${emailId}`, {
method: 'DELETE',
});
@@ -152,28 +122,19 @@ const EmailDetail: React.FC<EmailDetailProps> = ({ emailId, onClose }) => {
const data = await response.json();
if (data.success) {
setSuccessMessage(t('email.deleteSuccess'));
// fix: 使用全局通知函数
showSuccessMessage(t('email.deleteSuccess'));
// 2秒后关闭邮件详情
if (successTimeoutRef.current) {
window.clearTimeout(successTimeoutRef.current);
}
successTimeoutRef.current = window.setTimeout(() => {
setTimeout(() => {
onClose();
}, 2000);
} else {
throw new Error(data.error || 'Unknown error');
}
} catch (error) {
setErrorMessage(t('email.deleteFailed'));
// 3秒后清除错误信息
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
// fix: 使用全局通知函数
showErrorMessage(t('email.deleteFailed'));
}
};
@@ -285,13 +246,7 @@ const EmailDetail: React.FC<EmailDetailProps> = ({ emailId, onClose }) => {
return (
<div className="border rounded-lg p-6">
{/* 错误和成功提示 */}
{(errorMessage || successMessage) && (
<div className={`p-3 mb-4 rounded-md ${errorMessage ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'}`}>
{errorMessage || successMessage}
</div>
)}
{/* fix: 移除局部的错误和成功提示,使用全局通知 */}
{isLoading ? (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
+18 -72
View File
@@ -1,7 +1,8 @@
import React, { useState, useRef, useEffect } from 'react';
import React, { useState, useRef, useEffect, useContext } from 'react'; // feat: 导入 useContext
import { useTranslation } from 'react-i18next';
import { createRandomMailbox, createCustomMailbox } from '../utils/api';
import MailboxSwitcher from './MailboxSwitcher';
import { MailboxContext } from '../contexts/MailboxContext'; // feat: 导入 MailboxContext
interface HeaderMailboxProps {
mailbox: Mailbox | null;
@@ -19,20 +20,18 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
isLoading
}) => {
const { t } = useTranslation();
// feat: 从 context 中获取全局通知函数
const { showSuccessMessage, showErrorMessage } = useContext(MailboxContext);
const [isCustomMode, setIsCustomMode] = useState(false);
const [customAddress, setCustomAddress] = useState('');
const [selectedDomain, setSelectedDomain] = useState(domain);
const [isActionLoading, setIsActionLoading] = useState(false);
const [showCopyTooltip, setShowCopyTooltip] = useState(false);
const [copyError, setCopyError] = useState<string | null>(null);
const [refreshError, setRefreshError] = useState<string | null>(null);
// fix: 移除独立的错误状态,因为将使用全局通知
const [customAddressError, setCustomAddressError] = useState<string | null>(null);
const [customAddressSuccess, setCustomAddressSuccess] = useState<string | null>(null);
const [showRefreshSuccess, setShowRefreshSuccess] = useState(false);
const copyTooltipTimeoutRef = useRef<number | null>(null);
const refreshSuccessTimeoutRef = useRef<number | null>(null);
const successMessageTimeoutRef = useRef<number | null>(null);
const errorMessageTimeoutRef = useRef<number | null>(null);
// 当props中的domain变化时更新selectedDomain
useEffect(() => {
@@ -48,12 +47,6 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
if (refreshSuccessTimeoutRef.current) {
window.clearTimeout(refreshSuccessTimeoutRef.current);
}
if (successMessageTimeoutRef.current) {
window.clearTimeout(successMessageTimeoutRef.current);
}
if (errorMessageTimeoutRef.current) {
window.clearTimeout(errorMessageTimeoutRef.current);
}
};
}, []);
@@ -61,13 +54,10 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
// 复制邮箱地址到剪贴板
const copyToClipboard = () => {
// 清除之前的错误信息
setCopyError(null);
const fullAddress = mailbox.address.includes('@') ? mailbox.address : `${mailbox.address}@${selectedDomain}`;
navigator.clipboard.writeText(fullAddress)
.then(() => {
// 显示复制成功提示
// 显示复制成功提示 (Tooltip 形式,保留局部状态)
setShowCopyTooltip(true);
// 2秒后隐藏提示
@@ -79,24 +69,13 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
}, 2000);
})
.catch(() => {
// 显示复制失败错误
setCopyError(t('mailbox.copyFailed'));
// 3秒后隐藏错误
if (errorMessageTimeoutRef.current) {
window.clearTimeout(errorMessageTimeoutRef.current);
}
errorMessageTimeoutRef.current = window.setTimeout(() => {
setCopyError(null);
}, 3000);
// fix: 使用全局通知函数显示复制失败
showErrorMessage(t('mailbox.copyFailed'));
});
};
// 更换随机邮箱
const handleRefreshMailbox = async () => {
// 清除之前的错误信息
setRefreshError(null);
setIsActionLoading(true);
const result = await createRandomMailbox();
setIsActionLoading(false);
@@ -104,7 +83,7 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
if (result.success && result.mailbox) {
onMailboxChange(result.mailbox);
// 显示更新成功提示
// 显示更新成功提示 (Tooltip 形式,保留局部状态)
setShowRefreshSuccess(true);
// 3秒后隐藏提示
@@ -115,16 +94,8 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
setShowRefreshSuccess(false);
}, 3000);
} else {
// 显示刷新失败错误
setRefreshError(t('mailbox.refreshFailed'));
// 3秒后隐藏错误
if (errorMessageTimeoutRef.current) {
window.clearTimeout(errorMessageTimeoutRef.current);
}
errorMessageTimeoutRef.current = window.setTimeout(() => {
setRefreshError(null);
}, 3000);
// fix: 使用全局通知函数显示刷新失败
showErrorMessage(t('mailbox.refreshFailed'));
}
};
@@ -132,9 +103,8 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
const handleCreateCustom = async (e: React.FormEvent) => {
e.preventDefault();
// 清除之前的错误和成功信息
// 清除之前的错误信息
setCustomAddressError(null);
setCustomAddressSuccess(null);
if (!customAddress.trim()) {
setCustomAddressError(t('mailbox.invalidAddress'));
@@ -147,18 +117,13 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
if (result.success && result.mailbox) {
onMailboxChange(result.mailbox);
// 显示成功消息在表单下方
setCustomAddressSuccess(t('mailbox.createSuccess'));
// fix: 使用全局通知函数显示成功
showSuccessMessage(t('mailbox.createSuccess'));
// 3秒后自动关闭表单
if (successMessageTimeoutRef.current) {
window.clearTimeout(successMessageTimeoutRef.current);
}
successMessageTimeoutRef.current = window.setTimeout(() => {
setTimeout(() => {
setIsCustomMode(false);
setCustomAddress('');
setCustomAddressSuccess(null);
}, 3000);
} else {
// 检查错误信息是否包含"邮箱地址已存在"
@@ -167,9 +132,11 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
String(result.error).includes('已存在');
if (isAddressExistsError) {
// fix: 对于表单内校验错误,保留局部状态提示
setCustomAddressError(t('mailbox.addressExists'));
} else {
setCustomAddressError(t('mailbox.createFailed'));
// fix: 对于通用创建失败,使用全局通知
showErrorMessage(t('mailbox.createFailed'));
}
}
};
@@ -179,7 +146,6 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
setIsCustomMode(false);
setCustomAddress('');
setCustomAddressError(null);
setCustomAddressSuccess(null);
};
// 移动设备上的邮箱地址显示
@@ -222,7 +188,6 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
onChange={(e) => {
setCustomAddress(e.target.value);
if (customAddressError) setCustomAddressError(null);
if (customAddressSuccess) setCustomAddressSuccess(null);
}}
className={`w-32 md:w-40 px-2 py-1 text-sm border rounded-l-md focus:outline-none focus:ring-1 focus:ring-primary ${
customAddressError ? 'border-red-500' : ''
@@ -274,12 +239,6 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
</div>
)}
{/* 成功信息显示 */}
{customAddressSuccess && (
<div className="text-green-500 text-xs px-1">
{customAddressSuccess}
</div>
)}
</form>
) : (
<>
@@ -360,12 +319,6 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
</button>
</div>
{/* 错误信息显示 */}
{(copyError || refreshError) && (
<div className="text-red-500 text-xs mt-1">
{copyError || refreshError}
</div>
)}
</div>
{/* 移动版显示 */}
@@ -433,13 +386,6 @@ const HeaderMailbox: React.FC<HeaderMailboxProps> = ({
</button>
</div>
</div>
{/* 错误信息显示 */}
{(copyError || refreshError) && (
<div className="text-red-500 text-xs mt-1">
{copyError || refreshError}
</div>
)}
</>
)}
</div>
+45 -52
View File
@@ -40,6 +40,9 @@ interface MailboxContextType {
handleMailboxNotFound: () => Promise<void>;
errorMessage: string | null;
successMessage: string | null;
// feat: 添加用于显示全局通知的函数
showSuccessMessage: (message: string) => void;
showErrorMessage: (message: string) => void;
}
export const MailboxContext = createContext<MailboxContextType>({
@@ -62,7 +65,10 @@ export const MailboxContext = createContext<MailboxContextType>({
clearEmailCache: () => {},
handleMailboxNotFound: async () => {},
errorMessage: null,
successMessage: null
successMessage: null,
// feat: 提供默认空函数
showSuccessMessage: () => {},
showErrorMessage: () => {},
});
interface MailboxProviderProps {
@@ -83,6 +89,29 @@ export const MailboxProvider: React.FC<MailboxProviderProps> = ({ children }) =>
const errorTimeoutRef = useRef<number | null>(null);
const successTimeoutRef = useRef<number | null>(null);
// feat: 创建显示成功消息的函数
const showSuccessMessage = (message: string) => {
setSuccessMessage(message);
if (successTimeoutRef.current) {
window.clearTimeout(successTimeoutRef.current);
}
successTimeoutRef.current = window.setTimeout(() => {
setSuccessMessage(null);
}, 3000);
};
// feat: 创建显示错误消息的函数
const showErrorMessage = (message: string) => {
setErrorMessage(message);
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
};
// 清除提示的定时器
useEffect(() => {
return () => {
@@ -137,15 +166,8 @@ export const MailboxProvider: React.FC<MailboxProviderProps> = ({ children }) =>
clearEmailCache();
} else {
console.error('createNewMailbox: Failed to create mailbox:', result.error);
setErrorMessage(t('mailbox.createFailed'));
// 3秒后清除错误信息
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
// fix: 使用全局通知函数
showErrorMessage(t('mailbox.createFailed'));
throw new Error('Failed to create mailbox');
}
} catch (error) {
@@ -169,8 +191,8 @@ export const MailboxProvider: React.FC<MailboxProviderProps> = ({ children }) =>
const result = await apiDeleteMailbox(mailbox.address);
if (result.success) {
// 显示成功信息
setSuccessMessage(t('mailbox.deleteSuccess'));
// fix: 使用全局通知函数
showSuccessMessage(t('mailbox.deleteSuccess'));
// 清除本地数据
setMailbox(null);
@@ -179,41 +201,17 @@ export const MailboxProvider: React.FC<MailboxProviderProps> = ({ children }) =>
removeMailboxFromLocalStorage();
clearEmailCache();
// 3秒后清除成功信息
if (successTimeoutRef.current) {
window.clearTimeout(successTimeoutRef.current);
}
successTimeoutRef.current = window.setTimeout(() => {
setSuccessMessage(null);
}, 3000);
// 创建新邮箱
await createNewMailbox();
} else {
// 显示错误信息
setErrorMessage(t('mailbox.deleteFailed'));
// 3秒后清除错误信息
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
// fix: 使用全局通知函数
showErrorMessage(t('mailbox.deleteFailed'));
}
} catch (error) {
console.error('Error deleting mailbox:', error);
// 显示错误信息
setErrorMessage(t('mailbox.deleteFailed'));
// 3秒后清除错误信息
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
// fix: 使用全局通知函数
showErrorMessage(t('mailbox.deleteFailed'));
}
};
@@ -265,8 +263,8 @@ export const MailboxProvider: React.FC<MailboxProviderProps> = ({ children }) =>
// [fix]: 重构处理邮箱不存在的逻辑,避免页面刷新
const handleMailboxNotFound = async () => {
// 设置提示信息,告知用户正在创建新邮箱
setSuccessMessage(t('mailbox.creatingNew'));
// fix: 使用全局通知函数
showSuccessMessage(t('mailbox.creatingNew'));
// 清除当前无效的邮箱信息
removeMailboxFromLocalStorage();
@@ -274,14 +272,6 @@ export const MailboxProvider: React.FC<MailboxProviderProps> = ({ children }) =>
// 异步创建新邮箱,并更新应用状态
await createNewMailbox();
// 3秒后清除提示信息
if (successTimeoutRef.current) {
window.clearTimeout(successTimeoutRef.current);
}
successTimeoutRef.current = window.setTimeout(() => {
setSuccessMessage(null);
}, 3000);
};
// 添加邮件到缓存
@@ -376,10 +366,13 @@ export const MailboxProvider: React.FC<MailboxProviderProps> = ({ children }) =>
clearEmailCache,
handleMailboxNotFound,
errorMessage,
successMessage
successMessage,
// feat: 将函数添加到 context value 中
showSuccessMessage,
showErrorMessage,
}}
>
{/* [feat] 将错误和成功提示移至此处,作为全局通知组件 */}
{/* [feat] 全局通知组件 */}
{(errorMessage || successMessage) && (
<div className="fixed top-4 right-4 z-50 p-3 rounded-md shadow-lg max-w-md" style={{ backgroundColor: errorMessage ? '#FEE2E2' : '#ECFDF5', color: errorMessage ? '#991B1B' : '#065F46' }}>
{errorMessage || successMessage}
+16 -52
View File
@@ -15,28 +15,15 @@ const MailboxPage: React.FC = () => {
emails,
isEmailsLoading,
autoRefresh,
setAutoRefresh
setAutoRefresh,
// feat: 从 context 中获取全局通知函数
showSuccessMessage,
showErrorMessage
} = useContext(MailboxContext);
const [mailbox, setMailbox] = useState<Mailbox | null>(null);
const [selectedEmail, setSelectedEmail] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const errorTimeoutRef = useRef<number | null>(null);
const successTimeoutRef = useRef<number | null>(null);
// 清除提示的定时器
useEffect(() => {
return () => {
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
if (successTimeoutRef.current) {
window.clearTimeout(successTimeoutRef.current);
}
};
}, []);
// 获取邮箱信息
useEffect(() => {
@@ -49,13 +36,11 @@ const MailboxPage: React.FC = () => {
if (!response.ok) {
if (response.status === 404) {
setErrorMessage(t('mailbox.invalidAddress'));
// fix: 使用全局通知函数
showErrorMessage(t('mailbox.invalidAddress'));
// 3秒后导航到首页
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setTimeout(() => {
navigate('/');
}, 3000);
return;
@@ -70,22 +55,15 @@ const MailboxPage: React.FC = () => {
throw new Error(data.error || 'Unknown error');
}
} catch (error) {
setErrorMessage(String(error));
// 3秒后清除错误信息
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
// fix: 使用全局通知函数
showErrorMessage(String(error));
} finally {
setIsLoading(false);
}
};
fetchMailbox();
}, [address, navigate, t]);
}, [address, navigate, t, showErrorMessage]);
// 处理删除邮箱
const handleDeleteMailbox = async () => {
@@ -102,39 +80,25 @@ const MailboxPage: React.FC = () => {
const data = await response.json();
if (data.success) {
setSuccessMessage(t('mailbox.deleteSuccess'));
// fix: 使用全局通知函数
showSuccessMessage(t('mailbox.deleteSuccess'));
// 2秒后导航到首页
if (successTimeoutRef.current) {
window.clearTimeout(successTimeoutRef.current);
}
successTimeoutRef.current = window.setTimeout(() => {
setTimeout(() => {
navigate('/');
}, 2000);
} else {
throw new Error(data.error || 'Unknown error');
}
} catch (error) {
setErrorMessage(t('mailbox.deleteFailed'));
// 3秒后清除错误信息
if (errorTimeoutRef.current) {
window.clearTimeout(errorTimeoutRef.current);
}
errorTimeoutRef.current = window.setTimeout(() => {
setErrorMessage(null);
}, 3000);
// fix: 使用全局通知函数
showErrorMessage(t('mailbox.deleteFailed'));
}
};
return (
<div className="flex flex-col space-y-6">
{/* 错误和成功提示 */}
{(errorMessage || successMessage) && (
<div className={`p-3 rounded-md ${errorMessage ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'}`}>
{errorMessage || successMessage}
</div>
)}
{/* fix: 移除局部的错误和成功提示 */}
{mailbox && (
<MailboxInfo