桌面端快检记录新增列表及同步mes

This commit is contained in:
2026-06-22 17:38:49 +08:00
parent 3bce685f3a
commit efcd73a565
37 changed files with 2481 additions and 416 deletions

View File

@@ -0,0 +1,11 @@
using Prism.Events;
namespace YY.Admin.Core.Events;
public class RubberQuickTestRecordChangedPayload
{
public string Action { get; set; } = string.Empty;
public string? RecordId { get; set; }
}
public class RubberQuickTestRecordChangedEvent : PubSubEvent<RubberQuickTestRecordChangedPayload> { }

View File

@@ -0,0 +1,31 @@
using YY.Admin.Core.Entity;
namespace YY.Admin.Core.Services;
public interface IRubberQuickTestRecordService
{
Task<RubberQuickTestRecordPageResult> PageAsync(
int pageNo,
int pageSize,
string? filterRecordNo = null,
string? filterRubberMaterialName = null,
string? filterPlanNo = null,
CancellationToken ct = default);
Task<MesXslRubberQuickTestRecord?> GetByIdAsync(string id, CancellationToken ct = default);
RubberQuickTestRecordLocalItem? GetByLocalId(string localId);
Task<RubberQuickTestRecordSaveResult> SaveAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct = default);
/// <summary>删除本地同步失败的快检记录(已同步或待同步不可删)</summary>
bool DeleteFailedLocal(string localId);
string GenerateRecordNo(string rubberMaterialName);
}
public record RubberQuickTestRecordPageResult(
List<RubberQuickTestRecordListRow> Records,
long Total,
int PageNo,
int PageSize);

View File

@@ -7,6 +7,9 @@ public class MesXslRubberQuickTestRecord
public string? RubberMaterialId { get; set; }
public string? RubberMaterialName { get; set; }
public string? StdId { get; set; }
public string? StdName { get; set; }
public string? TestMethodId { get; set; }
public string? TestMethodName { get; set; }
public string? ProdEquipmentLedgerId { get; set; }
public string? ProdEquipmentName { get; set; }
public DateTime? ProductionDate { get; set; }
@@ -17,8 +20,18 @@ public class MesXslRubberQuickTestRecord
public string? InspectorUserId { get; set; }
public string? InspectorUsername { get; set; }
public string? InspectorRealname { get; set; }
public string? QuickTestTypeId { get; set; }
public string? QuickTestTypeName { get; set; }
public string? InspectResult { get; set; }
public string? ProductionPlanNo { get; set; }
public List<MesXslRubberQuickTestRecordLine>? LineList { get; set; }
public DateTime? CreateTime { get; set; }
public List<MesXslRubberQuickTestRecordStdLine>? StdLineList { get; set; }
public List<MesXslRubberQuickTestRecordRawLine>? RawLineList { get; set; }
public List<MesXslRubberQuickTestRecordChartPoint>? ChartPointList { get; set; }
/// <summary>列表展示:班次文本</summary>
public string? WorkShiftText { get; set; }
/// <summary>列表展示:是否合格</summary>
public string? InspectResultText { get; set; }
}

View File

@@ -0,0 +1,13 @@
namespace YY.Admin.Core.Entity;
/// <summary>胶料快检记录曲线图数据点</summary>
public class MesXslRubberQuickTestRecordChartPoint
{
public string? Id { get; set; }
public string? RecordId { get; set; }
public decimal? TimeMin { get; set; }
public decimal? UpperTemp { get; set; }
public decimal? LowerTemp { get; set; }
public decimal? TorqueS { get; set; }
public int? SortNo { get; set; }
}

View File

@@ -0,0 +1,16 @@
namespace YY.Admin.Core.Entity;
/// <summary>胶料快检记录数据标准明细(实验标准快照)</summary>
public class MesXslRubberQuickTestRecordStdLine
{
public string? Id { get; set; }
public string? RecordId { get; set; }
public string? DataPointId { get; set; }
public string? PointName { get; set; }
public decimal? LowerLimit { get; set; }
public decimal? UpperLimit { get; set; }
public decimal? LowerWarn { get; set; }
public decimal? UpperWarn { get; set; }
public decimal? TargetValue { get; set; }
public int? SortNo { get; set; }
}

View File

@@ -0,0 +1,32 @@
namespace YY.Admin.Core.Entity;
/// <summary>胶料快检记录列表行</summary>
public class RubberQuickTestRecordListRow
{
public string? LocalId { get; set; }
public string? MesId { get; set; }
public string? RecordNo { get; set; }
public DateTime? ProductionDate { get; set; }
public string? ProdEquipmentName { get; set; }
public string? WorkShiftDisplay { get; set; }
public string? ProductionPlanNo { get; set; }
public string? RubberMaterialName { get; set; }
public string? StdName { get; set; }
public string? TestMethodName { get; set; }
public string? QuickTestTypeName { get; set; }
public string? TrainNo { get; set; }
public int? InspectTimes { get; set; }
public string? InspectorRealname { get; set; }
public DateTime? InspectDate { get; set; }
public string? InspectResultDisplay { get; set; }
public string SyncStatus { get; set; } = "Pending";
public string SyncStatusDisplay => SyncStatus switch
{
"Synced" => "已同步",
"Failed" => "失败",
_ => "待同步"
};
/// <summary>仅本地同步失败记录可删除</summary>
public bool CanDelete => SyncStatus == "Failed" && !string.IsNullOrWhiteSpace(LocalId);
}

View File

@@ -0,0 +1,13 @@
namespace YY.Admin.Core.Entity;
/// <summary>桌面端本地胶料快检记录包装(含同步状态)</summary>
public class RubberQuickTestRecordLocalItem
{
public string LocalId { get; set; } = Guid.NewGuid().ToString("N");
public string? MesId { get; set; }
/// <summary>Pending / Synced / Failed</summary>
public string SyncStatus { get; set; } = "Pending";
public string? SyncError { get; set; }
public DateTime LocalCreateTime { get; set; } = DateTime.Now;
public MesXslRubberQuickTestRecord Record { get; set; } = new();
}

View File

@@ -0,0 +1,8 @@
namespace YY.Admin.Core.Entity;
public class RubberQuickTestRecordSaveResult
{
public MesXslRubberQuickTestRecord Record { get; set; } = new();
public string LocalId { get; set; } = string.Empty;
public string SyncStatus { get; set; } = "Pending";
}

View File

