diff --git a/static/history.js b/static/history.js deleted file mode 100644 index e2d9c47..0000000 --- a/static/history.js +++ /dev/null @@ -1,215 +0,0 @@ -// static/history.js - -// 页面加载完成后加载历史记录 -document.addEventListener('DOMContentLoaded', function() { - loadHistory(); -}); - -async function loadHistory() { - const fileList = document.getElementById('fileList'); - - try { - const response = await fetch('/api/history', { - method: 'POST' - }); - if (!response.ok) { - throw new Error('获取历史记录失败'); - } - - const data = await response.json(); - - if (data.files && data.files.length > 0) { - displayFileList(data.files); - } else { - fileList.innerHTML = ` -
-
📁
-

暂无历史记录

-

还没有上传过STP文件

- -
- `; - } - - } catch (error) { - fileList.innerHTML = ` -
-
❌
-

加载失败

-

${error.message}

- -
- `; - } -} - -function displayFileList(files) { - const fileList = document.getElementById('fileList'); - - fileList.innerHTML = files.map(file => ` -
-
-
-
📄 ${file.filename}
-
- 总记录: ${file.record_count} 条 | - 最后上传: ${formatDateTime(file.last_upload)} -
-
- -
-
-
-
-

正在加载记录详情...

-
-
-
- `).join(''); -} - -async function toggleFileRecords(filename) { - const fileItem = document.getElementById(`file-${filename}`); - const recordList = document.getElementById(`records-${filename}`); - const expandBtn = fileItem.querySelector('.expand-btn'); - - // 切换展开状态 - if (fileItem.classList.contains('expanded')) { - fileItem.classList.remove('expanded'); - expandBtn.textContent = '➕'; - return; - } - - // 先设置展开状态 - fileItem.classList.add('expanded'); - expandBtn.textContent = '➖'; - - // 如果已经加载过数据,直接显示 - if (recordList.dataset.loaded === 'true') { - return; - } - - try { - const response = await fetch(`/api/history/${encodeURIComponent(filename)}`, { - method: 'POST' - }); - if (!response.ok) { - throw new Error('获取记录详情失败'); - } - - const records = await response.json(); - - if (records.length > 0) { - recordList.innerHTML = records.map(record => ` -
-
-
- 📅 ${formatDateTime(record.upload_time)} -
-
- ${getStatusText(record.status)} -
-
-
-
文件大小: ${formatFileSize(record.file_size)}
-
任务ID: ${record.task_id}
-
状态: ${record.status}
- ${record.completed_at ? `
完成时间: ${formatDateTime(record.completed_at)}
` : ''} -
-
- `).join(''); - } else { - recordList.innerHTML = ` -
-

暂无详细记录

-
- `; - } - - recordList.dataset.loaded = 'true'; - - } catch (error) { - recordList.innerHTML = ` -
-

❌ 加载失败: ${error.message}

