feat(im): 增加群管理的完善

im
YunaiV 2026-05-01 08:19:13 +08:00
parent 8564788b11
commit 7ed6fa5579
5 changed files with 214 additions and 125 deletions

View File

@ -20,7 +20,12 @@ export interface ImManagerGroupMemberVO {
userId: number
nickname?: string
avatar?: string
displayUserName?: string
displayGroupName?: string
muted?: boolean
status: number
joinTime?: Date
quitTime?: Date
}
// 获得群分页
@ -43,7 +48,7 @@ export const unbanManagerGroup = (id: number) => {
return request.put({ url: '/im/manager/group/unban?id=' + id })
}
// 获得群成员列表
// 获得群成员列表(含已退群成员,由前端按需过滤)
export const getManagerGroupMemberList = (groupId: number) => {
return request.get({ url: '/im/manager/group/member/list?groupId=' + groupId })
}

View File

@ -0,0 +1,63 @@
<template>
<el-dialog v-model="dialogVisible" title="封禁群" width="500" :close-on-click-modal="false">
<el-form :model="formData" :rules="formRules" ref="formRef" label-width="80px">
<el-form-item label="群名称">
<span>{{ formData.groupName }}</span>
</el-form-item>
<el-form-item label="封禁原因" prop="reason">
<el-input
v-model="formData.reason"
type="textarea"
:rows="3"
maxlength="200"
show-word-limit
placeholder="请输入封禁原因"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :loading="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import * as ManagerGroupApi from '@/api/im/manager/group'
defineOptions({ name: 'ImGroupBanForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = reactive({ id: 0, groupName: '', reason: '' }) //
const formRef = ref() // Ref
const formRules = {
reason: [{ required: true, message: '封禁原因不能为空', trigger: 'blur' }]
}
/** 打开弹窗 */
const open = (row: ManagerGroupApi.ImManagerGroupVO) => {
formData.id = row.id
formData.groupName = row.name
formData.reason = ''
dialogVisible.value = true
}
defineExpose({ open })
/** 提交封禁 */
const emit = defineEmits(['success']) //
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
await ManagerGroupApi.banManagerGroup({ id: formData.id, reason: formData.reason })
message.success('封禁成功')
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<el-drawer v-model="drawerVisible" title="群详情" size="900px" :destroy-on-close="true">
<!-- 群基本信息 -->
<el-descriptions :column="2" border>
<el-descriptions-item label="群编号">{{ detail.id }}</el-descriptions-item>
<el-descriptions-item label="群名称">{{ detail.name }}</el-descriptions-item>
<el-descriptions-item label="头像">
<el-avatar :src="detail.avatar" :size="36">
{{ detail.name?.charAt(0) ?? '?' }}
</el-avatar>
</el-descriptions-item>
<el-descriptions-item label="群主">
{{ detail.ownerNickname || '-' }} ({{ detail.ownerUserId }})
</el-descriptions-item>
<el-descriptions-item label="成员数">{{ detail.memberCount ?? 0 }}</el-descriptions-item>
<el-descriptions-item label="群状态">
<dict-tag :type="DICT_TYPE.IM_GROUP_STATUS" :value="detail.status" />
</el-descriptions-item>
<el-descriptions-item label="封禁状态" :span="2">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detail.banned" />
<span v-if="detail.banned" class="ml-5px text-gray-400">{{ detail.bannedReason }}</span>
</el-descriptions-item>
<el-descriptions-item label="群公告" :span="2">{{ detail.notice || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ formatDate(detail.createTime) }}
</el-descriptions-item>
</el-descriptions>
<!-- 群成员列表 -->
<div class="mt-20px mb-15px flex items-center justify-between">
<span class="font-bold">群成员</span>
<el-checkbox v-model="activeOnly"></el-checkbox>
</div>
<el-table v-loading="loading" :data="filteredMembers" border>
<el-table-column label="头像" width="80" align="center">
<template #default="{ row }">
<el-avatar :src="row.avatar" :size="40">
{{ row.nickname?.charAt(0) ?? '?' }}
</el-avatar>
</template>
</el-table-column>
<el-table-column label="用户编号" prop="userId" width="100" align="center" />
<el-table-column label="昵称" prop="nickname" min-width="120" show-overflow-tooltip />
<el-table-column
label="组内显示名"
prop="displayUserName"
min-width="120"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.displayUserName || '-' }}</template>
</el-table-column>
<el-table-column
label="群显示备注"
prop="displayGroupName"
min-width="120"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.displayGroupName || '-' }}</template>
</el-table-column>
<el-table-column label="免打扰" prop="muted" width="80" align="center">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="row.muted" />
</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="100" align="center">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
</el-table-column>
<el-table-column
label="入群时间"
prop="joinTime"
width="170"
align="center"
:formatter="dateFormatter"
/>
<el-table-column
label="退群时间"
prop="quitTime"
width="170"
align="center"
:formatter="dateFormatter"
/>
</el-table>
</el-drawer>
</template>
<script lang="ts" setup>
import { dateFormatter, formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as ManagerGroupApi from '@/api/im/manager/group'
defineOptions({ name: 'ImGroupDetail' })
const drawerVisible = ref(false) //
const detail = ref<ManagerGroupApi.ImManagerGroupVO>(
{} as ManagerGroupApi.ImManagerGroupVO
) //
const loading = ref(false) //
const memberList = ref<ManagerGroupApi.ImManagerGroupMemberVO[]>([]) // 退
const activeOnly = ref(true) //
/** 当前展示的群成员(按 activeOnly 前端过滤) */
const filteredMembers = computed(() =>
activeOnly.value
? memberList.value.filter((m) => m.status === CommonStatusEnum.ENABLE)
: memberList.value
)
/** 打开详情,加载群成员 */
const open = async (row: ManagerGroupApi.ImManagerGroupVO) => {
detail.value = row
drawerVisible.value = true
activeOnly.value = true
loading.value = true
try {
memberList.value = await ManagerGroupApi.getManagerGroupMemberList(row.id)
} finally {
loading.value = false
}
}
defineExpose({ open })
</script>

View File

@ -1,51 +0,0 @@
<!-- TODO @AI git mv /Users/yunai/Java/yudao-all-in-im/yudao-ui-admin-vue3/src/views/im/manager/group -->
<!-- TODO @AI需要增加状态然后有个筛选仅展示当前群内的成员和不选中就是全部 -->
<!-- TODO @AIdisplayUserNamedisplayGroupNamemuted群成员状态退群时间需要也展示 -->
<template>
<el-drawer v-model="drawerVisible" :title="drawerTitle" size="600px" :destroy-on-close="true">
<el-table v-loading="loading" :data="memberList" border>
<el-table-column label="头像" width="80" align="center">
<template #default="{ row }">
<el-avatar :src="row.avatar" :size="40">
{{ row.nickname?.charAt(0) ?? '?' }}
</el-avatar>
</template>
</el-table-column>
<el-table-column label="用户编号" prop="userId" width="120" align="center" />
<el-table-column label="昵称" prop="nickname" min-width="120" />
<el-table-column
label="入群时间"
prop="joinTime"
width="180"
align="center"
:formatter="dateFormatter"
/>
</el-table>
</el-drawer>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as ManagerGroupApi from '@/api/im/manager/group'
defineOptions({ name: 'ImGroupMemberDrawer' })
const drawerVisible = ref(false) //
const drawerTitle = ref('群成员列表') //
const loading = ref(false) //
const memberList = ref<ManagerGroupApi.ImManagerGroupMemberVO[]>([]) //
/** 打开抽屉,加载指定群的成员 */
const open = async (groupId: number, groupName?: string) => {
drawerVisible.value = true
drawerTitle.value = groupName ? `群成员 - ${groupName}` : '群成员列表'
loading.value = true
try {
memberList.value = await ManagerGroupApi.getManagerGroupMemberList(groupId)
} finally {
loading.value = false
}
}
defineExpose({ open })
</script>

View File

@ -125,10 +125,10 @@
<el-button
link
type="primary"
@click="openMemberDrawer(row)"
@click="openDetail(row)"
v-hasPermi="['im:manager:group:query']"
>
成员
详情
</el-button>
<el-button
v-if="!row.banned"
@ -159,45 +159,22 @@
/>
</ContentWrap>
<!-- 群成员抽屉 -->
<!-- TODO @AIDrawerRef 简化下 -->
<GroupMemberDrawer ref="memberDrawerRef" />
<!-- 封禁原因弹窗 -->
<!-- TODO @AI独立出来 -->
<el-dialog v-model="banDialogVisible" title="封禁群" width="500" :close-on-click-modal="false">
<el-form :model="banForm" :rules="banFormRules" ref="banFormRef" label-width="80px">
<el-form-item label="群名称">
<span>{{ banForm.groupName }}</span>
</el-form-item>
<el-form-item label="封禁原因" prop="reason">
<el-input
v-model="banForm.reason"
type="textarea"
:rows="3"
maxlength="200"
show-word-limit
placeholder="请输入封禁原因"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :loading="banSubmitting" type="primary" @click="submitBan"> </el-button>
<el-button @click="banDialogVisible = false"> </el-button>
</template>
</el-dialog>
<!-- 群详情 -->
<GroupDetail ref="detailRef" />
<!-- 群封禁因弹窗 -->
<GroupBanForm ref="banFormRef" @success="getList" />
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as ManagerGroupApi from '@/api/im/manager/group'
import GroupMemberDrawer from './components/GroupMemberDrawer.vue'
import GroupDetail from './GroupDetail.vue'
import GroupBanForm from './GroupBanForm.vue'
defineOptions({ name: 'ImGroup' })
const message = useMessage() //
const { t } = useI18n() // TODO @AI linter
const loading = ref(true) //
const total = ref(0) //
@ -237,59 +214,29 @@ const resetQuery = () => {
handleQuery()
}
const memberDrawerRef = ref<InstanceType<typeof GroupMemberDrawer>>() // Ref
/** 打开群成员抽屉 */
const openMemberDrawer = (row: ManagerGroupApi.ImManagerGroupVO) => {
memberDrawerRef.value?.open(row.id, row.name)
}
const banDialogVisible = ref(false) //
const banSubmitting = ref(false) //
const banForm = reactive({ id: 0, groupName: '', reason: '' }) //
const banFormRef = ref() // Ref
const banFormRules = {
reason: [{ required: true, message: '封禁原因不能为空', trigger: 'blur' }]
/** 打开群详情 */
const detailRef = ref()
const openDetail = (row: ManagerGroupApi.ImManagerGroupVO) => {
detailRef.value.open(row)
}
/** 打开封禁弹窗 */
const banFormRef = ref()
const openBanDialog = (row: ManagerGroupApi.ImManagerGroupVO) => {
banForm.id = row.id
banForm.groupName = row.name
banForm.reason = ''
banDialogVisible.value = true
}
/** 提交封禁 */
const submitBan = async () => {
await banFormRef.value.validate()
banSubmitting.value = true
try {
//
await ManagerGroupApi.banManagerGroup({ id: banForm.id, reason: banForm.reason })
message.success('封禁成功')
banDialogVisible.value = false
//
await getList()
} finally {
banSubmitting.value = false
}
banFormRef.value.open(row)
}
/** 解封按钮操作 */
const handleUnban = async (row: ManagerGroupApi.ImManagerGroupVO) => {
// TODO @AI system user info manager
try {
//
await message.confirm(`确认解封群「${row.name}」吗?`)
} catch {
return
}
//
await ManagerGroupApi.unbanManagerGroup(row.id)
message.success('解封成功')
//
await getList()
//
await ManagerGroupApi.unbanManagerGroup(row.id)
message.success('解封成功')
//
await getList()
} catch {}
}
/** 初始化 */