群成员格子改回独立"移出"格子,点击打开成员列表,不再是逐头像角标+直接移出

用户再次纠正:不要在每个可移出成员的头像上单独加角标、点击直接弹确认移出;而是
像"+邀请"一样,做一个独立的"-移出"格子(同款虚线方块,仅 isAdmin 可见),点击后
打开"查看全部"成员列表,真正的移出操作留在列表里已有的那一行按钮上。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-24 17:28:17 +08:00
co-authored by Claude Sonnet 5
parent 05ee23aefa
commit 2427a3782c
+14 -47
View File
@@ -12,7 +12,6 @@ import { GroupAnnouncementModal } from '@/features/groups/GroupAnnouncementModal
import { GroupFilesModal } from '@/features/groups/GroupFilesModal';
import { GroupMembersModal } from '@/features/groups/GroupMembersModal';
import {
canRemoveMember,
disbandGroup,
getGroup,
getMyGroupProfile,
@@ -20,7 +19,6 @@ import {
getGroupSettings,
getGroupSummary,
leaveGroup,
removeGroupMember,
updateGroup,
updateGroupMemberSettings,
updateMyGroupNickname,
@@ -73,27 +71,6 @@ function RoleCorner({ role }: { role: GroupMember['role'] }) {
return null;
}
/** Corner "-" badge for a removable member's avatar in the compact grid -- the "outside the avatar,
* like the +邀请 tile" treatment (explicit user correction: a text "移出" link inside a member ROW is
* the full-list modal's job, not this grid's). Opposite corner from RoleCorner (top-right, not
* top-left) because both can legitimately apply to the same avatar at once -- an owner removing an
* admin still needs the "管" tag AND this badge on one avatar. */
function RemoveBadge({ pending, onRemove }: { pending: boolean; onRemove: () => void }) {
return (
<button
type="button"
disabled={pending}
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
aria-label="移出群聊"
className="absolute -right-1 -top-1 z-10 grid h-4 w-4 place-items-center rounded-full bg-danger-text text-white shadow-sm disabled:opacity-60 md:h-[18px] md:w-[18px]"
>
<Icon name="remove" className="text-[10px]" />
</button>
);
}
/**
* README §4.4's 群聊设置 detail rail (group-owner/admin/member view). Sibling of DetailColumn.tsx
@@ -207,19 +184,6 @@ export function GroupDetailColumn({ topicId, peerName, onClose, onOpenHistory }:
onError: (err) => showError(err, '群名保存失败'),
});
// Drives the corner "-" badge on the compact member grid below -- same removeGroupMember call and
// same invalidation keys as GroupMembersModal's own remove button (canRemoveMember is now shared
// from lib/api/groups.ts so the two surfaces can't drift on who's eligible to remove whom).
const removeMutation = useMutation({
mutationFn: (userId: string) => removeGroupMember(topicId, userId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['group-members', topicId] });
queryClient.invalidateQueries({ queryKey: ['group-members-preview', topicId] });
queryClient.invalidateQueries({ queryKey: ['group', topicId] });
},
onError: (err) => showError(err, '移出失败,请重试'),
});
// Both mutations call forgetTopic() the instant their own REST call succeeds, rather than waiting
// on the async native/`group.dissolved` gone-signal that normally follows a group actually going
// away -- real, user-reported bugs fixed by this: (1) the chat list kept showing a broken stale
@@ -409,17 +373,6 @@ export function GroupDetailColumn({ topicId, peerName, onClose, onOpenHistory }:
className="h-11 w-11 rounded-lg text-sm md:h-10 md:w-10"
/>
<RoleCorner role={m.role} />
{canRemoveMember(myUserId, myRole || 'member', m) ? (
<RemoveBadge
pending={removeMutation.isPending && removeMutation.variables === m.user_id}
onRemove={async () => {
const label = m.display_name || m.user_id;
if (await requestConfirm({ message: `确定将"${label}"移出群聊吗?`, danger: true })) {
removeMutation.mutate(m.user_id);
}
}}
/>
) : null}
<span className="w-full truncate text-center text-[11px] text-text-secondary">{m.display_name || m.user_id}</span>
</div>
))}
@@ -437,6 +390,20 @@ export function GroupDetailColumn({ topicId, peerName, onClose, onOpenHistory }:
<span className="text-[11px] text-text-secondary">邀请</span>
</button>
) : null}
{isAdmin ? (
// Sibling tile to "+邀请" right above -- same dashed-square treatment, mirrored icon.
// A single standalone entry point into the plain member list (not a per-avatar corner
// badge, and not a direct remove action) -- the actual "移出" control already lives on
// each removable row inside that list. Explicit user correction after a first attempt
// put a "-" badge on every removable avatar's own corner: that read as "many separate
// remove buttons" when the intent was one "+"-shaped entry point, mirroring "+邀请".
<button type="button" onClick={() => setOpenModal('members')} className="flex flex-col items-center gap-1">
<span className="grid h-11 w-11 place-items-center rounded-lg border border-dashed border-[#c5cad2] text-text-tertiary-3 md:h-10 md:w-10">
<Icon name="remove" className="text-[18px]" />
</span>
<span className="text-[11px] text-text-secondary">移出</span>
</button>
) : null}
</div>
<button type="button" onClick={() => setOpenModal('members')} className="mt-2 flex justify-center py-1 text-[13px] text-accent md:hidden">
查看全部成员 ({memberCount})