diff --git a/src/api/routes.py b/src/api/routes.py index 625cd50..98a0d68 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -405,6 +405,13 @@ async def process_file_core( mesh_json=mesh_json, quality="medium", ) + # 将简要网格摘要写入内存任务,便于前端展示汇总信息 + tasks[task_id]["mesh_summary"] = { + "vertex_count": len(vertices), + "face_count": len(faces), + "point_count": pointcloud.get("count"), + "quality": "medium", + } except Exception as mesh_err: # 网格失败不影响整体流程,只记录日志 logger.warning(f"网格生成或保存失败,不影响主流程: {mesh_err}") diff --git a/static/vue-app.js b/static/vue-app.js new file mode 100644 index 0000000..b1eedf1 --- /dev/null +++ b/static/vue-app.js @@ -0,0 +1,745 @@ +// static/vue-app.js +// 使用 CDN 引入的 Vue3 + Vue Router,构建单页应用 + +const { createApp, ref, computed, onMounted } = Vue; +const { createRouter, createWebHistory, useRoute, useRouter } = VueRouter; + +// 通用工具函数 +function formatFileSize(bytes) { + if (!bytes || bytes === 0) return "0 Bytes"; + const k = 1024; + const sizes = ["Bytes", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; +} + +function formatNumber(num) { + if (num === null || num === undefined) return "N/A"; + if (num >= 1_000_000) return (num / 1_000_000).toFixed(2) + "M"; + if (num >= 1_000) return (num / 1_000).toFixed(2) + "K"; + return num.toFixed ? num.toFixed(2) : String(num); +} + +function formatDateTime(dateString) { + if (!dateString) return "N/A"; + try { + return new Date(dateString).toLocaleString(); + } catch { + return dateString; + } +} + +function statusText(status) { + const map = { + pending: "排队中", + processing: "处理中", + completed: "已完成", + failed: "失败", + }; + return map[status] || status || "未知"; +} + +// 顶部导航 + 布局 +const App = { + setup() { + const route = useRoute(); + const router = useRouter(); + const health = ref(null); + + const isActive = (pathPrefix) => + computed(() => route.path === pathPrefix || route.path.startsWith(pathPrefix)); + + const loadHealth = async () => { + try { + const res = await fetch("/health", { method: "POST" }); + if (res.ok) { + health.value = await res.json(); + } + } catch { + health.value = null; + } + }; + + onMounted(loadHealth); + + return { route, router, health, isActive }; + }, + template: ` +
+
+
+ +
+

STP 模具几何分析中心

+

上传模型 → 自动分析几何 / 型腔 / 工艺 → 聚合展示关键指标