@@ -51,7 +51,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
// 胶料快检实验标准(桌面端只读)
new SysMenu{ Id=1300150011301, Pid=1300150000101, Title="胶料快检实验标准", Path="/xslmes/mesXslRubberQuickTestStd", Name="mesXslRubberQuickTestStd", Component="RubberQuickTestStdListView", Icon="&#xe7ce;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=112 },
// 胶料快检记录(紧接实验标准之后)
new SysMenu{ Id=1300150011201, Pid=1300150000101, Title="胶料快检记录", Path="/xslmes/rubberQuickTestOperation", Name="rubberQuickTestOperation", Component="RubberQuickTestOperationView", Icon="&#xe7de;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=113 },
new SysMenu{ Id=1300150011201, Pid=1300150000101, Title="胶料快检记录", Path="/xslmes/rubberQuickTestRecord", Name="rubberQuickTestRecord", Component="RubberQuickTestRecordListView", Icon="&#xe7de;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=113 },
// 密炼计划
new SysMenu{ Id=1300150011401, Pid=1300150000101, Title="密炼计划", Path="/xslmes/mesXslMixingProductionPlan", Name="mesXslMixingProductionPlan", Component="MixingProductionPlanListView", Icon="&#xe7ce;", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=114 },

View File

@@ -518,6 +518,9 @@ namespace YY.Admin.Core.SqlSugar
dbProvider.Updateable<SysMenu>()
.SetColumns(m => m.Title == "胶料快检记录")
.SetColumns(m => m.Path == "/xslmes/rubberQuickTestRecord")
.SetColumns(m => m.Name == "rubberQuickTestRecord")
.SetColumns(m => m.Component == "RubberQuickTestRecordListView")
.SetColumns(m => m.OrderNo == 113)
.Where(m => m.Id == quickTestRecordMenuId)
.ExecuteCommand();

View File

@@ -0,0 +1,502 @@
using Microsoft.Extensions.Configuration;
using Prism.Events;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;
using YY.Admin.Core;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Events;
using YY.Admin.Core.Services;
namespace YY.Admin.Services.Service.RubberQuickTest;
public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISingletonDependency
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly INetworkMonitor _networkMonitor;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger;
private readonly SemaphoreSlim _syncLock = new(1, 1);
private readonly object _cacheLock = new();
private readonly string _cacheFilePath;
private List<RubberQuickTestRecordLocalItem> _localItems = new();
private static readonly JsonSerializerOptions _jsonOpts = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new NullableDateTimeJsonConverter() }
};
public RubberQuickTestRecordService(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
INetworkMonitor networkMonitor,
IEventAggregator eventAggregator,
ILoggerService logger)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_networkMonitor = networkMonitor;
_eventAggregator = eventAggregator;
_logger = logger;
var appDataDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"YY.Admin", "sync-cache");
Directory.CreateDirectory(appDataDir);
_cacheFilePath = Path.Combine(appDataDir, "rubber-quick-test-record-items.json");
LoadCacheFromDisk();
_logger.Information($"[快检记录同步] 初始化完成,本地记录={_localItems.Count}");
_networkMonitor.StatusChanged += OnNetworkStatusChanged;
if (_networkMonitor.IsOnline)
_ = Task.Run(() => PushPendingAsync(CancellationToken.None));
}
private string BaseUrl => (_configuration.GetValue<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
private int DefaultTenantId => (int?)_configuration.GetValue<long?>("JeecgIntegration:DefaultTenantId") ?? 1002;
private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi");
public async Task<RubberQuickTestRecordPageResult> PageAsync(
int pageNo, int pageSize,
string? filterRecordNo = null,
string? filterRubberMaterialName = null,
string? filterPlanNo = null,
CancellationToken ct = default)
{
var rows = await BuildAllRowsAsync(ct).ConfigureAwait(false);
var filtered = ApplyFilters(rows, filterRecordNo, filterRubberMaterialName, filterPlanNo);
var total = filtered.Count;
var records = filtered
.OrderByDescending(r => r.InspectDate ?? DateTime.MinValue)
.Skip(Math.Max(0, (pageNo - 1) * pageSize))
.Take(pageSize)
.ToList();
return new RubberQuickTestRecordPageResult(records, total, pageNo, pageSize);
}
public async Task<MesXslRubberQuickTestRecord?> GetByIdAsync(string id, CancellationToken ct = default)
{
RubberQuickTestRecordLocalItem? local = null;
lock (_cacheLock)
{
local = _localItems.FirstOrDefault(x =>
string.Equals(x.LocalId, id, StringComparison.OrdinalIgnoreCase)
|| string.Equals(x.MesId, id, StringComparison.OrdinalIgnoreCase)
|| string.Equals(x.Record.RecordNo, id, StringComparison.OrdinalIgnoreCase));
}
if (local != null)
return CloneRecord(local.Record);
if (_networkMonitor.IsOnline)
{
try
{
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestRecord/anon/queryById?id={Uri.EscapeDataString(id)}&tenantId={DefaultTenantId}";
using var client = CreateClient();
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
if (resp.IsSuccessStatusCode)
{
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("result", out var resultEl))
return resultEl.Deserialize<MesXslRubberQuickTestRecord>(_jsonOpts);
}
}
catch (Exception ex)
{
_logger.Warning($"[快检记录详情] 远端查询异常 id={id}: {ex.Message}");
}
}
return null;
}
public RubberQuickTestRecordLocalItem? GetByLocalId(string localId)
{
lock (_cacheLock)
{
var item = _localItems.FirstOrDefault(x => string.Equals(x.LocalId, localId, StringComparison.OrdinalIgnoreCase));
return item == null ? null : CloneLocalItem(item);
}
}
public async Task<RubberQuickTestRecordSaveResult> SaveAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct = default)
{
var record = CloneRecord(entity);
record.CreateTime ??= DateTime.Now;
record.InspectTime ??= record.CreateTime;
var item = new RubberQuickTestRecordLocalItem
{
LocalId = Guid.NewGuid().ToString("N"),
LocalCreateTime = DateTime.Now,
SyncStatus = "Pending",
Record = record
};
if (_networkMonitor.IsOnline)
{
try
{
var recordNo = await RemoteAddAsync(record, ct).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(recordNo))
{
item.Record.RecordNo = recordNo;
item.SyncStatus = "Synced";
}
}
catch (Exception ex)
{
item.SyncStatus = "Failed";
item.SyncError = ex.Message;
_logger.Warning($"[快检记录新增] 远端失败,保留本地:{ex.Message}");
}
}
lock (_cacheLock)
{
_localItems.Add(CloneLocalItem(item));
SaveCacheToDiskUnsafe();
}
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Publish(new RubberQuickTestRecordChangedPayload { Action = "add", RecordId = item.LocalId });
return new RubberQuickTestRecordSaveResult
{
LocalId = item.LocalId,
SyncStatus = item.SyncStatus,
Record = CloneRecord(item.Record)
};
}
public bool DeleteFailedLocal(string localId)
{
if (string.IsNullOrWhiteSpace(localId)) return false;
bool removed;
lock (_cacheLock)
{
var item = _localItems.FirstOrDefault(x =>
string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase));
if (item == null || !string.Equals(item.SyncStatus, "Failed", StringComparison.OrdinalIgnoreCase))
return false;
removed = _localItems.Remove(item);
if (removed)
SaveCacheToDiskUnsafe();
}
if (!removed) return false;
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Publish(new RubberQuickTestRecordChangedPayload { Action = "delete", RecordId = localId });
_logger.Information($"[快检记录删除] 已删除同步失败本地记录 localId={localId}");
return true;
}
public string GenerateRecordNo(string rubberMaterialName)
{
var dateStr = DateTime.Now.ToString("yyyyMMdd");
var material = rubberMaterialName.Trim();
int maxSeq = 0;
lock (_cacheLock)
{
foreach (var item in _localItems)
{
var no = item.Record.RecordNo;
if (string.IsNullOrWhiteSpace(no) || !no.StartsWith(dateStr, StringComparison.Ordinal) || !no.EndsWith(material, StringComparison.Ordinal))
continue;
var seqPart = no.Substring(dateStr.Length, Math.Min(4, no.Length - dateStr.Length - material.Length));
if (int.TryParse(seqPart, out var seq))
maxSeq = Math.Max(maxSeq, seq);
}
}
return dateStr + (maxSeq + 1).ToString("D4") + material;
}
private async Task<List<RubberQuickTestRecordListRow>> BuildAllRowsAsync(CancellationToken ct)
{
var rows = new List<RubberQuickTestRecordListRow>();
var seenRecordNos = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
lock (_cacheLock)
{
foreach (var item in _localItems)
{
rows.Add(ToListRow(item));
if (!string.IsNullOrWhiteSpace(item.Record.RecordNo))
seenRecordNos.Add(item.Record.RecordNo);
}
}
if (_networkMonitor.IsOnline)
{
try
{
foreach (var remote in await FetchRemoteListAsync(ct).ConfigureAwait(false))
{
if (!string.IsNullOrWhiteSpace(remote.RecordNo) && seenRecordNos.Contains(remote.RecordNo))
continue;
rows.Add(ToListRow(remote, "Synced", remote.Id));
}
}
catch (Exception ex)
{
_logger.Warning($"[快检记录列表] 远端拉取失败:{ex.Message}");
}
}
return rows;
}
private static RubberQuickTestRecordListRow ToListRow(RubberQuickTestRecordLocalItem item) =>
ToListRow(item.Record, item.SyncStatus, item.MesId, item.LocalId);
private static RubberQuickTestRecordListRow ToListRow(
MesXslRubberQuickTestRecord r,
string syncStatus,
string? mesId = null,
string? localId = null) => new()
{
LocalId = localId,
MesId = mesId ?? r.Id,
RecordNo = r.RecordNo,
ProductionDate = r.ProductionDate,
ProdEquipmentName = r.ProdEquipmentName,
WorkShiftDisplay = r.WorkShiftText ?? r.WorkShift,
ProductionPlanNo = r.ProductionPlanNo,
RubberMaterialName = r.RubberMaterialName,
StdName = r.StdName,
TestMethodName = r.TestMethodName,
QuickTestTypeName = r.QuickTestTypeName,
TrainNo = r.TrainNo,
InspectTimes = r.InspectTimes,
InspectorRealname = r.InspectorRealname,
InspectDate = r.CreateTime ?? r.InspectTime,
InspectResultDisplay = r.InspectResultText ?? (r.InspectResult == "1" ? "合格" : r.InspectResult == "0" ? "不合格" : ""),
SyncStatus = syncStatus
};
private async Task<string?> RemoteAddAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct)
{
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestRecord/anon/add?tenantId={DefaultTenantId}";
var payload = CloneRecord(entity);
payload.Id = null;
using var client = CreateClient();
var body = JsonSerializer.Serialize(payload, _jsonOpts);
using var content = new StringContent(body, Encoding.UTF8, "application/json");
var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false);
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("success", out var successEl) || !successEl.GetBoolean())
{
var msg = doc.RootElement.TryGetProperty("message", out var msgEl) ? msgEl.GetString() : "保存失败";
throw new InvalidOperationException(msg ?? "保存失败");
}
if (doc.RootElement.TryGetProperty("result", out var resultEl) && resultEl.ValueKind == JsonValueKind.String)
return resultEl.GetString();
return entity.RecordNo;
}
private async Task<List<MesXslRubberQuickTestRecord>> FetchRemoteListAsync(CancellationToken ct)
{
var query = HttpUtility.ParseQueryString(string.Empty);
query["pageNo"] = "1";
query["pageSize"] = "10000";
query["tenantId"] = DefaultTenantId.ToString();
var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestRecord/anon/list?{query}";
using var client = CreateClient();
var resp = await client.GetAsync(url, ct).ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
using var doc = JsonDocument.Parse(json);
var result = doc.RootElement.GetProperty("result");
return result.GetProperty("records").Deserialize<List<MesXslRubberQuickTestRecord>>(_jsonOpts) ?? new();
}
private void OnNetworkStatusChanged(bool isOnline)
{
if (!isOnline) return;
_ = Task.Run(() => PushPendingAsync(CancellationToken.None));
}
private async Task PushPendingAsync(CancellationToken ct)
{
if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false)) return;
try
{
List<RubberQuickTestRecordLocalItem> pending;
lock (_cacheLock)
{
pending = _localItems.Where(x => x.SyncStatus != "Synced").Select(CloneLocalItem).ToList();
}
foreach (var item in pending)
{
if (!_networkMonitor.IsOnline) break;
try
{
var recordNo = await RemoteAddAsync(item.Record, ct).ConfigureAwait(false);
lock (_cacheLock)
{
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
if (target == null) continue;
if (!string.IsNullOrWhiteSpace(recordNo))
target.Record.RecordNo = recordNo;
target.SyncStatus = "Synced";
target.SyncError = null;
SaveCacheToDiskUnsafe();
}
}
catch (Exception ex)
{
lock (_cacheLock)
{
var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId);
if (target != null)
{
target.SyncStatus = "Failed";
target.SyncError = ex.Message;
SaveCacheToDiskUnsafe();
}
}
}
}
}
finally
{
_syncLock.Release();
}
}
private static List<RubberQuickTestRecordListRow> ApplyFilters(
List<RubberQuickTestRecordListRow> source,
string? filterRecordNo,
string? filterRubberMaterialName,
string? filterPlanNo)
{
IEnumerable<RubberQuickTestRecordListRow> q = source;
if (!string.IsNullOrWhiteSpace(filterRecordNo))
q = q.Where(r => (r.RecordNo ?? "").Contains(filterRecordNo.Trim(), StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(filterRubberMaterialName))
q = q.Where(r => (r.RubberMaterialName ?? "").Contains(filterRubberMaterialName.Trim(), StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(filterPlanNo))
q = q.Where(r => (r.ProductionPlanNo ?? "").Contains(filterPlanNo.Trim(), StringComparison.OrdinalIgnoreCase));
return q.ToList();
}
private void LoadCacheFromDisk()
{
try
{
if (!File.Exists(_cacheFilePath)) return;
var data = JsonSerializer.Deserialize<List<RubberQuickTestRecordLocalItem>>(File.ReadAllText(_cacheFilePath), _jsonOpts);
_localItems = data ?? new();
}
catch { _localItems = new(); }
}
private void SaveCacheToDiskUnsafe() =>
File.WriteAllText(_cacheFilePath, JsonSerializer.Serialize(_localItems, _jsonOpts));
private static RubberQuickTestRecordLocalItem CloneLocalItem(RubberQuickTestRecordLocalItem src) => new()
{
LocalId = src.LocalId,
MesId = src.MesId,
SyncStatus = src.SyncStatus,
SyncError = src.SyncError,
LocalCreateTime = src.LocalCreateTime,
Record = CloneRecord(src.Record)
};
private static MesXslRubberQuickTestRecord CloneRecord(MesXslRubberQuickTestRecord src) => new()
{
Id = src.Id,
RecordNo = src.RecordNo,
RubberMaterialId = src.RubberMaterialId,
RubberMaterialName = src.RubberMaterialName,
StdId = src.StdId,
StdName = src.StdName,
TestMethodId = src.TestMethodId,
TestMethodName = src.TestMethodName,
ProdEquipmentLedgerId = src.ProdEquipmentLedgerId,
ProdEquipmentName = src.ProdEquipmentName,
ProductionDate = src.ProductionDate,
TrainNo = src.TrainNo,
WorkShift = src.WorkShift,
InspectTimes = src.InspectTimes,
InspectTime = src.InspectTime,
InspectorUserId = src.InspectorUserId,
InspectorUsername = src.InspectorUsername,
InspectorRealname = src.InspectorRealname,
QuickTestTypeId = src.QuickTestTypeId,
QuickTestTypeName = src.QuickTestTypeName,
InspectResult = src.InspectResult,
ProductionPlanNo = src.ProductionPlanNo,
CreateTime = src.CreateTime,
StdLineList = src.StdLineList?.Select(l => new MesXslRubberQuickTestRecordStdLine
{
Id = l.Id, RecordId = l.RecordId, DataPointId = l.DataPointId, PointName = l.PointName,
LowerLimit = l.LowerLimit, UpperLimit = l.UpperLimit, LowerWarn = l.LowerWarn,
UpperWarn = l.UpperWarn, TargetValue = l.TargetValue, SortNo = l.SortNo
}).ToList(),
RawLineList = src.RawLineList?.Select(l => new MesXslRubberQuickTestRecordRawLine
{
Id = l.Id, RecordId = l.RecordId, RowNo = l.RowNo, DataPointId = l.DataPointId,
InspectItem = l.InspectItem, LowerLimit = l.LowerLimit, UpperLimit = l.UpperLimit,
InspectValue = l.InspectValue, RowInspectResult = l.RowInspectResult, SortNo = l.SortNo
}).ToList(),
ChartPointList = src.ChartPointList?.Select(p => new MesXslRubberQuickTestRecordChartPoint
{
Id = p.Id, RecordId = p.RecordId, TimeMin = p.TimeMin, UpperTemp = p.UpperTemp,
LowerTemp = p.LowerTemp, TorqueS = p.TorqueS, SortNo = p.SortNo
}).ToList(),
WorkShiftText = src.WorkShiftText,
InspectResultText = src.InspectResultText
};
private sealed class NullableDateTimeJsonConverter : JsonConverter<DateTime?>
{
private static readonly string[] SupportedFormats =
[
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.fff",
"yyyy-MM-ddTHH:mm:ss", "yyyy-MM-ddTHH:mm:ss.fff",
"yyyy-MM-ddTHH:mm:ssZ", "yyyy-MM-ddTHH:mm:ss.fffZ",
"yyyy-MM-dd"
];
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null) return null;
if (reader.TokenType == JsonTokenType.String)
{
var raw = reader.GetString();
if (string.IsNullOrWhiteSpace(raw)) return null;
if (DateTime.TryParseExact(raw, SupportedFormats,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.AssumeLocal, out var exact)) return exact;
if (DateTime.TryParse(raw, out var fallback)) return fallback;
}
throw new JsonException($"无法转换为 DateTime?token={reader.TokenType}");
}
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
if (value == null) writer.WriteNullValue();
else writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss"));
}
}
}

