修复html报告两种不同生成方法的问题,修复导出报告按钮无碰撞不显示的问题。

This commit is contained in:
tian 2026-02-07 18:30:29 +08:00
parent a655e8092c
commit 71fe79a447
3 changed files with 75 additions and 240 deletions

View File

@ -150,7 +150,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public bool IsGenerating
{
get => _isGenerating;
set => SetProperty(ref _isGenerating, value);
set
{
if (SetProperty(ref _isGenerating, value))
{
// 刷新命令状态
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
}
}
}
/// <summary>
@ -159,7 +166,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public bool HasCollisions
{
get => _hasCollisions;
set => SetProperty(ref _hasCollisions, value);
set
{
if (SetProperty(ref _hasCollisions, value))
{
// 刷新命令状态
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
}
}
}
/// <summary>
@ -415,7 +429,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
Statistics = new CollisionReportStatistics();
// 初始化命令
ExportReportCommand = new RelayCommand(async () => await ExportReportAsync(), () => !IsGenerating && HasCollisions);
ExportReportCommand = new RelayCommand(async () => await ExportReportAsync(), () => !IsGenerating);
CloseCommand = new RelayCommand(CloseWindow);
HighlightCollisionCommand = new RelayCommand<CollisionReportItem>(HighlightCollision, item => item?.CollisionData != null);
AddScreenshotCommand = new RelayCommand(ExecuteAddScreenshot, CanExecuteAddScreenshot);
@ -908,229 +922,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
/// <summary>
/// 生成HTML报告
/// 生成HTML报告 - 统一使用 CollisionReportHtmlGenerator
/// </summary>
private string GenerateHtmlReport(string htmlFilePath = null)
{
var html = new StringBuilder();
var now = DateTime.Now;
// HTML头部和样式
html.AppendLine("<!DOCTYPE html>");
html.AppendLine("<html><head>");
html.AppendLine("<meta charset='UTF-8'>");
html.AppendLine("<title>NavisworksTransport 碰撞检测报告</title>");
html.AppendLine("<style>");
html.AppendLine("body { font-family: 'Microsoft YaHei', Arial, sans-serif; margin: 20px; line-height: 1.6; }");
html.AppendLine("h1 { color: #2c5aa0; text-align: center; border-bottom: 2px solid #2c5aa0; padding-bottom: 10px; }");
html.AppendLine("h2 { color: #2c5aa0; margin-top: 25px; }");
html.AppendLine(".summary { background: #f8fbff; padding: 15px; border-radius: 8px; margin: 15px 0; border-left: 4px solid #2c5aa0; }");
html.AppendLine(".stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin: 15px 0; }");
html.AppendLine(".stat-item { background: white; padding: 15px; border-radius: 6px; border: 1px solid #ddd; text-align: center; }");
html.AppendLine(".stat-label { font-size: 12px; color: #666; margin-bottom: 5px; }");
html.AppendLine(".stat-value { font-size: 18px; font-weight: bold; color: #2c5aa0; }");
html.AppendLine(".moving-object { background: #f0f8ff; padding: 15px; border-radius: 8px; text-align: center; margin: 15px 0; border: 1px solid #b0d4f1; }");
html.AppendLine(".collided-list { background: #f8fbff; padding: 15px; border-radius: 8px; margin: 15px 0; max-height: 200px; overflow-y: auto; }");
html.AppendLine(".list-item { margin: 5px 0; padding: 5px; border-left: 3px solid #2c5aa0; padding-left: 10px; }");
html.AppendLine(".recommendation { background: #fff3cd; padding: 15px; border-radius: 8px; border-left: 4px solid #ffc107; margin: 15px 0; }");
html.AppendLine(".collision-item { background: white; margin: 10px 0; padding: 15px; border-radius: 6px; border: 1px solid #eee; }");
html.AppendLine(".collision-title { font-weight: bold; color: #333; margin-bottom: 8px; }");
html.AppendLine(".collision-status { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 11px; color: white; margin-bottom: 8px; }");
html.AppendLine(".status-new { background-color: #ff6b6b; } .status-active { background-color: #ffa500; }");
html.AppendLine(".status-reviewed { background-color: #87ceeb; } .status-approved { background-color: #98fb98; } .status-resolved { background-color: #90ee90; }");
html.AppendLine(".screenshot-section { margin: 20px 0; padding: 15px; background-color: #f5f5f5; border-radius: 5px; }");
html.AppendLine(".screenshot-container { text-align: center; }");
html.AppendLine(".screenshot-image { max-width: 100%; height: auto; border: 1px solid #ddd; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }");
html.AppendLine(".screenshot-info { margin-top: 10px; font-size: 12px; color: #666; }");
html.AppendLine("</style>");
html.AppendLine("</head><body>");
// 报告标题
html.AppendLine($"<h1>NavisworksTransport 碰撞检测报告</h1>");
html.AppendLine($"<p style='text-align: center; color: #666;'>生成时间: {now:yyyy年MM月dd日 HH:mm:ss}</p>");
html.AppendLine($"<p style='text-align: center; color: #666;'>报告类型: {Statistics.ReportType}</p>");
html.AppendLine($"<p style='text-align: center; color: #666;'>路径名称: {PathName}</p>");
// 总体统计
html.AppendLine("<div class='summary'>");
html.AppendLine("<h2>总体统计</h2>");
html.AppendLine("<div class='stats-grid'>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>总碰撞数</div>");
html.AppendLine($"<div class='stat-value'>{Statistics.TotalCollisions}</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>碰撞构件</div>");
html.AppendLine($"<div class='stat-value'>{UniqueCollidedObjectsCount}个</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>检测点</div>");
html.AppendLine($"<div class='stat-value'>{Statistics.AnimationCollisions}个</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
// 动画参数
html.AppendLine("<h2>动画参数</h2>");
html.AppendLine("<div class='stats-grid'>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>帧率</div>");
html.AppendLine($"<div class='stat-value'>{FrameRate} FPS</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>时长</div>");
html.AppendLine($"<div class='stat-value'>{Duration:F1} 秒</div>");
html.AppendLine("</div>");
html.AppendLine("<div class='stat-item'>");
html.AppendLine("<div class='stat-label'>检测容差</div>");
html.AppendLine($"<div class='stat-value'>{DetectionTolerance:F2} 米</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
// 碰撞场景截图(支持多张)
var validScreenshots = Screenshots?.Where(s => !string.IsNullOrEmpty(s.FilePath) && File.Exists(s.FilePath)).ToList();
if (validScreenshots?.Count > 0)
// 确保 CurrentReport 的截图列表与 ViewModel 同步
if (CurrentReport != null && Screenshots != null)
{
html.AppendLine("<h2>碰撞场景截图</h2>");
if (validScreenshots.Count > 1)
{
// 多张截图 - 画廊样式
html.AppendLine("<style>");
html.AppendLine(".screenshot-gallery { display: flex; flex-wrap: wrap; gap: 15px; margin: 15px 0; }");
html.AppendLine(".screenshot-item { flex: 0 0 calc(50% - 10px); background: white; padding: 10px; border-radius: 6px; border: 1px solid #ddd; box-sizing: border-box; }");
html.AppendLine(".screenshot-item:first-child { flex: 0 0 100%; }"); // 第一张图占满宽度
html.AppendLine(".screenshot-thumb { width: 100%; height: auto; border-radius: 4px; }");
html.AppendLine(".screenshot-desc { margin-top: 8px; font-size: 12px; color: #666; text-align: center; }");
html.AppendLine(".screenshot-meta { font-size: 11px; color: #999; text-align: center; margin-top: 4px; }");
html.AppendLine("</style>");
html.AppendLine("<div class='screenshot-gallery'>");
int index = 1;
foreach (var screenshot in validScreenshots.OrderBy(s => s.SortOrder))
{
string relativePath = PathHelper.GetRelativePath(htmlFilePath, screenshot.FilePath);
html.AppendLine("<div class='screenshot-item'>");
html.AppendLine($"<img src=\"{relativePath}\" alt=\"截图 {index}\" class=\"screenshot-thumb\"/>");
if (!string.IsNullOrEmpty(screenshot.Description))
{
html.AppendLine($"<div class='screenshot-desc'>{screenshot.Description}</div>");
}
html.AppendLine($"<div class='screenshot-meta'>截图 {index} • {screenshot.Width}x{screenshot.Height}</div>");
html.AppendLine("</div>");
index++;
}
html.AppendLine("</div>");
}
else
{
// 单张截图
var screenshot = validScreenshots.First();
html.AppendLine("<div class='screenshot-section'>");
string relativePath = PathHelper.GetRelativePath(htmlFilePath, screenshot.FilePath);
html.AppendLine("<div class='screenshot-container'>");
html.AppendLine($"<img src=\"{relativePath}\" alt=\"碰撞场景截图\" class=\"screenshot-image\"/>");
html.AppendLine("<div class='screenshot-info'>");
html.AppendLine($"<p>分辨率: {screenshot.Width} x {screenshot.Height}</p>");
html.AppendLine($"<p>格式: {screenshot.Format}</p>");
html.AppendLine("</div>");
html.AppendLine("</div>");
html.AppendLine("</div>");
}
}
// 运动构件信息
if (!string.IsNullOrEmpty(MovingObjectInfo))
{
html.AppendLine("<h2>运动构件信息</h2>");
html.AppendLine("<div class='moving-object'>");
html.AppendLine($"<strong>🚛 {MovingObjectInfo}</strong>");
html.AppendLine("</div>");
}
// 碰撞构件清单
if (CollidedObjectsList?.Count > 0)
{
html.AppendLine("<h2>碰撞构件清单</h2>");
html.AppendLine("<div class='collided-list'>");
int index = 1;
foreach (var objName in CollidedObjectsList.Take(50)) // 限制显示前50个
{
html.AppendLine($"<div class='list-item'>{index}. {objName}</div>");
index++;
}
if (CollidedObjectsList.Count > 50)
{
html.AppendLine($"<div class='list-item'><em>... 还有 {CollidedObjectsList.Count - 50} 个构件</em></div>");
}
html.AppendLine("</div>");
}
// 总结与建议
html.AppendLine("<h2>总结与建议</h2>");
html.AppendLine("<div class='recommendation'>");
html.AppendLine($"<p><strong>{SummaryMessage}</strong></p>");
if (!string.IsNullOrEmpty(RecommendationMessage))
{
html.AppendLine("<p><strong>建议措施:</strong></p>");
var recommendations = RecommendationMessage.Split('\n');
html.AppendLine("<ul>");
foreach (var recommendation in recommendations)
{
if (!string.IsNullOrWhiteSpace(recommendation))
{
html.AppendLine($"<li>{recommendation}</li>");
}
}
html.AppendLine("</ul>");
}
html.AppendLine("</div>");
// 详细碰撞信息前10个
if (HasCollisions && AnimationCollisions?.Count > 0)
{
html.AppendLine("<h2>碰撞详情</h2>");
foreach (var collision in AnimationCollisions.Take(10))
{
html.AppendLine("<div class='collision-item'>");
html.AppendLine($"<div class='collision-title'>{collision.Title}</div>");
// 状态标签
var statusClass = GetStatusCssClass(collision.StatusText);
html.AppendLine($"<span class='collision-status {statusClass}'>{collision.StatusText}</span>");
html.AppendLine($"<p>{collision.Description}</p>");
html.AppendLine($"<p style='font-size: 11px; color: #666;'>{collision.Details}</p>");
html.AppendLine("</div>");
}
if (AnimationCollisions.Count > 10)
{
html.AppendLine($"<p><em>... 还有 {AnimationCollisions.Count - 10} 个碰撞未显示</em></p>");
}
}
// HTML结尾
html.AppendLine($"<hr style='margin: 30px 0;'>");
html.AppendLine($"<p style='text-align: center; color: #999; font-size: 12px;'>报告生成完成 - {now:HH:mm:ss}</p>");
html.AppendLine("</body></html>");
return html.ToString();
}
/// <summary>
/// 获取状态对应的CSS类名
/// </summary>
private string GetStatusCssClass(string statusText)
{
switch (statusText)
{
case "新发现": return "status-new";
case "活跃": return "status-active";
case "已审阅": return "status-reviewed";
case "已批准": return "status-approved";
case "已解决": return "status-resolved";
default: return "";
CurrentReport.Screenshots = Screenshots.ToList();
}
return CollisionReportHtmlGenerator.GenerateHtmlReport(CurrentReport, htmlFilePath);
}
/// <summary>

View File

@ -539,8 +539,7 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav
<Button Content="导出报告"
Command="{Binding ExportReportCommand}"
Style="{StaticResource SecondaryButtonStyle}"
Margin="0,0,10,0"
Visibility="{Binding HasCollisions, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Margin="0,0,10,0"/>
<Button Content="关闭"
Click="CloseButton_Click"

View File

@ -105,35 +105,69 @@ namespace NavisworksTransport.Utils
{
html.AppendLine("<h2>碰撞场景截图</h2>");
// 多张截图样式
// 多张截图样式 - 大图+缩略图横排交互式布局
if (validScreenshots?.Count > 1)
{
html.AppendLine("<style>");
html.AppendLine(".screenshot-gallery { display: flex; flex-wrap: wrap; gap: 15px; margin: 15px 0; }");
html.AppendLine(".screenshot-item { flex: 0 0 calc(50% - 10px); background: white; padding: 10px; border-radius: 6px; border: 1px solid #ddd; box-sizing: border-box; }");
html.AppendLine(".screenshot-item:nth-child(odd) { flex: 0 0 100%; }"); // 第一张图占满宽度
html.AppendLine(".screenshot-thumb { width: 100%; height: auto; border-radius: 4px; cursor: pointer; transition: transform 0.2s; }");
html.AppendLine(".screenshot-thumb:hover { transform: scale(1.02); }");
html.AppendLine(".screenshot-desc { margin-top: 8px; font-size: 12px; color: #666; text-align: center; }");
html.AppendLine(".screenshot-meta { font-size: 11px; color: #999; text-align: center; margin-top: 4px; }");
// 主图区域样式
html.AppendLine(".main-image-container { background: white; padding: 15px; border-radius: 6px; border: 1px solid #ddd; margin-bottom: 15px; }");
html.AppendLine(".main-image { width: 100%; height: auto; border-radius: 4px; cursor: pointer; transition: transform 0.2s; }");
html.AppendLine(".main-image:hover { transform: scale(1.01); }");
html.AppendLine(".main-image-info { margin-top: 10px; font-size: 13px; color: #666; text-align: center; }");
// 缩略图横排样式
html.AppendLine(".thumbnail-row { display: flex; gap: 10px; overflow-x: auto; padding: 10px; background: #f9f9f9; border-radius: 6px; border: 1px solid #e0e0e0; }");
html.AppendLine(".thumbnail-item { flex: 0 0 auto; width: 120px; cursor: pointer; transition: all 0.2s; }");
html.AppendLine(".thumbnail-item img { width: 100%; height: 80px; object-fit: cover; border-radius: 4px; border: 2px solid transparent; transition: all 0.2s; }");
html.AppendLine(".thumbnail-item:hover img { border-color: #87ceeb; transform: scale(1.05); }");
html.AppendLine(".thumbnail-item.active img { border-color: #2B579A; box-shadow: 0 0 6px rgba(43,87,154,0.4); }");
html.AppendLine(".thumbnail-label { font-size: 11px; color: #666; text-align: center; margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }");
html.AppendLine(".thumbnail-item.active .thumbnail-label { color: #2B579A; font-weight: bold; }");
html.AppendLine("</style>");
html.AppendLine("<div class='screenshot-gallery'>");
// 主图区域
html.AppendLine("<div class='main-image-container'>");
html.AppendLine("<img id='mainImage' src=\"\" alt=\"主图\" class=\"main-image\" onclick=\"window.open(this.src)\">");
html.AppendLine("<div id='mainImageInfo' class='main-image-info'></div>");
html.AppendLine("</div>");
// 缩略图横排
html.AppendLine("<div class='thumbnail-row' id='thumbnailRow'>");
int index = 1;
foreach (var screenshot in validScreenshots.OrderBy(s => s.SortOrder))
{
string relativePath = PathHelper.GetRelativePath(htmlFilePath, screenshot.FilePath);
html.AppendLine("<div class='screenshot-item'>");
html.AppendLine($"<img src=\"{relativePath}\" alt=\"截图 {index}\" class=\"screenshot-thumb\" onclick=\"window.open(this.src)\"/>");
if (!string.IsNullOrEmpty(screenshot.Description))
{
html.AppendLine($"<div class='screenshot-desc'>{screenshot.Description}</div>");
}
html.AppendLine($"<div class='screenshot-meta'>截图 {index} • {screenshot.Width}x{screenshot.Height} • {screenshot.CaptureTime:yyyy-MM-dd HH:mm}</div>");
string label = string.IsNullOrEmpty(screenshot.Description) ? $"截图 {index}" : screenshot.Description;
string desc = screenshot.Description ?? $"截图 {index}";
html.AppendLine($"<div class='thumbnail-item' data-src=\"{relativePath}\" data-index=\"{index}\" data-width=\"{screenshot.Width}\" data-height=\"{screenshot.Height}\" data-time=\"{screenshot.CaptureTime:yyyy-MM-dd HH:mm}\" data-desc=\"{desc}\" onclick=\"selectImage(this)\">");
html.AppendLine($"<img src=\"{relativePath}\" alt=\"{label}\">");
html.AppendLine($"<div class='thumbnail-label'>{label}</div>");
html.AppendLine("</div>");
index++;
}
html.AppendLine("</div>");
// JavaScript交互
html.AppendLine("<script>");
html.AppendLine("function selectImage(element) {");
html.AppendLine(" // 更新选中状态");
html.AppendLine(" document.querySelectorAll('.thumbnail-item').forEach(function(el) { el.classList.remove('active'); });");
html.AppendLine(" element.classList.add('active');");
html.AppendLine(" // 更新主图");
html.AppendLine(" var mainImage = document.getElementById('mainImage');");
html.AppendLine(" mainImage.src = element.getAttribute('data-src');");
html.AppendLine(" // 更新主图信息");
html.AppendLine(" var desc = element.getAttribute('data-desc');");
html.AppendLine(" var width = element.getAttribute('data-width');");
html.AppendLine(" var height = element.getAttribute('data-height');");
html.AppendLine(" var time = element.getAttribute('data-time');");
html.AppendLine(" document.getElementById('mainImageInfo').innerHTML = desc + ' &bull; ' + width + 'x' + height + ' &bull; ' + time;");
html.AppendLine("}");
html.AppendLine("// 初始化显示第一张图");
html.AppendLine("document.addEventListener('DOMContentLoaded', function() {");
html.AppendLine(" var firstThumb = document.querySelector('.thumbnail-item');");
html.AppendLine(" if (firstThumb) selectImage(firstThumb);");
html.AppendLine("});");
html.AppendLine("</script>");
}
else
{
@ -329,4 +363,4 @@ namespace NavisworksTransport.Utils
}
}
}
}
}