Compare commits

...

11 Commits

26 changed files with 1155 additions and 216 deletions

View File

@@ -151,6 +151,7 @@ namespace ADP
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
//注册服务
containerRegistry.Register<IMonitorValueService, MonitorValueService>();
containerRegistry.Register<ITestReportService, TestReportService>();
}
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
protected override IModuleCatalog CreateModuleCatalog()

View File

@@ -567,7 +567,7 @@ namespace ADP.ViewModels
ScopedContext? targetContext = CurrentContext;
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
CurrentConfig.CurrentADPFile = "新建ADP文件";
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
targetContext.CurrentFilePath = null;
targetContext.Program.Parameters.Clear();
@@ -631,7 +631,7 @@ namespace ADP.ViewModels
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
return;
}
CurrentConfig.CurrentADPFile = filePath;
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
targetContext.Program.Parameters = program.Parameters;
targetContext.Program.StepCollection = program.StepCollection;
@@ -691,6 +691,7 @@ namespace ADP.ViewModels
if (saveFileDialog.ShowDialog() == true)
{
// 💡 2. 将新路径安全写入快照工位
CurrentConfig.CurrentADPFile = saveFileDialog.FileName;
targetContext.CurrentFilePath = saveFileDialog.FileName;
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
@@ -850,7 +851,7 @@ namespace ADP.ViewModels
break;
case "测试报告导出界面":
if (_globalInfo.CurrentScope != "default") return;
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
_regionManager.RequestNavigate("ShellViewManager", "ExportView");
break;
}
}

View File

