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:
co-authored by
Claude Sonnet 5
parent
9271a0a9c1
commit
1826d2906f
@@ -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 /> },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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 : (
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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">
|
||||||
退出登录
|
退出登录
|
||||||
|
|||||||
@@ -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()}`);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user