Compare commits
13 Commits
c592bcd272
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8daa1df8e9 | |||
| 7776e6c7ab | |||
| 2e71f68428 | |||
| 388edd1ada | |||
| 6057a71315 | |||
| 12de1b61e0 | |||
| 95700bfb8c | |||
| 013729003a | |||
| add1cd95be | |||
| 2a13c0ad34 | |||
| 92d477972f | |||
| 14d818083d | |||
| 07218fc737 |
@@ -151,6 +151,7 @@ namespace ADP
|
||||
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
||||
//注册服务
|
||||
containerRegistry.Register<IMonitorValueService, MonitorValueService>();
|
||||
containerRegistry.Register<ITestReportService, TestReportService>();
|
||||
}
|
||||
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
||||
protected override IModuleCatalog CreateModuleCatalog()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ncalc" Version="1.3.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
|
||||
<PackageReference Include="System.IO.Ports" Version="11.0.0-preview.4.26230.115" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -69,6 +69,10 @@ namespace MainModule.ViewModels
|
||||
string json = System.IO.File.ReadAllText(filePath);
|
||||
Newtonsoft.Json.JsonConvert.PopulateObject(json, _systemConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
_systemConfig = new SystemConfig();
|
||||
}
|
||||
}
|
||||
//容器解析顺序不要改变!!!
|
||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||
@@ -266,6 +270,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);
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.215-preview13" />
|
||||
<PackageReference Include="System.IO.Ports" Version="11.0.0-preview.4.26230.115" />
|
||||
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
17
Model/Models/TestReportModel.cs
Normal file
17
Model/Models/TestReportModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ namespace MonitorModule
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<MonitorView>("MonitorView");
|
||||
containerRegistry.RegisterForNavigation<RecordView>("RecordView");
|
||||
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
|
||||
containerRegistry.RegisterDialog<ValueLimitView>("ValueLimitView");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
109
Service/Implement/TestReportService.cs
Normal file
109
Service/Implement/TestReportService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Service/Interface/ITestReportService.cs
Normal file
37
Service/Interface/ITestReportService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)", ()=>{ });
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -29,52 +29,7 @@ namespace UIShare.GlobalVariable
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据标题(格子标识)加载独立的配置文件
|
||||
/// </summary>
|
||||
public static SystemConfig Load(string title)
|
||||
{
|
||||
if (string.IsNullOrEmpty(title))
|
||||
{
|
||||
throw new ArgumentException("配置标题不能为空", nameof(title));
|
||||
}
|
||||
|
||||
// 临时实例化一个对象以获取默认的 SystemPath
|
||||
var dummy = new SystemConfig();
|
||||
string configPath = Path.Combine(dummy.SystemPath, $"{title}.json");
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
// 如果不存在,创建一个带 Title 的默认配置并保存
|
||||
var defaultConfig = new SystemConfig { Title = title };
|
||||
EnsureDefaultCanDevice(defaultConfig);
|
||||
Save(defaultConfig);
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
lock (_fileLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(configPath);
|
||||
var config = JsonConvert.DeserializeObject<SystemConfig>(json, new JsonSerializerSettings
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All
|
||||
});
|
||||
|
||||
config ??= new SystemConfig { Title = title };
|
||||
EnsureDefaultCanDevice(config);
|
||||
return config;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(title, $"格子 [{title}] 配置加载失败: {ex.Message}");
|
||||
var fallback = new SystemConfig { Title = title };
|
||||
EnsureDefaultCanDevice(fallback);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 保存指定的配置实例
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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; } = "";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user