@@ -1,68 +1,381 @@
using Logger;
using MahApps.Metro.Controls;
using Model.Entity;
using Model.Models;
using NLog.Targets;
using OxyPlot;
using Service.Interface;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;
using Prism.Commands;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using UIShare.GlobalVariable;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
namespace CurveModule.ViewModels
{
/// <summary>
/// 曲线回读:加载 RecordView 导出的 CSV 文件,按 (MonitorName, Scope) 分组绘制曲线。
/// </summary>
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
{
#region
public bool KeepAlive => true;
public PlotModel Plot { get; }
/// <summary>已加载的曲线通道列表</summary>
public ObservableCollection<CurveRecallChannelVM> Channels { get; } = new();
private CurveRecallChannelVM? _selectedChannel;
public CurveRecallChannelVM? SelectedChannel
{
get => _selectedChannel;
set => SetProperty(ref _selectedChannel, value);
}
private string _statusMessage = "请加载 CSV 文件以回读曲线";
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
#endregion
#region
public ICommand LoadCsvCommand { get; }
public ICommand ResetViewCommand { get; }
public ICommand ClearCommand { get; }
#endregion
#region
// 颜色调色盘(与 MonitorView 一致)
private static readonly OxyColor[] _palette =
{
OxyColors.SteelBlue, OxyColors.IndianRed, OxyColors.SeaGreen,
OxyColors.DarkOrange, OxyColors.MediumPurple, OxyColors.Goldenrod,
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
};
private int _colorIndex;
#endregion
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
Plot = BuildEmptyPlot();
LoadCsvCommand = new DelegateCommand(OnLoadCsv);
ResetViewCommand = new DelegateCommand(OnResetView);
ClearCommand = new DelegateCommand(OnClear);
}
#region
/// <summary>
/// 打开 CSV 文件对话框,解析数据并按 (MonitorName, Scope) 分组绘制曲线。
/// </summary>
private void OnLoadCsv()
{
try
{
var dlg = new Microsoft.Win32.OpenFileDialog
{
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
Title = "选择要加载的监测数据 CSV 文件"
};
if (dlg.ShowDialog() != true) return;
var records = ParseCsv(dlg.FileName);
if (records.Count == 0)
{
StatusMessage = "CSV 文件为空或格式不正确";
return;
}
// 按 (MonitorName, Scope) 分组
var groups = records.GroupBy(r => (r.MonitorName, r.Scope))
.OrderBy(g => g.Key.MonitorName)
.ThenBy(g => g.Key.Scope);
// 清空旧数据
ClearPlotAndChannels();
foreach (var group in groups)
{
var color = _palette[_colorIndex % _palette.Length];
_colorIndex++;
string displayName = string.IsNullOrEmpty(group.Key.Scope)
? group.Key.MonitorName
: $"{group.Key.MonitorName} [{group.Key.Scope}]";
var channel = new CurveRecallChannelVM
{
MonitorName = group.Key.MonitorName,
Scope = group.Key.Scope,
DisplayName = displayName,
Color = color,
IsDisplayed = true
};
// 创建 LineSeries 并填充数据点
channel.Series = new LineSeries
{
Title = displayName,
Color = color,
StrokeThickness = 1.5
};
// 按时间排序
var sortedPoints = group.OrderBy(r => r.CreateTime);
foreach (var point in sortedPoints)
{
channel.Series.Points.Add(new DataPoint(point.CreateTime.ToOADate(), point.MonitorValue));
}
channel.PropertyChanged += OnChannelPropertyChanged;
Plot.Series.Add(channel.Series);
Channels.Add(channel);
}
Plot.InvalidatePlot(true);
StatusMessage = $"已加载 {Path.GetFileName(dlg.FileName)},共 {Channels.Count} 条曲线,{records.Count} 个数据点";
LoggerHelper.Info($"曲线回读:加载 {dlg.FileName}{Channels.Count} 条曲线,{records.Count} 个数据点");
}
catch (Exception ex)
{
LoggerHelper.Error($"加载 CSV 失败:{ex.Message}");
StatusMessage = $"加载失败:{ex.Message}";
}
}
private void OnResetView()
{
Plot.ResetAllAxes();
Plot.InvalidatePlot(false);
StatusMessage = "视图已复原";
}
private void OnClear()
{
ClearPlotAndChannels();
Plot.InvalidatePlot(true);
StatusMessage = "已清空,请重新加载 CSV 文件";
}
#endregion
#region CSV
/// <summary>
/// 解析 RecordView 导出的 CSV 文件,返回监测记录列表。
/// </summary>
private static List<MonitorRecord> ParseCsv(string path)
{
var results = new List<MonitorRecord>();
using var sr = new StreamReader(path, Encoding.UTF8);
// 读取表头
string? headerLine = sr.ReadLine();
if (headerLine == null) return results;
var headers = ParseCsvLine(headerLine);
int monitorNameIdx = headers.FindIndex(h => h.Trim() == "MonitorName");
int monitorValueIdx = headers.FindIndex(h => h.Trim() == "MonitorValue");
int scopeIdx = headers.FindIndex(h => h.Trim() == "Scope");
int createTimeIdx = headers.FindIndex(h => h.Trim() == "CreateTime");
if (monitorNameIdx < 0 || monitorValueIdx < 0 || scopeIdx < 0 || createTimeIdx < 0)
return results;
int maxIdx = Math.Max(monitorNameIdx, Math.Max(monitorValueIdx, Math.Max(scopeIdx, createTimeIdx)));
string? line;
while ((line = sr.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line)) continue;
var fields = ParseCsvLine(line);
if (fields.Count <= maxIdx) continue;
if (!double.TryParse(fields[monitorValueIdx].Trim(), out double value)) continue;
if (!DateTime.TryParse(fields[createTimeIdx].Trim(), out DateTime createTime)) continue;
results.Add(new MonitorRecord
{
MonitorName = fields[monitorNameIdx].Trim(),
MonitorValue = value,
Scope = fields[scopeIdx].Trim(),
CreateTime = createTime
});
}
return results;
}
/// <summary>
/// 解析单行 CSV正确处理引号转义。
/// </summary>
private static List<string> ParseCsvLine(string line)
{
var fields = new List<string>();
bool inQuotes = false;
var sb = new StringBuilder();
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (inQuotes)
{
if (c == '"')
{
if (i + 1 < line.Length && line[i + 1] == '"')
{
sb.Append('"');
i++;
}
else
{
inQuotes = false;
}
}
else
{
sb.Append(c);
}
}
else
{
if (c == '"')
{
inQuotes = true;
}
else if (c == ',')
{
fields.Add(sb.ToString());
sb.Clear();
}
else
{
sb.Append(c);
}
}
}
fields.Add(sb.ToString());
return fields;
}
#endregion
#region OxyPlot
/// <summary>
/// 构建空的 OxyPlot 模型(与 MonitorView 完全一致的配置)。
/// </summary>
private static PlotModel BuildEmptyPlot()
{
var pm = new PlotModel
{
Title = "曲线回读",
PlotAreaBorderColor = OxyColors.LightGray,
Background = OxyColors.White
};
pm.Axes.Add(new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = "时间",
StringFormat = "HH:mm:ss",
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None
});
pm.Axes.Add(new LinearAxis
{
Position = AxisPosition.Left,
Title = "值",
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None
});
pm.Legends.Add(new Legend
{
LegendPosition = LegendPosition.RightTop,
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
LegendBorder = OxyColors.LightGray
});
return pm;
}
#endregion
#region
private void OnChannelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(CurveRecallChannelVM.IsDisplayed)) return;
if (sender is not CurveRecallChannelVM channel) return;
if (channel.IsDisplayed)
{
if (channel.Series != null && !Plot.Series.Contains(channel.Series))
{
Plot.Series.Add(channel.Series);
}
}
else
{
if (channel.Series != null)
{
Plot.Series.Remove(channel.Series);
}
}
Plot.InvalidatePlot(true);
}
private void ClearPlotAndChannels()
{
foreach (var ch in Channels)
{
ch.PropertyChanged -= OnChannelPropertyChanged;
}
Plot.Series.Clear();
Channels.Clear();
_colorIndex = 0;
}
#endregion
#region
#region
public override void OnNavigatedTo(NavigationContext navigationContext)
{
}
public void Dispose()
{
ClearPlotAndChannels();
}
#endregion
}
/// <summary>
/// CSV 监测记录(单行数据)。
/// </summary>
internal class MonitorRecord
{
public string MonitorName { get; set; } = string.Empty;
public double MonitorValue { get; set; }
public string Scope { get; set; } = string.Empty;
public DateTime CreateTime { get; set; }
}
/// <summary>
/// 曲线回读通道MonitorName + Scope 唯一标识一条曲线。
/// </summary>
public class CurveRecallChannelVM : Prism.Mvvm.BindableBase
{
public string MonitorName { get; set; } = string.Empty;
public string Scope { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public OxyColor Color { get; set; } = OxyColors.SteelBlue;
private bool _isDisplayed = true;
public bool IsDisplayed
{
get => _isDisplayed;
set => SetProperty(ref _isDisplayed, value);
}
#endregion
public LineSeries? Series { get; set; }
}
}

View File

