CAN卡添加

This commit is contained in:
hsc
2026-07-01 17:37:30 +08:00
parent f27045153c
commit 63f43853fa
13 changed files with 1841 additions and 881 deletions

View File

@@ -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);

View File

@@ -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
}