View File

@@ -99,6 +99,7 @@ namespace YY.Admin
// 密炼物料皮重策略
containerRegistry.RegisterForNavigation<MixerMaterialTareStrategyListView>();
// 胶料快检记录操作台
containerRegistry.RegisterForNavigation<RubberQuickTestRecordListView>();
containerRegistry.RegisterForNavigation<RubberQuickTestOperationView>();
// 胶料快检实验标准(只读)
containerRegistry.RegisterForNavigation<RubberQuickTestStdListView>();

View File

@@ -89,7 +89,8 @@ public class SyncModule : IModule
containerRegistry.RegisterSingleton<PrintTemplateSyncCoordinator>();
containerRegistry.RegisterSingleton<PrintBizTemplateBindSyncCoordinator>();
// 胶料快检记录操作台
// 胶料快检记录:本地存储 + MES 同步
containerRegistry.RegisterSingleton<IRubberQuickTestRecordService, RubberQuickTestRecordService>();
containerRegistry.RegisterSingleton<IRubberQuickTestOperationService, RubberQuickTestOperationService>();
// 胶料快检实验标准MES 只读同步)

View File

@@ -153,6 +153,9 @@ namespace YY.Admin.ViewModels.Control
["mesXslMixerMaterialTareStrategy"] = "MixerMaterialTareStrategyListView",
// 已实现页面:胶料快检记录操作台
["RubberQuickTestRecordListView"] = "RubberQuickTestRecordListView",
["/xslmes/rubberQuickTestRecord"] = "RubberQuickTestRecordListView",
["rubberQuickTestRecord"] = "RubberQuickTestRecordListView",
["RubberQuickTestOperationView"] = "RubberQuickTestOperationView",
["/xslmes/rubberQuickTestOperation"] = "RubberQuickTestOperationView",
["rubberQuickTestOperation"] = "RubberQuickTestOperationView",

View File

