feat(im): align tenant admin im management
这个提交包含在:
父节点
c84a27f4ec
当前提交
de89297457
@ -177,6 +177,18 @@ export const imAdminApi = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateUser(
|
||||||
|
appId: string,
|
||||||
|
userId: string,
|
||||||
|
form: { nickname?: string; avatar?: string; gender?: string; status?: string },
|
||||||
|
) {
|
||||||
|
return imClient.put<{ data: ImUser }>(
|
||||||
|
`/api/im/admin/users/${encodeURIComponent(userId)}`,
|
||||||
|
form,
|
||||||
|
{ params: { appId } },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
updateWebhook(appId: string, webhookId: string, form: WebhookConfigForm) {
|
updateWebhook(appId: string, webhookId: string, form: WebhookConfigForm) {
|
||||||
return imClient.put<{ data: WebhookConfig }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}`, form, {
|
return imClient.put<{ data: WebhookConfig }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}`, form, {
|
||||||
params: { appId },
|
params: { appId },
|
||||||
@ -301,18 +313,56 @@ export const imAdminApi = {
|
|||||||
return imClient.delete<{ data: null }>(`/api/im/admin/groups/${encodeURIComponent(groupId)}`, { params: { appId } })
|
return imClient.delete<{ data: null }>(`/api/im/admin/groups/${encodeURIComponent(groupId)}`, { params: { appId } })
|
||||||
},
|
},
|
||||||
|
|
||||||
registerUser(appId: string, userId: string, nickname?: string, avatar?: string) {
|
registerUser(
|
||||||
|
appId: string,
|
||||||
|
userId: string,
|
||||||
|
nickname?: string,
|
||||||
|
avatar?: string,
|
||||||
|
gender?: string,
|
||||||
|
status?: string,
|
||||||
|
) {
|
||||||
return imClient.post<{ data: ImUser }>(
|
return imClient.post<{ data: ImUser }>(
|
||||||
'/api/im/admin/users',
|
'/api/im/admin/users',
|
||||||
{ userId, nickname, avatar },
|
{
|
||||||
|
userId,
|
||||||
|
nickname,
|
||||||
|
avatar,
|
||||||
|
...(gender ? { gender } : {}),
|
||||||
|
...(status ? { status } : {}),
|
||||||
|
},
|
||||||
{ params: { appId } },
|
{ params: { appId } },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
createGroup(appId: string, name: string, creatorId: string, memberIds: string[]) {
|
createGroup(
|
||||||
|
appId: string,
|
||||||
|
name: string,
|
||||||
|
creatorId: string,
|
||||||
|
memberIds: string[],
|
||||||
|
groupType?: string,
|
||||||
|
announcement?: string,
|
||||||
|
) {
|
||||||
return imClient.post<{ data: ImGroup }>(
|
return imClient.post<{ data: ImGroup }>(
|
||||||
'/api/im/admin/groups',
|
'/api/im/admin/groups',
|
||||||
{ name, creatorId, memberIds },
|
{
|
||||||
|
name,
|
||||||
|
creatorId,
|
||||||
|
memberIds,
|
||||||
|
...(groupType ? { groupType } : {}),
|
||||||
|
...(announcement ? { announcement } : {}),
|
||||||
|
},
|
||||||
|
{ params: { appId } },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateGroup(
|
||||||
|
appId: string,
|
||||||
|
groupId: string,
|
||||||
|
form: { name?: string; groupType?: string; announcement?: string },
|
||||||
|
) {
|
||||||
|
return imClient.put<{ data: ImGroup }>(
|
||||||
|
`/api/im/admin/groups/${encodeURIComponent(groupId)}`,
|
||||||
|
form,
|
||||||
{ params: { appId } },
|
{ params: { appId } },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -324,6 +374,32 @@ export const imAdminApi = {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
searchMessages(
|
||||||
|
appId: string,
|
||||||
|
filters: {
|
||||||
|
keyword?: string
|
||||||
|
chatType?: string
|
||||||
|
msgType?: string
|
||||||
|
startTime?: string
|
||||||
|
endTime?: string
|
||||||
|
page?: number
|
||||||
|
size?: number
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return imClient.get<{ data: PagedResult<ImMessage> }>('/api/im/admin/messages/search', {
|
||||||
|
params: {
|
||||||
|
appId,
|
||||||
|
...(filters.keyword ? { keyword: filters.keyword } : {}),
|
||||||
|
...(filters.chatType ? { chatType: filters.chatType } : {}),
|
||||||
|
...(filters.msgType ? { msgType: filters.msgType } : {}),
|
||||||
|
...(filters.startTime ? { startTime: filters.startTime } : {}),
|
||||||
|
...(filters.endTime ? { endTime: filters.endTime } : {}),
|
||||||
|
page: filters.page ?? 0,
|
||||||
|
size: filters.size ?? 20,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
getProfile(appId: string, userId: string) {
|
getProfile(appId: string, userId: string) {
|
||||||
return imClient.get<{ data: ImProfile }>(
|
return imClient.get<{ data: ImProfile }>(
|
||||||
`/api/im/accounts/${encodeURIComponent(userId)}`,
|
`/api/im/accounts/${encodeURIComponent(userId)}`,
|
||||||
|
|||||||
@ -17,12 +17,19 @@
|
|||||||
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
|
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
|
||||||
<el-tab-pane label="注册用户" name="users">
|
<el-tab-pane label="注册用户" name="users">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<el-button type="primary" @click="showRegisterUser = true">注册用户</el-button>
|
<el-button type="primary" @click="openCreateUserDialog">新增用户</el-button>
|
||||||
<el-button @click="loadUsers" :loading="loadingUsers">刷新</el-button>
|
<el-button @click="loadUsers" :loading="loadingUsers">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="users" v-loading="loadingUsers" border stripe>
|
<el-table :data="users" v-loading="loadingUsers" border stripe>
|
||||||
<el-table-column prop="userId" label="用户ID" width="180" />
|
<el-table-column prop="userId" label="用户ID" width="180" />
|
||||||
|
<el-table-column label="头像" width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-avatar :size="32" :src="row.avatar || undefined">
|
||||||
|
{{ userAvatarFallback(row) }}
|
||||||
|
</el-avatar>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="nickname" label="昵称" width="140" />
|
<el-table-column prop="nickname" label="昵称" width="140" />
|
||||||
<el-table-column prop="gender" label="性别" width="90">
|
<el-table-column prop="gender" label="性别" width="90">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@ -41,8 +48,11 @@
|
|||||||
{{ formatTime(row.createdAt) }}
|
{{ formatTime(row.createdAt) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="140" fixed="right">
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<el-button link type="primary" size="small" @click="openEditUserDialog(row)">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
:type="row.status === 'ACTIVE' ? 'danger' : 'success'"
|
:type="row.status === 'ACTIVE' ? 'danger' : 'success'"
|
||||||
@ -68,6 +78,7 @@
|
|||||||
|
|
||||||
<el-tab-pane label="群组列表" name="groups">
|
<el-tab-pane label="群组列表" name="groups">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
|
<el-button type="primary" @click="openCreateGroupDialog">创建群组</el-button>
|
||||||
<el-button @click="loadGroups" :loading="loadingGroups">刷新</el-button>
|
<el-button @click="loadGroups" :loading="loadingGroups">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -90,8 +101,11 @@
|
|||||||
<el-table-column prop="createdAt" label="创建时间" width="180">
|
<el-table-column prop="createdAt" label="创建时间" width="180">
|
||||||
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="120" fixed="right">
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<el-button link type="primary" size="small" @click="openEditGroupDialog(row)">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
<el-button link type="danger" size="small" @click="dismissGroup(row)">
|
<el-button link type="danger" size="small" @click="dismissGroup(row)">
|
||||||
解散
|
解散
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -103,13 +117,13 @@
|
|||||||
<el-tab-pane label="消息历史" name="messages">
|
<el-tab-pane label="消息历史" name="messages">
|
||||||
<el-form :inline="true" :model="historyForm" class="toolbar" label-width="0">
|
<el-form :inline="true" :model="historyForm" class="toolbar" label-width="0">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-input v-model="historyForm.userA" placeholder="用户A" clearable style="width:160px" />
|
<el-input v-model="historyForm.keyword" placeholder="关键词" clearable style="width:200px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-input v-model="historyForm.userB" placeholder="用户B" clearable style="width:160px" />
|
<el-select v-model="historyForm.chatType" placeholder="会话类型" clearable style="width:120px">
|
||||||
</el-form-item>
|
<el-option label="单聊" value="SINGLE" />
|
||||||
<el-form-item>
|
<el-option label="群聊" value="GROUP" />
|
||||||
<el-input v-model="historyForm.keyword" placeholder="关键词" clearable style="width:180px" />
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-select v-model="historyForm.msgType" placeholder="消息类型" clearable style="width:140px">
|
<el-select v-model="historyForm.msgType" placeholder="消息类型" clearable style="width:140px">
|
||||||
@ -129,7 +143,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" :loading="loadingMessages" @click="searchMessages">
|
<el-button type="primary" :loading="loadingMessages" @click="searchMessages">
|
||||||
查询
|
搜索
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@ -141,9 +155,9 @@
|
|||||||
<el-table-column prop="createdAt" label="时间" width="180">
|
<el-table-column prop="createdAt" label="时间" width="180">
|
||||||
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="chatType" label="会话" width="90" />
|
||||||
<el-table-column prop="fromUserId" label="发送者" width="160" />
|
<el-table-column prop="fromUserId" label="发送者" width="160" />
|
||||||
<el-table-column prop="toId" label="会话ID" width="180" />
|
<el-table-column prop="toId" label="会话ID" width="180" />
|
||||||
<el-table-column prop="chatType" label="类型" width="90" />
|
|
||||||
<el-table-column prop="msgType" label="消息类型" width="110" />
|
<el-table-column prop="msgType" label="消息类型" width="110" />
|
||||||
<el-table-column prop="status" label="状态" width="100" />
|
<el-table-column prop="status" label="状态" width="100" />
|
||||||
<el-table-column prop="content" label="内容" min-width="260">
|
<el-table-column prop="content" label="内容" min-width="260">
|
||||||
@ -171,89 +185,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane label="好友申请" name="friend-requests">
|
|
||||||
<div class="toolbar toolbar-space-between">
|
|
||||||
<div class="toolbar-group">
|
|
||||||
<el-select v-model="friendRequestDirection" style="width:140px" @change="loadFriendRequests">
|
|
||||||
<el-option label="收到的申请" value="incoming" />
|
|
||||||
<el-option label="发出的申请" value="outgoing" />
|
|
||||||
</el-select>
|
|
||||||
<el-button @click="loadFriendRequests" :loading="loadingFriendRequests">刷新</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button type="primary" @click="showSendFriendRequest = true">发送好友申请</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-table :data="friendRequests" v-loading="loadingFriendRequests" border stripe>
|
|
||||||
<el-table-column prop="createdAt" label="时间" width="180">
|
|
||||||
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="fromUserId" label="申请人" width="160" />
|
|
||||||
<el-table-column prop="toUserId" label="接收人" width="160" />
|
|
||||||
<el-table-column prop="remark" label="申请信息" min-width="220" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="status" label="状态" width="110">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag :type="friendRequestTagType(row.status)" size="small">{{ friendRequestStatusLabel(row.status) }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="reviewedAt" label="处理时间" width="180">
|
|
||||||
<template #default="{ row }">{{ formatTime(row.reviewedAt) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="180" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<template v-if="friendRequestDirection === 'incoming' && row.status === 'PENDING'">
|
|
||||||
<el-button link type="success" size="small" @click="approveFriend(row)">同意</el-button>
|
|
||||||
<el-button link type="danger" size="small" @click="declineFriend(row)">拒绝</el-button>
|
|
||||||
</template>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
<el-tab-pane label="入群申请" name="group-requests">
|
|
||||||
<div class="toolbar toolbar-space-between">
|
|
||||||
<div class="toolbar-group">
|
|
||||||
<el-input v-model="groupRequestGroupId" placeholder="群组ID" clearable style="width:260px" />
|
|
||||||
<el-button @click="loadGroupJoinRequests" :loading="loadingGroupRequests">刷新</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button type="primary" @click="showSendGroupJoinRequest = true">发起入群申请</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-alert
|
|
||||||
v-if="!groupRequestGroupId.trim()"
|
|
||||||
type="info"
|
|
||||||
show-icon
|
|
||||||
title="请输入群组ID后刷新,才能查看该群的入群申请。"
|
|
||||||
style="margin-bottom:12px"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-table :data="groupJoinRequests" v-loading="loadingGroupRequests" border stripe>
|
|
||||||
<el-table-column prop="createdAt" label="时间" width="180">
|
|
||||||
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="requesterId" label="申请人" width="160" />
|
|
||||||
<el-table-column prop="groupId" label="群组ID" width="180" />
|
|
||||||
<el-table-column prop="remark" label="申请信息" min-width="220" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="status" label="状态" width="110">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag :type="groupRequestTagType(row.status)" size="small">{{ groupRequestStatusLabel(row.status) }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="reviewedAt" label="处理时间" width="180">
|
|
||||||
<template #default="{ row }">{{ formatTime(row.reviewedAt) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="180" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<template v-if="row.status === 'PENDING'">
|
|
||||||
<el-button link type="success" size="small" @click="approveGroupJoin(row)">同意</el-button>
|
|
||||||
<el-button link type="danger" size="small" @click="declineGroupJoin(row)">拒绝</el-button>
|
|
||||||
</template>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
<el-tab-pane label="操作日志" name="logs">
|
<el-tab-pane label="操作日志" name="logs">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<el-button @click="loadOperationLogs" :loading="loadingLogs">刷新</el-button>
|
<el-button @click="loadOperationLogs" :loading="loadingLogs">刷新</el-button>
|
||||||
@ -264,10 +195,20 @@
|
|||||||
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="operatorId" label="操作者" width="160" />
|
<el-table-column prop="operatorId" label="操作者" width="160" />
|
||||||
<el-table-column prop="action" label="动作" width="180" />
|
<el-table-column prop="action" label="动作" width="180">
|
||||||
<el-table-column prop="resourceType" label="资源类型" width="120" />
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" effect="plain">{{ logActionLabel(row.action) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="resourceType" label="资源类型" width="120">
|
||||||
|
<template #default="{ row }">{{ logResourceLabel(row.resourceType) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="resourceId" label="资源ID" width="220" show-overflow-tooltip />
|
<el-table-column prop="resourceId" label="资源ID" width="220" show-overflow-tooltip />
|
||||||
<el-table-column prop="detail" label="详情" min-width="220" show-overflow-tooltip />
|
<el-table-column prop="detail" label="详情" min-width="320">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="log-detail">{{ formatLogDetail(row.detail) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@ -315,22 +256,40 @@
|
|||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-dialog v-model="showRegisterUser" title="注册用户" width="420px">
|
<el-dialog v-model="showRegisterUser" :title="editingUserId ? '编辑用户' : '注册用户'" width="520px" @closed="resetUserForm">
|
||||||
<el-form :model="registerForm" label-width="72px">
|
<el-form :model="registerForm" label-width="84px">
|
||||||
<el-form-item label="用户ID">
|
<el-form-item label="用户ID">
|
||||||
<el-input v-model="registerForm.userId" placeholder="全局唯一标识" />
|
<el-input v-model="registerForm.userId" placeholder="全局唯一标识" :disabled="!!editingUserId" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="昵称">
|
<el-form-item label="昵称">
|
||||||
<el-input v-model="registerForm.nickname" />
|
<el-input v-model="registerForm.nickname" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="头像">
|
||||||
|
<el-input v-model="registerForm.avatar" placeholder="头像 URL" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="性别">
|
||||||
|
<el-select v-model="registerForm.gender" style="width:100%">
|
||||||
|
<el-option label="未知" value="UNKNOWN" />
|
||||||
|
<el-option label="男" value="MALE" />
|
||||||
|
<el-option label="女" value="FEMALE" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="registerForm.status" style="width:100%">
|
||||||
|
<el-option label="正常" value="ACTIVE" />
|
||||||
|
<el-option label="封禁" value="BANNED" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="showRegisterUser = false">取消</el-button>
|
<el-button @click="showRegisterUser = false">取消</el-button>
|
||||||
<el-button type="primary" :loading="submittingRegister" @click="submitRegisterUser">注册</el-button>
|
<el-button type="primary" :loading="submittingRegister" @click="submitRegisterUser">
|
||||||
|
{{ editingUserId ? '保存' : '注册' }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="showCreateGroup" title="创建群组" width="520px" @closed="resetCreateGroupForm">
|
<el-dialog v-model="showCreateGroup" title="创建群组" width="600px" @closed="resetCreateGroupForm">
|
||||||
<el-form :model="createGroupForm" label-width="90px">
|
<el-form :model="createGroupForm" label-width="90px">
|
||||||
<el-form-item label="群名称">
|
<el-form-item label="群名称">
|
||||||
<el-input v-model="createGroupForm.name" />
|
<el-input v-model="createGroupForm.name" />
|
||||||
@ -338,6 +297,15 @@
|
|||||||
<el-form-item label="创建者ID">
|
<el-form-item label="创建者ID">
|
||||||
<el-input v-model="createGroupForm.creatorId" />
|
<el-input v-model="createGroupForm.creatorId" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="群类型">
|
||||||
|
<el-select v-model="createGroupForm.groupType" style="width:100%">
|
||||||
|
<el-option label="工作群" value="WORK" />
|
||||||
|
<el-option label="公开群" value="PUBLIC" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="公告">
|
||||||
|
<el-input v-model="createGroupForm.announcement" type="textarea" :rows="3" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="搜索成员">
|
<el-form-item label="搜索成员">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="memberSearchKeyword"
|
v-model="memberSearchKeyword"
|
||||||
@ -386,33 +354,27 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="showSendFriendRequest" title="发送好友申请" width="420px" @closed="resetSendFriendRequestForm">
|
<el-dialog v-model="showEditGroup" title="编辑群组" width="520px" @closed="resetEditGroupForm">
|
||||||
<el-form :model="sendFriendRequestForm" label-width="88px">
|
<el-form :model="editGroupForm" label-width="90px">
|
||||||
<el-form-item label="接收人ID">
|
|
||||||
<el-input v-model="sendFriendRequestForm.toUserId" placeholder="对方用户ID" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="申请信息">
|
|
||||||
<el-input v-model="sendFriendRequestForm.remark" type="textarea" :rows="3" placeholder="例如:我是 XXX,想和你加好友" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="showSendFriendRequest = false">取消</el-button>
|
|
||||||
<el-button type="primary" :loading="submittingSendFriendRequest" @click="submitSendFriendRequest">发送</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-dialog v-model="showSendGroupJoinRequest" title="发起入群申请" width="420px" @closed="resetSendGroupJoinRequestForm">
|
|
||||||
<el-form :model="sendGroupJoinRequestForm" label-width="88px">
|
|
||||||
<el-form-item label="群组ID">
|
<el-form-item label="群组ID">
|
||||||
<el-input v-model="sendGroupJoinRequestForm.groupId" placeholder="目标群组ID" />
|
<el-input :model-value="editingGroup?.id ?? ''" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="申请信息">
|
<el-form-item label="群名称">
|
||||||
<el-input v-model="sendGroupJoinRequestForm.remark" type="textarea" :rows="3" placeholder="例如:希望加入此群进行沟通" />
|
<el-input v-model="editGroupForm.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="群类型">
|
||||||
|
<el-select v-model="editGroupForm.groupType" style="width:100%">
|
||||||
|
<el-option label="工作群" value="WORK" />
|
||||||
|
<el-option label="公开群" value="PUBLIC" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="公告">
|
||||||
|
<el-input v-model="editGroupForm.announcement" type="textarea" :rows="4" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="showSendGroupJoinRequest = false">取消</el-button>
|
<el-button @click="showEditGroup = false">取消</el-button>
|
||||||
<el-button type="primary" :loading="submittingSendGroupJoinRequest" @click="submitSendGroupJoinRequest">发送</el-button>
|
<el-button type="primary" :loading="submittingEditGroup" @click="submitEditGroup">保存</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@ -518,20 +480,40 @@ const submittingSendGroupJoinRequest = ref(false)
|
|||||||
const sendGroupJoinRequestForm = ref({ groupId: '', remark: '' })
|
const sendGroupJoinRequestForm = ref({ groupId: '', remark: '' })
|
||||||
|
|
||||||
const historyForm = ref({
|
const historyForm = ref({
|
||||||
userA: '',
|
|
||||||
userB: '',
|
|
||||||
keyword: '',
|
keyword: '',
|
||||||
|
chatType: '',
|
||||||
msgType: '',
|
msgType: '',
|
||||||
timeRange: [] as string[] | null,
|
timeRange: [] as string[] | null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const showRegisterUser = ref(false)
|
const showRegisterUser = ref(false)
|
||||||
const submittingRegister = ref(false)
|
const submittingRegister = ref(false)
|
||||||
const registerForm = ref({ userId: '', nickname: '' })
|
const editingUserId = ref<string | null>(null)
|
||||||
|
const registerForm = ref({
|
||||||
|
userId: '',
|
||||||
|
nickname: '',
|
||||||
|
avatar: '',
|
||||||
|
gender: 'UNKNOWN' as 'UNKNOWN' | 'MALE' | 'FEMALE',
|
||||||
|
status: 'ACTIVE' as 'ACTIVE' | 'BANNED',
|
||||||
|
})
|
||||||
|
|
||||||
const showCreateGroup = ref(false)
|
const showCreateGroup = ref(false)
|
||||||
const submittingCreateGroup = ref(false)
|
const submittingCreateGroup = ref(false)
|
||||||
const createGroupForm = ref({ name: '', creatorId: '' })
|
const createGroupForm = ref({
|
||||||
|
name: '',
|
||||||
|
creatorId: '',
|
||||||
|
groupType: 'WORK' as 'WORK' | 'PUBLIC',
|
||||||
|
announcement: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const showEditGroup = ref(false)
|
||||||
|
const submittingEditGroup = ref(false)
|
||||||
|
const editingGroup = ref<ImGroup | null>(null)
|
||||||
|
const editGroupForm = ref({
|
||||||
|
name: '',
|
||||||
|
groupType: 'WORK' as 'WORK' | 'PUBLIC',
|
||||||
|
announcement: '',
|
||||||
|
})
|
||||||
|
|
||||||
const memberSearchKeyword = ref('')
|
const memberSearchKeyword = ref('')
|
||||||
const memberSearchResults = ref<ImUser[]>([])
|
const memberSearchResults = ref<ImUser[]>([])
|
||||||
@ -622,12 +604,71 @@ function removeMember(userId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetCreateGroupForm() {
|
function resetCreateGroupForm() {
|
||||||
createGroupForm.value = { name: '', creatorId: '' }
|
createGroupForm.value = { name: '', creatorId: '', groupType: 'WORK', announcement: '' }
|
||||||
memberSearchKeyword.value = ''
|
memberSearchKeyword.value = ''
|
||||||
memberSearchResults.value = []
|
memberSearchResults.value = []
|
||||||
selectedMembers.value = []
|
selectedMembers.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openCreateUserDialog() {
|
||||||
|
editingUserId.value = null
|
||||||
|
registerForm.value = {
|
||||||
|
userId: '',
|
||||||
|
nickname: '',
|
||||||
|
avatar: '',
|
||||||
|
gender: 'UNKNOWN',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
}
|
||||||
|
showRegisterUser.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditUserDialog(user: ImUser) {
|
||||||
|
editingUserId.value = user.userId
|
||||||
|
registerForm.value = {
|
||||||
|
userId: user.userId,
|
||||||
|
nickname: user.nickname ?? '',
|
||||||
|
avatar: user.avatar ?? '',
|
||||||
|
gender: user.gender ?? 'UNKNOWN',
|
||||||
|
status: user.status ?? 'ACTIVE',
|
||||||
|
}
|
||||||
|
showRegisterUser.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetUserForm() {
|
||||||
|
editingUserId.value = null
|
||||||
|
registerForm.value = {
|
||||||
|
userId: '',
|
||||||
|
nickname: '',
|
||||||
|
avatar: '',
|
||||||
|
gender: 'UNKNOWN',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreateGroupDialog() {
|
||||||
|
resetCreateGroupForm()
|
||||||
|
showCreateGroup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditGroupDialog(group: ImGroup) {
|
||||||
|
editingGroup.value = group
|
||||||
|
editGroupForm.value = {
|
||||||
|
name: group.name,
|
||||||
|
groupType: (group.groupType || 'WORK') as 'WORK' | 'PUBLIC',
|
||||||
|
announcement: group.announcement ?? '',
|
||||||
|
}
|
||||||
|
showEditGroup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetEditGroupForm() {
|
||||||
|
editingGroup.value = null
|
||||||
|
editGroupForm.value = {
|
||||||
|
name: '',
|
||||||
|
groupType: 'WORK',
|
||||||
|
announcement: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resetSendFriendRequestForm() {
|
function resetSendFriendRequestForm() {
|
||||||
sendFriendRequestForm.value = { toUserId: '', remark: '' }
|
sendFriendRequestForm.value = { toUserId: '', remark: '' }
|
||||||
}
|
}
|
||||||
@ -643,9 +684,8 @@ function resetWebhookForm() {
|
|||||||
|
|
||||||
function resetMessageSearch() {
|
function resetMessageSearch() {
|
||||||
historyForm.value = {
|
historyForm.value = {
|
||||||
userA: '',
|
|
||||||
userB: '',
|
|
||||||
keyword: '',
|
keyword: '',
|
||||||
|
chatType: '',
|
||||||
msgType: '',
|
msgType: '',
|
||||||
timeRange: [],
|
timeRange: [],
|
||||||
}
|
}
|
||||||
@ -689,20 +729,17 @@ async function loadGroups() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadMessages(page = historyPage.value) {
|
async function loadMessages(page = historyPage.value) {
|
||||||
const userA = historyForm.value.userA.trim()
|
|
||||||
const userB = historyForm.value.userB.trim()
|
|
||||||
if (!userA || !userB) {
|
|
||||||
ElMessage.warning('请填写用户A和用户B')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
loadingMessages.value = true
|
loadingMessages.value = true
|
||||||
try {
|
try {
|
||||||
const [startTime, endTime] = historyForm.value.timeRange ?? []
|
const [startTime, endTime] = historyForm.value.timeRange ?? []
|
||||||
const res = await imAdminApi.getMessages(appId, userA, userB, page, historyPageSize, {
|
const res = await imAdminApi.searchMessages(appId, {
|
||||||
keyword: historyForm.value.keyword.trim() || undefined,
|
keyword: historyForm.value.keyword.trim() || undefined,
|
||||||
|
chatType: historyForm.value.chatType || undefined,
|
||||||
msgType: historyForm.value.msgType || undefined,
|
msgType: historyForm.value.msgType || undefined,
|
||||||
startTime: startTime ? toLocalDateTime(startTime) : undefined,
|
startTime: startTime ? toLocalDateTime(startTime) : undefined,
|
||||||
endTime: endTime ? toLocalDateTime(endTime) : undefined,
|
endTime: endTime ? toLocalDateTime(endTime) : undefined,
|
||||||
|
page,
|
||||||
|
size: historyPageSize,
|
||||||
})
|
})
|
||||||
messages.value = res.data.data.content
|
messages.value = res.data.data.content
|
||||||
historyTotal.value = res.data.data.totalElements
|
historyTotal.value = res.data.data.totalElements
|
||||||
@ -966,20 +1003,37 @@ async function dismissGroup(group: ImGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitRegisterUser() {
|
async function submitRegisterUser() {
|
||||||
if (!registerForm.value.userId.trim()) {
|
if (!registerForm.value.userId.trim() && !editingUserId.value) {
|
||||||
ElMessage.warning('请填写用户ID')
|
ElMessage.warning('请填写用户ID')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
submittingRegister.value = true
|
submittingRegister.value = true
|
||||||
try {
|
try {
|
||||||
await imAdminApi.registerUser(appId, registerForm.value.userId.trim(), registerForm.value.nickname.trim())
|
const payload = {
|
||||||
|
nickname: registerForm.value.nickname.trim() || undefined,
|
||||||
|
avatar: registerForm.value.avatar.trim() || undefined,
|
||||||
|
gender: registerForm.value.gender,
|
||||||
|
status: registerForm.value.status,
|
||||||
|
}
|
||||||
|
if (editingUserId.value) {
|
||||||
|
await imAdminApi.updateUser(appId, editingUserId.value, payload)
|
||||||
|
ElMessage.success('用户已更新')
|
||||||
|
} else {
|
||||||
|
await imAdminApi.registerUser(
|
||||||
|
appId,
|
||||||
|
registerForm.value.userId.trim(),
|
||||||
|
payload.nickname,
|
||||||
|
payload.avatar,
|
||||||
|
payload.gender,
|
||||||
|
payload.status,
|
||||||
|
)
|
||||||
ElMessage.success('用户注册成功')
|
ElMessage.success('用户注册成功')
|
||||||
|
}
|
||||||
showRegisterUser.value = false
|
showRegisterUser.value = false
|
||||||
registerForm.value = { userId: '', nickname: '' }
|
|
||||||
loadUsers()
|
loadUsers()
|
||||||
loadStats()
|
loadStats()
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('注册失败,用户ID可能已存在')
|
ElMessage.error(editingUserId.value ? '更新失败' : '注册失败,用户ID可能已存在')
|
||||||
} finally {
|
} finally {
|
||||||
submittingRegister.value = false
|
submittingRegister.value = false
|
||||||
}
|
}
|
||||||
@ -993,7 +1047,14 @@ async function submitCreateGroup() {
|
|||||||
submittingCreateGroup.value = true
|
submittingCreateGroup.value = true
|
||||||
try {
|
try {
|
||||||
const memberIds = selectedMembers.value.map(item => item.userId)
|
const memberIds = selectedMembers.value.map(item => item.userId)
|
||||||
await imAdminApi.createGroup(appId, createGroupForm.value.name.trim(), createGroupForm.value.creatorId.trim(), memberIds)
|
await imAdminApi.createGroup(
|
||||||
|
appId,
|
||||||
|
createGroupForm.value.name.trim(),
|
||||||
|
createGroupForm.value.creatorId.trim(),
|
||||||
|
memberIds,
|
||||||
|
createGroupForm.value.groupType,
|
||||||
|
createGroupForm.value.announcement.trim() || undefined,
|
||||||
|
)
|
||||||
ElMessage.success('群组创建成功')
|
ElMessage.success('群组创建成功')
|
||||||
showCreateGroup.value = false
|
showCreateGroup.value = false
|
||||||
loadGroups()
|
loadGroups()
|
||||||
@ -1005,13 +1066,32 @@ async function submitCreateGroup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitEditGroup() {
|
||||||
|
if (!editingGroup.value) return
|
||||||
|
if (!editGroupForm.value.name.trim()) {
|
||||||
|
ElMessage.warning('请填写群名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submittingEditGroup.value = true
|
||||||
|
try {
|
||||||
|
await imAdminApi.updateGroup(appId, editingGroup.value.id, {
|
||||||
|
name: editGroupForm.value.name.trim(),
|
||||||
|
groupType: editGroupForm.value.groupType,
|
||||||
|
announcement: editGroupForm.value.announcement.trim() || undefined,
|
||||||
|
})
|
||||||
|
ElMessage.success('群组已更新')
|
||||||
|
showEditGroup.value = false
|
||||||
|
loadGroups()
|
||||||
|
} catch {
|
||||||
|
ElMessage.error('更新失败')
|
||||||
|
} finally {
|
||||||
|
submittingEditGroup.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleTabChange(tab: string) {
|
function handleTabChange(tab: string) {
|
||||||
if (tab === 'groups' && groups.value.length === 0) {
|
if (tab === 'groups' && groups.value.length === 0) {
|
||||||
loadGroups()
|
loadGroups()
|
||||||
} else if (tab === 'friend-requests' && friendRequests.value.length === 0) {
|
|
||||||
loadFriendRequests()
|
|
||||||
} else if (tab === 'group-requests' && groupJoinRequests.value.length === 0 && groupRequestGroupId.value.trim()) {
|
|
||||||
loadGroupJoinRequests()
|
|
||||||
} else if (tab === 'logs' && operationLogs.value.length === 0) {
|
} else if (tab === 'logs' && operationLogs.value.length === 0) {
|
||||||
loadOperationLogs()
|
loadOperationLogs()
|
||||||
} else if (tab === 'webhooks' && webhooks.value.length === 0) {
|
} else if (tab === 'webhooks' && webhooks.value.length === 0) {
|
||||||
@ -1035,8 +1115,58 @@ function handleOperationLogPageChange(page: number) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadStats()
|
loadStats()
|
||||||
loadUsers()
|
loadUsers()
|
||||||
loadFriendRequests()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function logActionLabel(action: string) {
|
||||||
|
return {
|
||||||
|
REGISTER_USER: '注册用户',
|
||||||
|
UPDATE_USER: '更新用户',
|
||||||
|
UPDATE_USER_STATUS: '调整状态',
|
||||||
|
CREATE_GROUP: '创建群组',
|
||||||
|
UPDATE_GROUP: '更新群组',
|
||||||
|
ADMIN_REVOKE_MESSAGE: '撤回消息',
|
||||||
|
CREATE_WEBHOOK: '创建回调',
|
||||||
|
UPDATE_WEBHOOK: '更新回调',
|
||||||
|
DELETE_WEBHOOK: '删除回调',
|
||||||
|
CREATE_KEYWORD_FILTER: '创建过滤',
|
||||||
|
UPDATE_KEYWORD_FILTER: '更新过滤',
|
||||||
|
DELETE_KEYWORD_FILTER: '删除过滤',
|
||||||
|
SET_GLOBAL_MUTE: '全局禁言',
|
||||||
|
VIEW_STATS: '查看统计',
|
||||||
|
VIEW_HISTORY: '查看消息',
|
||||||
|
SEARCH_USERS: '搜索用户',
|
||||||
|
SEARCH_GROUPS: '搜索群组',
|
||||||
|
SEARCH_MESSAGES: '搜索消息',
|
||||||
|
}[action] ?? action
|
||||||
|
}
|
||||||
|
|
||||||
|
function logResourceLabel(resourceType: string) {
|
||||||
|
return {
|
||||||
|
ACCOUNT: '用户',
|
||||||
|
GROUP: '群组',
|
||||||
|
MESSAGE: '消息',
|
||||||
|
WEBHOOK: '回调',
|
||||||
|
KEYWORD_FILTER: '过滤',
|
||||||
|
GLOBAL_MUTE: '全局禁言',
|
||||||
|
STATS: '统计',
|
||||||
|
}[resourceType] ?? resourceType
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLogDetail(detail?: string | null) {
|
||||||
|
if (!detail) return '-'
|
||||||
|
const trimmed = detail.trim()
|
||||||
|
if (!trimmed) return '-'
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(trimmed)
|
||||||
|
return typeof parsed === 'object' ? JSON.stringify(parsed, null, 2) : String(parsed)
|
||||||
|
} catch {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function userAvatarFallback(row: ImUser) {
|
||||||
|
return (row.nickname || row.userId || '?').slice(0, 1).toUpperCase()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -1128,4 +1258,10 @@ onMounted(() => {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-detail {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户