| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { GroupMemberLite } from '../../../../components/group' |
| | | import type { Conversation, GroupLite } from '../../../../types' |
| | | |
| | | import { computed, ref, watch } from 'vue' |
| | | |
| | | import { confirm } from '#/packages/effects/common-ui/src' |
| | | import { CommonStatusEnum } from '#/packages/constants/src' |
| | | import { IconifyIcon as Icon } from '#/packages/icons/src' |
| | | |
| | | import { Button, Drawer, Input, message, Popover, Switch } from 'ant-design-vue' |
| | | |
| | | import { |
| | | dissolveGroup, |
| | | muteAll, |
| | | updateGroup |
| | | } from '#/api/im/group' |
| | | import { quitGroup, updateGroupMember } from '#/api/im/group/member' |
| | | import { getCurrentUserId } from '#/views/im/utils/auth' |
| | | import { ImConversationType, ImGroupMemberRole } from '#/views/im/utils/constants' |
| | | import { toGroupCardTarget } from '#/views/im/utils/message' |
| | | import { isGroupQuit } from '#/views/im/utils/user' |
| | | |
| | | import { GroupAdminSetDialog } from '../../../../components/group' |
| | | import { GroupMemberAddDialog } from '../../../../components/group' |
| | | import { GroupMemberGrid } from '../../../../components/group' |
| | | import { GroupMemberRemoveDialog } from '../../../../components/group' |
| | | import { GroupOwnerTransferDialog } from '../../../../components/group' |
| | | import { GroupRequestListDialog } from '../../../../components/group' |
| | | import { RecommendCardDialog } from '../../../../components/user' |
| | | import { useConversationStore } from '../../../../store/conversationStore' |
| | | import { useGroupStore } from '../../../../store/groupStore' |
| | | |
| | | defineOptions({ name: 'ImConversationGroupSide' }) |
| | | |
| | | // 大群é»è®¤åªå±ç¤ºå N 个æåï¼4Ã4 å®«æ ¼ â 2 个ç¦çé¢çç»"æ·»å / ç§»åº"æé®ï¼ |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | conversation?: Conversation | null // å½åä¼è¯ï¼ç¨äºè¯» / åå
ææ°ãç½®é¡¶ç¶æ |
| | | group?: GroupLite & { groupRemark?: string; notice?: string; remarkNickName?: string; } // å½å群信æ¯ï¼å¯ç©ºï¼æ æ¿æ´»ç¾¤ä¼è¯æ¶ï¼ |
| | | members?: GroupMemberLite[] |
| | | modelValue?: boolean // æ½å±æ¯å¦æå¼ï¼v-modelï¼ |
| | | }>(), |
| | | { |
| | | conversation: null, |
| | | group: undefined, |
| | | modelValue: false, |
| | | members: () => [] |
| | | } |
| | | ) |
| | | |
| | | const emit = defineEmits<{ |
| | | openHistory: [] // ç¹å» "æ¥æ¾è天å
容" è¡ â ç¶ç»ä»¶æå¼ MessageHistory å¼¹çª |
| | | reload: [friendIds?: number[]] // é请 / ç§»é¤ / ä¿®æ¹ç¾¤èµæåï¼ç¶ç»ä»¶éæ°æç¾¤æ°æ®ï¼éè¯·æ°æååºæ¯éåº friendIds 让ä¸å±å¯éç²¾åå·æ° |
| | | 'update:modelValue': [value: boolean] |
| | | }>() |
| | | |
| | | const MEMBER_PREVIEW_COUNT = 14 |
| | | const conversationStore = useConversationStore() |
| | | const groupStore = useGroupStore() |
| | | |
| | | const visible = computed({ |
| | | get: () => props.modelValue, |
| | | set: (v) => emit('update:modelValue', v) |
| | | }) |
| | | |
| | | // ==================== è§è² / æåå±ç¤º ==================== |
| | | |
| | | const searchText = ref('') |
| | | const showAllMembers = ref(false) |
| | | |
| | | const inviteDialogRef = ref<InstanceType<typeof GroupMemberAddDialog>>() // é请好åå
¥ç¾¤å¼¹çª refï¼handleOpenInvite è° open({ groupId }) æå¼ |
| | | /** æå¼é请好åå
¥ç¾¤å¼¹çª */ |
| | | function handleOpenInvite() { |
| | | if (!props.group?.id) { |
| | | return |
| | | } |
| | | inviteDialogRef.value?.open({ groupId: props.group.id }) |
| | | } |
| | | |
| | | const myId = computed(() => getCurrentUserId()) |
| | | /** åå²é群群ï¼ç¦ææç¾¤æä½å
¥å£ï¼é请 / ç§»åº / æ¹èµæ / ç¦è¨ / å®¡æ¹ / éåºçï¼ï¼åªä¿çå±ç¤ºï¼props.group æ¯ GroupLite æ joinStatusï¼å store åå
¨é */ |
| | | const isQuitGroup = computed(() => { |
| | | const id = props.group?.id |
| | | return id != null && isGroupQuit(groupStore.getGroup(id)) |
| | | }) |
| | | const isOwner = computed( |
| | | () => !isQuitGroup.value && props.group != null && props.group.ownerId === myId.value |
| | | ) |
| | | /** å½åç¨æ·å¨ç¾¤éçè§è²ï¼æ¥èª props.members ç me è¡ï¼ï¼ç¨äºå¤å®æ¯å¦å¯ç§»åºä»äºº */ |
| | | const myRole = computed(() => props.members.find((m) => m.userId === myId.value)?.role) |
| | | /** 群主æç®¡çåï¼å¨æ½å±éæ "ç§»åºç¾¤æå" å
¥å£ï¼åå²é群群ä¸å¾è§ä¸ºæ æé */ |
| | | const isOwnerOrAdmin = computed( |
| | | () => |
| | | !isQuitGroup.value && |
| | | (myRole.value === ImGroupMemberRole.OWNER || myRole.value === ImGroupMemberRole.ADMIN) |
| | | ) |
| | | |
| | | /** æé¤å·²é群æå + å
³é®åè¿æ»¤ï¼æè§è²æåºï¼ç¾¤ä¸»â管çåâæ®éæåï¼åè§è²æ userId 稳å®ï¼ */ |
| | | const visibleMembers = computed(() => { |
| | | return props.members |
| | | .filter( |
| | | (member) => |
| | | member.status !== CommonStatusEnum.DISABLE && |
| | | (member.showName || '').includes(searchText.value) |
| | | ) |
| | | .toSorted((a, b) => { |
| | | const roleA = a.role ?? ImGroupMemberRole.NORMAL |
| | | const roleB = b.role ?? ImGroupMemberRole.NORMAL |
| | | return roleA === roleB ? a.userId - b.userId : roleA - roleB |
| | | }) |
| | | }) |
| | | |
| | | /** æå è§åï¼æç´¢ / å·²å±å¼ æ¶ä¸æå ï¼å
¶ä½åªåå N 个 */ |
| | | const moreMembersHidden = computed( |
| | | () => |
| | | !searchText.value && !showAllMembers.value && visibleMembers.value.length > MEMBER_PREVIEW_COUNT |
| | | ) |
| | | const displayMembers = computed(() => |
| | | moreMembersHidden.value |
| | | ? visibleMembers.value.slice(0, MEMBER_PREVIEW_COUNT) |
| | | : visibleMembers.value |
| | | ) |
| | | |
| | | // æ½å±å
³éæ¶æ¸
ææååºä¸´æ¶æï¼æç´¢å
³é®åãæ¥çæ´å¤å±å¼ï¼ |
| | | watch(visible, (v) => { |
| | | if (!v) { |
| | | searchText.value = '' |
| | | showAllMembers.value = false |
| | | } |
| | | }) |
| | | |
| | | // ==================== 群信æ¯ç¼è¾ ==================== |
| | | |
| | | const namePopoverVisible = ref(false) |
| | | const noticePopoverVisible = ref(false) |
| | | const remarkPopoverVisible = ref(false) |
| | | const groupRemarkPopoverVisible = ref(false) |
| | | const editName = ref('') |
| | | const editNotice = ref('') |
| | | const editRemark = ref('') |
| | | const editGroupRemark = ref('') |
| | | |
| | | // popover å¼¹åºæ¶æå½åå¼çè¿ç¼è¾æï¼é¿å
䏿¬¡æªä¿åçèå¼ |
| | | watch(namePopoverVisible, (v) => { |
| | | if (v) editName.value = props.group?.name || '' |
| | | }) |
| | | watch(noticePopoverVisible, (v) => { |
| | | if (v) editNotice.value = props.group?.notice || '' |
| | | }) |
| | | watch(remarkPopoverVisible, (v) => { |
| | | if (v) editRemark.value = props.group?.remarkNickName || '' |
| | | }) |
| | | watch(groupRemarkPopoverVisible, (v) => { |
| | | if (v) editGroupRemark.value = props.group?.groupRemark || '' |
| | | }) |
| | | |
| | | // æ½å±å
³éæ¶æ¸
æææ popoverï¼é¿å
䏿¬¡æå¼ä»å¼¹ç |
| | | watch(visible, (v) => { |
| | | if (!v) { |
| | | namePopoverVisible.value = false |
| | | noticePopoverVisible.value = false |
| | | remarkPopoverVisible.value = false |
| | | groupRemarkPopoverVisible.value = false |
| | | } |
| | | }) |
| | | |
| | | /** 群主ï¼ä¿å群åï¼èµ° /im/group/updateï¼ï¼trim å空å符串ææäº¤ï¼ä¸ saveGroupRemark è¡ä¸ºå¯¹é½ */ |
| | | async function saveName() { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | const trimmed = editName.value.trim() |
| | | if (!trimmed) { |
| | | message.warning('群åç§°ä¸è½ä¸ºç©º') |
| | | return |
| | | } |
| | | await updateGroup({ id: props.group.id, name: trimmed }) |
| | | namePopoverVisible.value = false |
| | | message.success('ä¿åæå') |
| | | emit('reload') |
| | | } |
| | | |
| | | /** 群主ï¼ä¿å群å
Œ */ |
| | | async function saveNotice() { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | await updateGroup({ id: props.group.id, notice: editNotice.value }) |
| | | noticePopoverVisible.value = false |
| | | message.success('ä¿åæå') |
| | | emit('reload') |
| | | } |
| | | |
| | | /** 群主ï¼åæ¢ãè¿ç¾¤å®¡æ¹ãå¼å
³ï¼å¼å¯åææãç³è¯·ããé请ãè·¯å¾é½é群主 / 管çååæ */ |
| | | async function handleJoinApprovalChange(value: boolean | number | string) { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | await updateGroup({ id: props.group.id, joinApproval: !!value }) |
| | | message.success('ä¿åæå') |
| | | emit('reload') |
| | | } |
| | | |
| | | /** 任使åï¼ä¿åç¾¤å¤æ³¨ï¼ä»
èªå·±å¯è§ï¼ä¼æ¿æ¢ä¼è¯å表 / é¡¶é¨ç¾¤åå±ç¤ºï¼ */ |
| | | async function saveGroupRemark() { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | await updateGroupMember({ |
| | | groupId: props.group.id, |
| | | groupRemark: editGroupRemark.value.trim() |
| | | }) |
| | | groupRemarkPopoverVisible.value = false |
| | | message.success('ä¿åæå') |
| | | emit('reload') |
| | | } |
| | | |
| | | /** 任使åï¼ä¿åèªå·±å¨ç¾¤éçæµç§°ï¼èµ° /im/group-member/updateï¼ */ |
| | | async function saveRemark() { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | await updateGroupMember({ |
| | | groupId: props.group.id, |
| | | displayUserName: editRemark.value |
| | | }) |
| | | remarkPopoverVisible.value = false |
| | | message.success('ä¿åæå') |
| | | emit('reload') |
| | | } |
| | | |
| | | // ==================== å¼å
³åæ¢ ==================== |
| | | |
| | | /** |
| | | * æ¶æ¯å
ææ°ï¼æ¬å° conversationStore ç«å³åï¼å端 /silent 弿¥åæ¥ï¼å¤±è´¥åæ»æ¬å° |
| | | * |
| | | * ä¸ ConversationItem å³é®èåç"æ¶æ¯å
ææ°"è¯ä¹ä¸è´ï¼åºå«ä»
å¨ UI å
¥å£ |
| | | */ |
| | | function onMutedChange(value: boolean | number | string) { |
| | | if (!props.conversation) { |
| | | return |
| | | } |
| | | const next = !!value |
| | | const { type, targetId } = props.conversation |
| | | conversationStore.setConversationSilent(type, targetId, next) |
| | | groupStore.setGroupSilent(targetId, next).catch((error) => { |
| | | console.error('[IM ConversationGroupSide] setGroupSilent 失败', { targetId }, error) |
| | | conversationStore.setConversationSilent(type, targetId, !next) |
| | | }) |
| | | } |
| | | |
| | | /** 置顶è天ï¼çº¯æ¬å° conversationStore æåºæï¼æ åç«¯åæ®µï¼ */ |
| | | function onTopChange(value: boolean | number | string) { |
| | | if (!props.conversation) { |
| | | return |
| | | } |
| | | conversationStore.setConversationTop(props.conversation.type, props.conversation.targetId, !!value) |
| | | } |
| | | |
| | | // ==================== å
¨ç¾¤ç¦è¨ ==================== |
| | | |
| | | /** å½å群æ¯å¦å
¨ç¾¤ç¦è¨ */ |
| | | const currentMutedAll = computed(() => { |
| | | if (!props.group) { |
| | | return false |
| | | } |
| | | return groupStore.getGroup(props.group.id)?.mutedAll ?? false |
| | | }) |
| | | |
| | | /** å
¨ç¾¤ç¦è¨å¼å
³åæ¢ */ |
| | | async function onMuteAllChange(value: boolean | number | string) { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | const newValue = !!value |
| | | await muteAll({ id: props.group.id, mutedAll: newValue }) |
| | | message.success(newValue ? 'å·²å¼å¯å
¨ç¾¤ç¦è¨' : 'å·²å
³éå
¨ç¾¤ç¦è¨') |
| | | emit('reload') |
| | | } |
| | | |
| | | // ==================== è¿ç¾¤å®¡æ¹ ==================== |
| | | |
| | | const requestListDialogRef = ref<InstanceType<typeof GroupRequestListDialog>>() // è¿ç¾¤ç³è¯·åè¡¨å¼¹çª refï¼handleOpenRequestList è° open({ groupId }) 触å |
| | | |
| | | /** æå¼å½å群çè¿ç¾¤ç³è¯·å表 */ |
| | | function handleOpenRequestList() { |
| | | if (!props.group?.id) { |
| | | return |
| | | } |
| | | requestListDialogRef.value?.open({ groupId: props.group.id }) |
| | | } |
| | | |
| | | // ==================== å享群åç ==================== |
| | | |
| | | const recommendCardDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // å享群åçå¼¹çª refï¼handleShareGroupCard è°ç¨ open({ target }) æå¼ |
| | | |
| | | /** å享群åçï¼æå½å群ä½ä¸ºåçæ¶æ¯æ¨èç»å
¶ä»ä¼è¯ */ |
| | | function handleShareGroupCard() { |
| | | const target = toGroupCardTarget(props.group) |
| | | if (!target) { |
| | | return |
| | | } |
| | | recommendCardDialogRef.value?.open({ target }) |
| | | } |
| | | |
| | | // ==================== éåºç¾¤è ==================== |
| | | |
| | | /** éåºç¾¤èï¼æ®éæåå
¥å£ï¼ç¾¤ä¸»éåºèµ°"è§£æ£ç¾¤"æ¯å¦ä¸æ¡è·¯å¾ï¼è¿éä¸å¤çï¼ */ |
| | | async function handleQuit() { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | // äºæ¬¡ç¡®è®¤ |
| | | try { |
| | | await confirm('éåºç¾¤èåå°ä¸åæ¥æ¶ç¾¤éçæ¶æ¯ï¼ç¡®è®¤éåºåï¼', '确认éåº') |
| | | } catch { |
| | | return |
| | | } |
| | | const groupId = props.group.id |
| | | await quitGroup(groupId) |
| | | // æ¬å°ç«å³ååºï¼å
æ self.member ç½® DISABLEï¼è®© GroupInfo ç isMember æ¶æï¼ï¼åæ¸
ä¼è¯ + 群 store |
| | | if (myId.value) { |
| | | groupStore.updateGroupMemberStatus(groupId, myId.value, CommonStatusEnum.DISABLE) |
| | | } |
| | | conversationStore.removeConversation(ImConversationType.GROUP, groupId) |
| | | groupStore.removeGroup(groupId) |
| | | message.success('å·²éåºç¾¤è') |
| | | visible.value = false |
| | | } |
| | | |
| | | /** è§£æ£ç¾¤èï¼ä»
群主å
¥å£ï¼ */ |
| | | async function handleDissolve() { |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | try { |
| | | await confirm('è§£æ£åæææåå°è¢«ç§»åºï¼ä¸æ æ³æ¢å¤ï¼ç¡®è®¤è§£æ£åï¼', '确认解æ£') |
| | | } catch { |
| | | return |
| | | } |
| | | const groupId = props.group.id |
| | | await dissolveGroup(groupId) |
| | | conversationStore.removeConversation(ImConversationType.GROUP, groupId) |
| | | groupStore.removeGroup(groupId) |
| | | message.success('群è已解æ£') |
| | | visible.value = false |
| | | } |
| | | |
| | | // ==================== 群主æä½ ==================== |
| | | // ç§»é¤ç¾¤æåï¼ç¾¤ä¸» / 管çåå¯è§ï¼+ 设置群管çåï¼ä»
群主ï¼+ 群主管çæè½¬è®©ï¼ä»
ç¾¤ä¸»ï¼ |
| | | |
| | | const removeDialogRef = ref<InstanceType<typeof GroupMemberRemoveDialog>>() // ç§»é¤ç¾¤æåå¼¹çª ref |
| | | const adminSetDialogRef = ref<InstanceType<typeof GroupAdminSetDialog>>() // 设置群管çåå¼¹çª ref |
| | | const ownerTransferDialogRef = ref<InstanceType<typeof GroupOwnerTransferDialog>>() // è½¬è®©ç¾¤ä¸»å¼¹çª ref |
| | | |
| | | // ---------- ç§»é¤ç¾¤æå ---------- |
| | | |
| | | /** æå¼ç§»é¤ç¾¤æåå¼¹çªï¼å§ç»éè群主ï¼ç®¡çåè§è§é¢å¤éèå
¶å®ç®¡çåï¼ç®¡çåä¸è½ç§»åºç®¡çåï¼ */ |
| | | function handleOpenRemove() { |
| | | if (!props.group?.id) { |
| | | return |
| | | } |
| | | const hideIds: number[] = [] |
| | | if (props.group.ownerId) { |
| | | hideIds.push(props.group.ownerId) |
| | | } |
| | | if (myRole.value === ImGroupMemberRole.ADMIN) { |
| | | props.members |
| | | .filter((m) => m.role === ImGroupMemberRole.ADMIN) |
| | | .forEach((m) => hideIds.push(m.userId)) |
| | | } |
| | | removeDialogRef.value?.open({ |
| | | groupId: props.group.id, |
| | | members: props.members, |
| | | hideIds |
| | | }) |
| | | } |
| | | |
| | | // ---------- 设置群管çå ---------- |
| | | |
| | | /** æå¼è®¾ç½®ç¾¤ç®¡çåå¼¹çªï¼å½å管çåé»è®¤å¾éï¼ç¾¤ä¸»ä»åéééè */ |
| | | function handleOpenAdminSet() { |
| | | if (!props.group?.id) { |
| | | return |
| | | } |
| | | // è¿æ»¤å·²é群æåï¼é¿å
maxSize åé¢è¢«éèæåå ç¨å¯¼è´æ æ³æ°å¢ç®¡çå |
| | | const currentAdminIds = props.members |
| | | .filter( |
| | | (member) => |
| | | member.role === ImGroupMemberRole.ADMIN && member.status !== CommonStatusEnum.DISABLE |
| | | ) |
| | | .map((member) => member.userId) |
| | | const hideIds = props.group.ownerId ? [props.group.ownerId] : [] |
| | | adminSetDialogRef.value?.open({ |
| | | groupId: props.group.id, |
| | | members: props.members, |
| | | currentAdminIds, |
| | | hideIds |
| | | }) |
| | | } |
| | | |
| | | // ---------- 群主管çæè½¬è®© ---------- |
| | | |
| | | /** æå¼è½¬è®©ç¾¤ä¸»å¼¹çªï¼å½åç¨æ·ä»åéééèï¼ä¸è½è½¬ç»èªå·±ï¼ */ |
| | | function handleOpenTransferOwner() { |
| | | if (!props.group?.id) { |
| | | return |
| | | } |
| | | ownerTransferDialogRef.value?.open({ |
| | | groupId: props.group.id, |
| | | members: props.members, |
| | | hideIds: [myId.value] |
| | | }) |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <!-- è天颿¿å³ä¾§ç¾¤ä¿¡æ¯æ½å±ï¼æåå®«æ ¼ + ç¾¤ä¿¡æ¯ + å¼å
³ + éåºç¾¤èï¼æ´ä½å¯¹é½å¾®ä¿¡ PC --> |
| | | <Drawer |
| | | v-model:open="visible" |
| | | :closable="false" |
| | | placement="right" |
| | | width="380px" |
| | | root-class-name="im-conversation-group-side__modal" |
| | | > |
| | | <div v-if="group" class="flex flex-col h-full bg-[var(--ant-color-bg-container)]"> |
| | | <!-- ä¸é¨ï¼å¯æ»å¨å
å®¹åº --> |
| | | <div class="flex-1 overflow-y-auto bg-[var(--ant-color-fill-secondary)]"> |
| | | <!-- ==================== 群æååº ==================== --> |
| | | <div class="px-4 pt-4 pb-[10px] bg-[var(--ant-color-bg-container)]"> |
| | | <Input v-model:value="searchText" placeholder="æç´¢ç¾¤æå" allow-clear> |
| | | <template #prefix> |
| | | <Icon |
| | | icon="ant-design:search-outlined" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </template> |
| | | </Input> |
| | | |
| | | <div class="flex flex-wrap gap-x-1 gap-y-[14px] mt-[14px]"> |
| | | <GroupMemberGrid |
| | | v-for="member in displayMembers" |
| | | :key="member.userId" |
| | | :member="member" |
| | | :size="50" |
| | | clickable |
| | | :group-name="group.name" |
| | | /> |
| | | |
| | | <!-- æ·»å ï¼ä»»ä½æåé½è½é请ï¼åå²é群群éèï¼ --> |
| | | <div |
| | | v-if="!isQuitGroup" |
| | | class="im-conversation-group-side__tile-wrap flex flex-col items-center w-[66px] cursor-pointer" |
| | | title="é请好åå
¥ç¾¤" |
| | | @click="handleOpenInvite" |
| | | > |
| | | <div class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--ant-color-text)] bg-[var(--ant-color-fill-tertiary)] border border-dashed border-[var(--ant-color-border)] rounded-md transition-colors duration-200"> |
| | | <Icon icon="ant-design:plus-outlined" /> |
| | | </div> |
| | | <div class="mt-1.5 text-12px leading-[1.5] text-[var(--ant-color-text)] text-center">æ·»å </div> |
| | | </div> |
| | | |
| | | <!-- ç§»åºï¼ç¾¤ä¸»æç®¡çåï¼ç®¡çååªè½ç§»åºæ®éæåï¼ç±åç«¯æ ¡éªï¼ --> |
| | | <div |
| | | v-if="isOwnerOrAdmin" |
| | | class="im-conversation-group-side__tile-wrap flex flex-col items-center w-[66px] cursor-pointer" |
| | | title="ç§»åºç¾¤æå" |
| | | @click="handleOpenRemove" |
| | | > |
| | | <div class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--ant-color-text)] bg-[var(--ant-color-fill-tertiary)] border border-dashed border-[var(--ant-color-border)] rounded-md transition-colors duration-200"> |
| | | <Icon icon="ant-design:minus-outlined" /> |
| | | </div> |
| | | <div class="mt-1.5 text-12px leading-[1.5] text-[var(--ant-color-text)] text-center">ç§»åº</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 大群æå ï¼é»è®¤åªå±ç¤ºå N 个ï¼ç¹ "æ¥çæ´å¤" å
¨å±å¼ï¼æç´¢æ¶ä¸æå ï¼ --> |
| | | <div |
| | | v-if="moreMembersHidden" |
| | | class="flex items-center justify-center gap-1 mt-[10px] pt-1.5 pb-0.5 text-12px text-[var(--ant-color-text-secondary)] cursor-pointer transition-colors duration-150 hover:text-[var(--ant-color-primary)]" |
| | | @click="showAllMembers = true" |
| | | > |
| | | æ¥çæ´å¤ |
| | | <Icon icon="ant-design:down-outlined" :size="10" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | |
| | | <!-- ==================== ç¾¤ä¿¡æ¯ ==================== --> |
| | | <!-- label å¨ä¸ãvalue å¨ä¸ï¼çºµåå å ï¼å¯¹é½å¾®ä¿¡ PC 设计ï¼ï¼åªæ "群å
¬å" å 为å
容é¿å > chevron --> |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <!-- 群èåç§°ï¼ç¾¤ä¸»å¯æ¹ï¼ --> |
| | | <Popover |
| | | v-if="isOwner" |
| | | v-model:open="namePopoverVisible" |
| | | trigger="click" |
| | | placement="leftTop" |
| | | :overlay-style="{ width: '280px' }" |
| | | > |
| | | <div |
| | | class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">群èåç§°</span> |
| | | <span class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] truncate">{{ group.name }}</span> |
| | | </div> |
| | | <template #content> |
| | | <div class="flex flex-col gap-2"> |
| | | <Input v-model:value="editName" :maxlength="20" placeholder="请è¾å
¥ç¾¤èåç§°" /> |
| | | <div class="flex justify-end gap-2"> |
| | | <Button size="small" @click="namePopoverVisible = false">åæ¶</Button> |
| | | <Button size="small" type="primary" @click="saveName">ä¿å</Button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </Popover> |
| | | <div |
| | | v-else |
| | | class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 transition-colors duration-150" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">群èåç§°</span> |
| | | <span class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] truncate">{{ group.name }}</span> |
| | | </div> |
| | | |
| | | <!-- 群å
¬åï¼ç¾¤ä¸»å¯æ¹ï¼ï¼å
容å¯è½å¾é¿ï¼å > chevron 表示å¯å±å¼ç¼è¾ --> |
| | | <Popover |
| | | v-if="isOwner" |
| | | v-model:open="noticePopoverVisible" |
| | | trigger="click" |
| | | placement="leftTop" |
| | | :overlay-style="{ width: '320px' }" |
| | | > |
| | | <div |
| | | class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | > |
| | | <div class="flex items-center justify-between gap-2"> |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">群å
Œ</span> |
| | | <Icon |
| | | icon="ant-design:right-outlined" |
| | | :size="11" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </div> |
| | | <span |
| | | v-if="group.notice" |
| | | class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] line-clamp-2" |
| | | > |
| | | {{ group.notice }} |
| | | </span> |
| | | <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]">æªè®¾ç½®</span> |
| | | </div> |
| | | <template #content> |
| | | <div class="flex flex-col gap-2"> |
| | | <Input.TextArea |
| | | v-model:value="editNotice" |
| | | :rows="4" |
| | | :maxlength="1024" |
| | | show-count |
| | | placeholder="请è¾å
¥ç¾¤å
Œ" |
| | | /> |
| | | <div class="flex justify-end gap-2"> |
| | | <Button size="small" @click="noticePopoverVisible = false">åæ¶</Button> |
| | | <Button size="small" type="primary" @click="saveNotice">ä¿å</Button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </Popover> |
| | | <div |
| | | v-else |
| | | class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 transition-colors duration-150" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">群å
Œ</span> |
| | | <span |
| | | v-if="group.notice" |
| | | class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] line-clamp-2" |
| | | > |
| | | {{ group.notice }} |
| | | </span> |
| | | <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]">æªè®¾ç½®</span> |
| | | </div> |
| | | |
| | | <!-- 夿³¨ï¼ä»
èªå·±å¯è§ï¼ä¿åå伿¿æ¢ä¼è¯å表 / é¡¶é¨ç¾¤åå±ç¤ºï¼ï¼åå²é群群éèï¼æ¹å¤æ³¨èµ° updateGroupMemberï¼å·²é群ä¼è¢«å端æ --> |
| | | <Popover |
| | | v-if="!isQuitGroup" |
| | | v-model:open="groupRemarkPopoverVisible" |
| | | trigger="click" |
| | | placement="leftTop" |
| | | :overlay-style="{ width: '280px' }" |
| | | > |
| | | <div |
| | | class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">夿³¨</span> |
| | | <span |
| | | v-if="group.groupRemark" |
| | | class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] line-clamp-2" |
| | | > |
| | | {{ group.groupRemark }} |
| | | </span> |
| | | <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]"> |
| | | 群èç夿³¨ä»
èªå·±å¯è§ |
| | | </span> |
| | | </div> |
| | | <template #content> |
| | | <div class="flex flex-col gap-2"> |
| | | <Input.TextArea |
| | | v-model:value="editGroupRemark" |
| | | :rows="3" |
| | | :maxlength="64" |
| | | show-count |
| | | placeholder="ä»
èªå·±å¯è§" |
| | | /> |
| | | <div class="flex justify-end gap-2"> |
| | | <Button size="small" @click="groupRemarkPopoverVisible = false">åæ¶</Button> |
| | | <Button size="small" type="primary" @click="saveGroupRemark">ä¿å</Button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </Popover> |
| | | |
| | | <!-- æå¨æ¬ç¾¤çæµç§°ï¼ä»»ä½æåé½è½æ¹èªå·±çï¼ï¼åå²é群群éèï¼èµ° updateGroupMemberï¼å·²é群ä¼è¢«å端æ --> |
| | | <Popover |
| | | v-if="!isQuitGroup" |
| | | v-model:open="remarkPopoverVisible" |
| | | trigger="click" |
| | | placement="leftTop" |
| | | :overlay-style="{ width: '280px' }" |
| | | > |
| | | <div |
| | | class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">æå¨æ¬ç¾¤çæµç§°</span> |
| | | <span |
| | | v-if="group.remarkNickName" |
| | | class="text-13px text-[var(--ant-color-text)] break-all leading-[1.6] truncate" |
| | | > |
| | | {{ group.remarkNickName }} |
| | | </span> |
| | | <span v-else class="text-13px text-[var(--ant-color-text-placeholder)] leading-[1.6]">ç¹å»è®¾ç½®</span> |
| | | </div> |
| | | <template #content> |
| | | <div class="flex flex-col gap-2"> |
| | | <Input v-model:value="editRemark" :maxlength="20" placeholder="请è¾å
¥æ¬ç¾¤æµç§°" /> |
| | | <div class="flex justify-end gap-2"> |
| | | <Button size="small" @click="remarkPopoverVisible = false">åæ¶</Button> |
| | | <Button size="small" type="primary" @click="saveRemark">ä¿å</Button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </Popover> |
| | | </div> |
| | | |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | |
| | | <!-- ==================== æ¥æ¾è天å
容 ==================== --> |
| | | <!-- ç¹å» â ç¶ç»ä»¶æå¼ MessageHistory å¼¹çª --> |
| | | |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <div |
| | | class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | @click="emit('openHistory')" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">æ¥æ¾è天å
容</span> |
| | | <Icon |
| | | icon="ant-design:right-outlined" |
| | | :size="11" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </div> |
| | | <!-- å享群åçï¼å¼¹ RecommendCardDialogï¼æå½å群ä½ä¸ºåçæ¶æ¯æ¨èç»å
¶ä»ä¼è¯ --> |
| | | <div |
| | | v-if="group" |
| | | class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | @click="handleShareGroupCard" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">å享群åç</span> |
| | | <Icon |
| | | icon="ant-design:right-outlined" |
| | | :size="11" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | |
| | | <!-- ==================== å¼å
³é¡¹ ==================== --> |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <div class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">æ¶æ¯å
ææ°</span> |
| | | <Switch :checked="!!conversation?.silent" @change="onMutedChange" /> |
| | | </div> |
| | | <div class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">置顶è天</span> |
| | | <Switch :checked="!!conversation?.top" @change="onTopChange" /> |
| | | </div> |
| | | <!-- å
¨ç¾¤ç¦è¨ï¼ä»
群主æç®¡çåå¯æä½ --> |
| | | <div v-if="isOwnerOrAdmin" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">å
¨ç¾¤ç¦è¨</span> |
| | | <Switch :checked="!!currentMutedAll" @change="onMuteAllChange" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ==================== è¿ç¾¤å®¡æ¹ ==================== --> |
| | | <!-- åç¬ä¸æ®µï¼ç¾¤ä¸»å¼å
³ + ç´§è·ã- è¿ç¾¤ç³è¯·ãå项ï¼ä¸å¾®ä¿¡ç¾¤ç®¡çå¸å±å¯¹é½ --> |
| | | <template v-if="isOwner || (isOwnerOrAdmin && !!group.joinApproval)"> |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <!-- è¿ç¾¤å®¡æ¹ï¼ä»
ç¾¤ä¸»å¯æä½ï¼å¼å¯åæ®éæåçãç³è¯·ããé请ãè·¯å¾é½é群主 / 管çååæï¼ç¾¤ä¸» / 管çåé请ç´è¿ --> |
| | | <div v-if="isOwner" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">è¿ç¾¤éè¦ç¾¤ä¸» / 群管ç确认</span> |
| | | <Switch :checked="!!group.joinApproval" @change="handleJoinApprovalChange" /> |
| | | </div> |
| | | <!-- è¿ç¾¤ç³è¯·å项ï¼ä»
å½å¼å¯å®¡æ¹ + å½åç¨æ·æ¯ owner / admin æ¶åºç°ï¼ç¹å»è¿å表 dialog --> |
| | | <div |
| | | v-if="isOwnerOrAdmin && !!group.joinApproval" |
| | | class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | @click="handleOpenRequestList" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">- è¿ç¾¤ç³è¯·</span> |
| | | <Icon |
| | | icon="ant-design:right-outlined" |
| | | :size="11" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- ==================== 群主æä½ ==================== --> |
| | | <!-- ä»
群主å¯è§ï¼å«ç®¡çå设置 + 群主管çæè½¬è®© --> |
| | | <template v-if="isOwner"> |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <div |
| | | class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | @click="handleOpenAdminSet" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">群管çå</span> |
| | | <Icon |
| | | icon="ant-design:right-outlined" |
| | | :size="11" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </div> |
| | | <div |
| | | class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | @click="handleOpenTransferOwner" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">群主管çæè½¬è®©</span> |
| | | <Icon |
| | | icon="ant-design:right-outlined" |
| | | :size="11" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | |
| | | <!-- ==================== åºé¨ï¼éåº / è§£æ£ç¾¤èï¼åå²é群群éèï¼å·²é群æ éåéï¼ ==================== --> |
| | | <div |
| | | v-if="!isQuitGroup" |
| | | class="flex-shrink-0 px-4 pt-[14px] pb-[18px] bg-[var(--ant-color-bg-container)] border-t border-t-solid border-[var(--im-border-color-lighter)]" |
| | | > |
| | | <!-- 群主ï¼è§£æ£ç¾¤è --> |
| | | <Button |
| | | v-if="isOwner" |
| | | class="w-full !h-9 text-14px" |
| | | danger |
| | | @click="handleDissolve" |
| | | > |
| | | è§£æ£ç¾¤è |
| | | </Button> |
| | | <!-- é群主ï¼éåºç¾¤è --> |
| | | <Button |
| | | v-else |
| | | class="w-full !h-9 text-14px" |
| | | danger |
| | | @click="handleQuit" |
| | | > |
| | | éåºç¾¤è |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ==================== åå¯¹è¯æ¡ ==================== --> |
| | | <!-- éè¯·æ°æå / éæåç§»é¤ --> |
| | | <GroupMemberAddDialog ref="inviteDialogRef" @reload="(ids) => $emit('reload', ids)" /> |
| | | <GroupMemberRemoveDialog ref="removeDialogRef" @reload="$emit('reload')" /> |
| | | |
| | | <!-- 群主æä½ï¼ç®¡çå设置ï¼ä¸ä¸ªå¼¹çªåå¹¶å¢ / å ï¼æäº¤æ¶ diffï¼+ 群主管çæè½¬è®© --> |
| | | <GroupAdminSetDialog ref="adminSetDialogRef" @reload="$emit('reload')" /> |
| | | <GroupOwnerTransferDialog ref="ownerTransferDialogRef" @reload="$emit('reload')" /> |
| | | |
| | | <!-- è¿ç¾¤ç³è¯·å表ï¼ä»
å½å¼å¯å®¡æ¹ + å½åç¨æ·æ¯ owner / admin æ¶å
¥å£å¯è§ï¼ --> |
| | | <GroupRequestListDialog ref="requestListDialogRef" /> |
| | | |
| | | <!-- å享群åçï¼æå½å群ä½ä¸ºåçæ¶æ¯æ¨èç»å
¶ä»ä¼è¯ --> |
| | | <RecommendCardDialog ref="recommendCardDialogRef" /> |
| | | </Drawer> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | /* ãæ·»å / ç§»åºãç¦çï¼hover æ¶èå¨å
é¨ icon-tile 走主è²ï¼è·¨åå
ç´ ç hover è卿 æ³ç¨åå
ç´ å·¥å
·ç±»è¡¨è¾¾ */ |
| | | .im-conversation-group-side__tile-wrap:hover .im-conversation-group-side__icon-tile { |
| | | color: var(--ant-color-primary); |
| | | border-color: var(--ant-color-primary); |
| | | background-color: var(--ant-color-primary-bg); |
| | | } |
| | | |
| | | /* :deep ç©¿é Icon å
é¨ svgï¼ el-icon å
¨å± color å¨æè²æ¨¡å¼ä¸è¢«ä¸»é¢çè¿ï¼é fill å°å½åè² */ |
| | | .im-conversation-group-side__icon-tile :deep(svg) { |
| | | fill: currentColor !important; |
| | | } |
| | | |
| | | /* ç¸é»ä¿¡æ¯è¡å åéçº¿ï¼ ç¸é»å
å¼éæ©å¨æ æ³ç¨å·¥å
·ç±»è¡¨è¾¾ */ |
| | | .im-conversation-group-side__row + .im-conversation-group-side__row { |
| | | border-top: 1px solid var(--im-border-color-lighter); |
| | | } |
| | | </style> |
| | | |
| | | <!-- AntD Drawer è¢«ä¼ éåºå½å scoped è¾¹çï¼è¿éé root-class-name åæé»è®¤ padding --> |
| | | <style> |
| | | .im-conversation-group-side__modal .ant-drawer-body { |
| | | padding: 0; |
| | | } |
| | | </style> |