@@ -10,9 +10,12 @@ using Prism.Events;
using Prism.Mvvm;
using Prism.Navigation;
using SkiaSharp;
using System.Collections.ObjectModel;
using System.Threading;
using YY.Admin.Core;
@@ -165,11 +168,11 @@ public class QuickTestInspectRowViewModel : BindableBase
/// <summary>胶料快检记录操作台 ViewModel密炼计划、实验标准均读本地缓存</summary>
public class RubberQuickTestOperationViewModel : BaseViewModel
public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware
{
private readonly IRubberQuickTestOperationService _operationService;
private readonly IRubberQuickTestRecordService _recordService;
private readonly IMixingProductionPlanService _planService;
@@ -177,6 +180,12 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
private readonly Random _rnd = new();
private bool _saveInProgress;
private string? _loadedDetailLocalId;
private string? _loadedDetailMesId;
private int _navigationApplyVersion;
private List<MesXslMixingProductionPlan> _allPlans = new();
private List<MesXslRubberQuickTestStd> _allStds = new();
@@ -200,7 +209,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
public RubberQuickTestOperationViewModel(
IRubberQuickTestOperationService operationService,
IRubberQuickTestRecordService recordService,
IMixingProductionPlanService planService,
@@ -212,7 +221,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
{
_operationService = operationService;
_recordService = recordService;
_planService = planService;
@@ -228,7 +237,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
RemoveInspectRowCommand = new DelegateCommand(() => RemoveInspectRow(SelectedInspectRow), () => SelectedInspectRow != null);
SaveCommand = new DelegateCommand(async () => await SaveAsync(), () => InspectRows.Count > 0);
SaveCommand = new DelegateCommand(async () => await SaveAsync(), () => CanSave);
RefreshPlansCommand = new DelegateCommand(async () => await LoadLocalDataAsync(showSuccess: true));
@@ -259,13 +268,104 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
_stdChangedToken = _eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
.Subscribe(async _ => await ReloadLocalDataQuietAsync(), ThreadOption.UIThread);
_ = LoadLocalDataAsync(showSuccess: false);
RecalculateOverallInspectResult();
}
private bool _isReadOnly;
public bool IsReadOnly
{
get => _isReadOnly;
private set
{
if (!SetProperty(ref _isReadOnly, value)) return;
RaisePropertyChanged(nameof(IsEditable));
RaisePropertyChanged(nameof(ShowSaveButton));
NotifySaveStateChanged();
InspectColumnsChanged?.Invoke();
}
}
public bool IsEditable => !IsReadOnly;
public bool ShowSaveButton => !IsReadOnly;
public bool CanSave => InspectRows.Count > 0 && !_saveInProgress && !IsReadOnly;
private void NotifySaveStateChanged()
{
RaisePropertyChanged(nameof(CanSave));
SaveCommand.RaiseCanExecuteChanged();
}
protected override void OnIsLoadingChanged()
{
base.OnIsLoadingChanged();
NotifySaveStateChanged();
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
_ = ApplyNavigationAsync(navigationContext.Parameters);
}
private async Task ApplyNavigationAsync(INavigationParameters parameters)
{
var version = Interlocked.Increment(ref _navigationApplyVersion);
if (parameters.TryGetValue<bool>("readOnly", out var readOnly) && readOnly)
{
if (parameters.TryGetValue<string>("localId", out var localId) && !string.IsNullOrWhiteSpace(localId))
{
_loadedDetailLocalId = localId;
_loadedDetailMesId = null;
await LoadFromLocalItemAsync(localId);
return;
}
if (parameters.TryGetValue<string>("mesId", out var mesId) && !string.IsNullOrWhiteSpace(mesId))
{
_loadedDetailMesId = mesId;
_loadedDetailLocalId = null;
await LoadFromMesIdAsync(mesId);
return;
}
}
if (version != _navigationApplyVersion) return;
_loadedDetailLocalId = null;
_loadedDetailMesId = null;
IsReadOnly = false;
ResetFormForNewEntry();
await LoadLocalDataAsync(showSuccess: false);
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
var parameters = navigationContext.Parameters;
if (parameters.TryGetValue<bool>("readOnly", out var readOnly) && readOnly)
{
if (parameters.TryGetValue<string>("localId", out var localId) && !string.IsNullOrWhiteSpace(localId))
return IsReadOnly && string.Equals(_loadedDetailLocalId, localId, StringComparison.OrdinalIgnoreCase);
if (parameters.TryGetValue<string>("mesId", out var mesId) && !string.IsNullOrWhiteSpace(mesId))
return IsReadOnly && string.Equals(_loadedDetailMesId, mesId, StringComparison.OrdinalIgnoreCase);
return false;
}
return !IsReadOnly && _loadedDetailLocalId == null && _loadedDetailMesId == null;
}
private void ResetFormForNewEntry()
{
RecordNo = null;
TrainNo = null;
InspectTimesText = "1";
ClearPlanAndStdSelection();
RecalculateOverallInspectResult();
NotifySaveStateChanged();
}
public void OnNavigatedFrom(NavigationContext navigationContext) { }
private static string ResolveInspectorDisplay()
@@ -648,8 +748,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
private async Task ReloadLocalDataQuietAsync()
{
if (IsReadOnly) return;
try
@@ -1070,7 +1170,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
InspectRows.Add(row);
SaveCommand.RaiseCanExecuteChanged();
NotifySaveStateChanged();
RecalculateOverallInspectResult();
@@ -1090,7 +1190,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
RenumberInspectRows();
SaveCommand.RaiseCanExecuteChanged();
NotifySaveStateChanged();
RecalculateOverallInspectResult();
@@ -1099,145 +1199,90 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
private async Task SaveAsync()
{
if (_saveInProgress) return;
if (string.IsNullOrWhiteSpace(TrainNo))
{
Growl.Warning("请填写车次");
return;
}
if (SelectedPlan == null)
{
Growl.Warning("请选择密炼计划");
return;
}
if (string.IsNullOrWhiteSpace(RubberMaterialName))
{
Growl.Warning("请先选择密炼计划以带出胶料名称");
return;
}
if (SelectedStd == null || _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;
_saveInProgress = true;
NotifySaveStateChanged();
try
{
if (string.IsNullOrWhiteSpace(TrainNo))
{
Growl.Warning("请填写车次");
return;
}
var record = BuildSaveRecord(lineList, rawLineList, inspectTimes, user);
if (SelectedPlan == null)
{
Growl.Warning("请选择密炼计划");
return;
}
var recordNo = await _operationService.SaveRecordAsync(record);
if (string.IsNullOrWhiteSpace(RubberMaterialName))
{
Growl.Warning("请先选择密炼计划以带出胶料名称");
return;
}
RecordNo = recordNo;
if (SelectedStd == null || _currentStd == null)
{
Growl.Warning("请选择实验标准");
return;
}
Growl.Success(string.IsNullOrWhiteSpace(recordNo)
if (InspectRows.Any(r => r.InspectResultText == "待填写" || string.IsNullOrWhiteSpace(r.InspectResultText)))
{
Growl.Warning("请手填全部检验行的检测值后再保存");
return;
}
? "胶料快检记录已保存到 MES"
if (!TryParseInspectTimes(out var inspectTimes, out var inspectTimesError))
{
Growl.Warning(inspectTimesError);
return;
}
: $"胶料快检记录已保存,单号:{recordNo}");
var rawLineList = BuildRawLineList();
if (rawLineList.Count == 0)
{
Growl.Warning("无法生成试验结果原始数据");
return;
}
var stdLineList = BuildStdLineList();
if (stdLineList.Count == 0)
{
Growl.Warning("无法生成数据标准明细");
return;
}
var chartPointList = BuildChartPointList();
var user = AppSession.CurrentUser;
IsLoading = true;
var record = BuildSaveRecord(stdLineList, rawLineList, chartPointList, inspectTimes, user);
var saved = await _recordService.SaveAsync(record);
RecordNo = saved.Record.RecordNo;
var syncHint = saved.SyncStatus == "Synced" ? "并已同步到 MES" : "MES 同步待重试)";
Growl.Success(string.IsNullOrWhiteSpace(RecordNo)
? $"胶料快检记录已保存到本地{syncHint}"
: $"胶料快检记录已保存,快检记录号:{RecordNo}{syncHint}");
InspectRows.Clear();
SaveCommand.RaiseCanExecuteChanged();
NotifySaveStateChanged();
}
catch (Exception ex)
{
Growl.Error($"保存失败:{ex.Message}");
}
finally
{
IsLoading = false;
_saveInProgress = false;
NotifySaveStateChanged();
}
}
@@ -1432,10 +1477,12 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
private MesXslRubberQuickTestRecord BuildSaveRecord(
List<MesXslRubberQuickTestRecordLine> lineList,
List<MesXslRubberQuickTestRecordStdLine> stdLineList,
List<MesXslRubberQuickTestRecordRawLine> rawLineList,
List<MesXslRubberQuickTestRecordChartPoint> chartPointList,
int inspectTimes,
SysUser? user)
@@ -1446,13 +1493,19 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
{
LineList = lineList,
StdLineList = stdLineList,
RawLineList = rawLineList,
ChartPointList = chartPointList,
InspectTime = DateTime.Now,
InspectResult = OverallInspectResultCode
CreateTime = DateTime.Now,
InspectResult = OverallInspectResultCode,
RecordNo = _recordService.GenerateRecordNo(RubberMaterialName ?? string.Empty)
};
@@ -1470,6 +1523,26 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
record.StdId = _currentStd.Id;
if (!string.IsNullOrWhiteSpace(_currentStd?.StdName))
record.StdName = _currentStd.StdName;
if (!string.IsNullOrWhiteSpace(_currentStd?.TestMethodId))
record.TestMethodId = _currentStd.TestMethodId;
if (!string.IsNullOrWhiteSpace(TestMethodName))
record.TestMethodName = TestMethodName;
if (!string.IsNullOrWhiteSpace(_currentStd?.QuickTestTypeId))
record.QuickTestTypeId = _currentStd.QuickTestTypeId;
if (!string.IsNullOrWhiteSpace(_currentStd?.QuickTestTypeName))
record.QuickTestTypeName = _currentStd.QuickTestTypeName;
if (!string.IsNullOrWhiteSpace(_selectedPlan?.MachineId))
@@ -1502,9 +1575,9 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
if (!string.IsNullOrWhiteSpace(_selectedPlan?.PlanMaterialNo))
if (!string.IsNullOrWhiteSpace(_selectedPlan?.PlanNo))
record.ProductionPlanNo = _selectedPlan.PlanMaterialNo;
record.ProductionPlanNo = _selectedPlan.PlanNo;
@@ -1528,6 +1601,169 @@ public class RubberQuickTestOperationViewModel : BaseViewModel
}
private List<MesXslRubberQuickTestRecordStdLine> BuildStdLineList()
{
var lines = new List<MesXslRubberQuickTestRecordStdLine>();
int sort = 0;
foreach (var col in DataPointColumns)
{
lines.Add(new MesXslRubberQuickTestRecordStdLine
{
DataPointId = col.DataPointId,
PointName = col.PointName,
LowerLimit = col.LowerLimit,
LowerWarn = col.LowerWarn,
TargetValue = col.TargetValue,
UpperWarn = col.UpperWarn,
UpperLimit = col.UpperLimit,
SortNo = sort++
});
}
return lines;
}
private List<MesXslRubberQuickTestRecordChartPoint> BuildChartPointList()
{
var points = new List<MesXslRubberQuickTestRecordChartPoint>();
int sort = 0;
for (int i = 0; i < ChartPointCount && i < UpperTempValues.Count; i++)
{
points.Add(new MesXslRubberQuickTestRecordChartPoint
{
TimeMin = (decimal)(UpperTempValues[i].X ?? ChartTimeMinutes[i]),
UpperTemp = UpperTempValues[i].Y.HasValue ? (decimal)UpperTempValues[i].Y!.Value : null,
LowerTemp = i < LowerTempValues.Count && LowerTempValues[i].Y.HasValue ? (decimal)LowerTempValues[i].Y!.Value : null,
TorqueS = i < TorqueValues.Count && TorqueValues[i].Y.HasValue ? (decimal)TorqueValues[i].Y!.Value : null,
SortNo = sort++
});
}
return points;
}
private async Task LoadFromLocalItemAsync(string localId)
{
var item = _recordService.GetByLocalId(localId);
if (item == null)
{
Growl.Warning("未找到快检记录");
return;
}
await ApplyRecordDetailAsync(item.Record);
}
private async Task LoadFromMesIdAsync(string mesId)
{
var record = await _recordService.GetByIdAsync(mesId);
if (record == null)
{
Growl.Warning("未找到快检记录");
return;
}
await ApplyRecordDetailAsync(record);
}
private async Task ApplyRecordDetailAsync(MesXslRubberQuickTestRecord r)
{
IsReadOnly = true;
RecordNo = r.RecordNo;
MixingDate = r.ProductionDate ?? DateTime.Today;
SelectedMachine = r.ProdEquipmentName;
TrainNo = r.TrainNo;
InspectTimesText = r.InspectTimes?.ToString() ?? "1";
InspectorDisplay = r.InspectorRealname ?? ResolveInspectorDisplay();
OverallInspectResultCode = r.InspectResult ?? "0";
OverallInspectResultDisplay = string.Equals(r.InspectResult, "1", StringComparison.Ordinal) ? "合格" : "不合格";
TestMethodName = r.TestMethodName;
MachineOptions.Clear();
if (!string.IsNullOrWhiteSpace(r.ProdEquipmentName))
MachineOptions.Add(r.ProdEquipmentName);
ShiftOptions.Clear();
var shift = new WorkShiftOption(r.WorkShift ?? "", r.WorkShift ?? "");
ShiftOptions.Add(shift);
SelectedShift = shift;
PlanOptions.Clear();
var plan = new MesXslMixingProductionPlan
{
PlanNo = r.ProductionPlanNo,
MaterialName = r.RubberMaterialName,
MachineName = r.ProdEquipmentName
};
PlanOptions.Add(plan);
_selectedPlan = plan;
RaisePropertyChanged(nameof(SelectedPlan));
RaisePropertyChanged(nameof(MachineName));
RaisePropertyChanged(nameof(RubberMaterialName));
StdOptions.Clear();
var std = new MesXslRubberQuickTestStd { Id = r.StdId, StdName = r.StdName, TestMethodName = r.TestMethodName };
StdOptions.Add(std);
_selectedStd = std;
RaisePropertyChanged(nameof(SelectedStd));
DataPointColumns.Clear();
InspectRows.Clear();
foreach (var sl in (r.StdLineList ?? new List<MesXslRubberQuickTestRecordStdLine>()).OrderBy(x => x.SortNo ?? 0))
{
DataPointColumns.Add(new MesXslRubberQuickTestStdLine
{
DataPointId = sl.DataPointId,
PointName = sl.PointName,
LowerLimit = sl.LowerLimit,
LowerWarn = sl.LowerWarn,
TargetValue = sl.TargetValue,
UpperWarn = sl.UpperWarn,
UpperLimit = sl.UpperLimit,
SortNo = sl.SortNo
});
}
InspectColumnsChanged?.Invoke();
var grouped = (r.RawLineList ?? new List<MesXslRubberQuickTestRecordRawLine>())
.GroupBy(x => x.RowNo ?? string.Empty)
.OrderBy(g => g.Key, StringComparer.Ordinal);
foreach (var g in grouped)
{
var row = new QuickTestInspectRowViewModel { RowNo = g.Key };
for (int i = 0; i < DataPointColumns.Count; i++)
{
var col = DataPointColumns[i];
var raw = g.FirstOrDefault(x => x.DataPointId == col.DataPointId || x.InspectItem == col.PointName);
var cell = new QuickTestInspectCellViewModel
{
DataPointId = col.DataPointId,
PointName = col.PointName ?? string.Empty,
LowerLimit = col.LowerLimit,
UpperLimit = col.UpperLimit,
Value = raw?.InspectValue
};
row.Cells.Add(cell);
}
row.RecalculateResult();
InspectRows.Add(row);
}
UpperTempValues.Clear();
LowerTempValues.Clear();
TorqueValues.Clear();
foreach (var pt in (r.ChartPointList ?? new List<MesXslRubberQuickTestRecordChartPoint>()).OrderBy(x => x.SortNo ?? 0))
{
var time = (double)(pt.TimeMin ?? 0);
if (pt.UpperTemp != null) UpperTempValues.Add(new ObservablePoint(time, (double)pt.UpperTemp.Value));
if (pt.LowerTemp != null) LowerTempValues.Add(new ObservablePoint(time, (double)pt.LowerTemp.Value));
if (pt.TorqueS != null) TorqueValues.Add(new ObservablePoint(time, (double)pt.TorqueS.Value));
}
if (UpperTempValues.Count > 0) UpperMoldTemp = UpperTempValues[^1].Y ?? 0;
if (LowerTempValues.Count > 0) LowerMoldTemp = LowerTempValues[^1].Y ?? 0;
if (TorqueValues.Count > 0) TorqueS = TorqueValues[^1].Y ?? 0;
await Task.CompletedTask;
}
private static Axis[] BuildTimeAxis() => new[]

View File

@@ -0,0 +1,104 @@
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using YY.Admin.Core.Entity;
namespace YY.Admin.ViewModels.RubberQuickTest;
public class RubberQuickTestRecordDetailDialogViewModel : BindableBase
{
private MesXslRubberQuickTestRecord? _record;
public MesXslRubberQuickTestRecord? Record
{
get => _record;
private set => SetProperty(ref _record, value);
}
private string? _inspectResultDisplay;
public string? InspectResultDisplay
{
get => _inspectResultDisplay;
private set => SetProperty(ref _inspectResultDisplay, value);
}
public ObservableCollection<MesXslRubberQuickTestRecordStdLine> StdLines { get; } = new();
public ObservableCollection<MesXslRubberQuickTestRecordRawLine> RawLines { get; } = new();
public ObservableCollection<ISeries> TemperatureSeries { get; }
public ObservableCollection<ISeries> TorqueSeries { get; }
public ObservableCollection<ObservablePoint> UpperTempValues { get; } = new();
public ObservableCollection<ObservablePoint> LowerTempValues { get; } = new();
public ObservableCollection<ObservablePoint> TorqueValues { get; } = new();
public Axis[] TemperatureXAxes { get; } = BuildTimeAxis();
public Axis[] TemperatureYAxes { get; } = BuildTempYAxis();
public Axis[] TorqueXAxes { get; } = BuildTimeAxis();
public Axis[] TorqueYAxes { get; } = BuildTorqueYAxis();
public RubberQuickTestRecordDetailDialogViewModel()
{
TemperatureSeries = new ObservableCollection<ISeries>
{
new LineSeries<ObservablePoint> { Name = "上模温度", Values = UpperTempValues, GeometrySize = 4, LineSmoothness = 0.3 },
new LineSeries<ObservablePoint> { Name = "下模温度", Values = LowerTempValues, GeometrySize = 4, LineSmoothness = 0.3 }
};
TorqueSeries = new ObservableCollection<ISeries>
{
new LineSeries<ObservablePoint> { Name = "S'(dNm)", Values = TorqueValues, GeometrySize = 4, LineSmoothness = 0.3 }
};
}
public void Initialize(MesXslRubberQuickTestRecord record)
{
Record = record;
InspectResultDisplay = record.InspectResult switch
{
"1" => "合格",
"0" => "不合格",
_ => record.InspectResultText ?? record.InspectResult ?? ""
};
StdLines.Clear();
foreach (var line in record.StdLineList ?? [])
StdLines.Add(line);
RawLines.Clear();
foreach (var line in record.RawLineList ?? [])
RawLines.Add(line);
UpperTempValues.Clear();
LowerTempValues.Clear();
TorqueValues.Clear();
foreach (var p in (record.ChartPointList ?? []).OrderBy(x => x.SortNo ?? 0))
{
var time = (double)(p.TimeMin ?? 0);
UpperTempValues.Add(new ObservablePoint(time, (double)(p.UpperTemp ?? 0)));
LowerTempValues.Add(new ObservablePoint(time, (double)(p.LowerTemp ?? 0)));
TorqueValues.Add(new ObservablePoint(time, (double)(p.TorqueS ?? 0)));
}
}
private static Axis[] BuildTimeAxis() => new[]
{
new Axis { Name = "时间(min)", MinLimit = 0, MaxLimit = 2, Labeler = v => v.ToString("0.0#") }
};
private static Axis[] BuildTempYAxis() => new[]
{
new Axis { Name = "温度(℃)", MinLimit = 189, MaxLimit = 201 }
};
private static readonly double[] TorqueYTicks = { 0.0, 3.0, 5.9, 8.9, 11.8, 14.8 };
private static Axis[] BuildTorqueYAxis() => new[]
{
new Axis
{
Name = "S'(dNm)",
MinLimit = 0,
MaxLimit = 14.8,
CustomSeparators = TorqueYTicks
}
};
}

View File

@@ -0,0 +1,227 @@
using HandyControl.Controls;
using Prism.Events;
using Prism.Navigation;
using System.Collections.ObjectModel;
using System.Diagnostics;
using YY.Admin.Core;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Events;
using YY.Admin.Core.Helper;
using YY.Admin.Core.Services;
using YY.Admin.Event;
using YY.Admin.Module;
using YY.Admin.Services.Service;
namespace YY.Admin.ViewModels.RubberQuickTest;
public class RubberQuickTestRecordListViewModel : BaseViewModel
{
private readonly IRubberQuickTestRecordService _recordService;
private readonly IJeecgDictSyncService _dictSyncService;
private SubscriptionToken? _changedToken;
private ObservableCollection<RubberQuickTestRecordListRow> _records = new();
public ObservableCollection<RubberQuickTestRecordListRow> Records
{
get => _records;
set => SetProperty(ref _records, value);
}
private long _total;
public long Total { get => _total; set => SetProperty(ref _total, value); }
private int _pageNo = 1;
public int PageNo { get => _pageNo; set => SetProperty(ref _pageNo, value); }
private int _pageSize = 20;
public int PageSize { get => _pageSize; set => SetProperty(ref _pageSize, value); }
private string? _filterRecordNo;
public string? FilterRecordNo { get => _filterRecordNo; set => SetProperty(ref _filterRecordNo, value); }
private string? _filterRubberMaterialName;
public string? FilterRubberMaterialName { get => _filterRubberMaterialName; set => SetProperty(ref _filterRubberMaterialName, value); }
private string? _filterPlanNo;
public string? FilterPlanNo { get => _filterPlanNo; set => SetProperty(ref _filterPlanNo, value); }
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand AddCommand { get; }
public DelegateCommand<RubberQuickTestRecordListRow> ViewDetailCommand { get; }
public DelegateCommand<RubberQuickTestRecordListRow> DeleteCommand { get; }
public DelegateCommand PrevPageCommand { get; }
public DelegateCommand NextPageCommand { get; }
public RubberQuickTestRecordListViewModel(
IRubberQuickTestRecordService recordService,
IJeecgDictSyncService dictSyncService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_recordService = recordService;
_dictSyncService = dictSyncService;
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
ResetCommand = new DelegateCommand(async () =>
{
FilterRecordNo = null;
FilterRubberMaterialName = null;
FilterPlanNo = null;
PageNo = 1;
await LoadAsync();
});
AddCommand = new DelegateCommand(OpenAddPage);
ViewDetailCommand = new DelegateCommand<RubberQuickTestRecordListRow>(async r => await ShowDetailAsync(r));
DeleteCommand = new DelegateCommand<RubberQuickTestRecordListRow>(
async r => await DeleteAsync(r),
r => r != null && r.CanDelete);
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
_changedToken = _eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>()
.Subscribe(async _ => await LoadAsync(), ThreadOption.UIThread);
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
try
{
await UIHelper.WaitForRenderAsync();
await LoadAsync();
}
catch (Exception ex)
{
Debug.WriteLine($"快检记录列表初始化失败: {ex.Message}");
}
}
public async Task LoadAsync()
{
try
{
IsLoading = true;
var result = await _recordService.PageAsync(
PageNo, PageSize, FilterRecordNo, FilterRubberMaterialName, FilterPlanNo);
var shiftMap = (await _dictSyncService.GetDictOptionsAsync("xslmes_rubber_quick_test_work_shift", includeAll: false))
.ToDictionary(x => x.Value, x => x.Key);
foreach (var r in result.Records)
{
if (!string.IsNullOrWhiteSpace(r.WorkShiftDisplay) && shiftMap.TryGetValue(r.WorkShiftDisplay, out var txt))
r.WorkShiftDisplay = txt;
}
Records = new ObservableCollection<RubberQuickTestRecordListRow>(result.Records);
Total = result.Total;
}
catch (Exception ex)
{
Growl.Error($"加载快检记录失败:{ex.Message}");
}
finally
{
IsLoading = false;
}
}
private void OpenAddPage()
{
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(new TabSource
{
Name = "新增胶料快检记录",
Icon = "\ue7de",
ViewName = "RubberQuickTestOperationView"
});
}
private async Task DeleteAsync(RubberQuickTestRecordListRow? row)
{
if (row == null || !row.CanDelete || string.IsNullOrWhiteSpace(row.LocalId))
return;
var label = row.RecordNo ?? row.LocalId;
var confirm = System.Windows.MessageBox.Show(
$"确定删除同步失败的快检记录「{label}」?\n此操作仅删除本地记录不可恢复。",
"确认删除",
System.Windows.MessageBoxButton.YesNo,
System.Windows.MessageBoxImage.Warning);
if (confirm != System.Windows.MessageBoxResult.Yes)
return;
try
{
if (_recordService.DeleteFailedLocal(row.LocalId))
{
Growl.Success("删除成功");
await LoadAsync();
}
else
{
Growl.Warning("仅同步失败的本地记录可删除");
}
}
catch (Exception ex)
{
Growl.Error($"删除失败:{ex.Message}");
}
}
private async Task ShowDetailAsync(RubberQuickTestRecordListRow? row)
{
if (row == null) return;
try
{
if (!string.IsNullOrWhiteSpace(row.LocalId))
{
if (_recordService.GetByLocalId(row.LocalId) == null)
{
Growl.Warning("未找到快检记录详情");
return;
}
}
else if (!string.IsNullOrWhiteSpace(row.MesId))
{
var remote = await _recordService.GetByIdAsync(row.MesId);
if (remote == null)
{
Growl.Warning("未找到快检记录详情");
return;
}
}
else
{
Growl.Warning("无法定位快检记录");
return;
}
var parameters = new NavigationParameters { { "readOnly", true } };
if (!string.IsNullOrWhiteSpace(row.LocalId))
parameters.Add("localId", row.LocalId);
else
parameters.Add("mesId", row.MesId!);
var title = row.RecordNo ?? row.LocalId ?? row.MesId ?? "详情";
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(new TabSource
{
Name = $"快检记录 {title}",
Icon = "\ue7de",
ViewName = "RubberQuickTestOperationView",
NavigationParameter = parameters
});
}
catch (Exception ex)
{
Growl.Error($"打开详情失败:{ex.Message}");
}
}
protected override void CleanUp()
{
base.CleanUp();
if (_changedToken != null)
_eventAggregator.GetEvent<RubberQuickTestRecordChangedEvent>().Unsubscribe(_changedToken);
}
}

View File

@@ -24,25 +24,34 @@
<Setter Property="Margin" Value="8,0,0,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="DataGridCellCenterTextStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="DataGridCellCenterEditStyle" TargetType="TextBox">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="52"/>
<RowDefinition Height="*"/>
<RowDefinition Height="56"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<Border Grid.Row="0" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Margin="-12,0,-12,8">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="20,0">
<Border Width="40" Height="40" CornerRadius="8" Background="{DynamicResource PrimaryBrush}" Margin="0,0,12,0">
<md:PackIcon Kind="Flask" Width="22" Height="22" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Margin="-12,0,-12,6">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="16,0">
<Border Width="32" Height="32" CornerRadius="6" Background="{DynamicResource PrimaryBrush}" Margin="0,0,10,0">
<md:PackIcon Kind="Flask" Width="18" Height="18" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="无转子流变仪 MDR S3L" FontSize="24" FontWeight="Bold"/>
<TextBlock Text="胶料快检记录 · 密炼快检试验操作台" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,4,0,0"/>
<TextBlock Text="无转子流变仪 MDR S3L" FontSize="18" FontWeight="Bold"/>
<TextBlock Text="胶料快检记录 · 密炼快检试验操作台" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,2,0,0"/>
</StackPanel>
</StackPanel>
</Border>
@@ -55,44 +64,70 @@
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<!-- 左侧:曲线 + 试验结果 -->
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto">
<StackPanel>
<Border Style="{StaticResource SectionBorderStyle}">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
<!-- 左侧:曲线图 : 试验结果 ≈ 3 : 2 -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{StaticResource SectionBorderStyle}" Padding="10,10,10,6" Margin="0,0,0,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,4">
<Border Width="4" Height="16" CornerRadius="2" Background="#1890ff"/>
<TextBlock Text="温度(℃)曲线图" Style="{StaticResource SectionTitleStyle}"/>
<Button Content="刷新演示" Command="{Binding RefreshChartDemoCommand}" Style="{StaticResource ButtonDefault}" Height="28" Padding="10,0" Margin="12,0,0,0"/>
<Button Content="刷新演示" Command="{Binding RefreshChartDemoCommand}" Style="{StaticResource ButtonDefault}" Height="26" Padding="8,0" Margin="10,0,0,0"/>
</StackPanel>
<TextBlock Text="{Binding ChartDemoHint}" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,6"/>
<Border Height="220" CornerRadius="4">
<TextBlock Grid.Row="1" Text="{Binding ChartDemoHint}" FontSize="10" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,4"
TextTrimming="CharacterEllipsis"/>
<Border Grid.Row="2" CornerRadius="4">
<lvc:CartesianChart Series="{Binding TemperatureSeries}"
XAxes="{Binding TemperatureXAxes}"
YAxes="{Binding TemperatureYAxes}"
LegendPosition="Top"/>
</Border>
</StackPanel>
</Grid>
</Border>
<Border Style="{StaticResource SectionBorderStyle}">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
<Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Padding="10,10,10,6" Margin="0,0,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,4">
<Border Width="4" Height="16" CornerRadius="2" Background="#1890ff"/>
<TextBlock Text="S'(dNm)曲线图" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel>
<Border Height="200" CornerRadius="4">
<Border Grid.Row="1" CornerRadius="4">
<lvc:CartesianChart Series="{Binding TorqueSeries}"
XAxes="{Binding TorqueXAxes}"
YAxes="{Binding TorqueYAxes}"
LegendPosition="Top"/>
</Border>
</StackPanel>
</Grid>
</Border>
</Grid>
<Border Style="{StaticResource SectionBorderStyle}">
<StackPanel>
<Grid Margin="0,0,0,8">
<Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Margin="0" Padding="10,10,10,8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,8">
<StackPanel Orientation="Horizontal">
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
<TextBlock Text="试验结果" Style="{StaticResource SectionTitleStyle}"/>
@@ -102,55 +137,75 @@
<Button Content="删除选中行" Command="{Binding RemoveInspectRowCommand}" Style="{StaticResource ButtonDanger}" Height="28" Padding="10,0"/>
</StackPanel>
</Grid>
<DataGrid x:Name="InspectResultGrid"
<DataGrid Grid.Row="1"
x:Name="InspectResultGrid"
ItemsSource="{Binding InspectRows}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
HeadersVisibility="Column"
MinHeight="160"
SelectionMode="Single"
IsReadOnly="False"
SelectionChanged="InspectResultGrid_SelectionChanged">
RowHeight="30"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
SelectionChanged="InspectResultGrid_SelectionChanged"
Style="{StaticResource CusDataGridStyle}"
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}">
</DataGrid>
<TextBlock Text="请手填各数据点检测值,系统将根据数据标准上下限自动判定合格/不合格" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,6,0,0"/>
</StackPanel>
<TextBlock Grid.Row="2" Text="请手填各数据点检测值,系统将根据数据标准上下限自动判定合格/不合格" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,6,0,0"/>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
<!-- 右侧 -->
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- 实时数据 -->
<Border Style="{StaticResource SectionBorderStyle}">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,12">
<Border Width="4" Height="18" CornerRadius="2" Background="#52c41a"/>
<!-- 右侧:实时数据 + 试验信息(上)与 数据标准(下 2*,与左侧试验结果同高) -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 实时数据(占用上部剩余空间) -->
<Border Grid.Row="0" Style="{StaticResource SectionBorderStyle}" Padding="10,8" Margin="0,0,0,8">
<Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<Border Width="4" Height="16" CornerRadius="2" Background="#52c41a"/>
<TextBlock Text="实时数据" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="1" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="上模温度(℃)" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,4"/>
<TextBlock Grid.Row="1" Text="{Binding UpperMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas"/>
<TextBlock Grid.Column="1" Text="下模温度(℃)" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,4"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding LowerMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="S'(dNm)" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,12,0,4"/>
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,32,0,0" Text="{Binding TorqueS, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas"/>
<StackPanel Grid.Column="0" Margin="0,0,6,0" VerticalAlignment="Center">
<TextBlock Text="上模温度(℃)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding UpperMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas" Margin="0,6,0,0" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="0,0,6,0" VerticalAlignment="Center">
<TextBlock Text="下模温度(℃)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding LowerMoldTemp, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas" Margin="0,6,0,0" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Column="2" VerticalAlignment="Center">
<TextBlock Text="S'(dNm)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding TorqueS, StringFormat={}{0:N2}}" FontSize="28" FontWeight="Bold" FontFamily="Consolas" Margin="0,6,0,0" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</Border>
<!-- 试验信息 -->
<Border Style="{StaticResource SectionBorderStyle}">
<Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Margin="0,0,0,0">
<StackPanel>
<Grid Margin="0,0,0,12">
<StackPanel Orientation="Horizontal">
@@ -310,48 +365,50 @@
TextWrapping="Wrap"/>
</StackPanel>
</Border>
</Grid>
<!-- 数据标准 -->
<Border Style="{StaticResource SectionBorderStyle}">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<!-- 数据标准(高度与左侧试验结果一致) -->
<Border Grid.Row="1" Style="{StaticResource SectionBorderStyle}" Margin="0" Padding="10,10,10,8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<Border Width="4" Height="18" CornerRadius="2" Background="#faad14"/>
<TextBlock Text="数据标准" Style="{StaticResource SectionTitleStyle}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding DataPointColumns}"
<DataGrid Grid.Row="1"
ItemsSource="{Binding DataPointColumns}"
AutoGenerateColumns="False"
IsReadOnly="True"
HeadersVisibility="Column"
MaxHeight="240"
ColumnWidth="*">
RowHeight="30"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
ColumnWidth="*"
Style="{StaticResource CusDataGridStyle}"
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}">
<DataGrid.Columns>
<DataGridTextColumn Header="数据点" Binding="{Binding PointName}" Width="5*"/>
<DataGridTextColumn Header="下限值" Binding="{Binding LowerLimit}" Width="2.5*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="上限值" Binding="{Binding UpperLimit}" Width="2.5*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="数据点" Binding="{Binding PointName}" Width="5*"
ElementStyle="{StaticResource DataGridCellCenterTextStyle}"/>
<DataGridTextColumn Header="下限值" Binding="{Binding LowerLimit, StringFormat={}{0:N2}}" Width="2.5*"
ElementStyle="{StaticResource DataGridCellCenterTextStyle}"/>
<DataGridTextColumn Header="上限值" Binding="{Binding UpperLimit, StringFormat={}{0:N2}}" Width="2.5*"
ElementStyle="{StaticResource DataGridCellCenterTextStyle}"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</Grid>
<!-- 底部操作 -->
<Border Grid.Row="2" Background="{DynamicResource RegionBrush}" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0" Margin="-12,8,-12,-12">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="20,0">
<Button Content="保存胶料快检记录" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Height="36" Width="160"/>
<Button Content="保存胶料快检记录" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Height="36" Width="160"
IsEnabled="{Binding CanSave}"
Visibility="{Binding ShowSaveButton, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</StackPanel>
</Border>
</Grid>

View File

@@ -62,6 +62,9 @@ public partial class RubberQuickTestOperationView : UserControl
var vm = _vm ?? DataContext as RubberQuickTestOperationViewModel;
if (vm == null) return;
var centerTextStyle = (Style)FindResource("DataGridCellCenterTextStyle");
var centerEditStyle = (Style)FindResource("DataGridCellCenterEditStyle");
InspectResultGrid.Columns.Clear();
InspectResultGrid.Columns.Add(new DataGridTextColumn
@@ -72,7 +75,8 @@ public partial class RubberQuickTestOperationView : UserControl
Mode = BindingMode.OneWay
},
IsReadOnly = true,
Width = 80
Width = 80,
ElementStyle = centerTextStyle
});
for (int i = 0; i < vm.DataPointColumns.Count; i++)
@@ -83,17 +87,23 @@ public partial class RubberQuickTestOperationView : UserControl
? $"数据点{columnIndex + 1}"
: col.PointName;
var valueBinding = new Binding($"Cells[{columnIndex}].Value")
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode = vm.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay,
TargetNullValue = string.Empty
};
if (vm.IsReadOnly)
valueBinding.StringFormat = "N2";
InspectResultGrid.Columns.Add(new DataGridTextColumn
{
Header = header,
Binding = new Binding($"Cells[{columnIndex}].Value")
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode = BindingMode.TwoWay,
TargetNullValue = string.Empty
},
Binding = valueBinding,
Width = 100,
IsReadOnly = false
IsReadOnly = vm.IsReadOnly,
ElementStyle = centerTextStyle,
EditingElementStyle = centerEditStyle
});
}
@@ -105,7 +115,8 @@ public partial class RubberQuickTestOperationView : UserControl
Mode = BindingMode.OneWay
},
IsReadOnly = true,
Width = 90
Width = 90,
ElementStyle = centerTextStyle
});
}

