From cb9cd49627b65a4c0e137e08063271a8cefe1826 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 23 七月 2026 17:48:49 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0

---
 src/views/im/home/components/user/user-info.vue |  447 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 447 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/components/user/user-info.vue b/src/views/im/home/components/user/user-info.vue
new file mode 100644
index 0000000..4c09cf1
--- /dev/null
+++ b/src/views/im/home/components/user/user-info.vue
@@ -0,0 +1,447 @@
+<script lang="ts" setup>
+import type { User } from '../../types'
+
+import { computed, h, nextTick, ref, watch } from 'vue'
+
+import { confirm } from '#/packages/effects/common-ui/src'
+import { DICT_TYPE } from '#/packages/constants/src'
+import { getDictLabel } from '#/packages/effects/hooks/src'
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+
+import { Button, Checkbox, Dropdown, Input, Menu, message } from 'ant-design-vue'
+
+import { getSimpleUser, type SystemUserApi } from '#/api/system/user'
+import { formatDate } from '#/views/im/utils/time'
+
+import { ImFriendAddSource } from '../../../utils/constants'
+import { toUserCardTarget } from '../../../utils/message'
+import { getGenderColor, getGenderIcon } from '../../../utils/user'
+import { useFriendStore } from '../../store/friendStore'
+import { FriendAddDialog } from '../friend'
+import RecommendCardDialog from './recommend-card-dialog.vue'
+import UserAvatar from './user-avatar.vue'
+
+defineOptions({ name: 'ImUserInfo' })
+
+const props = withDefaults(
+  defineProps<{
+    /** 鍔犲ソ鍙嬫潵婧愶紱榛樿 SEARCH锛涘弬瑙� ImFriendAddSource */
+    addSource?: number
+    /** 鏉ユ簮闄勫甫淇℃伅锛歛ddSource=GROUP锛堢兢鑱婏級鏃朵紶缇ゅ悕锛岀敤浜庛�屾垜鏄� XX 缇ょ殑 YY銆嶉濉瘽鏈� */
+    addSourceExtra?: string
+    /** 澶囨敞锛氫粎 relation=friend 鏃舵湁鏁堬紱绌轰覆 鈫� 鏄剧ず"娣诲姞澶囨敞"鍗犱綅 */
+    displayName?: string
+    /** UserAvatar 棰勮灞� z-index锛涙斁鍦ㄩ珮 z-index 娴眰锛堝 UserInfoCard锛夐噷闇�鎵嬪姩鎶珮 */
+    previewZIndex?: number
+    relation?: UserInfoRelation
+    /** 璧锋墜鐢ㄦ埛锛氳嚦灏戣鏈� id锛涙�у埆 / 閮ㄩ棬鐢辩粍浠舵寜 id 鎳掓媺鍚堝苟琛ラ綈 */
+    user: null | User
+  }>(),
+  {
+    addSource: ImFriendAddSource.SEARCH,
+    addSourceExtra: '',
+    displayName: '',
+    previewZIndex: 2000,
+    relation: 'readonly'
+  }
+)
+
+const emit = defineEmits<{
+  /** 鐢ㄦ埛鐐�"鍙戞秷鎭�"锛氬鑸� / 鍏虫诞灞傜瓑鍦烘櫙鐩稿叧鍔ㄤ綔鐢辩埗绾ф壙鎷咃紙姣斿 UserInfoCard 瑕� close锛� */
+  chat: [user: User]
+  /** 鍒犻櫎鑱旂郴浜烘垚鍔熷悗閫氱煡鐖剁骇锛坈onfirm + 璋� store 閮藉湪鏈粍浠跺唴鍋氬畬锛夛細鐖剁骇鍏虫诞灞� / 娓呴�変腑绛� */
+  deleted: [user: User]
+  /** 澶囨敞钀藉簱鎴愬姛鍚庨�氱煡鐖剁骇锛岀敤浜庡悓姝ョ埗绾ф寔鏈夌殑鏈湴 FriendLite / Friend 鍓湰锛堝 contact 椤电殑 selection锛� */
+  saved: [value: string]
+}>()
+
+/**
+ * 鍏崇郴鎬侊細鍐冲畾澶囨敞琛� / 鍙充笂 "..." 鑿滃崟 / 搴曢儴鍔ㄤ綔鍖虹殑鍐呭
+ * - friend: 澶囨敞锛堝彲缂栬緫锛�+ "..." 鍒犻櫎鑿滃崟 + 3 鍥炬爣鍔ㄤ綔
+ * - stranger: 鍗曟寜閽�"鍔犱负濂藉弸"
+ * - self: 鍗曟寜閽�"涓嶈兘鍜岃嚜宸辫亰澶�" disabled
+ * - readonly: 涓嶆覆鏌撳娉� / 鑿滃崟 / 鍔ㄤ綔鍖猴紝浠呭睍绀哄ご閮ㄤ俊鎭�
+ */
+export type UserInfoRelation = 'friend' | 'readonly' | 'self' | 'stranger'
+
+const friendStore = useFriendStore()
+
+const full = ref<null | User>(props.user) // 璧锋墜 user + getSimpleUser 鍚堝苟鍚庣殑瀹屾暣瀵硅薄锛堟�у埆 / 閮ㄩ棬琛ラ綈鐢級
+
+/** 涓绘爣棰橈細澶囨敞浼樺厛锛堝ソ鍙嬪満鏅級锛屽叾娆″師鏄电О */
+const headerName = computed(() => props.displayName || full.value?.nickname || '')
+
+const deptText = computed(() => full.value?.deptName || '-')
+
+const genderIcon = computed(() => getGenderIcon(full.value?.sex))
+const genderColor = computed(() => getGenderColor(full.value?.sex))
+
+/** 濂藉弸鍏崇郴璁板綍锛氭潵婧� / 娣诲姞鏃堕棿 / 鏄惁鎷夐粦浠庤繖閲屽彇锛堜粎 friend 鎬佷笅鎵嶆湁鎰忎箟锛� */
+const friendInfo = computed(() =>
+  props.user?.id ? friendStore.getFriend(props.user.id) : undefined
+)
+
+/** 鏄惁宸叉媺榛戯細鑿滃崟椤广�屽姞鍏ラ粦鍚嶅崟 / 绉诲嚭榛戝悕鍗曘�嶆寜杩欎釜鍒囨崲 */
+const isBlocked = computed(() => !!friendInfo.value?.blocked)
+
+const editingRemark = ref(false) // 澶囨敞鍐呰仈缂栬緫锛歟ditingRemark 鎺у埗杈撳叆鎬侊紱user 鍒囨崲鏃剁敱涓嬮潰鐨� watch 澶嶄綅閬垮厤鑴忔�佹硠婕�
+const remarkInput = ref('')
+const remarkInputRef = ref<null | { focus: () => void; select?: () => void }>(null)
+
+/**
+ * user.id 鍙樺寲鐨勭粺涓�澶勭悊锛�
+ * 1. 璧锋墜鐢� prop 鍏滃簳棣栧睆锛坒ull = props.user锛夛紝鍐� getSimpleUser 鍛戒腑鍚庡悎骞舵浛鎹�
+ * 2. 椤轰究澶嶄綅澶囨敞缂栬緫鎬侊紝閬垮厤涓婁竴涓敤鎴风殑鑴忚緭鍏ユ硠婕忓埌涓嬩竴涓�
+ * 3. 绔炴�佺敤 id 姣斿涓㈠純闄堟棫鍝嶅簲
+ */
+watch(
+  () => props.user?.id,
+  async (id) => {
+    full.value = props.user
+    editingRemark.value = false
+    if (!id) {
+      return
+    }
+    const data = (await getSimpleUser(id)) as User
+    if (props.user?.id !== id) {
+      return
+    }
+    full.value = { ...props.user, ...data }
+  },
+  { immediate: true }
+)
+
+/** 澶囨敞琛岀偣鍑伙細杩涚紪杈戞�� + 鎶婂綋鍓嶅娉ㄧ亴杩涜緭鍏ユ锛屼笅涓�甯ф妸鐒︾偣 / 鍏ㄩ�変氦缁� el-input */
+async function handleRowClick() {
+  if (editingRemark.value) {
+    return
+  }
+  remarkInput.value = props.displayName || ''
+  editingRemark.value = true
+  await nextTick()
+  remarkInputRef.value?.focus()
+  remarkInputRef.value?.select?.()
+}
+
+/**
+ * 淇濆瓨澶囨敞锛�
+ * 1. 閲嶅叆淇濇姢鈥斺�攂lur + Enter 鍚屾椂瑙﹀彂鍙蛋涓�娆★紙缂栬緫鎬佸厛澶嶄綅锛�
+ * 2. 鏃犲彉鍖栬烦杩囧悗绔皟鐢� + 涓嶆姏 saved锛岄伩鍏嶇埗绾ц鍚屾
+ * 3. 钀藉簱鎴愬姛 / 澶辫触閮藉湪缁勪欢鍐呰嚜闂細鎴愬姛鎶� saved 璁╃埗绾у悓姝ユ湰鍦板壇鏈紝澶辫触鐣欑紪杈戞�佸鐨勫睍绀哄�间笉鍔�
+ */
+async function saveRemark() {
+  if (!editingRemark.value) {
+    return
+  }
+  const userId = props.user?.id
+  if (!userId) {
+    editingRemark.value = false
+    return
+  }
+  const next = remarkInput.value.trim()
+  editingRemark.value = false
+  if (next === (props.displayName || '')) {
+    return
+  }
+  await friendStore.setFriendDisplayName(userId, next)
+  message.success('宸叉洿鏂板娉�')
+  emit('saved', next)
+}
+
+function cancelEditRemark() {
+  editingRemark.value = false
+}
+
+/** 鍙戞秷鎭細瀵艰埅 / 鍏虫诞灞傝繖浜�"涓氬姟渚�"鍔ㄤ綔浜ょ粰鐖剁骇锛屾湰缁勪欢鍙礋璐i�氱煡 */
+function handleChat() {
+  if (!props.user) {
+    return
+  }
+  emit('chat', props.user)
+}
+
+/** 鍗犱綅鎻愮ず锛氳闊� / 瑙嗛鑱婂ぉ鑳藉姏灏氭湭鎺ュ叆锛屽厛浠�"寮�鍙戜腑"鍙嬪ソ鎻愮ず */
+function handleComingSoon(featureName: string) {
+  message.info(`${featureName} 鍔熻兘寮�鍙戜腑`)
+}
+
+// ==================== 娣诲姞濂藉弸 / 鍒犻櫎濂藉弸 ====================
+
+const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>() // 鍔犲ソ鍙嬪脊绐� ref锛歨andleAddFriend 璋� open({ presetUser, addSource, addSourceExtra }) 瑙﹀彂
+
+const recommendDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // 鎺ㄨ崘鍚嶇墖寮圭獥 ref锛歨andleRecommend 璋冪敤 open({ target }) 鎵撳紑
+/** 鎶婁粬鎺ㄨ崘缁欐湅鍙嬶細寮� RecommendCardDialog 閫夌洰鏍囦細璇� */
+function handleRecommend() {
+  if (!props.user?.id) {
+    return
+  }
+  const target = toUserCardTarget(full.value)
+  if (!target) {
+    return
+  }
+  recommendDialogRef.value?.open({ target })
+}
+
+/** 鍔犱负濂藉弸锛氬脊 FriendAddDialog锛堝甫棰勫~鐢ㄦ埛锛夛紝璁╃敤鎴峰~鐢宠鐞嗙敱 + 澶囨敞鍚庡啀鍙戠敵璇� */
+function handleAddFriend() {
+  if (!props.user?.id) {
+    return
+  }
+  const presetUser: SystemUserApi.UserSimple = {
+    id: props.user.id,
+    nickname: props.user.nickname,
+    avatar: props.user.avatar,
+    sex: props.user.sex,
+    deptId: props.user.deptId,
+    deptName: props.user.deptName
+  } as SystemUserApi.UserSimple
+  friendAddDialogRef.value?.open({
+    presetUser,
+    addSource: props.addSource,
+    addSourceExtra: props.addSourceExtra
+  })
+}
+
+/** 鍔犲叆榛戝悕鍗曪細confirm 鈫� friendStore.blockFriend锛涘悗绔� FRIEND_BLOCK 鎺ㄥ埌鏃剁敱 dispatcher 鍚屾澶氱 */
+async function handleBlock() {
+  if (!props.user?.id) {
+    return
+  }
+  const target = props.user
+  try {
+    await confirm(`纭畾灏嗐��${target.nickname || ''}銆嶅姞鍏ラ粦鍚嶅崟鍚楋紵`, '鍔犲叆榛戝悕鍗�')
+  } catch {
+    return
+  }
+  await friendStore.blockFriend(target.id)
+  message.success('宸插姞鍏ラ粦鍚嶅崟')
+}
+
+/** 绉诲嚭榛戝悕鍗曪細鎿嶄綔娓╁拰涓嶅脊 confirm锛涘悗绔� FRIEND_UNBLOCK 鎺ㄥ埌鏃剁敱 dispatcher 鍚屾澶氱 */
+async function handleUnblock() {
+  if (!props.user?.id) {
+    return
+  }
+  await friendStore.unblockFriend(props.user.id)
+  message.success('宸茬Щ鍑洪粦鍚嶅崟')
+}
+
+/** 鍒犻櫎鑱旂郴浜猴細寮硅嚜瀹氫箟纭锛堝惈銆屽悓鏃舵竻绌鸿亰澶╄褰曘�嶉�夐」锛夆啋 friendStore.deleteFriend 鈫� 閫氱煡鐖剁骇鍏虫诞灞� / 娓呴�変腑 */
+async function handleDeleteFriend() {
+  if (!props.user?.id) {
+    return
+  }
+  const target = props.user
+  const clearConversation = ref(true)
+  try {
+    await confirm({
+      cancelText: '鍙栨秷',
+      confirmText: '鍒犻櫎',
+      content: h('div', { class: 'flex flex-col gap-3 text-sm' }, [
+        h('div', `纭畾鍒犻櫎濂藉弸銆�${target.nickname || ''}銆�?`),
+        h(
+          Checkbox,
+          {
+            checked: clearConversation.value,
+            'onUpdate:checked': (value: boolean) => {
+              clearConversation.value = value
+            }
+          },
+          () => '鍚屾椂娓呯┖鑱婂ぉ璁板綍'
+        )
+      ]),
+      icon: 'warning',
+      title: '鍒犻櫎鑱旂郴浜�'
+    })
+  } catch {
+    return
+  }
+  await friendStore.deleteFriend(target.id, clearConversation.value)
+  message.success('宸插垹闄ゅソ鍙�')
+  emit('deleted', target)
+}
+</script>
+
+<template>
+  <!--
+    鐢ㄦ埛鍚嶇墖鑷寘鍚粍浠讹紙娴眰 / 琛屽唴閫氱敤锛�
+    - 鐢� UserInfoCard锛堟诞灞傦級鍜� contact/index.vue锛堣鍐咃級鍏辩敤锛歎serInfoCard 鎶婂畠鏀捐繘 teleport 娴眰锛宑ontact 鐩存帴 mount 鍒板彸鏍�
+    - 鍏崇郴鎬佺敱 relation prop 鍐冲畾锛坒riend / stranger / self / readonly锛夛紝瀵瑰簲鍙充笂 "..." 鑿滃崟 + 搴曢儴鍔ㄤ綔鍖轰袱鍧楅兘鍐呭寲鍦ㄦ湰缁勪欢
+    - 澶囨敞 / 鍒犻櫎鑱旂郴浜� / 鍔犱负濂藉弸绛� store 鎿嶄綔閮藉湪鏈粍浠跺唴闂幆锛岀埗绾т粎鐩戝惉 chat / deleted / saved 绛夐�氱煡鍋氬鑸� / 鍏虫诞灞� / 鍚屾鍓湰
+  -->
+  <div>
+    <!-- 椤堕儴锛氬ご鍍忥紙宸︼級 + 鍚嶅瓧 + 鎬у埆鍥炬爣 + 鏄电О / 閮ㄩ棬锛堝彸锛� + 鍙充笂 "..." 鑿滃崟锛堜粎 friend 鎬侊級 -->
+    <div class="flex gap-3 items-start">
+      <UserAvatar
+        :id="full?.id"
+        :url="full?.avatar"
+        :name="full?.nickname"
+        :size="56"
+        :clickable="false"
+        previewable
+        :preview-z-index="previewZIndex"
+      />
+      <div class="flex-1 min-w-0">
+        <div class="flex items-center gap-1.5">
+          <span
+            class="text-lg font-semibold leading-snug truncate text-[var(--ant-color-text)]"
+          >
+            {{ headerName }}
+          </span>
+          <!-- 鎬у埆灏忓浘鏍囷細鐢疯摑銆佸コ绮夛紝鏈煡 / 0 涓嶅睍绀猴紝瀵归綈寰俊鐣欑櫧鍋氭硶 -->
+          <Icon
+            v-if="genderIcon"
+            :icon="genderIcon"
+            :size="16"
+            :color="genderColor"
+            class="flex-shrink-0"
+          />
+        </div>
+        <div class="mt-2 space-y-1 text-13px text-[var(--ant-color-text-secondary)]">
+          <!-- 浠呭綋澶囨敞宸茶鏃跺睍绀烘樀绉板壇琛岋紱鏈缃椂涓绘爣棰樺氨鏄� nickname锛岄伩鍏嶉噸澶� -->
+          <div v-if="displayName" class="truncate">鏄电О锛歿{ full?.nickname }}</div>
+          <div class="truncate">閮ㄩ棬锛歿{ deptText }}</div>
+        </div>
+      </div>
+      <!-- 鍙充笂 "..." 鑿滃崟锛氫粎 friend 鎬佸睍绀猴紱鍚�屽姞鍏�/绉诲嚭榛戝悕鍗曘�� + 銆屽垹闄よ仈绯讳汉銆� -->
+      <div v-if="relation === 'friend'" class="flex-shrink-0">
+        <Dropdown :trigger="['click']" placement="bottomRight" overlay-class-name="im-user-info__more-menu">
+          <div
+            class="flex items-center justify-center w-7 h-7 rounded cursor-pointer hover:bg-[var(--ant-color-fill-secondary)]"
+          >
+            <Icon icon="ep:more-filled" :size="18" class="text-[var(--ant-color-text-secondary)]" />
+          </div>
+          <template #overlay>
+            <Menu>
+              <!-- 鎶婁粬鎺ㄨ崘缁欐湅鍙嬶細浠ヤ釜浜哄悕鐗囨秷鎭舰寮忓彂鍒伴�変腑鐨勪細璇� -->
+              <Menu.Item @click="handleRecommend">鎶婁粬鎺ㄨ崘缁欐湅鍙�</Menu.Item>
+              <!-- 鎷夐粦 / 绉诲嚭榛戝悕鍗曪細鎸� friendInfo.blocked 鍒囨崲鏂囨 -->
+              <Menu.Item v-if="!isBlocked" divided @click="handleBlock">
+鍔犲叆榛戝悕鍗�
+</Menu.Item>
+              <Menu.Item v-else divided @click="handleUnblock">绉诲嚭榛戝悕鍗�</Menu.Item>
+              <Menu.Item divided @click="handleDeleteFriend">
+                <span class="text-[var(--ant-color-error)]">鍒犻櫎鑱旂郴浜�</span>
+              </Menu.Item>
+            </Menu>
+          </template>
+        </Dropdown>
+      </div>
+    </div>
+
+    <!-- 澶囨敞琛岋細浠� friend 鎬佸睍绀猴紝鏈紪杈戞椂鏁磋鍙偣锛涚紪杈戞�佺敤 el-input 琛屽唴鏇挎崲 value 鍒� -->
+    <template v-if="relation === 'friend'">
+      <div class="my-4 h-px bg-[var(--ant-color-border-secondary)]"></div>
+      <div
+        class="flex gap-5 items-center px-1.5 py-1.5 text-sm"
+        :class="
+          !editingRemark
+            ? 'group cursor-pointer rounded transition-colors hover:bg-[var(--ant-color-fill-tertiary)]'
+            : ''
+        "
+        @click="handleRowClick"
+      >
+        <span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">澶囨敞</span>
+        <Input
+          v-if="editingRemark"
+          ref="remarkInputRef"
+          v-model:value="remarkInput"
+          size="small"
+          :maxlength="20"
+          placeholder="娣诲姞澶囨敞"
+          class="flex-1"
+          @click.stop
+          @blur="saveRemark"
+          @keyup.enter="saveRemark"
+          @keyup.esc="cancelEditRemark"
+        />
+        <template v-else>
+          <span
+            class="flex-1 min-w-0 truncate"
+            :class="
+              displayName
+                ? 'text-[var(--ant-color-text)]'
+                : 'text-[var(--ant-color-text-placeholder)]'
+            "
+          >
+            {{ displayName || '娣诲姞澶囨敞' }}
+          </span>
+          <!-- 榛樿 opacity:0 鍗犱綅閬垮厤 hover 鏃跺叾瀹冨垪浣嶇Щ锛涗粎鐖惰 hover 鏃舵诞鐜� -->
+          <Icon
+            icon="ant-design:edit-outlined"
+            :size="14"
+            class="flex-shrink-0 text-[var(--ant-color-text-secondary)] opacity-0 group-hover:opacity-100 transition-opacity"
+          />
+        </template>
+      </div>
+
+      <!-- ==================== 鏇村淇℃伅锛氭潵婧� / 娣诲姞鏃堕棿锛屽榻愬井淇°�屾湅鍙嬭祫鏂欍�嶅垎鍧� ==================== -->
+      <template v-if="friendInfo?.addSource || friendInfo?.addTime">
+        <div class="my-4 h-px bg-[var(--ant-color-border-secondary)]"></div>
+        <div v-if="friendInfo?.addSource" class="flex gap-5 items-center px-1.5 py-1.5 text-sm">
+          <span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">鏉ユ簮</span>
+          <span class="flex-1 min-w-0 truncate text-[var(--ant-color-text)]">
+            {{ getDictLabel(DICT_TYPE.IM_FRIEND_ADD_SOURCE, friendInfo.addSource) }}
+          </span>
+        </div>
+        <div v-if="friendInfo?.addTime" class="flex gap-5 items-center px-1.5 py-1.5 text-sm">
+          <span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">娣诲姞鏃堕棿</span>
+          <span class="flex-1 min-w-0 truncate text-[var(--ant-color-text)]">
+            {{ formatDate(new Date(friendInfo.addTime), 'YYYY-MM-DD') }}
+          </span>
+        </div>
+      </template>
+    </template>
+
+    <!-- 鍔ㄤ綔鍖猴細濂藉弸 = 3 鍥炬爣锛涢檶鐢熶汉 = 鍔犱负濂藉弸鎸夐挳锛涜嚜宸� = disabled锛況eadonly 涓嶆覆鏌� -->
+    <template v-if="relation !== 'readonly'">
+      <div class="my-4 h-px bg-[var(--ant-color-border-secondary)]"></div>
+      <div v-if="relation === 'friend'" class="flex justify-around">
+        <!-- 涓夎繛鍥炬爣鎸夐挳锛氬浘涓婂瓧涓嬨�佷富鑹蹭綔涓哄彲鐐规殫绀猴紝hover 闄嶄笉閫忔槑搴﹀仛鍙嶉 -->
+        <div
+          class="flex flex-col gap-1.5 items-center cursor-pointer text-13px text-[var(--ant-color-primary)] transition-opacity hover:opacity-75"
+          @click="handleChat"
+        >
+          <Icon icon="ant-design:message-outlined" :size="22" />
+          <span>鍙戞秷鎭�</span>
+        </div>
+        <div
+          class="flex flex-col gap-1.5 items-center cursor-pointer text-13px text-[var(--ant-color-primary)] transition-opacity hover:opacity-75"
+          @click="handleComingSoon('璇煶鑱婂ぉ')"
+        >
+          <Icon icon="ant-design:phone-outlined" :size="22" />
+          <span>璇煶鑱婂ぉ</span>
+        </div>
+        <div
+          class="flex flex-col gap-1.5 items-center cursor-pointer text-13px text-[var(--ant-color-primary)] transition-opacity hover:opacity-75"
+          @click="handleComingSoon('瑙嗛鑱婂ぉ')"
+        >
+          <Icon icon="ant-design:video-camera-outlined" :size="22" />
+          <span>瑙嗛鑱婂ぉ</span>
+        </div>
+      </div>
+      <div v-else-if="relation === 'self'" class="flex justify-center">
+        <Button type="primary" disabled>涓嶈兘鍜岃嚜宸辫亰澶�</Button>
+      </div>
+      <div v-else class="flex justify-center">
+        <Button type="primary" @click="handleAddFriend">鍔犱负濂藉弸</Button>
+      </div>
+    </template>
+
+    <!-- 鍔犲ソ鍙嬪脊绐楋細鎼哄甫棰勫~鐢ㄦ埛璺宠繃鎼滅储姝ラ锛岀洿鎺ヨ繘鐢宠琛ㄥ崟锛堢悊鐢辨寜 addSource 鍖哄垎璇濇湳锛� -->
+    <FriendAddDialog ref="friendAddDialogRef" />
+
+    <!-- 鎶婁粬鎺ㄨ崘缁欐湅鍙嬪脊绐楋細浠� friend 鎬佷笅鍑虹幇鍏ュ彛 -->
+    <RecommendCardDialog ref="recommendDialogRef" />
+  </div>
+</template>
+
+<style>
+/*
+  闈� scoped锛歟l-dropdown 鐨勪笅鎷夎彍鍗曡蛋 teleport 鍒� body锛宻coped 閫変笉鍒般��
+  UserInfoCard 娴眰鐢� z-9998锛岃鎶婅繖鍧楁姮鍒版洿楂橈紙榛樿 --ant-z-index-popup-base ~2050 浼氳閬僵鍘嬩綇锛夈��
+*/
+.im-user-info__more-menu {
+  z-index: 10000 !important;
+}
+</style>

--
Gitblit v1.9.3