|
|
<template>
|
|
|
<div class="admin-requests-page">
|
|
|
<el-card>
|
|
|
<template #header>
|
|
|
<div class="card-header">
|
|
|
<span>证书审核</span>
|
|
|
<el-radio-group v-model="filterState" @change="loadRequests">
|
|
|
<el-radio-button :label="1">待审核</el-radio-button>
|
|
|
<el-radio-button :label="2">已通过</el-radio-button>
|
|
|
<el-radio-button :label="3">已拒绝</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<el-table :data="requests" v-loading="loading" style="width: 100%">
|
|
|
<el-table-column prop="id" label="申请ID" width="100" />
|
|
|
<el-table-column prop="username" label="用户名" width="120" />
|
|
|
<el-table-column prop="common_name" label="域名" />
|
|
|
<el-table-column prop="organization" label="组织" />
|
|
|
<el-table-column prop="email_address" label="邮箱" />
|
|
|
<el-table-column prop="state_text" label="状态" width="100">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="getStateType(row.state)">
|
|
|
{{ row.state_text }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="created_at" label="申请时间" width="180">
|
|
|
<template #default="{ row }">
|
|
|
{{ formatDate(row.created_at) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="280">
|
|
|
<template #default="{ row }">
|
|
|
<el-button size="small" @click="viewDetail(row)">查看详情</el-button>
|
|
|
<template v-if="filterState === 1">
|
|
|
<el-button size="small" type="success" @click="handleApprove(row)">
|
|
|
同意
|
|
|
</el-button>
|
|
|
<el-button size="small" type="danger" @click="handleReject(row)">
|
|
|
拒绝
|
|
|
</el-button>
|
|
|
</template>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 详情对话框 -->
|
|
|
<el-dialog v-model="detailDialogVisible" title="申请详情" width="80%">
|
|
|
<div v-if="selectedRequest" class="request-detail" v-loading="detailLoading">
|
|
|
<el-tabs>
|
|
|
<el-tab-pane label="申请信息">
|
|
|
<el-descriptions :column="2" border>
|
|
|
<el-descriptions-item label="申请ID">{{ selectedRequest.id }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="用户名">{{ selectedRequest.username || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="状态">
|
|
|
<el-tag :type="getStateType(selectedRequest.state)">
|
|
|
{{ selectedRequest.state_text }}
|
|
|
</el-tag>
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="申请时间">{{ formatDate(selectedRequest.created_at) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="国家">{{ selectedRequest.country || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="省/市">{{ selectedRequest.province || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="地区">{{ selectedRequest.locality || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="组织">{{ selectedRequest.organization || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="部门">{{ selectedRequest.organization_unit_name || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="域名">{{ selectedRequest.common_name }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="邮箱">{{ selectedRequest.email_address || '-' }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
</el-tab-pane>
|
|
|
|
|
|
<!-- 证书信息(如果已签发) -->
|
|
|
<el-tab-pane label="证书信息" v-if="selectedRequest.certificate">
|
|
|
<el-descriptions :column="2" border>
|
|
|
<el-descriptions-item label="证书ID">{{ selectedRequest.certificate.id }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="序列号">{{ selectedRequest.certificate.serial_number }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="状态">
|
|
|
<el-tag :type="selectedRequest.certificate.state === 1 ? 'success' : 'danger'">
|
|
|
{{ selectedRequest.certificate.state_text }}
|
|
|
</el-tag>
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="创建时间">{{ formatDate(selectedRequest.certificate.created_at) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="过期时间">{{ formatDate(selectedRequest.certificate.expire_time) }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
|
|
|
<div v-if="selectedRequest.certificate.certificate_info" class="cert-info-section" style="margin-top: 20px">
|
|
|
<h3>证书详细信息</h3>
|
|
|
<el-descriptions :column="2" border>
|
|
|
<el-descriptions-item label="国家" v-if="selectedRequest.certificate.certificate_info.subject.country">
|
|
|
{{ selectedRequest.certificate.certificate_info.subject.country }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="省/市" v-if="selectedRequest.certificate.certificate_info.subject.province">
|
|
|
{{ selectedRequest.certificate.certificate_info.subject.province }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="组织" v-if="selectedRequest.certificate.certificate_info.subject.organization">
|
|
|
{{ selectedRequest.certificate.certificate_info.subject.organization }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="域名">
|
|
|
{{ selectedRequest.certificate.certificate_info.subject.common_name }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="颁发者">
|
|
|
{{ selectedRequest.certificate.certificate_info.issuer.common_name || selectedRequest.certificate.certificate_info.issuer.organization }}
|
|
|
</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
</div>
|
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import { ref, onMounted } from 'vue'
|
|
|
import { getPendingRequests, approveRequest, rejectRequest, getRequestDetail } from '@/api/admin'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
export default {
|
|
|
name: 'AdminRequests',
|
|
|
setup() {
|
|
|
const requests = ref([])
|
|
|
const loading = ref(false)
|
|
|
const detailLoading = ref(false)
|
|
|
const filterState = ref(1)
|
|
|
const detailDialogVisible = ref(false)
|
|
|
const selectedRequest = ref(null)
|
|
|
|
|
|
const loadRequests = async () => {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
const res = await getPendingRequests(filterState.value)
|
|
|
requests.value = res.data
|
|
|
} catch (error) {
|
|
|
console.error(error)
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const handleApprove = (request) => {
|
|
|
ElMessageBox.confirm('确定要通过此证书申请吗?', '提示', {
|
|
|
confirmButtonText: '确定',
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning'
|
|
|
}).then(async () => {
|
|
|
try {
|
|
|
await approveRequest(request.id)
|
|
|
ElMessage.success('申请已通过,证书已签发')
|
|
|
loadRequests()
|
|
|
} catch (error) {
|
|
|
console.error(error)
|
|
|
}
|
|
|
}).catch(() => {})
|
|
|
}
|
|
|
|
|
|
const handleReject = (request) => {
|
|
|
ElMessageBox.confirm('确定要拒绝此证书申请吗?', '提示', {
|
|
|
confirmButtonText: '确定',
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning'
|
|
|
}).then(async () => {
|
|
|
try {
|
|
|
await rejectRequest(request.id)
|
|
|
ElMessage.success('申请已拒绝')
|
|
|
loadRequests()
|
|
|
} catch (error) {
|
|
|
console.error(error)
|
|
|
}
|
|
|
}).catch(() => {})
|
|
|
}
|
|
|
|
|
|
const viewDetail = async (request) => {
|
|
|
detailLoading.value = true
|
|
|
detailDialogVisible.value = true
|
|
|
try {
|
|
|
const res = await getRequestDetail(request.id)
|
|
|
selectedRequest.value = res.data
|
|
|
} catch (error) {
|
|
|
console.error(error)
|
|
|
// 如果获取详情失败,使用当前数据
|
|
|
selectedRequest.value = request
|
|
|
} finally {
|
|
|
detailLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const getStateType = (state) => {
|
|
|
const types = { 1: 'warning', 2: 'success', 3: 'danger' }
|
|
|
return types[state] || ''
|
|
|
}
|
|
|
|
|
|
const formatDate = (dateString) => {
|
|
|
if (!dateString) return '-'
|
|
|
// 后端返回的时间是北京时间(+08:00),直接格式化显示
|
|
|
const date = new Date(dateString)
|
|
|
// 使用toLocaleString,但指定时区为Asia/Shanghai,确保显示北京时间
|
|
|
return date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
loadRequests()
|
|
|
})
|
|
|
|
|
|
return {
|
|
|
requests,
|
|
|
loading,
|
|
|
detailLoading,
|
|
|
filterState,
|
|
|
detailDialogVisible,
|
|
|
selectedRequest,
|
|
|
loadRequests,
|
|
|
handleApprove,
|
|
|
handleReject,
|
|
|
viewDetail,
|
|
|
getStateType,
|
|
|
formatDate
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.admin-requests-page {
|
|
|
max-width: 1200px;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
.card-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.request-detail {
|
|
|
padding: 20px 0;
|
|
|
}
|
|
|
</style>
|
|
|
|