View File

@@ -0,0 +1,130 @@
<UserControl x:Class="YY.Admin.Views.RubberQuickTest.RubberQuickTestRecordDetailDialogView"
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"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:prism="http://prismlibrary.com/"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Width="1100"
MinHeight="680">
<Grid Background="{DynamicResource ThirdlyRegionBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<hc:SimplePanel Margin="20">
<TextBlock FontSize="18" Foreground="{DynamicResource PrimaryTextBrush}"
Text="胶料快检记录详情" HorizontalAlignment="Left"/>
<Button Width="22" Height="22" Command="hc:ControlCommands.Close"
Style="{StaticResource ButtonIcon}" Foreground="{DynamicResource PrimaryBrush}"
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"
Padding="0" HorizontalAlignment="Right" VerticalAlignment="Top"/>
</hc:SimplePanel>
<hc:ScrollViewer Grid.Row="1" IsInertiaEnabled="True">
<StackPanel Margin="20,0,20,0">
<hc:Row Gutter="10">
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.RecordNo, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="快检记录号" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.ProductionDate, Mode=OneWay, StringFormat={}{0:yyyy-MM-dd}}" IsReadOnly="True"
hc:InfoElement.Title="密炼日期" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.ProdEquipmentName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="密炼机台" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.ProductionPlanNo, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="密炼计划" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.RubberMaterialName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="胶料名称" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.StdName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="实验标准" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.TestMethodName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="实验方法" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.QuickTestTypeName, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="实验类型" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.TrainNo, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="车次" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.InspectTimes, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="试验次数" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.InspectorRealname, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="检验人" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding Record.CreateTime, Mode=OneWay, StringFormat={}{0:yyyy-MM-dd HH:mm}}" IsReadOnly="True"
hc:InfoElement.Title="检验日期" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
<hc:Col Span="8">
<hc:TextBox Text="{Binding InspectResultDisplay, Mode=OneWay}" IsReadOnly="True"
hc:InfoElement.Title="是否合格" hc:InfoElement.TitleWidth="90" Margin="0,0,0,8"/>
</hc:Col>
</hc:Row>
<TextBlock Text="温度曲线" FontWeight="SemiBold" Margin="0,8,0,6"/>
<Border Height="200" Margin="0,0,0,12">
<lvc:CartesianChart Series="{Binding TemperatureSeries}"
XAxes="{Binding TemperatureXAxes}"
YAxes="{Binding TemperatureYAxes}"
LegendPosition="Top"/>
</Border>
<TextBlock Text="S'曲线" FontWeight="SemiBold" Margin="0,0,0,6"/>
<Border Height="200" Margin="0,0,0,12">
<lvc:CartesianChart Series="{Binding TorqueSeries}"
XAxes="{Binding TorqueXAxes}"
YAxes="{Binding TorqueYAxes}"
LegendPosition="Top"/>
</Border>
<TextBlock Text="数据标准" FontWeight="SemiBold" Margin="0,0,0,6"/>
<DataGrid ItemsSource="{Binding StdLines}" AutoGenerateColumns="False" IsReadOnly="True"
MaxHeight="180" Margin="0,0,0,12">
<DataGrid.Columns>
<DataGridTextColumn Header="数据点" Binding="{Binding PointName}" Width="*"/>
<DataGridTextColumn Header="下限值" Binding="{Binding LowerLimit}" Width="100"/>
<DataGridTextColumn Header="上限值" Binding="{Binding UpperLimit}" Width="100"/>
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="试验结果" FontWeight="SemiBold" Margin="0,0,0,6"/>
<DataGrid ItemsSource="{Binding RawLines}" AutoGenerateColumns="False" IsReadOnly="True"
MaxHeight="220">
<DataGrid.Columns>
<DataGridTextColumn Header="编号" Binding="{Binding RowNo}" Width="80"/>
<DataGridTextColumn Header="数据点" Binding="{Binding InspectItem}" Width="*"/>
<DataGridTextColumn Header="检测值" Binding="{Binding InspectValue}" Width="100"/>
<DataGridTextColumn Header="行结果" Binding="{Binding RowInspectResult}" Width="80"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</hc:ScrollViewer>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="20">
<Button Content="关闭" Command="hc:ControlCommands.Close" Style="{StaticResource ButtonDefault}" Width="90"/>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,9 @@
namespace YY.Admin.Views.RubberQuickTest;
public partial class RubberQuickTestRecordDetailDialogView
{
public RubberQuickTestRecordDetailDialogView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,109 @@
<UserControl x:Class="YY.Admin.Views.RubberQuickTest.RubberQuickTestRecordListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid Style="{StaticResource BaseViewStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" CornerRadius="4" Margin="0 0 -10 0">
<hc:Row>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=4, Xl=4}">
<hc:TextBox Text="{Binding FilterRecordNo, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10" hc:InfoElement.Title="快检记录号"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitleWidth="80"
hc:InfoElement.ShowClearButton="True">
<hc:TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SearchCommand}"/>
</hc:TextBox.InputBindings>
</hc:TextBox>
</hc:Col>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=4, Xl=4}">
<hc:TextBox Text="{Binding FilterRubberMaterialName, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10" hc:InfoElement.Title="胶料名称"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitleWidth="80"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=4, Xl=4}">
<hc:TextBox Text="{Binding FilterPlanNo, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10" hc:InfoElement.Title="密炼计划"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.TitleWidth="80"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
</hc:Row>
</Border>
<Border Grid.Row="1" Margin="0,10">
<hc:UniformSpacingPanel Spacing="10">
<Button Style="{StaticResource ButtonPrimary}" Command="{Binding SearchCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Search"/>
<TextBlock Text="搜索" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource ButtonDefault}" Command="{Binding ResetCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Refresh"/>
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource ButtonSuccess}" Command="{Binding AddCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Plus"/>
<TextBlock Text="新增" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
</hc:UniformSpacingPanel>
</Border>
<DataGrid Grid.Row="2" ItemsSource="{Binding Records}" AutoGenerateColumns="False" IsReadOnly="True"
CanUserAddRows="False" SelectionMode="Single" HeadersVisibility="All"
Style="{StaticResource CusDataGridStyle}" ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}">
<DataGrid.Columns>
<DataGridTextColumn Header="快检记录号" Binding="{Binding RecordNo}" Width="160"/>
<DataGridTextColumn Header="密炼日期" Binding="{Binding ProductionDate, StringFormat='yyyy-MM-dd'}" Width="100"/>
<DataGridTextColumn Header="密炼机台" Binding="{Binding ProdEquipmentName}" Width="100"/>
<DataGridTextColumn Header="班次" Binding="{Binding WorkShiftDisplay}" Width="70"/>
<DataGridTextColumn Header="密炼计划" Binding="{Binding ProductionPlanNo}" Width="110"/>
<DataGridTextColumn Header="胶料名称" Binding="{Binding RubberMaterialName}" Width="120"/>
<DataGridTextColumn Header="实验标准" Binding="{Binding StdName}" Width="120"/>
<DataGridTextColumn Header="实验方法" Binding="{Binding TestMethodName}" Width="100"/>
<DataGridTextColumn Header="实验类型" Binding="{Binding QuickTestTypeName}" Width="100"/>
<DataGridTextColumn Header="车次" Binding="{Binding TrainNo}" Width="70"/>
<DataGridTextColumn Header="试验次数" Binding="{Binding InspectTimes}" Width="80"/>
<DataGridTextColumn Header="检验人" Binding="{Binding InspectorRealname}" Width="80"/>
<DataGridTextColumn Header="检验日期" Binding="{Binding InspectDate, StringFormat='yyyy-MM-dd HH:mm'}" Width="130"/>
<DataGridTextColumn Header="是否合格" Binding="{Binding InspectResultDisplay}" Width="80"/>
<DataGridTextColumn Header="同步状态" Binding="{Binding SyncStatusDisplay}" Width="80"/>
<DataGridTemplateColumn Header="操作" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="详情" Command="{Binding DataContext.ViewDetailCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Style="{StaticResource ButtonPrimary}" Height="26" Padding="8,0" Margin="0,0,6,0"/>
<Button Content="删除" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}" Style="{StaticResource ButtonDanger}" Height="26" Padding="8,0"
Visibility="{Binding CanDelete, Converter={StaticResource Boolean2VisibilityConverter}}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<TextBlock Text="{Binding Total, StringFormat=共 {0} 条}" VerticalAlignment="Center" Margin="0,0,16,0"
Foreground="{DynamicResource SecondaryTextBrush}"/>
<Button Content="上一页" Command="{Binding PrevPageCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,4,0" Width="80"/>
<TextBlock Text="{Binding PageNo, StringFormat=第 {0} 页}" VerticalAlignment="Center" Margin="8,0"/>
<Button Content="下一页" Command="{Binding NextPageCommand}" Style="{StaticResource ButtonDefault}" Width="80"/>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,11 @@
using System.Windows.Controls;
namespace YY.Admin.Views.RubberQuickTest;
public partial class RubberQuickTestRecordListView : UserControl
{
public RubberQuickTestRecordListView()
{
InitializeComponent();
}
}