// 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");