@@ -1,14 +1,158 @@
<UserControl x:Class="CurveModule.Views.CurveRecallView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="1080" d:DesignWidth="1920">
<Grid>
</Grid>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="700"
d:DesignWidth="1200">
<UserControl.Resources>
<converters:LessThanConverter x:Key="LessThanConverter"/>
</UserControl.Resources>
<Border Background="#F5F7FA">
<Grid x:Name="RootGrid" Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Row0标题 -->
<TextBlock Grid.Row="0"
Text="曲线回读"
FontSize="20" FontWeight="Bold"
Margin="4,0,0,8"/>
<!-- Row1工具栏 -->
<Border Grid.Row="1"
Background="White"
BorderBrush="#DDD" BorderThickness="1"
CornerRadius="4" Padding="8" Margin="0,0,0,6">
<StackPanel Orientation="Horizontal">
<Button Content="📂 加载 CSV"
Command="{Binding LoadCsvCommand}"
Padding="12,4"/>
<Button Content="↺ 复原视图"
Command="{Binding ResetViewCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="按数据范围重置坐标轴缩放/平移"/>
<Button Content="✕ 清空"
Command="{Binding ClearCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="清空已加载的曲线数据"/>
</StackPanel>
</Border>
<!-- Row2主体 -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="ColumnDefinition">
<Setter Property="Width" Value="260"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
<Setter Property="Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="ColumnDefinition">
<Setter Property="Width" Value="6"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
<Setter Property="Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧:通道列表 -->
<Border Grid.Column="0"
Background="White"
BorderBrush="#DDD" BorderThickness="1"
CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="#ECEFF4" Padding="8,4">
<TextBlock Text="曲线通道(勾选显示/隐藏)" FontWeight="Bold"/>
</Border>
<ListBox Grid.Row="1"
ItemsSource="{Binding Channels}"
SelectedItem="{Binding SelectedChannel}"
BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="2">
<DockPanel>
<CheckBox IsChecked="{Binding IsDisplayed}"
VerticalAlignment="Center"
ToolTip="勾选=在图表上显示,取消=隐藏"/>
<Border Width="10" Height="10"
CornerRadius="2"
VerticalAlignment="Center"
Margin="4,0,6,0">
<Border.Background>
<SolidColorBrush Color="SteelBlue"/>
</Border.Background>
</Border>
<TextBlock Text="{Binding DisplayName}"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</DockPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="2"
Text="{Binding StatusMessage}"
FontSize="11" Foreground="#666"
Margin="4,2" TextWrapping="Wrap"/>
</Grid>
</Border>
<GridSplitter Grid.Column="1"
HorizontalAlignment="Stretch"
Background="Transparent"/>
<!-- 右侧OxyPlot 图表 -->
<Border Grid.Column="2"
Background="White"
BorderBrush="#DDD" BorderThickness="1"
CornerRadius="4">
<oxy:PlotView Model="{Binding Plot}"
Background="Transparent"/>
</Border>
</Grid>
<!-- Row3状态栏 -->
<Border Grid.Row="3"
Background="#ECEFF4"
Padding="8,4" Margin="0,6,0,0"
CornerRadius="2">
<TextBlock Text="{Binding StatusMessage}"
Foreground="#444"
FontSize="12"/>
</Border>
</Grid>
</Border>
</UserControl>

View File

@@ -7,6 +7,10 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.104.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ORM\ORM.csproj" />
<ProjectReference Include="..\Service\Service.csproj" />

View File