- -
- `; - } -} - -async function viewRecordDetails(taskId) { - // 直接跳转到结果页面,由后端从数据库加载完整数据 - window.location.href = `/result/${taskId}`; -} - -async function reanalyzeTask(taskId) { - try { - const response = await fetch(`/api/reanalyze/${taskId}`, { - method: 'POST' - }); - - if (response.ok) { - alert('重新分析任务已启动,请稍后查看结果'); - // 跳转到分析状态页面 - window.location.href = `/status/${taskId}`; - } else { - throw new Error('重新分析失败'); - } - } catch (error) { - alert(`重新分析失败: ${error.message}`); - } -} - -function getStatusText(status) { - const statusMap = { - 'completed': '已完成', - 'processing': '处理中', - 'failed': '失败' - }; - return statusMap[status] || status; -} - -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 formatDateTime(dateString) { - if (!dateString) return 'N/A'; - // 后端已经返回格式化的时间字符串,直接返回 - return dateString; -} - -// 跳转到上传页面 -async function goToUpload() { - try { - // 上传页面需要GET请求,直接跳转 - window.location.href = '/'; - } catch (error) { - console.error('跳转失败:', error); - window.location.href = '/'; - } -} - -// 跳转到主页 -async function goToHome() { - try { - // 主页需要GET请求,直接跳转 - window.location.href = '/'; - } catch (error) { - console.error('跳转失败:', error); - window.location.href = '/'; - } -} \ No newline at end of file diff --git a/static/script.js b/static/script.js deleted file mode 100644 index 7a41332..0000000 --- a/static/script.js +++ /dev/null @@ -1,657 +0,0 @@ -// static/script.js -let selectedFile = null; -const uploadSection = document.getElementById('uploadSection'); -const uploadArea = document.getElementById('uploadArea'); -const fileInput = document.getElementById('fileInput'); -const uploadBtn = document.getElementById('uploadBtn'); -const loading = document.getElementById('loading'); -const resultsSection = document.getElementById('resultsSection'); -const errorMessage = document.getElementById('errorMessage'); -const taskInfo = document.getElementById('taskInfo'); -const geometryData = document.getElementById('geometryData'); -const boundingBoxData = document.getElementById('boundingBoxData'); -const topologyData = document.getElementById('topologyData'); -const featuresData = document.getElementById('featuresData'); -const recommendationsData = document.getElementById('recommendationsData'); -const metricsData = document.getElementById('metricsData'); -const analysisInfo = document.getElementById('analysisInfo'); - -// 页面加载时初始化 -document.addEventListener('DOMContentLoaded', function() { - console.log('页面加载完成'); - showUploadSection(); -}); - -// 显示上传区域 -function showUploadSection() { - uploadSection.style.display = 'block'; - resultsSection.style.display = 'none'; - resetUploadArea(); -} - -// 显示结果区域 -function showResultsSection() { - uploadSection.style.display = 'none'; - resultsSection.style.display = 'block'; -} - -// 重置上传区域 -function resetUploadArea() { - selectedFile = null; - uploadArea.innerHTML = ` -
📁
-

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

-

最大文件大小: 100MB

- - - `; - uploadBtn.disabled = true; - hideError(); - loading.style.display = 'none'; - - // 重新绑定事件 - const newFileInput = document.getElementById('fileInput'); - newFileInput.addEventListener('change', (e) => { - if (e.target.files.length > 0) { - handleFileSelect(e.target.files[0]); - } - }); -} - -// 拖放功能 -uploadArea.addEventListener('dragover', (e) => { - e.preventDefault(); - uploadArea.classList.add('dragover'); -}); - -uploadArea.addEventListener('dragleave', () => { - uploadArea.classList.remove('dragover'); -}); - -uploadArea.addEventListener('drop', (e) => { - e.preventDefault(); - uploadArea.classList.remove('dragover'); - const files = e.dataTransfer.files; - if (files.length > 0) { - handleFileSelect(files[0]); - } -}); - -// 文件选择 -fileInput.addEventListener('change', (e) => { - if (e.target.files.length > 0) { - handleFileSelect(e.target.files[0]); - } -}); - -function handleFileSelect(file) { - if (!file.name.toLowerCase().endsWith('.stp') && !file.name.toLowerCase().endsWith('.step')) { - showError('请选择STP或STEP格式的文件'); - return; - } - - if (file.size > 100 * 1024 * 1024) { - showError('文件大小不能超过100MB'); - return; - } - - selectedFile = file; - uploadArea.innerHTML = ` -
✅
-

已选择文件

-

${file.name}

-

大小: ${(file.size / 1024 / 1024).toFixed(2)} MB

