This commit is contained in:
cjw
2026-02-16 18:33:17 +08:00
parent d111721ed9
commit 1525f6fc0b
5 changed files with 25 additions and 849 deletions
+22 -291
View File
@@ -15,7 +15,6 @@ from database.database import get_db_session
from utils.logger import get_logger
from sqlalchemy.ext.asyncio import AsyncSession
from core.mold_generator import MoldCavityGenerator
from datetime import datetime
logger = get_logger(__name__)
@@ -117,56 +116,14 @@ async def upload_stp(
@router.get("/status/{task_id}")
@router.post("/status/{task_id}")
async def get_status(task_id: str, db_session: AsyncSession = Depends(get_db_session)):
async def get_status(task_id: str):
"""获取任务状态"""
# 先从内存查找
if task_id in tasks:
task = tasks[task_id]
logger.info(f"返回任务状态(内存): {task_id} - {task['status']}")
return task
# 内存中没有,从数据库查找
from sqlalchemy import select
from models.database import ProcessingTask, STPFile
result = await db_session.execute(
select(ProcessingTask, STPFile)
.join(STPFile, ProcessingTask.stp_file_id == STPFile.id)
.where(ProcessingTask.task_id == task_id)
)
task_record = result.first()
if not task_record:
if task_id not in tasks:
raise HTTPException(404, "任务不存在")
task, stp_file = task_record
task_data = {
"task_id": task.task_id,
"filename": stp_file.original_filename if stp_file else "",
"file_size": stp_file.file_size if stp_file else 0,
"status": task.status,
"progress": task.progress,
"current_step": task.current_step,
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
"completed_at": task.completed_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.completed_time else "",
"error": task.error_message if task.error_message else ""
}
# 尝试获取快照中的 key_info
from models.database import AnalysisResultSnapshot
snapshot_result = await db_session.execute(
select(AnalysisResultSnapshot)
.where(AnalysisResultSnapshot.stp_file_id == stp_file.id)
.order_by(AnalysisResultSnapshot.created_time.desc())
.limit(1)
)
snapshot = snapshot_result.scalar_one_or_none()
if snapshot and snapshot.key_info:
task_data["key_info"] = snapshot.key_info
logger.info(f"返回任务状态(数据库): {task_id} - {task['status']}")
return task_data
task = tasks[task_id]
logger.info(f"返回任务状态: {task_id} - {task['status']}")
return task
@router.get("/debug/tasks")
@@ -205,7 +162,7 @@ async def get_file_history(db_session: AsyncSession = Depends(get_db_session)):
file_groups[filename].append({
"task_id": task.task_id,
"filename": filename,
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
"upload_time": task.created_time.isoformat() if task.created_time else "",
"status": task.status,
"file_size": stp_file.file_size
})
@@ -260,9 +217,9 @@ async def get_file_records(filename: str, db_session: AsyncSession = Depends(get
"task_id": task.task_id,
"filename": stp_file.original_filename,
"file_size": stp_file.file_size,
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
"upload_time": task.created_time.isoformat() if task.created_time else "",
"status": task.status,
"completed_at": task.completed_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.completed_time else ""
"completed_at": task.completed_time.isoformat() if task.completed_time else ""
})
# 按上传时间排序(最新的在前)
@@ -287,92 +244,28 @@ async def history_page(request: Request):
})
@router.get("/api/snapshots/{stp_file_id}")
@router.post("/api/snapshots/{stp_file_id}")
async def get_analysis_snapshots(stp_file_id: int, db_session: AsyncSession = Depends(get_db_session)):
"""获取指定STP文件的分析结果快照列表"""
storage_service = StorageIntegrationService()
try:
snapshots = await storage_service.get_analysis_result_snapshots(db_session, stp_file_id)
# 构建返回数据
snapshot_list = []
for snapshot in snapshots:
snapshot_list.append({
"id": snapshot.id,
"snapshot_name": snapshot.snapshot_name,
"created_time": snapshot.created_time.isoformat() if snapshot.created_time else "",
"analysis_time": snapshot.analysis_time.isoformat() if snapshot.analysis_time else "",
"task_data": snapshot.task_data
})
return {
"stp_file_id": stp_file_id,
"total_snapshots": len(snapshot_list),
"snapshots": snapshot_list
}
except Exception as e:
logger.error(f"获取分析结果快照失败: {e}")
raise HTTPException(500, f"获取快照失败: {str(e)}")
@router.get("/api/snapshot/{snapshot_id}")
@router.post("/api/snapshot/{snapshot_id}")
async def get_analysis_snapshot(snapshot_id: int, db_session: AsyncSession = Depends(get_db_session)):
"""获取指定分析结果快照的完整数据"""
from sqlalchemy import select
from models.database import AnalysisResultSnapshot
try:
result = await db_session.execute(
select(AnalysisResultSnapshot).where(AnalysisResultSnapshot.id == snapshot_id)
)
snapshot = result.scalar_one_or_none()
if not snapshot:
raise HTTPException(404, "快照不存在")
return {
"id": snapshot.id,
"snapshot_name": snapshot.snapshot_name,
"stp_file_id": snapshot.stp_file_id,
"created_time": snapshot.created_time.isoformat() if snapshot.created_time else "",
"analysis_time": snapshot.analysis_time.isoformat() if snapshot.analysis_time else "",
"task_data": snapshot.task_data,
"geometry_data": snapshot.geometry_data,
"mold_cavity_data": snapshot.mold_cavity_data,
"key_info": snapshot.key_info,
"html_info": snapshot.html_info
}
except Exception as e:
logger.error(f"获取分析结果快照详情失败: {e}")
raise HTTPException(500, f"获取快照详情失败: {str(e)}")
@router.get("/result/{task_id}")
@router.post("/result/{task_id}")
async def result_page(request: Request, task_id: str, db_session: AsyncSession = Depends(get_db_session)):
"""结果详情页面"""
from sqlalchemy import select
from models.database import ProcessingTask, STPFile, GeometryData, MoldCavityData, HTMLFile, AnalysisResultSnapshot
# 先查询任务记录
task_result = await db_session.execute(
from models.database import ProcessingTask, STPFile, GeometryData, MoldCavityData, HTMLFile
# 从数据库查询任务详情
result = await db_session.execute(
select(ProcessingTask, STPFile)
.join(STPFile, ProcessingTask.stp_file_id == STPFile.id)
.where(ProcessingTask.task_id == task_id)
)
task_record = task_result.first()
task_record = result.first()
if not task_record:
raise HTTPException(404, "任务不存在")
task, stp_file = task_record
# 构建任务详情数据
# 构建任务详情数据(先只包含基本数据)
task_data = {
"task_id": task.task_id,
"filename": stp_file.original_filename if stp_file else "",
@@ -380,90 +273,11 @@ async def result_page(request: Request, task_id: str, db_session: AsyncSession =
"status": task.status,
"progress": task.progress,
"current_step": task.current_step,
"upload_time": task.created_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.created_time else "",
"completed_at": task.completed_time.astimezone().strftime('%Y-%m-%d %H:%M:%S') if task.completed_time else "",
"created_at": task.created_time.isoformat() if task.created_time else "",
"completed_at": task.completed_time.isoformat() if task.completed_time else "",
"error": task.error_message if task.error_message else ""
}
# 尝试从分析结果快照获取数据
snapshot_result = await db_session.execute(
select(AnalysisResultSnapshot)
.where(AnalysisResultSnapshot.stp_file_id == stp_file.id)
.order_by(AnalysisResultSnapshot.created_time.desc())
.limit(1)
)
snapshot = snapshot_result.scalar_one_or_none()
if snapshot:
# 从快照获取完整的数据
task_data["geometry_data"] = snapshot.geometry_data if snapshot.geometry_data else None
task_data["key_info"] = snapshot.key_info if snapshot.key_info else None
task_data["mold_cavity_data"] = snapshot.mold_cavity_data if snapshot.mold_cavity_data else None
logger.info(f"从快照加载任务数据: {task_id}")
else:
# 如果没有快照,从各个表分别获取
result = await db_session.execute(
select(GeometryData, MoldCavityData, HTMLFile)
.outerjoin(MoldCavityData, GeometryData.stp_file_id == MoldCavityData.stp_file_id)
.outerjoin(HTMLFile, GeometryData.stp_file_id == HTMLFile.stp_file_id)
.where(GeometryData.stp_file_id == stp_file.id)
)
other_data = result.first()
if other_data:
geometry_data, mold_cavity_data, html_file = other_data
# 如果有几何数据,添加到返回结果
if geometry_data:
task_data["geometry_data"] = {
"volume": geometry_data.volume,
"surface_area": geometry_data.surface_area,
"bounding_box": {
"min": geometry_data.bounding_box_min,
"max": geometry_data.bounding_box_max,
"dimensions": [
geometry_data.bounding_box_max[0] - geometry_data.bounding_box_min[0] if geometry_data.bounding_box_max and geometry_data.bounding_box_min else 0,
geometry_data.bounding_box_max[1] - geometry_data.bounding_box_min[1] if geometry_data.bounding_box_max and geometry_data.bounding_box_min else 0,
geometry_data.bounding_box_max[2] - geometry_data.bounding_box_min[2] if geometry_data.bounding_box_max and geometry_data.bounding_box_min else 0
]
},
"topology": {
"faces": geometry_data.topology_faces,
"edges": geometry_data.topology_edges,
"vertices": geometry_data.topology_vertices
},
"center_of_mass": geometry_data.center_of_mass
}
# 如果有模具型腔数据,添加到返回结果
if mold_cavity_data:
task_data["key_info"] = {
"metadata": {
"shrinkage_rate": mold_cavity_data.shrinkage_rate,
"draft_angle": mold_cavity_data.draft_angle
},
"manufacturing_info": {
"mold_material": mold_cavity_data.mold_material,
"estimated_clamping_force": mold_cavity_data.estimated_clamping_force,
"parting_line_length": mold_cavity_data.parting_line_length,
"estimated_cycle_time": mold_cavity_data.estimated_cycle_time,
"manufacturing_tolerance": mold_cavity_data.manufacturing_tolerance
},
"mold_cavities": {
"cavity_key_info": {
"geometric_characteristics": {
"product_weight": mold_cavity_data.product_weight,
"product_volume": mold_cavity_data.product_volume,
"wall_thickness_range": mold_cavity_data.wall_thickness_range,
"complexity_score": mold_cavity_data.complexity_score
}
}
}
}
logger.info(f"从各个表加载任务数据: {task_id}")
from fastapi.templating import Jinja2Templates
import os
# 简化路径配置,直接使用当前工作目录下的templates文件夹
@@ -638,8 +452,7 @@ async def process_file_core(
"mold_hardness": "HB 150-170",
"surface_finish": "Ra 0.8 μm",
"parting_line_length": f"{parting_line_length:.2f} mm",
"estimated_cycle_time": f"{int(cycle_time)} 秒",
"manufacturing_tolerance": "±0.1 mm"
"estimated_cycle_time": f"{int(cycle_time)} 秒"
},
"mold_cavities": {
"cavity_count": 1,
@@ -656,55 +469,6 @@ async def process_file_core(
"warpage_risk": "low"
}
}
},
# 添加特征检测数据
"detected_features": [
{
"feature_type": "壁厚检测",
"confidence": 0.85,
"location": "整体结构",
"description": f"平均壁厚: {avg_thickness_mm:.2f} mm"
},
{
"feature_type": "拔模角检测",
"confidence": 0.92,
"location": "主要表面",
"description": "拔模角符合要求"
},
{
"feature_type": "几何复杂性",
"confidence": 0.78,
"location": "整体结构",
"description": f"复杂度评分: {complexity_score:.2f}"
}
],
# 添加设计建议数据
"design_recommendations": [
{
"rec_type": "壁厚优化",
"priority": "medium",
"description": f"建议优化壁厚均匀性,当前范围: {wall_thickness_min:.2f} - {wall_thickness_max:.2f} mm",
"reason": "提高注塑成型质量"
},
{
"rec_type": "拔模角",
"priority": "low",
"description": "拔模角符合标准要求",
"reason": "便于脱模"
},
{
"rec_type": "结构简化",
"priority": "medium",
"description": f"建议简化复杂结构,当前复杂度: {complexity_score:.2f}",
"reason": "降低制造成本"
}
],
# 添加质量指标数据
"quality_metrics": {
"volume_utilization": round(complexity_score * 100, 2),
"wall_thickness_uniformity": round((1 - (wall_thickness_max - wall_thickness_min) / avg_thickness_mm) * 100, 2),
"geometric_complexity": round(complexity_score * 10, 2),
"moldability_score": round((complexity_score + 1) * 50, 2)
}
}
@@ -758,39 +522,6 @@ async def process_file_core(
db_session, task_id, "completed", 100, "模具型腔生成完成"
)
# 构建完整的分析结果数据
task_info = tasks[task_id]
# 构建任务基本信息
task_data = {
"task_id": task_info["task_id"],
"filename": task_info["filename"],
"file_size": task_info["file_size"],
"status": task_info["status"],
"upload_time": task_info["upload_time"],
"completed_at": str(datetime.now())
}
# 保存分析结果快照到数据库
try:
await storage_service.save_analysis_result_snapshot(
db_session,
stp_file_id,
task_data=task_data,
geometry_data=geometry_data,
mold_cavity_data=detailed_cavity_json,
key_info=detailed_cavity_json,
html_info={
"filename": Path(html_file_path).name,
"file_path": html_file_path,
"generated_time": str(datetime.now())
}
)
logger.info(f"分析结果快照保存成功: {task_id}")
except Exception as e:
logger.error(f"保存分析结果快照失败: {e}")
# 不影响主流程,继续执行
# 更新内存任务状态
tasks[task_id]["geometry_data"] = geometry_data
tasks[task_id]["analysis_result"] = analysis_result
-31
View File
@@ -199,8 +199,6 @@ class MoldCavityData(Base):
product_volume = Column(Float, nullable=True)
wall_thickness_range = Column(String(50), nullable=True)
complexity_score = Column(Float, nullable=True)
estimated_cycle_time = Column(String(50), nullable=True)
manufacturing_tolerance = Column(String(50), nullable=True)
# 质量评估
weld_line_risk = Column(String(50), nullable=True) # 熔接痕风险
@@ -328,32 +326,3 @@ class SystemLog(Base):
def __repr__(self):
return f"<SystemLog(id={self.id}, level='{self.level}', module='{self.module}')>"
class AnalysisResultSnapshot(Base):
"""分析结果快照表 - 存储每次分析结果的完整副本"""
__tablename__ = "analysis_result_snapshots"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 快照标识信息
snapshot_name = Column(String(255), nullable=False, index=True) # 文件名+日期时间格式
snapshot_type = Column(String(50), default="complete") # complete, geometry_only, key_info_only
# 完整分析结果数据
task_data = Column(JSON, nullable=False) # 任务基本信息
geometry_data = Column(JSON, nullable=True) # 几何数据
mold_cavity_data = Column(JSON, nullable=True) # 模具型腔数据
key_info = Column(JSON, nullable=True) # 关键信息
html_info = Column(JSON, nullable=True) # HTML文件信息
# 时间戳
created_time = Column(DateTime, default=func.now())
analysis_time = Column(DateTime, nullable=False) # 原始分析时间
# 关联关系
stp_file = relationship("STPFile")
def __repr__(self):
return f"<AnalysisResultSnapshot(id={self.id}, snapshot_name='{self.snapshot_name}', stp_file_id={self.stp_file_id})>"
+2 -75
View File
@@ -3,7 +3,7 @@
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
from pathlib import Path
from typing import Optional, Dict, Any, List
from typing import Optional, Dict, Any
import json
from datetime import datetime
@@ -11,7 +11,7 @@ from models.database import (
STPFile, GeometryData, MoldCavityData,
HTMLFile, ProcessingTask, User,
FeatureDetection, DesignRecommendation,
UserActivity, SystemLog, AnalysisResultSnapshot
UserActivity, SystemLog
)
from storage.rustfs_storage import rustfs_manager
from utils.logger import get_logger
@@ -493,79 +493,6 @@ class StorageIntegrationService:
logger.info(f"STP文件及其关联数据已删除: {stp_file_id}")
async def save_analysis_result_snapshot(
self,
session: AsyncSession,
stp_file_id: int,
task_data: Dict[str, Any],
geometry_data: Optional[Dict[str, Any]] = None,
mold_cavity_data: Optional[Dict[str, Any]] = None,
key_info: Optional[Dict[str, Any]] = None,
html_info: Optional[Dict[str, Any]] = None
) -> AnalysisResultSnapshot:
"""保存分析结果快照 - 文件名+日期时间命名方式"""
# 获取STP文件信息
stp_file = await session.get(STPFile, stp_file_id)
if not stp_file:
raise ValueError(f"STP文件不存在: {stp_file_id}")
# 生成快照名称:文件名_YYYYMMDD_HHMMSS
filename = stp_file.original_filename
# 移除文件扩展名
base_name = Path(filename).stem
# 生成时间戳
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
snapshot_name = f"{base_name}_{timestamp}"
# 获取分析时间(从任务数据中获取或使用当前时间)
analysis_time_str = task_data.get('created_at') or task_data.get('upload_time')
if analysis_time_str:
analysis_time = datetime.fromisoformat(analysis_time_str.replace('Z', '+00:00'))
else:
analysis_time = datetime.now()
# 创建快照记录
snapshot = AnalysisResultSnapshot(
stp_file_id=stp_file_id,
snapshot_name=snapshot_name,
task_data=task_data,
geometry_data=geometry_data,
mold_cavity_data=mold_cavity_data,
key_info=key_info,
html_info=html_info,
analysis_time=analysis_time
)
session.add(snapshot)
await session.commit()
await session.refresh(snapshot)
logger.info(f"分析结果快照保存成功: {snapshot_name} (ID: {snapshot.id})")
return snapshot
async def get_analysis_result_snapshots(
self,
session: AsyncSession,
stp_file_id: int,
limit: int = 10
) -> List[AnalysisResultSnapshot]:
"""获取指定STP文件的分析结果快照列表"""
from sqlalchemy import select
from sqlalchemy.orm import selectinload
result = await session.execute(
select(AnalysisResultSnapshot)
.where(AnalysisResultSnapshot.stp_file_id == stp_file_id)
.order_by(AnalysisResultSnapshot.created_time.desc())
.limit(limit)
)
snapshots = result.scalars().all()
logger.info(f"获取到 {len(snapshots)} 个分析结果快照")
return snapshots
# 全局存储集成服务实例
storage_integration = StorageIntegrationService()
+1 -1
View File
@@ -159,7 +159,7 @@
<button class="upload-btn" onclick="location.href='/history'">
📋 返回历史记录
</button>
<button class="upload-btn" onclick="location.href='/'">
<button class="upload-btn" onclick="location.href='/upload'">
📁 上传新文件
</button>
<button class="upload-btn" onclick="location.href='/'">
-451
View File
@@ -1,451 +0,0 @@
<!-- templates/snapshot.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>
.snapshot-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;
}
.snapshot-info-card {
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.snapshot-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;
}
.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;
}
.no-data {
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); }
}
.error-message {
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 6px;
padding: 15px;
color: #c62828;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="snapshot-container">
<div class="header-nav">
<div>
<h1>📊 分析结果快照</h1>
<p>查看历史分析结果的完整副本</p>
</div>
<div class="nav-buttons">
<button class="upload-btn" onclick="location.href='/history'">
📋 返回历史记录
</button>
<button class="upload-btn" onclick="location.href='/'">
📁 上传新文件
</button>
<button class="upload-btn" onclick="location.href='/')">
🏠 返回主页
</button>
</div>
</div>
<div id="snapshotInfo" class="snapshot-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 snapshotId = pathParts[pathParts.length - 1];
// 页面加载完成后获取快照数据
document.addEventListener('DOMContentLoaded', function() {
loadSnapshotData(snapshotId);
});
async function loadSnapshotData(snapshotId) {
try {
const response = await fetch(`/api/snapshot/${snapshotId}`, {
method: 'POST'
});
if (!response.ok) {
throw new Error('获取快照数据失败');
}
const snapshot = await response.json();
displaySnapshotInfo(snapshot);
displayResults(snapshot);
} catch (error) {
document.getElementById('snapshotInfo').innerHTML = `
<div class="error-message">
<h3>❌ 加载失败</h3>
<p>${error.message}</p>
<button class="upload-btn" onclick="loadSnapshotData('${snapshotId}')">
重试
</button>
</div>
`;
}
}
function displaySnapshotInfo(snapshot) {
const snapshotInfo = document.getElementById('snapshotInfo');
snapshotInfo.innerHTML = `
<h2>📝 快照信息</h2>
<div class="snapshot-info-grid">
<div class="info-item">
<div class="info-label">快照名称</div>
<div class="info-value">${snapshot.snapshot_name || 'N/A'}</div>
</div>
<div class="info-item">
<div class="info-label">快照ID</div>
<div class="info-value">${snapshot.id || 'N/A'}</div>
</div>
<div class="info-item">
<div class="info-label">文件ID</div>
<div class="info-value">${snapshot.stp_file_id || 'N/A'}</div>
</div>
<div class="info-item">
<div class="info-label">分析时间</div>
<div class="info-value">${snapshot.analysis_time ? new Date(snapshot.analysis_time).toLocaleString() : 'N/A'}</div>
</div>
<div class="info-item">
<div class="info-label">保存时间</div>
<div class="info-value">${snapshot.created_time ? new Date(snapshot.created_time).toLocaleString() : 'N/A'}</div>
</div>
</div>
`;
}
function displayResults(snapshot) {
const resultsGrid = document.getElementById('resultsGrid');
if (!snapshot.task_data) {
resultsGrid.innerHTML = `
<div class="no-data">
<div style="font-size: 48px; margin-bottom: 20px;">📁</div>
<h3>无分析结果数据</h3>
<p>该快照没有包含分析结果数据</p>
</div>
`;
return;
}
const task = snapshot.task_data;
const geometryData = snapshot.geometry_data;
const keyInfo = snapshot.key_info;
resultsGrid.innerHTML = `
<div class="result-section">
<div class="section-title">📝 任务信息</div>
<div class="data-grid">
<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>
<div class="data-item">
<div class="data-label">状态</div>
<div class="data-value">${getStatusText(task.status)}</div>
</div>
<div class="data-item">
<div class="data-label">上传时间</div>
<div class="data-value">${task.upload_time ? new Date(task.upload_time).toLocaleString() : 'N/A'}</div>
</div>
<div class="data-item">
<div class="data-label">完成时间</div>
<div class="data-value">${task.completed_at ? new Date(task.completed_at).toLocaleString() : 'N/A'}</div>
</div>
</div>
</div>
${geometryData ? `
<div class="result-section">
<div class="section-title">📐 几何属性</div>
<div class="data-grid">
<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>
</div>
</div>
` : '<div class="no-data">无几何数据</div>'}
${keyInfo ? `
<div class="result-section">
<div class="section-title">🔧 关键工艺参数</div>
<div class="data-grid">
${displayKeyInfo(keyInfo)}
</div>
</div>
` : '<div class="no-data">无关键信息数据</div>'}
${geometryData && geometryData.bounding_box ? `
<div class="result-section">
<div class="section-title">📦 边界框信息</div>
<div class="data-grid">
${displayBoundingBoxData(geometryData)}
</div>
</div>
` : ''}
${geometryData && geometryData.topology ? `
<div class="result-section">
<div class="section-title">🔺 拓扑结构</div>
<div class="data-grid">
${displayTopologyData(geometryData)}
</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>