@@ -46,18 +46,31 @@ export default function SessionsPage({ focusSessionId, active, onOpenNote }: Pro
4646 const openIdRef = useRef < number | null > ( null ) ;
4747 const prevFinishedRef = useRef < Set < number > > ( new Set ( ) ) ; // 上次快照的已完成 id 集
4848 const toastTimerRef = useRef < number | null > ( null ) ;
49+ // TD-003 模式:请求序号防竞态——live:status 与 session:fused 可并发触发刷新,
50+ // 慢响应返回时不覆盖新结果(旧快照短暂回显 + justFinished 重复计数)
51+ const refreshSeqRef = useRef ( 0 ) ;
4952
5053 const showToast = useCallback ( ( msg : string , kind : Toast [ "kind" ] ) => {
5154 setToast ( { msg, kind } ) ;
5255 if ( toastTimerRef . current ) window . clearTimeout ( toastTimerRef . current ) ;
5356 toastTimerRef . current = window . setTimeout ( ( ) => setToast ( null ) , 3000 ) ;
5457 } , [ ] ) ;
5558
59+ // toast 定时器卸载清理(防卸载后 setState)
60+ useEffect (
61+ ( ) => ( ) => {
62+ if ( toastTimerRef . current ) window . clearTimeout ( toastTimerRef . current ) ;
63+ } ,
64+ [ ] ,
65+ ) ;
66+
5667 /** 拉取会话列表(含转化标记);对比快照计算"新完成"计数(事件刷新后提示)。 */
5768 const refresh = useCallback ( async ( ) => {
69+ const seq = ++ refreshSeqRef . current ;
5870 try {
5971 setLoading ( true ) ;
6072 const list = await invoke < SessionListItem [ ] > ( "list_sessions" , { limit : 200 } ) ;
73+ if ( seq !== refreshSeqRef . current ) return ; // 竞态防护:过期响应直接丢弃
6174 setItems ( list ) ;
6275 const finishedNow = new Set (
6376 list . filter ( ( i ) => i . session . status === "finished" ) . map ( ( i ) => i . session . id ) ,
@@ -68,9 +81,9 @@ export default function SessionsPage({ focusSessionId, active, onOpenNote }: Pro
6881 }
6982 prevFinishedRef . current = finishedNow ;
7083 } catch ( e ) {
71- showToast ( `会话列表加载失败: ${ e } ` , "err" ) ;
84+ if ( seq === refreshSeqRef . current ) showToast ( `会话列表加载失败: ${ e } ` , "err" ) ;
7285 } finally {
73- setLoading ( false ) ;
86+ if ( seq === refreshSeqRef . current ) setLoading ( false ) ;
7487 }
7588 } , [ showToast ] ) ;
7689
@@ -138,6 +151,7 @@ export default function SessionsPage({ focusSessionId, active, onOpenNote }: Pro
138151 try {
139152 setGroups ( await invoke < CourseGroup [ ] > ( "list_session_courses" ) ) ;
140153 } catch ( e ) {
154+ setGrouped ( false ) ; // 失败回滚——避免"分组中"按钮与未分组列表状态不一致
141155 showToast ( `课程分组加载失败: ${ e } ` , "err" ) ;
142156 }
143157 }
0 commit comments