feat: add template center list

This commit is contained in:
zwt13703
2026-07-01 21:50:07 +08:00
parent 5948334d15
commit 1ff0fdf602
4 changed files with 127 additions and 6 deletions
+6
View File
@@ -17,6 +17,12 @@ from ..services.template_service import create_template_record, save_template_bl
router = APIRouter(prefix="/api/templates", tags=["templates"])
@router.get("")
def list_templates(db: Session = Depends(get_db)):
templates = db.query(Template).order_by(Template.updated_at.desc(), Template.id.desc()).all()
return {"data": [serialize_template(template) for template in templates], "message": "ok"}
@router.post("/upload")
def upload_template(
file: UploadFile = File(...),
+13
View File
@@ -176,3 +176,16 @@
5. 上传失败时给出后端服务或数据库未启动的提示。
6. 执行前端生产构建验证。
- **执行结果**: 模板中心“上传模板”按钮已接入后端上传接口,成功后可进入模板编辑页。
## 会话 ID: 20260701-template-list
- [2026-07-01 21:49:52]
- **执行原因**: 继续完善模板中心,使上传后的模板可在列表中查看和进入标注页。
- **执行过程**:
1. 后端新增 `GET /api/templates`,按更新时间和 ID 倒序返回模板列表。
2. 前端模板 API 模块新增 `getTemplates`
3. 模板中心页面初始化时加载模板列表。
4. 将模板中心空态升级为 Ant Design Vue 表格,展示名称、类型、版本、状态、更新时间和标注操作。
5. 增加列表加载失败提示和重试按钮。
6. 上传成功后刷新模板列表并跳转编辑页。
7. 执行后端路由检查和前端生产构建验证。
- **执行结果**: 模板中心已具备模板列表展示、失败重试和进入标注页能力。
+15
View File
@@ -30,6 +30,21 @@ export type TemplateDetailResponse = {
configs: Record<string, BlockConfig>;
};
export type TemplateListItem = {
id: number;
name: string;
type: string;
version: string;
status: number;
created_at?: string | null;
updated_at?: string | null;
};
export async function getTemplates() {
const response = await apiClient.get<{ data: TemplateListItem[]; message: string }>("/templates");
return response.data.data;
}
export async function getTemplateDetail(templateId: number) {
const response = await apiClient.get<{ data: TemplateDetailResponse; message: string }>(
`/templates/${templateId}`,
+93 -6
View File
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { FileTextOutlined, PlusOutlined } from "@ant-design/icons-vue";
import { EditOutlined, FileTextOutlined, PlusOutlined, ReloadOutlined } from "@ant-design/icons-vue";
import { message } from "ant-design-vue";
import { uploadTemplate } from "@/api/templates";
import { getTemplates, type TemplateListItem, uploadTemplate } from "@/api/templates";
import { useAppStore } from "@/stores/appStore";
const appStore = useAppStore();
@@ -14,11 +14,39 @@ const uploadOpen = ref(false);
const uploading = ref(false);
const fileInputRef = ref<HTMLInputElement | null>(null);
const selectedFile = ref<File | null>(null);
const loading = ref(false);
const loadError = ref("");
const templates = ref<TemplateListItem[]>([]);
const form = ref({
name: "",
type: "report",
});
const tableColumns = [
{ title: "模板名称", dataIndex: "name", key: "name" },
{ title: "类型", dataIndex: "type", key: "type", width: 120 },
{ title: "版本", dataIndex: "version", key: "version", width: 120 },
{ title: "状态", dataIndex: "status", key: "status", width: 100 },
{ title: "更新时间", dataIndex: "updated_at", key: "updated_at", width: 190 },
{ title: "操作", key: "actions", width: 110 },
];
onMounted(() => {
loadTemplates();
});
async function loadTemplates() {
loading.value = true;
loadError.value = "";
try {
templates.value = await getTemplates();
} catch {
loadError.value = "模板列表加载失败,请确认后端服务和数据库已启动";
} finally {
loading.value = false;
}
}
function openUploadModal() {
uploadOpen.value = true;
}
@@ -51,6 +79,7 @@ async function submitUpload() {
const result = await uploadTemplate(selectedFile.value, form.value.name.trim(), form.value.type);
message.success(`模板上传成功,解析 ${result.block_count} 个区域`);
uploadOpen.value = false;
await loadTemplates();
await router.push(`/templates/${result.template_id}/edit`);
} catch {
message.error("模板上传失败,请确认后端服务和数据库已启动");
@@ -58,6 +87,15 @@ async function submitUpload() {
uploading.value = false;
}
}
function openEditor(templateId: number) {
router.push(`/templates/${templateId}/edit`);
}
function formatTime(value?: string | null) {
if (!value) return "-";
return value.replace("T", " ").slice(0, 19);
}
</script>
<template>
@@ -91,10 +129,55 @@ async function submitUpload() {
</header>
<section class="content">
<div class="empty-panel">
<a-alert
v-if="loadError"
class="list-alert"
type="error"
show-icon
:message="loadError"
>
<template #action>
<a-button size="small" @click="loadTemplates">
<template #icon><ReloadOutlined /></template>
重试
</a-button>
</template>
</a-alert>
<a-table
v-if="templates.length"
:columns="tableColumns"
:data-source="templates"
:loading="loading"
:pagination="{ pageSize: 8 }"
row-key="id"
size="middle"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<strong>{{ record.name }}</strong>
</template>
<template v-else-if="column.key === 'status'">
<a-tag :color="record.status === 1 ? 'green' : 'default'">
{{ record.status === 1 ? "启用" : "停用" }}
</a-tag>
</template>
<template v-else-if="column.key === 'updated_at'">
{{ formatTime(record.updated_at) }}
</template>
<template v-else-if="column.key === 'actions'">
<a-button size="small" type="link" @click="openEditor(record.id)">
<template #icon><EditOutlined /></template>
标注
</a-button>
</template>
</template>
</a-table>
<div v-else class="empty-panel">
<FileTextOutlined class="empty-icon" />
<h2>前端基础工程已就绪</h2>
<p>Vue 3ViteAnt Design VueRouterPiniaAxios 与全局样式变量已接入</p>
<h2>{{ loading ? "正在加载模板" : "暂无模板" }}</h2>
<p>{{ loadError ? "可点击重试或先上传一个 Word 模板。" : "上传 Word 模板后即可进入标注配置页。" }}</p>
</div>
</section>
</section>
@@ -273,6 +356,10 @@ h1 {
overflow: auto;
}
.list-alert {
margin-bottom: 16px;
}
.empty-panel {
display: grid;
min-height: 320px;