@@ -1,23 +1,16 @@
using Logger;
using MahApps.Metro.Controls;
using ClosedXML.Excel;
using Logger;
using Microsoft.Win32;
using Model;
using Model.Entity;
using Model.Models;
using NLog.Targets;
using OxyPlot;
using Service.Interface;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using UIShare.GlobalVariable;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
namespace ExportModule.ViewModels
@@ -26,43 +19,244 @@ namespace ExportModule.ViewModels
{
#region
public bool KeepAlive => true;
private ObservableCollection<TestReportModel> _testReportModelList = new();
public ObservableCollection<TestReportModel> TestReportModelList
{
get { return _testReportModelList; }
set { SetProperty(ref _testReportModelList, value); }
}
private DateTime? _startTime;
public DateTime? StartTime
{
get => _startTime;
set => SetProperty(ref _startTime, value);
}
private DateTime? _endTime;
public DateTime? EndTime
{
get => _endTime;
set => SetProperty(ref _endTime, value);
}
private string _selectedScope = "全部";
public string SelectedScope
{
get => _selectedScope;
set => SetProperty(ref _selectedScope, value);
}
public ObservableCollection<string> ScopeOptions { get; } = new()
{
"全部",
"TestCell1", "TestCell2", "TestCell3", "TestCell4",
"TestCell5", "TestCell6", "TestCell7", "TestCell8"
};
private string _statusMessage = "就绪";
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
private int _totalCount;
public int TotalCount
{
get => _totalCount;
set => SetProperty(ref _totalCount, value);
}
private TestReportModel? _selectedTestReport;
public TestReportModel? SelectedTestReport
{
get => _selectedTestReport;
set => SetProperty(ref _selectedTestReport, value);
}
#endregion
#region
public ICommand LoadedCommand { get; }
public ICommand QueryCommand { get; }
public ICommand ExportCommand { get; }
#endregion
#region
private readonly ITestReportService _testReportService;
#endregion
public ExportViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_testReportService = containerProvider.Resolve<ITestReportService>();
LoadedCommand = new AsyncDelegateCommand(OnLoad);
QueryCommand = new AsyncDelegateCommand(OnQuery);
ExportCommand = new AsyncDelegateCommand(OnExport);
}
#region
/// <summary>
/// 加载全部(无筛选条件)
/// </summary>
private async Task OnLoad()
{
var result = await _testReportService.GetFilterList();
if (result.IsSuccess)
{
TestReportModelList = new ObservableCollection<TestReportModel>(result.Data ?? new List<TestReportModel>());
TotalCount = TestReportModelList.Count;
ShowInfoMessageBox($"加载完成,共 {TotalCount} 条记录", () => { });
}
else
{
TestReportModelList = new ObservableCollection<TestReportModel>();
TotalCount = 0;
ShowErrorMessageBox($"加载失败:{result.Msg}", () => { });
LoggerHelper.Error(result.Msg);
}
}
/// <summary>
/// 查询(根据开始日期、结束日期、台架名称筛选)
/// </summary>
private async Task OnQuery()
{
// 处理结束日期:若用户只选了日期没选时间,默认到当天末尾
DateTime? queryEnd = EndTime;
if (queryEnd.HasValue)
{
queryEnd = queryEnd.Value.Date.AddDays(1).AddSeconds(-1);
}
string? scope = SelectedScope == "全部" ? null : SelectedScope;
var result = await _testReportService.GetFilterList(StartTime, queryEnd, scope);
if (result.IsSuccess)
{
TestReportModelList = new ObservableCollection<TestReportModel>(result.Data ?? new List<TestReportModel>());
TotalCount = TestReportModelList.Count;
ShowInfoMessageBox($"查询完成,共 {TotalCount} 条记录", () => { });
}
else
{
TestReportModelList = new ObservableCollection<TestReportModel>();
TotalCount = 0;
ShowErrorMessageBox($"查询失败:{result.Msg}", () => { });
LoggerHelper.Error(result.Msg);
}
}
/// <summary>
/// 导出 Excel根据用户在表格中选中的 TestReportModel.TestRoundId 查询并导出完整步骤数据
/// </summary>
private async Task OnExport()
{
if (SelectedTestReport == null)
{
ShowErrorMessageBox("请先在列表中选择一条测试记录。", () => { });
return;
}
StatusMessage = "正在查询导出数据...";
var entityResult = await _testReportService.GetByTestRoundIdAsync(SelectedTestReport.TestRoundId);
if (!entityResult.IsSuccess || entityResult.Data == null || entityResult.Data.Count == 0)
{
ShowErrorMessageBox("未找到该测试记录的详细步骤数据。", () => { });
return;
}
var dialog = new SaveFileDialog
{
Filter = "Excel 工作簿 (*.xlsx)|*.xlsx|所有文件 (*.*)|*.*",
DefaultExt = ".xlsx",
FileName = $"测试报告_{SelectedTestReport.Scope}_{SelectedTestReport.StartTime:yyyyMMdd_HHmmss}.xlsx"
};
if (dialog.ShowDialog() != true) return;
var entities = entityResult.Data;
StatusMessage = $"正在导出 {entities.Count} 条步骤记录...";
await Task.Run(() => ExportToExcel(dialog.FileName, entities));
ShowInfoMessageBox($"导出完成,共 {entities.Count} 条步骤记录,已保存至:{dialog.FileName}", () => { });
}
#endregion
#region
private static void ExportToExcel(string filePath, List<TestReportEntity> entities)
{
using var workbook = new XLWorkbook();
var ws = workbook.Worksheets.Add("测试报告");
// 表头
string[] headers = {
"Id", "CreateTime", "TestRoundId", "Scope", "FileName",
"StepIndex", "StepName", "StepType", "MethodName", "MethodFullName",
"Result", "RunTimeMs", "OutputValue", "Depth", "IsErrorStep", "LoopRemaining"
};
for (int c = 0; c < headers.Length; c++)
{
ws.Cell(1, c + 1).Value = headers[c];
}
// 标题行样式
var headerRange = ws.Range(1, 1, 1, headers.Length);
headerRange.Style.Font.Bold = true;
headerRange.Style.Fill.BackgroundColor = XLColor.FromArgb(0xECEFF4);
headerRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
headerRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
// 填充数据
for (int i = 0; i < entities.Count; i++)
{
var e = entities[i];
int row = i + 2;
ws.Cell(row, 1).Value = e.Id;
ws.Cell(row, 2).Value = e.CreateTime.ToString("yyyy-MM-dd HH:mm:ss");
ws.Cell(row, 3).Value = e.TestRoundId.ToString();
ws.Cell(row, 4).Value = e.Scope ?? "";
ws.Cell(row, 5).Value = e.FileName ?? "";
ws.Cell(row, 6).Value = e.StepIndex;
ws.Cell(row, 7).Value = e.StepName ?? "";
ws.Cell(row, 8).Value = e.StepType ?? "";
ws.Cell(row, 9).Value = e.MethodName ?? "";
ws.Cell(row, 10).Value = e.MethodFullName ?? "";
ws.Cell(row, 11).Value = e.Result;
ws.Cell(row, 12).Value = e.RunTimeMs;
ws.Cell(row, 13).Value = e.OutputValue ?? "";
ws.Cell(row, 14).Value = e.Depth;
ws.Cell(row, 15).Value = e.IsErrorStep ? "是" : "否";
ws.Cell(row, 16).Value = e.LoopRemaining;
// 数据行边框
var dataRange = ws.Range(row, 1, row, headers.Length);
dataRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
dataRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
}
ws.Columns().AdjustToContents();
workbook.SaveAs(filePath);
}
#endregion
#region
public override void OnNavigatedTo(NavigationContext navigationContext)
{
}
public void Dispose()
{
}
#endregion
}
}

View File

