using HandyControl.Controls; using LiveChartsCore; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Painting; using Prism.Mvvm; using SkiaSharp; using System.Collections.ObjectModel; using YY.Admin.Core; using YY.Admin.Core.Entity; using YY.Admin.Core.Services; using YY.Admin.Core.Session; namespace YY.Admin.ViewModels.RubberQuickTest; /// 密炼生产计划班次展开项(桌面端筛选用) public class MixingPlanShiftOption { public string PlanRowId { get; set; } = string.Empty; public string? MachineId { get; set; } public string? MachineName { get; set; } public string ShiftKey { get; set; } = string.Empty; public string ShiftCode { get; set; } = string.Empty; public string ShiftName { get; set; } = string.Empty; public DateTime? OrderDate { get; set; } public string? PlanId { get; set; } public string? OrderNo { get; set; } public string? FormulaName { get; set; } public string? MaterialName { get; set; } public string DisplayText => string.IsNullOrWhiteSpace(OrderNo) ? MaterialName ?? FormulaName ?? string.Empty : $"{OrderNo} | {MaterialName ?? FormulaName}"; } public class QuickTestInspectCellViewModel : BindableBase { public string? DataPointId { get; set; } public string PointName { get; set; } = string.Empty; public decimal? LowerLimit { get; set; } public decimal? UpperLimit { get; set; } private decimal? _value; public decimal? Value { get => _value; set { if (SetProperty(ref _value, value)) ValueChanged?.Invoke(this); } } public event Action? ValueChanged; } public class QuickTestInspectRowViewModel : BindableBase { public string RowNo { get; set; } = string.Empty; public ObservableCollection Cells { get; } = new(); private string _inspectResultText = string.Empty; public string InspectResultText { get => _inspectResultText; set => SetProperty(ref _inspectResultText, value); } public string InspectResultCode { get; private set; } = "0"; public void RecalculateResult() { if (Cells.Count == 0) { InspectResultCode = "0"; InspectResultText = string.Empty; RaisePropertyChanged(nameof(InspectResultText)); return; } if (Cells.Any(c => c.Value == null)) { InspectResultCode = "0"; InspectResultText = "待填写"; RaisePropertyChanged(nameof(InspectResultText)); return; } var pass = Cells.All(c => { if (c.LowerLimit != null && c.Value < c.LowerLimit) return false; if (c.UpperLimit != null && c.Value > c.UpperLimit) return false; return true; }); InspectResultCode = pass ? "1" : "0"; InspectResultText = pass ? "合格" : "不合格"; RaisePropertyChanged(nameof(InspectResultText)); } } /// 胶料快检记录操作台 ViewModel public class RubberQuickTestOperationViewModel : BaseViewModel { private readonly IRubberQuickTestOperationService _operationService; private readonly Random _rnd = new(); private List _allPlans = new(); private List _allShiftOptions = new(); private MesXslRubberQuickTestStd? _currentStd; private const int ChartPointCount = 5; public event Action? InspectColumnsChanged; public RubberQuickTestOperationViewModel( IRubberQuickTestOperationService operationService, IContainerExtension container, IRegionManager regionManager) : base(container, regionManager) { _operationService = operationService; _inspectTimesText = "1"; AddInspectRowCommand = new DelegateCommand(AddInspectRow, () => DataPointColumns.Count > 0); RemoveInspectRowCommand = new DelegateCommand(() => RemoveInspectRow(SelectedInspectRow), () => SelectedInspectRow != null); SaveCommand = new DelegateCommand(async () => await SaveAsync(), () => InspectRows.Count > 0); RefreshPlansCommand = new DelegateCommand(async () => await LoadPlansAsync()); RefreshChartDemoCommand = new DelegateCommand(FillRandomChartData); TemperatureSeries = new ObservableCollection { new LineSeries { Name = "上模温度", Values = UpperTempValues, GeometrySize = 4, LineSmoothness = 0.3 }, new LineSeries { Name = "下模温度", Values = LowerTempValues, GeometrySize = 4, LineSmoothness = 0.3 } }; TorqueSeries = new ObservableCollection { new LineSeries { Name = "S'(dNm)", Values = TorqueValues, GeometrySize = 4, LineSmoothness = 0.3 } }; FillRandomChartData(); _ = LoadPlansAsync(); } /// 中间库未接入时的曲线演示说明 public string ChartDemoHint => "演示数据(中间库未接入,打开页面自动生成;接入后将显示实测曲线)"; public ObservableCollection TemperatureSeries { get; } public ObservableCollection TorqueSeries { get; } public ObservableCollection UpperTempValues { get; } = new(); public ObservableCollection LowerTempValues { get; } = new(); public ObservableCollection TorqueValues { get; } = new(); public Axis[] TemperatureXAxes { get; } = BuildTimeAxis(); public Axis[] TemperatureYAxes { get; } = BuildTempYAxis(); public Axis[] TorqueXAxes { get; } = BuildTimeAxis(); public Axis[] TorqueYAxes { get; } = BuildTorqueYAxis(); public ObservableCollection MachineOptions { get; } = new(); public ObservableCollection ShiftOptions { get; } = new(); public ObservableCollection PlanOptions { get; } = new(); public ObservableCollection DataPointColumns { get; } = new(); public ObservableCollection InspectRows { get; } = new(); private QuickTestInspectRowViewModel? _selectedInspectRow; public QuickTestInspectRowViewModel? SelectedInspectRow { get => _selectedInspectRow; set { if (SetProperty(ref _selectedInspectRow, value)) RemoveInspectRowCommand.RaiseCanExecuteChanged(); } } private DateTime _mixingDate = DateTime.Today; public DateTime MixingDate { get => _mixingDate; set { if (!SetProperty(ref _mixingDate, value)) return; RefreshMachineOptions(); ClearPlanSelection(); } } private string? _selectedMachine; public string? SelectedMachine { get => _selectedMachine; set { if (!SetProperty(ref _selectedMachine, value)) return; RefreshShiftOptions(); ClearPlanSelection(); } } private WorkShiftOption? _selectedShift; public WorkShiftOption? SelectedShift { get => _selectedShift; set { if (!SetProperty(ref _selectedShift, value)) return; RefreshPlanOptions(); ClearPlanSelection(); } } private MixingPlanShiftOption? _selectedPlan; public MixingPlanShiftOption? SelectedPlan { get => _selectedPlan; set { if (!SetProperty(ref _selectedPlan, value)) return; ApplySelectedPlan(); } } public string? ProductionOrderNo => _selectedPlan?.OrderNo; public string? MachineName => _selectedPlan?.MachineName ?? SelectedMachine; public string? WorkShiftDisplay => SelectedShift?.Name ?? string.Empty; public string? RubberMaterialName => _selectedPlan?.MaterialName ?? _selectedPlan?.FormulaName; private string? _trainNo; public string? TrainNo { get => _trainNo; set => SetProperty(ref _trainNo, value); } private string? _inspectTimesText; /// 试验次数(手填正整数) public string? InspectTimesText { get => _inspectTimesText; set => SetProperty(ref _inspectTimesText, value); } private double _upperMoldTemp; public double UpperMoldTemp { get => _upperMoldTemp; set => SetProperty(ref _upperMoldTemp, value); } private double _lowerMoldTemp; public double LowerMoldTemp { get => _lowerMoldTemp; set => SetProperty(ref _lowerMoldTemp, value); } private double _torqueS; public double TorqueS { get => _torqueS; set => SetProperty(ref _torqueS, value); } public DelegateCommand AddInspectRowCommand { get; } public DelegateCommand RemoveInspectRowCommand { get; } public DelegateCommand SaveCommand { get; } public DelegateCommand RefreshPlansCommand { get; } public DelegateCommand RefreshChartDemoCommand { get; } private async Task LoadPlansAsync() { IsLoading = true; try { _allPlans = await _operationService.GetMixingProductionPlansAsync(); BuildAllShiftOptions(); RefreshMachineOptions(); Growl.Success("密炼生产计划已刷新"); } catch (Exception ex) { Growl.Error($"加载密炼生产计划失败:{ex.Message}"); } finally { IsLoading = false; } } private void BuildAllShiftOptions() { _allShiftOptions.Clear(); foreach (var row in _allPlans) { if (string.IsNullOrWhiteSpace(row.PlanId)) continue; var shiftCode = row.ShiftFlag?.ToString() ?? string.Empty; _allShiftOptions.Add(new MixingPlanShiftOption { PlanRowId = row.Id ?? string.Empty, MachineId = row.MachineId, MachineName = row.MachineName, ShiftKey = shiftCode, ShiftCode = shiftCode, ShiftName = row.ShiftFlagText, OrderDate = row.PlanDate, PlanId = row.PlanId, OrderNo = row.OrderNo, FormulaName = row.FormulaName, MaterialName = row.MaterialName }); } } private IEnumerable FilteredByDate => _allShiftOptions.Where(o => o.OrderDate?.Date == MixingDate.Date); private void RefreshMachineOptions() { MachineOptions.Clear(); foreach (var name in FilteredByDate.Select(o => o.MachineName).Where(n => !string.IsNullOrWhiteSpace(n)).Distinct().OrderBy(n => n)) MachineOptions.Add(name!); if (SelectedMachine != null && !MachineOptions.Contains(SelectedMachine)) SelectedMachine = null; if (SelectedMachine == null && MachineOptions.Count > 0) SelectedMachine = MachineOptions[0]; else RefreshShiftOptions(); } private void RefreshShiftOptions() { ShiftOptions.Clear(); var machine = SelectedMachine; var shifts = FilteredByDate .Where(o => string.IsNullOrWhiteSpace(machine) || o.MachineName == machine) .GroupBy(o => o.ShiftCode) .Select(g => g.First()) .OrderBy(o => o.ShiftCode); foreach (var s in shifts) ShiftOptions.Add(new WorkShiftOption(s.ShiftCode, s.ShiftName)); if (SelectedShift != null && !ShiftOptions.Any(x => x.Code == SelectedShift.Code)) SelectedShift = null; if (SelectedShift == null && ShiftOptions.Count > 0) SelectedShift = ShiftOptions[0]; else RefreshPlanOptions(); } private void RefreshPlanOptions() { PlanOptions.Clear(); var machine = SelectedMachine; var shiftCode = SelectedShift?.Code; foreach (var o in FilteredByDate .Where(x => string.IsNullOrWhiteSpace(machine) || x.MachineName == machine) .Where(x => string.IsNullOrWhiteSpace(shiftCode) || x.ShiftCode == shiftCode) .OrderBy(x => x.OrderNo)) PlanOptions.Add(o); if (SelectedPlan != null && PlanOptions.All(p => p.PlanId != SelectedPlan.PlanId)) SelectedPlan = null; if (SelectedPlan == null && PlanOptions.Count > 0) SelectedPlan = PlanOptions[0]; } private void ClearPlanSelection() { _selectedPlan = null; RaisePropertyChanged(nameof(SelectedPlan)); RaisePropertyChanged(nameof(ProductionOrderNo)); RaisePropertyChanged(nameof(MachineName)); RaisePropertyChanged(nameof(WorkShiftDisplay)); RaisePropertyChanged(nameof(RubberMaterialName)); ClearStdAndResults(); } private void ApplySelectedPlan() { RaisePropertyChanged(nameof(ProductionOrderNo)); RaisePropertyChanged(nameof(MachineName)); RaisePropertyChanged(nameof(WorkShiftDisplay)); RaisePropertyChanged(nameof(RubberMaterialName)); _ = LoadStdAsync(); } private async Task LoadStdAsync() { DataPointColumns.Clear(); InspectRows.Clear(); _currentStd = null; InspectColumnsChanged?.Invoke(); var rubberName = RubberMaterialName; if (string.IsNullOrWhiteSpace(rubberName)) return; IsLoading = true; try { _currentStd = await _operationService.GetStdByRubberMaterialNameAsync(rubberName); if (_currentStd?.LineList == null || _currentStd.LineList.Count == 0) { Growl.Warning($"未找到胶料「{rubberName}」的使用中且已批准的快检实验标准"); return; } foreach (var line in _currentStd.LineList.OrderBy(l => l.SortNo ?? 0)) DataPointColumns.Add(line); InspectColumnsChanged?.Invoke(); AddInspectRow(); AddInspectRowCommand.RaiseCanExecuteChanged(); } catch (Exception ex) { Growl.Error($"加载实验标准失败:{ex.Message}"); } finally { IsLoading = false; } } private void ClearStdAndResults() { DataPointColumns.Clear(); InspectRows.Clear(); _currentStd = null; InspectColumnsChanged?.Invoke(); } /// 打开页面或中间库无数据时填充曲线演示数据(5 点对应 0~2min) private void FillRandomChartData() { UpperTempValues.Clear(); LowerTempValues.Clear(); TorqueValues.Clear(); for (int i = 0; i < ChartPointCount; i++) { var upper = 192 + _rnd.NextDouble() * 8; var lower = upper - 2 - _rnd.NextDouble() * 2; var torque = Math.Min(14, i * 2.5 + _rnd.NextDouble() * 2.5); UpperTempValues.Add(Math.Round(upper, 2)); LowerTempValues.Add(Math.Round(lower, 2)); TorqueValues.Add(Math.Round(torque, 2)); } UpperMoldTemp = UpperTempValues[^1]; LowerMoldTemp = LowerTempValues[^1]; TorqueS = TorqueValues[^1]; } private void AddInspectRow() { var rowIndex = InspectRows.Count + 1; var row = new QuickTestInspectRowViewModel { RowNo = $"{rowIndex}_1" }; foreach (var col in DataPointColumns) { var cell = new QuickTestInspectCellViewModel { DataPointId = col.DataPointId, PointName = col.PointName ?? string.Empty, LowerLimit = col.LowerLimit, UpperLimit = col.UpperLimit }; cell.ValueChanged += _ => row.RecalculateResult(); row.Cells.Add(cell); } row.RecalculateResult(); InspectRows.Add(row); SaveCommand.RaiseCanExecuteChanged(); } private void RemoveInspectRow(QuickTestInspectRowViewModel? row) { if (row == null) return; InspectRows.Remove(row); SelectedInspectRow = null; SaveCommand.RaiseCanExecuteChanged(); } private async Task SaveAsync() { if (string.IsNullOrWhiteSpace(TrainNo)) { Growl.Warning("请填写车次"); return; } if (string.IsNullOrWhiteSpace(ProductionOrderNo)) { Growl.Warning("请选择密炼计划以带出生产订单号"); return; } if (string.IsNullOrWhiteSpace(RubberMaterialName)) { Growl.Warning("请先选择密炼计划以带出胶料名称"); return; } if (_currentStd == null) { Growl.Warning("未加载实验标准,无法保存"); return; } if (InspectRows.Any(r => r.InspectResultText == "待填写" || string.IsNullOrWhiteSpace(r.InspectResultText))) { Growl.Warning("请手填全部检验行的检测值后再保存"); return; } if (!TryParseInspectTimes(out var inspectTimes, out var inspectTimesError)) { Growl.Warning(inspectTimesError); return; } var lineList = BuildAveragedLineList(); if (lineList.Count == 0) { Growl.Warning("无法根据试验结果计算明细数据"); return; } var rawLineList = BuildRawLineList(); if (rawLineList.Count == 0) { Growl.Warning("无法生成试验结果原始数据"); return; } var user = AppSession.CurrentUser; IsLoading = true; try { var record = BuildSaveRecord(lineList, rawLineList, inspectTimes, user); await _operationService.SaveRecordAsync(record); Growl.Success("快检记录已保存到 MES"); InspectRows.Clear(); SaveCommand.RaiseCanExecuteChanged(); } catch (Exception ex) { Growl.Error($"保存失败:{ex.Message}"); } finally { IsLoading = false; } } private bool TryParseInspectTimes(out int value, out string error) { error = string.Empty; var text = string.IsNullOrWhiteSpace(InspectTimesText) ? "1" : InspectTimesText.Trim(); if (!int.TryParse(text, out var n) || n <= 0) { error = "试验次数须填写正整数"; value = 0; return false; } value = n; return true; } /// 试验结果各数据点检测值取平均,写入快检记录明细 private List BuildAveragedLineList() { var lines = new List(); for (int i = 0; i < DataPointColumns.Count; i++) { var col = DataPointColumns[i]; var values = InspectRows .Where(r => i < r.Cells.Count && r.Cells[i].Value != null) .Select(r => r.Cells[i].Value!.Value) .ToList(); if (values.Count == 0) continue; var avg = values.Average(v => (double)v); var avgDecimal = Math.Round((decimal)avg, 6); var line = new MesXslRubberQuickTestRecordLine { SortNo = i }; if (!string.IsNullOrWhiteSpace(col.DataPointId)) line.DataPointId = col.DataPointId; if (!string.IsNullOrWhiteSpace(col.PointName)) line.InspectItem = col.PointName; if (col.LowerLimit != null) line.LowerLimit = col.LowerLimit; if (col.UpperLimit != null) line.UpperLimit = col.UpperLimit; line.InspectValue = avgDecimal; lines.Add(line); } return lines; } /// 试验结果全部检测值写入原始数据明细 private List BuildRawLineList() { var rawLines = new List(); int sort = 0; foreach (var row in InspectRows) { for (int i = 0; i < row.Cells.Count; i++) { var cell = row.Cells[i]; if (cell.Value == null) continue; var raw = new MesXslRubberQuickTestRecordRawLine { SortNo = sort++ }; if (!string.IsNullOrWhiteSpace(row.RowNo)) raw.RowNo = row.RowNo; if (!string.IsNullOrWhiteSpace(cell.DataPointId)) raw.DataPointId = cell.DataPointId; if (!string.IsNullOrWhiteSpace(cell.PointName)) raw.InspectItem = cell.PointName; if (cell.LowerLimit != null) raw.LowerLimit = cell.LowerLimit; if (cell.UpperLimit != null) raw.UpperLimit = cell.UpperLimit; raw.InspectValue = cell.Value; raw.RowInspectResult = row.InspectResultCode; rawLines.Add(raw); } } return rawLines; } /// 主表检验结果:任一行不合格则不合格 private static string JudgeInspectResultFromRows(IEnumerable rows) { if (!rows.Any()) return "0"; return rows.Any(r => r.InspectResultCode != "1") ? "0" : "1"; } private MesXslRubberQuickTestRecord BuildSaveRecord( List lineList, List rawLineList, int inspectTimes, SysUser? user) { var record = new MesXslRubberQuickTestRecord { LineList = lineList, RawLineList = rawLineList, InspectTime = DateTime.Now, InspectResult = JudgeInspectResultFromRows(InspectRows) }; if (!string.IsNullOrWhiteSpace(_currentStd?.RubberMaterialId)) record.RubberMaterialId = _currentStd.RubberMaterialId; if (!string.IsNullOrWhiteSpace(RubberMaterialName)) record.RubberMaterialName = RubberMaterialName; if (!string.IsNullOrWhiteSpace(_currentStd?.Id)) record.StdId = _currentStd.Id; if (!string.IsNullOrWhiteSpace(_selectedPlan?.MachineId)) record.ProdEquipmentLedgerId = _selectedPlan.MachineId; if (!string.IsNullOrWhiteSpace(MachineName)) record.ProdEquipmentName = MachineName; record.ProductionDate = MixingDate; if (!string.IsNullOrWhiteSpace(TrainNo)) record.TrainNo = TrainNo.Trim(); if (SelectedShift != null && !string.IsNullOrWhiteSpace(SelectedShift.Code)) record.WorkShift = SelectedShift.Code; record.InspectTimes = inspectTimes; if (!string.IsNullOrWhiteSpace(ProductionOrderNo)) record.ProductionPlanNo = ProductionOrderNo; var inspectorId = user?.JeecgBizUserId ?? (user != null ? user.Id.ToString() : null); if (!string.IsNullOrWhiteSpace(inspectorId)) record.InspectorUserId = inspectorId; if (!string.IsNullOrWhiteSpace(user?.Account)) record.InspectorUsername = user.Account; if (!string.IsNullOrWhiteSpace(user?.RealName)) record.InspectorRealname = user.RealName; return record; } private static Axis[] BuildTimeAxis() => new[] { new Axis { Name = "时间(min)", MinLimit = 0, MaxLimit = 2, Labels = new[] { "0", "0.5", "1", "1.5", "2" }, LabelsPaint = new SolidColorPaint(SKColors.Gray), NamePaint = new SolidColorPaint(SKColors.Gray) } }; private static Axis[] BuildTempYAxis() => new[] { new Axis { Name = "温度(℃)", MinLimit = 189, MaxLimit = 201, Labeler = v => v switch { 189 => "189", 192 => "192", 195 => "195", 198 => "198", 201 => "201", _ => string.Empty }, LabelsPaint = new SolidColorPaint(SKColors.Gray), NamePaint = new SolidColorPaint(SKColors.Gray) } }; private static Axis[] BuildTorqueYAxis() => new[] { new Axis { Name = "S'(dNm)", MinLimit = 0, MaxLimit = 14, Labeler = v => v switch { 0 => "0", 3 => "3", 6 => "6", 8 => "8", 11 => "11", 14 => "14", _ => string.Empty }, LabelsPaint = new SolidColorPaint(SKColors.Gray), NamePaint = new SolidColorPaint(SKColors.Gray) } }; public new void Destroy() => base.Destroy(); } public class WorkShiftOption { public WorkShiftOption(string code, string name) { Code = code; Name = name; } public string Code { get; } public string Name { get; } }