From 5a24edc3b006e06168d80dc4f33fc3690eca6f7c Mon Sep 17 00:00:00 2001 From: zwt13703 Date: Wed, 1 Jul 2026 21:48:11 +0800 Subject: [PATCH] feat: connect template editor to APIs --- docs/tasks/task_detail_2026_07_01.md | 12 ++++++++ frontend/src/api/templates.ts | 43 ++++++++++++++++++++++++++ frontend/src/stores/templateStore.ts | 45 ++++++++++++++++++++++++---- frontend/src/views/TemplateEdit.vue | 5 +++- 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 frontend/src/api/templates.ts diff --git a/docs/tasks/task_detail_2026_07_01.md b/docs/tasks/task_detail_2026_07_01.md index a41d485..7e1d4c8 100644 --- a/docs/tasks/task_detail_2026_07_01.md +++ b/docs/tasks/task_detail_2026_07_01.md @@ -152,3 +152,15 @@ 8. `TopToolbar.vue` 接入模板信息和保存状态。 9. 执行前端构建验证。 - **执行结果**: 完成任务 067-073,基础 MVP 任务清单 001-073 已全部标注完成。 + +## 会话 ID: 20260701-frontend-api-integration +- [2026-07-01 21:47:57] +- **执行原因**: 用户要求提交当前代码后继续推进项目实现。 +- **执行过程**: + 1. 检查 git 工作区,确认上一轮代码已提交且无未提交变更,因此不创建空提交。 + 2. 新增前端模板 API 模块,封装 `GET /api/templates/{id}` 与 `POST /api/templates/{id}/blocks/{blockId}/config`。 + 3. 改造 `templateStore`,从后端模板详情接口加载模板、预览 HTML、blocks、tree 和 configs。 + 4. 改造配置保存逻辑,保存时调用后端 block config upsert 接口。 + 5. 改造 `TemplateEdit.vue`,从路由参数读取模板 ID 后加载对应模板。 + 6. 执行前端生产构建验证。 +- **执行结果**: 前端模板编辑页已从纯 mock 数据推进到后端接口驱动,构建验证通过。 diff --git a/frontend/src/api/templates.ts b/frontend/src/api/templates.ts new file mode 100644 index 0000000..443b1c0 --- /dev/null +++ b/frontend/src/api/templates.ts @@ -0,0 +1,43 @@ +import { apiClient } from "./base"; +import type { BlockConfig } from "@/stores/templateStore"; + +export type TemplateDetailResponse = { + template: { + id: number; + name: string; + version: string; + }; + preview_html: string; + blocks: Array<{ + block_id: string; + block_type?: string; + type?: string; + text_preview?: string; + text?: string; + level?: number; + sort_order?: number; + table_rows?: number; + table_cols?: number; + }>; + tree: Array>; + configs: Record; +}; + +export async function getTemplateDetail(templateId: number) { + const response = await apiClient.get<{ data: TemplateDetailResponse; message: string }>( + `/templates/${templateId}`, + ); + return response.data.data; +} + +export async function saveTemplateBlockConfig( + templateId: number, + blockId: string, + config: BlockConfig, +) { + const response = await apiClient.post<{ data: BlockConfig; message: string }>( + `/templates/${templateId}/blocks/${blockId}/config`, + config, + ); + return response.data.data; +} diff --git a/frontend/src/stores/templateStore.ts b/frontend/src/stores/templateStore.ts index 46bc8fe..56cbdef 100644 --- a/frontend/src/stores/templateStore.ts +++ b/frontend/src/stores/templateStore.ts @@ -1,4 +1,5 @@ import { defineStore } from "pinia"; +import { getTemplateDetail, saveTemplateBlockConfig } from "@/api/templates"; export type RegionType = "ai_generate" | "manual" | "fixed" | "table"; @@ -48,6 +49,21 @@ const mockPreviewHtml = ` `; +function normalizeTreeNode(node: Record): TreeNode { + const id = String(node.id ?? node.block_id ?? ""); + const name = String(node.name ?? node.text ?? node.text_preview ?? id); + const children = Array.isArray(node.children) + ? node.children.map((child) => normalizeTreeNode(child as Record)) + : undefined; + + return { + id, + name, + status: "empty", + children, + }; +} + export const useTemplateStore = defineStore("template", { state: () => ({ loading: false, @@ -112,20 +128,37 @@ export const useTemplateStore = defineStore("template", { configById: (state) => (blockId: string) => state.configs[blockId], }, actions: { - async loadTemplate() { + async loadTemplate(templateId = 1) { this.loading = true; this.error = ""; try { - await new Promise((resolve) => window.setTimeout(resolve, 200)); - } catch { - this.error = "模板加载失败"; + const detail = await getTemplateDetail(templateId); + this.template = { + id: detail.template.id, + name: detail.template.name, + version: detail.template.version, + }; + this.previewHtml = detail.preview_html; + this.blocks = detail.blocks.map((block) => ({ + block_id: block.block_id, + block_type: (block.block_type ?? block.type ?? "paragraph") as TemplateBlock["block_type"], + text_preview: block.text_preview ?? block.text ?? "", + level: block.level ?? 0, + sort_order: block.sort_order ?? 0, + table_rows: block.table_rows, + table_cols: block.table_cols, + })); + this.tree = detail.tree.map((node) => normalizeTreeNode(node)); + this.configs = detail.configs; + } catch (error) { + this.error = error instanceof Error ? error.message : "模板加载失败"; } finally { this.loading = false; } }, async saveBlockConfig(blockId: string, config: BlockConfig) { - await new Promise((resolve) => window.setTimeout(resolve, 400)); - this.configs[blockId] = { ...config }; + const savedConfig = await saveTemplateBlockConfig(this.template.id, blockId, config); + this.configs[blockId] = { ...savedConfig }; }, }, }); diff --git a/frontend/src/views/TemplateEdit.vue b/frontend/src/views/TemplateEdit.vue index ddd863b..a461c21 100644 --- a/frontend/src/views/TemplateEdit.vue +++ b/frontend/src/views/TemplateEdit.vue @@ -1,5 +1,6 @@