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

@@ -14,8 +14,14 @@ import { postPath } from '../utils/permalink';
import { userPath } from '../utils/userPath';
import { InFlowSiteFooter } from '../components/SiteFooter';
import { cn } from '@/lib/utils';
import { getSessionSnapshot, setSessionSnapshot, deleteSessionSnapshot } from '../utils/sessionPageCache';
import { PAGE_FORCE_REFRESH_EVENT } from '../utils/feedCache';
import { PAGE_SOFT_REFRESH_COMMIT_EVENT } from '../utils/softRefresh';
type MsgTab = 'dm' | 'notify';
type ConvSnap = { conversations: MessageConversation[]; total: number; page: number };
type NotifySnap = { notifications: PrivateMessage[]; total: number; page: number };
type ThreadSnap = { messages: PrivateMessage[]; total: number; peerUser: User | null };
const NOTIFY_KINDS = [
{ key: 'all', label: '全部' },
@@ -136,11 +142,28 @@ export default function MessagesPage() {
}, []);
const loadConversations = useCallback(async (page = 1, append = false) => {
const key = `messages:conv:${page}`;
if (!append) {
const hit = getSessionSnapshot<ConvSnap>(key);
if (hit) {
setConversations(hit.conversations);
setConvTotal(hit.total);
setConvPage(hit.page);
setListLoading(false);
return;
}
}
setListLoading(true);
try {
const r = await api.messageConversations({ page, size: 30 });
const next = r.conversations || [];
setConversations((prev) => (append ? [...prev, ...next] : next));
setConversations((prev) => {
const merged = append ? [...prev, ...next] : next;
if (!append) {
setSessionSnapshot(key, { conversations: merged, total: r.total || 0, page: r.page || page });
}
return merged;
});
setConvTotal(r.total || 0);
setConvPage(r.page || page);
} catch (e: unknown) {
@@ -151,6 +174,17 @@ export default function MessagesPage() {
}, []);
const loadNotifications = useCallback(async (page = 1, append = false, kind = 'all') => {
const key = `messages:notify:${kind}:${page}`;
if (!append) {
const hit = getSessionSnapshot<NotifySnap>(key);
if (hit) {
setNotifications(hit.notifications);
setNotifyTotal(hit.total);
setNotifyPage(hit.page);
setNotifyLoading(false);
return;
}
}
setNotifyLoading(true);
try {
const r = await api.messageNotifications({
@@ -164,7 +198,9 @@ export default function MessagesPage() {
// 打开通知页时标已读(首屏)
if (!append && page === 1) {
await api.markNotificationsRead().catch(() => undefined);
setNotifications(next.map((m) => ({ ...m, is_read: true })));
const marked = next.map((m) => ({ ...m, is_read: true }));
setNotifications(marked);
setSessionSnapshot(key, { notifications: marked, total: r.total || 0, page: r.page || page });
setNotifyUnread(0);
window.dispatchEvent(new Event('messages-unread-refresh'));
} else {
@@ -177,20 +213,47 @@ export default function MessagesPage() {
}
}, []);
const [threadEpoch, setThreadEpoch] = useState(0);
useEffect(() => {
if (authLoading) return;
if (!user) {
nav(loginPath('/messages'));
return;
}
void refreshUnreadSplit();
if (tab === 'dm') {
if (!getSessionSnapshot('messages:conv:1')) void refreshUnreadSplit();
loadConversations(1);
} else {
if (!getSessionSnapshot(`messages:notify:${notifyKind}:1`)) void refreshUnreadSplit();
loadNotifications(1, false, notifyKind);
}
}, [user, authLoading, nav, tab, notifyKind, loadConversations, loadNotifications, refreshUnreadSplit]);
useEffect(() => {
const onForce = () => {
// 下拉预热会话列表后直接重读;线程快照需作废以便重拉
void refreshUnreadSplit();
if (tab === 'dm') {
void loadConversations(1);
if (peerSelected && selectedPeer !== null) {
deleteSessionSnapshot(`messages:thread:${selectedPeer}`);
setThreadEpoch(n => n + 1);
}
} else {
// 通知列表:预热未覆盖 kind,作废后重拉
deleteSessionSnapshot(`messages:notify:${notifyKind}:1`);
void loadNotifications(1, false, notifyKind);
}
};
window.addEventListener(PAGE_FORCE_REFRESH_EVENT, onForce);
window.addEventListener(PAGE_SOFT_REFRESH_COMMIT_EVENT, onForce);
return () => {
window.removeEventListener(PAGE_FORCE_REFRESH_EVENT, onForce);
window.removeEventListener(PAGE_SOFT_REFRESH_COMMIT_EVENT, onForce);
};
}, [tab, notifyKind, peerSelected, selectedPeer, loadConversations, loadNotifications, refreshUnreadSplit]);
const scrollToBottom = useCallback((smooth = false) => {
requestAnimationFrame(() => {
threadEndRef.current?.scrollIntoView({ behavior: smooth ? 'smooth' : 'auto', block: 'end' });
@@ -204,15 +267,30 @@ export default function MessagesPage() {
setMsgTotal(0);
return;
}
const cached = getSessionSnapshot<ThreadSnap>(`messages:thread:${selectedPeer}`);
if (cached) {
setMessages(cached.messages);
setMsgTotal(cached.total);
setPeerUser(cached.peerUser);
setThreadLoading(false);
return;
}
let cancelled = false;
setThreadLoading(true);
stickToBottomRef.current = true;
api.conversationMessages(selectedPeer, { size: 50 })
.then((r) => {
if (cancelled) return;
setMessages(r.messages || []);
const messages = r.messages || [];
const peer = r.peer_user || null;
setMessages(messages);
setMsgTotal(r.total || 0);
setPeerUser(r.peer_user || null);
setPeerUser(peer);
setSessionSnapshot(`messages:thread:${selectedPeer}`, {
messages,
total: r.total || 0,
peerUser: peer,
});
setConversations((prev) => prev.map((c) => (
c.peer_user_id === selectedPeer ? { ...c, unread_count: 0 } : c
)));
@@ -226,7 +304,7 @@ export default function MessagesPage() {
if (!cancelled) setThreadLoading(false);
});
return () => { cancelled = true; };
}, [user, peerSelected, selectedPeer, refreshUnreadSplit]);
}, [user, peerSelected, selectedPeer, refreshUnreadSplit, threadEpoch]);
useEffect(() => {
if (!threadLoading && stickToBottomRef.current) {
@@ -321,7 +399,17 @@ export default function MessagesPage() {
try {
const r = await api.sendMessage({ to_user_id: selectedPeer, content });
stickToBottomRef.current = true;
setMessages((prev) => [...prev, r.message]);
setMessages((prev) => {
const next = [...prev, r.message];
if (selectedPeer != null) {
setSessionSnapshot(`messages:thread:${selectedPeer}`, {
messages: next,
total: msgTotal + 1,
peerUser,
});
}
return next;
});
setMsgTotal((n) => n + 1);
setDraft('');
setConversations((prev) => {
@@ -335,7 +423,9 @@ export default function MessagesPage() {
unread_count: 0,
updated_at: r.message.created_at,
};
return [next, ...rest];
const merged = [next, ...rest];
setSessionSnapshot('messages:conv:1', { conversations: merged, total: convTotal, page: 1 });
return merged;
});
scrollToBottom(true);
} catch (e: unknown) {