You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
9.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>