+
+
+ +
+ +
+ +
+ + +
+ `, +}; + +// 仪表盘视图:上传 + 总览 +const DashboardView = { + setup() { + const router = useRouter(); + + const selectedFile = ref(null); + const uploading = ref(false); + const error = ref(""); + const currentTask = ref(null); + const polling = ref(false); + const historySummary = ref(null); + + const totalFiles = computed(() => historySummary.value?.total_files || 0); + const totalRecords = computed(() => + (historySummary.value?.files || []).reduce( + (sum, f) => sum + (f.record_count || 0), + 0 + ) + ); + const latestFile = computed(() => + (historySummary.value?.files || [])[0] || null + ); + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (!file) return; + + if ( + !file.name.toLowerCase().endsWith(".stp") && + !file.name.toLowerCase().endsWith(".step") + ) { + error.value = "请选择 STP 或 STEP 格式文件"; + selectedFile.value = null; + return; + } + if (file.size > 100 * 1024 * 1024) { + error.value = "文件大小不能超过 100MB"; + selectedFile.value = null; + return; + } + + error.value = ""; + selectedFile.value = file; + }; + + const uploadFile = async () => { + if (!selectedFile.value) return; + uploading.value = true; + error.value = ""; + + const formData = new FormData(); + formData.append("file", selectedFile.value); + + try { + const res = await fetch("/upload", { + method: "POST", + body: formData, + }); + if (!res.ok) { + throw new Error(`上传失败: ${res.status} ${res.statusText}`); + } + const data = await res.json(); + currentTask.value = { + task_id: data.task_id, + status: "processing", + filename: data.file_info?.filename, + file_size: data.file_info?.size, + }; + startPolling(data.task_id); + } catch (e) { + error.value = e.message || "上传失败"; + } finally { + uploading.value = false; + } + }; + + const startPolling = async (taskId) => { + polling.value = true; + const poll = async () => { + try { + const res = await fetch(`/status/${taskId}`, { method: "POST" }); + if (!res.ok) { + throw new Error("查询任务状态失败"); + } + const task = await res.json(); + currentTask.value = task; + + if (task.status === "completed") { + polling.value = false; + // 跳转到结果详情页 + router.push(`/result/${task.task_id}`); + } else if (task.status === "failed") { + polling.value = false; + error.value = `分析失败: ${task.error || "未知错误"}`; + } else { + setTimeout(poll, 1000); + } + } catch (e) { + polling.value = false; + error.value = e.message || "轮询失败"; + } + }; + poll(); + }; + + const loadHistorySummary = async () => { + try { + const res = await fetch("/api/history", { method: "POST" }); + if (res.ok) { + historySummary.value = await res.json(); + } + } catch { + historySummary.value = null; + } + }; + + onMounted(() => { + loadHistorySummary(); + }); + + return { + selectedFile, + uploading, + error, + currentTask, + polling, + historySummary, + totalFiles, + totalRecords, + latestFile, + handleFileChange, + uploadFile, + formatFileSize, + statusText, + formatDateTime, + }; + }, + template: ` +
+
+
+

📁 上传 STP / STEP 文件

+

上传后系统会自动解析几何、生成模具型腔并计算关键工艺参数。

+ +
+ + + + +

{{ error }}

+
+ +
+

当前任务

+
+ 任务 ID:{{ currentTask.task_id }} +
+
+ 文件名:{{ currentTask.filename || 'N/A' }} +
+
+ 文件大小:{{ currentTask.file_size ? formatFileSize(currentTask.file_size) : 'N/A' }} +
+
+ 状态: + + {{ statusText(currentTask.status) }} + +
+
正在后台分析,请稍候,完成后会跳转到结果页。
+
+
+ +
+

📊 项目总览

+
+
+
已分析文件数
+
{{ totalFiles }}
+
+
+
总处理记录数
+
{{ totalRecords }}
+
+
+
最近上传文件
+
+
{{ latestFile.filename }}
+
+ 最近上传:{{ formatDateTime(latestFile.last_upload) }}
+ 历史记录:{{ latestFile.record_count }} 条 +
+
+
+
+ 还没有历史记录,先上传一个 STP 文件试试吧。 +
+
+ + 查看详细历史记录 → +
+
+
+ `, +}; + +// 历史记录视图 +const HistoryView = { + setup() { + const router = useRouter(); + const files = ref([]); + const loading = ref(true); + const error = ref(""); + const expanded = ref({}); + + const loadHistory = async () => { + loading.value = true; + error.value = ""; + try { + const res = await fetch("/api/history", { method: "POST" }); + if (!res.ok) throw new Error("获取历史记录失败"); + const data = await res.json(); + files.value = data.files || []; + } catch (e) { + error.value = e.message || "加载失败"; + } finally { + loading.value = false; + } + }; + + const toggleExpand = async (filename) => { + expanded.value[filename] = !expanded.value[filename]; + // 首次展开时加载该文件的所有记录 + if (expanded.value[filename]) { + const file = files.value.find((f) => f.filename === filename); + if (!file.records) { + try { + const res = await fetch( + `/api/history/${encodeURIComponent(filename)}`, + { method: "POST" } + ); + if (!res.ok) throw new Error("加载记录失败"); + file.records = await res.json(); + } catch (e) { + error.value = e.message || "加载记录失败"; + } + } + } + }; + + const openResult = (taskId) => { + router.push(`/result/${taskId}`); + }; + + onMounted(loadHistory); + + return { + files, + loading, + error, + expanded, + toggleExpand, + openResult, + formatFileSize, + formatDateTime, + statusText, + }; + }, + template: ` +
+

