feat(ops-platform): add service activation request review UI

ServiceRequestsView: table of all activation requests with status filter,
approve/reject dialogs with optional review note. Route /service-requests
added; sidebar menu item '服务开通审核' added to MainLayout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-04-25 06:40:46 +08:00
父节点 8d28f80761
当前提交 0603d81393
共有 4 个文件被更改,包括 164 次插入0 次删除

查看文件

@ -25,6 +25,24 @@ export interface Statistics {
onlineUsers: number
}
export interface ServiceRequest {
id: string
appId: string
platform: string
serviceType: string
status: 'PENDING' | 'APPROVED' | 'REJECTED'
applyReason?: string
reviewNote?: string
createdAt: string
reviewedAt?: string
}
export interface ServiceRequestPage {
content: ServiceRequest[]
total: number
totalPages: number
}
export const opsApi = {
listTenants: (keyword = '', page = 0, size = 20) =>
client.get<{ data: TenantPage }>('/ops/tenants', { params: { keyword, page, size } }),
@ -34,4 +52,13 @@ export const opsApi = {
statistics: () =>
client.get<{ data: Statistics }>('/ops/statistics'),
listServiceRequests: (status = '', page = 0, size = 20) =>
client.get<{ data: ServiceRequestPage }>('/ops/service-requests', { params: { status, page, size } }),
approveRequest: (requestId: string, reviewNote = '') =>
client.post<{ data: ServiceRequest }>(`/ops/service-requests/${requestId}/approve`, { reviewNote }),
rejectRequest: (requestId: string, reviewNote = '') =>
client.post<{ data: ServiceRequest }>(`/ops/service-requests/${requestId}/reject`, { reviewNote }),
}

查看文件

@ -12,6 +12,7 @@ const router = createRouter({
{ path: '', redirect: '/tenants' },
{ path: 'tenants', component: () => import('@/views/tenants/TenantListView.vue') },
{ path: 'statistics', component: () => import('@/views/statistics/StatisticsView.vue') },
{ path: 'service-requests', component: () => import('@/views/services/ServiceRequestsView.vue') },
],
},
],

查看文件

@ -7,6 +7,7 @@
<el-menu router :default-active="$route.path" background-color="#1d2129" text-color="#c9d1d9" active-text-color="#409eff">
<el-menu-item index="/tenants"><el-icon><Avatar /></el-icon></el-menu-item>
<el-menu-item index="/statistics"><el-icon><TrendCharts /></el-icon></el-menu-item>
<el-menu-item index="/service-requests"><el-icon><Bell /></el-icon></el-menu-item>
</el-menu>
</el-aside>
<el-container>

查看文件

@ -0,0 +1,135 @@
<template>
<div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
<h2>服务开通申请</h2>
<el-select v-model="statusFilter" placeholder="全部状态" clearable style="width:160px" @change="loadRequests">
<el-option label="待审核" value="PENDING" />
<el-option label="已通过" value="APPROVED" />
<el-option label="已拒绝" value="REJECTED" />
</el-select>
</div>
<el-table :data="requests" v-loading="loading">
<el-table-column prop="appId" label="AppID" width="180" />
<el-table-column prop="platform" label="平台" width="100">
<template #default="{ row }">
<el-tag size="small">{{ row.platform }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="serviceType" label="服务类型" width="100">
<template #default="{ row }">
<el-tag size="small" type="info">{{ row.serviceType }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="applyReason" label="申请原因" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="申请时间" width="160">
<template #default="{ row }">{{ fmt(row.createdAt) }}</template>
</el-table-column>
<el-table-column prop="reviewNote" label="审核备注" show-overflow-tooltip />
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<template v-if="row.status === 'PENDING'">
<el-button link type="success" @click="openReview(row, 'approve')">通过</el-button>
<el-button link type="danger" @click="openReview(row, 'reject')">拒绝</el-button>
</template>
<span v-else class="el-text--info" style="font-size:12px">已处理</span>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:16px"
:current-page="page + 1" :page-size="size" :total="total"
layout="prev, pager, next" @current-change="(p: number) => { page = p - 1; loadRequests() }" />
<el-dialog v-model="showReview" :title="reviewAction === 'approve' ? '通过申请' : '拒绝申请'" width="420px">
<el-form>
<el-form-item label="审核备注">
<el-input v-model="reviewNote" type="textarea" :rows="3" placeholder="可选备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showReview = false">取消</el-button>
<el-button :type="reviewAction === 'approve' ? 'success' : 'danger'"
:loading="submitting" @click="submitReview">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { opsApi, type ServiceRequest } from '@/api/ops'
const requests = ref<ServiceRequest[]>([])
const loading = ref(false)
const statusFilter = ref('')
const page = ref(0)
const size = ref(20)
const total = ref(0)
const showReview = ref(false)
const reviewAction = ref<'approve' | 'reject'>('approve')
const reviewNote = ref('')
const currentRequest = ref<ServiceRequest | null>(null)
const submitting = ref(false)
async function loadRequests() {
loading.value = true
try {
const res = await opsApi.listServiceRequests(statusFilter.value, page.value, size.value)
requests.value = res.data.data.content
total.value = res.data.data.total
} catch {
requests.value = []
} finally {
loading.value = false
}
}
function openReview(row: ServiceRequest, action: 'approve' | 'reject') {
currentRequest.value = row
reviewAction.value = action
reviewNote.value = ''
showReview.value = true
}
async function submitReview() {
if (!currentRequest.value) return
submitting.value = true
try {
if (reviewAction.value === 'approve') {
await opsApi.approveRequest(currentRequest.value.id, reviewNote.value)
ElMessage.success('已通过申请,服务已开通')
} else {
await opsApi.rejectRequest(currentRequest.value.id, reviewNote.value)
ElMessage.warning('已拒绝申请')
}
showReview.value = false
loadRequests()
} catch {
ElMessage.error('操作失败')
} finally {
submitting.value = false
}
}
function statusType(status: string) {
return status === 'APPROVED' ? 'success' : status === 'REJECTED' ? 'danger' : 'warning'
}
function statusLabel(status: string) {
return status === 'APPROVED' ? '已通过' : status === 'REJECTED' ? '已拒绝' : '待审核'
}
function fmt(dt: string) {
return new Date(dt).toLocaleString('zh-CN')
}
onMounted(loadRequests)
</script>