From 70cc977e16e77b771fe3ac36b95938f52697f98a Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 15 Sep 2026 23:28:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E5=B7=A6=E4=B8=8A=E8=A7=92?= =?UTF-8?q?=E8=87=AA=E5=B7=B1=E5=A4=B4=E5=83=8F=E6=8E=A5=E5=85=A5=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E6=95=B0=E6=8D=AE=EF=BC=8C=E4=BC=9A=E8=AF=9D=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E5=A4=B1=E8=B4=A5=E4=B8=8D=E5=86=8D=E9=9D=99=E9=BB=98?= =?UTF-8?q?=E5=90=9E=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NavRail 的自己头像此前是硬编码的"我"字,AppShell 从没往下传过真实的 user_id/nickname/avatar_file_id,所以无论是谁登录、有没有上传头像都不会变; 现在改为拉取 /v1/im/me 并用 Avatar 组件渲染,和设置页/联系人等处一致。 ConversationView 打开会话失败时原来是 .catch(() => {}) 整个吞掉,用户会看到 空白的消息列表且没有任何提示;由于已读回执只在拿到消息后才会发,未读角标 也会因此永久卡住。现在失败会弹出错误提示,不再静默。 Co-Authored-By: Claude Sonnet 5 --- web/src/app/AppShell.tsx | 15 ++++++++++++++- web/src/app/NavRail.tsx | 16 ++++++++++++---- web/src/features/messages/ConversationView.tsx | 8 +++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/web/src/app/AppShell.tsx b/web/src/app/AppShell.tsx index cf9863c3..4b767d6f 100644 --- a/web/src/app/AppShell.tsx +++ b/web/src/app/AppShell.tsx @@ -1,9 +1,11 @@ import { useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; import { Outlet } from 'react-router-dom'; import { CallRoot } from '@/features/calls/CallRoot'; import { useBootstrap } from '@/lib/api/hooks'; +import { getMe } from '@/lib/api/profile'; import { useChatList } from '@/lib/tinode/hooks'; import { initCallSignaling } from '@/stores/calls'; @@ -19,6 +21,12 @@ import { NavRail } from './NavRail'; export function AppShell() { const contacts = useChatList(); const bootstrap = useBootstrap(); + // Same `['me']` query SettingsLayout already runs -- sharing the key means whichever mounts + // first fetches it and the other just reads the cache, not two redundant requests. This was + // never wired up here before, so the rail's self-tile (NavRail below) had no avatar/name data + // at all and fell back to a permanently-hardcoded "我" glyph regardless of who was logged in or + // what avatar they'd uploaded -- see Avatar.tsx's own comment for the wider version of this gap. + const meQuery = useQuery({ queryKey: ['me'], queryFn: getMe }); // Once per app session, regardless of which page is open -- an incoming call must surface even // while browsing contacts/settings/etc, not only while a conversation happens to be open. useEffect(() => { @@ -37,7 +45,12 @@ export function AppShell() { return (
- +
diff --git a/web/src/app/NavRail.tsx b/web/src/app/NavRail.tsx index 7934b815..d2d6a06f 100644 --- a/web/src/app/NavRail.tsx +++ b/web/src/app/NavRail.tsx @@ -1,5 +1,6 @@ import { Link, useLocation } from 'react-router-dom'; +import { Avatar } from './Avatar'; import { Icon } from './Icon'; interface NavRailItem { @@ -54,13 +55,20 @@ function NavRailButton({ item }: { item: NavRailItem }) { ); } +interface NavRailProps { + unreadMessages?: number; + /** The logged-in user's own id/name/avatar (`GET /v1/im/me`) -- drives the rail's top self-tile. + * All optional so the rail still renders (as a letter-tile fallback) before that request settles. */ + selfId?: string; + selfName?: string; + selfAvatarFileId?: string; +} + /** Persistent 64px icon rail, visible at >=640px (see App.tsx for the <640 bottom-tab fallback). */ -export function NavRail({ selfInitial = '我', unreadMessages = 0 }: { selfInitial?: string; unreadMessages?: number }) { +export function NavRail({ unreadMessages = 0, selfId, selfName, selfAvatarFileId }: NavRailProps) { return (