From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块

---
 src/views/im/home/pages/conversation/components/conversation/conversation-group-side.vue |  832 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 832 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/pages/conversation/components/conversation/conversation-group-side.vue b/src/views/im/home/pages/conversation/components/conversation/conversation-group-side.vue
new file mode 100644
index 0000000..e3d2003
--- /dev/null
+++ b/src/views/im/home/pages/conversation/components/conversation/conversation-group-side.vue
@@ -0,0 +1,832 @@
+<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 // 鎶藉眽鏄惁鎵撳紑锛坴-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锛歨andleOpenInvite 璋� 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锛歨andleOpenRequestList 璋� open({ groupId }) 瑙﹀彂
+
+/** 鎵撳紑褰撳墠缇ょ殑杩涚兢鐢宠鍒楄〃 */
+function handleOpenRequestList() {
+  if (!props.group?.id) {
+    return
+  }
+  requestListDialogRef.value?.open({ groupId: props.group.id })
+}
+
+// ==================== 鍒嗕韩缇ゅ悕鐗� ====================
+
+const recommendCardDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // 鍒嗕韩缇ゅ悕鐗囧脊绐� ref锛歨andleShareGroupCard 璋冪敤 open({ target }) 鎵撳紑
+
+/** 鍒嗕韩缇ゅ悕鐗囷細鎶婂綋鍓嶇兢浣滀负鍚嶇墖娑堟伅鎺ㄨ崘缁欏叾浠栦細璇� */
+function handleShareGroupCard() {
+  const target = toGroupCardTarget(props.group)
+  if (!target) {
+    return
+  }
+  recommendCardDialogRef.value?.open({ target })
+}
+
+// ==================== 閫�鍑虹兢鑱� ====================
+
+/** 閫�鍑虹兢鑱婏紙鏅�氭垚鍛樺叆鍙o紱缇や富閫�鍑鸿蛋"瑙f暎缇�"鏄彟涓�鏉¤矾寰勶紝杩欓噷涓嶅鐞嗭級 */
+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
+}
+
+/** 瑙f暎缇よ亰锛堜粎缇や富鍏ュ彛锛� */
+async function handleDissolve() {
+  if (!props.group) {
+    return
+  }
+  try {
+    await confirm('瑙f暎鍚庢墍鏈夋垚鍛樺皢琚Щ鍑猴紝涓旀棤娉曟仮澶嶏紝纭瑙f暎鍚楋紵', '纭瑙f暎')
+  } 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 鍦ㄤ笂銆乿alue 鍦ㄤ笅锛岀旱鍚戝爢鍙狅紙瀵归綈寰俊 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>
+
+      <!-- ==================== 搴曢儴锛氶��鍑� / 瑙f暎缇よ亰锛堝巻鍙查��缇ょ兢闅愯棌锛屽凡閫�缇ゆ棤闇�鍐嶉��锛� ==================== -->
+      <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)]"
+      >
+        <!-- 缇や富锛氳В鏁g兢鑱� -->
+        <Button
+          v-if="isOwner"
+          class="w-full !h-9 text-14px"
+          danger
+          @click="handleDissolve"
+        >
+          瑙f暎缇よ亰
+        </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 鏃跺叆鍙e彲瑙侊級 -->
+    <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>

--
Gitblit v1.9.3