diff --git a/frontend/app/chat/ChatShell.tsx b/frontend/app/chat/ChatShell.tsx index ceaff56..89fed22 100644 --- a/frontend/app/chat/ChatShell.tsx +++ b/frontend/app/chat/ChatShell.tsx @@ -290,10 +290,8 @@ export default function ChatShell({ return (
diff --git a/frontend/app/chat/[id]/ChatRoomClient.tsx b/frontend/app/chat/[id]/ChatRoomClient.tsx index 4ce2476..76127b9 100644 --- a/frontend/app/chat/[id]/ChatRoomClient.tsx +++ b/frontend/app/chat/[id]/ChatRoomClient.tsx @@ -382,8 +382,9 @@ export default function ChatRoomClient({ const scrollToBottom = useCallback((smooth = false) => { const el = listRef.current; if (!el) return; + // column-reverse:scrollTop=0 即视觉底部(最新消息) el.scrollTo({ - top: el.scrollHeight, + top: 0, behavior: smooth ? "smooth" : "auto", }); }, []); @@ -412,7 +413,7 @@ export default function ChatRoomClient({ if (!canView) return; realtime.joinRoom(roomId); void refreshMembers(); - requestAnimationFrame(() => scrollToBottom(false)); + // 列表用 flex-col-reverse,SSR/无 JS 默认即贴底,无需首屏 scrollToBottom(避免 F5 闪一下) markReadToLatest(); return () => { realtime.leaveRoom(roomId); @@ -431,9 +432,8 @@ export default function ChatRoomClient({ requestAnimationFrame(() => { const el = listRef.current; if (!el) return; - const nearBottom = - el.scrollHeight - el.scrollTop - el.clientHeight < 120; - if (nearBottom) { + // column-reverse:靠近 scrollTop=0 即贴底 + if (el.scrollTop < 120) { scrollToBottom(true); markReadToLatest(); } @@ -499,7 +499,9 @@ export default function ChatRoomClient({ const loadMore = useCallback(async () => { if (!canView || !hasMore || loadingMore || messages.length === 0) return; setLoadingMore(true); - const prevHeight = listRef.current?.scrollHeight ?? 0; + const el0 = listRef.current; + const prevHeight = el0?.scrollHeight ?? 0; + const prevTop = el0?.scrollTop ?? 0; try { const before = messages[0].id; const res = await apiListChatMessages(roomId, before, 30); @@ -509,7 +511,8 @@ export default function ChatRoomClient({ requestAnimationFrame(() => { const el = listRef.current; if (!el) return; - el.scrollTop = el.scrollHeight - prevHeight; + // column-reverse:历史插在上方,用高度差补偿保持视口锚点 + el.scrollTop = prevTop + (el.scrollHeight - prevHeight); }); } else { setHasMore(false); @@ -524,10 +527,11 @@ export default function ChatRoomClient({ const handleScroll = useCallback(() => { const el = listRef.current; if (!el) return; - if (el.scrollTop < 60) { + // column-reverse:滚向上方历史 ≈ 接近 max scrollTop + if (el.scrollHeight - el.scrollTop - el.clientHeight < 60) { loadMore(); } - if (el.scrollHeight - el.scrollTop - el.clientHeight < 60) { + if (el.scrollTop < 60) { markReadToLatest(); } }, [loadMore, markReadToLatest]); @@ -934,7 +938,7 @@ export default function ChatRoomClient({ style={ embedded ? undefined - : { height: "calc(100vh - 12rem)", minHeight: "420px" } + : { height: "100%", minHeight: 0 } } >
{!canView ? ( @@ -1062,7 +1066,7 @@ export default function ChatRoomClient({

) : ( - <> +
{hasMore && (
)}
diff --git a/frontend/app/chat/[id]/page.tsx b/frontend/app/chat/[id]/page.tsx index 1ee3e6e..d1ebebe 100644 --- a/frontend/app/chat/[id]/page.tsx +++ b/frontend/app/chat/[id]/page.tsx @@ -66,29 +66,27 @@ export default async function ChatRoomPage({ params }: PageProps) { : room.name; return ( -
- - - -
+ + + ); } diff --git a/frontend/app/chat/layout.tsx b/frontend/app/chat/layout.tsx new file mode 100644 index 0000000..fa1a2cc --- /dev/null +++ b/frontend/app/chat/layout.tsx @@ -0,0 +1,14 @@ +/** + * 聊天路由布局:标记 data-chat-viewport,配合 globals.css + * 将页面锁在一屏内(隐藏页脚、收紧 main 内边距),仅对话区滚动。 + */ +export default function ChatLayout({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index d7c3799..7cae236 100644 --- a/frontend/app/chat/page.tsx +++ b/frontend/app/chat/page.tsx @@ -27,8 +27,6 @@ export default async function ChatListPage() { } return ( -
- -
+ ); } diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 45ca2c1..d451199 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -113,6 +113,26 @@ body { text-rendering: optimizeLegibility; } +/* 聊天页:整页锁在视口内,避免「页面滚动 + 对话区滚动」双滚动条 */ +body:has([data-chat-viewport]) { + height: 100dvh; + max-height: 100dvh; + overflow: hidden; +} +body:has([data-chat-viewport]) > footer { + display: none; +} +body:has([data-chat-viewport]) #main { + flex: 1 1 0; + min-height: 0; + display: flex; + flex-direction: column; + /* 覆盖 root layout 的 py-7/sm:py-9,只留紧凑间距 */ + padding-top: 0.75rem; + padding-bottom: 0.75rem; + overflow: hidden; +} + ::selection { background: var(--accent); color: var(--accent-on);