AnsysLink/frontend/static/js/main.js

997 lines
40 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// CAE Mesh Generator - Main JavaScript
class MeshGeneratorApp {
constructor() {
this.uploadedFile = null;
this.processingStatus = null;
this.statusPollingInterval = null;
this.init();
}
init() {
this.setupEventListeners();
this.checkSystemStatus();
}
setupEventListeners() {
// File upload events
const fileInput = document.getElementById('file-input');
const uploadArea = document.getElementById('upload-area');
if (fileInput) fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
// Drag and drop events
if (uploadArea) {
uploadArea.addEventListener('dragover', (e) => this.handleDragOver(e));
uploadArea.addEventListener('dragleave', (e) => this.handleDragLeave(e));
uploadArea.addEventListener('drop', (e) => this.handleFileDrop(e));
}
// Processing control buttons
const startBtn = document.getElementById('start-processing');
const pauseBtn = document.getElementById('pause-processing');
const stopBtn = document.getElementById('stop-processing');
if (startBtn) startBtn.addEventListener('click', () => this.startProcessing());
if (pauseBtn) pauseBtn.addEventListener('click', () => this.pauseProcessing());
if (stopBtn) stopBtn.addEventListener('click', () => this.stopProcessing());
// Step item click events - only for quality and results
const stepQuality = document.getElementById('step-quality');
const stepResults = document.getElementById('step-results');
if (stepQuality) stepQuality.addEventListener('click', () => this.handleStepClick('quality'));
if (stepResults) stepResults.addEventListener('click', () => this.handleStepClick('results'));
// Download buttons - ensure they are properly bound
const downloadMesh = document.getElementById('download-mesh');
const downloadReport = document.getElementById('download-report');
const downloadImage = document.getElementById('download-image');
console.log('Setting up download button event listeners...');
console.log('Download mesh button:', downloadMesh);
console.log('Download report button:', downloadReport);
console.log('Download image button:', downloadImage);
if (downloadMesh) {
downloadMesh.addEventListener('click', (e) => {
console.log('Download mesh button clicked');
e.preventDefault();
this.downloadMesh();
});
} else {
console.error('Download mesh button not found!');
}
if (downloadReport) {
downloadReport.addEventListener('click', (e) => {
console.log('Download report button clicked');
e.preventDefault();
this.downloadReport();
});
} else {
console.error('Download report button not found!');
}
if (downloadImage) {
downloadImage.addEventListener('click', (e) => {
console.log('Download image button clicked');
e.preventDefault();
this.downloadImage();
});
} else {
console.error('Download image button not found!');
}
// Visualization controls (with null checks)
const viewSelector = document.getElementById('view-selector');
const refreshVisualization = document.getElementById('refresh-visualization');
if (viewSelector) {
viewSelector.addEventListener('change', (e) => this.changeView(e.target.value));
}
if (refreshVisualization) {
refreshVisualization.addEventListener('click', () => this.refreshVisualization());
}
}
// Step Click Handling
handleStepClick(stepType) {
console.log(`Step clicked: ${stepType}`);
switch(stepType) {
case 'process':
if (this.uploadedFile && !this.isProcessing()) {
this.showAlert('信息', '开始网格生成...', 'info');
this.startProcessing();
} else if (!this.uploadedFile) {
this.showAlert('提示', '请先上传STEP文件', 'warning');
} else if (this.isProcessing()) {
this.showAlert('提示', '网格生成正在进行中...', 'info');
}
break;
case 'quality':
this.showAlert('信息', '网格生成完成后才能查看质量检查结果', 'info');
break;
case 'results':
this.showAlert('信息', '网格生成完成后才能下载结果', 'info');
break;
}
}
isProcessing() {
return this.statusPollingInterval !== null;
}
// File Upload Handling
handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
this.validateAndUploadFile(file);
}
}
handleDragOver(event) {
event.preventDefault();
event.stopPropagation();
const uploadArea = document.getElementById('upload-area');
if (uploadArea) uploadArea.classList.add('dragover');
}
handleDragLeave(event) {
event.preventDefault();
event.stopPropagation();
const uploadArea = document.getElementById('upload-area');
if (uploadArea) uploadArea.classList.remove('dragover');
}
handleFileDrop(event) {
event.preventDefault();
event.stopPropagation();
const uploadArea = document.getElementById('upload-area');
if (uploadArea) uploadArea.classList.remove('dragover');
const files = event.dataTransfer.files;
if (files.length > 0) {
this.validateAndUploadFile(files[0]);
}
}
validateAndUploadFile(file) {
// Validate file type
const allowedTypes = ['.step', '.stp'];
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedTypes.includes(fileExtension)) {
this.showAlert('错误', '请选择STEP格式文件 (.step 或 .stp)', 'danger');
return;
}
// Validate file size (100MB limit)
const maxSize = 100 * 1024 * 1024; // 100MB in bytes
if (file.size > maxSize) {
this.showAlert('错误', '文件大小不能超过100MB', 'danger');
return;
}
this.uploadFile(file);
}
async uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
try {
this.showFileInfo(file, '上传中...');
this.updateStepStatus('upload', 'active');
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
if (response.ok) {
this.uploadedFile = file;
this.showFileInfo(file, '上传成功');
this.updateStepStatus('upload', 'completed');
this.updateStepStatus('validate', 'completed');
this.enableProcessingControls();
this.showProcessingSection();
this.showAlert('成功', '文件上传成功!可以开始网格生成。', 'success');
} else {
throw new Error(result.error || '上传失败');
}
} catch (error) {
console.error('Upload error:', error);
this.showAlert('错误', `文件上传失败: ${error.message}`, 'danger');
this.updateStepStatus('upload', 'error');
}
}
showFileInfo(file, status) {
const fileInfo = document.getElementById('file-info');
const fileName = document.getElementById('file-name');
const fileSize = document.getElementById('file-size');
const fileStatus = document.getElementById('file-status');
if (fileName) fileName.textContent = file.name;
if (fileSize) fileSize.textContent = this.formatFileSize(file.size);
if (fileStatus) fileStatus.textContent = status;
if (fileInfo) {
fileInfo.style.display = 'block';
fileInfo.classList.add('fade-in');
}
}
formatFileSize(bytes) {
if (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];
}
// Processing Control
enableProcessingControls() {
const startBtn = document.getElementById('start-processing');
if (startBtn) startBtn.disabled = false;
}
async startProcessing() {
if (!this.uploadedFile) {
this.showAlert('错误', '请先上传STEP文件', 'warning');
return;
}
try {
this.updateProcessingUI(true);
this.updateStepStatus('process', 'active');
this.showProcessingSection();
const response = await fetch('/api/mesh/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
simulation_mode: false
})
});
const result = await response.json();
if (response.ok) {
this.startStatusPolling();
this.addLogEntry('info', '网格生成已启动');
} else {
throw new Error(result.error || '启动失败');
}
} catch (error) {
console.error('Processing error:', error);
this.showAlert('错误', `启动网格生成失败: ${error.message}`, 'danger');
this.updateProcessingUI(false);
}
}
pauseProcessing() {
this.showAlert('信息', '暂停功能暂未实现', 'info');
}
stopProcessing() {
if (this.statusPollingInterval) {
clearInterval(this.statusPollingInterval);
this.statusPollingInterval = null;
}
this.updateProcessingUI(false);
this.addLogEntry('warning', '处理已停止');
}
updateProcessingUI(isProcessing) {
const startBtn = document.getElementById('start-processing');
const pauseBtn = document.getElementById('pause-processing');
const stopBtn = document.getElementById('stop-processing');
if (startBtn) startBtn.disabled = isProcessing;
if (pauseBtn) pauseBtn.disabled = !isProcessing;
if (stopBtn) stopBtn.disabled = !isProcessing;
}
showProcessingSection() {
const processingSection = document.getElementById('processing-section');
if (processingSection) {
processingSection.style.display = 'block';
processingSection.classList.add('fade-in');
}
}
// Status Polling
startStatusPolling() {
this.statusPollingInterval = setInterval(() => {
this.checkProcessingStatus();
}, 2000);
}
async checkProcessingStatus() {
try {
const response = await fetch('/api/mesh/status');
const result = await response.json();
if (response.ok) {
const statusData = result.status;
this.updateStatusDisplay(statusData);
if (statusData.status === 'COMPLETED') {
this.handleProcessingComplete();
} else if (statusData.status === 'ERROR') {
this.handleProcessingError(statusData.error_message);
}
}
} catch (error) {
console.error('Status polling error:', error);
}
}
updateStatusDisplay(status) {
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const currentStep = document.getElementById('current-step');
const estimatedTime = document.getElementById('estimated-time');
const progress = status.progress_percentage || 0;
if (progressBar) progressBar.style.width = `${progress}%`;
if (progressText) progressText.textContent = `${progress}%`;
if (currentStep) currentStep.textContent = status.current_operation || status.message || '处理中...';
if (estimatedTime) estimatedTime.textContent = status.estimated_time || '--';
if (status.message) {
const logContainer = document.getElementById('log-container');
const lastLogEntry = logContainer?.lastElementChild;
const newMessage = status.message;
if (!lastLogEntry || !lastLogEntry.textContent.includes(newMessage)) {
this.addLogEntry('info', newMessage);
}
}
}
handleProcessingComplete() {
clearInterval(this.statusPollingInterval);
this.statusPollingInterval = null;
this.updateProcessingUI(false);
this.updateStepStatus('process', 'completed');
this.updateStepStatus('quality', 'completed');
this.updateStepStatus('results', 'completed');
this.addLogEntry('success', '网格生成完成!');
this.showAlert('成功', '网格生成已完成!', 'success');
this.loadResults();
}
handleProcessingError(error) {
clearInterval(this.statusPollingInterval);
this.statusPollingInterval = null;
this.updateProcessingUI(false);
this.addLogEntry('error', `处理失败: ${error}`);
this.showAlert('错误', `网格生成失败: ${error}`, 'danger');
}
addLogEntry(type, message) {
const logContainer = document.getElementById('log-container');
if (!logContainer) return;
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${type}`;
logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span>${message}`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
// Results Display
async loadResults() {
try {
const response = await fetch('/api/mesh/result');
const result = await response.json();
if (response.ok) {
this.displayResults(result);
this.showResultsSection();
} else {
throw new Error(result.error || '获取结果失败');
}
} catch (error) {
console.error('Load results error:', error);
this.showAlert('错误', `加载结果失败: ${error.message}`, 'danger');
}
}
displayResults(apiResult) {
const result = apiResult.result;
const basicInfo = result.basic_info;
// Update statistics
const elementCount = document.getElementById('element-count');
const nodeCount = document.getElementById('node-count');
const qualityScore = document.getElementById('quality-score');
const generationTime = document.getElementById('generation-time');
if (elementCount) elementCount.textContent = basicInfo.element_count?.toLocaleString() || '--';
if (nodeCount) nodeCount.textContent = basicInfo.node_count?.toLocaleString() || '--';
if (qualityScore) qualityScore.textContent = basicInfo.quality_score?.toFixed(2) || '--';
if (generationTime) generationTime.textContent = basicInfo.generation_time?.toFixed(1) || '--';
// Update quality status
this.updateQualityStatus(basicInfo.quality_score);
this.displayQualityDetails(result.quality_details);
// Load visualization
this.loadVisualization();
// Enable download buttons
this.enableDownloadButtons();
}
updateQualityStatus(score) {
const qualityStatus = document.getElementById('quality-status');
if (!qualityStatus) return;
const badge = qualityStatus.querySelector('.badge');
if (!badge) return;
const normalizedScore = score / 100;
if (normalizedScore >= 0.8) {
badge.className = 'badge bg-success';
badge.textContent = '优秀';
} else if (normalizedScore >= 0.6) {
badge.className = 'badge bg-warning';
badge.textContent = '良好';
} else if (normalizedScore >= 0.4) {
badge.className = 'badge bg-warning';
badge.textContent = '一般';
} else {
badge.className = 'badge bg-danger';
badge.textContent = '需要改进';
}
}
displayQualityDetails(details) {
const qualityDetails = document.getElementById('quality-details');
if (!qualityDetails) return;
const currentScore = document.getElementById('quality-score')?.textContent || '--';
qualityDetails.innerHTML = `
<div class="quality-info">
<div class="row">
<div class="col-6">
<p><strong>当前分数:</strong> ${currentScore}/100</p>
<p><strong>状态:</strong> 网格质量检查通过</p>
</div>
<div class="col-6">
<p><strong>最高分:</strong> 100分</p>
<p><strong>合格线:</strong> 40分</p>
</div>
</div>
<div class="mt-2">
<small class="text-muted">质量评估基于单元形状、长宽比、偏斜度等多项指标综合计算</small>
</div>
</div>
`;
}
showResultsSection() {
const resultsSection = document.getElementById('results-section');
if (resultsSection) {
resultsSection.style.display = 'block';
resultsSection.classList.add('fade-in');
}
}
loadVisualization() {
const visualizationContainer = this.findOrCreateVisualizationContainer();
fetch('/api/mesh/result?include_visualization=true')
.then(response => response.json())
.then(result => {
if (result.success && result.result.basic_info.mesh_image_path) {
this.displayMeshImage(result.result.basic_info.mesh_image_path);
} else {
this.loadStaticVisualization();
}
})
.catch(error => {
console.log('可视化加载失败:', error);
this.loadStaticVisualization();
});
}
loadStaticVisualization() {
const visualizationContainer = document.getElementById('mesh-visualization');
if (!visualizationContainer) return;
const possibleImages = [
'/static/visualizations/current_mesh_preview.png',
'/static/visualizations/mesh_visualization_' + new Date().toISOString().slice(0,10).replace(/-/g,'') + '.png'
];
this.tryLoadImages(possibleImages, 0, visualizationContainer);
}
tryLoadImages(imageUrls, index, container) {
if (index >= imageUrls.length) {
container.innerHTML = `
<div class="text-center p-4">
<i class="fas fa-cube fa-3x text-muted mb-3"></i>
<p class="text-muted">网格可视化图像暂不可用</p>
<small class="text-muted">网格已成功生成,但预览图像未找到</small>
</div>
`;
return;
}
const img = new Image();
img.onload = () => {
container.innerHTML = `
<img src="${imageUrls[index]}" alt="网格可视化" class="img-fluid rounded" style="max-height: 400px;">
<p class="mt-2 text-muted">网格可视化图像</p>
`;
};
img.onerror = () => {
this.tryLoadImages(imageUrls, index + 1, container);
};
img.src = imageUrls[index];
}
findOrCreateVisualizationContainer() {
let container = document.getElementById('mesh-visualization');
if (!container) {
const resultsSection = document.getElementById('results-section');
if (!resultsSection) return null;
const visualizationRow = document.createElement('div');
visualizationRow.className = 'row mt-4';
visualizationRow.innerHTML = `
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-eye me-2"></i>
网格可视化
</h5>
</div>
<div class="card-body">
<div id="mesh-visualization" class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2 text-muted">正在加载可视化图像...</p>
</div>
</div>
</div>
</div>
`;
const exportRow = resultsSection.querySelector('.row.mt-4');
if (exportRow) {
exportRow.parentNode.insertBefore(visualizationRow, exportRow);
} else {
resultsSection.appendChild(visualizationRow);
}
container = document.getElementById('mesh-visualization');
}
return container;
}
enableDownloadButtons() {
console.log('Enabling download buttons...');
const downloadMesh = document.getElementById('download-mesh');
const downloadReport = document.getElementById('download-report');
const downloadImage = document.getElementById('download-image');
console.log('Found buttons:', {
mesh: !!downloadMesh,
report: !!downloadReport,
image: !!downloadImage
});
if (downloadMesh) {
downloadMesh.disabled = false;
downloadMesh.classList.remove('disabled');
console.log('Mesh download button enabled');
} else {
console.error('Mesh download button not found!');
}
if (downloadReport) {
downloadReport.disabled = false;
downloadReport.classList.remove('disabled');
console.log('Report download button enabled');
} else {
console.error('Report download button not found!');
}
if (downloadImage) {
downloadImage.disabled = false;
downloadImage.classList.remove('disabled');
console.log('Image download button enabled');
} else {
console.error('Image download button not found!');
}
console.log('All download buttons have been processed');
}
// Download Functions
async downloadMesh() {
try {
console.log('Starting mesh file download...');
this.showAlert('信息', '正在准备网格文件下载...', 'info');
const response = await fetch('/api/mesh/download/mesh');
console.log('Download response status:', response.status);
if (response.ok) {
const blob = await response.blob();
console.log('Downloaded blob size:', blob.size);
if (blob.size === 0) {
throw new Error('下载的文件为空');
}
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `blade_mesh_${new Date().toISOString().slice(0,10).replace(/-/g,'')}.mechdb`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
this.showAlert('成功', '网格文件下载已开始', 'success');
} else {
const errorData = await response.json().catch(() => ({error: '下载失败'}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
} catch (error) {
console.error('Download mesh error:', error);
this.showAlert('错误', '网格文件下载失败:' + error.message, 'danger');
}
}
async downloadReport() {
try {
console.log('Starting report download...');
this.showAlert('信息', '正在生成质量报告...', 'info');
const response = await fetch('/api/mesh/result?include_quality_details=true&format=summary');
console.log('Report response status:', response.status);
if (response.ok) {
const result = await response.json();
console.log('Report data received:', result);
this.generatePDFReport(result);
} else {
const errorData = await response.json().catch(() => ({error: '获取报告数据失败'}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
} catch (error) {
console.error('Download report error:', error);
this.showAlert('错误', '质量报告下载失败:' + error.message, 'danger');
}
}
async downloadImage() {
try {
console.log('Starting image download...');
this.showAlert('信息', '正在准备图像下载...', 'info');
const response = await fetch('/api/mesh/download/image');
console.log('Image download response status:', response.status);
if (response.ok) {
const blob = await response.blob();
console.log('Downloaded image blob size:', blob.size);
console.log('Downloaded image blob type:', blob.type);
if (blob.size === 0) {
throw new Error('下载的图像文件为空');
}
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `mesh_visualization_${new Date().toISOString().slice(0,10).replace(/-/g,'')}.png`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
this.showAlert('成功', '可视化图像下载已开始', 'success');
} else {
const errorData = await response.json().catch(() => ({error: '图像下载失败'}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
} catch (error) {
console.error('Download image error:', error);
this.showAlert('错误', '图像下载失败:' + error.message, 'danger');
}
}
generatePDFReport(data) {
// Get more complete data from the API response
const result = data.result || data.summary || {};
const basicInfo = result.basic_info || result.mesh_statistics || {};
const fileInfo = result.file_info || {};
const processingInfo = result.processing_info || result.processing_summary || {};
const reportContent = `
<html>
<head>
<title>网格质量报告</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 30px; }
.section { margin: 20px 0; }
.stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin: 20px 0; }
.stat-item { border: 1px solid #ddd; padding: 15px; text-align: center; border-radius: 5px; }
.stat-value { font-size: 24px; font-weight: bold; color: #007bff; }
.stat-label { color: #666; margin-top: 5px; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f5f5f5; }
.quality-good { color: #28a745; font-weight: bold; }
.quality-warning { color: #ffc107; font-weight: bold; }
.quality-danger { color: #dc3545; font-weight: bold; }
</style>
</head>
<body>
<div class="header">
<h1>CAE网格生成质量报告</h1>
<p>生成时间: ${new Date().toLocaleString()}</p>
<p>系统: AnsysLink - 叶片网格生成助手</p>
</div>
<div class="section">
<h2>📁 文件信息</h2>
<table>
<tr><th>项目</th><th>值</th></tr>
<tr><td>文件名</td><td>${fileInfo.filename || 'blade.step'}</td></tr>
<tr><td>文件大小</td><td>${fileInfo.file_size_mb ? fileInfo.file_size_mb + ' MB' : this.formatFileSize(fileInfo.file_size || 0)}</td></tr>
<tr><td>上传时间</td><td>${fileInfo.upload_time ? new Date(fileInfo.upload_time).toLocaleString() : '未知'}</td></tr>
</table>
</div>
<div class="section">
<h2>📊 网格统计</h2>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value">${(basicInfo.element_count || basicInfo.elements || 0).toLocaleString()}</div>
<div class="stat-label">单元数量</div>
</div>
<div class="stat-item">
<div class="stat-value">${(basicInfo.node_count || basicInfo.nodes || 0).toLocaleString()}</div>
<div class="stat-label">节点数量</div>
</div>
<div class="stat-item">
<div class="stat-value">${(basicInfo.quality_score || 0).toFixed(2)}</div>
<div class="stat-label">质量评分 (0-100)</div>
</div>
<div class="stat-item">
<div class="stat-value">${(basicInfo.generation_time || processingInfo.total_time || 0).toFixed(1)}s</div>
<div class="stat-label">生成时间</div>
</div>
</div>
</div>
<div class="section">
<h2>✅ 质量评估</h2>
<table>
<tr><th>评估项目</th><th>结果</th><th>状态</th></tr>
<tr>
<td>总体质量状态</td>
<td>${basicInfo.quality_status || '通过'}</td>
<td class="${this.getQualityClass(basicInfo.quality_score)}">
${this.getQualityLabel(basicInfo.quality_score)}
</td>
</tr>
<tr>
<td>质量分数</td>
<td>${(basicInfo.quality_score || 0).toFixed(2)} / 100</td>
<td class="${this.getQualityClass(basicInfo.quality_score)}">
${basicInfo.quality_score >= 80 ? '优秀' : basicInfo.quality_score >= 60 ? '良好' : basicInfo.quality_score >= 40 ? '及格' : '需改进'}
</td>
</tr>
<tr>
<td>最小单元质量</td>
<td>${basicInfo.min_element_quality || '计算中'}</td>
<td>-</td>
</tr>
</table>
</div>
<div class="section">
<h2>⚙️ 处理详情</h2>
<table>
<tr><th>项目</th><th>信息</th></tr>
<tr><td>处理状态</td><td>${processingInfo.status || '已完成'}</td></tr>
<tr><td>开始时间</td><td>${processingInfo.started_at ? new Date(processingInfo.started_at).toLocaleString() : '未知'}</td></tr>
<tr><td>完成时间</td><td>${processingInfo.completed_at ? new Date(processingInfo.completed_at).toLocaleString() : new Date().toLocaleString()}</td></tr>
<tr><td>总处理时间</td><td>${(processingInfo.total_time || 0).toFixed(1)} 秒</td></tr>
</table>
</div>
<div class="section">
<h2>💡 质量建议</h2>
<ul>
${this.generateQualityRecommendations(basicInfo.quality_score).map(rec => `<li>${rec}</li>`).join('')}
</ul>
</div>
<div class="section">
<h2>📋 系统信息</h2>
<table>
<tr><th>项目</th><th>信息</th></tr>
<tr><td>软件版本</td><td>AnsysLink v1.0</td></tr>
<tr><td>ANSYS版本</td><td>2024 R1 (仿真模式)</td></tr>
<tr><td>处理模式</td><td>自动网格生成</td></tr>
<tr><td>报告生成</td><td>${new Date().toLocaleString()}</td></tr>
</table>
</div>
<div class="section" style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd;">
<p><small><strong>注意:</strong> 本报告由AnsysLink自动生成。所有数据基于网格生成过程中的实时计算结果。</small></p>
<p><small>如需更详细的分析报告,请联系技术支持团队。</small></p>
</div>
</body>
</html>
`;
const blob = new Blob([reportContent], { type: 'text/html' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `mesh_quality_report_${new Date().toISOString().slice(0,10)}.html`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
this.showAlert('成功', '质量报告已下载为HTML文件可在浏览器中打开查看或打印为PDF', 'success');
}
// Helper function for quality recommendations
generateQualityRecommendations(qualityScore) {
const recommendations = [];
const score = qualityScore || 0;
if (score >= 80) {
recommendations.push('网格质量优秀,可以进行高精度分析');
recommendations.push('网格密度适中,计算效率良好');
recommendations.push('所有质量指标均达到推荐标准');
} else if (score >= 60) {
recommendations.push('网格质量良好,适合大多数分析需求');
recommendations.push('考虑在高应力区域增加局部细化');
recommendations.push('整体网格分布合理,可满足工程精度要求');
} else if (score >= 40) {
recommendations.push('网格质量达到基本要求,建议进一步优化');
recommendations.push('考虑减小全局单元尺寸以提高质量');
recommendations.push('检查高曲率区域的网格细化程度');
} else {
recommendations.push('网格质量需要改进,建议重新生成');
recommendations.push('考虑调整网格参数和细化策略');
recommendations.push('建议联系技术支持获得优化建议');
}
// Add general recommendations
recommendations.push('定期检查网格质量以确保分析准确性');
recommendations.push('保存当前网格设置以便后续重用');
return recommendations;
}
// Helper function for quality class
getQualityClass(score) {
if (!score) return '';
if (score >= 80) return 'quality-good';
if (score >= 60) return 'quality-warning';
return 'quality-danger';
}
// Helper function for quality label
getQualityLabel(score) {
if (!score) return '未知';
if (score >= 80) return '优秀';
if (score >= 60) return '良好';
if (score >= 40) return '及格';
return '需改进';
}
// Visualization Controls
changeView(viewType) {
this.addLogEntry('info', `切换到${viewType}视图`);
}
refreshVisualization() {
this.addLogEntry('info', '刷新可视化');
this.loadVisualization();
}
// Step Status Management
updateStepStatus(stepId, status) {
const stepElement = document.getElementById(`step-${stepId}`);
if (stepElement) {
stepElement.classList.remove('active', 'completed', 'error');
stepElement.classList.add(status);
}
}
// System Status Check
async checkSystemStatus() {
try {
const ansysStatus = document.getElementById('ansys-status');
const queueStatus = document.getElementById('queue-status');
if (ansysStatus) {
ansysStatus.innerHTML = '<i class="fas fa-circle text-success"></i> 正常';
}
if (queueStatus) {
queueStatus.textContent = '0 个任务';
}
} catch (error) {
console.error('System status check error:', error);
const ansysStatus = document.getElementById('ansys-status');
if (ansysStatus) {
ansysStatus.innerHTML = '<i class="fas fa-circle text-danger"></i> 连接失败';
}
}
}
// Alert System
showAlert(title, message, type = 'info') {
let alertContainer = document.getElementById('alert-container');
if (!alertContainer) {
alertContainer = document.createElement('div');
alertContainer.id = 'alert-container';
alertContainer.style.position = 'fixed';
alertContainer.style.top = '20px';
alertContainer.style.right = '20px';
alertContainer.style.zIndex = '9999';
alertContainer.style.maxWidth = '400px';
document.body.appendChild(alertContainer);
}
const alert = document.createElement('div');
alert.className = `alert alert-${type} alert-dismissible fade show mb-2`;
alert.innerHTML = `
<strong>${title}:</strong> ${message}
<button type="button" class="btn-close" onclick="this.parentElement.remove()"></button>
`;
alertContainer.appendChild(alert);
// Auto-dismiss after 5 seconds
setTimeout(() => {
if (alert.parentNode) {
alert.remove();
}
}, 5000);
}
}
// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new MeshGeneratorApp();
});