- `; - uploadBtn.disabled = false; - hideError(); -} - -async function uploadFile() { - if (!selectedFile) return; - - loading.style.display = 'block'; - uploadBtn.disabled = true; - hideError(); - - const formData = new FormData(); - formData.append('file', selectedFile); - - try { - const response = await fetch('/upload', { - method: 'POST', - body: formData - }); - - if (!response.ok) { - throw new Error(`上传失败: ${response.status} ${response.statusText}`); - } - - const result = await response.json(); - console.log('上传结果:', result); - - // 开始轮询任务状态 - pollTaskStatus(result.task_id); - - } catch (error) { - showError('上传失败: ' + error.message); - loading.style.display = 'none'; - uploadBtn.disabled = false; - } -} - -async function pollTaskStatus(taskId) { - try { - const response = await fetch(`/status/${taskId}`, { - method: 'POST' - }); - const task = await response.json(); - - console.log('任务状态:', task.status); - console.log('完整任务数据:', task); - - updateTaskInfo(task); - - if (task.status === 'completed') { - loading.style.display = 'none'; - // 跳转到统一的分析结果页面 - window.location.href = `/result/${task.task_id}`; - } else if (task.status === 'failed') { - loading.style.display = 'none'; - showError('分析失败: ' + (task.error || '未知错误')); - uploadBtn.disabled = false; - } else { - setTimeout(() => pollTaskStatus(taskId), 1000); - } - - } catch (error) { - console.error('轮询错误:', error); - loading.style.display = 'none'; - showError('查询状态失败: ' + error.message); - uploadBtn.disabled = false; - } -} - -function updateTaskInfo(task) { - taskInfo.innerHTML = ` -
-
📋 任务ID
-
${task.task_id || 'N/A'}
-
-
-
🔄 状态
-
- ${getStatusText(task.status)} -
-
-
-
📁 文件名
-
${task.filename || 'N/A'}
-
-
-
📏 文件大小
-
${task.file_size ? formatFileSize(task.file_size) : 'N/A'}
-
- `; -} - -function displayAllResults(task) { - console.log('显示所有结果:', task); - - displayGeometryData(task); - displayBoundingBoxData(task); - displayTopologyData(task); - - // 添加模具型腔数据显示 - if (task.cavity_data) { - displayCavityData(task.cavity_data); - } - if (task.key_info) { - displayKeyInfo(task.key_info); - } - - displayFeaturesData(task); - displayRecommendationsData(task); - displayMetricsData(task); - displayAnalysisInfo(task); -} - -function displayGeometryData(task) { - if (task.geometry_data) { - const geo = task.geometry_data; - geometryData.innerHTML = ` -
-
📦 体积
-
- ${geo.volume ? formatNumber(geo.volume) : 'N/A'} - mm³ -
-
-
-
📐 表面积
-
- ${geo.surface_area ? formatNumber(geo.surface_area) : 'N/A'} - mm² -
-
-
-
📏 体积表面积比
-
- ${geo.volume && geo.surface_area ? (geo.volume / geo.surface_area).toFixed(4) : 'N/A'} -
-
- `; - } else { - geometryData.innerHTML = '
无几何数据
'; - } -} - -function displayBoundingBoxData(task) { - if (task.geometry_data && task.geometry_data.bounding_box) { - const bbox = task.geometry_data.bounding_box; - boundingBoxData.innerHTML = ` -
-
📍 最小坐标
-
- X: ${bbox.min[0].toFixed(2)}
- Y: ${bbox.min[1].toFixed(2)}
- Z: ${bbox.min[2].toFixed(2)} -
-
-
-
📍 最大坐标
-
- X: ${bbox.max[0].toFixed(2)}
- Y: ${bbox.max[1].toFixed(2)}
- Z: ${bbox.max[2].toFixed(2)} -
-
-
-
📏 尺寸
-
- ${bbox.dimensions[0].toFixed(2)} × ${bbox.dimensions[1].toFixed(2)} × ${bbox.dimensions[2].toFixed(2)} - mm -
-
- `; - } else { - boundingBoxData.innerHTML = '
无边界框数据
'; - } -} - -function displayTopologyData(task) { - if (task.geometry_data && task.geometry_data.topology) { - const topo = task.geometry_data.topology; - topologyData.innerHTML = ` -
-
🔺 面数
-
${topo.faces || 0}
-
-
-
📏 边数
-
${topo.edges || 0}
-
-
-
📍 顶点数
-
${topo.vertices || 0}
-
-
-
📊 拓扑复杂度
-
${calculateTopologyComplexity(topo)}
-
- `; - } else { - topologyData.innerHTML = '
无拓扑数据
'; - } -} - -function displayFeaturesData(task) { - if (task.analysis_result && task.analysis_result.detected_features) { - const features = task.analysis_result.detected_features; - - if (features.length > 0) { - featuresData.innerHTML = features.map(feature => ` -
-
-
${getFeatureTypeText(feature.feature_type)}
-
置信度: ${(feature.confidence * 100).toFixed(0)}%
-
-
-
📍 位置
-
${feature.location.map(v => v.toFixed(2)).join(', ')}
-
-
-
📏 尺寸
-
${feature.dimensions.map(v => v.toFixed(2)).join(' × ')} mm
-
- ${feature.recommendations && feature.recommendations.length > 0 ? ` -
-
💡 建议
- -
- ` : ''} -
- `).join(''); - } else { - featuresData.innerHTML = '
未检测到明显特征
'; - } - } else { - featuresData.innerHTML = '
无特征数据
'; - } -} - -function displayRecommendationsData(task) { - if (task.analysis_result && task.analysis_result.design_recommendations) { - const recommendations = task.analysis_result.design_recommendations; - - if (recommendations.length > 0) { - recommendationsData.innerHTML = recommendations.map(rec => ` -
-
-
${getRecommendationTypeText(rec.type)}
-
${getPriorityText(rec.priority)}
-
-
-
📝 描述
-
${rec.description}
-
-
-
📋 原因
-
${rec.reason}
-
- ${Object.keys(rec.parameters).length > 0 ? ` -
-
⚙️ 参数
-
${formatParameters(rec.parameters)}
-
- ` : ''} -
- `).join(''); - } else { - recommendationsData.innerHTML = '
无设计建议
'; - } - } else { - recommendationsData.innerHTML = '
无建议数据
'; - } -} - -function displayMetricsData(task) { - if (task.analysis_result && task.analysis_result.quality_metrics) { - const metrics = task.analysis_result.quality_metrics; - metricsData.innerHTML = ` -
-
体积利用率
-
- ${(metrics.volume_utilization * 100).toFixed(1)}% -
-
${getVolumeUtilizationText(metrics.volume_utilization)}
-
-
-
拓扑复杂度
-
- ${metrics.topology_complexity.toFixed(2)} -
-
${getComplexityText(metrics.topology_complexity)}
-
-
-
壁厚均匀性
-
- ${(metrics.wall_uniformity * 100).toFixed(1)}% -
-
${getUniformityText(metrics.wall_uniformity)}
-
- `; - } else { - metricsData.innerHTML = '
无质量指标数据
'; - } -} - -function displayAnalysisInfo(task) { - let infoHTML = ''; - - if (task.geometry_data) { - const geo = task.geometry_data; - infoHTML += ` -
-
🔧 分析方法
-
${geo.analysis_method || '未知'}
-
- `; - } - - if (task.analysis_result) { - const analysis = task.analysis_result; - infoHTML += ` -
-
✅ 分析状态
-
${task.status === 'completed' ? '分析完成' : '分析中'}
-
-
-
📋 分析摘要
-
${analysis.analysis_summary || '无摘要'}
-
- `; - } - - infoHTML += ` -
-
📅 处理时间
-
${task.completed_at ? new Date(task.completed_at).toLocaleString() : new Date().toLocaleString()}
-
- `; - - analysisInfo.innerHTML = infoHTML; -} - -// 工具函数 -function getStatusText(status) { - const statusMap = { - 'processing': '处理中', - 'completed': '已完成', - 'failed': '失败' - }; - return statusMap[status] || status; -} - -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) return 'N/A'; - if (num >= 1000000) { - return (num / 1000000).toFixed(2) + 'M'; - } else if (num >= 1000) { - return (num / 1000).toFixed(2) + 'K'; - } else { - return num.toFixed(2); - } -} - -function calculateTopologyComplexity(topo) { - const totalElements = (topo.faces || 0) + (topo.edges || 0) + (topo.vertices || 0); - if (totalElements < 100) return '简单'; - if (totalElements < 1000) return '中等'; - return '复杂'; -} - -function getFeatureTypeText(type) { - const typeMap = { - 'thin_wall': '薄壁区域', - 'thick_wall': '厚壁区域', - 'rib_structure': '加强筋结构', - 'boss_feature': 'BOSS柱', - 'draft_angle': '拔模角度', - 'cooling_system': '冷却系统' - }; - return typeMap[type] || type; -} - -function getRecommendationTypeText(type) { - const typeMap = { - 'wall_thickness': '壁厚优化', - 'draft_angle': '拔模角度', - 'rib_design': '加强筋设计', - 'boss_design': 'BOSS柱设计', - 'cooling_system': '冷却系统' - }; - return typeMap[type] || type; -} - -function getPriorityText(priority) { - const priorityMap = { - 'high': '高优先级', - 'medium': '中优先级', - 'low': '低优先级' - }; - return priorityMap[priority] || priority; -} - -function formatParameters(parameters) { - const parameterMap = { - 'min_angle': '最小角度', - 'preferred_angle': '推荐角度' - }; - - return Object.entries(parameters).map(([key, value]) => { - const displayKey = parameterMap[key] || key; - if (typeof value === 'number') { - return `${displayKey}: ${value.toFixed(2)}`; - } - return `${displayKey}: ${value}`; - }).join('; '); -} - -function getMetricClass(value, goodThreshold, excellentThreshold, reverse = false) { - if (reverse) { - if (value <= goodThreshold) return 'metric-good'; - if (value <= excellentThreshold) return 'metric-warning'; - return 'metric-poor'; - } else { - if (value >= excellentThreshold) return 'metric-good'; - if (value >= goodThreshold) return 'metric-warning'; - return 'metric-poor'; - } -} - -function getVolumeUtilizationText(value) { - if (value >= 0.6) return '优秀'; - if (value >= 0.3) return '良好'; - return '待优化'; -} - -function getComplexityText(value) { - if (value <= 0.3) return '简单'; - if (value <= 0.7) return '中等'; - return '复杂'; -} - -function getUniformityText(value) { - if (value >= 0.8) return '均匀'; - if (value >= 0.6) return '一般'; - return '不均匀'; -} - -function showError(message) { - errorMessage.textContent = message; - errorMessage.style.display = 'block'; -} - -function hideError() { - errorMessage.style.display = 'none'; -} - -// 跳转到历史页面 -function goToHistory() { - // 创建表单进行POST请求跳转,更安全 - const form = document.createElement('form'); - form.method = 'POST'; - form.action = '/history'; - document.body.appendChild(form); - form.submit(); -} - -// 添加显示函数 -function displayCavityData(cavityData) { - const cavityDiv = document.getElementById('cavityData'); - cavityDiv.innerHTML = ` -
${JSON.stringify(cavityData, null, 2)}
- `; -} - -function displayKeyInfo(keyInfo) { - const keyInfoDiv = document.getElementById('keyInfoData'); - - if (!keyInfo) { - keyInfoDiv.innerHTML = '
无关键信息数据
'; - return; - } - - // 从正确的数据结构中提取数据 - const metadata = keyInfo.metadata || {}; - const manufacturingInfo = keyInfo.manufacturing_info || {}; - const moldCavities = keyInfo.mold_cavities || {}; - const cavityKeyInfo = moldCavities.cavity_key_info || {}; - const geoChars = cavityKeyInfo.geometric_characteristics || {}; - - keyInfoDiv.innerHTML = ` -
-
🔧 模具参数
-
-
-
收缩率
-
${metadata.shrinkage_rate !== undefined ? metadata.shrinkage_rate : 'N/A'}
-
-
-
拔模角
-
${metadata.draft_angle !== undefined ? metadata.draft_angle + '°' : 'N/A'}
-
-
-
分型线长度
-
${manufacturingInfo.parting_line_length || 'N/A'}
-
- -
-
📐 几何特性
-
-
-
产品体积
-
${geoChars.product_volume || 'N/A'}
-
-
-
产品重量
-
${geoChars.product_weight || 'N/A'}
-
-
-
壁厚范围
-
${geoChars.wall_thickness_range || 'N/A'}
-
- -
-
⚙️ 制造要求
-
-
-
型腔材料
-
${manufacturingInfo.mold_material || 'N/A'}
-
-
-
硬度
-
${manufacturingInfo.mold_hardness || 'N/A'}
-
-
-
表面光洁度
-
${manufacturingInfo.surface_finish || 'N/A'}
-
-
-
预估周期
-
${manufacturingInfo.estimated_cycle_time || 'N/A'}
-
- `; -} diff --git a/static/style.css b/static/style.css index 20ce51c..010855a 100644 --- a/static/style.css +++ b/static/style.css @@ -1,102 +1,255 @@ -/* static/style.css */ +/** + * STP模具几何分析中心 - 统一设计系统 + * 优化版本:现代化UI设计,响应式布局 + */ + +/* 设计系统变量 */ +:root { + /* 颜色系统 */ + --primary-color: #2563eb; + --primary-hover: #1d4ed8; + --secondary-color: #64748b; + --success-color: #10b981; + --warning-color: #f59e0b; + --error-color: #ef4444; + --info-color: #3b82f6; + + /* 中性色 */ + --gray-50: #f8fafc; + --gray-100: #f1f5f9; + --gray-200: #e2e8f0; + --gray-300: #cbd5e1; + --gray-400: #94a3b8; + --gray-500: #64748b; + --gray-600: #475569; + --gray-700: #334155; + --gray-800: #1e293b; + --gray-900: #0f172a; + + /* 间距系统 */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + + /* 字体系统 */ + --font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 1.875rem; + + /* 阴影系统 */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1); + + /* 圆角系统 */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-full: 9999px; +} + +/* 基础重置 */ * { - margin: 0; - padding: 0; - box-sizing: border-box; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; } body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: linear-gradient(135deg, #1f2933 0%, #111827 100%); - min-height: 100vh; - padding: 16px; - color: #111827; + font-family: var(--font-family); + background: linear-gradient(135deg, var(--gray-900) 0%, var(--gray-800) 100%); + min-height: 100vh; + padding: var(--space-md); + color: var(--gray-800); + line-height: 1.6; } -/* 旧版容器保留,兼容可能的使用 */ -.container { - max-width: 1200px; - margin: 0 auto; - background: white; - border-radius: 15px; - box-shadow: 0 20px 40px rgba(0,0,0,0.1); - overflow: hidden; +/* 应用外壳 */ +.app-shell { + max-width: 1400px; + margin: 0 auto; + min-height: calc(100vh - 2 * var(--space-md)); + display: flex; + flex-direction: column; } -.header { - background: linear-gradient(135deg, #2c3e50, #34495e); - color: white; - padding: 30px; - text-align: center; +/* 通知系统 */ +.notification-container { + position: fixed; + top: var(--space-md); + right: var(--space-md); + z-index: 1000; + max-width: 400px; } -.header h1 { - font-size: 2.5em; - margin-bottom: 10px; +.notification { + background: white; + border-radius: var(--radius-md); + padding: var(--space-md); + margin-bottom: var(--space-sm); + box-shadow: var(--shadow-lg); + border-left: 4px solid var(--info-color); + animation: slideIn 0.3s ease-out; + display: flex; + align-items: center; + justify-content: space-between; } -.header p { - opacity: 0.9; - font-size: 1.1em; +.notification-success { + border-left-color: var(--success-color); } -.upload-section { - padding: 40px; - text-align: center; +.notification-error { + border-left-color: var(--error-color); } -.upload-area { - border: 3px dashed #3498db; - border-radius: 10px; - padding: 60px 40px; - margin: 20px 0; - background: #f8f9fa; - transition: all 0.3s ease; - cursor: pointer; +.notification-warning { + border-left-color: var(--warning-color); } -.upload-area:hover { - border-color: #2980b9; - background: #e8f4fc; +.notification-message { + flex: 1; + color: var(--gray-700); } -.upload-area.dragover { - border-color: #27ae60; - background: #d5f4e6; +.notification-close { + background: none; + border: none; + font-size: var(--font-size-lg); + color: var(--gray-400); + cursor: pointer; + padding: var(--space-xs); + margin-left: var(--space-sm); + border-radius: var(--radius-sm); + transition: all 0.2s; } -.upload-icon { - font-size: 4em; - color: #3498db; - margin-bottom: 20px; +.notification-close:hover { + background: var(--gray-100); + color: var(--gray-600); } -.file-input { - display: none; +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } } -.upload-btn { - background: linear-gradient(135deg, #3498db, #2980b9); - color: white; - border: none; - padding: 15px 40px; - font-size: 1.1em; - border-radius: 50px; - cursor: pointer; - transition: all 0.3s ease; - margin: 10px; +/* 应用头部 */ +.app-header { + background: white; + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + padding: var(--space-2xl); + box-shadow: var(--shadow-md); + margin-bottom: var(--space-lg); } -.upload-btn:hover { - transform: translateY(-2px); - box-shadow: 0 10px 20px rgba(52, 152, 219, 0.3); +.app-title { + display: flex; + align-items: center; + gap: var(--space-lg); + margin-bottom: var(--space-lg); } -.upload-btn:disabled { - background: #bdc3c7; - cursor: not-allowed; - transform: none; - box-shadow: none; +.logo { + font-size: var(--font-size-3xl); + background: linear-gradient(135deg, var(--primary-color), var(--info-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.app-title h1 { + font-size: var(--font-size-3xl); + color: var(--gray-900); + margin-bottom: var(--space-xs); +} + +.app-subtitle { + color: var(--gray-600); + font-size: var(--font-size-base); +} + +.app-nav { + display: flex; + gap: var(--space-md); + border-top: 1px solid var(--gray-200); + padding-top: var(--space-lg); +} + +.nav-link { + padding: var(--space-sm) var(--space-md); + text-decoration: none; + color: var(--gray-600); + border-radius: var(--radius-md); + transition: all 0.2s; + font-weight: 500; +} + +.nav-link:hover { + color: var(--primary-color); + background: var(--gray-50); +} + +.nav-link.active { + color: var(--primary-color); + background: var(--gray-50); + box-shadow: var(--shadow-sm); +} + +/* 主要内容区域 */ +.app-main { + flex: 1; + background: white; + border-radius: 0 0 var(--radius-xl) var(--radius-xl); + padding: var(--space-2xl); + box-shadow: var(--shadow-md); +} + +/* 应用底部 */ +.app-footer { + background: var(--gray-800); + color: white; + padding: var(--space-lg); + border-radius: var(--radius-md); + margin-top: var(--space-lg); + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--font-size-sm); +} + +.footer-left, .footer-right { + display: flex; + align-items: center; + gap: var(--space-md); +} + +.status-success { + color: var(--success-color); +} + +.status-error { + color: var(--error-color); } .results-section { diff --git a/static/vue-app.js b/static/vue-app.js index b1eedf1..7dbe93a 100644 --- a/static/vue-app.js +++ b/static/vue-app.js @@ -1,9 +1,18 @@ -// static/vue-app.js -// 使用 CDN 引入的 Vue3 + Vue Router,构建单页应用 +/** + * STP模具几何分析中心 - Vue3单页应用 + * 优化版本:统一前端实现,增强用户体验 + */ -const { createApp, ref, computed, onMounted } = Vue; +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"; @@ -23,7 +32,7 @@ function formatNumber(num) { function formatDateTime(dateString) { if (!dateString) return "N/A"; try { - return new Date(dateString).toLocaleString(); + return new Date(dateString).toLocaleString('zh-CN'); } catch { return dateString; } @@ -39,12 +48,49 @@ function statusText(status) { 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 health = ref(null); const isActive = (pathPrefix) => computed(() => route.path === pathPrefix || route.path.startsWith(pathPrefix)); @@ -53,30 +99,66 @@ const App = { try { const res = await fetch("/health", { method: "POST" }); if (res.ok) { - health.value = await res.json(); + appState.health = await res.json(); } - } catch { - health.value = null; + } catch (error) { + appState.health = null; + handleApiError(error, '健康检查'); } }; - onMounted(loadHealth); + const dismissNotification = (id) => { + const index = appState.notifications.findIndex(n => n.id === id); + if (index > -1) { + appState.notifications.splice(index, 1); + } + }; - return { route, router, health, isActive }; + onMounted(() => { + loadHealth(); + // 定时检查健康状态(每30秒) + setInterval(loadHealth, 30000); + }); + + return { + route, + router, + appState, + isActive, + dismissNotification, + getStatusClass, + statusText + }; }, template: `
+ +
+
+ {{ notification.message }} + +
+
+

