fix(web): code review round 1 remaining UI fixes

- EmojiPanel: add missing 手势/物品 categories, 12-column grid per README §3.3
- DetailColumn: add 投诉举报 row, real ID/region/签名 via new getUserProfile
  API, chevron_right icons now use --color-icon-arrow token (were tertiary
  text color), regroup rows so dividers only sit between sections not every
  row (matches design-code reference exactly)
- MessageBubble/ConversationView: right-clicked message bubble now gets the
  spec's outline while its context menu is open
- ChatListColumn/AppShell: pinned 系统消息/文件助手 rows using confirmed
  bootstrap entries/entry_badges topic names ("sys"/"slf"); route to the
  existing SectionPlaceholder pattern since their own dedicated layouts are
  Phase 8, not built yet

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-08 03:58:06 +08:00
co-authored by Claude Sonnet 5
parent a7da4dc079
commit 93d4077d1e
9 changed files with 191 additions and 42 deletions
+32 -11
View File
@@ -11,6 +11,7 @@ import {
getBlocks,
getConversationSettings,
getFriends,
getUserProfile,
unblockUser,
updateConversationSettings,
updateFriendRemark,
@@ -53,6 +54,11 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
const friendsQuery = useQuery({ queryKey: ['friends'], queryFn: () => getFriends() });
const friend = friendsQuery.data?.items.find((f) => f.user_id === topicId);
const profileQuery = useQuery({ queryKey: ['user-profile', topicId], queryFn: () => getUserProfile(topicId) });
const idLine = [profileQuery.data?.public_account && `ID:${profileQuery.data.public_account}`, profileQuery.data?.region]
.filter(Boolean)
.join(' · ');
const settingsQuery = useQuery({
queryKey: ['conversation-settings', topicId],
queryFn: () => getConversationSettings(topicId),
@@ -98,6 +104,10 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
deleteFriendMutation.mutate();
};
const reportAbuse = () => {
window.alert('该功能暂未开放,如需投诉举报请联系客服。');
};
return (
<aside className="flex h-full w-full flex-none flex-col overflow-y-auto border-l border-border-panel bg-white md:w-[300px]">
<header className="flex h-15 flex-none items-center justify-between border-b border-list-item px-5">
@@ -109,11 +119,15 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
) : null}
</header>
<div className="flex flex-col items-center gap-1 px-5 pb-4 pt-6">
<div className="flex flex-col items-center gap-1 border-b border-list-item px-5 pb-4 pt-6">
<span className={`grid h-18 w-18 place-items-center rounded-2xl text-2xl font-medium text-white ${avatarBgClass(topicId)}`}>
{avatarLetter(peerName)}
</span>
<strong className="mt-2 text-[17px] font-semibold text-text-heading">{peerName}</strong>
{idLine ? <span className="text-xs text-text-tertiary-2">{idLine}</span> : null}
{profileQuery.data?.signature ? (
<p className="m-0 text-center text-[13px] text-text-secondary">{profileQuery.data.signature}</p>
) : null}
<div className="mt-3 flex gap-3">
<button
type="button"
@@ -134,10 +148,10 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
</div>
</div>
<div className="px-5 pb-3 text-sm">
<div className="border-b border-list-item px-5 pb-3 pt-2 text-sm">
<button type="button" onClick={() => setHistoryTab('text')} className="flex w-full items-center justify-between py-2">
<span className="text-text-body">查找聊天记录</span>
<Icon name="chevron_right" className="text-[18px] text-text-tertiary-2" />
<Icon name="chevron_right" className="text-[20px] text-icon-arrow" />
</button>
<div className="mt-1 grid grid-cols-4 gap-2">
{(
@@ -156,7 +170,7 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
</div>
</div>
<div className="flex flex-col border-t border-list-item px-5 pb-2 text-sm">
<div className="flex flex-col border-b border-list-item px-5 text-sm">
{remarkEditing ? (
<div className="flex items-center gap-2 py-3">
<span className="flex-none text-text-body">备注</span>
@@ -181,13 +195,13 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
className="flex items-center justify-between py-3 text-left"
>
<span className="text-text-body">备注</span>
<span className="flex items-center gap-1 text-text-tertiary-2">
<span className="flex items-center gap-0.5 text-[13px] text-text-tertiary-2">
{friend?.remark || '未设置'}
<Icon name="chevron_right" className="text-[18px]" />
<Icon name="chevron_right" className="text-[20px] text-icon-arrow" />
</span>
</button>
)}
<div className="flex items-center justify-between border-t border-list-item py-3">
<div className="flex items-center justify-between py-3">
<span className="text-text-body">置顶聊天</span>
<Toggle
checked={!!settingsQuery.data?.pinned}
@@ -195,7 +209,7 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
onChange={() => updateSettings.mutate({ pinned: !settingsQuery.data?.pinned })}
/>
</div>
<div className="flex items-center justify-between border-t border-list-item py-3">
<div className="flex items-center justify-between py-3">
<span className="text-text-body">消息免打扰</span>
<Toggle
checked={!!settingsQuery.data?.mute_notifications}
@@ -203,13 +217,20 @@ export function DetailColumn({ topicId, peerName, onClose }: DetailColumnProps)
onChange={() => updateSettings.mutate({ mute_notifications: !settingsQuery.data?.mute_notifications })}
/>
</div>
<div className="flex items-center justify-between border-t border-list-item py-3">
<div className="flex items-center justify-between py-3">
<span className="text-text-body">加入黑名单</span>
<Toggle checked={isBlocked} disabled={blockMutation.isPending} onChange={() => blockMutation.mutate(!isBlocked)} />
</div>
<button type="button" onClick={clearHistory} className="flex items-center justify-between border-t border-list-item py-3 text-left">
</div>
<div className="flex flex-col px-5 pb-2 text-sm">
<button type="button" onClick={clearHistory} className="flex items-center justify-between py-3 text-left">
<span className="text-text-body">清空聊天记录</span>
<Icon name="chevron_right" className="text-[18px] text-text-tertiary-2" />
<Icon name="chevron_right" className="text-[20px] text-icon-arrow" />
</button>
<button type="button" onClick={reportAbuse} className="flex items-center justify-between py-3 text-left">
<span className="text-text-body">投诉举报</span>
<Icon name="chevron_right" className="text-[20px] text-icon-arrow" />
</button>
</div>