From 68e7d1792485b390dee402d8680da3bcdec21288 Mon Sep 17 00:00:00 2001 From: gujiangjiang Date: Fri, 10 Oct 2025 05:36:19 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E9=9B=86=E4=B8=AD=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=85=A8=E5=B1=80Toast=E9=80=9A=E7=9F=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/EmailDetail.tsx | 67 +++------------- frontend/src/components/HeaderMailbox.tsx | 90 +++++---------------- frontend/src/contexts/MailboxContext.tsx | 97 +++++++++++------------ frontend/src/pages/MailboxPage.tsx | 68 ++++------------ 4 files changed, 90 insertions(+), 232 deletions(-) diff --git a/frontend/src/components/EmailDetail.tsx b/frontend/src/components/EmailDetail.tsx index af00f0c..6f79b20 100644 --- a/frontend/src/components/EmailDetail.tsx +++ b/frontend/src/components/EmailDetail.tsx @@ -21,35 +21,16 @@ interface Attachment { const EmailDetail: React.FC = ({ 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(null); const [attachments, setAttachments] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isLoadingAttachments, setIsLoadingAttachments] = useState(false); - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - const errorTimeoutRef = useRef(null); - const successTimeoutRef = useRef(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 = ({ 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 = ({ 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 = ({ 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 = ({ emailId, onClose }) => { return (
- {/* 错误和成功提示 */} - {(errorMessage || successMessage) && ( -
- {errorMessage || successMessage} -
- )} - + {/* fix: 移除局部的错误和成功提示,使用全局通知 */} {isLoading ? (
diff --git a/frontend/src/components/HeaderMailbox.tsx b/frontend/src/components/HeaderMailbox.tsx index 212b791..37233e0 100644 --- a/frontend/src/components/HeaderMailbox.tsx +++ b/frontend/src/components/HeaderMailbox.tsx @@ -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 = ({ 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(null); - const [refreshError, setRefreshError] = useState(null); + // fix: 移除独立的错误状态,因为将使用全局通知 const [customAddressError, setCustomAddressError] = useState(null); - const [customAddressSuccess, setCustomAddressSuccess] = useState(null); const [showRefreshSuccess, setShowRefreshSuccess] = useState(false); const copyTooltipTimeoutRef = useRef(null); const refreshSuccessTimeoutRef = useRef(null); - const successMessageTimeoutRef = useRef(null); - const errorMessageTimeoutRef = useRef(null); // 当props中的domain变化时更新selectedDomain useEffect(() => { @@ -48,12 +47,6 @@ const HeaderMailbox: React.FC = ({ 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 = ({ // 复制邮箱地址到剪贴板 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 = ({ }, 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 = ({ if (result.success && result.mailbox) { onMailboxChange(result.mailbox); - // 显示更新成功提示 + // 显示更新成功提示 (Tooltip 形式,保留局部状态) setShowRefreshSuccess(true); // 3秒后隐藏提示 @@ -115,16 +94,8 @@ const HeaderMailbox: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ setIsCustomMode(false); setCustomAddress(''); setCustomAddressError(null); - setCustomAddressSuccess(null); }; // 移动设备上的邮箱地址显示 @@ -222,7 +188,6 @@ const HeaderMailbox: React.FC = ({ 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 = ({
)} - {/* 成功信息显示 */} - {customAddressSuccess && ( -
- {customAddressSuccess} -
- )} ) : ( <> @@ -360,12 +319,6 @@ const HeaderMailbox: React.FC = ({
- {/* 错误信息显示 */} - {(copyError || refreshError) && ( -
- {copyError || refreshError} -
- )} {/* 移动版显示 */} @@ -433,13 +386,6 @@ const HeaderMailbox: React.FC = ({ - - {/* 错误信息显示 */} - {(copyError || refreshError) && ( -
- {copyError || refreshError} -
- )} )} diff --git a/frontend/src/contexts/MailboxContext.tsx b/frontend/src/contexts/MailboxContext.tsx index 8f39da1..00a65c8 100644 --- a/frontend/src/contexts/MailboxContext.tsx +++ b/frontend/src/contexts/MailboxContext.tsx @@ -40,6 +40,9 @@ interface MailboxContextType { handleMailboxNotFound: () => Promise; errorMessage: string | null; successMessage: string | null; + // feat: 添加用于显示全局通知的函数 + showSuccessMessage: (message: string) => void; + showErrorMessage: (message: string) => void; } export const MailboxContext = createContext({ @@ -62,7 +65,10 @@ export const MailboxContext = createContext({ clearEmailCache: () => {}, handleMailboxNotFound: async () => {}, errorMessage: null, - successMessage: null + successMessage: null, + // feat: 提供默认空函数 + showSuccessMessage: () => {}, + showErrorMessage: () => {}, }); interface MailboxProviderProps { @@ -83,6 +89,29 @@ export const MailboxProvider: React.FC = ({ children }) => const errorTimeoutRef = useRef(null); const successTimeoutRef = useRef(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 = ({ 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 = ({ 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 = ({ 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 = ({ children }) => // [fix]: 重构处理邮箱不存在的逻辑,避免页面刷新 const handleMailboxNotFound = async () => { - // 设置提示信息,告知用户正在创建新邮箱 - setSuccessMessage(t('mailbox.creatingNew')); + // fix: 使用全局通知函数 + showSuccessMessage(t('mailbox.creatingNew')); // 清除当前无效的邮箱信息 removeMailboxFromLocalStorage(); @@ -274,14 +272,6 @@ export const MailboxProvider: React.FC = ({ 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 = ({ children }) => clearEmailCache, handleMailboxNotFound, errorMessage, - successMessage + successMessage, + // feat: 将函数添加到 context value 中 + showSuccessMessage, + showErrorMessage, }} > - {/* [feat] 将错误和成功提示移至此处,作为全局通知组件 */} + {/* [feat] 全局通知组件 */} {(errorMessage || successMessage) && (
{errorMessage || successMessage} diff --git a/frontend/src/pages/MailboxPage.tsx b/frontend/src/pages/MailboxPage.tsx index 4042697..5c792f2 100644 --- a/frontend/src/pages/MailboxPage.tsx +++ b/frontend/src/pages/MailboxPage.tsx @@ -15,28 +15,15 @@ const MailboxPage: React.FC = () => { emails, isEmailsLoading, autoRefresh, - setAutoRefresh + setAutoRefresh, + // feat: 从 context 中获取全局通知函数 + showSuccessMessage, + showErrorMessage } = useContext(MailboxContext); const [mailbox, setMailbox] = useState(null); const [selectedEmail, setSelectedEmail] = useState(null); const [isLoading, setIsLoading] = useState(true); - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - const errorTimeoutRef = useRef(null); - const successTimeoutRef = useRef(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 (
- {/* 错误和成功提示 */} - {(errorMessage || successMessage) && ( -
- {errorMessage || successMessage} -
- )} + {/* fix: 移除局部的错误和成功提示 */} {mailbox && ( Date: Fri, 10 Oct 2025 06:03:19 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=85=A8=E9=9D=A2=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=E5=85=A8=E5=B1=80Toast=E9=80=9A=E7=9F=A5=EF=BC=8C?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E5=88=B7=E6=96=B0=E3=80=81=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E3=80=81=E6=9B=B4=E6=8D=A2=E5=92=8C=E5=88=A0=E9=99=A4=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E7=AD=89=E5=A4=9A=E4=B8=AA=E6=93=8D=E4=BD=9C=E5=9C=BA?= =?UTF-8?q?=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/i18n/locales/en.json | 7 +- frontend/i18n/locales/zh-CN.json | 7 +- frontend/src/components/EmailList.tsx | 3 +- frontend/src/components/HeaderMailbox.tsx | 118 ++++---------------- frontend/src/components/MailboxSwitcher.tsx | 29 +++-- frontend/src/contexts/MailboxContext.tsx | 42 ++++--- 6 files changed, 80 insertions(+), 126 deletions(-) diff --git a/frontend/i18n/locales/en.json b/frontend/i18n/locales/en.json index cfa4a65..046e919 100644 --- a/frontend/i18n/locales/en.json +++ b/frontend/i18n/locales/en.json @@ -57,14 +57,19 @@ "notFound": "Mailbox not found or expired", "creatingNew": "Creating new mailbox...", "switch": "Switch Mailbox", + "switchSuccess": "Mailbox switched successfully", "savedMailboxes": "Saved Mailboxes", "confirmDeleteMailbox": "Are you sure you want to delete this saved mailbox?", + "deleteSavedSuccess": "Saved mailbox deleted successfully", "confirmClearAllMailboxes": "Are you sure you want to clear all saved mailboxes? (The current mailbox will not be deleted)", - "clearAll": "Clear All" + "clearAll": "Clear All", + "clearAllSuccess": "All saved mailboxes have been cleared", + "clearAllFailed": "Failed to clear some mailboxes ({{count}})" }, "email": { "inbox": "Inbox", "refresh": "Refresh Emails", + "refreshSuccess": "Email list has been refreshed", "autoRefresh": "Auto Refresh", "autoRefreshOn": "Auto Refresh: On", "autoRefreshOff": "Auto Refresh: Off", diff --git a/frontend/i18n/locales/zh-CN.json b/frontend/i18n/locales/zh-CN.json index 108b173..95a55f8 100644 --- a/frontend/i18n/locales/zh-CN.json +++ b/frontend/i18n/locales/zh-CN.json @@ -57,14 +57,19 @@ "notFound": "邮箱不存在或已过期", "creatingNew": "正在创建新邮箱...", "switch": "切换邮箱", + "switchSuccess": "邮箱切换成功", "savedMailboxes": "已保存的邮箱", "confirmDeleteMailbox": "您确定要删除这个已保存的邮箱吗?", + "deleteSavedSuccess": "已保存的邮箱删除成功", "confirmClearAllMailboxes": "您确定要清空所有已保存的邮箱吗?(当前使用的邮箱不会被删除)", - "clearAll": "全部清除" + "clearAll": "全部清除", + "clearAllSuccess": "已清空所有保存的邮箱", + "clearAllFailed": "部分邮箱清空失败 ({{count}}个)" }, "email": { "inbox": "收件箱", "refresh": "刷新邮件", + "refreshSuccess": "邮件列表已刷新", "autoRefresh": "自动刷新", "autoRefreshOn": "自动刷新: 开", "autoRefreshOff": "自动刷新: 关", diff --git a/frontend/src/components/EmailList.tsx b/frontend/src/components/EmailList.tsx index 2396e0a..ceb18ea 100644 --- a/frontend/src/components/EmailList.tsx +++ b/frontend/src/components/EmailList.tsx @@ -62,7 +62,8 @@ const EmailList: React.FC = ({ }; const handleRefresh = () => { - refreshEmails(); + // feat: 调用 context 中的 refreshEmails,并传入 true 表示是手动刷新 + refreshEmails(true); }; const toggleAutoRefresh = () => { diff --git a/frontend/src/components/HeaderMailbox.tsx b/frontend/src/components/HeaderMailbox.tsx index 37233e0..0b25694 100644 --- a/frontend/src/components/HeaderMailbox.tsx +++ b/frontend/src/components/HeaderMailbox.tsx @@ -1,8 +1,8 @@ -import React, { useState, useRef, useEffect, useContext } from 'react'; // feat: 导入 useContext +import React, { useState, useRef, useEffect, useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { createRandomMailbox, createCustomMailbox } from '../utils/api'; import MailboxSwitcher from './MailboxSwitcher'; -import { MailboxContext } from '../contexts/MailboxContext'; // feat: 导入 MailboxContext +import { MailboxContext } from '../contexts/MailboxContext'; interface HeaderMailboxProps { mailbox: Mailbox | null; @@ -20,36 +20,18 @@ const HeaderMailbox: React.FC = ({ isLoading }) => { const { t } = useTranslation(); - // feat: 从 context 中获取全局通知函数 + // feat: 统一使用全局通知 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); - // fix: 移除独立的错误状态,因为将使用全局通知 const [customAddressError, setCustomAddressError] = useState(null); - const [showRefreshSuccess, setShowRefreshSuccess] = useState(false); - const copyTooltipTimeoutRef = useRef(null); - const refreshSuccessTimeoutRef = useRef(null); - - // 当props中的domain变化时更新selectedDomain + useEffect(() => { setSelectedDomain(domain); }, [domain]); - // 清除提示的定时器 - useEffect(() => { - return () => { - if (copyTooltipTimeoutRef.current) { - window.clearTimeout(copyTooltipTimeoutRef.current); - } - if (refreshSuccessTimeoutRef.current) { - window.clearTimeout(refreshSuccessTimeoutRef.current); - } - }; - }, []); - if (!mailbox || isLoading) return null; // 复制邮箱地址到剪贴板 @@ -57,16 +39,8 @@ const HeaderMailbox: React.FC = ({ const fullAddress = mailbox.address.includes('@') ? mailbox.address : `${mailbox.address}@${selectedDomain}`; navigator.clipboard.writeText(fullAddress) .then(() => { - // 显示复制成功提示 (Tooltip 形式,保留局部状态) - setShowCopyTooltip(true); - - // 2秒后隐藏提示 - if (copyTooltipTimeoutRef.current) { - window.clearTimeout(copyTooltipTimeoutRef.current); - } - copyTooltipTimeoutRef.current = window.setTimeout(() => { - setShowCopyTooltip(false); - }, 2000); + // feat: 使用全局通知替换 Tooltip + showSuccessMessage(t('mailbox.copySuccess')); }) .catch(() => { // fix: 使用全局通知函数显示复制失败 @@ -82,17 +56,8 @@ const HeaderMailbox: React.FC = ({ if (result.success && result.mailbox) { onMailboxChange(result.mailbox); - - // 显示更新成功提示 (Tooltip 形式,保留局部状态) - setShowRefreshSuccess(true); - - // 3秒后隐藏提示 - if (refreshSuccessTimeoutRef.current) { - window.clearTimeout(refreshSuccessTimeoutRef.current); - } - refreshSuccessTimeoutRef.current = window.setTimeout(() => { - setShowRefreshSuccess(false); - }, 3000); + // feat: 使用全局通知替换 Tooltip + showSuccessMessage(t('mailbox.refreshSuccess')); } else { // fix: 使用全局通知函数显示刷新失败 showErrorMessage(t('mailbox.refreshFailed')); @@ -120,17 +85,14 @@ const HeaderMailbox: React.FC = ({ // fix: 使用全局通知函数显示成功 showSuccessMessage(t('mailbox.createSuccess')); - // 3秒后自动关闭表单 + // 成功后延迟关闭自定义输入框 setTimeout(() => { setIsCustomMode(false); setCustomAddress(''); - }, 3000); + }, 1500); + } else { - // 检查错误信息是否包含"邮箱地址已存在" - const isAddressExistsError = - result.error === 'Address already exists' || - String(result.error).includes('已存在'); - + const isAddressExistsError = result.error === 'Address already exists' || String(result.error).includes('已存在'); if (isAddressExistsError) { // fix: 对于表单内校验错误,保留局部状态提示 setCustomAddressError(t('mailbox.addressExists')); @@ -272,22 +234,14 @@ const HeaderMailbox: React.FC = ({ />
- - - {/* 复制成功提示 */} - {showCopyTooltip && ( -
- {t('mailbox.copySuccess')} -
-
- )}
@@ -299,14 +253,6 @@ const HeaderMailbox: React.FC = ({ > - - {/* 更新成功提示 */} - {showRefreshSuccess && ( -
- {t('mailbox.refreshSuccess')} -
-
- )}
- - - {/* 复制成功提示 */} - {showCopyTooltip && ( -
- {t('mailbox.copySuccess')} -
-
- )}
@@ -360,29 +298,21 @@ const HeaderMailbox: React.FC = ({
- - {/* 更新成功提示 */} - {showRefreshSuccess && ( -
- {t('mailbox.refreshSuccess')} -
-
- )}
diff --git a/frontend/src/components/MailboxSwitcher.tsx b/frontend/src/components/MailboxSwitcher.tsx index 58e0e88..2dea035 100644 --- a/frontend/src/components/MailboxSwitcher.tsx +++ b/frontend/src/components/MailboxSwitcher.tsx @@ -1,7 +1,7 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useContext } from 'react'; // feat: 导入 useContext import { useTranslation } from 'react-i18next'; -// [feat]: 导入 deleteMailbox API 函数 import { deleteMailbox as apiDeleteMailbox } from '../utils/api'; +import { MailboxContext } from '../contexts/MailboxContext'; // feat: 导入 MailboxContext interface MailboxSwitcherProps { currentMailbox: Mailbox; @@ -15,6 +15,8 @@ const MailboxSwitcher: React.FC = ({ domain }) => { const { t } = useTranslation(); + // feat: 从 context 中获取全局通知函数 + const { showSuccessMessage, showErrorMessage } = useContext(MailboxContext); const [savedMailboxes, setSavedMailboxes] = useState([]); const [showDropdown, setShowDropdown] = useState(false); const dropdownRef = useRef(null); @@ -103,9 +105,11 @@ const MailboxSwitcher: React.FC = ({ const handleSwitchMailbox = (mailbox: Mailbox) => { onSwitchMailbox(mailbox); setShowDropdown(false); + // feat: 切换邮箱也给出提示 + showSuccessMessage(t('mailbox.switchSuccess')); }; - // 删除单个已保存的邮箱(包括后端) + // 删除单个已保存的邮箱 const handleDeleteMailbox = async (address: string) => { if (window.confirm(t('mailbox.confirmDeleteMailbox'))) { // 调用API删除后端的邮箱 @@ -115,14 +119,16 @@ const MailboxSwitcher: React.FC = ({ const updatedMailboxes = savedMailboxes.filter(m => m.address !== address); setSavedMailboxes(updatedMailboxes); localStorage.setItem('savedMailboxes', JSON.stringify(updatedMailboxes)); + // feat: 删除成功提示 + showSuccessMessage(t('mailbox.deleteSavedSuccess')); } else { - // 如果删除失败,可以给用户一个提示 - alert(t('mailbox.deleteFailed')); + // feat: 删除失败提示 + showErrorMessage(t('mailbox.deleteFailed')); } } }; - // [feat]: 清空所有已保存的邮箱(包括后端数据) + // 清空所有已保存的邮箱 const handleClearAllMailboxes = async () => { if (window.confirm(t('mailbox.confirmClearAllMailboxes'))) { // 找出所有需要删除的邮箱(即列表中,非当前正在使用的邮箱) @@ -136,7 +142,8 @@ const MailboxSwitcher: React.FC = ({ // 并行执行所有后端删除请求 const deletePromises = mailboxesToDelete.map(m => apiDeleteMailbox(m.address)); - await Promise.all(deletePromises); + // feat: 使用 Promise.allSettled 来处理部分失败的情况 + const results = await Promise.allSettled(deletePromises); // 从前端列表中只保留当前使用的邮箱 const currentMailboxToKeep = savedMailboxes.find(m => m.address === currentMailbox.address); @@ -146,6 +153,14 @@ const MailboxSwitcher: React.FC = ({ setSavedMailboxes(mailboxesToKeep); localStorage.setItem('savedMailboxes', JSON.stringify(mailboxesToKeep)); setShowDropdown(false); + + // feat: 根据结果显示不同的通知 + const failedCount = results.filter(r => r.status === 'rejected').length; + if (failedCount > 0) { + showErrorMessage(t('mailbox.clearAllFailed', { count: failedCount })); + } else { + showSuccessMessage(t('mailbox.clearAllSuccess')); + } } }; diff --git a/frontend/src/contexts/MailboxContext.tsx b/frontend/src/contexts/MailboxContext.tsx index 00a65c8..9e85fc5 100644 --- a/frontend/src/contexts/MailboxContext.tsx +++ b/frontend/src/contexts/MailboxContext.tsx @@ -33,7 +33,7 @@ interface MailboxContextType { setAutoRefresh: (autoRefresh: boolean) => void; createNewMailbox: () => Promise; deleteMailbox: () => Promise; - refreshEmails: () => Promise; + refreshEmails: (isManual?: boolean) => Promise; // feat: 添加一个参数以区分手动刷新 emailCache: EmailCache; addToEmailCache: (emailId: string, email: Email, attachments: any[]) => void; clearEmailCache: () => void; @@ -148,24 +148,18 @@ export const MailboxProvider: React.FC = ({ children }) => // 清除之前的错误和成功信息 setErrorMessage(null); setSuccessMessage(null); - - console.log('createNewMailbox: Started'); setIsLoading(true); - - console.log('createNewMailbox: Calling createRandomMailbox...'); const result = await createRandomMailbox(); - console.log('createNewMailbox: createRandomMailbox result:', result); - if (result.success && result.mailbox) { - console.log('createNewMailbox: Setting new mailbox:', result.mailbox); setMailbox(result.mailbox); saveMailboxToLocalStorage(result.mailbox); // [fix]: 创建新邮箱后,清空旧的邮件列表和缓存 setEmails([]); setSelectedEmail(null); clearEmailCache(); + // feat: 创建新邮箱也给出提示 + showSuccessMessage(t('mailbox.createSuccess')); } else { - console.error('createNewMailbox: Failed to create mailbox:', result.error); // fix: 使用全局通知函数 showErrorMessage(t('mailbox.createFailed')); throw new Error('Failed to create mailbox'); @@ -215,13 +209,9 @@ export const MailboxProvider: React.FC = ({ children }) => } }; - // 刷新邮件列表 - const refreshEmails = async () => { - if (!mailbox) return; - - // 防止重复请求 - if (isEmailsLoading) return; - + // feat: 增加 isManual 参数,只有手动点击刷新时才显示Toast + const refreshEmails = async (isManual = false) => { + if (!mailbox || isEmailsLoading) return; setIsEmailsLoading(true); try { @@ -229,13 +219,25 @@ export const MailboxProvider: React.FC = ({ children }) => if (result.success) { setEmails(result.emails); + // feat: 手动刷新成功时显示Toast + if (isManual) { + showSuccessMessage(t('email.refreshSuccess')); + } } else if (result.notFound) { // [fix]: 如果邮箱不存在,调用 handleMailboxNotFound 进行平滑处理,而不是强制刷新页面 await handleMailboxNotFound(); + } else { + // feat: 刷新失败时也显示Toast + if (isManual) { + showErrorMessage(t('email.fetchFailed')); + } } } catch (error) { // 错误处理 console.error('Error refreshing emails:', error); + if (isManual) { + showErrorMessage(t('email.fetchFailed')); + } } finally { setIsEmailsLoading(false); } @@ -244,14 +246,10 @@ export const MailboxProvider: React.FC = ({ children }) => // 自动刷新邮件 useEffect(() => { if (!mailbox || isLoading) return; - - // 首次加载邮件(无论autoRefresh是否开启) - refreshEmails(); - - // 如果自动刷新开启,则设置定时器 + refreshEmails(); // 初始加载不显示 a Toast let intervalId: number | undefined; if (autoRefresh) { - intervalId = window.setInterval(refreshEmails, AUTO_REFRESH_INTERVAL); + intervalId = window.setInterval(() => refreshEmails(), AUTO_REFRESH_INTERVAL); // 自动刷新不显示 a Toast } return () => {