新增课程起止日期管理

This commit is contained in:
Certificate System
2026-08-13 14:39:44 +08:00
parent 2e1a639179
commit c9667d56fb
20 changed files with 313 additions and 33 deletions

View File

@@ -19,6 +19,8 @@ export interface PublicCertificate {
project_code: string;
course_name: string | null;
stage_name: string | null;
course_start_date: string | null;
course_end_date: string | null;
issue_date: string;
issuer_name: string;
status: string;
@@ -70,6 +72,8 @@ export interface AdminCertificate {
class_name: string | null;
course_name: string | null;
stage_name: string | null;
course_start_date: string | null;
course_end_date: string | null;
issue_date: string;
issuer_name: string;
status: string;

View File

@@ -21,6 +21,13 @@
<el-form-item label="发证日期">
<el-date-picker v-model="form.issue_date" type="date" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="课程开始日期">
<el-date-picker v-model="form.course_start_date" type="date" value-format="YYYY-MM-DD" placeholder="选择开始日期" />
</el-form-item>
<el-form-item label="课程结束日期">
<el-date-picker v-model="form.course_end_date" type="date" value-format="YYYY-MM-DD" placeholder="选择结束日期" />
<div class="form-tip">证书正文将显示这段课程起止时间结束日期不能早于开始日期</div>
</el-form-item>
<el-form-item label="课程名称">
<el-input v-model.trim="form.course_name" />
</el-form-item>
@@ -64,6 +71,9 @@
<el-table-column prop="learner_name" label="学员姓名" width="100" show-overflow-tooltip />
<el-table-column prop="project_code" label="项目" width="72" />
<el-table-column prop="certificate_name" label="证书名称" min-width="140" show-overflow-tooltip />
<el-table-column label="课程时间" width="190">
<template #default="{ row }">{{ coursePeriodText(row) }}</template>
</el-table-column>
<el-table-column prop="issue_date" label="发证日期" width="108" />
<el-table-column label="PDF" width="92">
<template #default="{ row }">
@@ -96,6 +106,7 @@
<el-descriptions-item label="项目/课程/阶段">
{{ preview.project_code }} {{ preview.course_name || "" }} {{ preview.stage_name || "" }}
</el-descriptions-item>
<el-descriptions-item label="课程时间">{{ coursePeriodText(preview) }}</el-descriptions-item>
<el-descriptions-item label="发证日期">{{ preview.issue_date }}</el-descriptions-item>
<el-descriptions-item label="发证单位">{{ preview.issuer_name }}</el-descriptions-item>
<el-descriptions-item label="公开链接">
@@ -160,6 +171,8 @@ const form = reactive({
project_code: "",
course_name: "",
stage_name: "",
course_start_date: "",
course_end_date: "",
issue_date: "",
issuer_name: "",
remark: "",
@@ -175,6 +188,12 @@ function pdfStatusText(status: string) {
return { generated: "已生成", cleaned: "已清理", not_generated: "未生成" }[status] || status;
}
function coursePeriodText(certificate: Pick<AdminCertificate, "course_start_date" | "course_end_date" | "issue_date">) {
const start = certificate.course_start_date || certificate.issue_date;
const end = certificate.course_end_date || certificate.issue_date;
return `${start}${end}`;
}
const pregenerationDone = computed(() => ["completed", "completed_with_errors"].includes(pregenerationJob.value?.status || ""));
async function loadProjects() {
@@ -204,8 +223,12 @@ function handleSelectionChange(rows: AdminCertificate[]) {
}
async function createCertificate() {
if (!form.learner_id || !form.project_code || !form.issue_date || !form.issuer_name) {
ElMessage.warning("请填写学员ID、项目、发证日期和发证单位");
if (!form.learner_id || !form.project_code || !form.course_start_date || !form.course_end_date || !form.issue_date || !form.issuer_name) {
ElMessage.warning("请填写学员ID、项目、课程起止日期、发证日期和发证单位");
return;
}
if (form.course_end_date < form.course_start_date) {
ElMessage.warning("课程结束日期不能早于课程开始日期");
return;
}
await http.post("/admin/certificates", {
@@ -213,6 +236,8 @@ async function createCertificate() {
project_code: form.project_code,
course_name: form.course_name || null,
stage_name: form.stage_name || null,
course_start_date: form.course_start_date,
course_end_date: form.course_end_date,
issue_date: form.issue_date,
issuer_name: form.issuer_name,
remark: form.remark || null,

View File

@@ -8,6 +8,13 @@
<el-button @click="downloadTemplate">下载模板</el-button>
</header>
<el-alert
class="import-tip"
type="info"
:closable="false"
title="课程开始日期、课程结束日期和发证日期均为必填项,请统一使用 YYYY-MM-DD 格式,例如 2026-06-01。"
/>
<el-upload
class="upload"
drag
@@ -224,9 +231,14 @@ onUnmounted(stopPregenerationPolling);
}
.upload {
margin-top: 16px;
margin-bottom: 12px;
}
.import-tip {
margin-bottom: 4px;
}
.upload-icon {
font-size: 36px;
color: #208a87;

View File

@@ -17,6 +17,10 @@
<dt>证书编号</dt>
<dd>{{ certificate.certificate_no }}</dd>
</div>
<div>
<dt>课程时间</dt>
<dd>{{ coursePeriodText }}</dd>
</div>
<div>
<dt>发证日期</dt>
<dd>{{ certificate.issue_date }}</dd>
@@ -36,7 +40,7 @@
<script setup lang="ts">
import { ElLoading, ElMessage } from "element-plus";
import { onMounted, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { http, type PublicCertificate } from "../api";
@@ -48,6 +52,12 @@ const certificate = ref<PublicCertificate | null>(null);
const message = ref("");
const messageType = ref<"success" | "warning" | "error">("success");
const downloading = ref(false);
const coursePeriodText = computed(() => {
if (!certificate.value) return "";
const start = certificate.value.course_start_date || certificate.value.issue_date;
const end = certificate.value.course_end_date || certificate.value.issue_date;
return `${start}${end}`;
});
function statusText(status: string) {
return status === "valid" ? "有效" : "已作废";

View File

@@ -9,6 +9,8 @@
<el-descriptions-item label="证书编号">{{ certificate.certificate_no }}</el-descriptions-item>
<el-descriptions-item label="学员姓名">{{ certificate.learner_name }}</el-descriptions-item>
<el-descriptions-item label="证书名称">{{ certificate.certificate_name }}</el-descriptions-item>
<el-descriptions-item label="课程时间">{{ coursePeriodText }}</el-descriptions-item>
<el-descriptions-item label="发证日期">{{ certificate.issue_date }}</el-descriptions-item>
<el-descriptions-item label="发证单位">{{ certificate.issuer_name }}</el-descriptions-item>
</el-descriptions>
</section>
@@ -16,7 +18,7 @@
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { http, type PublicCertificate } from "../api";
@@ -26,6 +28,12 @@ const token = String(route.params.token);
const certificate = ref<PublicCertificate | null>(null);
const message = ref("正在核验证书信息");
const messageType = ref<"success" | "warning" | "error" | "info">("info");
const coursePeriodText = computed(() => {
if (!certificate.value) return "";
const start = certificate.value.course_start_date || certificate.value.issue_date;
const end = certificate.value.course_end_date || certificate.value.issue_date;
return `${start}${end}`;
});
async function loadCertificate() {
try {