@@ -1,14 +1,204 @@
<UserControl x:Class="ExportModule.Views.ExportView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="1080" d:DesignWidth="1920">
<Grid>
</Grid>
<UserControl x:Class="ExportModule.Views.ExportView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="700"
d:DesignWidth="1200">
<UserControl.Resources>
<converters:LessThanConverter x:Key="LessThanConverter"/>
</UserControl.Resources>
<Border Background="#F5F7FA">
<Grid x:Name="RootGrid" Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ========== 标题 ========== -->
<TextBlock Grid.Row="0"
Text="测试报告导出"
FontSize="20" FontWeight="Bold"
Margin="4,4,0,12"/>
<!-- ========== 主内容卡片 ========== -->
<Border Grid.Row="1"
Background="White"
BorderBrush="#DDD" BorderThickness="1"
CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<!-- 工具栏 -->
<RowDefinition Height="Auto"/>
<!-- 数据表格 -->
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- ====== 工具栏(筛选条件 + 操作按钮) ====== -->
<Border Grid.Row="0"
Background="#ECEFF4"
Padding="12,8"
BorderBrush="#DDD" BorderThickness="0,0,0,1">
<!-- 第一行:筛选条件 -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 开始日期 -->
<TextBlock Grid.Column="0"
Text="开始日期:"
VerticalAlignment="Center"
FontWeight="SemiBold"
Margin="0,0,4,0"/>
<DatePicker Grid.Column="1"
materialDesign:HintAssist.Hint=""
SelectedDate="{Binding StartTime}"
VerticalAlignment="Center"
Margin="0,0,16,0"/>
<!-- 结束日期 -->
<TextBlock Grid.Column="2"
Text="结束日期:"
VerticalAlignment="Center"
FontWeight="SemiBold"
Margin="0,0,4,0"/>
<DatePicker Grid.Column="3"
materialDesign:HintAssist.Hint=""
SelectedDate="{Binding EndTime}"
VerticalAlignment="Center"
Margin="0,0,16,0"/>
<!-- 台架选择 -->
<TextBlock Grid.Column="4"
Text="台架:"
VerticalAlignment="Center"
FontWeight="SemiBold"
Margin="0,0,4,0"/>
<ComboBox Grid.Column="5"
materialDesign:HintAssist.Hint=""
ItemsSource="{Binding ScopeOptions}"
SelectedItem="{Binding SelectedScope}"
VerticalAlignment="Center"
Margin="0,0,16,0"/>
<!-- 操作按钮 -->
<Button Grid.Column="6"
Content="查询"
Command="{Binding QueryCommand}"
Style="{StaticResource MaterialDesignFlatButton}"
Padding="16,6"
Margin="4,0"
VerticalAlignment="Center"/>
<Button Grid.Column="7"
Content="导出 Excel"
Command="{Binding ExportCommand}"
Style="{StaticResource MaterialDesignFlatButton}"
Padding="16,6"
Margin="4,0"
VerticalAlignment="Center"/>
<Button Grid.Column="8"
Content="加载全部"
Command="{Binding LoadedCommand}"
Style="{StaticResource MaterialDesignFlatButton}"
Padding="16,6"
Margin="4,0"
VerticalAlignment="Center"/>
</Grid>
</Border>
<!-- ====== 数据展示区 ====== -->
<DataGrid Grid.Row="1"
ItemsSource="{Binding TestReportModelList}"
AutoGenerateColumns="False"
IsReadOnly="True"
RowHeaderWidth="0"
BorderThickness="0"
Background="White"
AlternatingRowBackground="#F5F7FA"
SelectionMode="Single"
SelectedItem="{Binding SelectedTestReport}"
Margin="8">
<DataGrid.Columns>
<DataGridTextColumn Header="TestRoundId"
Width="280"
Binding="{Binding TestRoundId}"/>
<DataGridTextColumn Header="台架名称"
Width="120"
Binding="{Binding Scope}"/>
<DataGridTextColumn Header="开始时间"
Width="180"
Binding="{Binding StartTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}"/>
<DataGridTextColumn Header="结束时间"
Width="180"
Binding="{Binding EndTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}"/>
<DataGridTextColumn Header="文件名"
Width="*"
Binding="{Binding FileName}"/>
</DataGrid.Columns>
<DataGrid.Resources>
<!-- 标题行样式:与项目风格一致 -->
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="#ECEFF4"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="BorderBrush" Value="#DDD"/>
<Setter Property="BorderThickness" Value="0,0,1,1"/>
<Setter Property="Padding" Value="8,6"/>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Border>
<!-- ========== 状态栏 ========== -->
<Border Grid.Row="2"
Background="#ECEFF4"
Padding="10,6"
Margin="0,8,0,0"
CornerRadius="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding StatusMessage}"
Foreground="#444"
FontSize="12"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Foreground="#666"
FontSize="12"
VerticalAlignment="Center"
Margin="8,0,0,0">
<Run Text="记录数:"/>
<Run Text="{Binding TotalCount}"/>
</TextBlock>
</Grid>
</Border>
</Grid>
</Border>
</UserControl>

View File

@@ -266,6 +266,7 @@ namespace MainModule.ViewModels
if(_systemConfig.DefaultProgramFilePath != null&&File.Exists(_systemConfig.DefaultProgramFilePath))
{
var filePath = _systemConfig.DefaultProgramFilePath;
_systemConfig.CurrentADPFile= _systemConfig.DefaultProgramFilePath;
// 读取 JSON 文件
string json = File.ReadAllText(filePath);

View File

@@ -16,6 +16,9 @@ namespace Model.Entity
/// <summary>作用域/台架名称</summary>
[SugarColumn(ColumnName = "Scope", ColumnDescription = "台架名称")]
public string Scope { get; set; } = string.Empty;
/// <summary>测试项名称当前ADP文件路径</summary>
[SugarColumn(ColumnName = "FileName", ColumnDescription = "测试项名称")]
public string FileName { get; set; } = string.Empty;
/// <summary>步骤序号</summary>
[SugarColumn(ColumnName = "StepIndex")]

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Models
{
public class TestReportModel
{
public Guid TestRoundId { get; set; }
public string Scope { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string FileName { get; set; }
}
}

View File

@@ -17,7 +17,6 @@ namespace MonitorModule
{
containerRegistry.RegisterForNavigation<MonitorView>("MonitorView");
containerRegistry.RegisterForNavigation<RecordView>("RecordView");
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
containerRegistry.RegisterDialog<ValueLimitView>("ValueLimitView");
}
}

View File

@@ -1,68 +0,0 @@
using Logger;
using MahApps.Metro.Controls;
using Model.Entity;
using Model.Models;
using NLog.Targets;
using OxyPlot;
using Service.Interface;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using UIShare.GlobalVariable;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
namespace MonitorModule.ViewModels
{
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
{
#region
public bool KeepAlive => true;
#endregion
#region
#endregion
#region
#endregion
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
}
#region
#endregion
#region
#endregion
#region
public override void OnNavigatedTo(NavigationContext navigationContext)
{
}
public void Dispose()
{
}
#endregion
}
}

View File

