feat(web): 会话头像/名称可点击展开详情栏,移除"关于 MeetingIM"设置页

聊天窗口头部的头像和昵称此前只是静态展示,只有右侧"i"图标能展开
详情栏;现在点击头像或名称同样会触发 onToggleDetail,单聊群聊均
生效。

按产品决定移除设置里的"关于 MeetingIM"页面(桌面目录、移动端"我的"
二级设置列表两处入口一并去掉),该页面唯一内容是版本号和一个后端
本就不支持 platform=web 的检查更新按钮,删除后 AboutPage.tsx 和
仅被它使用的 lib/api/app.ts 一并清理为死代码。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-10 11:06:46 +08:00
co-authored by Claude Sonnet 5
parent 9271a0a9c1
commit 1826d2906f
5 changed files with 27 additions and 99 deletions
-2
View File
@@ -20,7 +20,6 @@ import { FavoritesLayout } from '@/features/favorites/FavoritesLayout';
import { ConversationView } from '@/features/messages/ConversationView'; import { ConversationView } from '@/features/messages/ConversationView';
import { EmptyMain } from '@/features/messages/EmptyMain'; import { EmptyMain } from '@/features/messages/EmptyMain';
import { MessagesLayout } from '@/features/messages/MessagesLayout'; import { MessagesLayout } from '@/features/messages/MessagesLayout';
import { AboutPage } from '@/features/settings/AboutPage';
import { GeneralSettingsPage } from '@/features/settings/GeneralSettingsPage'; import { GeneralSettingsPage } from '@/features/settings/GeneralSettingsPage';
import { NotificationsSettingsPage } from '@/features/settings/NotificationsSettingsPage'; import { NotificationsSettingsPage } from '@/features/settings/NotificationsSettingsPage';
import { PrivacySettingsPage } from '@/features/settings/PrivacySettingsPage'; import { PrivacySettingsPage } from '@/features/settings/PrivacySettingsPage';
@@ -84,7 +83,6 @@ const router = createBrowserRouter([
{ path: 'notifications', element: <NotificationsSettingsPage /> }, { path: 'notifications', element: <NotificationsSettingsPage /> },
{ path: 'privacy', element: <PrivacySettingsPage /> }, { path: 'privacy', element: <PrivacySettingsPage /> },
{ path: 'general', element: <GeneralSettingsPage /> }, { path: 'general', element: <GeneralSettingsPage /> },
{ path: 'about', element: <AboutPage /> },
], ],
}, },
], ],
+20 -18
View File
@@ -546,24 +546,26 @@ export function ConversationView() {
<Link to="/messages" className="grid h-9.5 w-9.5 flex-none place-items-center text-text-heading md:hidden"> <Link to="/messages" className="grid h-9.5 w-9.5 flex-none place-items-center text-text-heading md:hidden">
<Icon name="arrow_back_ios_new" /> <Icon name="arrow_back_ios_new" />
</Link> </Link>
<span <button type="button" onClick={onToggleDetail} className="flex items-center gap-2.5 text-left">
className={`grid h-9.5 w-9.5 flex-none place-items-center rounded-lg text-sm font-medium text-white ${avatarBgClass(topicId)}`} <span
> className={`grid h-9.5 w-9.5 flex-none place-items-center rounded-lg text-sm font-medium text-white ${avatarBgClass(topicId)}`}
{avatarLetter(peerName)} >
</span> {avatarLetter(peerName)}
<div className="flex flex-col"> </span>
<strong className="text-base font-semibold text-text-heading"> <div className="flex flex-col">
{peerName} <strong className="text-base font-semibold text-text-heading">
{isGroup ? <span className="ml-1 font-normal text-text-tertiary-2">({groupMemberCount})</span> : null} {peerName}
</strong> {isGroup ? <span className="ml-1 font-normal text-text-tertiary-2">({groupMemberCount})</span> : null}
{isGroup ? ( </strong>
groupSublineParts.length ? <span className="text-xs text-text-tertiary-2">{groupSublineParts.join(' · ')}</span> : null {isGroup ? (
) : ( groupSublineParts.length ? <span className="text-xs text-text-tertiary-2">{groupSublineParts.join(' · ')}</span> : null
<span className={`text-xs ${contact?.online ? 'text-online' : 'text-text-tertiary-2'}`}> ) : (
{contact?.online ? '在线' : '离线'} <span className={`text-xs ${contact?.online ? 'text-online' : 'text-text-tertiary-2'}`}>
</span> {contact?.online ? '在线' : '离线'}
)} </span>
</div> )}
</div>
</button>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{isGroup ? null : ( {isGroup ? null : (
-47
View File
@@ -1,47 +0,0 @@
import { useMutation } from '@tanstack/react-query';
import { getLatestRelease } from '@/lib/api/app';
const CURRENT_VERSION = __APP_VERSION__;
/**
* README §8.7's "v0.25 · 有新版本" row. Confirmed live (2026-09-08): `platform:"web"` is REJECTED
* outright -- `400 MALFORMED_REQUEST {"details":{"field":"platform"}}` -- the real backend only
* accepts the doc's own example values (`ios`/`android`), no web support at all. Passing a wrong
* platform to get SOME answer would be dishonest (it'd be checking updates for the wrong client),
* so this shows an honest "not supported" state instead of pretending the check succeeded.
*/
export function AboutPage() {
const checkMutation = useMutation({ mutationFn: () => getLatestRelease(CURRENT_VERSION) });
return (
<div className="mx-auto w-full max-w-[720px] px-5 py-8 md:px-10">
<h2 className="mb-4 text-xl font-semibold text-text-heading">关于 MeetingIM</h2>
<div className="rounded-xl bg-white text-sm">
<div className="flex items-center justify-between border-b border-border-list-item px-7 py-4">
<span className="text-text-body">当前版本</span>
<span className="text-text-tertiary-2">{CURRENT_VERSION}</span>
</div>
<button
type="button"
onClick={() => checkMutation.mutate()}
disabled={checkMutation.isPending}
className="flex w-full items-center justify-between px-7 py-4 text-left"
>
<span className="text-text-body">检查更新</span>
<span className="text-text-tertiary-2">
{checkMutation.isPending
? '检查中…'
: checkMutation.isError
? '暂不支持检查更新'
: checkMutation.data
? checkMutation.data.update_type === 'none'
? '已是最新版本'
: `发现新版本 ${checkMutation.data.latest_version}`
: '›'}
</span>
</button>
</div>
</div>
);
}
+7 -12
View File
@@ -15,7 +15,6 @@ const DIRECTORY = [
{ key: 'notifications', to: '/settings/notifications', icon: 'notifications', label: '消息通知' }, { key: 'notifications', to: '/settings/notifications', icon: 'notifications', label: '消息通知' },
{ key: 'privacy', to: '/settings/privacy', icon: 'lock', label: '隐私' }, { key: 'privacy', to: '/settings/privacy', icon: 'lock', label: '隐私' },
{ key: 'general', to: '/settings/general', icon: 'tune', label: '通用' }, { key: 'general', to: '/settings/general', icon: 'tune', label: '通用' },
{ key: 'about', to: '/settings/about', icon: 'info', label: '关于 MeetingIM' },
]; ];
/** /**
@@ -183,14 +182,14 @@ export function SettingsLayout() {
</div> </div>
{/* Mobile "设置" sub-list (mock `07/1e`) -- a second, DIFFERENT grouping from both the hub {/* Mobile "设置" sub-list (mock `07/1e`) -- a second, DIFFERENT grouping from both the hub
above and the desktop directory: 清空缓存/意见反馈/关于 sit here directly, not nested above and the desktop directory: 清空缓存/意见反馈 sit here directly, not nested under
under 通用. 清空缓存 links through to 通用 rather than duplicating that action (and its 通用. 清空缓存 links through to 通用 rather than duplicating that action (and its
already-established "no fake byte count" honesty) in a second place; 意见反馈 has no already-established "no fake byte count" honesty) in a second place; 意见反馈 has no
generic post-login feedback endpoint anywhere in this protocol (the only feedback API, generic post-login feedback endpoint anywhere in this protocol (the only feedback API,
`/tenant-entry-feedback`, is unauthenticated and pre-login-only per its own name) so this `/tenant-entry-feedback`, is unauthenticated and pre-login-only per its own name) so this
is a real, recorded backend gap, not a missed wire-up. 关于 omits the mock's red-dot is a real, recorded backend gap, not a missed wire-up. 关于 MeetingIM removed entirely per
"有新版本" -- already confirmed elsewhere that state can never be real (the update-check product decision -- its only real content was a version string and an update-check the
endpoint always rejects `platform=web`), showing it would be lying about system state. */} backend rejects for `platform=web`, not worth a dedicated page. */}
<div className={`flex-1 flex-col overflow-y-auto md:hidden ${mobileSubView ? 'flex' : 'hidden'}`}> <div className={`flex-1 flex-col overflow-y-auto md:hidden ${mobileSubView ? 'flex' : 'hidden'}`}>
<div className="grid h-12 flex-none grid-cols-[44px_1fr_44px] items-center bg-white px-1"> <div className="grid h-12 flex-none grid-cols-[44px_1fr_44px] items-center bg-white px-1">
<button type="button" onClick={() => setMobileSubView(false)} className="grid h-9 w-9 place-items-center text-text-heading"> <button type="button" onClick={() => setMobileSubView(false)} className="grid h-9 w-9 place-items-center text-text-heading">
@@ -201,7 +200,7 @@ export function SettingsLayout() {
</div> </div>
<div className="flex flex-col gap-2.5 py-2.5"> <div className="flex flex-col gap-2.5 py-2.5">
<div className="flex flex-col bg-white"> <div className="flex flex-col bg-white">
{DIRECTORY.filter((item) => item.key !== 'profile' && item.key !== 'about').map((item, i, arr) => ( {DIRECTORY.filter((item) => item.key !== 'profile').map((item, i, arr) => (
<Link <Link
key={item.key} key={item.key}
to={item.to} to={item.to}
@@ -217,14 +216,10 @@ export function SettingsLayout() {
<span>清空缓存</span> <span>清空缓存</span>
<Icon name="chevron_right" className="text-xl text-icon-arrow" /> <Icon name="chevron_right" className="text-xl text-icon-arrow" />
</Link> </Link>
<button type="button" onClick={notImplemented} className="flex items-center justify-between border-b border-border-list-item px-5 py-3.5 text-left text-base text-text-body"> <button type="button" onClick={notImplemented} className="flex items-center justify-between px-5 py-3.5 text-left text-base text-text-body">
<span>意见反馈</span> <span>意见反馈</span>
<Icon name="chevron_right" className="text-xl text-icon-arrow" /> <Icon name="chevron_right" className="text-xl text-icon-arrow" />
</button> </button>
<Link to="/settings/about" className="flex items-center justify-between px-5 py-3.5 text-base text-text-body no-underline">
<span>关于 MeetingIM</span>
<Icon name="chevron_right" className="text-xl text-icon-arrow" />
</Link>
</div> </div>
<button type="button" onClick={logout} disabled={loggingOut} className="bg-white py-3.5 text-center text-base text-danger-text disabled:opacity-50"> <button type="button" onClick={logout} disabled={loggingOut} className="bg-white py-3.5 text-center text-base text-danger-text disabled:opacity-50">
退出登录 退出登录
-20
View File
@@ -1,20 +0,0 @@
import { imGet } from './client';
export interface AppRelease {
latest_version: string;
min_supported_version: string;
update_type: 'none' | 'optional' | 'required';
release_notes?: string;
store_url?: string;
package_url?: string;
checksum?: string;
published_at?: string;
}
/** docs/IM-前后端对接文档.md:2366-2367. `platform` here is `web`, matching this rewrite's own
* target -- not confirmed against a real response (the doc only shows ios/android in its own
* example), so `update_type` may come back differently for an unrecognized platform value. */
export function getLatestRelease(currentVersion: string, platform: 'web' | 'ios' | 'android' = 'web') {
const qs = new URLSearchParams({ platform, current_version: currentVersion, channel: 'stable' });
return imGet<AppRelease>(`/app/releases/latest?${qs.toString()}`);
}