From 5948334d15d36f1222d509bb32e9034bb213a124 Mon Sep 17 00:00:00 2001 From: zwt13703 Date: Wed, 1 Jul 2026 21:49:06 +0800 Subject: [PATCH] feat: add template upload UI flow --- docs/tasks/task_detail_2026_07_01.md | 12 +++ frontend/src/api/base.ts | 3 - frontend/src/api/templates.ts | 20 ++++ frontend/src/views/TemplateCenter.vue | 130 +++++++++++++++++++++++++- 4 files changed, 160 insertions(+), 5 deletions(-) diff --git a/docs/tasks/task_detail_2026_07_01.md b/docs/tasks/task_detail_2026_07_01.md index 7e1d4c8..a22a828 100644 --- a/docs/tasks/task_detail_2026_07_01.md +++ b/docs/tasks/task_detail_2026_07_01.md @@ -164,3 +164,15 @@ 5. 改造 `TemplateEdit.vue`,从路由参数读取模板 ID 后加载对应模板。 6. 执行前端生产构建验证。 - **执行结果**: 前端模板编辑页已从纯 mock 数据推进到后端接口驱动,构建验证通过。 + +## 会话 ID: 20260701-template-upload-ui +- [2026-07-01 21:48:56] +- **执行原因**: 继续推进模板创建到编辑页的真实使用闭环。 +- **执行过程**: + 1. 调整 axios base 配置,移除固定 `Content-Type`,支持 JSON 和 multipart 自动识别。 + 2. 在前端模板 API 模块中新增 `uploadTemplate`,封装 `POST /api/templates/upload`。 + 3. 在模板中心增加上传模板弹窗,包含模板名称、模板类型和 `.docx` 文件选择。 + 4. 上传成功后展示解析区域数量,并跳转到 `/templates/{template_id}/edit`。 + 5. 上传失败时给出后端服务或数据库未启动的提示。 + 6. 执行前端生产构建验证。 +- **执行结果**: 模板中心“上传模板”按钮已接入后端上传接口,成功后可进入模板编辑页。 diff --git a/frontend/src/api/base.ts b/frontend/src/api/base.ts index 05add1f..d8de02b 100644 --- a/frontend/src/api/base.ts +++ b/frontend/src/api/base.ts @@ -3,9 +3,6 @@ import axios from "axios"; export const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000/api", timeout: 15000, - headers: { - "Content-Type": "application/json", - }, }); apiClient.interceptors.response.use( diff --git a/frontend/src/api/templates.ts b/frontend/src/api/templates.ts index 443b1c0..215e272 100644 --- a/frontend/src/api/templates.ts +++ b/frontend/src/api/templates.ts @@ -1,6 +1,13 @@ import { apiClient } from "./base"; import type { BlockConfig } from "@/stores/templateStore"; +export type UploadTemplateResponse = { + template_id: number; + name: string; + block_count: number; + status: string; +}; + export type TemplateDetailResponse = { template: { id: number; @@ -30,6 +37,19 @@ export async function getTemplateDetail(templateId: number) { return response.data.data; } +export async function uploadTemplate(file: File, name: string, type: string) { + const formData = new FormData(); + formData.append("file", file); + formData.append("name", name); + formData.append("type", type); + + const response = await apiClient.post<{ data: UploadTemplateResponse; message: string }>( + "/templates/upload", + formData, + ); + return response.data.data; +} + export async function saveTemplateBlockConfig( templateId: number, blockId: string, diff --git a/frontend/src/views/TemplateCenter.vue b/frontend/src/views/TemplateCenter.vue index f12fd1b..d76f059 100644 --- a/frontend/src/views/TemplateCenter.vue +++ b/frontend/src/views/TemplateCenter.vue @@ -1,11 +1,63 @@ @@ -211,4 +301,40 @@ h1 { margin: 0; color: var(--c-text-muted); } + +.upload-form { + display: grid; + gap: 14px; +} + +.upload-form label { + display: grid; + gap: 6px; + color: var(--c-text-secondary); + font-size: 12px; +} + +.hidden-input { + display: none; +} + +.file-picker button { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 88px; + gap: 8px; + border: 1px dashed var(--c-border); + border-radius: var(--radius-md); + color: var(--c-text-secondary); + background: var(--c-surface); + cursor: pointer; +} + +.file-picker button:hover { + border-color: var(--c-primary); + color: var(--c-primary); + background: var(--c-primary-soft); +}