init
This commit is contained in:
@@ -0,0 +1,600 @@
|
||||
// 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 = `
|
||||
<div class="upload-icon">📁</div>
|
||||
<h3>拖放文件到此处或点击选择</h3>
|
||||
<p>最大文件大小: 100MB</p>
|
||||
<input type="file" id="fileInput" class="file-input" accept=".stp,.step">
|
||||
<button class="upload-btn" onclick="document.getElementById('fileInput').click()">
|
||||
选择文件
|
||||
</button>
|
||||
`;
|
||||
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 = `
|
||||
<div class="upload-icon">✅</div>
|
||||
<h3>已选择文件</h3>
|
||||
<p><strong>${file.name}</strong></p>
|
||||
<p>大小: ${(file.size / 1024 / 1024).toFixed(2)} MB</p>
|
||||
`;
|
||||
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}`);
|
||||
const task = await response.json();
|
||||
|
||||
console.log('任务状态:', task.status);
|
||||
console.log('完整任务数据:', task);
|
||||
|
||||
updateTaskInfo(task);
|
||||
|
||||
if (task.status === 'completed') {
|
||||
loading.style.display = 'none';
|
||||
showResultsSection();
|
||||
displayAllResults(task);
|
||||
} 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 = `
|
||||
<div class="data-item">
|
||||
<div class="data-label">📋 任务ID</div>
|
||||
<div class="data-value">${task.task_id || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">🔄 状态</div>
|
||||
<div class="data-value">
|
||||
<span class="status-badge status-${task.status}">${getStatusText(task.status)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📁 文件名</div>
|
||||
<div class="data-value">${task.filename || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📏 文件大小</div>
|
||||
<div class="data-value">${task.file_size ? formatFileSize(task.file_size) : 'N/A'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="data-item">
|
||||
<div class="data-label">📦 体积</div>
|
||||
<div class="data-value">
|
||||
${geo.volume ? formatNumber(geo.volume) : 'N/A'}
|
||||
<span class="data-unit">mm³</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📐 表面积</div>
|
||||
<div class="data-value">
|
||||
${geo.surface_area ? formatNumber(geo.surface_area) : 'N/A'}
|
||||
<span class="data-unit">mm²</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📏 体积表面积比</div>
|
||||
<div class="data-value">
|
||||
${geo.volume && geo.surface_area ? (geo.volume / geo.surface_area).toFixed(4) : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
geometryData.innerHTML = '<div class="data-item"><div class="data-value">无几何数据</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function displayBoundingBoxData(task) {
|
||||
if (task.geometry_data && task.geometry_data.bounding_box) {
|
||||
const bbox = task.geometry_data.bounding_box;
|
||||
boundingBoxData.innerHTML = `
|
||||
<div class="data-item coordinate-item">
|
||||
<div class="coordinate-label">📍 最小坐标</div>
|
||||
<div class="coordinate-value">
|
||||
X: ${bbox.min[0].toFixed(2)}<br>
|
||||
Y: ${bbox.min[1].toFixed(2)}<br>
|
||||
Z: ${bbox.min[2].toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item coordinate-item">
|
||||
<div class="coordinate-label">📍 最大坐标</div>
|
||||
<div class="coordinate-value">
|
||||
X: ${bbox.max[0].toFixed(2)}<br>
|
||||
Y: ${bbox.max[1].toFixed(2)}<br>
|
||||
Z: ${bbox.max[2].toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📏 尺寸</div>
|
||||
<div class="data-value">
|
||||
${bbox.dimensions[0].toFixed(2)} × ${bbox.dimensions[1].toFixed(2)} × ${bbox.dimensions[2].toFixed(2)}
|
||||
<span class="data-unit">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
boundingBoxData.innerHTML = '<div class="data-item"><div class="data-value">无边界框数据</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function displayTopologyData(task) {
|
||||
if (task.geometry_data && task.geometry_data.topology) {
|
||||
const topo = task.geometry_data.topology;
|
||||
topologyData.innerHTML = `
|
||||
<div class="data-item">
|
||||
<div class="data-label">🔺 面数</div>
|
||||
<div class="data-value">${topo.faces || 0}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📏 边数</div>
|
||||
<div class="data-value">${topo.edges || 0}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📍 顶点数</div>
|
||||
<div class="data-value">${topo.vertices || 0}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📊 拓扑复杂度</div>
|
||||
<div class="data-value">${calculateTopologyComplexity(topo)}</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
topologyData.innerHTML = '<div class="data-item"><div class="data-value">无拓扑数据</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
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 => `
|
||||
<div class="feature-item">
|
||||
<div class="feature-header">
|
||||
<div class="feature-type">${getFeatureTypeText(feature.feature_type)}</div>
|
||||
<div class="confidence-badge">置信度: ${(feature.confidence * 100).toFixed(0)}%</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📍 位置</div>
|
||||
<div class="data-value">${feature.location.map(v => v.toFixed(2)).join(', ')}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📏 尺寸</div>
|
||||
<div class="data-value">${feature.dimensions.map(v => v.toFixed(2)).join(' × ')} mm</div>
|
||||
</div>
|
||||
${feature.recommendations && feature.recommendations.length > 0 ? `
|
||||
<div class="data-item">
|
||||
<div class="data-label">💡 建议</div>
|
||||
<ul class="recommendation-list">
|
||||
${feature.recommendations.map(rec => `<li>${rec}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
featuresData.innerHTML = '<div class="data-item"><div class="data-value">未检测到明显特征</div></div>';
|
||||
}
|
||||
} else {
|
||||
featuresData.innerHTML = '<div class="data-item"><div class="data-value">无特征数据</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
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 => `
|
||||
<div class="recommendation-item recommendation-${rec.priority}">
|
||||
<div class="recommendation-header">
|
||||
<div class="feature-type">${getRecommendationTypeText(rec.type)}</div>
|
||||
<div class="priority-badge priority-${rec.priority}">${getPriorityText(rec.priority)}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📝 描述</div>
|
||||
<div class="data-value">${rec.description}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📋 原因</div>
|
||||
<div class="data-value">${rec.reason}</div>
|
||||
</div>
|
||||
${Object.keys(rec.parameters).length > 0 ? `
|
||||
<div class="data-item">
|
||||
<div class="data-label">⚙️ 参数</div>
|
||||
<div class="data-value">${formatParameters(rec.parameters)}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
recommendationsData.innerHTML = '<div class="data-item"><div class="data-value">无设计建议</div></div>';
|
||||
}
|
||||
} else {
|
||||
recommendationsData.innerHTML = '<div class="data-item"><div class="data-value">无建议数据</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function displayMetricsData(task) {
|
||||
if (task.analysis_result && task.analysis_result.quality_metrics) {
|
||||
const metrics = task.analysis_result.quality_metrics;
|
||||
metricsData.innerHTML = `
|
||||
<div class="metric-item">
|
||||
<div class="metric-label">体积利用率</div>
|
||||
<div class="metric-value ${getMetricClass(metrics.volume_utilization, 0.3, 0.6)}">
|
||||
${(metrics.volume_utilization * 100).toFixed(1)}%
|
||||
</div>
|
||||
<div class="data-value">${getVolumeUtilizationText(metrics.volume_utilization)}</div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<div class="metric-label">拓扑复杂度</div>
|
||||
<div class="metric-value ${getMetricClass(metrics.topology_complexity, 0.3, 0.7, true)}">
|
||||
${metrics.topology_complexity.toFixed(2)}
|
||||
</div>
|
||||
<div class="data-value">${getComplexityText(metrics.topology_complexity)}</div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<div class="metric-label">壁厚均匀性</div>
|
||||
<div class="metric-value ${getMetricClass(metrics.wall_uniformity, 0.6, 0.8)}">
|
||||
${(metrics.wall_uniformity * 100).toFixed(1)}%
|
||||
</div>
|
||||
<div class="data-value">${getUniformityText(metrics.wall_uniformity)}</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
metricsData.innerHTML = '<div class="data-item"><div class="data-value">无质量指标数据</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function displayAnalysisInfo(task) {
|
||||
let infoHTML = '';
|
||||
|
||||
if (task.geometry_data) {
|
||||
const geo = task.geometry_data;
|
||||
infoHTML += `
|
||||
<div class="data-item">
|
||||
<div class="data-label">🔧 分析方法</div>
|
||||
<div class="data-value">${geo.analysis_method || '未知'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (task.analysis_result) {
|
||||
const analysis = task.analysis_result;
|
||||
infoHTML += `
|
||||
<div class="data-item">
|
||||
<div class="data-label">✅ 分析状态</div>
|
||||
<div class="data-value">${task.status === 'completed' ? '分析完成' : '分析中'}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">📋 分析摘要</div>
|
||||
<div class="data-value">${analysis.analysis_summary || '无摘要'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
infoHTML += `
|
||||
<div class="data-item">
|
||||
<div class="data-label">📅 处理时间</div>
|
||||
<div class="data-value">${task.completed_at ? new Date(task.completed_at).toLocaleString() : new Date().toLocaleString()}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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) {
|
||||
return Object.entries(parameters).map(([key, value]) => {
|
||||
if (typeof value === 'number') {
|
||||
return `${key}: ${value.toFixed(2)}`;
|
||||
}
|
||||
return `${key}: ${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 displayCavityData(cavityData) {
|
||||
const cavityDiv = document.getElementById('cavityData');
|
||||
cavityDiv.innerHTML = `
|
||||
<pre>${JSON.stringify(cavityData, null, 2)}</pre>
|
||||
`;
|
||||
}
|
||||
|
||||
function displayKeyInfo(keyInfo) {
|
||||
const keyInfoDiv = document.getElementById('keyInfoData');
|
||||
|
||||
if (!keyInfo) {
|
||||
keyInfoDiv.innerHTML = '<div class="data-item"><div class="data-value">无关键信息数据</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const moldParams = keyInfo.mold_parameters || {};
|
||||
const geoChars = keyInfo.geometric_characteristics || {};
|
||||
const manuReqs = keyInfo.manufacturing_requirements || {};
|
||||
|
||||
keyInfoDiv.innerHTML = `
|
||||
<h4>模具参数</h4>
|
||||
<p>收缩率: ${moldParams.shrinkage_rate || 'N/A'}</p>
|
||||
<p>拔模角: ${moldParams.draft_angle || 'N/A'}</p>
|
||||
<p>分型线长度: ${moldParams.parting_line_length || 'N/A'} mm</p>
|
||||
|
||||
<h4>几何特性</h4>
|
||||
<p>产品体积: ${geoChars.product_volume || 'N/A'}</p>
|
||||
<p>产品重量: ${geoChars.product_weight || 'N/A'}</p>
|
||||
<p>壁厚范围: ${geoChars.wall_thickness_range || 'N/A'}</p>
|
||||
|
||||
<h4>制造要求</h4>
|
||||
<p>型腔材料: ${manuReqs.cavity_material || 'N/A'}</p>
|
||||
<p>硬度: ${manuReqs.hardness || 'N/A'}</p>
|
||||
<p>表面光洁度: ${manuReqs.surface_finish || 'N/A'}</p>
|
||||
<p>预估周期: ${manuReqs.estimated_cycle_time || 'N/A'}</p>
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
/* static/style.css */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #2c3e50, #34495e);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
opacity: 0.9;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 3px dashed #3498db;
|
||||
border-radius: 10px;
|
||||
padding: 60px 40px;
|
||||
margin: 20px 0;
|
||||
background: #f8f9fa;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #2980b9;
|
||||
background: #e8f4fc;
|
||||
}
|
||||
|
||||
.upload-area.dragover {
|
||||
border-color: #27ae60;
|
||||
background: #d5f4e6;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 4em;
|
||||
color: #3498db;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.upload-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(52, 152, 219, 0.3);
|
||||
}
|
||||
|
||||
.upload-btn:disabled {
|
||||
background: #bdc3c7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.results-section {
|
||||
padding: 0 40px 40px;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
margin: 15px 0;
|
||||
border-left: 5px solid #3498db;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.result-card h3 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.task-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.geometry-data {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.bounding-box-data {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.topology-data {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.features-data {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.recommendations-data {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.metrics-data {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.analysis-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.data-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.data-label {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.95em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
color: #34495e;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.data-unit {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.coordinate-item {
|
||||
background: linear-gradient(135deg, #e8f4fc, #d1edff);
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.coordinate-label {
|
||||
font-weight: bold;
|
||||
color: #2980b9;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.coordinate-value {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 6px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-processing {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background: #d1edff;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
display: none;
|
||||
border-left: 5px solid #e74c3c;
|
||||
}
|
||||
|
||||
.system-info {
|
||||
background: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
border-radius: 0 0 15px 15px;
|
||||
}
|
||||
|
||||
/* 特征和建议的特殊样式 */
|
||||
.feature-item {
|
||||
background: linear-gradient(135deg, #e8f4fc, #d1edff);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.recommendation-item {
|
||||
background: linear-gradient(135deg, #fff3cd, #ffeaa7);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #f39c12;
|
||||
}
|
||||
|
||||
.recommendation-high {
|
||||
border-left-color: #e74c3c;
|
||||
background: linear-gradient(135deg, #f8d7da, #f5c6cb);
|
||||
}
|
||||
|
||||
.recommendation-medium {
|
||||
border-left-color: #f39c12;
|
||||
background: linear-gradient(135deg, #fff3cd, #ffeaa7);
|
||||
}
|
||||
|
||||
.recommendation-low {
|
||||
border-left-color: #27ae60;
|
||||
background: linear-gradient(135deg, #d1edff, #bee5eb);
|
||||
}
|
||||
|
||||
.feature-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.feature-type {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.confidence-badge {
|
||||
background: #3498db;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.recommendation-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.priority-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.priority-high {
|
||||
background: #e74c3c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.priority-medium {
|
||||
background: #f39c12;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.priority-low {
|
||||
background: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.recommendation-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.recommendation-list li {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.recommendation-list li:before {
|
||||
content: "💡";
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.recommendation-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 1.8em;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.metric-good {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.metric-warning {
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.metric-poor {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
margin: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.results-section {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.geometry-data,
|
||||
.bounding-box-data,
|
||||
.topology-data,
|
||||
.features-data,
|
||||
.recommendations-data,
|
||||
.metrics-data,
|
||||
.analysis-info {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user