|
@@ -30,14 +30,12 @@
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
<el-form-item>
|
|
|
<el-button
|
|
<el-button
|
|
|
- v-preReClick
|
|
|
|
|
icon="search"
|
|
icon="search"
|
|
|
type="primary"
|
|
type="primary"
|
|
|
@click="onSubmit"
|
|
@click="onSubmit"
|
|
|
>查询
|
|
>查询
|
|
|
</el-button>
|
|
</el-button>
|
|
|
<el-button
|
|
<el-button
|
|
|
- v-preReClick
|
|
|
|
|
icon="refresh"
|
|
icon="refresh"
|
|
|
@click="onReset"
|
|
@click="onReset"
|
|
|
>重置
|
|
>重置
|
|
@@ -50,14 +48,12 @@
|
|
|
|
|
|
|
|
<div class="gva-btn-list">
|
|
<div class="gva-btn-list">
|
|
|
<el-button
|
|
<el-button
|
|
|
- v-preReClick
|
|
|
|
|
icon="plus"
|
|
icon="plus"
|
|
|
type="primary"
|
|
type="primary"
|
|
|
@click="issueCertDialog()"
|
|
@click="issueCertDialog()"
|
|
|
>签发根证书
|
|
>签发根证书
|
|
|
</el-button>
|
|
</el-button>
|
|
|
<el-button
|
|
<el-button
|
|
|
- v-preReClick
|
|
|
|
|
icon="plus"
|
|
icon="plus"
|
|
|
type="primary"
|
|
type="primary"
|
|
|
@click="addCertDialog()"
|
|
@click="addCertDialog()"
|
|
@@ -81,7 +77,7 @@
|
|
|
header-align="center"
|
|
header-align="center"
|
|
|
label="证书名称"
|
|
label="证书名称"
|
|
|
prop="certName"
|
|
prop="certName"
|
|
|
- width="300"
|
|
|
|
|
|
|
+ width="280"
|
|
|
/>
|
|
/>
|
|
|
<el-table-column
|
|
<el-table-column
|
|
|
label="证书算法"
|
|
label="证书算法"
|
|
@@ -118,37 +114,66 @@
|
|
|
<el-table-column
|
|
<el-table-column
|
|
|
fixed="right"
|
|
fixed="right"
|
|
|
label="操作"
|
|
label="操作"
|
|
|
|
|
+ width="260"
|
|
|
>
|
|
>
|
|
|
-<!-- <template #default="scope">
|
|
|
|
|
- <el-button
|
|
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <!-- 下载(保留之前的下拉) -->
|
|
|
|
|
+ <el-dropdown
|
|
|
|
|
+ trigger="hover"
|
|
|
|
|
+ class="download-dropdown"
|
|
|
|
|
+ @command="(command) => handleDownload(scope.row, command)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-preReClick
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ >下载</el-button>
|
|
|
|
|
+ <template #dropdown>
|
|
|
|
|
+ <el-dropdown-menu>
|
|
|
|
|
+ <el-dropdown-item command="pem">下载 PEM 格式</el-dropdown-item>
|
|
|
|
|
+ <el-dropdown-item command="p12">下载 P12 格式</el-dropdown-item>
|
|
|
|
|
+ </el-dropdown-menu>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dropdown>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 冻结 / 解冻:同一按钮,按状态切换 -->
|
|
|
|
|
+<!-- <el-button
|
|
|
|
|
+ v-if="scope.row.status === '1'"
|
|
|
v-preReClick
|
|
v-preReClick
|
|
|
link
|
|
link
|
|
|
type="primary"
|
|
type="primary"
|
|
|
- @click="openEditDialog(scope.row)"
|
|
|
|
|
- >下载
|
|
|
|
|
- </el-button>
|
|
|
|
|
|
|
+ @click="handleFreeze(scope.row)"
|
|
|
|
|
+ >冻结</el-button>
|
|
|
<el-button
|
|
<el-button
|
|
|
|
|
+ v-else-if="scope.row.status === '3'"
|
|
|
v-preReClick
|
|
v-preReClick
|
|
|
link
|
|
link
|
|
|
type="primary"
|
|
type="primary"
|
|
|
- @click="updateStatus(scope.row, '2')"
|
|
|
|
|
- >冻结
|
|
|
|
|
- </el-button>
|
|
|
|
|
|
|
+ @click="handleUnfreeze(scope.row)"
|
|
|
|
|
+ >解冻</el-button>
|
|
|
|
|
+
|
|
|
|
|
+ <!– 续期:有效 / 冻结 / 已过期 可续 –>
|
|
|
<el-button
|
|
<el-button
|
|
|
v-preReClick
|
|
v-preReClick
|
|
|
link
|
|
link
|
|
|
type="primary"
|
|
type="primary"
|
|
|
- @click="updateStatus(scope.row, '1')"
|
|
|
|
|
- >延期
|
|
|
|
|
- </el-button>
|
|
|
|
|
|
|
+ @click="handleRenew(scope.row)"
|
|
|
|
|
+ >续期</el-button>
|
|
|
|
|
+
|
|
|
|
|
+ <!– 吊销:未吊销的都可吊销 –>
|
|
|
<el-button
|
|
<el-button
|
|
|
|
|
+ v-if="scope.row.status !== '4'"
|
|
|
v-preReClick
|
|
v-preReClick
|
|
|
link
|
|
link
|
|
|
- type="primary"
|
|
|
|
|
- @click="updateStatus(scope.row, '1')"
|
|
|
|
|
- >吊销
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </template>-->
|
|
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ @click="handleRevoke(scope.row)"
|
|
|
|
|
+ >吊销</el-button>-->
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ @click="handleDelete(scope.row)"
|
|
|
|
|
+ >删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
|
|
|
|
@@ -159,11 +184,16 @@
|
|
|
@close="handleClose"
|
|
@close="handleClose"
|
|
|
>
|
|
>
|
|
|
<el-form
|
|
<el-form
|
|
|
|
|
+ ref="uploadFormRef"
|
|
|
:model="uploadForm"
|
|
:model="uploadForm"
|
|
|
|
|
+ :rules="uploadRules"
|
|
|
label-width="100px"
|
|
label-width="100px"
|
|
|
>
|
|
>
|
|
|
<!-- 证书名称(可选) -->
|
|
<!-- 证书名称(可选) -->
|
|
|
- <el-form-item label="证书名称">
|
|
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ label="证书名称"
|
|
|
|
|
+ prop="certName"
|
|
|
|
|
+ >
|
|
|
<el-input
|
|
<el-input
|
|
|
v-model="uploadForm.certName"
|
|
v-model="uploadForm.certName"
|
|
|
placeholder="请输入证书名称(可选)"
|
|
placeholder="请输入证书名称(可选)"
|
|
@@ -199,6 +229,7 @@
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="handleClose">取消</el-button>
|
|
<el-button @click="handleClose">取消</el-button>
|
|
|
<el-button
|
|
<el-button
|
|
|
|
|
+ v-preReClick
|
|
|
type="primary"
|
|
type="primary"
|
|
|
:loading="uploading"
|
|
:loading="uploading"
|
|
|
@click="handleSubmit"
|
|
@click="handleSubmit"
|
|
@@ -224,7 +255,10 @@
|
|
|
label="证书算法"
|
|
label="证书算法"
|
|
|
prop="keyAlgorithm"
|
|
prop="keyAlgorithm"
|
|
|
>
|
|
>
|
|
|
- <el-select v-model="addCertForm.keyAlgorithm" placeholder="请选择证书算法">
|
|
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="addCertForm.keyAlgorithm"
|
|
|
|
|
+ placeholder="请选择证书算法"
|
|
|
|
|
+ >
|
|
|
<el-option
|
|
<el-option
|
|
|
v-for="item in keyAlgoOptions"
|
|
v-for="item in keyAlgoOptions"
|
|
|
:key="item.value"
|
|
:key="item.value"
|
|
@@ -238,52 +272,82 @@
|
|
|
label="密钥长度"
|
|
label="密钥长度"
|
|
|
prop="keySize"
|
|
prop="keySize"
|
|
|
>
|
|
>
|
|
|
- <el-select v-model="addCertForm.keySize" placeholder="请选择密钥长度">
|
|
|
|
|
- <el-option label="2048" value="2048" />
|
|
|
|
|
- <el-option label="4096" value="4096" />
|
|
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="addCertForm.keySize"
|
|
|
|
|
+ placeholder="请选择密钥长度"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ label="2048"
|
|
|
|
|
+ value="2048"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ label="4096"
|
|
|
|
|
+ value="4096"
|
|
|
|
|
+ />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="证书名称"
|
|
label="证书名称"
|
|
|
prop="certName"
|
|
prop="certName"
|
|
|
>
|
|
>
|
|
|
- <el-input v-model="addCertForm.certName" placeholder="请输入证书名称" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="addCertForm.certName"
|
|
|
|
|
+ placeholder="请输入证书名称"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="公用名"
|
|
label="公用名"
|
|
|
prop="commonName"
|
|
prop="commonName"
|
|
|
>
|
|
>
|
|
|
- <el-input v-model="addCertForm.commonName" placeholder="请输入证书CN项" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="addCertForm.commonName"
|
|
|
|
|
+ placeholder="请输入证书CN项"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="国家"
|
|
label="国家"
|
|
|
prop="country"
|
|
prop="country"
|
|
|
>
|
|
>
|
|
|
- <el-input v-model="addCertForm.country" placeholder="请输入证书C项" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="addCertForm.country"
|
|
|
|
|
+ placeholder="请输入证书C项"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="组织"
|
|
label="组织"
|
|
|
prop="organization"
|
|
prop="organization"
|
|
|
>
|
|
>
|
|
|
- <el-input v-model="addCertForm.organization" placeholder="请输入证书O项" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="addCertForm.organization"
|
|
|
|
|
+ placeholder="请输入证书O项"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="组织单位"
|
|
label="组织单位"
|
|
|
prop="organizationUnit"
|
|
prop="organizationUnit"
|
|
|
>
|
|
>
|
|
|
- <el-input v-model="addCertForm.organizationUnit" placeholder="请输入证书OU项" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="addCertForm.organizationUnit"
|
|
|
|
|
+ placeholder="请输入证书OU项"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="省份"
|
|
label="省份"
|
|
|
prop="state"
|
|
prop="state"
|
|
|
>
|
|
>
|
|
|
- <el-input v-model="addCertForm.state" placeholder="请输入证书ST项" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="addCertForm.state"
|
|
|
|
|
+ placeholder="请输入证书ST项"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="城市"
|
|
label="城市"
|
|
|
prop="locality"
|
|
prop="locality"
|
|
|
>
|
|
>
|
|
|
- <el-input v-model="addCertForm.locality" placeholder="请输入证书L项" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="addCertForm.locality"
|
|
|
|
|
+ placeholder="请输入证书L项"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item
|
|
<el-form-item
|
|
|
label="证书有效期"
|
|
label="证书有效期"
|
|
@@ -299,7 +363,11 @@
|
|
|
|
|
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="issueCertDialogVisible = false">取消</el-button>
|
|
<el-button @click="issueCertDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="submitAddCertForm">提交</el-button>
|
|
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-preReClick
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="submitAddCertForm"
|
|
|
|
|
+ >提交</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
@@ -321,8 +389,14 @@
|
|
|
</template>
|
|
</template>
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref } from 'vue'
|
|
import { ref } from 'vue'
|
|
|
-import { addRootCa, listCertificates, uploadRoot } from '@/api/sysApplication/certificate'
|
|
|
|
|
-import { ElMessage } from 'element-plus'
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ addRootCa, deleteCertificate, downloadPemCombined, downloadPkcs12, freezeCertificate,
|
|
|
|
|
+ listCertificates,
|
|
|
|
|
+ renewCertificate,
|
|
|
|
|
+ revokeCertificate, unfreezeCertificate,
|
|
|
|
|
+ uploadRoot,
|
|
|
|
|
+} from '@/api/sysApplication/certificate'
|
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
const searchInfo = ref({})
|
|
const searchInfo = ref({})
|
|
|
const addCertFormRef = ref(null)
|
|
const addCertFormRef = ref(null)
|
|
|
const elSearchFormRef = ref(null)
|
|
const elSearchFormRef = ref(null)
|
|
@@ -389,10 +463,83 @@ const addCertForm = ref({
|
|
|
certUsage: 'ROOT_CA',
|
|
certUsage: 'ROOT_CA',
|
|
|
remark: ''
|
|
remark: ''
|
|
|
})
|
|
})
|
|
|
|
|
+const uploadFormRef = ref(null)
|
|
|
|
|
+const uploadRules = {
|
|
|
|
|
+ certName: [{
|
|
|
|
|
+ validator: (_rule, value, callback) => {
|
|
|
|
|
+ if (value && value.length > 30) {
|
|
|
|
|
+ callback(new Error('证书名称长度不能超过30个字符'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }]
|
|
|
|
|
+}
|
|
|
|
|
+const optionalDnFieldRule = (label) => ({
|
|
|
|
|
+ validator: (_rule, value, callback) => {
|
|
|
|
|
+ if (value === '' || value === null || value === undefined) {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.length > 64) {
|
|
|
|
|
+ callback(new Error(`证书${label}项长度不能超过64个字符`))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+})
|
|
|
const addCertRules = {
|
|
const addCertRules = {
|
|
|
- certName: [{ required: true, message: '请输入证书名称', trigger: 'blur' }],
|
|
|
|
|
- commonName: [{ required: true, message: '请输入证书CN项', trigger: 'blur' }],
|
|
|
|
|
- country: [{ required: true, message: '请输入证书C项', trigger: 'blur' }],
|
|
|
|
|
|
|
+ certName: [{ required: true, message: '请输入证书名称', trigger: 'blur' },
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (_rule, value, callback) => {
|
|
|
|
|
+ if (value === '' || value === null || value === undefined) {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.length > 30) {
|
|
|
|
|
+ callback(new Error('证书名称长度不能超过30个字符'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ commonName: [{ required: true, message: '请输入证书CN项', trigger: 'blur' },
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (_rule, value, callback) => {
|
|
|
|
|
+ if (value === '' || value === null || value === undefined) {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value.length > 64) {
|
|
|
|
|
+ callback(new Error('证书CN项长度不能超过64个字符'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ organization: [optionalDnFieldRule('O')],
|
|
|
|
|
+ organizationUnit: [optionalDnFieldRule('OU')],
|
|
|
|
|
+ state: [optionalDnFieldRule('ST')],
|
|
|
|
|
+ locality: [optionalDnFieldRule('L')],
|
|
|
|
|
+ country: [{ required: true, message: '请输入证书C项', trigger: 'blur' },
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (_rule, value, callback) => {
|
|
|
|
|
+ const country = typeof value === 'string' ? value.trim() : ''
|
|
|
|
|
+ if (!/^[A-Z]{2}$/.test(country)) {
|
|
|
|
|
+ callback(new Error('证书C项必须为2位大写国家代码,如 CN'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
keyAlgorithm: [{ required: true, message: '请选择证书算法', trigger: 'change' }],
|
|
keyAlgorithm: [{ required: true, message: '请选择证书算法', trigger: 'change' }],
|
|
|
keySize: [{ required: true, message: '请选择密钥长度', trigger: 'change' }],
|
|
keySize: [{ required: true, message: '请选择密钥长度', trigger: 'change' }],
|
|
|
validityDays: [
|
|
validityDays: [
|
|
@@ -481,29 +628,35 @@ const handleExceed = () => {
|
|
|
|
|
|
|
|
// 提交
|
|
// 提交
|
|
|
const handleSubmit = async() => {
|
|
const handleSubmit = async() => {
|
|
|
- if (!uploadForm.value.file) {
|
|
|
|
|
- ElMessage.warning('请先选择证书文件')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ uploadFormRef.value?.validate(async valid => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
|
|
|
- const formData = new FormData()
|
|
|
|
|
- formData.append('file', uploadForm.value.file)
|
|
|
|
|
- if (uploadForm.value.certName) {
|
|
|
|
|
- formData.append('certName', uploadForm.value.certName)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!uploadForm.value.file) {
|
|
|
|
|
+ ElMessage.warning('请先选择证书文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- uploading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const res = uploadRoot(formData)
|
|
|
|
|
- // 根据你的 ResponseDTO 结构判断
|
|
|
|
|
- ElMessage.success("上传根证书成功")
|
|
|
|
|
- addCertDialogVisible.value = false
|
|
|
|
|
- await getTableData()
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- ElMessage.error(err.message)
|
|
|
|
|
- } finally {
|
|
|
|
|
- uploading.value = false
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const formData = new FormData()
|
|
|
|
|
+ formData.append('file', uploadForm.value.file)
|
|
|
|
|
+ if (uploadForm.value.certName) {
|
|
|
|
|
+ formData.append('certName', uploadForm.value.certName)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uploading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = uploadRoot(formData)
|
|
|
|
|
+ // 根据你的 ResponseDTO 结构判断
|
|
|
|
|
+ if (res.code === 100000) {
|
|
|
|
|
+ ElMessage.success('上传根证书成功')
|
|
|
|
|
+ addCertDialogVisible.value = false
|
|
|
|
|
+ await getTableData()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ ElMessage.error(err.message)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
const fileList = ref([])
|
|
const fileList = ref([])
|
|
|
const uploadRef = ref(null)
|
|
const uploadRef = ref(null)
|
|
@@ -516,8 +669,165 @@ const handleClose = () => {
|
|
|
fileList.value = []
|
|
fileList.value = []
|
|
|
uploadRef.value?.clearFiles()
|
|
uploadRef.value?.clearFiles()
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 触发下载
|
|
|
|
|
+ * @param {Object} row - 表格行数据
|
|
|
|
|
+ * @param {String} format - 'pem' | 'p12'
|
|
|
|
|
+ */
|
|
|
|
|
+const handleDownload = async(row, format) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ let res
|
|
|
|
|
+ if (format === 'pem') {
|
|
|
|
|
+ res = await downloadPemCombined({ id: row.id })
|
|
|
|
|
+ saveBlob(res, `certificate-${row.id}.pem`)
|
|
|
|
|
+ } else if (format === 'p12') {
|
|
|
|
|
+ // p12 需要密码,弹框让用户输入
|
|
|
|
|
+ const { value: password } = await ElMessageBox.prompt(
|
|
|
|
|
+ '请输入 P12 文件的保护密码',
|
|
|
|
|
+ '下载 P12',
|
|
|
|
|
+ {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ inputType: 'password',
|
|
|
|
|
+ inputValidator: (val) => {
|
|
|
|
|
+ if (!val) return '密码不能为空'
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ res = await downloadPkcs12({ id: row.id, password })
|
|
|
|
|
+ saveBlob(res, `certificate-${row.id}.p12`)
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ // 用户取消弹框
|
|
|
|
|
+ if (err === 'cancel' || err === 'close') return
|
|
|
|
|
+ console.error('下载失败', err)
|
|
|
|
|
+ ElMessage.error('下载失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 冻结
|
|
|
|
|
+const handleFreeze = async(row) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm(`确认冻结证书「${row.certSerialNumber}」吗?`, '冻结确认', {
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消'
|
|
|
|
|
+ })
|
|
|
|
|
+ await freezeCertificate({ id: row.id })
|
|
|
|
|
+ ElMessage.success('冻结成功')
|
|
|
|
|
+ await getTableData()
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ if (err === 'cancel' || err === 'close') return
|
|
|
|
|
+ console.error(err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 解冻
|
|
|
|
|
+const handleUnfreeze = async(row) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm(`确认解冻证书「${row.certSerialNumber}」吗?`, '解冻确认', {
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消'
|
|
|
|
|
+ })
|
|
|
|
|
+ await unfreezeCertificate({ id: row.id })
|
|
|
|
|
+ ElMessage.success('解冻成功')
|
|
|
|
|
+ await getTableData()
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ if (err === 'cancel' || err === 'close') return
|
|
|
|
|
+ console.error(err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 续期
|
|
|
|
|
+const handleRenew = async(row) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { value } = await ElMessageBox.prompt(
|
|
|
|
|
+ '请输入续期天数',
|
|
|
|
|
+ '证书续期',
|
|
|
|
|
+ {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ inputValue: 365, // 默认值,对应后端 default 365
|
|
|
|
|
+ inputType: 'number',
|
|
|
|
|
+ inputValidator: (val) => {
|
|
|
|
|
+ const num = Number(val)
|
|
|
|
|
+ if (!val || isNaN(num) || num < 1) return '请输入大于等于 1 的天数'
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ const res = await renewCertificate({ id: row.id, validityDays: Number(value) })
|
|
|
|
|
+ if (res.code === 100000) {
|
|
|
|
|
+ ElMessage.success('续期成功')
|
|
|
|
|
+ await getTableData()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ if (err === 'cancel' || err === 'close') return
|
|
|
|
|
+ console.error(err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 吊销
|
|
|
|
|
+const handleRevoke = async(row) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { value } = await ElMessageBox.prompt(
|
|
|
|
|
+ '请输入吊销原因(可选)',
|
|
|
|
|
+ '证书吊销',
|
|
|
|
|
+ {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ inputType: 'textarea',
|
|
|
|
|
+ inputPlaceholder: '请输入吊销原因',
|
|
|
|
|
+ inputValidator: () => true // reason 非必填,不校验
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ await revokeCertificate({ id: row.id, reason: value || '' })
|
|
|
|
|
+ ElMessage.success('吊销成功')
|
|
|
|
|
+ await getTableData()
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ if (err === 'cancel' || err === 'close') return
|
|
|
|
|
+ console.error(err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+const handleDelete = async(row) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm(`确认删除证书「${row.certSerialNumber}」吗?`, '删除确认', {
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消'
|
|
|
|
|
+ })
|
|
|
|
|
+ await deleteCertificate(row.id)
|
|
|
|
|
+ ElMessage.success('删除成功')
|
|
|
|
|
+ await getTableData()
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ if (err === 'cancel' || err === 'close') return
|
|
|
|
|
+ console.error(err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+/**
|
|
|
|
|
+ * 通用 blob 保存工具
|
|
|
|
|
+ */
|
|
|
|
|
+const saveBlob = (res, filename) => {
|
|
|
|
|
+ // 注意:如果 service 拦截器已经处理过 response.data,res 可能直接就是 blob
|
|
|
|
|
+ const blobData = res.data instanceof Blob ? res.data : new Blob([res.data])
|
|
|
|
|
+ const url = window.URL.createObjectURL(blobData)
|
|
|
|
|
+ const link = document.createElement('a')
|
|
|
|
|
+ link.href = url
|
|
|
|
|
+ link.download = filename
|
|
|
|
|
+ document.body.appendChild(link)
|
|
|
|
|
+ link.click()
|
|
|
|
|
+ document.body.removeChild(link)
|
|
|
|
|
+ window.URL.revokeObjectURL(url)
|
|
|
|
|
+}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|
|
|
-
|
|
|
|
|
|
|
+.download-dropdown {
|
|
|
|
|
+ vertical-align: middle;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ margin-right: 12px;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|