feat: add template center list
This commit is contained in:
@@ -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(...),
|
||||
|
||||
@@ -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. 执行后端路由检查和前端生产构建验证。
|
||||
- **执行结果**: 模板中心已具备模板列表展示、失败重试和进入标注页能力。
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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 3、Vite、Ant Design Vue、Router、Pinia、Axios 与全局样式变量已接入。</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;
|
||||
|
||||
Reference in New Issue
Block a user