33// 用户偏好设置表单(Client Component)
44// 负责:拉取偏好数据、渲染编辑 UI、提交保存、同步 ThemeProvider
55
6- import { useEffect , useState } from "react" ;
6+ import { useEffect , useRef , useState } from "react" ;
77import { useRouter } from "next/navigation" ;
88import { useAuth } from "@/lib/use-auth" ;
99import { useTheme } from "@/app/components/ThemeProvider" ;
@@ -49,6 +49,8 @@ export function SettingsForm() {
4949 type : "success" | "error" ;
5050 msg : string ;
5151 } | null > ( null ) ;
52+ // toast 定时器 ref:新 toast / 卸载时清掉旧 timer,避免 setState on unmounted
53+ const toastTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
5254
5355 // 未登录时重定向
5456 useEffect ( ( ) => {
@@ -61,7 +63,13 @@ export function SettingsForm() {
6163 useEffect ( ( ) => {
6264 if ( status !== "authenticated" ) return ;
6365 const token = getToken ( ) ;
64- if ( ! token ) return ;
66+ // token 缺失时立刻结束 loading 并提示 + 跳转,否则页面会卡在骨架屏
67+ if ( ! token ) {
68+ setLoading ( false ) ;
69+ showToast ( "error" , "登录态丢失,请重新登录" ) ;
70+ router . replace ( "/login?redirect=/settings" ) ;
71+ return ;
72+ }
6573
6674 fetch ( "/api/user-center/preferences" , {
6775 headers : { satoken : token } ,
@@ -72,23 +80,45 @@ export function SettingsForm() {
7280 } )
7381 . then ( ( body ) => {
7482 if ( body ?. success && body ?. data ) {
75- setPrefs ( { ...DEFAULT_PREFS , ...body . data } ) ;
83+ const merged = { ...DEFAULT_PREFS , ...body . data } ;
84+ setPrefs ( merged ) ;
85+ // 加载出来的 theme 立即同步到 ThemeProvider,避免"已保存设置与当前主题不一致"
86+ setTheme ( merged . theme ) ;
7687 }
7788 } )
7889 . catch ( ( ) => {
7990 showToast ( "error" , "无法加载偏好设置,已显示默认值" ) ;
8091 } )
8192 . finally ( ( ) => setLoading ( false ) ) ;
93+ // setTheme 是 ThemeProvider 提供的稳定引用,router 同理;这里依赖 status 变化触发
94+ // eslint-disable-next-line react-hooks/exhaustive-deps
8295 } , [ status ] ) ;
8396
97+ // 组件卸载时清掉残留 toast timer
98+ useEffect ( ( ) => {
99+ return ( ) => {
100+ if ( toastTimerRef . current ) {
101+ clearTimeout ( toastTimerRef . current ) ;
102+ }
103+ } ;
104+ } , [ ] ) ;
105+
84106 function showToast ( type : "success" | "error" , msg : string ) {
107+ if ( toastTimerRef . current ) {
108+ clearTimeout ( toastTimerRef . current ) ;
109+ }
85110 setToast ( { type, msg } ) ;
86- setTimeout ( ( ) => setToast ( null ) , 3000 ) ;
111+ toastTimerRef . current = setTimeout ( ( ) => setToast ( null ) , 3000 ) ;
87112 }
88113
89114 async function handleSave ( ) {
90115 const token = getToken ( ) ;
91- if ( ! token ) return ;
116+ // token 缺失时给明确反馈并跳转登录,而不是静默返回让用户摸不着头脑
117+ if ( ! token ) {
118+ showToast ( "error" , "登录态丢失,请重新登录后再保存" ) ;
119+ router . replace ( "/login?redirect=/settings" ) ;
120+ return ;
121+ }
92122 setSaving ( true ) ;
93123 try {
94124 const res = await fetch ( "/api/user-center/preferences" , {
0 commit comments