deving
This commit is contained in:
@@ -0,0 +1,165 @@
|
|||||||
|
// static/history.js
|
||||||
|
|
||||||
|
// 页面加载完成后加载历史记录
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadHistory() {
|
||||||
|
const fileList = document.getElementById('fileList');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/history');
|
||||||
|
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} 条 |
|
||||||
|
最后上传: ${new Date(file.last_upload).toLocaleString()}
|
||||||
|
</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)}`);
|
||||||
|
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">
|
||||||
|
📅 ${new Date(record.upload_time).toLocaleString()}
|
||||||
|
</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>完成时间: ${new Date(record.completed_at).toLocaleTimeString()}</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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewRecordDetails(taskId) {
|
||||||
|
// 跳转到详细结果页面
|
||||||
|
window.location.href = `/result/${taskId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
<!-- templates/history.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>历史记录 - STP文件几何分析工具</title>
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<style>
|
||||||
|
.history-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.expanded {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
background: #f9fff9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-meta {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-list {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.expanded .record-list {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-time {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-status {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-completed {
|
||||||
|
background: #e8f5e8;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-processing {
|
||||||
|
background: #fff3e0;
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-failed {
|
||||||
|
background: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-details {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-records {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 40px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #4CAF50;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="history-container">
|
||||||
|
<div class="header-nav">
|
||||||
|
<div>
|
||||||
|
<h1>📊 历史记录</h1>
|
||||||
|
<p>查看所有上传的STP文件分析记录</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<button class="upload-btn" onclick="location.href='/upload'">
|
||||||
|
📁 上传新文件
|
||||||
|
</button>
|
||||||
|
<button class="upload-btn" onclick="location.href='/'">
|
||||||
|
🔄 返回主页
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file-list" id="fileList">
|
||||||
|
<div class="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>正在加载历史记录...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/history.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
<!-- templates/result.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>分析结果 - STP文件几何分析工具</title>
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<style>
|
||||||
|
.result-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-info-card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #333;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-completed {
|
||||||
|
background: #e8f5e8;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-processing {
|
||||||
|
background: #fff3e0;
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-failed {
|
||||||
|
background: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-section {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-value {
|
||||||
|
color: #333;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #ffebee;
|
||||||
|
border: 1px solid #ffcdd2;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
color: #c62828;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 40px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="result-container">
|
||||||
|
<div class="header-nav">
|
||||||
|
<div>
|
||||||
|
<h1>📊 分析结果详情</h1>
|
||||||
|
<p>查看STP文件的详细分析结果</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<button class="upload-btn" onclick="location.href='/history'">
|
||||||
|
📋 返回历史记录
|
||||||
|
</button>
|
||||||
|
<button class="upload-btn" onclick="location.href='/upload'">
|
||||||
|
📁 上传新文件
|
||||||
|
</button>
|
||||||
|
<button class="upload-btn" onclick="location.href='/'">
|
||||||
|
🏠 返回主页
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="taskInfo" class="task-info-card">
|
||||||
|
<div class="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>正在加载任务信息...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="results-grid" id="resultsGrid">
|
||||||
|
<!-- 结果内容将通过JavaScript动态加载 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 从URL获取任务ID
|
||||||
|
const pathParts = window.location.pathname.split('/');
|
||||||
|
const taskId = pathParts[pathParts.length - 1];
|
||||||
|
|
||||||
|
// 页面加载完成后获取任务数据
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadTaskData(taskId);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadTaskData(taskId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/status/${taskId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('获取任务数据失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = await response.json();
|
||||||
|
displayTaskInfo(task);
|
||||||
|
displayResults(task);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
document.getElementById('taskInfo').innerHTML = `
|
||||||
|
<div class="error-message">
|
||||||
|
<h3>❌ 加载失败</h3>
|
||||||
|
<p>${error.message}</p>
|
||||||
|
<button class="upload-btn" onclick="loadTaskData('${taskId}')">
|
||||||
|
重试
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayTaskInfo(task) {
|
||||||
|
const taskInfo = document.getElementById('taskInfo');
|
||||||
|
|
||||||
|
taskInfo.innerHTML = `
|
||||||
|
<h2>📝 任务信息</h2>
|
||||||
|
<div class="task-info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">任务ID</div>
|
||||||
|
<div class="info-value">${task.task_id || 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">文件名</div>
|
||||||
|
<div class="info-value">${task.filename || 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">文件大小</div>
|
||||||
|
<div class="info-value">${task.file_size ? formatFileSize(task.file_size) : 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">状态</div>
|
||||||
|
<div class="info-value">
|
||||||
|
<span class="status-badge status-${task.status}">${getStatusText(task.status)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">上传时间</div>
|
||||||
|
<div class="info-value">${task.upload_time ? new Date(task.upload_time).toLocaleString() : 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">完成时间</div>
|
||||||
|
<div class="info-value">${task.completed_at ? new Date(task.completed_at).toLocaleString() : 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${task.error ? `
|
||||||
|
<div class="error-message" style="margin-top: 15px;">
|
||||||
|
<strong>错误信息:</strong> ${task.error}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayResults(task) {
|
||||||
|
const resultsGrid = document.getElementById('resultsGrid');
|
||||||
|
|
||||||
|
if (task.status !== 'completed') {
|
||||||
|
resultsGrid.innerHTML = `
|
||||||
|
<div class="no-data">
|
||||||
|
<div style="font-size: 48px; margin-bottom: 20px;">⏳</div>
|
||||||
|
<h3>任务尚未完成</h3>
|
||||||
|
<p>当前状态: ${getStatusText(task.status)}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsGrid.innerHTML = `
|
||||||
|
<div class="result-section">
|
||||||
|
<div class="section-title">📐 几何属性</div>
|
||||||
|
<div class="data-grid">
|
||||||
|
${displayGeometryData(task.geometry_data)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-section">
|
||||||
|
<div class="section-title">🔧 关键工艺参数</div>
|
||||||
|
<div class="data-grid">
|
||||||
|
${displayKeyInfo(task.key_info)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-section">
|
||||||
|
<div class="section-title">📦 边界框信息</div>
|
||||||
|
<div class="data-grid">
|
||||||
|
${displayBoundingBoxData(task.geometry_data)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-section">
|
||||||
|
<div class="section-title">🔺 拓扑结构</div>
|
||||||
|
<div class="data-grid">
|
||||||
|
${displayTopologyData(task.geometry_data)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayGeometryData(geometryData) {
|
||||||
|
if (!geometryData) return '<div class="no-data">无几何数据</div>';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="data-item">
|
||||||
|
<div class="data-label">体积</div>
|
||||||
|
<div class="data-value">${geometryData.volume ? formatNumber(geometryData.volume) + ' mm³' : 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-item">
|
||||||
|
<div class="data-label">表面积</div>
|
||||||
|
<div class="data-value">${geometryData.surface_area ? formatNumber(geometryData.surface_area) + ' mm²' : 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-item">
|
||||||
|
<div class="data-label">体积表面积比</div>
|
||||||
|
<div class="data-value">${geometryData.volume && geometryData.surface_area ? (geometryData.volume / geometryData.surface_area).toFixed(4) : 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayKeyInfo(keyInfo) {
|
||||||
|
if (!keyInfo) return '<div class="no-data">无关键信息数据</div>';
|
||||||
|
|
||||||
|
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 || {};
|
||||||
|
|
||||||
|
return `
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
<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.estimated_cycle_time || 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayBoundingBoxData(geometryData) {
|
||||||
|
if (!geometryData || !geometryData.bounding_box) return '<div class="no-data">无边界框数据</div>';
|
||||||
|
|
||||||
|
const bbox = geometryData.bounding_box;
|
||||||
|
return `
|
||||||
|
<div class="data-item">
|
||||||
|
<div class="data-label">最小坐标</div>
|
||||||
|
<div class="data-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">
|
||||||
|
<div class="data-label">最大坐标</div>
|
||||||
|
<div class="data-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)} mm</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayTopologyData(geometryData) {
|
||||||
|
if (!geometryData || !geometryData.topology) return '<div class="no-data">无拓扑数据</div>';
|
||||||
|
|
||||||
|
const topo = geometryData.topology;
|
||||||
|
return `
|
||||||
|
<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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user