fix: 软刷新齐套前保留旧画面,避免一点击就卸光

Logo/下拉静默预热后一次覆盖;commit 不再先 reset/loading;顺带会话预取与可配置 Feed 排序标签。
This commit is contained in:
2026-09-01 04:56:10 +08:00
parent ba60a9b1c4
commit 9e134916a4
47 changed files with 2446 additions and 636 deletions

View File

@@ -2,38 +2,115 @@ import { useCallback, useEffect, useState } from 'react';
import { notify } from '@/lib/notify';
import { api } from '../api/client';
import type { CheckInStatus } from '../api/types';
import { getSessionSnapshot, setSessionSnapshot } from '../utils/sessionPageCache';
import { PAGE_FORCE_REFRESH_EVENT } from '../utils/feedCache';
import { PAGE_SOFT_REFRESH_COMMIT_EVENT } from '../utils/softRefresh';
import { useAuth } from './useAuth';
/** 签到会话快照键(与 prefetchRoute 共用) */
export function checkInCacheKey(userId: number) {
return `checkin:${userId}`;
}
/** 每日签到状态与操作(侧栏、积分页等复用) */
export function useCheckIn(enabled = true) {
const { user, refresh } = useAuth();
const [status, setStatus] = useState<CheckInStatus | null>(null);
const [loading, setLoading] = useState(false);
const userId = user?.id;
const [status, setStatus] = useState<CheckInStatus | null>(() => {
if (!userId || !enabled) return null;
return getSessionSnapshot<CheckInStatus>(checkInCacheKey(userId)) ?? null;
});
const [loading, setLoading] = useState(() => {
if (!userId || !enabled) return false;
return getSessionSnapshot<CheckInStatus>(checkInCacheKey(userId)) === undefined;
});
const [busy, setBusy] = useState(false);
const load = useCallback(() => {
if (!user || !enabled) {
const load = useCallback((opts?: { force?: boolean }) => {
if (!userId || !enabled) {
setStatus(null);
setLoading(false);
return;
}
const key = checkInCacheKey(userId);
if (!opts?.force) {
const hit = getSessionSnapshot<CheckInStatus>(key);
if (hit !== undefined) {
setStatus(hit);
setLoading(false);
return;
}
}
setLoading(true);
api.checkInStatus()
.then(d => setStatus(d.check_in))
.catch(e => notify.error(e instanceof Error ? e.message : '加载签到状态失败'))
.then((d) => {
setSessionSnapshot(key, d.check_in);
setStatus(d.check_in);
})
.catch((e) => notify.error(e instanceof Error ? e.message : '加载签到状态失败'))
.finally(() => setLoading(false));
}, [user, enabled]);
}, [userId, enabled]);
useEffect(() => {
load();
}, [load]);
if (!userId || !enabled) {
setStatus(null);
setLoading(false);
return;
}
const key = checkInCacheKey(userId);
const hit = getSessionSnapshot<CheckInStatus>(key);
if (hit !== undefined) {
setStatus(hit);
setLoading(false);
return;
}
setStatus(null);
setLoading(true);
load({ force: true });
}, [userId, enabled, load]);
// 软刷新 commit仅快照命中才盖未命中保持旧 UI后台静默再拉
useEffect(() => {
const applyHitOrSilent = (forceLoading: boolean) => {
if (!userId || !enabled) return;
const key = checkInCacheKey(userId);
const hit = getSessionSnapshot<CheckInStatus>(key);
if (hit !== undefined) {
setStatus(hit);
setLoading(false);
return;
}
if (forceLoading) {
load({ force: true });
return;
}
// 软刷新未命中:不 setLoading静默覆盖
api.checkInStatus()
.then((d) => {
setSessionSnapshot(key, d.check_in);
setStatus(d.check_in);
setLoading(false);
})
.catch(() => { /* 保持旧 UI */ });
};
const onForce = () => applyHitOrSilent(true);
const onCommit = () => applyHitOrSilent(false);
window.addEventListener(PAGE_FORCE_REFRESH_EVENT, onForce);
window.addEventListener(PAGE_SOFT_REFRESH_COMMIT_EVENT, onCommit);
return () => {
window.removeEventListener(PAGE_FORCE_REFRESH_EVENT, onForce);
window.removeEventListener(PAGE_SOFT_REFRESH_COMMIT_EVENT, onCommit);
};
}, [userId, enabled, load]);
const doCheckIn = useCallback(async () => {
if (!user) return;
if (!userId) return;
setBusy(true);
try {
const r = await api.checkIn();
notify.success(`签到成功,+${r.check_in.today_points} 积分`);
setSessionSnapshot(checkInCacheKey(userId), r.check_in);
setStatus(r.check_in);
await refresh();
} catch (e: unknown) {
@@ -41,13 +118,13 @@ export function useCheckIn(enabled = true) {
} finally {
setBusy(false);
}
}, [user, refresh]);
}, [userId, refresh]);
return {
status,
loading,
busy,
doCheckIn,
reload: load,
reload: () => load({ force: true }),
};
}