diff --git a/web/src/features/groups/GroupAdminModal.tsx b/web/src/features/groups/GroupAdminModal.tsx index 53aab35a..077160d6 100644 --- a/web/src/features/groups/GroupAdminModal.tsx +++ b/web/src/features/groups/GroupAdminModal.tsx @@ -5,6 +5,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Icon } from '@/app/Icon'; import { Toast } from '@/app/Toast'; import { JoinRequestsModal } from '@/features/groups/JoinRequestsModal'; +import { privateChatOption, privateChatOptions } from '@/features/groups/group-settings'; import { addGroupAdmins, getAllGroupMembers, @@ -184,11 +185,8 @@ export function GroupAdminModal({ topicId, onClose }: GroupAdminModalProps) {
成员私聊权限 setGov({ member_private_chat: v === 'all' })} disabled={!govQuery.data || govMutation.isPending || govQuery.data?.member_private_chat_editable === false} /> diff --git a/web/src/features/groups/GroupDetailColumn.tsx b/web/src/features/groups/GroupDetailColumn.tsx index 4ab6b416..2dd72349 100644 --- a/web/src/features/groups/GroupDetailColumn.tsx +++ b/web/src/features/groups/GroupDetailColumn.tsx @@ -11,6 +11,7 @@ import { GroupAdminModal } from '@/features/groups/GroupAdminModal'; import { GroupAnnouncementModal } from '@/features/groups/GroupAnnouncementModal'; import { GroupFilesModal } from '@/features/groups/GroupFilesModal'; import { GroupMembersModal } from '@/features/groups/GroupMembersModal'; +import { canEditGroupAvatar, groupAvatarError } from '@/features/groups/group-settings'; import { canRemoveMember, disbandGroup, @@ -28,6 +29,7 @@ import { type GroupSettings, } from '@/lib/api/groups'; import type { MediaType } from '@/lib/api/messages'; +import { uploadFile } from '@/lib/api/upload'; import { MAX_TITLE_LENGTH } from '@/lib/config'; import { useTinode } from '@/lib/tinode/hooks'; import { forgetTopic } from '@/lib/tinode/topics'; @@ -129,6 +131,10 @@ export function GroupDetailColumn({ topicId, peerName, onClose, onOpenHistory }: const myRole = settingsQuery.data?.my_role; const isOwner = myRole === 'owner'; const isAdmin = myRole === 'admin' || isOwner; + const canEditAvatar = canEditGroupAvatar( + settingsQuery.data?.permissions?.can_edit_profile ?? groupQuery.data?.permissions?.can_edit_profile, + myRole, + ); // GroupAdminModal's "邀请入群权限" governance toggle (所有成员/仅管理员) was added without ever // being read anywhere -- every real gate on the invite affordance stayed hardcoded to // owner/admin-only, so flipping that toggle to "所有成员" had zero effect on what a plain member @@ -186,6 +192,30 @@ export function GroupDetailColumn({ topicId, peerName, onClose, onOpenHistory }: onError: (err) => showError(err, '群名保存失败'), }); + const avatarMutation = useMutation({ + mutationFn: async ({ file, topic, version }: { file: File; topic: string; version: number }) => { + const uploaded = await uploadFile(file, undefined, 'group_avatar'); + return { ...(await updateGroup(topic, { avatar_file_id: uploaded.file_id, version })), avatar_file_id: uploaded.file_id }; + }, + onSuccess: (data, { topic }) => { + queryClient.setQueryData(['group', topic], (prev: GroupProfile | undefined) => (prev ? { ...prev, ...data } : data)); + queryClient.invalidateQueries({ queryKey: ['group', topic] }); + queryClient.invalidateQueries({ queryKey: ['group-settings', topic] }); + queryClient.invalidateQueries({ queryKey: ['groups', 'all'] }); + }, + onError: (err) => showError(err, '群头像修改失败'), + }); + const avatarUploading = avatarMutation.isPending && avatarMutation.variables?.topic === topicId; + + const handlePickAvatar = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + e.target.value = ''; + if (!file) return; + const error = groupAvatarError(file); + if (error) setErrorToast(error); + else if (groupQuery.data) avatarMutation.mutate({ file, topic: topicId, version: groupQuery.data.version }); + }; + // 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 @@ -304,12 +334,20 @@ export function GroupDetailColumn({ topicId, peerName, onClose, onOpenHistory }:
- +
+ + {canEditAvatar && groupQuery.data ? ( + + ) : null} +
{nameEditing ? (
@@ -317,13 +355,13 @@ export function GroupDetailColumn({ topicId, peerName, onClose, onOpenHistory }: autoFocus value={nameDraft} onChange={(e) => setNameDraft(e.target.value.slice(0, MAX_TITLE_LENGTH))} - onKeyDown={(e) => e.key === 'Enter' && nameDraft.trim() && nameMutation.mutate(nameDraft.trim())} + onKeyDown={(e) => e.key === 'Enter' && nameDraft.trim() && !avatarUploading && nameMutation.mutate(nameDraft.trim())} className="min-w-0 flex-1 rounded-md bg-surface-input px-2 py-1 text-sm outline-none" />