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-private-side.vue | 285 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 285 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/pages/conversation/components/conversation/conversation-private-side.vue b/src/views/im/home/pages/conversation/components/conversation/conversation-private-side.vue
new file mode 100644
index 0000000..7939da1
--- /dev/null
+++ b/src/views/im/home/pages/conversation/components/conversation/conversation-private-side.vue
@@ -0,0 +1,285 @@
+<script lang="ts" setup>
+import type { Conversation, Friend } from '../../../../types'
+
+import { computed, ref, watch } from 'vue'
+
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+
+import { Button, Drawer, Input, message, Popover, Spin, Switch } from 'ant-design-vue'
+
+import { useConversationStore } from '#/views/im/home/store/conversationStore'
+import { useFriendStore } from '#/views/im/home/store/friendStore'
+import { useGroupStore } from '#/views/im/home/store/groupStore'
+import { ImConversationType } from '#/views/im/utils/constants'
+import { getFriendDisplayName, getGroupDisplayName } from '#/views/im/utils/user'
+
+import { GroupCreateDialog } from '../../../../components/group'
+import { UserAvatar } from '../../../../components/user'
+
+defineOptions({ name: 'ImConversationPrivateSide' })
+
+const props = withDefaults(
+ defineProps<{
+ conversation?: Conversation | null // 褰撳墠浼氳瘽锛堝彇缃《 / 鍏嶆墦鎵版�侊級
+ friend?: Friend // 瀵规柟濂藉弸淇℃伅锛堝彇澶村儚 / 鏄电О锛�
+ modelValue?: boolean // 鎶藉眽寮�鍏筹紙v-model锛�
+ }>(),
+ {
+ conversation: null,
+ friend: undefined,
+ modelValue: false
+ }
+)
+
+const emit = defineEmits<{
+ openHistory: [] // 鐐瑰嚮 "鏌ユ壘鑱婂ぉ鍐呭" 琛� 鈫� 鐖剁粍浠舵墦寮� MessageHistory 寮圭獥
+ 'update:modelValue': [value: boolean]
+}>()
+
+const visible = computed({
+ get: () => props.modelValue,
+ set: (value) => emit('update:modelValue', value)
+})
+
+const conversationStore = useConversationStore()
+const friendStore = useFriendStore()
+const groupStore = useGroupStore()
+
+/** tile 鏍囩 / 鍚庣画鑱婂ぉ鐣岄潰鐢ㄧ殑灞曠ず鍚嶏細澶囨敞浼樺厛 */
+const displayName = computed(() => (props.friend ? getFriendDisplayName(props.friend) : ''))
+
+const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>() // 鍙戣捣缇よ亰寮圭獥 ref锛歨andleOpenCreateGroup 璋� open({ lockedIds }) 閿佸畾瀵规柟
+
+/** 鎵撳紑鍙戣捣缇よ亰寮圭獥锛氭妸瀵规柟榛樿鍕句笂涓斾笉鍙彇娑堬紝瀵瑰簲寰俊"鍩轰簬绉佽亰鍙戣捣缇よ亰" */
+function handleOpenCreateGroup() {
+ const lockedIds = props.friend ? [props.friend.friendUserId] : []
+ createGroupDialogRef.value?.open({ lockedIds })
+}
+
+const displayNamePopoverVisible = ref(false)
+const editDisplayName = ref('')
+
+// popover 寮瑰嚭鏃舵妸褰撳墠澶囨敞鐏岃繘缂栬緫鎬侊紝閬垮厤涓婃鏈繚瀛樼殑鑴忓��
+watch(displayNamePopoverVisible, (open) => {
+ if (open) {
+ editDisplayName.value = props.friend?.displayName || ''
+ }
+})
+
+// 鎶藉眽鍏抽棴鏃舵妸杩樻病鏀舵帀鐨� popover 涓�骞舵竻鎺夛紝閬垮厤涓嬫鎵撳紑闂竴涓�
+watch(visible, (open) => {
+ if (!open) {
+ displayNamePopoverVisible.value = false
+ }
+})
+
+/** 澶囨敞 popover 鐐瑰嚮淇濆瓨 */
+async function handleSaveDisplayName() {
+ if (!props.friend) {
+ return
+ }
+ await friendStore.setFriendDisplayName(props.friend.friendUserId, editDisplayName.value)
+ displayNamePopoverVisible.value = false
+ message.success('淇濆瓨鎴愬姛')
+}
+
+/**
+ * 鍒囧厤鎵撴壈锛氫箰瑙傚弻鍐� conversationStore + friendStore锛涘悗绔け璐ュ洖婊� conversation 鐘舵�侊紝淇濇寔涓� ConversationItem.handleMuted 涓�鑷�
+ */
+function handleMutedChange(value: boolean | number | string) {
+ if (!props.conversation) {
+ return
+ }
+ const next = !!value
+ const { type, targetId } = props.conversation
+ conversationStore.setConversationSilent(type, targetId, next)
+ if (type !== ImConversationType.PRIVATE) {
+ return
+ }
+ friendStore.setFriendSilent(targetId, next).catch((error) => {
+ console.error('[IM ConversationPrivateSide] 鍒囨崲鍏嶆墦鎵板け璐�', { targetId }, error)
+ conversationStore.setConversationSilent(type, targetId, !next)
+ })
+}
+
+/** 鍒囩疆椤讹細绾湰鍦� conversationStore 鎺掑簭鎬侊紙鏃犲悗绔瓧娈碉級 */
+function handleTopChange(value: boolean | number | string) {
+ if (!props.conversation) {
+ return
+ }
+ conversationStore.setConversationTop(props.conversation.type, props.conversation.targetId, !!value)
+}
+
+/** 缇ゅ垱寤烘垚鍔燂細璺冲埌鏂扮兢浼氳瘽 + 鍏虫帀鏈晶鎶藉眽锛岃鐢ㄦ埛涓撴敞鏂扮兢 */
+function handleGroupCreated(groupId: number) {
+ const group = groupStore.getGroup(groupId)
+ if (!group) {
+ return
+ }
+ conversationStore.openConversation(
+ groupId,
+ ImConversationType.GROUP,
+ getGroupDisplayName(group),
+ group.avatar || '',
+ { silent: !!group.silent }
+ )
+ visible.value = false
+}
+</script>
+
+<template>
+ <!--
+ 绉佽亰渚ц竟鎶藉眽
+ - 鏁翠綋缁撴瀯瀵归綈 ConversationGroupSide锛氬鏍� + 淇℃伅琛� + 寮�鍏�
+ - 椤堕儴濂藉弸瀹牸 + "+" tile锛氱偣 + 璋冭捣 GroupCreateDialog 骞堕攣瀹氬鏂癸紝瀵归綈寰俊"鍩轰簬绉佽亰鍙戣捣缇よ亰"
+ - "娓呯┖鑱婂ぉ璁板綍"鎸夐挳鍦� WeChat 閲屾湁锛屼絾鐩墠鍚庣娌″缓娑堟伅娓呯┖鑳藉姏锛屽厛涓嶅姞閬垮厤鍋氬崐鍚婂瓙
+ -->
+ <Drawer
+ v-model:open="visible"
+ :closable="false"
+ placement="right"
+ width="340px"
+ root-class-name="im-conversation-private-side__modal"
+ >
+ <!-- friend 缂哄け鍦烘櫙锛氶檶鐢熶汉浼氳瘽鍒氭墦寮� / 濂藉弸鏁版嵁杩樻病琛ユ媺鍒帮紱绌虹櫧浼氳鐢ㄦ埛浠ヤ负鎶藉眽鍧忎簡锛岀粰涓姞杞藉崰浣� -->
+ <div
+ v-if="!friend"
+ class="flex flex-col items-center justify-center h-full text-13px text-[var(--ant-color-text-placeholder)] bg-[var(--ant-color-bg-container)]"
+ >
+ <Spin tip="鍔犺浇涓�..." />
+ </div>
+ <div v-else 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)]">
+ <!-- 濂藉弸瀹牸锛氬師 tile + "+" tile锛屽榻� GroupSide 瑙嗚锛岃涓ょ鎶藉眽鐪嬭捣鏉ユ槸涓�瀹剁殑 -->
+ <div class="flex flex-wrap gap-1 px-4 pt-4 pb-[14px] bg-[var(--ant-color-bg-container)]">
+ <div class="flex flex-col items-center w-[66px]">
+ <UserAvatar
+ :id="friend.friendUserId"
+ :url="friend.avatar"
+ :name="friend.nickname"
+ :size="50"
+ />
+ <div class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--ant-color-text)] text-center truncate">
+ {{ displayName }}
+ </div>
+ </div>
+
+ <!-- + tile锛氱偣鍑昏皟璧� GroupCreateDialog锛屾妸瀵规柟 id 浣滀负 lockedIds 浼犲叆 -->
+ <div
+ class="im-conversation-private-side__tile-wrap-clickable flex flex-col items-center w-[66px] cursor-pointer"
+ title="鍙戣捣缇よ亰"
+ @click="handleOpenCreateGroup"
+ >
+ <div class="im-conversation-private-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="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--ant-color-text)] text-center truncate">娣诲姞</div>
+ </div>
+ </div>
+
+ <div class="flex-shrink-0 h-[10px]"></div>
+
+ <!-- 澶囨敞锛堜粎鑷繁鍙锛夛細鐐瑰嚮寮� popover 缂栬緫锛屼繚瀛樺悗绔嬪嵆鍒锋柊鏈娊灞� + 浼氳瘽鍒楄〃灞曠ず鍚� -->
+ <div class="bg-[var(--ant-color-bg-container)]">
+ <Popover
+ v-model:open="displayNamePopoverVisible"
+ trigger="click"
+ placement="leftTop"
+ :overlay-style="{ width: '280px' }"
+ >
+ <div
+ class="im-conversation-private-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="friend.displayName"
+ class="text-13px leading-[1.6] text-[var(--ant-color-text)] break-all line-clamp-2"
+ >
+ {{ friend.displayName }}
+ </span>
+ <span v-else class="text-13px leading-[1.6] text-[var(--ant-color-text-placeholder)]">
+ 濂藉弸澶囨敞浠呰嚜宸卞彲瑙�
+ </span>
+ </div>
+ <template #content>
+ <div class="flex flex-col gap-2">
+ <Input
+ v-model:value="editDisplayName"
+ :maxlength="16"
+ show-count
+ placeholder="璇疯緭鍏ュ娉ㄥ悕"
+ />
+ <div class="flex justify-end gap-2">
+ <Button size="small" @click="displayNamePopoverVisible = false">鍙栨秷</Button>
+ <Button size="small" type="primary" @click="handleSaveDisplayName">
+ 淇濆瓨
+ </Button>
+ </div>
+ </div>
+ </template>
+ </Popover>
+ </div>
+
+ <div class="flex-shrink-0 h-[10px]"></div>
+
+ <!-- 鏌ユ壘鑱婂ぉ鍐呭 -->
+ <div class="bg-[var(--ant-color-bg-container)]">
+ <div
+ class="im-conversation-private-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>
+ </div>
+
+ <div class="flex-shrink-0 h-[10px]"></div>
+
+ <!-- 寮�鍏抽」 -->
+ <div class="bg-[var(--ant-color-bg-container)]">
+ <div class="im-conversation-private-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="handleMutedChange" />
+ </div>
+ <div class="im-conversation-private-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="handleTopChange" />
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 瀛愬璇濇锛氬彂璧风兢鑱婏紙閿佸畾瀵规柟涓哄凡閫夛級 -->
+ <GroupCreateDialog ref="createGroupDialogRef" @created="handleGroupCreated" />
+ </Drawer>
+</template>
+
+<style scoped>
+/* 銆�+銆� tile锛� hover 鏃惰仈鍔ㄥ唴閮� icon-tile 璧颁富鑹诧紱 璺ㄥ瓙鍏冪礌鐨� hover 鑱斿姩鏃犳硶鐢ㄥ崟鍏冪礌宸ュ叿绫昏〃杈� */
+.im-conversation-private-side__tile-wrap-clickable:hover .im-conversation-private-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-private-side__icon-tile :deep(svg) {
+ fill: currentColor !important;
+}
+
+/* 鐩搁偦淇℃伅琛屽姞鍒嗛殧绾匡紱 鐩搁偦鍏勫紵閫夋嫨鍣ㄦ棤娉曠敤宸ュ叿绫昏〃杈� */
+.im-conversation-private-side__row + .im-conversation-private-side__row {
+ border-top: 1px solid var(--im-border-color-lighter);
+}
+</style>
+
+<!-- 鍚� GroupSide锛歛ntd Drawer 浼犻�佸埌 body 鍚� scoped data-v 钀戒笉鍒� body 涓婏紝闈� root-class-name 浣滅鍏堥�夋嫨鍣ㄥ啓鍏ㄥ眬瑙勫垯鍘嬫帀榛樿 padding -->
+<style>
+.im-conversation-private-side__modal .ant-drawer-body {
+ padding: 0;
+}
+</style>
--
Gitblit v1.9.3