/** * STP模具几何分析中心 - Vue3单页应用 * 优化版本:统一前端实现,增强用户体验 */ const { createApp, ref, computed, onMounted, reactive } = Vue; const { createRouter, createWebHistory, useRoute, useRouter } = VueRouter; // 全局状态管理 const appState = reactive({ health: null, loading: false, notifications: [] }); // 通用工具函数 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('zh-CN'); } catch { return dateString; } } function statusText(status) { const map = { pending: "排队中", processing: "处理中", completed: "已完成", failed: "失败", }; return map[status] || status || "未知"; } // 状态样式映射 function getStatusClass(status) { const classMap = { pending: "status-pending", processing: "status-processing", completed: "status-completed", failed: "status-failed" }; return classMap[status] || "status-pending"; } // 添加通知 function addNotification(message, type = 'info') { const notification = { id: Date.now(), message, type, timestamp: new Date() }; appState.notifications.push(notification); // 自动移除通知 setTimeout(() => { const index = appState.notifications.findIndex(n => n.id === notification.id); if (index > -1) { appState.notifications.splice(index, 1); } }, 5000); } // 错误处理 function handleApiError(error, context = '') { console.error(`API错误 [${context}]:`, error); const message = error.message || '请求失败,请稍后重试'; addNotification(message, 'error'); return message; } // 顶部导航 + 布局 const App = { setup() { const route = useRoute(); const router = useRouter(); 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) { appState.health = await res.json(); } } catch (error) { appState.health = null; handleApiError(error, '健康检查'); } }; const dismissNotification = (id) => { const index = appState.notifications.findIndex(n => n.id === id); if (index > -1) { appState.notifications.splice(index, 1); } }; onMounted(() => { loadHealth(); // 定时检查健康状态(每30秒) setInterval(loadHealth, 30000); }); return { route, router, appState, isActive, dismissNotification, getStatusClass, statusText }; }, template: `
{{ notification.message }}

STP 模具几何分析中心

专业模具几何分析,支持型腔自动生成与工艺参数计算

`, }; // 仪表盘视图:上传 + 总览 const DashboardView = { setup() { const router = useRouter(); const state = reactive({ selectedFile: null, uploading: false, error: "", currentTask: null, polling: false, historySummary: null, dragOver: false }); const totalFiles = computed(() => state.historySummary?.total_files || 0); const totalRecords = computed(() => (state.historySummary?.files || []).reduce( (sum, f) => sum + (f.record_count || 0), 0 ) ); const latestFile = computed(() => (state.historySummary?.files || [])[0] || null ); const handleFileChange = (event) => { const file = event.target.files[0]; if (!file) return; validateAndSelectFile(file); }; const validateAndSelectFile = (file) => { if (!file.name.toLowerCase().endsWith(".stp") && !file.name.toLowerCase().endsWith(".step")) { state.error = "请选择 STP 或 STEP 格式文件"; state.selectedFile = null; return; } if (file.size > 100 * 1024 * 1024) { state.error = "文件大小不能超过 100MB"; state.selectedFile = null; return; } state.error = ""; state.selectedFile = file; addNotification(`已选择文件: ${file.name}`, 'success'); }; const handleDragOver = (event) => { event.preventDefault(); state.dragOver = true; }; const handleDragLeave = (event) => { event.preventDefault(); state.dragOver = false; }; const handleDrop = (event) => { event.preventDefault(); state.dragOver = false; const files = event.dataTransfer.files; if (files.length > 0) { validateAndSelectFile(files[0]); } }; const uploadFile = async () => { if (!state.selectedFile) return; state.uploading = true; state.error = ""; const formData = new FormData(); formData.append("file", state.selectedFile); 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(); state.currentTask = { task_id: data.task_id, status: "processing", filename: data.file_info?.filename, file_size: data.file_info?.size, }; addNotification(`文件上传成功,开始分析...`, 'success'); startPolling(data.task_id); } catch (e) { const errorMsg = handleApiError(e, '文件上传'); state.error = errorMsg; } finally { state.uploading = false; } }; const startPolling = async (taskId) => { state.polling = true; const poll = async () => { try { const res = await fetch(`/status/${taskId}`, { method: "POST" }); if (!res.ok) { throw new Error("查询任务状态失败"); } const task = await res.json(); state.currentTask = task; if (task.status === "completed") { state.polling = false; addNotification(`分析完成,正在跳转到结果页面...`, 'success'); setTimeout(() => { router.push(`/result/${task.task_id}`); }, 1000); } else if (task.status === "failed") { state.polling = false; const errorMsg = task.error || "未知错误"; state.error = `分析失败: ${errorMsg}`; addNotification(`分析失败: ${errorMsg}`, 'error'); } else { setTimeout(poll, 1000); } } catch (e) { state.polling = false; const errorMsg = handleApiError(e, '任务轮询'); state.error = errorMsg; } }; poll(); }; const loadHistorySummary = async () => { try { const res = await fetch("/api/history", { method: "POST" }); if (res.ok) { state.historySummary = await res.json(); } } catch (e) { state.historySummary = null; handleApiError(e, '加载历史记录'); } }; const clearFile = () => { state.selectedFile = null; state.error = ""; }; onMounted(() => { loadHistorySummary(); }); return { state, totalFiles, totalRecords, latestFile, handleFileChange, handleDragOver, handleDragLeave, handleDrop, uploadFile, clearFile, formatFileSize, statusText, formatDateTime, getStatusClass }; }, 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");