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/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