From 91c82965b2d987452ca276e3a03b48c8dcda2ae9 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 19 八月 2026 11:47:26 +0800
Subject: [PATCH] config(env): 更新生产环境配置和公司名称

---
 src/views/im/home/components/friend/friend-add-dialog.vue |  289 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 289 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/components/friend/friend-add-dialog.vue b/src/views/im/home/components/friend/friend-add-dialog.vue
new file mode 100644
index 0000000..152f4ca
--- /dev/null
+++ b/src/views/im/home/components/friend/friend-add-dialog.vue
@@ -0,0 +1,289 @@
+<script lang="ts" setup>
+import type { SystemUserApi } from '#/api/system/user'
+
+import { computed, ref } from 'vue'
+
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+import { useUserStore } from '#/packages/stores/src'
+
+import { Button, Input, message, Modal, Spin } from 'ant-design-vue'
+
+import { getSimpleUserListByNickname } from '#/api/system/user'
+import { getCurrentUserId } from '#/views/im/utils/auth'
+
+import { ImFriendAddSource } from '../../../utils/constants'
+import { getGenderColor, getGenderIcon } from '../../../utils/user'
+import { useFriendStore } from '../../store/friendStore'
+import { UserAvatar } from '../user'
+
+defineOptions({ name: 'ImFriendAddDialog' })
+
+const visible = ref(false) // 寮圭獥鏄惁鍙
+const presetUser = ref<null | SystemUserApi.UserSimple>(null) // 棰勫~鐩爣鐢ㄦ埛锛氶潪 null 鏃惰烦杩囨悳绱㈡楠わ紝鐩存帴杩涘叆鐢宠琛ㄥ崟锛堢兢鎴愬憳鍔犲ソ鍙� / 鍚嶇墖鍔犲ソ鍙嬬瓑鍦烘櫙锛�
+const addSource = ref<number>(ImFriendAddSource.SEARCH) // 娣诲姞鏉ユ簮锛涘弬瑙� ImFriendAddSourceEnum锛岄粯璁� SEARCH
+const addSourceExtra = ref<string>('') // 鏉ユ簮闄勫甫淇℃伅锛歛ddSource=ImFriendAddSource.GROUP 鏃朵紶缇ゅ悕锛岃瘽鏈嫾涓恒�屾垜鏄� XX 缇ょ殑 YY銆�
+
+defineExpose({
+  /** 鎵撳紑鍔犲ソ鍙嬪脊绐楋細reset 鈫� 鐏屽弬 鈫� visible=true锛涗笉浼� opts 璧版悳绱㈡ā寮� */
+  open(opts?: { addSource?: number; addSourceExtra?: string; presetUser?: null | SystemUserApi.UserSimple; }) {
+    presetUser.value = opts?.presetUser ?? null
+    addSource.value = opts?.addSource ?? ImFriendAddSource.SEARCH
+    addSourceExtra.value = opts?.addSourceExtra ?? ''
+    resetAll()
+    visible.value = true
+  }
+})
+
+const friendStore = useFriendStore()
+const userStore = useUserStore()
+
+/** 褰撳墠鐧诲綍鐢ㄦ埛缂栧彿锛涚敤 computed 鍖呬竴灞傦紝鍒囪处鍙峰悗闅� wsCache 閲嶅彇锛岄伩鍏嶉《灞傛眰鍊煎湪 keep-alive 瀹炰緥閲屾寔鏈夋棫 id */
+const currentUserId = computed(() => getCurrentUserId())
+
+/** 鎼滅储缁撴灉杩囨护鎺夎嚜宸憋紱鐢� v-if 鑰岄潪 v-show锛岄伩鍏� DOM 鍗犱綅 + 澶村儚鏃犳晥璇锋眰 */
+const visibleUsers = computed(() =>
+  users.value.filter((user) => user.id !== currentUserId.value)
+)
+const keyword = ref('') // 鎼滅储鍏抽敭瀛�
+const users = ref<SystemUserApi.UserSimple[]>([]) // 鎼滅储缁撴灉
+const searched = ref(false) // 鏄惁宸叉悳绱�
+const loading = ref(false) // 鎼滅储鍔犺浇涓�
+const step = ref<'apply' | 'search'>('search') // 褰撳墠姝ラ锛歴earch=鎼滅储鍒楄〃锛沘pply=鐢宠琛ㄥ崟
+const targetUser = ref<null | SystemUserApi.UserSimple>(null) // 鐢宠鐩爣鐢ㄦ埛
+const applyContent = ref('') // 鐢宠鐞嗙敱锛堥粯璁ゅ~銆屾垜鏄� ${褰撳墠鏄电О}銆嶏紝瀵归綈寰俊浜や簰锛�
+const displayName = ref('') // 瀵规帴鏀舵柟鐨勫娉紙浠呰嚜宸卞彲瑙侊級
+const submitting = ref(false) // 鎻愪氦涓�
+
+/** 寮圭獥鏍囬闅忔楠ゅ垏鎹� */
+const dialogTitle = computed(() => (step.value === 'apply' ? '鐢宠娣诲姞鏈嬪弸' : '娣诲姞濂藉弸'))
+
+/** 鏄惁棰勫~妯″紡锛坧resetUser 涓嶄负绌� 鈫� 璺宠繃鎼滅储锛屽叧闂嵆閿�姣侊紝鏃犮�屽彇娑堣繑鍥炴悳绱€�嶆寜閽級 */
+const presetMode = computed(() => !!presetUser.value)
+
+function resetAll() {
+  keyword.value = ''
+  users.value = []
+  searched.value = false
+  // 棰勫~妯″紡锛氱洿鎺ヨ繘鐢宠琛ㄥ崟锛宼argetUser 鍙栬嚜 presetUser锛涚敵璇风悊鐢辨寜 addSource 鍖哄垎璇濇湳
+  if (presetUser.value) {
+    targetUser.value = presetUser.value
+    applyContent.value = buildPresetApplyContent()
+    displayName.value = ''
+    step.value = 'apply'
+    return
+  }
+  // 闈為濉ā寮忥細榛樿杩涙悳绱㈡楠�
+  step.value = 'search'
+  targetUser.value = null
+  applyContent.value = ''
+  displayName.value = ''
+}
+
+/** 棰勫~妯″紡涓嬬殑鐢宠鐞嗙敱璇濇湳锛氱兢鑱娿�屾垜鏄�"XX 缇�"鐨� YY銆嶏紱鍏跺畠銆屾垜鏄� YY銆� */
+function buildPresetApplyContent(): string {
+  const myNickname = userStore.userInfo?.nickname || ''
+  if (!myNickname) {
+    return ''
+  }
+  // 缇よ亰鍦烘櫙鎷煎甫缇ゅ悕鐨勮瘽鏈紱鍏跺畠鍦烘櫙榛樿銆屾垜鏄� YY銆�
+  const groupExtra = addSource.value === ImFriendAddSource.GROUP ? addSourceExtra.value : ''
+  return groupExtra ? `鎴戞槸"${groupExtra}"鐨�${myNickname}` : `鎴戞槸${myNickname}`
+}
+
+/** 鎸夋樀绉版悳绱㈢敤鎴凤細绌哄叧閿瓧鐩存帴娓呯┖缁撴灉 */
+async function handleSearch() {
+  searched.value = true
+  if (!keyword.value.trim()) {
+    users.value = []
+    return
+  }
+  loading.value = true
+  try {
+    users.value = (await getSimpleUserListByNickname(keyword.value.trim())) || []
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 杩涘叆鐢宠姝ラ锛氶濉敵璇风悊鐢便�屾垜鏄� ${褰撳墠鐢ㄦ埛鏄电О}銆嶏紙瀵归綈寰俊浜や簰锛� */
+function enterApply(user: SystemUserApi.UserSimple) {
+  targetUser.value = user
+  const myNickname = userStore.userInfo?.nickname || ''
+  applyContent.value = myNickname ? `鎴戞槸${myNickname}` : ''
+  displayName.value = ''
+  step.value = 'apply'
+}
+
+/** 鍙栨秷鐢宠锛屽洖鍒版悳绱㈡楠� */
+function backToSearch() {
+  step.value = 'search'
+  targetUser.value = null
+}
+
+/** 鎻愪氦濂藉弸鐢宠锛氳繑鍥� requestId 璧般�岀瓑寰呴獙璇併�嶏紱杩斿洖 null 琛ㄧず鍚庣鍛戒腑銆屽崟鍚戝ソ鍙嬮潤榛橀噸鍚�嶅垎鏀紝宸茬洿鎺ユ垚涓哄ソ鍙� */
+async function handleSubmitApply() {
+  if (!targetUser.value) {
+    return
+  }
+  // 棰勬牎楠岋細涓嶈兘鍔犺嚜宸憋紙鎼滅储鍒楄〃宸茶繃婊わ紝杩欓噷鍏滃簳 presetUser / 鍚嶇墖鍏ュ彛绛夊満鏅級
+  if (targetUser.value.id === currentUserId.value) {
+    message.warning('涓嶈兘娣诲姞鑷繁涓哄ソ鍙�')
+    return
+  }
+  submitting.value = true
+  try {
+    const requestId = await friendStore.applyFriendRequest({
+      toUserId: targetUser.value.id,
+      applyContent: applyContent.value.trim() || undefined,
+      displayName: displayName.value.trim() || undefined,
+      addSource: addSource.value
+    })
+    // silent 鍒嗘敮锛堝凡鏄崟鍚戝ソ鍙嬭闈欓粯閲嶅惎锛夛細涓诲姩 fetchFriendInfo 鍏ュ簱锛屼笉渚濊禆 WS FRIEND_ADD 鎺ㄩ�侊紝閬垮厤涓㈡帹鏃跺垪琛ㄧ湅涓嶅埌
+    if (requestId === null) {
+      await friendStore.fetchFriendInfo(targetUser.value.id)
+    }
+    message.success(requestId ? '鐢宠宸插彂閫侊紝绛夊緟瀵规柟楠岃瘉' : '宸叉坊鍔犱负濂藉弸')
+    visible.value = false
+  } catch {
+    // 涓氬姟閿欒锛堝凡鏄ソ鍙� / 琚鏂规媺榛� / 鐢ㄦ埛琚鐢� 绛夛級锛氬叏灞�鎷︽埅鍣ㄥ凡寮归敊璇彁绀猴紝鏈湴鍏冲脊绐楅伩鍏嶈剰鐘舵�佸仠鐣�
+    visible.value = false
+  } finally {
+    submitting.value = false
+  }
+}
+</script>
+
+<template>
+  <!--
+    娣诲姞濂藉弸瀵硅瘽妗嗭紙鍙屽眰娴佺▼锛�
+    - 绗竴灞� search锛氭寜鏄电О鎼滅储鐢ㄦ埛鍒楄〃
+    - 绗簩灞� apply锛氶�変腑鐢ㄦ埛鍚庡睍寮�銆岀敵璇锋坊鍔犳湅鍙嬨�嶈〃鍗曪紙鐢宠鐞嗙敱 + 澶囨敞锛�
+  -->
+  <Modal
+    v-model:open="visible"
+    :title="dialogTitle"
+    width="480px"
+    :mask-closable="false"
+    :footer="step === 'apply' ? undefined : null"
+  >
+    <!-- 绗竴灞傦細鎼滅储 + 鐢ㄦ埛鍒楄〃 -->
+    <template v-if="step === 'search'">
+      <Input
+        v-model:value="keyword"
+        placeholder="杈撳叆鏄电О鍥炶溅鎼滅储锛堟渶澶氬睍绀� 20 鏉★級"
+        allow-clear
+        @keyup.enter="handleSearch"
+      >
+        <template #suffix>
+          <Icon icon="ant-design:search-outlined" class="cursor-pointer" @click="handleSearch" />
+        </template>
+      </Input>
+
+      <Spin :spinning="loading" wrapper-class-name="w-full">
+        <div class="h-[400px] mt-2.5">
+          <div
+            v-if="visibleUsers.length === 0"
+            class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
+          >
+            {{ searched ? '娌℃湁鎼滃埌鐢ㄦ埛' : '杈撳叆鍏抽敭瀛楀悗鍥炶溅寮�濮嬫悳绱�' }}
+          </div>
+          <div
+            v-for="user in visibleUsers"
+            :key="user.id"
+            class="flex gap-3 items-center px-2 py-2.5 border-b border-b-solid border-[var(--ant-color-border-secondary)]"
+          >
+            <UserAvatar
+              :id="user.id"
+              :url="user.avatar"
+              :name="user.nickname"
+              :size="42"
+              :clickable="false"
+            />
+            <div class="flex-1 min-w-0 overflow-hidden">
+              <!-- 鏄电О + 鎬у埆鍥炬爣 -->
+              <div
+                class="flex items-center gap-1 text-sm font-semibold text-[var(--ant-color-text)]"
+              >
+                <span class="truncate">{{ user.nickname }}</span>
+                <Icon
+                  v-if="getGenderIcon(user.sex)"
+                  :icon="getGenderIcon(user.sex)"
+                  :size="14"
+                  :color="getGenderColor(user.sex)"
+                  class="flex-shrink-0"
+                />
+              </div>
+              <!-- 閮ㄩ棬 -->
+              <div
+                v-if="user.deptName"
+                class="mt-0.5 text-xs truncate text-[var(--ant-color-text-secondary)]"
+              >
+                {{ user.deptName }}
+              </div>
+            </div>
+            <!-- 宸叉槸濂藉弸鏄剧ず銆屽凡娣诲姞銆嶏紱鍚﹀垯鏄剧ず銆屾坊鍔犮�嶏紙鐐瑰嚮杩涘叆 apply 姝ラ锛� -->
+            <Button
+              v-if="!friendStore.isActiveFriend(user.id)"
+              type="primary"
+              size="small"
+              @click="enterApply(user)"
+            >
+              娣诲姞
+            </Button>
+            <Button v-else size="small" disabled>宸叉坊鍔�</Button>
+          </div>
+        </div>
+      </Spin>
+    </template>
+
+    <!-- 绗簩灞傦細鐢宠琛ㄥ崟锛堝榻愬井淇°�岀敵璇锋坊鍔犳湅鍙嬨�嶏級 -->
+    <template v-if="step === 'apply' && targetUser">
+      <!-- 閫変腑鐨勭敤鎴峰崱鐗� -->
+      <div
+        class="flex gap-3 items-center px-2 py-3 mb-4 rounded-md bg-[var(--ant-color-fill-secondary)]"
+      >
+        <UserAvatar
+          :id="targetUser.id"
+          :url="targetUser.avatar"
+          :name="targetUser.nickname"
+          :size="42"
+          :clickable="false"
+        />
+        <div class="flex-1 min-w-0 overflow-hidden">
+          <div class="text-sm font-semibold text-[var(--ant-color-text)] truncate">
+            {{ targetUser.nickname }}
+          </div>
+          <div
+            v-if="targetUser.deptName"
+            class="mt-0.5 text-xs truncate text-[var(--ant-color-text-secondary)]"
+          >
+            {{ targetUser.deptName }}
+          </div>
+        </div>
+      </div>
+
+      <div class="text-13px text-[var(--ant-color-text-secondary)] mb-1.5">鍙戦�佹坊鍔犳湅鍙嬬敵璇�</div>
+      <Input.TextArea
+        v-model:value="applyContent"
+        :rows="3"
+        :maxlength="255"
+        show-count
+        placeholder="璇峰~鍐欑敵璇风悊鐢�"
+      />
+
+      <div class="text-13px text-[var(--ant-color-text-secondary)] mt-3 mb-1.5">澶囨敞</div>
+      <Input
+        v-model:value="displayName"
+        :maxlength="16"
+        placeholder="缁欏鏂硅捣涓娉紙浠呰嚜宸卞彲瑙侊紝鍙笉濉級"
+      />
+    </template>
+
+    <!-- 浠呭湪 apply 姝ラ鏄剧ず footer 鎿嶄綔鎸夐挳锛坰lot 蹇呴』鏄� el-dialog 鐩存帴瀛愯妭鐐癸級 -->
+    <template v-if="step === 'apply'" #footer>
+      <!-- 棰勫~妯″紡鏃犳悳绱㈡楠わ紝銆屽彇娑堛�嶇洿鎺ュ叧闂脊绐� -->
+      <Button @click="presetMode ? (visible = false) : backToSearch()">鍙栨秷</Button>
+      <Button type="primary" :loading="submitting" @click="handleSubmitApply"> 纭畾 </Button>
+    </template>
+  </Modal>
+</template>

--
Gitblit v1.9.3