📋 历史记录

+

按文件名聚合展示所有上传与分析任务。

+ +
+
+

正在加载历史记录...

+
+ +

{{ error }}

+ +
+
📁
+

暂无历史记录,先在仪表盘上传一个文件吧。

+
+ +
+
+
+
+
📄 {{ file.filename }}
+
+ 总记录:{{ file.record_count }} 条 + | 最近上传:{{ formatDateTime(file.last_upload) }} +
+
+ +
+ +
+
+
+

正在加载记录...

+
+
+ 暂无详细记录 +
+
+
+ 📅 {{ formatDateTime(record.upload_time) }} + + {{ statusText(record.status) }} + +
+
+ 任务 ID:{{ record.task_id }} + 文件大小:{{ formatFileSize(record.file_size) }} + 完成时间:{{ formatDateTime(record.completed_at) }} +
+
+
+
+
+
+ `, +}; + +// 结果详情视图 +const ResultView = { + setup() { + const route = useRoute(); + const task = ref(null); + const loading = ref(true); + const error = ref(""); + + const taskId = computed(() => route.params.taskId); + + const loadTask = async () => { + loading.value = true; + error.value = ""; + try { + const res = await fetch(`/status/${taskId.value}`, { method: "POST" }); + if (!res.ok) throw new Error("获取任务数据失败"); + const data = await res.json(); + task.value = data; + } catch (e) { + error.value = e.message || "加载失败"; + } finally { + loading.value = false; + } + }; + + const geometry = computed(() => task.value?.geometry_data || null); + const keyInfo = computed(() => task.value?.key_info || null); + const meshSummary = computed(() => task.value?.mesh_summary || null); + + onMounted(loadTask); + + return { + task, + loading, + error, + taskId, + geometry, + keyInfo, + meshSummary, + formatNumber, + formatFileSize, + formatDateTime, + statusText, + }; + }, + template: ` +
+

📊 分析结果详情

+ +
+
+

正在加载任务 {{ taskId }} 的分析结果...

+
+ +

{{ error }}

+ +
+
+

📝 任务信息

+
+
+ 任务 ID + {{ task.task_id || 'N/A' }} +
+
+ 文件名 + {{ task.filename || 'N/A' }} +
+
+ 文件大小 + {{ task.file_size ? formatFileSize(task.file_size) : 'N/A' }} +
+
+ 状态 + + + {{ statusText(task.status) }} + + +
+
+ 上传时间 + {{ task.upload_time ? formatDateTime(task.upload_time) : 'N/A' }} +
+
+ 完成时间 + {{ task.completed_at ? formatDateTime(task.completed_at) : 'N/A' }} +
+
+
+ 错误信息:{{ task.error }} +
+
+ +
+
+

📐 几何属性

+
+
+ 体积 + + {{ geometry.volume ? formatNumber(geometry.volume) + ' mm³' : 'N/A' }} + +
+
+ 表面积 + + {{ geometry.surface_area ? formatNumber(geometry.surface_area) + ' mm²' : 'N/A' }} + +
+
+ 体积/面积比 + + {{ + geometry.volume && geometry.surface_area + ? (geometry.volume / geometry.surface_area).toFixed(4) + : 'N/A' + }} + +
+
+
无几何数据
+
+ +
+

📦 边界框

+
+
+ 尺寸 (mm) + + {{ geometry.bounding_box.dimensions[0].toFixed(2) }} × + {{ geometry.bounding_box.dimensions[1].toFixed(2) }} × + {{ geometry.bounding_box.dimensions[2].toFixed(2) }} + +
+
+ 最小坐标 + + X: {{ geometry.bounding_box.min[0].toFixed(2) }}, + Y: {{ geometry.bounding_box.min[1].toFixed(2) }}, + Z: {{ geometry.bounding_box.min[2].toFixed(2) }} + +
+
+ 最大坐标 + + X: {{ geometry.bounding_box.max[0].toFixed(2) }}, + Y: {{ geometry.bounding_box.max[1].toFixed(2) }}, + Z: {{ geometry.bounding_box.max[2].toFixed(2) }} + +
+
+
无边界框数据
+
+ +
+

