/** * STP模具几何分析中心 - Vue3单页应用 * 版本: 5.0.0 - Modern Minimal UI * 设计灵感: Linear, Notion, Vercel, Stripe */ const { createApp, ref, computed, onMounted, reactive, watch, nextTick } = Vue; const { createRouter, createWebHistory, useRoute, useRouter } = VueRouter; const appState = reactive({ health: null, loading: false, notifications: [], theme: 'modern' }); function formatFileSize(bytes) { if (!bytes || bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "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: "badge-warning", processing: "badge-info", completed: "badge-success", failed: "badge-error" }; return classMap[status] || "badge-neutral"; } let notificationId = 0; function addNotification(message, type = 'info') { const id = ++notificationId; const notification = { id, message, type, timestamp: new Date(), visible: true }; appState.notifications.push(notification); setTimeout(() => { const index = appState.notifications.findIndex(n => n.id === id); if (index > -1) { appState.notifications[index].visible = false; setTimeout(() => { const idx = appState.notifications.findIndex(n => n.id === id); if (idx > -1) { appState.notifications.splice(idx, 1); } }, 300); } }, 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; } }; const dismissNotification = (id) => { const index = appState.notifications.findIndex(n => n.id === id); if (index > -1) { appState.notifications[index].visible = false; setTimeout(() => { const idx = appState.notifications.findIndex(n => n.id === id); if (idx > -1) { appState.notifications.splice(idx, 1); } }, 300); } }; onMounted(() => { loadHealth(); setInterval(loadHealth, 30000); }); return { route, router, appState, isActive, dismissNotification, getStatusClass, statusText }; }, template: `
{{ notification.type === 'success' ? '✓' : notification.type === 'error' ? '✕' : notification.type === 'warning' ? '!' : 'i' }}
{{ notification.message }}
◈
MoldInsight
STP 模具几何分析
仪表盘 历史记录
系统运行中
PythonOCC: {{ appState.health?.pythonocc ? '可用' : '检测中...' }} 任务数: {{ appState.health.total_tasks }} 版本 5.0.0
`, }; const DashboardView = { setup() { const router = useRouter(); const state = reactive({ selectedFile: null, uploading: false, error: "", currentTask: null, polling: false, historySummary: null, dragOver: false, progress: 0 }); 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 = ""; state.progress = 0; 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; state.progress = 10; let pollCount = 0; const maxPolls = 300; const poll = async () => { try { pollCount++; state.progress = Math.min(90, 10 + pollCount * 0.5); const res = await fetch(`/status/${taskId}`, { method: "POST" }); if (res.status === 404) { state.polling = false; state.error = "任务不存在或已被删除"; addNotification("任务不存在或已被删除", 'error'); return; } if (!res.ok) { throw new Error(`查询任务状态失败: ${res.status} ${res.statusText}`); } const task = await res.json(); state.currentTask = task; if (task.status === "completed") { state.polling = false; state.progress = 100; addNotification(`分析完成,正在跳转...`, 'success'); setTimeout(() => { router.push(`/result/${task.task_id}`); }, 1000); } else if (task.status === "failed") { state.polling = false; state.progress = 0; const errorMsg = task.error || "未知错误"; state.error = `分析失败: ${errorMsg}`; addNotification(`分析失败: ${errorMsg}`, 'error'); } else if (pollCount >= maxPolls) { state.polling = false; state.progress = 0; state.error = "任务处理超时,请稍后查看结果"; addNotification("任务处理超时", 'warning'); } else { setTimeout(poll, 1000); } } catch (e) { state.polling = false; state.progress = 0; 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; } }; 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: `
📁
{{ totalFiles }}
已分析文件
📊
{{ totalRecords }}
处理记录
⚡
{{ state.polling ? '...' : '就绪' }}
系统状态
🎯
{{ latestFile ? '1' : '0' }}
最近文件
📤
上传文件

{{ state.error }}

当前任务 {{ statusText(state.currentTask.status) }}
{{ state.currentTask.filename }}
📊
项目总览
📄
{{ latestFile.filename }}
{{ latestFile.record_count }} 条记录 {{ formatDateTime(latestFile.last_upload) }}
📁
暂无历史记录
上传一个 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) }}
加载中...
暂无详细记录
📊
{{ record.task_id.slice(0, 8) }}...
{{ formatDateTime(record.upload_time) }} {{ formatFileSize(record.file_size) }}
{{ statusText(record.status) }}
`, }; 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: `
📊 分析结果
返回仪表盘
加载中...

{{ error }}

📝
任务信息
{{ statusText(task.status) }}
📄
{{ task.filename || 'N/A' }}
文件名
💾
{{ task.file_size ? formatFileSize(task.file_size) : 'N/A' }}
文件大小
🆔
{{ task.task_id }}
任务 ID
📅
{{ task.upload_time ? formatDateTime(task.upload_time) : 'N/A' }}
上传时间
错误: {{ task.error }}
📐
几何属性
体积
{{ geometry.volume ? formatNumber(geometry.volume) : 'N/A' }} mm³
表面积
{{ geometry.surface_area ? formatNumber(geometry.surface_area) : 'N/A' }} mm²
无几何数据
📦
边界框
尺寸
{{ geometry.bounding_box.dimensions[0].toFixed(2) }} × {{ geometry.bounding_box.dimensions[1].toFixed(2) }} × {{ geometry.bounding_box.dimensions[2].toFixed(2) }} mm
无边界框数据
🔺
拓扑结构
面 / 边 / 顶点
{{ geometry.topology.faces || 0 }} / {{ geometry.topology.edges || 0 }} / {{ geometry.topology.vertices || 0 }}
无拓扑数据
🔧
工艺参数
📉
{{ keyInfo.metadata.shrinkage_rate }}
收缩率
📐
{{ keyInfo.metadata.draft_angle }}°
拔模角
💪
{{ keyInfo.manufacturing_info.estimated_clamping_force }}
预估夹紧力
`, }; const routes = [ { path: "/", component: DashboardView }, { path: "/history", component: HistoryView }, { path: "/result/:taskId", component: ResultView }, ]; const router = createRouter({ history: createWebHistory(), routes, }); const app = createApp(App); app.use(router); app.mount("#app");