feat(tenant-platform): add app store config tab, store review status, enhanced release dialogs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
54abc82e46
当前提交
c84a27f4ec
@ -92,6 +92,54 @@ export interface ImStats {
|
||||
todayMessages: number
|
||||
}
|
||||
|
||||
export interface OperationLog {
|
||||
id: string
|
||||
appId: string
|
||||
operatorId: string
|
||||
action: string
|
||||
resourceType: string
|
||||
resourceId?: string | null
|
||||
detail?: string | null
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
export interface FriendRequest {
|
||||
id: string
|
||||
appId: string
|
||||
fromUserId: string
|
||||
toUserId: string
|
||||
remark?: string | null
|
||||
status: 'PENDING' | 'ACCEPTED' | 'REJECTED'
|
||||
createdAt: number
|
||||
reviewedAt?: number | null
|
||||
}
|
||||
|
||||
export interface WebhookConfig {
|
||||
id: string
|
||||
appId: string
|
||||
url: string
|
||||
secret?: string | null
|
||||
enabled: boolean
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
export interface WebhookConfigForm {
|
||||
url: string
|
||||
secret?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export interface GroupJoinRequest {
|
||||
id: string
|
||||
appId: string
|
||||
groupId: string
|
||||
requesterId: string
|
||||
remark?: string | null
|
||||
status: 'PENDING' | 'ACCEPTED' | 'REJECTED'
|
||||
createdAt: number
|
||||
reviewedAt?: number | null
|
||||
}
|
||||
|
||||
export const imAdminApi = {
|
||||
listUsers(appId: string, page = 0, size = 20) {
|
||||
return imClient.get<{ data: PagedResult<ImUser> }>(
|
||||
@ -111,6 +159,108 @@ export const imAdminApi = {
|
||||
return imClient.get<{ data: ImStats }>('/api/im/admin/stats', { params: { appId } })
|
||||
},
|
||||
|
||||
getOperationLogs(appId: string, page = 0, size = 20) {
|
||||
return imClient.get<{ data: PagedResult<OperationLog> }>('/api/im/admin/operation-logs', {
|
||||
params: { appId, page, size },
|
||||
})
|
||||
},
|
||||
|
||||
listWebhooks(appId: string) {
|
||||
return imClient.get<{ data: WebhookConfig[] }>('/api/im/admin/webhooks', {
|
||||
params: { appId },
|
||||
})
|
||||
},
|
||||
|
||||
createWebhook(appId: string, form: WebhookConfigForm) {
|
||||
return imClient.post<{ data: WebhookConfig }>('/api/im/admin/webhooks', form, {
|
||||
params: { appId },
|
||||
})
|
||||
},
|
||||
|
||||
updateWebhook(appId: string, webhookId: string, form: WebhookConfigForm) {
|
||||
return imClient.put<{ data: WebhookConfig }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}`, form, {
|
||||
params: { appId },
|
||||
})
|
||||
},
|
||||
|
||||
deleteWebhook(appId: string, webhookId: string) {
|
||||
return imClient.delete<{ data: null }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}`, {
|
||||
params: { appId },
|
||||
})
|
||||
},
|
||||
|
||||
listFriendRequests(appId: string, direction: 'incoming' | 'outgoing' = 'incoming') {
|
||||
return imClient.get<{ data: FriendRequest[] }>('/api/im/friend-requests', {
|
||||
params: { appId, direction },
|
||||
})
|
||||
},
|
||||
|
||||
sendFriendRequest(appId: string, toUserId: string, remark?: string) {
|
||||
return imClient.post<{ data: FriendRequest }>(
|
||||
'/api/im/friend-requests',
|
||||
null,
|
||||
{
|
||||
params: {
|
||||
appId,
|
||||
toUserId,
|
||||
...(remark ? { remark } : {}),
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
|
||||
acceptFriendRequest(appId: string, requestId: string) {
|
||||
return imClient.post<{ data: FriendRequest }>(
|
||||
`/api/im/friend-requests/${encodeURIComponent(requestId)}/accept`,
|
||||
null,
|
||||
{ params: { appId } },
|
||||
)
|
||||
},
|
||||
|
||||
rejectFriendRequest(appId: string, requestId: string) {
|
||||
return imClient.post<{ data: FriendRequest }>(
|
||||
`/api/im/friend-requests/${encodeURIComponent(requestId)}/reject`,
|
||||
null,
|
||||
{ params: { appId } },
|
||||
)
|
||||
},
|
||||
|
||||
listGroupJoinRequests(appId: string, groupId: string) {
|
||||
return imClient.get<{ data: GroupJoinRequest[] }>(
|
||||
`/api/im/groups/${encodeURIComponent(groupId)}/join-requests`,
|
||||
{ params: { appId } },
|
||||
)
|
||||
},
|
||||
|
||||
sendGroupJoinRequest(appId: string, groupId: string, remark?: string) {
|
||||
return imClient.post<{ data: GroupJoinRequest }>(
|
||||
`/api/im/groups/${encodeURIComponent(groupId)}/join-requests`,
|
||||
null,
|
||||
{
|
||||
params: {
|
||||
appId,
|
||||
...(remark ? { remark } : {}),
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
|
||||
acceptGroupJoinRequest(appId: string, groupId: string, requestId: string) {
|
||||
return imClient.post<{ data: GroupJoinRequest }>(
|
||||
`/api/im/groups/${encodeURIComponent(groupId)}/join-requests/${encodeURIComponent(requestId)}/accept`,
|
||||
null,
|
||||
{ params: { appId } },
|
||||
)
|
||||
},
|
||||
|
||||
rejectGroupJoinRequest(appId: string, groupId: string, requestId: string) {
|
||||
return imClient.post<{ data: GroupJoinRequest }>(
|
||||
`/api/im/groups/${encodeURIComponent(groupId)}/join-requests/${encodeURIComponent(requestId)}/reject`,
|
||||
null,
|
||||
{ params: { appId } },
|
||||
)
|
||||
},
|
||||
|
||||
getMessages(
|
||||
appId: string,
|
||||
userA: string,
|
||||
|
||||
@ -29,12 +29,25 @@ updateClient.interceptors.request.use((config) => {
|
||||
return config
|
||||
})
|
||||
|
||||
export type StoreType = 'HUAWEI' | 'MI' | 'OPPO' | 'VIVO' | 'HONOR' | 'APP_STORE' | 'GOOGLE_PLAY'
|
||||
export type StoreReviewState = 'PENDING' | 'UNDER_REVIEW' | 'APPROVED' | 'REJECTED'
|
||||
|
||||
export interface StoreConfig {
|
||||
id: string
|
||||
appId: string
|
||||
storeType: StoreType
|
||||
configJson?: string
|
||||
enabled: boolean
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface AppVersion {
|
||||
id: string
|
||||
appId: string
|
||||
platform: 'ANDROID' | 'IOS'
|
||||
versionName: string
|
||||
versionCode: number
|
||||
packageName?: string
|
||||
downloadUrl?: string
|
||||
changeLog?: string
|
||||
forceUpdate: boolean
|
||||
@ -43,6 +56,11 @@ export interface AppVersion {
|
||||
grayPercent: number
|
||||
appStoreUrl?: string
|
||||
marketUrl?: string
|
||||
scheduledPublishAt?: string
|
||||
autoPublishAfterReview: boolean
|
||||
webhookUrl?: string
|
||||
storeSubmitTargets?: string
|
||||
storeReviewStatus?: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
@ -140,4 +158,36 @@ export const updateAdminApi = {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
},
|
||||
|
||||
// ── Store config ────────────────────────────────────────────────────────
|
||||
|
||||
getStoreConfigs(appId: string) {
|
||||
return updateClient.get<{ data: StoreConfig[] }>('/api/v1/updates/store/configs', { params: { appId } })
|
||||
},
|
||||
|
||||
saveStoreConfig(appId: string, storeType: StoreType, configJson: string, enabled: boolean) {
|
||||
return updateClient.put<{ data: StoreConfig }>(
|
||||
`/api/v1/updates/store/configs/${storeType}`,
|
||||
{ configJson, enabled },
|
||||
{ params: { appId } },
|
||||
)
|
||||
},
|
||||
|
||||
deleteStoreConfig(appId: string, storeType: StoreType) {
|
||||
return updateClient.delete(`/api/v1/updates/store/configs/${storeType}`, { params: { appId } })
|
||||
},
|
||||
|
||||
executeSubmitToStores(versionId: string, storeTypes: StoreType[]) {
|
||||
return updateClient.post<{ data: AppVersion }>(
|
||||
`/api/v1/updates/store/app/${versionId}/execute-submit`,
|
||||
{ storeTypes },
|
||||
)
|
||||
},
|
||||
|
||||
updateStoreReview(versionId: string, storeType: StoreType, state: StoreReviewState) {
|
||||
return updateClient.post<{ data: AppVersion }>(
|
||||
`/api/v1/updates/store/app/${versionId}/review`,
|
||||
{ storeType, state },
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -170,6 +170,148 @@
|
||||
@current-change="handleHistoryPageChange"
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<el-table :data="operationLogs" v-loading="loadingLogs" border stripe>
|
||||
<el-table-column prop="createdAt" label="时间" width="180">
|
||||
<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="resourceId" label="资源ID" width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="detail" label="详情" min-width="220" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-if="operationLogTotal > operationLogPageSize"
|
||||
style="margin-top:16px"
|
||||
layout="prev, pager, next"
|
||||
:total="operationLogTotal"
|
||||
:page-size="operationLogPageSize"
|
||||
:current-page="operationLogPage + 1"
|
||||
@current-change="handleOperationLogPageChange"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="回调配置" name="webhooks">
|
||||
<div class="toolbar toolbar-space-between">
|
||||
<el-button type="primary" @click="openCreateWebhookDialog">新增回调</el-button>
|
||||
<el-button @click="loadWebhooks" :loading="loadingWebhooks">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="webhooks" v-loading="loadingWebhooks" border stripe>
|
||||
<el-table-column prop="url" label="回调地址" min-width="260" show-overflow-tooltip />
|
||||
<el-table-column prop="secret" label="密钥" width="120">
|
||||
<template #default="{ row }">
|
||||
{{ row.secret ? '******' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="enabled" label="启用" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">
|
||||
{{ row.enabled ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" width="180">
|
||||
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openEditWebhookDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="deleteWebhook(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
@ -243,6 +385,59 @@
|
||||
<el-button type="primary" :loading="submittingCreateGroup" @click="submitCreateGroup">创建</el-button>
|
||||
</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-form-item label="群组ID">
|
||||
<el-input v-model="sendGroupJoinRequestForm.groupId" placeholder="目标群组ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请信息">
|
||||
<el-input v-model="sendGroupJoinRequestForm.remark" type="textarea" :rows="3" placeholder="例如:希望加入此群进行沟通" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showSendGroupJoinRequest = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submittingSendGroupJoinRequest" @click="submitSendGroupJoinRequest">发送</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showWebhookDialog"
|
||||
:title="editingWebhookId ? '编辑回调配置' : '新增回调配置'"
|
||||
width="520px"
|
||||
@closed="resetWebhookForm"
|
||||
>
|
||||
<el-form :model="webhookForm" label-width="90px">
|
||||
<el-form-item label="回调地址">
|
||||
<el-input v-model="webhookForm.url" placeholder="https://example.com/webhook" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回调密钥">
|
||||
<el-input v-model="webhookForm.secret" placeholder="留空则不修改" />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="webhookForm.enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showWebhookDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submittingWebhook" @click="submitWebhookForm">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -251,7 +446,17 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { imAdminApi, type ImGroup, type ImMessage, type ImStats, type ImUser } from '@/api/im'
|
||||
import {
|
||||
imAdminApi,
|
||||
type FriendRequest,
|
||||
type GroupJoinRequest,
|
||||
type ImGroup,
|
||||
type ImMessage,
|
||||
type ImStats,
|
||||
type ImUser,
|
||||
type OperationLog,
|
||||
type WebhookConfig,
|
||||
} from '@/api/im'
|
||||
|
||||
const route = useRoute()
|
||||
const appId = route.params.appId as string
|
||||
@ -285,6 +490,33 @@ const historyPage = ref(0)
|
||||
const historyPageSize = 20
|
||||
const historyTotal = ref(0)
|
||||
|
||||
const operationLogs = ref<OperationLog[]>([])
|
||||
const loadingLogs = ref(false)
|
||||
const operationLogPage = ref(0)
|
||||
const operationLogPageSize = 20
|
||||
const operationLogTotal = ref(0)
|
||||
|
||||
const webhooks = ref<WebhookConfig[]>([])
|
||||
const loadingWebhooks = ref(false)
|
||||
const showWebhookDialog = ref(false)
|
||||
const submittingWebhook = ref(false)
|
||||
const editingWebhookId = ref<string | null>(null)
|
||||
const webhookForm = ref({ url: '', secret: '', enabled: true })
|
||||
|
||||
const friendRequests = ref<FriendRequest[]>([])
|
||||
const loadingFriendRequests = ref(false)
|
||||
const friendRequestDirection = ref<'incoming' | 'outgoing'>('incoming')
|
||||
const showSendFriendRequest = ref(false)
|
||||
const submittingSendFriendRequest = ref(false)
|
||||
const sendFriendRequestForm = ref({ toUserId: '', remark: '' })
|
||||
|
||||
const groupJoinRequests = ref<GroupJoinRequest[]>([])
|
||||
const loadingGroupRequests = ref(false)
|
||||
const groupRequestGroupId = ref('')
|
||||
const showSendGroupJoinRequest = ref(false)
|
||||
const submittingSendGroupJoinRequest = ref(false)
|
||||
const sendGroupJoinRequestForm = ref({ groupId: '', remark: '' })
|
||||
|
||||
const historyForm = ref({
|
||||
userA: '',
|
||||
userB: '',
|
||||
@ -325,6 +557,22 @@ function formatContent(content: string) {
|
||||
return content.length > 120 ? `${content.slice(0, 120)}...` : content
|
||||
}
|
||||
|
||||
function friendRequestStatusLabel(status: string) {
|
||||
return { PENDING: '待处理', ACCEPTED: '已同意', REJECTED: '已拒绝' }[status] ?? status
|
||||
}
|
||||
|
||||
function friendRequestTagType(status: string) {
|
||||
return { PENDING: 'warning', ACCEPTED: 'success', REJECTED: 'danger' }[status] ?? 'info'
|
||||
}
|
||||
|
||||
function groupRequestStatusLabel(status: string) {
|
||||
return { PENDING: '待处理', ACCEPTED: '已同意', REJECTED: '已拒绝' }[status] ?? status
|
||||
}
|
||||
|
||||
function groupRequestTagType(status: string) {
|
||||
return { PENDING: 'warning', ACCEPTED: 'success', REJECTED: 'danger' }[status] ?? 'info'
|
||||
}
|
||||
|
||||
function parseIdList(value: string) {
|
||||
try {
|
||||
const parsed = JSON.parse(value)
|
||||
@ -380,6 +628,19 @@ function resetCreateGroupForm() {
|
||||
selectedMembers.value = []
|
||||
}
|
||||
|
||||
function resetSendFriendRequestForm() {
|
||||
sendFriendRequestForm.value = { toUserId: '', remark: '' }
|
||||
}
|
||||
|
||||
function resetSendGroupJoinRequestForm() {
|
||||
sendGroupJoinRequestForm.value = { groupId: '', remark: '' }
|
||||
}
|
||||
|
||||
function resetWebhookForm() {
|
||||
editingWebhookId.value = null
|
||||
webhookForm.value = { url: '', secret: '', enabled: true }
|
||||
}
|
||||
|
||||
function resetMessageSearch() {
|
||||
historyForm.value = {
|
||||
userA: '',
|
||||
@ -453,10 +714,120 @@ async function loadMessages(page = historyPage.value) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOperationLogs(page = operationLogPage.value) {
|
||||
loadingLogs.value = true
|
||||
try {
|
||||
const res = await imAdminApi.getOperationLogs(appId, page, operationLogPageSize)
|
||||
operationLogs.value = res.data.data.content
|
||||
operationLogTotal.value = res.data.data.totalElements
|
||||
operationLogPage.value = page
|
||||
} catch {
|
||||
ElMessage.error('加载操作日志失败')
|
||||
} finally {
|
||||
loadingLogs.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWebhooks() {
|
||||
loadingWebhooks.value = true
|
||||
try {
|
||||
const res = await imAdminApi.listWebhooks(appId)
|
||||
webhooks.value = res.data.data
|
||||
} catch {
|
||||
ElMessage.error('加载回调配置失败')
|
||||
} finally {
|
||||
loadingWebhooks.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFriendRequests() {
|
||||
loadingFriendRequests.value = true
|
||||
try {
|
||||
const res = await imAdminApi.listFriendRequests(appId, friendRequestDirection.value)
|
||||
friendRequests.value = res.data.data
|
||||
} catch {
|
||||
ElMessage.error('加载好友申请失败')
|
||||
} finally {
|
||||
loadingFriendRequests.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGroupJoinRequests() {
|
||||
const groupId = groupRequestGroupId.value.trim()
|
||||
if (!groupId) {
|
||||
ElMessage.warning('请先输入群组ID')
|
||||
return
|
||||
}
|
||||
loadingGroupRequests.value = true
|
||||
try {
|
||||
const res = await imAdminApi.listGroupJoinRequests(appId, groupId)
|
||||
groupJoinRequests.value = res.data.data
|
||||
} catch {
|
||||
ElMessage.error('加载入群申请失败')
|
||||
} finally {
|
||||
loadingGroupRequests.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function searchMessages() {
|
||||
await loadMessages(0)
|
||||
}
|
||||
|
||||
function openCreateWebhookDialog() {
|
||||
resetWebhookForm()
|
||||
showWebhookDialog.value = true
|
||||
}
|
||||
|
||||
function openEditWebhookDialog(row: WebhookConfig) {
|
||||
editingWebhookId.value = row.id
|
||||
webhookForm.value = {
|
||||
url: row.url,
|
||||
secret: '',
|
||||
enabled: row.enabled,
|
||||
}
|
||||
showWebhookDialog.value = true
|
||||
}
|
||||
|
||||
async function submitWebhookForm() {
|
||||
if (!webhookForm.value.url.trim()) {
|
||||
ElMessage.warning('请填写回调地址')
|
||||
return
|
||||
}
|
||||
submittingWebhook.value = true
|
||||
try {
|
||||
const payload = {
|
||||
url: webhookForm.value.url.trim(),
|
||||
secret: webhookForm.value.secret.trim() || undefined,
|
||||
enabled: webhookForm.value.enabled,
|
||||
}
|
||||
if (editingWebhookId.value) {
|
||||
await imAdminApi.updateWebhook(appId, editingWebhookId.value, payload)
|
||||
ElMessage.success('回调配置已更新')
|
||||
} else {
|
||||
await imAdminApi.createWebhook(appId, payload)
|
||||
ElMessage.success('回调配置已创建')
|
||||
}
|
||||
showWebhookDialog.value = false
|
||||
resetWebhookForm()
|
||||
loadWebhooks()
|
||||
} catch {
|
||||
ElMessage.error('保存回调配置失败')
|
||||
} finally {
|
||||
submittingWebhook.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteWebhook(row: WebhookConfig) {
|
||||
await ElMessageBox.confirm(`确认删除回调配置 ${row.url}?`, '删除回调配置', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
await imAdminApi.deleteWebhook(appId, row.id)
|
||||
ElMessage.success('回调配置已删除')
|
||||
loadWebhooks()
|
||||
}
|
||||
|
||||
async function toggleUserStatus(user: ImUser) {
|
||||
const nextStatus = user.status === 'ACTIVE' ? 'BANNED' : 'ACTIVE'
|
||||
const action = nextStatus === 'BANNED' ? '封禁' : '解封'
|
||||
@ -481,6 +852,108 @@ async function revokeMessage(message: ImMessage) {
|
||||
loadMessages(historyPage.value)
|
||||
}
|
||||
|
||||
async function approveFriend(request: FriendRequest) {
|
||||
await ElMessageBox.confirm(`确认同意好友申请 ${request.fromUserId} -> ${request.toUserId}?`, '同意好友申请', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
await imAdminApi.acceptFriendRequest(appId, request.id)
|
||||
ElMessage.success('已同意好友申请')
|
||||
loadFriendRequests()
|
||||
}
|
||||
|
||||
async function declineFriend(request: FriendRequest) {
|
||||
await ElMessageBox.confirm(`确认拒绝好友申请 ${request.fromUserId} -> ${request.toUserId}?`, '拒绝好友申请', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
await imAdminApi.rejectFriendRequest(appId, request.id)
|
||||
ElMessage.success('已拒绝好友申请')
|
||||
loadFriendRequests()
|
||||
}
|
||||
|
||||
async function submitSendFriendRequest() {
|
||||
if (!sendFriendRequestForm.value.toUserId.trim()) {
|
||||
ElMessage.warning('请填写接收人ID')
|
||||
return
|
||||
}
|
||||
submittingSendFriendRequest.value = true
|
||||
try {
|
||||
await imAdminApi.sendFriendRequest(
|
||||
appId,
|
||||
sendFriendRequestForm.value.toUserId.trim(),
|
||||
sendFriendRequestForm.value.remark.trim() || undefined,
|
||||
)
|
||||
ElMessage.success('好友申请已发送')
|
||||
showSendFriendRequest.value = false
|
||||
resetSendFriendRequestForm()
|
||||
loadFriendRequests()
|
||||
} catch {
|
||||
ElMessage.error('发送失败')
|
||||
} finally {
|
||||
submittingSendFriendRequest.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function approveGroupJoin(request: GroupJoinRequest) {
|
||||
const groupId = groupRequestGroupId.value.trim()
|
||||
if (!groupId) {
|
||||
ElMessage.warning('请先输入群组ID')
|
||||
return
|
||||
}
|
||||
await ElMessageBox.confirm(`确认同意群 ${groupId} 的入群申请 ${request.requesterId}?`, '同意入群申请', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
await imAdminApi.acceptGroupJoinRequest(appId, groupId, request.id)
|
||||
ElMessage.success('已同意入群申请')
|
||||
loadGroupJoinRequests()
|
||||
}
|
||||
|
||||
async function declineGroupJoin(request: GroupJoinRequest) {
|
||||
const groupId = groupRequestGroupId.value.trim()
|
||||
if (!groupId) {
|
||||
ElMessage.warning('请先输入群组ID')
|
||||
return
|
||||
}
|
||||
await ElMessageBox.confirm(`确认拒绝群 ${groupId} 的入群申请 ${request.requesterId}?`, '拒绝入群申请', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
await imAdminApi.rejectGroupJoinRequest(appId, groupId, request.id)
|
||||
ElMessage.success('已拒绝入群申请')
|
||||
loadGroupJoinRequests()
|
||||
}
|
||||
|
||||
async function submitSendGroupJoinRequest() {
|
||||
const groupId = sendGroupJoinRequestForm.value.groupId.trim()
|
||||
if (!groupId) {
|
||||
ElMessage.warning('请填写群组ID')
|
||||
return
|
||||
}
|
||||
submittingSendGroupJoinRequest.value = true
|
||||
try {
|
||||
await imAdminApi.sendGroupJoinRequest(
|
||||
appId,
|
||||
groupId,
|
||||
sendGroupJoinRequestForm.value.remark.trim() || undefined,
|
||||
)
|
||||
ElMessage.success('入群申请已发送')
|
||||
showSendGroupJoinRequest.value = false
|
||||
resetSendGroupJoinRequestForm()
|
||||
groupRequestGroupId.value = groupId
|
||||
loadGroupJoinRequests()
|
||||
} catch {
|
||||
ElMessage.error('发送失败')
|
||||
} finally {
|
||||
submittingSendGroupJoinRequest.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function dismissGroup(group: ImGroup) {
|
||||
await ElMessageBox.confirm(`确认解散群组 ${group.name}?`, '解散群组', {
|
||||
type: 'warning',
|
||||
@ -535,6 +1008,14 @@ async function submitCreateGroup() {
|
||||
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) {
|
||||
loadWebhooks()
|
||||
}
|
||||
}
|
||||
|
||||
@ -547,9 +1028,14 @@ function handleHistoryPageChange(page: number) {
|
||||
loadMessages(page - 1)
|
||||
}
|
||||
|
||||
function handleOperationLogPageChange(page: number) {
|
||||
loadOperationLogs(page - 1)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
loadUsers()
|
||||
loadFriendRequests()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -577,6 +1063,20 @@ onMounted(() => {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.toolbar-space-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.toolbar-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
|
||||
文件差异内容过多而无法显示
加载差异
正在加载...
在新工单中引用
屏蔽一个用户