Skip to content

Commit 47c25ba

Browse files
committed
fix(sessions): 审查即修——refresh 竞态/批量栏/分组回滚/toast
1 parent ec41b06 commit 47c25ba

2 files changed

Lines changed: 18 additions & 4 deletions

File tree

‎app/src/components/SessionListPanel.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,8 @@ export default function SessionListPanel({
400400
)}
401401
</div>
402402

403-
{/* 批量操作栏(勾选后出现) */}
404-
{selected.size > 0 && (
403+
{/* 批量操作栏(勾选后出现;段搜索命中视图隐藏——避免对不可见列表误操作) */}
404+
{!hits && selected.size > 0 && (
405405
<div style={{ borderTop: "1px solid #e5e7eb", padding: 8, display: "flex", gap: 6, alignItems: "center", background: "#fff" }}>
406406
<span style={{ fontSize: 12, color: "#374151" }}>已选 {selected.size} 个</span>
407407
<button

‎app/src/pages/SessionsPage.tsx‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)