fix(frontend): 修复提示不可见导致"保存没反应"
useToast 曾是只写局部 state 的 stub,返回的 toasts 数组无任何页面渲染, 而全局仅挂载 sonner 的 <Toaster />,导致 31 个组件的操作提示全部隐形 (用户表现为"点击保存没反应",实际请求已成功到达后端)。 - use-toast.tsx: useToast 统一转发到 sonner,保持返回值结构兼容 - ProfilePage.tsx: 密码修改去掉静默 return,校验失败给出明确提示; 保存按钮改为始终可点,保证点击必有反馈 // Logan.Li
This commit is contained in:
1 parent
4658812876
commit
52e93316a7
2 files changed
+33
-21
No files matched your search
@@ -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() {
|
||||
<Label className="text-xs text-[var(--dg-text-muted)]">新密码</Label>
|
||||
<Input type="password" value={newPwd} onChange={e => setNewPwd(e.target.value)} className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]" />
|
||||
</div>
|
||||
<Button size="sm" onClick={handleChangePassword} disabled={pwdSaving || !oldPwd || newPwd.length < 6}>
|
||||
<Button size="sm" onClick={handleChangePassword} disabled={pwdSaving}>
|
||||
{pwdSaving ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null}
|
||||
保存密码
|
||||
</Button>
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
// useToast — 统一委托到全局 sonner <Toaster />,确保提示真实可见。// Logan.Li
|
||||
//
|
||||
// 历史问题:早期版本把 toast 存在 hook 内的局部 state 并返回 toasts 数组,
|
||||
// 但没有任何页面渲染该数组(全局只挂载了 sonner 的 <Toaster />),
|
||||
// 导致所有 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<Toast[]>([]);
|
||||
|
||||
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 };
|
||||
}
|
||||
Reference in new issue
Block a user