🔺 拓扑结构

+
+
+ 面数 + {{ geometry.topology.faces || 0 }} +
+
+ 边数 + {{ geometry.topology.edges || 0 }} +
+
+ 顶点数 + {{ geometry.topology.vertices || 0 }} +
+
+
无拓扑数据
+
+
+ +
+
+

🔧 模具 / 工艺关键信息

+
+
+ 收缩率 + + {{ keyInfo.metadata?.shrinkage_rate ?? 'N/A' }} + +
+
+ 拔模角 + + {{ + keyInfo.metadata?.draft_angle !== undefined + ? keyInfo.metadata.draft_angle + '°' + : 'N/A' + }} + +
+
+ 预估夹紧力 + + {{ keyInfo.manufacturing_info?.estimated_clamping_force || 'N/A' }} + +
+
+ 模具尺寸 (mm) + + {{ + keyInfo.manufacturing_info?.estimated_mold_size + ? keyInfo.manufacturing_info.estimated_mold_size.length + + ' × ' + + keyInfo.manufacturing_info.estimated_mold_size.width + + ' × ' + + keyInfo.manufacturing_info.estimated_mold_size.height + : 'N/A' + }} + +
+
+ 产品重量 + + {{ + keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics + ?.product_weight || 'N/A' + }} + +
+
+ 壁厚范围 + + {{ + keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics + ?.wall_thickness_range || 'N/A' + }} + +
+
+ 预估成型周期 + + {{ keyInfo.manufacturing_info?.estimated_cycle_time || 'N/A' }} + +
+
+
无型腔 / 工艺数据
+
+ +
+

🧱 网格摘要

+
+
+ 网格质量等级 + {{ meshSummary.quality }} +
+
+ 顶点数 + {{ meshSummary.vertex_count }} +
+
+ 面数 + {{ meshSummary.face_count }} +
+
+ 点云采样点数 + {{ meshSummary.point_count ?? 'N/A' }} +
+
+
+ 当前任务未生成网格摘要,或网格生成过程中出现问题。 +
+
+
+
+
+ `, +}; + +// 路由配置 +const routes = [ + { path: "/", component: DashboardView }, + { path: "/history", component: HistoryView }, + { path: "/result/:taskId", component: ResultView }, +]; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +// 挂载应用 +createApp(App).use(router).mount("#app"); + diff --git a/templates/history.html b/templates/history.html index 9422bf1..118e2e6 100644 --- a/templates/history.html +++ b/templates/history.html @@ -4,204 +4,15 @@ - 历史记录 - STP文件几何分析工具 + 历史记录 - STP 模具几何分析中心 - -
-
-
-

📊 历史记录

-

查看所有上传的STP文件分析记录

-
- -
+
-
-
-
-

正在加载历史记录...

-
-
-
- - + + + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index dea05c8..6ce7ce0 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,127 +4,15 @@ - STP文件几何分析工具 + STP 模具几何分析中心 -
-
-

🔧 STP文件几何分析工具

-

上传STP/STEP文件,自动分析几何属性和拓扑结构

-
+
- -
-

选择STP文件

-

支持 .stp 和 .step 格式文件

- -
-
📁
-

拖放文件到此处或点击选择

-

最大文件大小: 100MB

- - -
- - - -
-
-

正在分析文件,请稍候...

-
- -
-
- - - - -
-
-

PythonOCC 可用: {{ pythonocc_available }} | 服务版本: 3.0.0

-
- - 📋 查看历史记录 - -
-
-
-
- - + + + + \ No newline at end of file diff --git a/templates/result.html b/templates/result.html index 8b9a7e0..4379e35 100644 --- a/templates/result.html +++ b/templates/result.html @@ -4,434 +4,15 @@ - 分析结果 - STP文件几何分析工具 + 分析结果 - STP 模具几何分析中心 - -
-
-
-

📊 分析结果详情

-

查看STP文件的详细分析结果

-
- -
+
-
-
-
-

正在加载任务信息...

-
-
- -
- -
-
- - + + + + \ No newline at end of file