From 52e93316a7f49b1198e1e439d6a1929547f94426 Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Mon, 14 Sep 2026 02:13:34 +0000 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E4=B8=8D=E5=8F=AF=E8=A7=81=E5=AF=BC=E8=87=B4"?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=B2=A1=E5=8F=8D=E5=BA=94"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useToast 曾是只写局部 state 的 stub,返回的 toasts 数组无任何页面渲染, 而全局仅挂载 sonner 的 ,导致 31 个组件的操作提示全部隐形 (用户表现为"点击保存没反应",实际请求已成功到达后端)。 - use-toast.tsx: useToast 统一转发到 sonner,保持返回值结构兼容 - ProfilePage.tsx: 密码修改去掉静默 return,校验失败给出明确提示; 保存按钮改为始终可点,保证点击必有反馈 // Logan.Li --- frontend/src/components/user/ProfilePage.tsx | 14 +++++-- frontend/src/hooks/use-toast.tsx | 40 +++++++++++--------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/user/ProfilePage.tsx b/frontend/src/components/user/ProfilePage.tsx index 125c7dc..ab1c95e 100644 --- a/frontend/src/components/user/ProfilePage.tsx +++ b/frontend/src/components/user/ProfilePage.tsx @@ -55,11 +55,19 @@ export default function ProfilePage() { const username = profile?.username || storeUser?.username || ''; const handleChangePassword = async () => { - if (!oldPwd || newPwd.length < 6) return; + // 显式校验并给出提示,避免"点了没反应"。// Logan.Li + if (!oldPwd) { + toast({ title: '请先输入当前密码', variant: 'destructive' }); + return; + } + if (newPwd.length < 6) { + toast({ title: '新密码至少 6 位', variant: 'destructive' }); + return; + } setPwdSaving(true); try { await authApi.changePassword({ old_password: oldPwd, new_password: newPwd }); - toast({ title: '密码已修改' }); + toast({ title: '密码已修改', description: '下次登录请使用新密码' }); setOldPwd(''); setNewPwd(''); } catch (e: unknown) { @@ -121,7 +129,7 @@ export default function ProfilePage() { setNewPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" /> - diff --git a/frontend/src/hooks/use-toast.tsx b/frontend/src/hooks/use-toast.tsx index 9b550e7..79cd854 100644 --- a/frontend/src/hooks/use-toast.tsx +++ b/frontend/src/hooks/use-toast.tsx @@ -1,33 +1,37 @@ -import { useState, useCallback } from 'react'; +// useToast — 统一委托到全局 sonner ,确保提示真实可见。// Logan.Li +// +// 历史问题:早期版本把 toast 存在 hook 内的局部 state 并返回 toasts 数组, +// 但没有任何页面渲染该数组(全局只挂载了 sonner 的 ), +// 导致所有 useToast() 调用产生的提示都是隐形的(表现为"保存没反应")。 +// 现统一转发到 sonner,保持返回值结构兼容既有调用方。// Logan.Li -interface Toast { - id: string; +import { useCallback } from 'react'; +import { toast as sonnerToast } from 'sonner'; + +export interface ToastOptions { title: string; description?: string; variant?: 'default' | 'destructive'; } interface UseToastReturn { - toast: (opts: { title: string; description?: string; variant?: 'default' | 'destructive' }) => void; - toasts: Toast[]; + toast: (opts: ToastOptions) => void; + toasts: never[]; dismiss: (id: string) => void; } export function useToast(): UseToastReturn { - const [toasts, setToasts] = useState([]); - - const toast = useCallback((opts: { title: string; description?: string; variant?: 'default' | 'destructive' }) => { - const id = Math.random().toString(36).slice(2); - const t: Toast = { id, ...opts }; - setToasts((prev) => [...prev, t]); - setTimeout(() => { - setToasts((prev) => prev.filter((x) => x.id !== id)); - }, 4000); + const toast = useCallback((opts: ToastOptions) => { + const { title, description, variant } = opts; + if (variant === 'destructive') { + sonnerToast.error(title, { description }); + } else { + sonnerToast.success(title, { description }); + } }, []); - const dismiss = useCallback((id: string) => { - setToasts((prev) => prev.filter((t) => t.id !== id)); - }, []); + // sonner 自带 dismiss 能力,此处保留空实现以兼容既有解构。// Logan.Li + const dismiss = useCallback(() => {}, []); - return { toast, toasts, dismiss }; + return { toast, toasts: [], dismiss }; }