feat(im): align tenant admin im management

这个提交包含在:
XuqmGroup 2026-04-29 10:04:13 +08:00
父节点 c84a27f4ec
当前提交 de89297457
共有 2 个文件被更改,包括 364 次插入152 次删除

查看文件

@ -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) {
return imClient.put<{ data: WebhookConfig }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}`, form, {
params: { appId },
@ -301,18 +313,56 @@ export const imAdminApi = {
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 }>(
'/api/im/admin/users',
{ userId, nickname, avatar },
{
userId,
nickname,
avatar,
...(gender ? { gender } : {}),
...(status ? { status } : {}),
},
{ 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 }>(
'/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 } },
)
},
@ -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) {
return imClient.get<{ data: ImProfile }>(
`/api/im/accounts/${encodeURIComponent(userId)}`,

查看文件

@ -17,12 +17,19 @@
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="注册用户" name="users">
<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>
</div>
<el-table :data="users" v-loading="loadingUsers" border stripe>
<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="gender" label="性别" width="90">
<template #default="{ row }">
@ -41,8 +48,11 @@
{{ formatTime(row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="140" fixed="right">
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="openEditUserDialog(row)">
编辑
</el-button>
<el-button
link
:type="row.status === 'ACTIVE' ? 'danger' : 'success'"
@ -68,6 +78,7 @@
<el-tab-pane label="群组列表" name="groups">
<div class="toolbar">
<el-button type="primary" @click="openCreateGroupDialog">创建群组</el-button>
<el-button @click="loadGroups" :loading="loadingGroups">刷新</el-button>
</div>
@ -90,8 +101,11 @@
<el-table-column prop="createdAt" label="创建时间" width="180">
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<el-table-column label="操作" width="180" fixed="right">
<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>
@ -103,13 +117,13 @@
<el-tab-pane label="消息历史" name="messages">
<el-form :inline="true" :model="historyForm" class="toolbar" label-width="0">
<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-input v-model="historyForm.userB" placeholder="用户B" clearable style="width:160px" />
</el-form-item>
<el-form-item>
<el-input v-model="historyForm.keyword" placeholder="关键词" clearable style="width:180px" />
<el-select v-model="historyForm.chatType" placeholder="会话类型" clearable style="width:120px">
<el-option label="单聊" value="SINGLE" />
<el-option label="群聊" value="GROUP" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="historyForm.msgType" placeholder="消息类型" clearable style="width:140px">
@ -129,7 +143,7 @@
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loadingMessages" @click="searchMessages">
查询
搜索
</el-button>
</el-form-item>
<el-form-item>
@ -141,9 +155,9 @@
<el-table-column prop="createdAt" label="时间" width="180">
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column prop="chatType" label="会话" width="90" />
<el-table-column prop="fromUserId" label="发送者" width="160" />
<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="status" label="状态" width="100" />
<el-table-column prop="content" label="内容" min-width="260">
@ -171,89 +185,6 @@
/>
</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">
<div class="toolbar">
<el-button @click="loadOperationLogs" :loading="loadingLogs">刷新</el-button>
@ -264,10 +195,20 @@
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column prop="operatorId" label="操作者" width="160" />
<el-table-column prop="action" label="动作" width="180" />
<el-table-column prop="resourceType" label="资源类型" width="120" />
<el-table-column prop="action" label="动作" width="180">
<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="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-pagination
@ -315,22 +256,40 @@
</el-tabs>
</el-card>
<el-dialog v-model="showRegisterUser" title="注册用户" width="420px">
<el-form :model="registerForm" label-width="72px">
<el-dialog v-model="showRegisterUser" :title="editingUserId ? '编辑用户' : '注册用户'" width="520px" @closed="resetUserForm">
<el-form :model="registerForm" label-width="84px">
<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 label="昵称">
<el-input v-model="registerForm.nickname" />
</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>
<template #footer>
<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>
</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-item label="群名称">
<el-input v-model="createGroupForm.name" />
@ -338,6 +297,15 @@
<el-form-item label="创建者ID">
<el-input v-model="createGroupForm.creatorId" />
</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-input
v-model="memberSearchKeyword"
@ -386,33 +354,27 @@
</template>
</el-dialog>
<el-dialog v-model="showSendFriendRequest" title="发送好友申请" width="420px" @closed="resetSendFriendRequestForm">
<el-form :model="sendFriendRequestForm" label-width="88px">
<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-dialog v-model="showEditGroup" title="编辑群组" width="520px" @closed="resetEditGroupForm">
<el-form :model="editGroupForm" label-width="90px">
<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 label="申请信息">
<el-input v-model="sendGroupJoinRequestForm.remark" type="textarea" :rows="3" placeholder="例如:希望加入此群进行沟通" />
<el-form-item label="群名称">
<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>
<template #footer>
<el-button @click="showSendGroupJoinRequest = false">取消</el-button>
<el-button type="primary" :loading="submittingSendGroupJoinRequest" @click="submitSendGroupJoinRequest">发送</el-button>
<el-button @click="showEditGroup = false">取消</el-button>
<el-button type="primary" :loading="submittingEditGroup" @click="submitEditGroup">保存</el-button>
</template>
</el-dialog>
@ -518,20 +480,40 @@ const submittingSendGroupJoinRequest = ref(false)
const sendGroupJoinRequestForm = ref({ groupId: '', remark: '' })
const historyForm = ref({
userA: '',
userB: '',
keyword: '',
chatType: '',
msgType: '',
timeRange: [] as string[] | null,
})
const showRegisterUser = 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 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 memberSearchResults = ref<ImUser[]>([])
@ -622,12 +604,71 @@ function removeMember(userId: string) {
}
function resetCreateGroupForm() {
createGroupForm.value = { name: '', creatorId: '' }
createGroupForm.value = { name: '', creatorId: '', groupType: 'WORK', announcement: '' }
memberSearchKeyword.value = ''
memberSearchResults.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() {
sendFriendRequestForm.value = { toUserId: '', remark: '' }
}
@ -643,9 +684,8 @@ function resetWebhookForm() {
function resetMessageSearch() {
historyForm.value = {
userA: '',
userB: '',
keyword: '',
chatType: '',
msgType: '',
timeRange: [],
}
@ -689,20 +729,17 @@ async function loadGroups() {
}
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
try {
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,
chatType: historyForm.value.chatType || undefined,
msgType: historyForm.value.msgType || undefined,
startTime: startTime ? toLocalDateTime(startTime) : undefined,
endTime: endTime ? toLocalDateTime(endTime) : undefined,
page,
size: historyPageSize,
})
messages.value = res.data.data.content
historyTotal.value = res.data.data.totalElements
@ -966,20 +1003,37 @@ async function dismissGroup(group: ImGroup) {
}
async function submitRegisterUser() {
if (!registerForm.value.userId.trim()) {
if (!registerForm.value.userId.trim() && !editingUserId.value) {
ElMessage.warning('请填写用户ID')
return
}
submittingRegister.value = true
try {
await imAdminApi.registerUser(appId, registerForm.value.userId.trim(), registerForm.value.nickname.trim())
ElMessage.success('用户注册成功')
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('用户注册成功')
}
showRegisterUser.value = false
registerForm.value = { userId: '', nickname: '' }
loadUsers()
loadStats()
} catch {
ElMessage.error('注册失败,用户ID可能已存在')
ElMessage.error(editingUserId.value ? '更新失败' : '注册失败,用户ID可能已存在')
} finally {
submittingRegister.value = false
}
@ -993,7 +1047,14 @@ async function submitCreateGroup() {
submittingCreateGroup.value = true
try {
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('群组创建成功')
showCreateGroup.value = false
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) {
if (tab === 'groups' && groups.value.length === 0) {
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) {
loadOperationLogs()
} else if (tab === 'webhooks' && webhooks.value.length === 0) {
@ -1035,8 +1115,58 @@ function handleOperationLogPageChange(page: number) {
onMounted(() => {
loadStats()
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>
<style scoped>
@ -1128,4 +1258,10 @@ onMounted(() => {
max-width: 100%;
word-break: break-all;
}
.log-detail {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.5;
}
</style>