@@ -64,10 +64,15 @@ namespace MonitorModule.ViewModels
}
/// <summary>查询日期null = 全部日期)</summary>
public DateTime? SelectedDate
public DateTime? SelectedStartDate
{
get => _selectedDate;
set => SetProperty(ref _selectedDate, value);
get => _selectedStartDate;
set => SetProperty(ref _selectedStartDate, value);
}
public DateTime? SelectedEndDate
{
get => _selectedEndDate;
set => SetProperty(ref _selectedEndDate, value);
}
public DataTable ResultTable
@@ -142,7 +147,8 @@ namespace MonitorModule.ViewModels
private string? _selectedMonitorName;
private ObservableCollection<string> _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" });
private string? _selectedScope = "全部";
private DateTime? _selectedDate;
private DateTime? _selectedStartDate;
private DateTime? _selectedEndDate;
private DataTable _resultTable = new();
private string _statusMessage = "未连接";
private int _pageIndex = 1;
@@ -257,7 +263,8 @@ namespace MonitorModule.ViewModels
{
SelectedMonitorName = null;
SelectedScope = "全部";
SelectedDate = null;
SelectedStartDate = null;
SelectedEndDate = null;
PageIndex = 1;
Query();
}
@@ -274,8 +281,8 @@ namespace MonitorModule.ViewModels
var db = SqlSugarContext.DbContext;
// 预计算日期范围,避免在表达式树中访问可空属性
DateTime? dateStart = SelectedDate?.Date;
DateTime? dateEnd = dateStart?.AddDays(1);
DateTime? dateStart = SelectedStartDate?.Date;
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
var query = db.Queryable<MonitorValueEntity>()
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
@@ -317,8 +324,8 @@ namespace MonitorModule.ViewModels
{
var db = SqlSugarContext.DbContext;
DateTime? dateStart = SelectedDate?.Date;
DateTime? dateEnd = dateStart?.AddDays(1);
DateTime? dateStart = SelectedStartDate?.Date;
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
var query = db.Queryable<MonitorValueEntity>()
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)

View File

@@ -1,14 +0,0 @@
<UserControl x:Class="MonitorModule.Views.CurveRecallView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="1080" d:DesignWidth="1920">
<Grid>
</Grid>
</UserControl>

View File

@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MonitorModule.Views
{
/// <summary>
/// CurveRecallView.xaml 的交互逻辑
/// </summary>
public partial class CurveRecallView : UserControl
{
public CurveRecallView()
{
InitializeComponent();
}
}
}

View File

@@ -52,6 +52,7 @@
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
@@ -88,20 +89,25 @@
VerticalAlignment="Center"
Margin="12,0,4,0"/>
<DatePicker Grid.Column="5"
SelectedDate="{Binding SelectedDate}"
materialDesign:HintAssist.Hint="全部"
SelectedDate="{Binding SelectedStartDate}"
materialDesign:HintAssist.Hint="开始时间"
VerticalAlignment="Center"/>
<DatePicker Grid.Column="6"
Margin=" 5 0 0 0"
SelectedDate="{Binding SelectedEndDate}"
materialDesign:HintAssist.Hint="结束时间"
VerticalAlignment="Center"/>
<!-- 按钮 -->
<Button Grid.Column="6"
<Button Grid.Column="7"
Content="查询"
Command="{Binding QueryCommand}"
Padding="12,4" Margin="12,0,0,0"/>
<Button Grid.Column="7"
<Button Grid.Column="8"
Content="清除"
Command="{Binding ClearFilterCommand}"
Padding="12,4" Margin="6,0,0,0"/>
<Button Grid.Column="8"
<Button Grid.Column="9"
Content="导出 CSV"
Command="{Binding ExportCsvCommand}"
Padding="12,4" Margin="6,0,0,0"/>

View File

@@ -0,0 +1,109 @@
using Model;
using Model.Entity;
using Model.Models;
using ORM;
using Service.Interface;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static System.Formats.Asn1.AsnWriter;
namespace Service.Implement
{
public class TestReportService : BaseService<TestReportEntity>, ITestReportService
{
public TestReportService(SqlSugarRepository<TestReportEntity> repository) : base(repository)
{
}
/// <summary>
/// 根据 TestRoundId 获取符合条件的测试步骤
/// </summary>
public async Task<Result<List<TestReportEntity>>> GetByTestRoundIdAsync(Guid testRoundId)
{
try
{
var list = await _repository.Entities
.Where(x => x.TestRoundId == testRoundId)
.ToListAsync();
return Result<List<TestReportEntity>>.Success(list);
}
catch (Exception ex)
{
return Result<List<TestReportEntity>>.Error("根据 TestRoundId 查询数据失败", ex);
}
}
/// <summary>
/// 根据 TestRoundId 分组,获取唯一记录并按分组统计最大与最小时间,
/// 支持按日期范围与台架名称筛选。
/// </summary>
public async Task<Result<IList<TestReportModel>>> GetFilterList(DateTime? startTime = null, DateTime? endTime = null, string? scope = null)
{
try
{
// 使用 SqlSugar 的 GroupBy 查询,聚合最大/最小时间
// 推荐采用这种按组投影的方式,将分组计算交由数据库处理,性能最佳
var query = _repository.Context.Queryable<TestReportEntity>();
// 日期范围筛选
if (startTime.HasValue)
query = query.Where(x => x.CreateTime >= startTime.Value);
if (endTime.HasValue)
query = query.Where(x => x.CreateTime <= endTime.Value);
// 台架名称筛选
if (!string.IsNullOrEmpty(scope))
query = query.Where(x => x.Scope == scope);
var list = await query
.GroupBy(x => x.TestRoundId)
.Select(x => new TestReportModel
{
TestRoundId = x.TestRoundId,
StartTime = SqlFunc.AggregateMin(x.CreateTime),
EndTime = SqlFunc.AggregateMax(x.CreateTime),
Scope = x.Scope,
FileName = x.FileName,
})
.ToListAsync();
return Result<IList<TestReportModel>>.Success(list);
}
catch (Exception ex)
{
return Result<IList<TestReportModel>>.Error("获取分组过滤列表失败", ex);
}
}
/// <summary>
/// 根据日期和台架名称筛选获取完整的测试步骤实体列表(无分组,用于导出)
/// </summary>
public async Task<Result<List<TestReportEntity>>> GetEntitiesByFilter(DateTime? startTime = null, DateTime? endTime = null, string? scope = null)
{
try
{
var query = _repository.Context.Queryable<TestReportEntity>();
if (startTime.HasValue)
query = query.Where(x => x.CreateTime >= startTime.Value);
if (endTime.HasValue)
query = query.Where(x => x.CreateTime <= endTime.Value);
if (!string.IsNullOrEmpty(scope))
query = query.Where(x => x.Scope == scope);
var list = await query
.OrderBy(x => x.CreateTime)
.ToListAsync();
return Result<List<TestReportEntity>>.Success(list);
}
catch (Exception ex)
{
return Result<List<TestReportEntity>>.Error("获取导出数据失败", ex);
}
}
}
}

