using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using HMI.Base; using HMI.Core.Entity; using HMI.Core.Manager; using HMI.Core.Setting; using HMI.Core.Util; using HMI.UI.Util; using Microsoft.Win32; using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; using NPOI.Util; using NPOI.XSSF.UserModel; using Panuon.WPF.UI; using System.Collections.ObjectModel; using System.Data; using System.IO; using System.Windows; namespace Plugin.QualityReport { public class CMKData { public object[] values = new object[5]; public object d1 { get => values[0]; set => values[0] = value; } public object d2 { get => values[1]; set => values[1] = value; } public object d3 { get => values[2]; set => values[2] = value; } public object d4 { get => values[3]; set => values[3] = value; } public object d5 { get => values[4]; set => values[4] = value; } } public class MSAData { public object[] values = new object[9]; public object d1 { get => values[0]; set => values[0] = value; } public object d2 { get => values[1]; set => values[1] = value; } public object d3 { get => values[2]; set => values[2] = value; } public object d4 { get => values[3]; set => values[3] = value; } public object d5 { get => values[4]; set => values[4] = value; } public object d6 { get => values[5]; set => values[5] = value; } public object d7 { get => values[6]; set => values[6] = value; } public object d8 { get => values[7]; set => values[7] = value; } public object d9 { get => values[8]; set => values[8] = value; } } public class RepeatData { public string Title { get; set; } public object[] values = new object[10]; public object d1 { get => values[0]; set => values[0] = value; } public object d2 { get => values[1]; set => values[1] = value; } public object d3 { get => values[2]; set => values[2] = value; } public object d4 { get => values[3]; set => values[3] = value; } public object d5 { get => values[4]; set => values[4] = value; } public object d6 { get => values[5]; set => values[5] = value; } public object d7 { get => values[6]; set => values[6] = value; } public object d8 { get => values[7]; set => values[7] = value; } public object d9 { get => values[8]; set => values[8] = value; } public object d10 { get => values[9]; set => values[9] = value; } } /// /// QualityReport.xaml 的交互逻辑 /// [ObservableObject] public partial class QualityReport : SimpleMaskControl { [ObservableProperty] private string model; [ObservableProperty] private List models; [ObservableProperty] private DateTime startTimeCMK; [ObservableProperty] private DateTime endTimeCMK; [ObservableProperty] private string testItemCMK = "TestItem"; [ObservableProperty] private List datasourceCMK = null; [ObservableProperty] private List datasourceMSA = null; [ObservableProperty] private List datasourceRepeat = null; [ObservableProperty] private string snr; [ObservableProperty] private ObservableCollection selectedSnr = new ObservableCollection { }; [ObservableProperty] private ObservableCollection selectedSnrRepeat = new ObservableCollection { }; public QualityReport() { DataContext = this; InitializeComponent(); StartTimeCMK = DateTime.Today; EndTimeCMK = DateTime.Today.AddDays(1); } public override void Active() { base.Active(); Models = ProductManager.GetProducts>() ?? new List(); } private string[] GetFiles(DateTime? startTime, DateTime? endTime) { string dir = ParamSetting.DataLogFolder; string startName = $"Data_{startTime?.ToString("yyyyMMdd")}"; string endName = $"Data_{endTime?.ToString("yyyyMMdd")}"; var oldFiles = Directory.GetFiles(dir, "Data_*.db"); oldFiles = oldFiles.Where(file => { var fileName = Path.GetFileNameWithoutExtension(file); bool flag = true; flag &= (string.Compare(fileName, startName) == 1 || fileName == startName); flag &= (string.Compare(endName, fileName) == 1 || fileName == endName); return flag; }).ToArray(); return oldFiles; } public DataTable SelectByFiles(string[] files, Func ConcatAction, SortParam sorter, PageParam page) { if (files == null || files.Length <= 0) { return new DataTable(); } if (files.Length < Sqlite3Helper.MAX_ATTACHED) { using (var helper = new Sqlite3Helper(files[0])) { string[] sqls = new string[files.Length]; for (int i = 0; i < files.Length; i++) { var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'"; helper.Execute(attach); sqls[i] = ConcatAction(files[i], $"db{i}"); } string sql = string.Join(" UNION ALL ", sqls); sql = $"SELECT * FROM ({sql}) as _temp"; if (sorter != null) { sql = $"{sql} ORDER BY {sorter.field} {sorter.sort}"; } if (page != null) { sql = $"{sql} LIMIT {page.limit} OFFSET {page.offset}"; } return helper.GetDataTable(sql); } } else { DataTable dt = new DataTable(); Sqlite3Helper helper = null; string[] sqls = new string[Sqlite3Helper.MAX_ATTACHED]; var fff = () => { if (helper != null) { string sql = string.Join(" UNION ALL ", sqls.Where(s => s != null)); sql = $"SELECT * FROM ({sql}) as _temp"; dt.Merge(helper.GetDataTable(sql)); sqls = new string[Sqlite3Helper.MAX_ATTACHED]; helper.Dispose(); helper = null; } }; try { for (int i = 0; i < files.Length; i++) { if (i % Sqlite3Helper.MAX_ATTACHED == 0) { fff(); helper = new Sqlite3Helper(files[i]); } var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'"; helper.Execute(attach); sqls[i % Sqlite3Helper.MAX_ATTACHED] = ConcatAction(files[i], $"db{i}"); } fff(); if (dt.Rows.Count > 0) { if (sorter != null) { dt.DefaultView.Sort = $"{sorter.field} {sorter.sort}"; } if (page != null) { var rows = dt.Rows.Cast(); var curRows = rows.Skip(page.offset).Take(page.limit).ToArray(); var sb = dt.Clone(); if (dt.Rows.Count > page.offset) { if (dt.Rows.Count < page.limit) { sb = dt; } else { for (int i = page.offset; i < page.offset + page.limit; i++) { sb.ImportRow(dt.Rows[i]); } } } dt = sb; // dt = new DataTable(curRows); } } } finally { helper?.Dispose(); } return dt; } } [RelayCommand] public void QueryCMK() { try { if (string.IsNullOrEmpty(Model) || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null) { ToastHelper.Error("请输入型号、测试项、开始时间和结束时间!"); return; } string[] files = GetFiles(StartTimeCMK, EndTimeCMK); DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' AND step='{TestItemCMK}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'"; }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 50, offset = 0 }); List list = new List(); object[] values = null; for (int i = 0; i < dt.Rows.Count; i++) { if (i % 5 == 0) { values = new object[5]; list.Add(new CMKData { values = values }); } values[i % 5] = dt.Rows[i]["value"]; } DatasourceCMK = list; } catch (Exception ex) { } } [RelayCommand] public void ExportCMK() { try { if (string.IsNullOrEmpty(Model) || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null) { ToastHelper.Error("请输入型号、测试项、开始时间和结束时间!"); return; } string[] files = GetFiles(StartTimeCMK, EndTimeCMK); DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' AND step='{TestItemCMK}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'"; }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 50, offset = 0 }); List list = new List(); object[] values = null; for (int i = 0; i < dt.Rows.Count; i++) { if (i % 5 == 0) { values = new object[5]; list.Add(new CMKData { values = values }); } values[i % 5] = dt.Rows[i]["value"]; } using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "CMK模板.xls"))) { IWorkbook workbook = new HSSFWorkbook(fs); ISheet sheet = workbook.GetSheet("CMK"); for (int i = 0; i < list.Count; i++) { int num = i + 9; if (num > 24) { break; } IRow row = sheet.GetRow(num); CMKData data = list[i]; for (int j = 0; j < 5; j++) { if (data.values[j] != null) { row.GetCell(j).SetCellValue(double.Parse(data.values[j].ToString())); } } } sheet.ForceFormulaRecalculation = true;//执行公式 //方法二 //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll(); SaveFileDialog sfd = new SaveFileDialog { Filter = "Excel文件 (*.xls)|*.xls", }; if (sfd.ShowDialog() == true) { using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate)) { workbook.Write(sfs); } } } } catch (Exception ex) { } } [RelayCommand] private void AddSnr() { if (SelectedSnr.Count >= 9) { ToastHelper.Warning("最多选取9个产品"); return; } if (SelectedSnr.Contains(Snr)) { ToastHelper.Warning($"条码:[{Snr}] 已被选中"); return; } SelectedSnr.Add(Snr); } [RelayCommand] private void AddSnr2Repeat() { if (SelectedSnrRepeat.Count >= 10) { ToastHelper.Warning("最多选取10个产品"); return; } if (SelectedSnrRepeat.Contains(Snr)) { ToastHelper.Warning($"条码:[{Snr}] 已被选中"); return; } SelectedSnrRepeat.Add(Snr); } [RelayCommand] public void RemoveSN(object obj) { SelectedSnr.Remove(obj.ToString()); } [RelayCommand] public void QueryMSA() { try { if (string.IsNullOrEmpty(Model) || SelectedSnr.Count <= 0 || StartTimeCMK == null || EndTimeCMK == null) { ToastHelper.Error("请输入型号、选中的条码、开始时间和结束时间!"); return; } string[] files = GetFiles(StartTimeCMK, EndTimeCMK); Dictionary> map = new Dictionary>(); foreach (var sn in SelectedSnr) { List datas = new List(); DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT result,time FROM {db}.testResult WHERE model='{Model}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'"; }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 10, offset = 0 }); datas = dt.Rows.Cast().Select(r => string.Equals(r["result"].ToString(), "passed", StringComparison.OrdinalIgnoreCase) || string.Equals(r["result"].ToString(), "ok", StringComparison.OrdinalIgnoreCase) ? 1 : 0) .ToList(); map[sn] = datas; } List temp = new List() { new MSAData() }; int i = 0; foreach (var key in map.Keys) { temp[0].values[i] = key; var vals = map[key]; for (int j = 0; j < vals.Count; j++) { if (temp.Count < j + 2) { temp.Add(new MSAData()); } temp[j + 1].values[i] = vals[j]; } i++; } DatasourceMSA = temp; //RefreshMSAGrid(map); } catch (Exception ex) { } } [RelayCommand] public void ExportMSA() { try { if (string.IsNullOrEmpty(Model) || SelectedSnr.Count <= 0 || StartTimeCMK == null || EndTimeCMK == null) { ToastHelper.Error("请输入型号、选中的条码、开始时间和结束时间!"); return; } string[] files = GetFiles(StartTimeCMK, EndTimeCMK); Dictionary> map = new Dictionary>(); foreach (var sn in SelectedSnr) { List datas = new List(); DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT result,time FROM {db}.testResult WHERE model='{Model}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'"; }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 10, offset = 0 }); datas = dt.Rows.Cast().Select(r => string.Equals(r["result"].ToString(), "passed", StringComparison.OrdinalIgnoreCase) || string.Equals(r["result"].ToString(), "ok", StringComparison.OrdinalIgnoreCase) ? 1 : 0) .ToList(); map[sn] = datas; } using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "MSA模板.xlsx"))) { IWorkbook workbook = new XSSFWorkbook(fs); ISheet sheet = workbook.GetSheet("数据输入"); int column = 2; foreach (string snr in map.Keys) { if (column > 10) { break; } var datas = map[snr]; sheet.GetRow(9).GetCell(column).SetCellValue(snr); for (int i = 0; i < datas.Count; i++) { if (i > 49) break; sheet.GetRow(i + 11).GetCell(column).SetCellValue(datas[i]); } column++; } sheet.ForceFormulaRecalculation = true;//执行公式 //方法二 //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll(); SaveFileDialog sfd = new SaveFileDialog { Filter = "Excel文件 (*.xlsx)|*.xlsx", }; if (sfd.ShowDialog() == true) { using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate)) { workbook.Write(sfs); } } } } catch (Exception ex) { } } [RelayCommand] public void QueryRepeat() { try { if (string.IsNullOrEmpty(Model) || SelectedSnrRepeat.Count <= 0 || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null) { ToastHelper.Error("请输入型号、选中的条码、测试项、开始时间和结束时间!"); return; } string[] files = GetFiles(StartTimeCMK, EndTimeCMK); Dictionary> map = new Dictionary>(); foreach (var sn in SelectedSnrRepeat) { DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' And step='{TestItemCMK}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'"; }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 9, offset = 0 }); var datas = dt.Rows.Cast().Select(r => Math.Round(double.Parse(r["value"].ToString()), 3, MidpointRounding.AwayFromZero)).ToList(); map[sn] = datas; } List temp = new List() { new RepeatData { Title = "条码" } }; int i = 0; foreach (var key in map.Keys) { temp[0].values[i] = key; var vals = map[key]; for (int j = 0; j < vals.Count; j++) { if (temp.Count < j + 2) { temp.Add(new RepeatData()); } temp[j + 1].values[i] = vals[j]; } i++; } DatasourceRepeat = temp; } catch (Exception ex) { log.Error("重复性查询失败", ex); MsgBoxHelper2.Error($"重复性查询失败: {ex.Message}"); } } [RelayCommand] public void ExportRepeat() { try { if (string.IsNullOrEmpty(Model) || SelectedSnrRepeat.Count <= 0 || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null) { ToastHelper.Error("请输入型号、选中的条码、测试项、开始时间和结束时间!"); return; } string[] files = GetFiles(StartTimeCMK, EndTimeCMK); Dictionary> map = new Dictionary>(); foreach (var sn in SelectedSnrRepeat) { DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' And step='{TestItemCMK}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'"; }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 9, offset = 0 }); var datas = dt.Rows.Cast().Select(r => Math.Round(double.Parse(r["value"].ToString()), 3, MidpointRounding.AwayFromZero)).ToList(); map[sn] = datas; } List temp = new List() { new RepeatData() }; int i = 0; foreach (var key in map.Keys) { temp[0].values[i] = key; var vals = map[key]; for (int j = 0; j < vals.Count; j++) { if (temp.Count < j + 2) { temp.Add(new RepeatData()); } temp[j + 1].values[i] = vals[j]; } i++; } using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "重复性模板.xls"))) { IWorkbook workbook = new HSSFWorkbook(fs); ISheet sheet = workbook.GetSheet("sheet1"); sheet.GetRow(10).GetCell(2).SetCellValue(TestItemCMK); int column = 2; foreach (string snr in map.Keys) { if (column > 11) { break; } var datas = map[snr]; sheet.GetRow(11).GetCell(column).SetCellValue(snr); for (int j = 0; j < datas.Count; j++) { if (j > 8) break; int row = j + 13; if (j > 2 && j <= 5) { row = j + 15; } else if (j > 5) { row = j + 17; } sheet.GetRow(row).GetCell(column).SetCellValue(datas[j]); } column++; } sheet.ForceFormulaRecalculation = true;//执行公式 //方法二 //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll(); SaveFileDialog sfd = new SaveFileDialog { Filter = "Excel文件 (*.xls)|*.xls", }; if (sfd.ShowDialog() == true) { using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate)) { workbook.Write(sfs); ToastHelper.Success("导出成功"); } } } } catch (Exception ex) { log.Error("重复性导出失败", ex); MsgBoxHelper2.Error($"导出失败: {ex.Message}"); } } private void SearchTextChanged(object sender, Panuon.WPF.SearchTextChangedRoutedEventArgs e) { SearchBox box = (SearchBox)sender; try { string[] files = GetFiles(StartTimeCMK, EndTimeCMK).OrderBy(s => s).Take(7).ToArray(); if (box.Tag.ToString() == "Step") { DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT DISTINCT step FROM {db}.serialData WHERE model='{Model}' And step LIKE '%{TestItemCMK}%'"; }, null, null); var datas = dt.Rows.Cast().Select(r => r["step"].ToString()).Distinct().ToList(); box.ItemsSource = datas; } else { DataTable dt = SelectByFiles(files, (file, db) => { return $"SELECT DISTINCT sn FROM {db}.testResult WHERE model='{Model}' And sn LIKE '%{Snr}%'"; }, null, null); var datas = dt.Rows.Cast().Select(r => r["sn"].ToString()).Distinct().ToList(); box.ItemsSource = datas; } } catch { box.ItemsSource = new string[] { }; } } private void Button_Click(object sender, RoutedEventArgs e) { } } }