CAN卡添加
This commit is contained in:
@@ -182,12 +182,14 @@ namespace MonitorModule.ViewModels
|
||||
channel.Record(time, args.Value);
|
||||
|
||||
// 入队数据库批量写入
|
||||
// CreateTime 使用 args.Time(采样触发时刻),而不是 DateTime.Now(设备响应到达时刻),
|
||||
// 这样即使设备响应有延迟,数据库里的时间戳仍然按设定采样间隔分布。
|
||||
var entity = new MonitorValueEntity
|
||||
{
|
||||
MonitorName = channel.DisplayName,
|
||||
MonitorValue = args.Value,
|
||||
Scope = args.Scope,
|
||||
CreateTime = DateTime.Now,
|
||||
CreateTime = args.Time,
|
||||
IsDel = 0
|
||||
};
|
||||
_insertQueue.Enqueue(entity);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using Logger;
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Logger;
|
||||
using Model.Entity;
|
||||
using ORM;
|
||||
using SqlSugar;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
@@ -12,9 +17,9 @@ using UIShare.ViewModelBase;
|
||||
namespace MonitorModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录界面:用于查看运行目录下 SQL/ADP.db 中的数据。
|
||||
/// 功能:表选择 / 关键字 WHERE 查询 / 分页 / 导出 CSV。
|
||||
/// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份查询状态。
|
||||
/// 记录界面:查询 MonitorValueEntity 表中的历史监测数据。
|
||||
/// 功能:监测项筛选 / 台架筛选 / 日期筛选 / 分页 / 导出 CSV。
|
||||
/// 使用 SqlSugarContext.DbContext 全局单例,与写入端共用同一数据库连接。
|
||||
/// </summary>
|
||||
public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
@@ -30,30 +35,39 @@ namespace MonitorModule.ViewModels
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<string> TableNames
|
||||
/// <summary>监测项名称下拉列表</summary>
|
||||
public ObservableCollection<string> MonitorNames
|
||||
{
|
||||
get => _tableNames;
|
||||
set => SetProperty(ref _tableNames, value);
|
||||
get => _monitorNames;
|
||||
set => SetProperty(ref _monitorNames, value);
|
||||
}
|
||||
|
||||
public string? SelectedTable
|
||||
/// <summary>选中的监测项(null = 全部)</summary>
|
||||
public string? SelectedMonitorName
|
||||
{
|
||||
get => _selectedTable;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedTable, value))
|
||||
{
|
||||
PageIndex = 1;
|
||||
Query();
|
||||
}
|
||||
}
|
||||
get => _selectedMonitorName;
|
||||
set => SetProperty(ref _selectedMonitorName, value);
|
||||
}
|
||||
|
||||
// 用户填写的 WHERE 子句(不带 WHERE 关键字),例如:Status='OK' AND Id>10
|
||||
public string WhereClause
|
||||
/// <summary>台架(作用域)下拉列表</summary>
|
||||
public ObservableCollection<string> ScopeNames
|
||||
{
|
||||
get => _whereClause;
|
||||
set => SetProperty(ref _whereClause, value);
|
||||
get => _scopeNames;
|
||||
set => SetProperty(ref _scopeNames, value);
|
||||
}
|
||||
|
||||
/// <summary>选中的台架(null = 全部)</summary>
|
||||
public string? SelectedScope
|
||||
{
|
||||
get => _selectedScope;
|
||||
set => SetProperty(ref _selectedScope, value);
|
||||
}
|
||||
|
||||
/// <summary>查询日期(null = 全部日期)</summary>
|
||||
public DateTime? SelectedDate
|
||||
{
|
||||
get => _selectedDate;
|
||||
set => SetProperty(ref _selectedDate, value);
|
||||
}
|
||||
|
||||
public DataTable ResultTable
|
||||
@@ -111,40 +125,38 @@ namespace MonitorModule.ViewModels
|
||||
|
||||
#region 命令
|
||||
public ICommand LoadedCommand { get; }
|
||||
public ICommand RefreshTablesCommand { get; }
|
||||
public ICommand QueryCommand { get; }
|
||||
public ICommand ClearFilterCommand { get; }
|
||||
public ICommand FirstPageCommand { get; }
|
||||
public ICommand PrevPageCommand { get; }
|
||||
public ICommand NextPageCommand { get; }
|
||||
public ICommand LastPageCommand { get; }
|
||||
public ICommand ExportCsvCommand { get; }
|
||||
// 双击展开/折叠:与 MonitorView/AutomatedTestingView 共用
|
||||
public ICommand RefreshCommand { get; }
|
||||
#endregion
|
||||
|
||||
#region 私有字段
|
||||
// 数据库相对路径:运行目录\SQL\ADP.db(数据库未就绪时容错处理,不抛异常)
|
||||
private static readonly string DbFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SQL");
|
||||
private static readonly string DbPath = Path.Combine(DbFolder, "ADP.db");
|
||||
private static readonly string ConnStr = $"Data Source={DbPath};Version=3;";
|
||||
private bool IsInitiated = false;
|
||||
private IScopedProvider _scope;
|
||||
private string _testStatus = string.Empty;
|
||||
private ObservableCollection<string> _tableNames = new();
|
||||
private string? _selectedTable;
|
||||
private string _whereClause = string.Empty;
|
||||
private ObservableCollection<string> _monitorNames = new();
|
||||
private string? _selectedMonitorName;
|
||||
private ObservableCollection<string> _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" });
|
||||
private string? _selectedScope = "全部";
|
||||
private DateTime? _selectedDate;
|
||||
private DataTable _resultTable = new();
|
||||
private string _statusMessage = "未连接";
|
||||
private int _pageIndex = 1;
|
||||
private int _pageSize = 50;
|
||||
private long _totalCount;
|
||||
private bool IsInitiated = false;
|
||||
#endregion
|
||||
|
||||
public RecordViewModel(IContainerExtension container) : base(container)
|
||||
{
|
||||
_globalInfo = container.Resolve<GlobalInfo>();
|
||||
LoadedCommand = new DelegateCommand(LoadTables);
|
||||
RefreshTablesCommand = new DelegateCommand(LoadTables);
|
||||
LoadedCommand = new DelegateCommand(LoadFilterOptions);
|
||||
QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); });
|
||||
ClearFilterCommand = new DelegateCommand(ClearFilter);
|
||||
FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } });
|
||||
PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } });
|
||||
NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } });
|
||||
@@ -158,105 +170,136 @@ namespace MonitorModule.ViewModels
|
||||
_scope?.Dispose();
|
||||
}
|
||||
|
||||
#region 数据库操作
|
||||
private SqlSugarClient CreateClient()
|
||||
#region 筛选条件加载
|
||||
/// <summary>
|
||||
/// 加载筛选条件:监测项通过反射发现(与 MonitorViewModel 一致),台架从数据库查询。
|
||||
/// </summary>
|
||||
private void LoadFilterOptions()
|
||||
{
|
||||
return new SqlSugarClient(new ConnectionConfig
|
||||
{
|
||||
DbType = SqlSugar.DbType.Sqlite,
|
||||
ConnectionString = ConnStr,
|
||||
IsAutoCloseConnection = true,
|
||||
InitKeyType = InitKeyType.Attribute
|
||||
});
|
||||
// 监测项:反射发现当前作用域所有带 [Monitorable] 的设备方法
|
||||
DiscoverMonitorNames();
|
||||
|
||||
Query();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取数据库中的所有用户表。数据库不存在时不报错,仅在状态栏提示。
|
||||
/// 反射扫描当前作用域 DeviceManager 中所有带 [Monitorable] 特性的设备方法,
|
||||
/// 生成与 MonitorViewModel 完全一致的 DisplayName 列表,填充到下拉框。
|
||||
/// 这样即使数据库还没数据,用户也能看到所有可筛选的监测项。
|
||||
/// </summary>
|
||||
private void LoadTables()
|
||||
private void DiscoverMonitorNames()
|
||||
{
|
||||
MonitorNames = new ObservableCollection<string>();
|
||||
|
||||
if (_scope == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(DbFolder)) Directory.CreateDirectory(DbFolder);
|
||||
if (!File.Exists(DbPath))
|
||||
var deviceManager = _scope.Resolve<DeviceManager>();
|
||||
if (deviceManager?.DeviceMap == null || deviceManager.DeviceMap.Count == 0)
|
||||
{
|
||||
TableNames = new ObservableCollection<string>();
|
||||
SelectedTable = null;
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
StatusMessage = $"数据库尚未创建:{DbPath}";
|
||||
StatusMessage = "当前工位无可用设备";
|
||||
return;
|
||||
}
|
||||
|
||||
using var db = CreateClient();
|
||||
var tables = db.DbMaintenance.GetTableInfoList(false)
|
||||
.Select(t => t.Name)
|
||||
.OrderBy(n => n)
|
||||
.ToList();
|
||||
// 构建 设备实例 → 硬件指纹 的反向查找表
|
||||
var instanceToFp = new Dictionary<object, string>(ReferenceEqualityComparer.Instance);
|
||||
foreach (var poolEntry in _globalInfo.HardwarePool)
|
||||
{
|
||||
var lazy = poolEntry.Value;
|
||||
if (lazy.IsValueCreated && lazy.Value != null)
|
||||
instanceToFp[lazy.Value] = poolEntry.Key;
|
||||
}
|
||||
|
||||
TableNames = new ObservableCollection<string>(tables);
|
||||
if (tables.Count == 0)
|
||||
var names = new List<string>();
|
||||
|
||||
foreach (var kvp in deviceManager.DeviceMap)
|
||||
{
|
||||
SelectedTable = null;
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
StatusMessage = "数据库已连接,但暂无数据表";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"已连接:{DbPath}({tables.Count} 张表)";
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !tables.Contains(SelectedTable))
|
||||
string deviceName = kvp.Key;
|
||||
var device = kvp.Value;
|
||||
var deviceType = device.GetType();
|
||||
|
||||
// 只列出在硬件指纹池中注册的设备
|
||||
if (!instanceToFp.ContainsKey(device))
|
||||
continue;
|
||||
|
||||
var methods = deviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m =>
|
||||
{
|
||||
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
|
||||
if (m.ReturnType != typeof(Task<string>)) return false;
|
||||
var parms = m.GetParameters();
|
||||
return parms.Length == 0 ||
|
||||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
|
||||
});
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
SelectedTable = tables[0]; // setter 会触发 Query
|
||||
}
|
||||
else
|
||||
{
|
||||
Query();
|
||||
var attr = method.GetCustomAttribute<MonitorableAttribute>();
|
||||
string displayName = !string.IsNullOrEmpty(attr?.Description)
|
||||
? $"{deviceName}.{attr.Description}"
|
||||
: $"{deviceName}.{method.Name}";
|
||||
names.Add(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
names.Sort();
|
||||
MonitorNames = new ObservableCollection<string>(names);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,$"加载数据表失败:{ex.Message}");
|
||||
StatusMessage = $"加载失败:{ex.Message}";
|
||||
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"反射发现监测项失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>清除所有筛选条件</summary>
|
||||
private void ClearFilter()
|
||||
{
|
||||
SelectedMonitorName = null;
|
||||
SelectedScope = "全部";
|
||||
SelectedDate = null;
|
||||
PageIndex = 1;
|
||||
Query();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 查询
|
||||
/// <summary>
|
||||
/// 执行分页查询。WHERE 为空则查询全部。
|
||||
/// 按当前筛选条件分页查询 MonitorValueEntity。
|
||||
/// </summary>
|
||||
private void Query()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
||||
{
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var db = CreateClient();
|
||||
string table = $"[{SelectedTable}]";
|
||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
||||
var db = SqlSugarContext.DbContext;
|
||||
|
||||
// COUNT 总数
|
||||
var countSql = $"SELECT COUNT(*) FROM {table} WHERE {where}";
|
||||
TotalCount = db.Ado.GetLong(countSql);
|
||||
// 预计算日期范围,避免在表达式树中访问可空属性
|
||||
DateTime? dateStart = SelectedDate?.Date;
|
||||
DateTime? dateEnd = dateStart?.AddDays(1);
|
||||
|
||||
var query = db.Queryable<MonitorValueEntity>()
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
|
||||
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
|
||||
.OrderBy(x => x.CreateTime, OrderByType.Desc);
|
||||
|
||||
TotalCount = query.Count();
|
||||
|
||||
// 修正越界
|
||||
if (PageIndex > TotalPages) PageIndex = TotalPages;
|
||||
if (PageIndex < 1) PageIndex = 1;
|
||||
|
||||
int offset = (PageIndex - 1) * PageSize;
|
||||
var dataSql = $"SELECT * FROM {table} WHERE {where} LIMIT {PageSize} OFFSET {offset}";
|
||||
ResultTable = db.Ado.GetDataTable(dataSql);
|
||||
int skip = (PageIndex - 1) * PageSize;
|
||||
ResultTable = query
|
||||
.Skip(skip)
|
||||
.Take(PageSize)
|
||||
.ToDataTable();
|
||||
|
||||
StatusMessage = $"表 [{SelectedTable}] 共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
|
||||
StatusMessage = $"共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(TestStatus,$"查询失败:{ex.Message}");
|
||||
LoggerHelper.ErrorWithNotify(TestStatus, $"查询失败:{ex.Message}");
|
||||
StatusMessage = $"查询失败:{ex.Message}";
|
||||
ResultTable = new DataTable();
|
||||
TotalCount = 0;
|
||||
@@ -266,36 +309,42 @@ namespace MonitorModule.ViewModels
|
||||
|
||||
#region 导出 CSV
|
||||
/// <summary>
|
||||
/// 按当前 WHERE 条件导出全部结果到 CSV(不分页)。
|
||||
/// 按当前筛选条件导出全部结果到 CSV(不分页)。
|
||||
/// </summary>
|
||||
private void ExportCsv()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
||||
{
|
||||
StatusMessage = "无可导出数据";
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
FileName = $"{SelectedTable}_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
|
||||
Title = $"导出 [{SelectedTable}] 为 CSV"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
try
|
||||
{
|
||||
using var db = CreateClient();
|
||||
string table = $"[{SelectedTable}]";
|
||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
||||
var sql = $"SELECT * FROM {table} WHERE {where}";
|
||||
var dt = db.Ado.GetDataTable(sql);
|
||||
var db = SqlSugarContext.DbContext;
|
||||
|
||||
DateTime? dateStart = SelectedDate?.Date;
|
||||
DateTime? dateEnd = dateStart?.AddDays(1);
|
||||
|
||||
var query = db.Queryable<MonitorValueEntity>()
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
||||
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
|
||||
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
|
||||
.OrderBy(x => x.CreateTime, OrderByType.Desc);
|
||||
|
||||
var dt = query.ToDataTable();
|
||||
if (dt.Rows.Count == 0)
|
||||
{
|
||||
StatusMessage = "无可导出数据";
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
FileName = $"MonitorData_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
|
||||
Title = "导出监测数据为 CSV"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
WriteCsv(dlg.FileName, dt);
|
||||
|
||||
StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
|
||||
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
||||
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出监测数据至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -333,7 +382,6 @@ namespace MonitorModule.ViewModels
|
||||
if (string.IsNullOrEmpty(TestStatus)) return;
|
||||
_globalInfo.CurrentScope = TestStatus;
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -341,14 +389,14 @@ namespace MonitorModule.ViewModels
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
if (!IsInitiated&&navigationContext.Parameters.ContainsKey("Name"))
|
||||
if (!IsInitiated && navigationContext.Parameters.ContainsKey("Name"))
|
||||
{
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
_scope = _globalInfo.ScopeDic[TestStatus];
|
||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||
IsInitiated = true;
|
||||
}
|
||||
LoadTables();
|
||||
LoadFilterOptions();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user