From 5367b3b4d92588c4e76728e6bd4ad6aae0cbb967 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 23 七月 2026 13:05:25 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0

---
 src/views/im/home/components/group/group-request-list-dialog.vue |  377 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 377 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/components/group/group-request-list-dialog.vue b/src/views/im/home/components/group/group-request-list-dialog.vue
new file mode 100644
index 0000000..0b4cf6d
--- /dev/null
+++ b/src/views/im/home/components/group/group-request-list-dialog.vue
@@ -0,0 +1,377 @@
+<script lang="ts" setup>
+import type { ImGroupRequestApi } from '#/api/im/group/request'
+
+import { computed, ref, watch } from 'vue'
+
+import { prompt } from '#/packages/effects/common-ui/src'
+
+import { Empty, Input, message, Modal, Spin } from 'ant-design-vue'
+
+import { getGroupRequestListByGroupId } from '#/api/im/group/request'
+import { ImGroupRequestHandleResult } from '#/views/im/utils/constants'
+
+import { useGroupRequestStore } from '../../store/groupRequestStore'
+import { UserAvatar } from '../user'
+
+defineOptions({ name: 'ImGroupRequestListDialog' })
+
+const groupRequestStore = useGroupRequestStore()
+
+const visible = ref(false)
+const groupId = ref<number | undefined>() // 褰撳墠灞曠ず鐨勭兢缂栧彿锛泆ndefined 鏃惰蛋鍏ㄥ眬鏈鐞嗗垪琛紙store.unhandledList锛�
+const loading = ref(false)
+const groupList = ref<ImGroupRequestApi.GroupRequestRespVO[]>([])
+const actingId = ref<null | number>(null)
+
+defineExpose({
+  /** 鎵撳紑杩涚兢鐢宠寮圭獥锛歳eset 鈫� 鐏屽弬 鈫� visible=true锛涗笉浼� groupId 璧板叏灞�鏈鐞嗗垪琛� */
+  open(opts?: { groupId?: number }) {
+    groupId.value = opts?.groupId
+    actingId.value = null
+    visible.value = true
+  }
+})
+
+/** 鏁版嵁婧愶細鍗曠兢妯″紡鐢� fetch 鍥炴潵鐨� groupList锛涘叏灞�妯″紡鐩存帴璇� store.unhandledList锛屽鐞嗗悗 store 鑷姩 reactive 鍚屾 */
+const list = computed<ImGroupRequestApi.GroupRequestRespVO[]>(() =>
+  groupId.value ? groupList.value : groupRequestStore.unhandledList
+)
+
+/** 椤堕儴鍗$墖锛氭渶鏂颁竴鏉★紱绌烘暟缁勬椂涓� null */
+const latest = computed(() => list.value[0] || null)
+/** 鍘嗗彶鍒楄〃锛氶櫎鏈�鏂颁竴鏉″鐨勫叾浣� */
+const histories = computed(() => list.value.slice(1))
+
+/** 鎵撳紑 dialog 鏃舵媺鏁版嵁锛氬崟缇ゆ媺 API锛涘叏灞�鐩存帴璇� store锛涘叧闂椂娓呮帀鍗曠兢缂撳瓨 */
+watch(
+  [visible, groupId],
+  ([isVisible, currentGroupId]) => {
+    if (isVisible && currentGroupId) {
+      void fetchList(currentGroupId)
+    } else if (!isVisible) {
+      groupList.value = []
+    }
+  },
+  { immediate: true }
+)
+
+/**
+ * 鍗曠兢妯″紡涓嬭闃� store 涓綊灞炴湰缇ょ殑鏈鐞嗗垪琛ㄥ彉鍖栵細杩滅浜嬩欢锛圵S 1503 鏂扮敵璇� / 鍏朵粬绠$悊鍛樺鐞嗭級瑙﹀彂鏃� refetch
+ * 鎷挎渶鏂� handleResult锛涙湰绔� agree / refuse 鏈熼棿 actingId 閿佷綇锛岃烦杩囨湰绔姩浣滃紩鍙戠殑 store 鍙樺寲閬垮厤鍐椾綑 RTT
+ *
+ * key 涓嶈兘鍙� join id锛氬鐢ㄦ棫璁板綍鏃跺悓涓� requestId 鐨� applyContent / inviterUserId 浼氬埛鏂颁絾 id 涓嶅彉锛屽繀椤绘妸鍐呭瀛楁涔熺撼鍏ヨЕ鍙�
+ */
+watch(
+  () =>
+    groupId.value && visible.value
+      ? groupRequestStore.unhandledList
+          .filter((request) => request.groupId === groupId.value)
+          .map(
+            (request) =>
+              `${request.id}:${request.inviterUserId ?? ''}:${request.applyContent ?? ''}`
+          )
+          .join(',')
+      : null,
+  (current, previous) => {
+    if (current === null || previous === undefined || current === previous) {
+      return
+    }
+    if (actingId.value !== null) {
+      return
+    }
+    if (groupId.value) {
+      void fetchList(groupId.value)
+    }
+  }
+)
+
+let fetchSeq = 0 // 鍗曡皟閫掑璇锋眰搴忓彿锛涘悓缇や篃浼氬洜涓� WS 1503 鎺ㄩ�佽Е鍙戦澶� fetch锛屼贡搴忚繑鍥炴椂鏃у搷搴斾笉鑳借鐩栨柊鏁版嵁
+async function fetchList(targetGroupId: number) {
+  const seq = ++fetchSeq
+  loading.value = true
+  try {
+    const data = (await getGroupRequestListByGroupId(targetGroupId)) || []
+    // 鏈熼棿鍒囩兢 / 鍏冲脊绐� / 鍙堣Е鍙戞洿鏂� fetch锛氫涪鍝嶅簲
+    if (seq !== fetchSeq || !visible.value || groupId.value !== targetGroupId) {
+      return
+    }
+    groupList.value = data
+  } finally {
+    // 鏃ц姹� finally 鍛戒腑鏃舵柊璇锋眰浠嶅湪璺戯紝璺宠繃閬垮厤鎻愬墠鍏� loading
+    if (seq === fetchSeq) {
+      loading.value = false
+    }
+  }
+}
+
+/** 鍚屾剰锛氳蛋 store 鍚屾鍏ㄥ眬鏈鐞嗗垪琛� + 鏈湴鏇存柊 handleResult 璁╂寜閽彉鐏� */
+async function handleAgree(item: ImGroupRequestApi.GroupRequestRespVO) {
+  if (actingId.value !== null) return
+  actingId.value = item.id
+  try {
+    await groupRequestStore.agreeGroupRequest(item.id)
+    updateLocalResult(item.id, ImGroupRequestHandleResult.AGREED)
+    message.success('宸插悓鎰�')
+  } finally {
+    actingId.value = null
+  }
+}
+
+/** 鎷掔粷锛氬脊鐞嗙敱杈撳叆妗嗭紱涓虹┖鍒欎笉甯� handleContent */
+async function handleRefuse(item: ImGroupRequestApi.GroupRequestRespVO) {
+  if (actingId.value !== null) return
+  let handleContent: string
+  try {
+    const result = await prompt<string>({
+      component: Input,
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ嫆缁濈悊鐢憋紙鍙�夛級'
+      },
+      content: '',
+      modelPropName: 'value',
+      title: '鎷掔粷鐢宠'
+    })
+    handleContent = result || ''
+  } catch {
+    return
+  }
+  actingId.value = item.id
+  try {
+    await groupRequestStore.refuseGroupRequest(item.id, handleContent || undefined)
+    updateLocalResult(item.id, ImGroupRequestHandleResult.REFUSED)
+    message.success('宸叉嫆缁�')
+  } finally {
+    actingId.value = null
+  }
+}
+
+/** 鍗曠兢妯″紡涓嬪鐞嗗悗鏇存柊 groupList 閲岀殑 handleResult锛屾寜閽浆銆屽凡鍚屾剰 / 宸叉嫆缁濄�嶇伆鎬侊紱鍏ㄥ眬妯″紡 store 鐩存帴绉婚櫎璇ラ」鏃犻渶鏇存柊 */
+function updateLocalResult(id: number, handleResult: number) {
+  const target = groupList.value.find((r) => r.id === id)
+  if (target) {
+    target.handleResult = handleResult
+  }
+}
+</script>
+
+<template>
+  <!--
+    缇ゃ�岃繘缇ょ敵璇枫�嶅垪琛ㄥ璇濇
+    - 浠呯兢涓� / 绠$悊鍛樺叆鍙e彲杈撅紱灞曠ず褰撳墠缇や笅鍏ㄩ儴鐢宠锛堝惈宸插鐞嗭級
+    - 椤堕儴鏈�鏂颁竴鏉″崱鐗囧寲绐佸嚭锛堝甫鐢宠鐞嗙敱锛夛紝鍏朵綑鎸� id 鍊掑簭绱у噾鍒楄〃
+    - 鍚屾剰 / 鎷掔粷璧� groupRequestStore 鐨� action锛屽鐞嗗悗鏈湴鏇存柊 handleResult 璁╂寜閽浆鐏版��
+  -->
+  <Modal
+    v-model:open="visible"
+    title="杩涚兢鐢宠"
+    width="560px"
+    :footer="null"
+    :mask-closable="false"
+    class="im-group-request-list__dialog"
+  >
+    <Spin :spinning="loading" wrapper-class-name="w-full">
+      <div class="flex flex-col gap-3 max-h-[60vh] overflow-y-auto pr-1">
+        <!-- 绌烘�� -->
+        <Empty v-if="!loading && list.length === 0" description="鏆傛棤杩涚兢鐢宠" />
+
+      <!-- 椤堕儴鍗$墖锛氭渶鏂颁竴鏉� -->
+        <div
+          v-if="latest"
+          class="flex flex-col gap-2.5 p-3.5 rounded-[10px] border border-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-bg-container)] shadow-[0_1px_3px_rgba(0,0,0,0.04)]"
+        >
+        <div class="flex items-center gap-3">
+          <UserAvatar
+            :url="latest.userAvatar"
+            :name="latest.userNickname"
+            :size="44"
+            :clickable="false"
+          />
+          <div class="flex-1 min-w-0">
+            <div
+              class="truncate text-sm font-medium leading-[1.4] text-[var(--ant-color-text)]"
+            >
+              {{ latest.userNickname || `鐢ㄦ埛 ${latest.userId}` }}
+            </div>
+            <div
+              class="truncate mt-[2px] text-12px leading-[1.5] text-[var(--ant-color-text-secondary)]"
+            >
+              <template v-if="latest.inviterUserId">
+                閫氳繃
+                <span class="text-[var(--ant-color-primary)]">
+                  {{ latest.inviterNickname || `鐢ㄦ埛 ${latest.inviterUserId}` }}
+                </span>
+                鐨勯個璇疯繘缇�
+              </template>
+              <template v-else>鐢宠鍔犲叆</template>
+            </div>
+          </div>
+          <span
+            v-if="latest.handleResult === ImGroupRequestHandleResult.AGREED"
+            class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
+          >
+            宸插悓鎰�
+          </span>
+          <span
+            v-else-if="latest.handleResult === ImGroupRequestHandleResult.REFUSED"
+            class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
+          >
+            宸叉嫆缁�
+          </span>
+          <div v-else class="flex gap-1.5 flex-shrink-0">
+            <button
+              class="im-group-request-list__btn im-group-request-list__btn--primary"
+              :disabled="actingId === latest.id"
+              @click="handleAgree(latest)"
+            >
+              纭
+            </button>
+            <button
+              class="im-group-request-list__btn im-group-request-list__btn--ghost"
+              :disabled="actingId === latest.id"
+              @click="handleRefuse(latest)"
+            >
+              鎷掔粷
+            </button>
+          </div>
+        </div>
+        <!-- 鐢宠鐞嗙敱锛氶個璇峰満鏅樉绀洪個璇蜂汉 + 鐣欒█锛涗富鍔ㄧ敵璇锋樉绀虹敵璇蜂汉 + 鐣欒█ -->
+        <div
+          v-if="latest.applyContent"
+          class="px-3 py-2 rounded-md text-[13px] leading-[1.5] break-all bg-[var(--ant-color-fill-secondary)] text-[var(--ant-color-text)]"
+        >
+          <span class="text-[var(--ant-color-primary)]">
+            {{
+              latest.inviterUserId
+                ? latest.inviterNickname || `鐢ㄦ埛 ${latest.inviterUserId}`
+                : latest.userNickname || `鐢ㄦ埛 ${latest.userId}`
+            }}锛�
+          </span>
+          {{ latest.applyContent }}
+        </div>
+      </div>
+
+      <!-- 鍒嗗壊绾匡細浠呭湪鏈夋洿鏃╃敵璇锋椂鍑虹幇 -->
+        <div
+          v-if="histories.length > 0"
+          class="flex items-center justify-center mt-1.5 -mb-0.5 text-12px text-[var(--ant-color-text-placeholder)]"
+        >
+          <span>浠ヤ笅涓烘洿鏃╃殑鐢宠</span>
+        </div>
+
+        <!-- 鍘嗗彶鐢宠鍒楄〃 -->
+        <div
+          v-for="item in histories"
+          :key="item.id"
+          class="flex flex-col gap-2.5 px-3.5 py-2.5 rounded-[10px] border border-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-bg-container)] shadow-[0_1px_3px_rgba(0,0,0,0.04)]"
+        >
+        <div class="flex items-center gap-3">
+          <UserAvatar
+            :url="item.userAvatar"
+            :name="item.userNickname"
+            :size="40"
+            :clickable="false"
+          />
+          <div class="flex-1 min-w-0">
+            <div
+              class="truncate text-sm font-medium leading-[1.4] text-[var(--ant-color-text)]"
+            >
+              {{ item.userNickname || `鐢ㄦ埛 ${item.userId}` }}
+            </div>
+            <div
+              class="truncate mt-[2px] text-12px leading-[1.5] text-[var(--ant-color-text-secondary)]"
+            >
+              <template v-if="item.inviterUserId">
+                閫氳繃
+                <span class="text-[var(--ant-color-primary)]">
+                  {{ item.inviterNickname || `鐢ㄦ埛 ${item.inviterUserId}` }}
+                </span>
+                鐨勯個璇疯繘缇�
+              </template>
+              <template v-else>鐢宠鍔犲叆</template>
+            </div>
+          </div>
+          <span
+            v-if="item.handleResult === ImGroupRequestHandleResult.AGREED"
+            class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
+          >
+            宸插悓鎰�
+          </span>
+          <span
+            v-else-if="item.handleResult === ImGroupRequestHandleResult.REFUSED"
+            class="flex-shrink-0 text-[13px] text-[var(--ant-color-text-placeholder)]"
+          >
+            宸叉嫆缁�
+          </span>
+          <div v-else class="flex gap-1.5 flex-shrink-0">
+            <button
+              class="im-group-request-list__btn im-group-request-list__btn--primary"
+              :disabled="actingId === item.id"
+              @click="handleAgree(item)"
+            >
+              纭
+            </button>
+            <button
+              class="im-group-request-list__btn im-group-request-list__btn--ghost"
+              :disabled="actingId === item.id"
+              @click="handleRefuse(item)"
+            >
+              鎷掔粷
+            </button>
+          </div>
+        </div>
+        </div>
+      </div>
+    </Spin>
+  </Modal>
+</template>
+
+<style scoped>
+/* 鑷粯鎸夐挳锛氳创杩戝井淇″皬鑽父鏍峰紡锛涗笌 :disabled銆�:hover:not(:disabled) 绛変吉绫诲彔鍔� modifier 绫荤殑缁勫悎閫夋嫨鍣ㄥ啓鍦� class 閲屾垚鏈珮锛岀暀 SCSS */
+.im-group-request-list__btn {
+  flex-shrink: 0;
+  min-width: 56px;
+  height: 28px;
+  padding: 0 12px;
+  font-size: 13px;
+  border-radius: 4px;
+  cursor: pointer;
+  border: 1px solid transparent;
+  transition:
+    background-color 0.15s,
+    border-color 0.15s,
+    color 0.15s;
+}
+.im-group-request-list__btn:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.im-group-request-list__btn--primary {
+  color: #fff;
+  background-color: var(--ant-color-primary);
+  border-color: var(--ant-color-primary);
+}
+.im-group-request-list__btn--primary:hover:not(:disabled) {
+  background-color: var(--ant-color-primary-hover);
+  border-color: var(--ant-color-primary-hover);
+}
+
+.im-group-request-list__btn--ghost {
+  color: var(--ant-color-text);
+  background-color: var(--ant-color-bg-container);
+  border-color: var(--ant-color-border);
+}
+.im-group-request-list__btn--ghost:hover:not(:disabled) {
+  color: var(--ant-color-primary);
+  border-color: var(--ant-color-primary);
+}
+</style>
+
+<style>
+/* el-dialog 鍐呴儴 body 閫氳繃 teleport 娓叉煋鍒� body锛宻coped 閫変笉鍒帮紝鐣欓潪 scoped 鍏ㄥ眬瑕嗙洊 */
+.im-group-request-list__dialog .el-dialog__body {
+  padding: 12px 20px 8px;
+  background-color: var(--ant-color-fill-secondary);
+}
+</style>

--
Gitblit v1.9.3