View File

@@ -0,0 +1,37 @@
using Model;
using Model.Entity;
using Model.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Service.Interface
{
public interface ITestReportService:IBaseService<TestReportEntity>
{
/// <summary>
/// 根据TestRoundId获符合的测试步骤
/// </summary>
/// <param name="TestRoundId">TestRoundId</param>
/// <returns>返回符合的TestReportEntity列表</returns>
Task<Result<List<TestReportEntity>>> GetByTestRoundIdAsync(Guid TestRoundId);
/// <summary>
/// 根据 TestRoundId 分组,获取唯一记录并按分组统计最大与最小时间
/// </summary>
/// <param name="startTime">开始日期筛选(可选)</param>
/// <param name="endTime">结束日期筛选(可选)</param>
/// <param name="scope">台架名称筛选(可选)</param>
Task<Result<IList<TestReportModel>>> GetFilterList(DateTime? startTime = null, DateTime? endTime = null, string? scope = null);
/// <summary>
/// 根据日期和台架名称筛选获取完整的测试步骤实体列表(无分组,用于导出)
/// </summary>
/// <param name="startTime">开始日期筛选(可选)</param>
/// <param name="endTime">结束日期筛选(可选)</param>
/// <param name="scope">台架名称筛选(可选)</param>
Task<Result<List<TestReportEntity>>> GetEntitiesByFilter(DateTime? startTime = null, DateTime? endTime = null, string? scope = null);
}
}

View File

@@ -402,11 +402,11 @@
<Border Background="White"
BorderBrush="#E0E0E0" BorderThickness="1"
CornerRadius="4" Padding="14">
<StackPanel>
<!--<StackPanel>
<TextBlock Text="高级设置" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
<TextBlock Foreground="#888" FontSize="12" TextWrapping="Wrap"
Text="超时、重试、缓存策略等高级选项后续在此扩展。"/>
</StackPanel>
</StackPanel>-->
</Border>
</StackPanel>
</ScrollViewer>

View File