STP 模具几何分析中心

-

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

+

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

@@ -86,11 +168,15 @@ const App = {
@@ -102,53 +188,77 @@ 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 state = reactive({ + selectedFile: null, + uploading: false, + error: "", + currentTask: null, + polling: false, + historySummary: null, + dragOver: false + }); - const totalFiles = computed(() => historySummary.value?.total_files || 0); + const totalFiles = computed(() => state.historySummary?.total_files || 0); const totalRecords = computed(() => - (historySummary.value?.files || []).reduce( + (state.historySummary?.files || []).reduce( (sum, f) => sum + (f.record_count || 0), 0 ) ); const latestFile = computed(() => - (historySummary.value?.files || [])[0] || null + (state.historySummary?.files || [])[0] || null ); const handleFileChange = (event) => { const file = event.target.files[0]; if (!file) return; + validateAndSelectFile(file); + }; - if ( - !file.name.toLowerCase().endsWith(".stp") && - !file.name.toLowerCase().endsWith(".step") - ) { - error.value = "请选择 STP 或 STEP 格式文件"; - selectedFile.value = null; + 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) { - error.value = "文件大小不能超过 100MB"; - selectedFile.value = null; + state.error = "文件大小不能超过 100MB"; + state.selectedFile = null; return; } - error.value = ""; - selectedFile.value = file; + 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 (!selectedFile.value) return; - uploading.value = true; - error.value = ""; + if (!state.selectedFile) return; + state.uploading = true; + state.error = ""; const formData = new FormData(); - formData.append("file", selectedFile.value); + formData.append("file", state.selectedFile); try { const res = await fetch("/upload", { @@ -159,22 +269,24 @@ const DashboardView = { throw new Error(`上传失败: ${res.status} ${res.statusText}`); } const data = await res.json(); - currentTask.value = { + 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) { - error.value = e.message || "上传失败"; + const errorMsg = handleApiError(e, '文件上传'); + state.error = errorMsg; } finally { - uploading.value = false; + state.uploading = false; } }; const startPolling = async (taskId) => { - polling.value = true; + state.polling = true; const poll = async () => { try { const res = await fetch(`/status/${taskId}`, { method: "POST" }); @@ -182,21 +294,26 @@ const DashboardView = { throw new Error("查询任务状态失败"); } const task = await res.json(); - currentTask.value = task; + state.currentTask = task; if (task.status === "completed") { - polling.value = false; - // 跳转到结果详情页 - router.push(`/result/${task.task_id}`); + state.polling = false; + addNotification(`分析完成,正在跳转到结果页面...`, 'success'); + setTimeout(() => { + router.push(`/result/${task.task_id}`); + }, 1000); } else if (task.status === "failed") { - polling.value = false; - error.value = `分析失败: ${task.error || "未知错误"}`; + state.polling = false; + const errorMsg = task.error || "未知错误"; + state.error = `分析失败: ${errorMsg}`; + addNotification(`分析失败: ${errorMsg}`, 'error'); } else { setTimeout(poll, 1000); } } catch (e) { - polling.value = false; - error.value = e.message || "轮询失败"; + state.polling = false; + const errorMsg = handleApiError(e, '任务轮询'); + state.error = errorMsg; } }; poll(); @@ -206,32 +323,38 @@ const DashboardView = { try { const res = await fetch("/api/history", { method: "POST" }); if (res.ok) { - historySummary.value = await res.json(); + state.historySummary = await res.json(); } - } catch { - historySummary.value = null; + } catch (e) { + state.historySummary = null; + handleApiError(e, '加载历史记录'); } }; + const clearFile = () => { + state.selectedFile = null; + state.error = ""; + }; + onMounted(() => { loadHistorySummary(); }); return { - selectedFile, - uploading, - error, - currentTask, - polling, - historySummary, + state, totalFiles, totalRecords, latestFile, handleFileChange, + handleDragOver, + handleDragLeave, + handleDrop, uploadFile, + clearFile, formatFileSize, statusText, formatDateTime, + getStatusClass }; }, template: ` diff --git a/templates/history.html b/templates/history.html index 118e2e6..6e99ec2 100644 --- a/templates/history.html +++ b/templates/history.html @@ -1,11 +1,13 @@ - 历史记录 - STP 模具几何分析中心 + + +
diff --git a/templates/index.html b/templates/index.html index 6ce7ce0..51c224a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,11 +1,13 @@ - - STP 模具几何分析中心 + STP 模具几何分析中心 - 仪表盘 + + +
@@ -14,5 +16,13 @@ + + + \ No newline at end of file diff --git a/templates/result.html b/templates/result.html index 4379e35..4097626 100644 --- a/templates/result.html +++ b/templates/result.html @@ -1,11 +1,13 @@ - 分析结果 - STP 模具几何分析中心 + + +