Compare commits
13 Commits
9281b48a2b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e71f68428 | |||
| 388edd1ada | |||
| 6057a71315 | |||
| 12de1b61e0 | |||
| 95700bfb8c | |||
| 013729003a | |||
| add1cd95be | |||
| 2a13c0ad34 | |||
| 92d477972f | |||
| 14d818083d | |||
| 07218fc737 | |||
| c592bcd272 | |||
| 092087f670 |
7
ADP.sln
7
ADP.sln
@@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "加密狗", "加密狗\加
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurveModule", "CurveModule\CurveModule.csproj", "{84006890-9288-44C4-9D17-FA88F3FDECFA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurveModule", "CurveModule\CurveModule.csproj", "{84006890-9288-44C4-9D17-FA88F3FDECFA}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportModule", "ExportModule\ExportModule.csproj", "{8923122F-F7D3-4AE7-9E86-9E78F1296A11}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -137,6 +139,10 @@ Global
|
|||||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -152,5 +158,6 @@ Global
|
|||||||
{31F84D0E-3634-4C70-A830-21AF412E3BBF} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
{31F84D0E-3634-4C70-A830-21AF412E3BBF} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
{AF2533DD-599D-49D7-942B-B0C26534D15F} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
{AF2533DD-599D-49D7-942B-B0C26534D15F} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
{84006890-9288-44C4-9D17-FA88F3FDECFA} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
{84006890-9288-44C4-9D17-FA88F3FDECFA} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -22,8 +22,10 @@
|
|||||||
<ProjectReference Include="..\BenchMovementModule\BenchMovementModule.csproj" />
|
<ProjectReference Include="..\BenchMovementModule\BenchMovementModule.csproj" />
|
||||||
<ProjectReference Include="..\CANModule\CANModule.csproj" />
|
<ProjectReference Include="..\CANModule\CANModule.csproj" />
|
||||||
<ProjectReference Include="..\Command\Command.csproj" />
|
<ProjectReference Include="..\Command\Command.csproj" />
|
||||||
|
<ProjectReference Include="..\CurveModule\CurveModule.csproj" />
|
||||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||||
<ProjectReference Include="..\DeviceEditModule\DeviceEditModule.csproj" />
|
<ProjectReference Include="..\DeviceEditModule\DeviceEditModule.csproj" />
|
||||||
|
<ProjectReference Include="..\ExportModule\ExportModule.csproj" />
|
||||||
<ProjectReference Include="..\LoginModule\LoginModule.csproj" />
|
<ProjectReference Include="..\LoginModule\LoginModule.csproj" />
|
||||||
<ProjectReference Include="..\MainModule\MainModule.csproj" />
|
<ProjectReference Include="..\MainModule\MainModule.csproj" />
|
||||||
<ProjectReference Include="..\MonitorModule\MonitorModule.csproj" />
|
<ProjectReference Include="..\MonitorModule\MonitorModule.csproj" />
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ namespace ADP
|
|||||||
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
||||||
//注册服务
|
//注册服务
|
||||||
containerRegistry.Register<IMonitorValueService, MonitorValueService>();
|
containerRegistry.Register<IMonitorValueService, MonitorValueService>();
|
||||||
|
containerRegistry.Register<ITestReportService, TestReportService>();
|
||||||
}
|
}
|
||||||
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
||||||
protected override IModuleCatalog CreateModuleCatalog()
|
protected override IModuleCatalog CreateModuleCatalog()
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Threading; // 💡 新增:引入定时器命名空间
|
using System.Windows.Threading;
|
||||||
using UIShare;
|
using UIShare;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare.PubEvent;
|
using UIShare.PubEvent;
|
||||||
using UIShare.UIViewModel;
|
using UIShare.UIViewModel;
|
||||||
using ZLGUSBCANFD;
|
using SqlSugar;
|
||||||
|
|
||||||
namespace ADP.ViewModels
|
namespace ADP.ViewModels
|
||||||
{
|
{
|
||||||
@@ -567,7 +567,7 @@ namespace ADP.ViewModels
|
|||||||
ScopedContext? targetContext = CurrentContext;
|
ScopedContext? targetContext = CurrentContext;
|
||||||
|
|
||||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||||
|
CurrentConfig.CurrentADPFile = "新建ADP文件";
|
||||||
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
|
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
|
||||||
targetContext.CurrentFilePath = null;
|
targetContext.CurrentFilePath = null;
|
||||||
targetContext.Program.Parameters.Clear();
|
targetContext.Program.Parameters.Clear();
|
||||||
@@ -631,7 +631,7 @@ namespace ADP.ViewModels
|
|||||||
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
|
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
CurrentConfig.CurrentADPFile = filePath;
|
||||||
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
||||||
targetContext.Program.Parameters = program.Parameters;
|
targetContext.Program.Parameters = program.Parameters;
|
||||||
targetContext.Program.StepCollection = program.StepCollection;
|
targetContext.Program.StepCollection = program.StepCollection;
|
||||||
@@ -691,6 +691,7 @@ namespace ADP.ViewModels
|
|||||||
if (saveFileDialog.ShowDialog() == true)
|
if (saveFileDialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
// 💡 2. 将新路径安全写入快照工位
|
// 💡 2. 将新路径安全写入快照工位
|
||||||
|
CurrentConfig.CurrentADPFile = saveFileDialog.FileName;
|
||||||
targetContext.CurrentFilePath = saveFileDialog.FileName;
|
targetContext.CurrentFilePath = saveFileDialog.FileName;
|
||||||
|
|
||||||
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
||||||
@@ -848,6 +849,10 @@ namespace ADP.ViewModels
|
|||||||
if (_globalInfo.CurrentScope != "default") return;
|
if (_globalInfo.CurrentScope != "default") return;
|
||||||
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
|
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
|
||||||
break;
|
break;
|
||||||
|
case "测试报告导出界面":
|
||||||
|
if (_globalInfo.CurrentScope != "default") return;
|
||||||
|
_regionManager.RequestNavigate("ShellViewManager", "ExportView");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,11 @@
|
|||||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||||
Style="{StaticResource MaterialDesignFlatButton}"
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
Margin="8" />
|
Margin="8" />
|
||||||
|
<Button Content="测试报告导出界面"
|
||||||
|
Command="{Binding NavigateCommand}"
|
||||||
|
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||||
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
|
Margin="8" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</materialDesign:DrawerHost.LeftDrawerContent>
|
</materialDesign:DrawerHost.LeftDrawerContent>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -189,6 +194,7 @@ Command="{Binding SelectCANMessageCommand}">
|
|||||||
Foreground="Black" />
|
Foreground="Black" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<!-- 获得文件路径字符串 -->
|
<!-- 获得文件路径字符串 -->
|
||||||
<MenuItem Header="获得文件路径字符串"
|
<MenuItem Header="获得文件路径字符串"
|
||||||
Foreground="Black"
|
Foreground="Black"
|
||||||
|
|||||||
@@ -1,68 +1,381 @@
|
|||||||
using Logger;
|
using Logger;
|
||||||
using MahApps.Metro.Controls;
|
|
||||||
using Model.Entity;
|
|
||||||
using Model.Models;
|
|
||||||
using NLog.Targets;
|
|
||||||
using OxyPlot;
|
using OxyPlot;
|
||||||
using Service.Interface;
|
using OxyPlot.Axes;
|
||||||
|
using OxyPlot.Legends;
|
||||||
|
using OxyPlot.Series;
|
||||||
|
using Prism.Commands;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Data;
|
using System.IO;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Threading;
|
|
||||||
using UIShare.GlobalVariable;
|
|
||||||
using UIShare.UIViewModel;
|
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
namespace CurveModule.ViewModels
|
namespace CurveModule.ViewModels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 曲线回读:加载 RecordView 导出的 CSV 文件,按 (MonitorName, Scope) 分组绘制曲线。
|
||||||
|
/// </summary>
|
||||||
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||||
{
|
{
|
||||||
#region 属性
|
#region 属性
|
||||||
public bool KeepAlive => true;
|
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
|
#endregion
|
||||||
|
|
||||||
#region 命令
|
#region 命令
|
||||||
|
public ICommand LoadCsvCommand { get; }
|
||||||
|
public ICommand ResetViewCommand { get; }
|
||||||
|
public ICommand ClearCommand { get; }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 私有字段
|
#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
|
#endregion
|
||||||
|
|
||||||
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
{
|
{
|
||||||
|
Plot = BuildEmptyPlot();
|
||||||
|
LoadCsvCommand = new DelegateCommand(OnLoadCsv);
|
||||||
|
ResetViewCommand = new DelegateCommand(OnResetView);
|
||||||
|
ClearCommand = new DelegateCommand(OnClear);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 命令处理
|
#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
|
#endregion
|
||||||
|
|
||||||
#region 辅助方法
|
#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
|
#endregion
|
||||||
#region 生命周期
|
|
||||||
|
|
||||||
|
#region 生命周期
|
||||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
ClearPlotAndChannels();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineSeries? Series { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,156 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
mc:Ignorable="d"
|
|
||||||
xmlns:prism="http://prismlibrary.com/"
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:oxy="http://oxyplot.org/wpf"
|
||||||
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
d:DesignHeight="1080" d:DesignWidth="1920">
|
mc:Ignorable="d"
|
||||||
<Grid>
|
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>
|
</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>
|
</UserControl>
|
||||||
|
|||||||
20
ExportModule/ExportModule.cs
Normal file
20
ExportModule/ExportModule.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
using ExportModule.Views;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ExportModule
|
||||||
|
{
|
||||||
|
public class ExportModule: IModule
|
||||||
|
{
|
||||||
|
public void OnInitialized(IContainerProvider containerProvider)
|
||||||
|
{
|
||||||
|
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||||
|
{
|
||||||
|
containerRegistry.RegisterForNavigation<ExportView>("ExportView");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
ExportModule/ExportModule.csproj
Normal file
20
ExportModule/ExportModule.csproj
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ClosedXML" Version="0.104.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ORM\ORM.csproj" />
|
||||||
|
<ProjectReference Include="..\Service\Service.csproj" />
|
||||||
|
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
262
ExportModule/ViewModels/ExportViewModel.cs
Normal file
262
ExportModule/ViewModels/ExportViewModel.cs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
using ClosedXML.Excel;
|
||||||
|
using Logger;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using Model.Models;
|
||||||
|
using Service.Interface;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace ExportModule.ViewModels
|
||||||
|
{
|
||||||
|
public class ExportViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||||
|
{
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
}
|
||||||
204
ExportModule/Views/ExportView.xaml
Normal file
204
ExportModule/Views/ExportView.xaml
Normal file
@@ -0,0 +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: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>
|
||||||
@@ -13,14 +13,14 @@ using System.Windows.Media.Imaging;
|
|||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
namespace MonitorModule.Views
|
namespace ExportModule.Views
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CurveRecallView.xaml 的交互逻辑
|
/// ExportView.xaml 的交互逻辑
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CurveRecallView : UserControl
|
public partial class ExportView : UserControl
|
||||||
{
|
{
|
||||||
public CurveRecallView()
|
public ExportView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,8 @@ namespace MainModule.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 执行异常流程(带并发保护,同一时间仅允许一次)
|
/// 执行异常流程(带并发保护,同一时间仅允许一次)。
|
||||||
|
/// 如果正常流程正在运行,先取消并等待其停止,再执行异常流程。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task TriggerErrorStepsAsync()
|
private async Task TriggerErrorStepsAsync()
|
||||||
{
|
{
|
||||||
@@ -143,6 +144,34 @@ namespace MainModule.ViewModels
|
|||||||
_isRunningErrorSteps = true;
|
_isRunningErrorSteps = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 1. 如果正常流程正在运行,先停止它
|
||||||
|
// IsStop == false 表示正在运行;IsStop == null 表示未启动或已结束
|
||||||
|
if (_scopedContext.IsStop == false)
|
||||||
|
{
|
||||||
|
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 报警触发,正在停止正常流程...");
|
||||||
|
_stepRunning.stepCTS.Cancel();
|
||||||
|
|
||||||
|
// 等待 ShellViewModel 清理完毕(IsStop 变回 null、stepCTS 被重置)
|
||||||
|
// ExecuteMethodStep 内部硬编码使用 stepCTS.Token,必须等它重置后才能跑异常流程
|
||||||
|
var deadline = DateTime.Now.AddSeconds(5);
|
||||||
|
while ((_scopedContext.IsStop != null || _stepRunning.stepCTS.IsCancellationRequested)
|
||||||
|
&& DateTime.Now < deadline)
|
||||||
|
{
|
||||||
|
await Task.Delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超时后仍未重置则强制重置,确保异常流程的设备命令能拿到有效 Token
|
||||||
|
if (_stepRunning.stepCTS.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_stepRunning.stepCTS = new CancellationTokenSource();
|
||||||
|
_scopedContext.IsStop = null;
|
||||||
|
_scopedContext.RunState = "运行";
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 正常流程已停止,开始执行异常流程");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 执行异常流程
|
||||||
await _stepRunning.ExecuteErrorSteps(
|
await _stepRunning.ExecuteErrorSteps(
|
||||||
_scopedContext.Program,
|
_scopedContext.Program,
|
||||||
cancellationToken: _stepRunning.errorStepCTS.Token);
|
cancellationToken: _stepRunning.errorStepCTS.Token);
|
||||||
@@ -237,6 +266,7 @@ namespace MainModule.ViewModels
|
|||||||
if(_systemConfig.DefaultProgramFilePath != null&&File.Exists(_systemConfig.DefaultProgramFilePath))
|
if(_systemConfig.DefaultProgramFilePath != null&&File.Exists(_systemConfig.DefaultProgramFilePath))
|
||||||
{
|
{
|
||||||
var filePath = _systemConfig.DefaultProgramFilePath;
|
var filePath = _systemConfig.DefaultProgramFilePath;
|
||||||
|
_systemConfig.CurrentADPFile= _systemConfig.DefaultProgramFilePath;
|
||||||
// 读取 JSON 文件
|
// 读取 JSON 文件
|
||||||
string json = File.ReadAllText(filePath);
|
string json = File.ReadAllText(filePath);
|
||||||
|
|
||||||
|
|||||||
67
Model/Entity/TestReportEntity.cs
Normal file
67
Model/Entity/TestReportEntity.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Model.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 测试报告记录:每次 ExecuteSteps / ExecuteErrorSteps 中每个步骤的执行结果。
|
||||||
|
/// 同一次运行的所有步骤共享同一个 TestRoundId。
|
||||||
|
/// </summary>
|
||||||
|
public class TestReportEntity : BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>同一次运行的统一标识(StepRunning.TestRoundID)</summary>
|
||||||
|
[SugarColumn(ColumnName = "TestRoundId", ColumnDescription = "运行轮次标识")]
|
||||||
|
public Guid TestRoundId { get; set; }
|
||||||
|
|
||||||
|
/// <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")]
|
||||||
|
public int StepIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>步骤名称</summary>
|
||||||
|
[SugarColumn(ColumnName = "StepName", Length = 200)]
|
||||||
|
public string StepName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>步骤类型(普通步骤/循环开始/循环结束)</summary>
|
||||||
|
[SugarColumn(ColumnName = "StepType", Length = 50)]
|
||||||
|
public string StepType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>执行的方法名称</summary>
|
||||||
|
[SugarColumn(ColumnName = "MethodName", Length = 200, IsNullable = true)]
|
||||||
|
public string? MethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>方法的完整类型名(实际方法所属类)</summary>
|
||||||
|
[SugarColumn(ColumnName = "MethodFullName", Length = 500, IsNullable = true)]
|
||||||
|
public string? MethodFullName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>执行结果(成功/失败/未执行/跳过)</summary>
|
||||||
|
[SugarColumn(ColumnName = "Result", Length = 20)]
|
||||||
|
public string Result { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>执行耗时(毫秒)</summary>
|
||||||
|
[SugarColumn(ColumnName = "RunTimeMs", IsNullable = true)]
|
||||||
|
public int? RunTimeMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>输出值(如果有)</summary>
|
||||||
|
[SugarColumn(ColumnName = "OutputValue", Length = 500, IsNullable = true)]
|
||||||
|
public string? OutputValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>子程序嵌套深度(0=主程序)</summary>
|
||||||
|
[SugarColumn(ColumnName = "Depth")]
|
||||||
|
public int Depth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>是否异常流程步骤</summary>
|
||||||
|
[SugarColumn(ColumnName = "IsErrorStep")]
|
||||||
|
public bool IsErrorStep { get; set; }
|
||||||
|
|
||||||
|
/// <summary>循环剩余次数(仅循环步骤有值)</summary>
|
||||||
|
[SugarColumn(ColumnName = "LoopRemaining", IsNullable = true)]
|
||||||
|
public int? LoopRemaining { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
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<MonitorView>("MonitorView");
|
||||||
containerRegistry.RegisterForNavigation<RecordView>("RecordView");
|
containerRegistry.RegisterForNavigation<RecordView>("RecordView");
|
||||||
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
|
|
||||||
containerRegistry.RegisterDialog<ValueLimitView>("ValueLimitView");
|
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>
|
/// <summary>查询日期(null = 全部日期)</summary>
|
||||||
public DateTime? SelectedDate
|
public DateTime? SelectedStartDate
|
||||||
{
|
{
|
||||||
get => _selectedDate;
|
get => _selectedStartDate;
|
||||||
set => SetProperty(ref _selectedDate, value);
|
set => SetProperty(ref _selectedStartDate, value);
|
||||||
|
}
|
||||||
|
public DateTime? SelectedEndDate
|
||||||
|
{
|
||||||
|
get => _selectedEndDate;
|
||||||
|
set => SetProperty(ref _selectedEndDate, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataTable ResultTable
|
public DataTable ResultTable
|
||||||
@@ -142,7 +147,8 @@ namespace MonitorModule.ViewModels
|
|||||||
private string? _selectedMonitorName;
|
private string? _selectedMonitorName;
|
||||||
private ObservableCollection<string> _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" });
|
private ObservableCollection<string> _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" });
|
||||||
private string? _selectedScope = "全部";
|
private string? _selectedScope = "全部";
|
||||||
private DateTime? _selectedDate;
|
private DateTime? _selectedStartDate;
|
||||||
|
private DateTime? _selectedEndDate;
|
||||||
private DataTable _resultTable = new();
|
private DataTable _resultTable = new();
|
||||||
private string _statusMessage = "未连接";
|
private string _statusMessage = "未连接";
|
||||||
private int _pageIndex = 1;
|
private int _pageIndex = 1;
|
||||||
@@ -257,7 +263,8 @@ namespace MonitorModule.ViewModels
|
|||||||
{
|
{
|
||||||
SelectedMonitorName = null;
|
SelectedMonitorName = null;
|
||||||
SelectedScope = "全部";
|
SelectedScope = "全部";
|
||||||
SelectedDate = null;
|
SelectedStartDate = null;
|
||||||
|
SelectedEndDate = null;
|
||||||
PageIndex = 1;
|
PageIndex = 1;
|
||||||
Query();
|
Query();
|
||||||
}
|
}
|
||||||
@@ -274,8 +281,8 @@ namespace MonitorModule.ViewModels
|
|||||||
var db = SqlSugarContext.DbContext;
|
var db = SqlSugarContext.DbContext;
|
||||||
|
|
||||||
// 预计算日期范围,避免在表达式树中访问可空属性
|
// 预计算日期范围,避免在表达式树中访问可空属性
|
||||||
DateTime? dateStart = SelectedDate?.Date;
|
DateTime? dateStart = SelectedStartDate?.Date;
|
||||||
DateTime? dateEnd = dateStart?.AddDays(1);
|
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
|
||||||
|
|
||||||
var query = db.Queryable<MonitorValueEntity>()
|
var query = db.Queryable<MonitorValueEntity>()
|
||||||
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
||||||
@@ -317,8 +324,8 @@ namespace MonitorModule.ViewModels
|
|||||||
{
|
{
|
||||||
var db = SqlSugarContext.DbContext;
|
var db = SqlSugarContext.DbContext;
|
||||||
|
|
||||||
DateTime? dateStart = SelectedDate?.Date;
|
DateTime? dateStart = SelectedStartDate?.Date;
|
||||||
DateTime? dateEnd = dateStart?.AddDays(1);
|
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
|
||||||
|
|
||||||
var query = db.Queryable<MonitorValueEntity>()
|
var query = db.Queryable<MonitorValueEntity>()
|
||||||
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
.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>
|
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
<ColumnDefinition Width="150"/>
|
<ColumnDefinition Width="150"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="150"/>
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
@@ -88,20 +89,25 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="12,0,4,0"/>
|
Margin="12,0,4,0"/>
|
||||||
<DatePicker Grid.Column="5"
|
<DatePicker Grid.Column="5"
|
||||||
SelectedDate="{Binding SelectedDate}"
|
SelectedDate="{Binding SelectedStartDate}"
|
||||||
materialDesign:HintAssist.Hint="全部"
|
materialDesign:HintAssist.Hint="开始时间"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<DatePicker Grid.Column="6"
|
||||||
|
Margin=" 5 0 0 0"
|
||||||
|
SelectedDate="{Binding SelectedEndDate}"
|
||||||
|
materialDesign:HintAssist.Hint="结束时间"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
<!-- 按钮 -->
|
<!-- 按钮 -->
|
||||||
<Button Grid.Column="6"
|
<Button Grid.Column="7"
|
||||||
Content="查询"
|
Content="查询"
|
||||||
Command="{Binding QueryCommand}"
|
Command="{Binding QueryCommand}"
|
||||||
Padding="12,4" Margin="12,0,0,0"/>
|
Padding="12,4" Margin="12,0,0,0"/>
|
||||||
<Button Grid.Column="7"
|
<Button Grid.Column="8"
|
||||||
Content="清除"
|
Content="清除"
|
||||||
Command="{Binding ClearFilterCommand}"
|
Command="{Binding ClearFilterCommand}"
|
||||||
Padding="12,4" Margin="6,0,0,0"/>
|
Padding="12,4" Margin="6,0,0,0"/>
|
||||||
<Button Grid.Column="8"
|
<Button Grid.Column="9"
|
||||||
Content="导出 CSV"
|
Content="导出 CSV"
|
||||||
Command="{Binding ExportCsvCommand}"
|
Command="{Binding ExportCsvCommand}"
|
||||||
Padding="12,4" Margin="6,0,0,0"/>
|
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"
|
<Border Background="White"
|
||||||
BorderBrush="#E0E0E0" BorderThickness="1"
|
BorderBrush="#E0E0E0" BorderThickness="1"
|
||||||
CornerRadius="4" Padding="14">
|
CornerRadius="4" Padding="14">
|
||||||
<StackPanel>
|
<!--<StackPanel>
|
||||||
<TextBlock Text="高级设置" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
<TextBlock Text="高级设置" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||||
<TextBlock Foreground="#888" FontSize="12" TextWrapping="Wrap"
|
<TextBlock Foreground="#888" FontSize="12" TextWrapping="Wrap"
|
||||||
Text="超时、重试、缓存策略等高级选项后续在此扩展。"/>
|
Text="超时、重试、缓存策略等高级选项后续在此扩展。"/>
|
||||||
</StackPanel>
|
</StackPanel>-->
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@@ -93,7 +93,20 @@ namespace TestingModule.ViewModels.Dialogs
|
|||||||
var index = Program.Parameters
|
var index = Program.Parameters
|
||||||
.Select((x, i) => new { x, i })
|
.Select((x, i) => new { x, i })
|
||||||
.FirstOrDefault(p => p.x.ID == _ScopedContext.SelectedParameter.ID)?.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)
|
if (index.HasValue)
|
||||||
{
|
{
|
||||||
Program.Parameters[index.Value] = Parameter;
|
Program.Parameters[index.Value] = Parameter;
|
||||||
|
|||||||
@@ -190,13 +190,13 @@ namespace TestingModule.ViewModels
|
|||||||
|
|
||||||
private void ParameterDelete()
|
private void ParameterDelete()
|
||||||
{
|
{
|
||||||
if (!_globalInfo.IsAdmin) return;
|
if (!_globalInfo.IsAdmin || !SelectedParameter.IsEditable) return;
|
||||||
Program.Parameters.Remove(SelectedParameter);
|
Program.Parameters.Remove(SelectedParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParameterEdit()
|
private void ParameterEdit()
|
||||||
{
|
{
|
||||||
if (!_globalInfo.IsAdmin||!SelectedParameter.IsEditable) return;
|
if (!_globalInfo.IsAdmin) return;
|
||||||
var param = new DialogParameters
|
var param = new DialogParameters
|
||||||
{
|
{
|
||||||
{ "Mode",SelectedParameter==null?"ADD":"Edit" },
|
{ "Mode",SelectedParameter==null?"ADD":"Edit" },
|
||||||
@@ -207,10 +207,11 @@ namespace TestingModule.ViewModels
|
|||||||
if (r.Result == ButtonResult.OK)
|
if (r.Result == ButtonResult.OK)
|
||||||
{
|
{
|
||||||
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
|
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
|
||||||
|
ShowInfoMessageBox("保存成功", () => { });
|
||||||
}
|
}
|
||||||
else
|
else if (r.Result == ButtonResult.Yes)
|
||||||
{
|
{
|
||||||
|
ShowErrorMessageBox("公共变量只能设置变量类型(Input,Temp)", ()=>{ });
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ namespace TestingModule.ViewModels
|
|||||||
_ScopedContext = scopedContext;
|
_ScopedContext = scopedContext;
|
||||||
_systemConfig = systemConfig;
|
_systemConfig = systemConfig;
|
||||||
_globalInfo = globalInfo;
|
_globalInfo = globalInfo;
|
||||||
|
Title = _systemConfig.Title;
|
||||||
EditStepCommand = new DelegateCommand(EditStep);
|
EditStepCommand = new DelegateCommand(EditStep);
|
||||||
CopyStepCommand = new DelegateCommand(CopyStep);
|
CopyStepCommand = new DelegateCommand(CopyStep);
|
||||||
PasteStepCommand = new DelegateCommand(PasteStep);
|
PasteStepCommand = new DelegateCommand(PasteStep);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Grid>
|
<Grid>
|
||||||
<GroupBox Header="程序编辑区域">
|
<GroupBox Header="{Binding Title}">
|
||||||
<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
|
<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
|
||||||
<i:Interaction.Behaviors>
|
<i:Interaction.Behaviors>
|
||||||
<behaviors:TabControlSelectionChangedBehavior Command="{Binding TabSelectionChangedCommand}"
|
<behaviors:TabControlSelectionChangedBehavior Command="{Binding TabSelectionChangedCommand}"
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using UIShare.PubEvent;
|
|||||||
using Common.Tools;
|
using Common.Tools;
|
||||||
using Logger;
|
using Logger;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
|
using Model.Entity;
|
||||||
|
using Service.Interface;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -26,6 +28,7 @@ namespace UIShare
|
|||||||
//private Devices _devices;
|
//private Devices _devices;
|
||||||
private IContainerProvider containerProvider;
|
private IContainerProvider containerProvider;
|
||||||
private IEventAggregator _eventAggregator;
|
private IEventAggregator _eventAggregator;
|
||||||
|
private ITestReportService _testReportService;
|
||||||
|
|
||||||
private readonly Dictionary<Guid, ParameterVM> tmpParameters = [];
|
private readonly Dictionary<Guid, ParameterVM> tmpParameters = [];
|
||||||
|
|
||||||
@@ -42,13 +45,14 @@ namespace UIShare
|
|||||||
/// <summary>标记是否已被注销,执行方法据此安全退出</summary>
|
/// <summary>标记是否已被注销,执行方法据此安全退出</summary>
|
||||||
private volatile bool _disposed = false;
|
private volatile bool _disposed = false;
|
||||||
|
|
||||||
private Guid TestRoundID;
|
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;
|
_scopedContext = ScopedContext;
|
||||||
_systemConfig = systemConfig;
|
_systemConfig = systemConfig;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_deviceManager= deviceManager;
|
_deviceManager= deviceManager;
|
||||||
|
_testReportService = testReportService;
|
||||||
//_devices = containerProvider.Resolve<Devices>();
|
//_devices = containerProvider.Resolve<Devices>();
|
||||||
}
|
}
|
||||||
public async Task<bool> ExecuteErrorSteps(ProgramVM program, int depth = 0, CancellationToken cancellationToken = default)
|
public async Task<bool> ExecuteErrorSteps(ProgramVM program, int depth = 0, CancellationToken cancellationToken = default)
|
||||||
@@ -114,6 +118,7 @@ namespace UIShare
|
|||||||
step.CurrentLoopCount = context.LoopCount;
|
step.CurrentLoopCount = context.LoopCount;
|
||||||
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始,共{context.LoopCount}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始,共{context.LoopCount}次", depth);
|
||||||
index++;
|
index++;
|
||||||
|
await SaveStepRecordAsync(step, depth, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理循环结束
|
// 处理循环结束
|
||||||
@@ -124,6 +129,7 @@ namespace UIShare
|
|||||||
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth: depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth: depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
index++;
|
index++;
|
||||||
|
await SaveStepRecordAsync(step, depth, true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +144,7 @@ namespace UIShare
|
|||||||
// 继续循环:跳转到循环开始后的第一条指令
|
// 继续循环:跳转到循环开始后的第一条指令
|
||||||
index = context.StartIndex + 1;
|
index = context.StartIndex + 1;
|
||||||
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
||||||
|
await SaveStepRecordAsync(step, depth, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -154,6 +161,7 @@ namespace UIShare
|
|||||||
program.ErrorStepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1;
|
program.ErrorStepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1;
|
||||||
loopStopwatchStack.Pop();
|
loopStopwatchStack.Pop();
|
||||||
}
|
}
|
||||||
|
await SaveStepRecordAsync(step, depth, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +220,7 @@ namespace UIShare
|
|||||||
stepStopwatch.Stop();
|
stepStopwatch.Stop();
|
||||||
step.RunTime = (int)stepStopwatch.ElapsedMilliseconds;
|
step.RunTime = (int)stepStopwatch.ElapsedMilliseconds;
|
||||||
}
|
}
|
||||||
|
await SaveStepRecordAsync(step, depth, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +293,7 @@ namespace UIShare
|
|||||||
step.CurrentLoopCount = context.LoopCount;
|
step.CurrentLoopCount = context.LoopCount;
|
||||||
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始({step.Name}),共{context.LoopCount}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始({step.Name}),共{context.LoopCount}次", depth);
|
||||||
index++;
|
index++;
|
||||||
|
await SaveStepRecordAsync(step, depth, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理循环结束
|
// 处理循环结束
|
||||||
@@ -294,6 +304,7 @@ namespace UIShare
|
|||||||
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth:depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth:depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
index++;
|
index++;
|
||||||
|
await SaveStepRecordAsync(step, depth, false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +319,7 @@ namespace UIShare
|
|||||||
// 继续循环:跳转到循环开始后的第一条指令
|
// 继续循环:跳转到循环开始后的第一条指令
|
||||||
index = context.StartIndex + 1;
|
index = context.StartIndex + 1;
|
||||||
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
||||||
|
await SaveStepRecordAsync(step, depth, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -324,6 +336,7 @@ namespace UIShare
|
|||||||
program.StepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1;
|
program.StepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1;
|
||||||
loopStopwatchStack.Pop();
|
loopStopwatchStack.Pop();
|
||||||
}
|
}
|
||||||
|
await SaveStepRecordAsync(step, depth, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,6 +402,7 @@ namespace UIShare
|
|||||||
_scopedContext.SingleStep = false;
|
_scopedContext.SingleStep = false;
|
||||||
_eventAggregator.GetEvent<RunSingalCompletedEvent>().Publish("Play");
|
_eventAggregator.GetEvent<RunSingalCompletedEvent>().Publish("Play");
|
||||||
}
|
}
|
||||||
|
await SaveStepRecordAsync(step, depth, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,6 +683,61 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将单个步骤的执行结果保存到数据库(测试报告)。
|
||||||
|
/// 同一次运行的所有步骤共享 TestRoundID,导出时按此 Guid 查询。
|
||||||
|
/// </summary>
|
||||||
|
private async Task SaveStepRecordAsync(StepVM step, int depth, bool isErrorStep)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 获取输出参数值
|
||||||
|
string? outputValue = null;
|
||||||
|
if (step.Method != null)
|
||||||
|
{
|
||||||
|
var outputParam = step.Method.Parameters.FirstOrDefault(p => p.Category == ParameterCategory.Output);
|
||||||
|
if (outputParam?.Value != null)
|
||||||
|
outputValue = outputParam.Value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = new TestReportEntity
|
||||||
|
{
|
||||||
|
TestRoundId = TestRoundID,
|
||||||
|
Scope = _systemConfig.Title,
|
||||||
|
FileName = _systemConfig.CurrentADPFile ?? "",
|
||||||
|
StepIndex = step.Index,
|
||||||
|
StepName = step.Name ?? "",
|
||||||
|
StepType = step.StepType ?? "普通步骤",
|
||||||
|
MethodName = step.Method?.Name,
|
||||||
|
MethodFullName = step.Method?.FullName,
|
||||||
|
Result = step.Result switch
|
||||||
|
{
|
||||||
|
-1 => "未执行",
|
||||||
|
0 => "执行中",
|
||||||
|
1 => "成功",
|
||||||
|
2 => "失败",
|
||||||
|
_ => "未知"
|
||||||
|
},
|
||||||
|
RunTimeMs = step.RunTime,
|
||||||
|
OutputValue = outputValue,
|
||||||
|
Depth = depth,
|
||||||
|
IsErrorStep = isErrorStep,
|
||||||
|
LoopRemaining = step.CurrentLoopCount,
|
||||||
|
CreateTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _testReportService.InsertAsync(entity);
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"保存步骤记录失败 [{step.Index}]: {result.Msg}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"保存步骤记录失败 [{step.Index}]: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetAllStepStatus(ObservableCollection<StepVM> StepCollection)
|
public void ResetAllStepStatus(ObservableCollection<StepVM> StepCollection)
|
||||||
{
|
{
|
||||||
foreach (var step in StepCollection)
|
foreach (var step in StepCollection)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace UIShare.GlobalVariable
|
|||||||
public string TSMasterName { get; set; } = "ADP测试上位机";
|
public string TSMasterName { get; set; } = "ADP测试上位机";
|
||||||
public string SubProgramFilePath { get; set; } = @"D:\ADP\子程序\";
|
public string SubProgramFilePath { get; set; } = @"D:\ADP\子程序\";
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string CurrentADPFile { get; set; }
|
||||||
public int PerformanceLevel { get; set; } = 50;
|
public int PerformanceLevel { get; set; } = 50;
|
||||||
public string DefaultProgramFilePath { get; set; } = "";
|
public string DefaultProgramFilePath { get; set; } = "";
|
||||||
public string DefaultBLFFilePath { get; set; } = "";
|
public string DefaultBLFFilePath { get; set; } = "";
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
<ProjectReference Include="..\Common\Common.csproj" />
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||||
<ProjectReference Include="..\Logger\Logger.csproj" />
|
<ProjectReference Include="..\Logger\Logger.csproj" />
|
||||||
|
<ProjectReference Include="..\ORM\ORM.csproj" />
|
||||||
|
<ProjectReference Include="..\Service\Service.csproj" />
|
||||||
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
|
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace UIShare.ViewModelBase
|
|||||||
var dialogParams = new DialogParameters();
|
var dialogParams = new DialogParameters();
|
||||||
dialogParams.Add("Title", "错误");
|
dialogParams.Add("Title", "错误");
|
||||||
dialogParams.Add("Message", Message);
|
dialogParams.Add("Message", Message);
|
||||||
dialogParams.Add("Icon", "info");
|
dialogParams.Add("Icon", "error");
|
||||||
dialogParams.Add("ShowOk", true);
|
dialogParams.Add("ShowOk", true);
|
||||||
_dialogService.ShowDialog("MessageBox", dialogParams, result =>
|
_dialogService.ShowDialog("MessageBox", dialogParams, result =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user