@@ -93,7 +93,20 @@ namespace TestingModule.ViewModels.Dialogs
var index = Program.Parameters
.Select((x, i) => new { x, i })
.FirstOrDefault(p => p.x.ID == _ScopedContext.SelectedParameter.ID)?.i;
if (!_ScopedContext.SelectedParameter.IsEditable)
{
//公共变量只允许更改ParameterCategory为Input,Temp
if (Program.Parameters[index.Value].Value != Parameter.Value
|| Program.Parameters[index.Value].UpperLimit != Parameter.UpperLimit
|| Program.Parameters[index.Value].LowerLimit != Parameter.LowerLimit
|| Program.Parameters[index.Value].Name != Parameter.Name
|| Parameter.Category == ParameterCategory.Output
|| Program.Parameters[index.Value].Type != Parameter.Type)
{
RequestClose.Invoke(ButtonResult.Yes);
return;
}
}
if (index.HasValue)
{
Program.Parameters[index.Value] = Parameter;

View File

@@ -190,13 +190,13 @@ namespace TestingModule.ViewModels
private void ParameterDelete()
{
if (!_globalInfo.IsAdmin) return;
if (!_globalInfo.IsAdmin || !SelectedParameter.IsEditable) return;
Program.Parameters.Remove(SelectedParameter);
}
private void ParameterEdit()
{
if (!_globalInfo.IsAdmin||!SelectedParameter.IsEditable) return;
if (!_globalInfo.IsAdmin) return;
var param = new DialogParameters
{
{ "Mode",SelectedParameter==null?"ADD":"Edit" },
@@ -207,10 +207,11 @@ namespace TestingModule.ViewModels
if (r.Result == ButtonResult.OK)
{
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
ShowInfoMessageBox("保存成功", () => { });
}
else
else if (r.Result == ButtonResult.Yes)
{
ShowErrorMessageBox("公共变量只能设置变量类型Input,Temp", ()=>{ });
}
});

View File

@@ -4,7 +4,7 @@ using Common.Tools;
using Logger;
using MaterialDesignThemes.Wpf;
using Model.Entity;
using ORM;
using Service.Interface;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -28,6 +28,7 @@ namespace UIShare
//private Devices _devices;
private IContainerProvider containerProvider;
private IEventAggregator _eventAggregator;
private ITestReportService _testReportService;
private readonly Dictionary<Guid, ParameterVM> tmpParameters = [];
@@ -45,12 +46,13 @@ namespace UIShare
private volatile bool _disposed = false;
public Guid TestRoundID;
public StepRunning(ScopedContext ScopedContext, SystemConfig systemConfig,IEventAggregator eventAggregator, DeviceManager deviceManager)
public StepRunning(ScopedContext ScopedContext, SystemConfig systemConfig,IEventAggregator eventAggregator, DeviceManager deviceManager, ITestReportService testReportService)
{
_scopedContext = ScopedContext;
_systemConfig = systemConfig;
_eventAggregator = eventAggregator;
_deviceManager= deviceManager;
_testReportService = testReportService;
//_devices = containerProvider.Resolve<Devices>();
}
public async Task<bool> ExecuteErrorSteps(ProgramVM program, int depth = 0, CancellationToken cancellationToken = default)
@@ -116,7 +118,7 @@ namespace UIShare
step.CurrentLoopCount = context.LoopCount;
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始,共{context.LoopCount}次", depth);
index++;
SaveStepRecord(step, depth, true);
await SaveStepRecordAsync(step, depth, true);
}
// 处理循环结束
@@ -127,7 +129,7 @@ namespace UIShare
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth: depth);
step.Result = 2;
index++;
SaveStepRecord(step, depth, true);
await SaveStepRecordAsync(step, depth, true);
continue;
}
@@ -142,7 +144,7 @@ namespace UIShare
// 继续循环:跳转到循环开始后的第一条指令
index = context.StartIndex + 1;
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
SaveStepRecord(step, depth, true);
await SaveStepRecordAsync(step, depth, true);
}
else
{
@@ -159,7 +161,7 @@ namespace UIShare
program.ErrorStepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1;
loopStopwatchStack.Pop();
}
SaveStepRecord(step, depth, true);
await SaveStepRecordAsync(step, depth, true);
}
}
@@ -218,7 +220,7 @@ namespace UIShare
stepStopwatch.Stop();
step.RunTime = (int)stepStopwatch.ElapsedMilliseconds;
}
SaveStepRecord(step, depth, true);
await SaveStepRecordAsync(step, depth, true);
}
}
@@ -291,7 +293,7 @@ namespace UIShare
step.CurrentLoopCount = context.LoopCount;
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始({step.Name}),共{context.LoopCount}次", depth);
index++;
SaveStepRecord(step, depth, false);
await SaveStepRecordAsync(step, depth, false);
}
// 处理循环结束
@@ -302,7 +304,7 @@ namespace UIShare
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth:depth);
step.Result = 2;
index++;
SaveStepRecord(step, depth, false);
await SaveStepRecordAsync(step, depth, false);
continue;
}
@@ -317,7 +319,7 @@ namespace UIShare
// 继续循环:跳转到循环开始后的第一条指令
index = context.StartIndex + 1;
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
SaveStepRecord(step, depth, false);
await SaveStepRecordAsync(step, depth, false);
}
else
{
@@ -334,7 +336,7 @@ namespace UIShare
program.StepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1;
loopStopwatchStack.Pop();
}
SaveStepRecord(step, depth, false);
await SaveStepRecordAsync(step, depth, false);
}
}
@@ -400,7 +402,7 @@ namespace UIShare
_scopedContext.SingleStep = false;
_eventAggregator.GetEvent<RunSingalCompletedEvent>().Publish("Play");
}
SaveStepRecord(step, depth, false);
await SaveStepRecordAsync(step, depth, false);
}
}
@@ -685,7 +687,7 @@ namespace UIShare
/// 将单个步骤的执行结果保存到数据库(测试报告)。
/// 同一次运行的所有步骤共享 TestRoundID导出时按此 Guid 查询。
/// </summary>
private void SaveStepRecord(StepVM step, int depth, bool isErrorStep)
private async Task SaveStepRecordAsync(StepVM step, int depth, bool isErrorStep)
{
try
{
@@ -702,6 +704,7 @@ namespace UIShare
{
TestRoundId = TestRoundID,
Scope = _systemConfig.Title,
FileName = _systemConfig.CurrentADPFile ?? "",
StepIndex = step.Index,
StepName = step.Name ?? "",
StepType = step.StepType ?? "普通步骤",
@@ -723,7 +726,11 @@ namespace UIShare
CreateTime = DateTime.Now
};
SqlSugarContext.DbContext.Insertable(entity).ExecuteCommand();
var result = await _testReportService.InsertAsync(entity);
if (!result.IsSuccess)
{
LoggerHelper.Error($"保存步骤记录失败 [{step.Index}]: {result.Msg}");
}
}
catch (Exception ex)
{

View File

@@ -24,6 +24,7 @@ namespace UIShare.GlobalVariable
public string TSMasterName { get; set; } = "ADP测试上位机";
public string SubProgramFilePath { get; set; } = @"D:\ADP\子程序\";
public string Title { get; set; } = string.Empty;
public string CurrentADPFile { get; set; }
public int PerformanceLevel { get; set; } = 50;
public string DefaultProgramFilePath { get; set; } = "";
public string DefaultBLFFilePath { get; set; } = "";

View File

@@ -22,6 +22,7 @@
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
<ProjectReference Include="..\Logger\Logger.csproj" />
<ProjectReference Include="..\ORM\ORM.csproj" />
<ProjectReference Include="..\Service\Service.csproj" />
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
</ItemGroup>
</Project>

View File

@@ -44,7 +44,7 @@ namespace UIShare.ViewModelBase
var dialogParams = new DialogParameters();
dialogParams.Add("Title", "错误");
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("Icon", "error");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBox", dialogParams, result =>
{

View File

@@ -21,7 +21,7 @@ namespace ZLGUSBCANFD
// 每个通道分配独立的 DBC 引擎句柄与锁彻底解决台架间DBC冲突与锁竞争
private readonly uint[] _dbcHandles;
private readonly bool[] _isDbcLoadedArray;
private readonly object[] _channelLocks; // 通道级细粒度锁
private readonly object[] _channelLocks; // 通道级细粒度锁
// 异步高性能接收线程控制
private volatile bool _isRunning = false;
@@ -619,7 +619,7 @@ namespace ZLGUSBCANFD
/// <param name="物理值">要写入的实际物理数值</param>
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(单位毫秒)</param>
/// <returns>操作是否成功</returns>
public virtual bool (uint , string IDStr, string , double , int = 0, bool =false)
public virtual bool (uint , string IDStr, string , double , int = 0, bool = false)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
@@ -877,7 +877,7 @@ namespace ZLGUSBCANFD
}
finally
{
Marshal.FreeHGlobal(ptrDbcMsg);
}
}