deving
This commit is contained in:
@@ -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 = `
|
|
||||||
<div class="no-records">
|
|
||||||
<div style="font-size: 48px; margin-bottom: 20px;">📁</div>
|
|
||||||
<h3>暂无历史记录</h3>
|
|
||||||
<p>还没有上传过STP文件</p>
|
|
||||||
<button class="upload-btn" onclick="location.href='/upload'">
|
|
||||||
上传第一个文件
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
fileList.innerHTML = `
|
|
||||||
<div class="no-records">
|
|
||||||
<div style="font-size: 48px; margin-bottom: 20px;">❌</div>
|
|
||||||
<h3>加载失败</h3>
|
|
||||||
<p>${error.message}</p>
|
|
||||||
<button class="upload-btn" onclick="loadHistory()">
|
|
||||||
重试
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayFileList(files) {
|
|
||||||
const fileList = document.getElementById('fileList');
|
|
||||||
|
|
||||||
fileList.innerHTML = files.map(file => `
|
|
||||||
<div class="file-item" id="file-${file.filename}">
|
|
||||||
<div class="file-header" onclick="toggleFileRecords('${file.filename}')">
|
|
||||||
<div class="file-info">
|
|
||||||
<div class="file-name">📄 ${file.filename}</div>
|
|
||||||
<div class="file-meta">
|
|
||||||
总记录: ${file.record_count} 条 |
|
|
||||||
最后上传: ${formatDateTime(file.last_upload)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="expand-btn" onclick="event.stopPropagation(); toggleFileRecords('${file.filename}')">
|
|
||||||
➕
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="record-list" id="records-${file.filename}">
|
|
||||||
<div class="loading">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>正在加载记录详情...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).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 => `
|
|
||||||
<div class="record-item" onclick="viewRecordDetails('${record.task_id}')">
|
|
||||||
<div class="record-header">
|
|
||||||
<div class="record-time">
|
|
||||||
📅 ${formatDateTime(record.upload_time)}
|
|
||||||
</div>
|
|
||||||
<div class="record-status status-${record.status}">
|
|
||||||
${getStatusText(record.status)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="record-details">
|
|
||||||
<div>文件大小: ${formatFileSize(record.file_size)}</div>
|
|
||||||
<div>任务ID: ${record.task_id}</div>
|
|
||||||
<div>状态: ${record.status}</div>
|
|
||||||
${record.completed_at ? `<div>完成时间: ${formatDateTime(record.completed_at)}</div>` : ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
} else {
|
|
||||||
recordList.innerHTML = `
|
|
||||||
<div class="no-records">
|
|
||||||
<p>暂无详细记录</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
recordList.dataset.loaded = 'true';
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
recordList.innerHTML = `
|
|
||||||
<div class="no-records">
|
|
||||||
<p>❌ 加载失败: ${error.message}</p>
|
|
||||||
<button class="upload-btn" onclick="toggleFileRecords('${filename}')">
|
|
||||||
重试
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 = `
|
|
||||||
<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}`, {
|
|
||||||
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 = `
|
|
||||||
<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) {
|
|
||||||
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 = `
|
|
||||||
<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 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 = `
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">🔧 模具参数</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">收缩率</div>
|
|
||||||
<div class="data-value">${metadata.shrinkage_rate !== undefined ? metadata.shrinkage_rate : 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">拔模角</div>
|
|
||||||
<div class="data-value">${metadata.draft_angle !== undefined ? metadata.draft_angle + '°' : 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">分型线长度</div>
|
|
||||||
<div class="data-value">${manufacturingInfo.parting_line_length || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="data-item" style="margin-top: 15px;">
|
|
||||||
<div class="data-label">📐 几何特性</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">产品体积</div>
|
|
||||||
<div class="data-value">${geoChars.product_volume || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">产品重量</div>
|
|
||||||
<div class="data-value">${geoChars.product_weight || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">壁厚范围</div>
|
|
||||||
<div class="data-value">${geoChars.wall_thickness_range || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="data-item" style="margin-top: 15px;">
|
|
||||||
<div class="data-label">⚙️ 制造要求</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">型腔材料</div>
|
|
||||||
<div class="data-value">${manufacturingInfo.mold_material || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">硬度</div>
|
|
||||||
<div class="data-value">${manufacturingInfo.mold_hardness || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">表面光洁度</div>
|
|
||||||
<div class="data-value">${manufacturingInfo.surface_finish || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-label">预估周期</div>
|
|
||||||
<div class="data-value">${manufacturingInfo.estimated_cycle_time || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
+222
-69
@@ -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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: var(--font-family);
|
||||||
background: linear-gradient(135deg, #1f2933 0%, #111827 100%);
|
background: linear-gradient(135deg, var(--gray-900) 0%, var(--gray-800) 100%);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 16px;
|
padding: var(--space-md);
|
||||||
color: #111827;
|
color: var(--gray-800);
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 旧版容器保留,兼容可能的使用 */
|
/* 应用外壳 */
|
||||||
.container {
|
.app-shell {
|
||||||
max-width: 1200px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: white;
|
min-height: calc(100vh - 2 * var(--space-md));
|
||||||
border-radius: 15px;
|
display: flex;
|
||||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
flex-direction: column;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
/* 通知系统 */
|
||||||
background: linear-gradient(135deg, #2c3e50, #34495e);
|
.notification-container {
|
||||||
color: white;
|
position: fixed;
|
||||||
padding: 30px;
|
top: var(--space-md);
|
||||||
text-align: center;
|
right: var(--space-md);
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header h1 {
|
.notification {
|
||||||
font-size: 2.5em;
|
background: white;
|
||||||
margin-bottom: 10px;
|
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 {
|
.notification-success {
|
||||||
opacity: 0.9;
|
border-left-color: var(--success-color);
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-section {
|
.notification-error {
|
||||||
padding: 40px;
|
border-left-color: var(--error-color);
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-area {
|
.notification-warning {
|
||||||
border: 3px dashed #3498db;
|
border-left-color: var(--warning-color);
|
||||||
border-radius: 10px;
|
|
||||||
padding: 60px 40px;
|
|
||||||
margin: 20px 0;
|
|
||||||
background: #f8f9fa;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-area:hover {
|
.notification-message {
|
||||||
border-color: #2980b9;
|
flex: 1;
|
||||||
background: #e8f4fc;
|
color: var(--gray-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-area.dragover {
|
.notification-close {
|
||||||
border-color: #27ae60;
|
background: none;
|
||||||
background: #d5f4e6;
|
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 {
|
.notification-close:hover {
|
||||||
font-size: 4em;
|
background: var(--gray-100);
|
||||||
color: #3498db;
|
color: var(--gray-600);
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-input {
|
@keyframes slideIn {
|
||||||
display: none;
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-btn {
|
/* 应用头部 */
|
||||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
.app-header {
|
||||||
color: white;
|
background: white;
|
||||||
border: none;
|
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||||
padding: 15px 40px;
|
padding: var(--space-2xl);
|
||||||
font-size: 1.1em;
|
box-shadow: var(--shadow-md);
|
||||||
border-radius: 50px;
|
margin-bottom: var(--space-lg);
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
margin: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-btn:hover {
|
.app-title {
|
||||||
transform: translateY(-2px);
|
display: flex;
|
||||||
box-shadow: 0 10px 20px rgba(52, 152, 219, 0.3);
|
align-items: center;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-btn:disabled {
|
.logo {
|
||||||
background: #bdc3c7;
|
font-size: var(--font-size-3xl);
|
||||||
cursor: not-allowed;
|
background: linear-gradient(135deg, var(--primary-color), var(--info-color));
|
||||||
transform: none;
|
-webkit-background-clip: text;
|
||||||
box-shadow: none;
|
-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 {
|
.results-section {
|
||||||
|
|||||||
+183
-60
@@ -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 { createRouter, createWebHistory, useRoute, useRouter } = VueRouter;
|
||||||
|
|
||||||
|
// 全局状态管理
|
||||||
|
const appState = reactive({
|
||||||
|
health: null,
|
||||||
|
loading: false,
|
||||||
|
notifications: []
|
||||||
|
});
|
||||||
|
|
||||||
// 通用工具函数
|
// 通用工具函数
|
||||||
function formatFileSize(bytes) {
|
function formatFileSize(bytes) {
|
||||||
if (!bytes || bytes === 0) return "0 Bytes";
|
if (!bytes || bytes === 0) return "0 Bytes";
|
||||||
@@ -23,7 +32,7 @@ function formatNumber(num) {
|
|||||||
function formatDateTime(dateString) {
|
function formatDateTime(dateString) {
|
||||||
if (!dateString) return "N/A";
|
if (!dateString) return "N/A";
|
||||||
try {
|
try {
|
||||||
return new Date(dateString).toLocaleString();
|
return new Date(dateString).toLocaleString('zh-CN');
|
||||||
} catch {
|
} catch {
|
||||||
return dateString;
|
return dateString;
|
||||||
}
|
}
|
||||||
@@ -39,12 +48,49 @@ function statusText(status) {
|
|||||||
return map[status] || 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 = {
|
const App = {
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const health = ref(null);
|
|
||||||
|
|
||||||
const isActive = (pathPrefix) =>
|
const isActive = (pathPrefix) =>
|
||||||
computed(() => route.path === pathPrefix || route.path.startsWith(pathPrefix));
|
computed(() => route.path === pathPrefix || route.path.startsWith(pathPrefix));
|
||||||
@@ -53,30 +99,66 @@ const App = {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch("/health", { method: "POST" });
|
const res = await fetch("/health", { method: "POST" });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
health.value = await res.json();
|
appState.health = await res.json();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
health.value = null;
|
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: `
|
template: `
|
||||||
<div class="app-shell">
|
<div class="app-shell">
|
||||||
|
<!-- 通知区域 -->
|
||||||
|
<div class="notification-container" v-if="appState.notifications.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="notification in appState.notifications"
|
||||||
|
:key="notification.id"
|
||||||
|
:class="['notification', 'notification-' + notification.type]"
|
||||||
|
>
|
||||||
|
<span class="notification-message">{{ notification.message }}</span>
|
||||||
|
<button class="notification-close" @click="dismissNotification(notification.id)">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
<div class="app-title">
|
<div class="app-title">
|
||||||
<span class="logo">🔧</span>
|
<span class="logo">🔧</span>
|
||||||
<div>
|
<div>
|
||||||
<h1>STP 模具几何分析中心</h1>
|
<h1>STP 模具几何分析中心</h1>
|
||||||
<p>上传模型 → 自动分析几何 / 型腔 / 工艺 → 聚合展示关键指标</p>
|
<p class="app-subtitle">专业模具几何分析,支持型腔自动生成与工艺参数计算</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<nav class="app-nav">
|
<nav class="app-nav">
|
||||||
<router-link :class="['nav-link', { active: isActive('/').value }]" to="/">仪表盘</router-link>
|
<router-link :class="['nav-link', { active: isActive('/').value }]" to="/">
|
||||||
<router-link :class="['nav-link', { active: isActive('/history').value }]" to="/history">历史记录</router-link>
|
📊 仪表盘
|
||||||
|
</router-link>
|
||||||
|
<router-link :class="['nav-link', { active: isActive('/history').value }]" to="/history">
|
||||||
|
📋 历史记录
|
||||||
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -86,11 +168,15 @@ const App = {
|
|||||||
|
|
||||||
<footer class="app-footer">
|
<footer class="app-footer">
|
||||||
<div class="footer-left">
|
<div class="footer-left">
|
||||||
<span>PythonOCC: <strong>{{ health?.pythonocc ? '可用' : '未知' }}</strong></span>
|
<span>PythonOCC: <strong :class="appState.health?.pythonocc ? 'status-success' : 'status-error'">
|
||||||
<span v-if="health"> | 当前任务数: {{ health.total_tasks }}</span>
|
{{ appState.health?.pythonocc ? '可用' : '未知' }}
|
||||||
|
</strong></span>
|
||||||
|
<span v-if="appState.health"> | 当前任务数: {{ appState.health.total_tasks }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-right">
|
<div class="footer-right">
|
||||||
<span>后端版本: 3.0.0</span>
|
<span>版本: 3.0.0</span>
|
||||||
|
<span> | </span>
|
||||||
|
<span>模具几何分析系统</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,53 +188,77 @@ const DashboardView = {
|
|||||||
setup() {
|
setup() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const selectedFile = ref(null);
|
const state = reactive({
|
||||||
const uploading = ref(false);
|
selectedFile: null,
|
||||||
const error = ref("");
|
uploading: false,
|
||||||
const currentTask = ref(null);
|
error: "",
|
||||||
const polling = ref(false);
|
currentTask: null,
|
||||||
const historySummary = ref(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(() =>
|
const totalRecords = computed(() =>
|
||||||
(historySummary.value?.files || []).reduce(
|
(state.historySummary?.files || []).reduce(
|
||||||
(sum, f) => sum + (f.record_count || 0),
|
(sum, f) => sum + (f.record_count || 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const latestFile = computed(() =>
|
const latestFile = computed(() =>
|
||||||
(historySummary.value?.files || [])[0] || null
|
(state.historySummary?.files || [])[0] || null
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFileChange = (event) => {
|
const handleFileChange = (event) => {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
validateAndSelectFile(file);
|
||||||
|
};
|
||||||
|
|
||||||
if (
|
const validateAndSelectFile = (file) => {
|
||||||
!file.name.toLowerCase().endsWith(".stp") &&
|
if (!file.name.toLowerCase().endsWith(".stp") &&
|
||||||
!file.name.toLowerCase().endsWith(".step")
|
!file.name.toLowerCase().endsWith(".step")) {
|
||||||
) {
|
state.error = "请选择 STP 或 STEP 格式文件";
|
||||||
error.value = "请选择 STP 或 STEP 格式文件";
|
state.selectedFile = null;
|
||||||
selectedFile.value = null;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.size > 100 * 1024 * 1024) {
|
if (file.size > 100 * 1024 * 1024) {
|
||||||
error.value = "文件大小不能超过 100MB";
|
state.error = "文件大小不能超过 100MB";
|
||||||
selectedFile.value = null;
|
state.selectedFile = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
error.value = "";
|
state.error = "";
|
||||||
selectedFile.value = file;
|
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 () => {
|
const uploadFile = async () => {
|
||||||
if (!selectedFile.value) return;
|
if (!state.selectedFile) return;
|
||||||
uploading.value = true;
|
state.uploading = true;
|
||||||
error.value = "";
|
state.error = "";
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", selectedFile.value);
|
formData.append("file", state.selectedFile);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/upload", {
|
const res = await fetch("/upload", {
|
||||||
@@ -159,22 +269,24 @@ const DashboardView = {
|
|||||||
throw new Error(`上传失败: ${res.status} ${res.statusText}`);
|
throw new Error(`上传失败: ${res.status} ${res.statusText}`);
|
||||||
}
|
}
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
currentTask.value = {
|
state.currentTask = {
|
||||||
task_id: data.task_id,
|
task_id: data.task_id,
|
||||||
status: "processing",
|
status: "processing",
|
||||||
filename: data.file_info?.filename,
|
filename: data.file_info?.filename,
|
||||||
file_size: data.file_info?.size,
|
file_size: data.file_info?.size,
|
||||||
};
|
};
|
||||||
|
addNotification(`文件上传成功,开始分析...`, 'success');
|
||||||
startPolling(data.task_id);
|
startPolling(data.task_id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e.message || "上传失败";
|
const errorMsg = handleApiError(e, '文件上传');
|
||||||
|
state.error = errorMsg;
|
||||||
} finally {
|
} finally {
|
||||||
uploading.value = false;
|
state.uploading = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startPolling = async (taskId) => {
|
const startPolling = async (taskId) => {
|
||||||
polling.value = true;
|
state.polling = true;
|
||||||
const poll = async () => {
|
const poll = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/status/${taskId}`, { method: "POST" });
|
const res = await fetch(`/status/${taskId}`, { method: "POST" });
|
||||||
@@ -182,21 +294,26 @@ const DashboardView = {
|
|||||||
throw new Error("查询任务状态失败");
|
throw new Error("查询任务状态失败");
|
||||||
}
|
}
|
||||||
const task = await res.json();
|
const task = await res.json();
|
||||||
currentTask.value = task;
|
state.currentTask = task;
|
||||||
|
|
||||||
if (task.status === "completed") {
|
if (task.status === "completed") {
|
||||||
polling.value = false;
|
state.polling = false;
|
||||||
// 跳转到结果详情页
|
addNotification(`分析完成,正在跳转到结果页面...`, 'success');
|
||||||
router.push(`/result/${task.task_id}`);
|
setTimeout(() => {
|
||||||
|
router.push(`/result/${task.task_id}`);
|
||||||
|
}, 1000);
|
||||||
} else if (task.status === "failed") {
|
} else if (task.status === "failed") {
|
||||||
polling.value = false;
|
state.polling = false;
|
||||||
error.value = `分析失败: ${task.error || "未知错误"}`;
|
const errorMsg = task.error || "未知错误";
|
||||||
|
state.error = `分析失败: ${errorMsg}`;
|
||||||
|
addNotification(`分析失败: ${errorMsg}`, 'error');
|
||||||
} else {
|
} else {
|
||||||
setTimeout(poll, 1000);
|
setTimeout(poll, 1000);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
polling.value = false;
|
state.polling = false;
|
||||||
error.value = e.message || "轮询失败";
|
const errorMsg = handleApiError(e, '任务轮询');
|
||||||
|
state.error = errorMsg;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
poll();
|
poll();
|
||||||
@@ -206,32 +323,38 @@ const DashboardView = {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch("/api/history", { method: "POST" });
|
const res = await fetch("/api/history", { method: "POST" });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
historySummary.value = await res.json();
|
state.historySummary = await res.json();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (e) {
|
||||||
historySummary.value = null;
|
state.historySummary = null;
|
||||||
|
handleApiError(e, '加载历史记录');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearFile = () => {
|
||||||
|
state.selectedFile = null;
|
||||||
|
state.error = "";
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadHistorySummary();
|
loadHistorySummary();
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedFile,
|
state,
|
||||||
uploading,
|
|
||||||
error,
|
|
||||||
currentTask,
|
|
||||||
polling,
|
|
||||||
historySummary,
|
|
||||||
totalFiles,
|
totalFiles,
|
||||||
totalRecords,
|
totalRecords,
|
||||||
latestFile,
|
latestFile,
|
||||||
handleFileChange,
|
handleFileChange,
|
||||||
|
handleDragOver,
|
||||||
|
handleDragLeave,
|
||||||
|
handleDrop,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
|
clearFile,
|
||||||
formatFileSize,
|
formatFileSize,
|
||||||
statusText,
|
statusText,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
|
getStatusClass
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<!-- templates/history.html -->
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>历史记录 - STP 模具几何分析中心</title>
|
<title>历史记录 - STP 模具几何分析中心</title>
|
||||||
|
<meta name="description" content="查看所有已处理的STP文件历史记录和分析任务">
|
||||||
|
<meta name="keywords" content="STP历史记录,分析任务,文件管理">
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<link rel="preconnect" href="https://unpkg.com">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
+12
-2
@@ -1,11 +1,13 @@
|
|||||||
<!-- templates/index.html -->
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>STP 模具几何分析中心</title>
|
<title>STP 模具几何分析中心 - 仪表盘</title>
|
||||||
|
<meta name="description" content="专业的STP/STEP文件几何分析工具,支持模具型腔自动生成和工艺参数计算">
|
||||||
|
<meta name="keywords" content="STP,STEP,模具分析,几何分析,型腔设计">
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<link rel="preconnect" href="https://unpkg.com">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
@@ -14,5 +16,13 @@
|
|||||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||||
<script src="https://unpkg.com/vue-router@4/dist/vue-router.global.prod.js"></script>
|
<script src="https://unpkg.com/vue-router@4/dist/vue-router.global.prod.js"></script>
|
||||||
<script src="/static/vue-app.js"></script>
|
<script src="/static/vue-app.js"></script>
|
||||||
|
|
||||||
|
<!-- 预加载关键资源 -->
|
||||||
|
<script>
|
||||||
|
// 页面加载优化
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
console.log('STP模具分析中心已加载完成');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
<!-- templates/result.html -->
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>分析结果 - STP 模具几何分析中心</title>
|
<title>分析结果 - STP 模具几何分析中心</title>
|
||||||
|
<meta name="description" content="查看详细的STP文件几何分析结果,包括模具型腔参数和工艺建议">
|
||||||
|
<meta name="keywords" content="STP分析结果,模具参数,几何分析报告">
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<link rel="preconnect" href="https://unpkg.com">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user