新增多证书模板管理流程
This commit is contained in:
@@ -15,6 +15,17 @@
|
||||
title="课程开始日期、课程结束日期和发证日期均为必填项,请统一使用 YYYY-MM-DD 格式,例如 2026-06-01。"
|
||||
/>
|
||||
|
||||
<div class="batch-template">
|
||||
<div>
|
||||
<strong>本批次证书模板</strong>
|
||||
<span>确认导入后,整批证书都会固化为所选模板。</span>
|
||||
</div>
|
||||
<el-select v-model="templateCode" placeholder="选择证书模板">
|
||||
<el-option v-for="item in templates" :key="item.code" :label="item.name" :value="item.code" />
|
||||
</el-select>
|
||||
<img v-if="selectedTemplate" :src="selectedTemplate.preview_url" :alt="selectedTemplate.name" />
|
||||
</div>
|
||||
|
||||
<el-upload
|
||||
class="upload"
|
||||
drag
|
||||
@@ -34,6 +45,9 @@
|
||||
<el-table :data="batches" border>
|
||||
<el-table-column prop="id" label="批次ID" width="90" />
|
||||
<el-table-column prop="filename" label="文件名" min-width="220" />
|
||||
<el-table-column label="证书模板" width="170">
|
||||
<template #default="{ row }">{{ templateName(row.template_code) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="120">
|
||||
<template #default="{ row }">{{ statusText(row.status) }}</template>
|
||||
</el-table-column>
|
||||
@@ -99,10 +113,12 @@ import { UploadFilled } from "@element-plus/icons-vue";
|
||||
import { ElMessage, ElMessageBox, type UploadFile } from "element-plus";
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
import { http, type ImportBatch, type PdfPregenerationJob } from "../api";
|
||||
import { http, type CertificateTemplate, type ImportBatch, type PdfPregenerationJob } from "../api";
|
||||
import { apiErrorMessage, downloadFile } from "../download";
|
||||
|
||||
const batches = ref<ImportBatch[]>([]);
|
||||
const templates = ref<CertificateTemplate[]>([]);
|
||||
const templateCode = ref("classic");
|
||||
const selectedFile = ref<File | null>(null);
|
||||
const uploading = ref(false);
|
||||
const confirmingId = ref<number | null>(null);
|
||||
@@ -112,6 +128,11 @@ const pregenerationJob = ref<PdfPregenerationJob | null>(null);
|
||||
let pregenerationTimer: number | null = null;
|
||||
|
||||
const pregenerationDone = computed(() => ["completed", "completed_with_errors"].includes(pregenerationJob.value?.status || ""));
|
||||
const selectedTemplate = computed(() => templates.value.find((item) => item.code === templateCode.value));
|
||||
|
||||
function templateName(code: string) {
|
||||
return templates.value.find((item) => item.code === code)?.name || code;
|
||||
}
|
||||
|
||||
function statusText(status: string) {
|
||||
const labels: Record<string, string> = {
|
||||
@@ -136,12 +157,18 @@ async function loadBatches() {
|
||||
batches.value = data;
|
||||
}
|
||||
|
||||
async function loadTemplates() {
|
||||
const { data } = await http.get<CertificateTemplate[]>("/admin/certificate-templates");
|
||||
templates.value = data;
|
||||
}
|
||||
|
||||
async function uploadFile() {
|
||||
if (!selectedFile.value) return;
|
||||
uploading.value = true;
|
||||
try {
|
||||
const body = new FormData();
|
||||
body.append("file", selectedFile.value);
|
||||
body.append("template_code", templateCode.value);
|
||||
await http.post("/admin/import-batches", body);
|
||||
ElMessage.success("文件已上传并完成校验,请检查结果后点击确认导入");
|
||||
selectedFile.value = null;
|
||||
@@ -213,7 +240,7 @@ function stopPregenerationPolling() {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadBatches);
|
||||
onMounted(() => Promise.all([loadBatches(), loadTemplates()]));
|
||||
onUnmounted(stopPregenerationPolling);
|
||||
</script>
|
||||
|
||||
@@ -235,6 +262,35 @@ onUnmounted(stopPregenerationPolling);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.batch-template {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 1fr) 240px 180px;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
margin-top: 16px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--app-border);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.batch-template div {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.batch-template span {
|
||||
color: var(--app-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.batch-template img {
|
||||
width: 180px;
|
||||
aspect-ratio: 1.36;
|
||||
object-fit: contain;
|
||||
border: 1px solid #edf2f4;
|
||||
}
|
||||
|
||||
.import-tip {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
@@ -289,4 +345,14 @@ onUnmounted(stopPregenerationPolling);
|
||||
color: #b91c1c;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.batch-template {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.batch-template img {
|
||||
width: min(100%, 260px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user