feat: add template upload UI flow

This commit is contained in:
zwt13703
2026-07-01 21:49:06 +08:00
parent 5a24edc3b0
commit 5948334d15
4 changed files with 160 additions and 5 deletions
-3
View File
@@ -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(
+20
View File
@@ -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,
+128 -2
View File
@@ -1,11 +1,63 @@
<script setup lang="ts">
import { computed } from "vue";
import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import { FileTextOutlined, PlusOutlined } from "@ant-design/icons-vue";
import { message } from "ant-design-vue";
import { uploadTemplate } from "@/api/templates";
import { useAppStore } from "@/stores/appStore";
const appStore = useAppStore();
const router = useRouter();
const statusClass = computed(() => (appStore.saveStatus === "已保存" ? "saved" : "dirty"));
const uploadOpen = ref(false);
const uploading = ref(false);
const fileInputRef = ref<HTMLInputElement | null>(null);
const selectedFile = ref<File | null>(null);
const form = ref({
name: "",
type: "report",
});
function openUploadModal() {
uploadOpen.value = true;
}
function chooseFile() {
fileInputRef.value?.click();
}
function handleFileChange(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0] ?? null;
selectedFile.value = file;
if (file && !form.value.name) {
form.value.name = file.name.replace(/\.docx$/i, "");
}
}
async function submitUpload() {
if (!selectedFile.value) {
message.warning("请先选择 .docx 文件");
return;
}
if (!form.value.name.trim()) {
message.warning("请输入模板名称");
return;
}
uploading.value = true;
try {
const result = await uploadTemplate(selectedFile.value, form.value.name.trim(), form.value.type);
message.success(`模板上传成功,解析 ${result.block_count} 个区域`);
uploadOpen.value = false;
await router.push(`/templates/${result.template_id}/edit`);
} catch {
message.error("模板上传失败,请确认后端服务和数据库已启动");
} finally {
uploading.value = false;
}
}
</script>
<template>
@@ -31,7 +83,7 @@ const statusClass = computed(() => (appStore.saveStatus === "已保存" ? "saved
</div>
<div class="toolbar-actions">
<span class="save-status" :class="statusClass">{{ appStore.saveStatus }}</span>
<a-button type="primary">
<a-button type="primary" @click="openUploadModal">
<template #icon><PlusOutlined /></template>
上传模板
</a-button>
@@ -46,6 +98,44 @@ const statusClass = computed(() => (appStore.saveStatus === "已保存" ? "saved
</div>
</section>
</section>
<a-modal
v-model:open="uploadOpen"
title="上传模板"
:confirm-loading="uploading"
ok-text="上传"
cancel-text="取消"
@ok="submitUpload"
>
<div class="upload-form">
<label>
模板名称
<a-input v-model:value="form.name" placeholder="请输入模板名称" />
</label>
<label>
模板类型
<a-select v-model:value="form.type">
<a-select-option value="report">报告类</a-select-option>
<a-select-option value="official">公文类</a-select-option>
<a-select-option value="summary">总结类</a-select-option>
<a-select-option value="contract">合同类</a-select-option>
</a-select>
</label>
<div class="file-picker">
<input
ref="fileInputRef"
class="hidden-input"
type="file"
accept=".docx"
@change="handleFileChange"
/>
<button type="button" @click="chooseFile">
<FileTextOutlined />
{{ selectedFile ? selectedFile.name : "选择 .docx 文件" }}
</button>
</div>
</div>
</a-modal>
</main>
</template>
@@ -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);
}
</style>