Files
certificate-system/frontend/src/views/AdminProjects.vue
2026-08-13 18:06:05 +08:00

222 lines
6.8 KiB
Vue

<template>
<section>
<header class="page-head">
<div>
<h2>项目管理</h2>
<p>维护项目代码和导入时使用的证书默认信息</p>
</div>
<el-button type="primary" @click="saveProject">{{ editingId ? "保存修改" : "新增项目" }}</el-button>
</header>
<el-card class="panel" shadow="never">
<el-form class="form-grid" label-position="top">
<el-form-item label="项目代码">
<el-input v-model.trim="form.code" :disabled="!!editingId" maxlength="16" />
</el-form-item>
<el-form-item label="项目名称">
<el-input v-model.trim="form.name" maxlength="128" />
</el-form-item>
<el-form-item label="默认发证单位">
<el-input v-model.trim="form.default_issuer_name" maxlength="128" />
</el-form-item>
<el-form-item label="默认证书模板">
<el-select v-model="form.default_template_code">
<el-option v-for="item in templates" :key="item.code" :label="item.name" :value="item.code" />
</el-select>
</el-form-item>
<el-form-item label="默认课程名称">
<el-input v-model.trim="form.default_course_name" maxlength="128" />
</el-form-item>
<el-form-item label="默认阶段名称">
<el-input v-model.trim="form.default_stage_name" maxlength="128" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status">
<el-option label="启用" value="active" />
<el-option label="停用" value="disabled" />
</el-select>
</el-form-item>
<el-form-item label=" ">
<el-button @click="resetForm">清空</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="panel" shadow="never">
<div class="toolbar">
<el-input v-model.trim="keyword" placeholder="搜索项目代码、名称、课程、阶段或状态" clearable />
</div>
<el-table :data="projects" border>
<el-table-column prop="code" label="项目代码" width="120" />
<el-table-column prop="name" label="项目名称" width="150" />
<el-table-column prop="default_course_name" label="课程名称" min-width="140" />
<el-table-column prop="default_stage_name" label="阶段名称" min-width="120" />
<el-table-column prop="default_issuer_name" label="发证单位" min-width="140" />
<el-table-column label="默认模板" min-width="150">
<template #default="{ row }">{{ templateName(row.default_template_code) }}</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 'active' ? 'success' : 'info'">
{{ statusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="editProject(row)">修改</el-button>
<el-button size="small" @click="toggleStatus(row)">
{{ row.status === "active" ? "停用" : "启用" }}
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</section>
</template>
<script setup lang="ts">
import { ElMessage } from "element-plus";
import { computed, onMounted, reactive, ref } from "vue";
import { http, type CertificateTemplate, type ProjectCourse } from "../api";
const allProjects = ref<ProjectCourse[]>([]);
const keyword = ref("");
const editingId = ref<number | null>(null);
const templates = ref<CertificateTemplate[]>([]);
const form = reactive({
code: "",
name: "",
default_course_name: "",
default_stage_name: "",
default_issuer_name: "本公司",
default_template_code: "classic",
status: "active",
});
const projects = computed(() => {
const value = keyword.value.toLowerCase();
if (!value) return allProjects.value;
return allProjects.value.filter((item) => {
const text = [
item.code,
item.name,
item.default_course_name || "",
item.default_stage_name || "",
item.default_issuer_name,
statusText(item.status),
].join(" ").toLowerCase();
return text.includes(value);
});
});
function statusText(status: string) {
return status === "active" ? "启用" : "停用";
}
async function loadProjects() {
const { data } = await http.get<ProjectCourse[]>("/admin/projects");
allProjects.value = data;
}
async function loadTemplates() {
const { data } = await http.get<CertificateTemplate[]>("/admin/certificate-templates");
templates.value = data;
}
function templateName(code: string) {
return templates.value.find((item) => item.code === code)?.name || code;
}
function editProject(row: ProjectCourse) {
editingId.value = row.id;
Object.assign(form, {
code: row.code,
name: row.name,
default_course_name: row.default_course_name || "",
default_stage_name: row.default_stage_name || "",
default_issuer_name: row.default_issuer_name,
default_template_code: row.default_template_code,
status: row.status,
});
}
function resetForm() {
editingId.value = null;
Object.assign(form, {
code: "",
name: "",
default_course_name: "",
default_stage_name: "",
default_issuer_name: "本公司",
default_template_code: "classic",
status: "active",
});
}
function projectPayload() {
return {
code: form.code,
name: form.name,
default_course_name: form.default_course_name || null,
default_stage_name: form.default_stage_name || null,
default_issuer_name: form.default_issuer_name,
default_template_code: form.default_template_code,
status: form.status,
};
}
async function saveProject() {
if (!form.code || !form.name || !form.default_issuer_name) {
ElMessage.warning("项目代码、项目名称和发证单位必填");
return;
}
if (editingId.value) {
await http.put(`/admin/projects/${editingId.value}`, projectPayload());
} else {
await http.post("/admin/projects", projectPayload());
}
ElMessage.success("项目已保存");
resetForm();
await loadProjects();
}
async function toggleStatus(row: ProjectCourse) {
await http.put(`/admin/projects/${row.id}`, { status: row.status === "active" ? "disabled" : "active" });
await loadProjects();
}
onMounted(() => Promise.all([loadProjects(), loadTemplates()]));
</script>
<style scoped>
.page-head,
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.page-head p {
margin: 6px 0 0;
color: #64748b;
}
.panel {
margin-bottom: 16px;
border-radius: 8px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.toolbar .el-input {
max-width: 420px;
}
</style>