diff --git a/yy-admin-master/YY.Admin.Core/Core/Services/IRubberQuickTestRecordService.cs b/yy-admin-master/YY.Admin.Core/Core/Services/IRubberQuickTestRecordService.cs index ca20c70d..b4ab0d93 100644 --- a/yy-admin-master/YY.Admin.Core/Core/Services/IRubberQuickTestRecordService.cs +++ b/yy-admin-master/YY.Admin.Core/Core/Services/IRubberQuickTestRecordService.cs @@ -18,12 +18,25 @@ public interface IRubberQuickTestRecordService Task SaveAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct = default); - /// 删除本地同步失败的快检记录(已同步或待同步不可删) - bool DeleteFailedLocal(string localId); + /// 更新本地未同步快检记录 + Task UpdateLocalAsync(string localId, MesXslRubberQuickTestRecord entity, CancellationToken ct = default); + + /// 删除本地未同步的快检记录(已同步不可删) + bool DeleteUnsyncedLocal(string localId); + + /// 将本地未同步记录推送到 MES(须已联网且密炼计划信息完整) + Task SyncLocalAsync(string localId, CancellationToken ct = default); string GenerateRecordNo(string rubberMaterialName); } +public class RubberQuickTestRecordSyncResult +{ + public bool Success { get; set; } + public string? Message { get; set; } + public string? RecordNo { get; set; } +} + public record RubberQuickTestRecordPageResult( List Records, long Total, diff --git a/yy-admin-master/YY.Admin.Core/Entity/RubberQuickTestRecordListRow.cs b/yy-admin-master/YY.Admin.Core/Entity/RubberQuickTestRecordListRow.cs index 324decf4..c73e229c 100644 --- a/yy-admin-master/YY.Admin.Core/Entity/RubberQuickTestRecordListRow.cs +++ b/yy-admin-master/YY.Admin.Core/Entity/RubberQuickTestRecordListRow.cs @@ -23,10 +23,16 @@ public class RubberQuickTestRecordListRow public string SyncStatusDisplay => SyncStatus switch { "Synced" => "已同步", - "Failed" => "失败", - _ => "待同步" + "Failed" => "同步失败", + _ => "未同步" }; - /// 仅本地同步失败记录可删除 - public bool CanDelete => SyncStatus == "Failed" && !string.IsNullOrWhiteSpace(LocalId); + /// 本地未同步记录可删除 + public bool CanDelete => SyncStatus != "Synced" && !string.IsNullOrWhiteSpace(LocalId); + + /// 本地未同步记录可手动同步 + public bool CanSync => SyncStatus != "Synced" && !string.IsNullOrWhiteSpace(LocalId); + + /// 本地未同步记录可编辑 + public bool CanEdit => CanSync; } diff --git a/yy-admin-master/YY.Admin.Services/Service/MesQuickTestDataPollManager.cs b/yy-admin-master/YY.Admin.Services/Service/MesQuickTestDataPollManager.cs new file mode 100644 index 00000000..5e303623 --- /dev/null +++ b/yy-admin-master/YY.Admin.Services/Service/MesQuickTestDataPollManager.cs @@ -0,0 +1,62 @@ +using YY.Admin.Core; + +namespace YY.Admin.Services.Service; + +/// +/// 胶料快检相关 MES 只读数据(实验标准、密炼计划)专用轮询管理器。 +/// +public class MesQuickTestDataPollManager : ISingletonDependency +{ + public static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(2); + + private readonly List<(string Name, Func Task)> _tasks = []; + private readonly Timer _timer; + private readonly ILoggerService _logger; + private volatile bool _tickRunning; + + public MesQuickTestDataPollManager(ILoggerService logger) + { + _logger = logger; + _timer = new Timer(OnTick, null, PollInterval, PollInterval); + _logger.Information($"[快检数据轮询] 已启动,间隔 {PollInterval.TotalSeconds} 秒"); + } + + public void Register(string name, Func pollTask) + { + lock (_tasks) + _tasks.Add((name, pollTask)); + _logger.Information($"[快检数据轮询] 注册任务: {name}"); + } + + private void OnTick(object? _) + { + if (_tickRunning) return; + + _ = Task.Run(async () => + { + if (_tickRunning) return; + _tickRunning = true; + try + { + List<(string Name, Func Task)> snapshot; + lock (_tasks) snapshot = [.. _tasks]; + + foreach (var (name, task) in snapshot) + { + try + { + await task().ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.Warning($"[快检数据轮询] 任务 [{name}] 异常: {ex.Message}"); + } + } + } + finally + { + _tickRunning = false; + } + }); + } +} diff --git a/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanService.cs b/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanService.cs index e1cedc17..62eaa518 100644 --- a/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanService.cs @@ -53,17 +53,13 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle LoadCacheFromDisk(); _logger.Information($"[密炼计划] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}"); - - _networkMonitor.StatusChanged += OnNetworkStatusChanged; - if (_networkMonitor.IsOnline) - _ = Task.Run(() => SyncFromRemoteAsync(CancellationToken.None)); } private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); private int DefaultTenantId => (int?)_configuration.GetValue("JeecgIntegration:DefaultTenantId") ?? 1002; private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi"); - public async Task PageAsync( + public Task PageAsync( int pageNo, int pageSize, DateTime? planDateFrom = null, DateTime? planDateTo = null, @@ -73,18 +69,7 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle string? materialName = null, CancellationToken ct = default) { - if (_networkMonitor.IsOnline) - { - try - { - await SyncFromRemoteAsync(ct).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.Warning($"[密炼计划] 列表拉取失败,使用本地缓存:{ex.Message}"); - } - } - + ct.ThrowIfCancellationRequested(); List source; lock (_cacheLock) source = _localCache.Select(Clone).ToList(); @@ -95,25 +80,14 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle .Skip(Math.Max(0, (pageNo - 1) * pageSize)) .Take(pageSize) .ToList(); - return new MixingProductionPlanPageResult(records, total, pageNo, pageSize); + return Task.FromResult(new MixingProductionPlanPageResult(records, total, pageNo, pageSize)); } - public async Task> GetAllCachedAsync(CancellationToken ct = default) + public Task> GetAllCachedAsync(CancellationToken ct = default) { - if (_networkMonitor.IsOnline) - { - try - { - await SyncFromRemoteAsync(ct).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.Warning($"[密炼计划] 全量拉取失败,使用本地缓存:{ex.Message}"); - } - } - + ct.ThrowIfCancellationRequested(); lock (_cacheLock) - return _localCache.Select(Clone).ToList(); + return Task.FromResult(_localCache.Select(Clone).ToList()); } public async Task SyncFromRemoteAsync(CancellationToken ct = default) @@ -190,25 +164,6 @@ public class MixingProductionPlanService : IMixingProductionPlanService, ISingle } } - private void OnNetworkStatusChanged(bool isOnline) - { - if (!isOnline) return; - _ = Task.Run(async () => - { - try - { - if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false)) - return; - _eventAggregator.GetEvent() - .Publish(new MixingProductionPlanChangedPayload { Action = "reconnect" }); - } - catch (Exception ex) - { - _logger.Warning($"[密炼计划] 重连同步失败:{ex.Message}"); - } - }); - } - private static bool IsPlanContentEqual(MesXslMixingProductionPlan a, MesXslMixingProductionPlan b) => string.Equals(GetPlanFingerprint(a), GetPlanFingerprint(b), StringComparison.Ordinal); diff --git a/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanSyncCoordinator.cs b/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanSyncCoordinator.cs index 2e97e3c6..4a31fda0 100644 --- a/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanSyncCoordinator.cs +++ b/yy-admin-master/YY.Admin.Services/Service/MixingProductionPlan/MixingProductionPlanSyncCoordinator.cs @@ -3,33 +3,58 @@ using System.Text.Json; using YY.Admin.Core; using YY.Admin.Core.Events; using YY.Admin.Core.Services; +using YY.Admin.Services.Service; namespace YY.Admin.Services.Service.MixingProductionPlan; public class MixingProductionPlanSyncCoordinator : ISingletonDependency { private readonly IEventAggregator _eventAggregator; + private readonly IMixingProductionPlanService _planService; private readonly ILoggerService _logger; public MixingProductionPlanSyncCoordinator( IEventAggregator eventAggregator, - SyncPollManager pollManager, + IMixingProductionPlanService planService, + MesQuickTestDataPollManager pollManager, ILoggerService logger) { _eventAggregator = eventAggregator; + _planService = planService; _logger = logger; _eventAggregator.GetEvent() .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); - pollManager.Register("密炼计划", () => - { - _eventAggregator.GetEvent() - .Publish(new MixingProductionPlanChangedPayload { Action = "poll" }); - return Task.CompletedTask; - }); + _eventAggregator.GetEvent() + .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread); + + pollManager.Register("密炼计划", () => PollSyncAsync("poll")); _logger.Information("[密炼计划] MixingProductionPlanSyncCoordinator 已启动"); + _ = PollSyncAsync("startup"); + } + + private async Task PollSyncAsync(string action) + { + try + { + if (!await _planService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false)) + return; + + _eventAggregator.GetEvent() + .Publish(new MixingProductionPlanChangedPayload { Action = action }); + } + catch (Exception ex) + { + _logger.Warning($"[密炼计划] 轮询同步失败:{ex.Message}"); + } + } + + private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload) + { + if (!payload.IsOnline) return; + _ = Task.Run(() => PollSyncAsync("reconnect")); } private void OnRemoteCommand(RemoteCommandPayload payload) @@ -53,7 +78,18 @@ public class MixingProductionPlanSyncCoordinator : ISingletonDependency MixingProductionPlanId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null }; _logger.Information($"[密炼计划] STOMP action={changed.Action}, id={changed.MixingProductionPlanId}"); - _eventAggregator.GetEvent().Publish(changed); + _ = Task.Run(async () => + { + try + { + if (await _planService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false)) + _eventAggregator.GetEvent().Publish(changed); + } + catch (Exception ex) + { + _logger.Warning($"[密炼计划] STOMP 同步失败:{ex.Message}"); + } + }); } catch (Exception ex) { diff --git a/yy-admin-master/YY.Admin.Services/Service/RubberQuickTest/RubberQuickTestRecordService.cs b/yy-admin-master/YY.Admin.Services/Service/RubberQuickTest/RubberQuickTestRecordService.cs index 61c365c5..f8b9baeb 100644 --- a/yy-admin-master/YY.Admin.Services/Service/RubberQuickTest/RubberQuickTestRecordService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/RubberQuickTest/RubberQuickTestRecordService.cs @@ -54,10 +54,6 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing LoadCacheFromDisk(); _logger.Information($"[快检记录同步] 初始化完成,本地记录={_localItems.Count}"); - - _networkMonitor.StatusChanged += OnNetworkStatusChanged; - if (_networkMonitor.IsOnline) - _ = Task.Run(() => PushPendingAsync(CancellationToken.None)); } private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); @@ -180,7 +176,79 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing }; } - public bool DeleteFailedLocal(string localId) + public async Task UpdateLocalAsync(string localId, MesXslRubberQuickTestRecord entity, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(localId)) + throw new ArgumentException("无效的记录标识", nameof(localId)); + + var record = CloneRecord(entity); + RubberQuickTestRecordLocalItem? existing; + lock (_cacheLock) + { + existing = _localItems.FirstOrDefault(x => + string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase)); + } + + if (existing == null) + throw new InvalidOperationException("未找到本地快检记录"); + + if (string.Equals(existing.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException("已同步记录不可编辑"); + + record.CreateTime = existing.Record.CreateTime ?? record.CreateTime ?? DateTime.Now; + record.InspectTime = record.InspectTime ?? record.CreateTime; + if (string.IsNullOrWhiteSpace(record.RecordNo)) + record.RecordNo = existing.Record.RecordNo; + + var item = CloneLocalItem(existing); + item.Record = record; + item.SyncStatus = "Pending"; + item.SyncError = null; + + if (_networkMonitor.IsOnline && HasCompletePlanInfo(record)) + { + 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) + { + var target = _localItems.FirstOrDefault(x => + string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase)); + if (target == null) + throw new InvalidOperationException("未找到本地快检记录"); + + target.Record = CloneRecord(item.Record); + target.SyncStatus = item.SyncStatus; + target.SyncError = item.SyncError; + SaveCacheToDiskUnsafe(); + } + + _eventAggregator.GetEvent() + .Publish(new RubberQuickTestRecordChangedPayload { Action = "update", RecordId = localId }); + + return new RubberQuickTestRecordSaveResult + { + LocalId = localId, + SyncStatus = item.SyncStatus, + Record = CloneRecord(item.Record) + }; + } + + public bool DeleteUnsyncedLocal(string localId) { if (string.IsNullOrWhiteSpace(localId)) return false; @@ -189,7 +257,7 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing { var item = _localItems.FirstOrDefault(x => string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase)); - if (item == null || !string.Equals(item.SyncStatus, "Failed", StringComparison.OrdinalIgnoreCase)) + if (item == null || string.Equals(item.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase)) return false; removed = _localItems.Remove(item); @@ -201,10 +269,92 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing _eventAggregator.GetEvent() .Publish(new RubberQuickTestRecordChangedPayload { Action = "delete", RecordId = localId }); - _logger.Information($"[快检记录删除] 已删除同步失败本地记录 localId={localId}"); + _logger.Information($"[快检记录删除] 已删除本地未同步记录 localId={localId}"); return true; } + public async Task SyncLocalAsync(string localId, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(localId)) + return new RubberQuickTestRecordSyncResult { Success = false, Message = "无效的记录标识" }; + + if (!_networkMonitor.IsOnline) + return new RubberQuickTestRecordSyncResult { Success = false, Message = "当前离线,无法同步" }; + + RubberQuickTestRecordLocalItem? item; + lock (_cacheLock) + { + item = _localItems.FirstOrDefault(x => + string.Equals(x.LocalId, localId.Trim(), StringComparison.OrdinalIgnoreCase)); + } + + if (item == null) + return new RubberQuickTestRecordSyncResult { Success = false, Message = "未找到本地快检记录" }; + + if (string.Equals(item.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase)) + return new RubberQuickTestRecordSyncResult { Success = true, Message = "记录已同步", RecordNo = item.Record.RecordNo }; + + if (!HasCompletePlanInfo(item.Record)) + return new RubberQuickTestRecordSyncResult { Success = false, Message = PlanInfoValidationMessage }; + + if (!await _syncLock.WaitAsync(0, ct).ConfigureAwait(false)) + return new RubberQuickTestRecordSyncResult { Success = false, Message = "同步正在进行中,请稍后再试" }; + + try + { + var recordNo = await RemoteAddAsync(item.Record, ct).ConfigureAwait(false); + lock (_cacheLock) + { + var target = _localItems.FirstOrDefault(x => x.LocalId == item.LocalId); + if (target == null) + return new RubberQuickTestRecordSyncResult { Success = false, Message = "记录已被删除" }; + + if (!string.IsNullOrWhiteSpace(recordNo)) + target.Record.RecordNo = recordNo; + target.SyncStatus = "Synced"; + target.SyncError = null; + SaveCacheToDiskUnsafe(); + } + + _eventAggregator.GetEvent() + .Publish(new RubberQuickTestRecordChangedPayload { Action = "sync", RecordId = item.LocalId }); + + return new RubberQuickTestRecordSyncResult + { + Success = true, + Message = "同步成功", + RecordNo = recordNo ?? item.Record.RecordNo + }; + } + 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(); + } + } + + _logger.Warning($"[快检记录同步] 手动同步失败 localId={localId}: {ex.Message}"); + return new RubberQuickTestRecordSyncResult { Success = false, Message = ex.Message }; + } + finally + { + _syncLock.Release(); + } + } + + private const string PlanInfoValidationMessage = "请维护密炼计划信息(密炼计划、密炼日期、密炼机台)"; + + private static bool HasCompletePlanInfo(MesXslRubberQuickTestRecord record) => + !string.IsNullOrWhiteSpace(record.ProductionPlanNo) + && record.ProductionDate != null + && !string.IsNullOrWhiteSpace(record.ProdEquipmentName); + public string GenerateRecordNo(string rubberMaterialName) { var dateStr = DateTime.Now.ToString("yyyyMMdd"); @@ -327,61 +477,6 @@ public class RubberQuickTestRecordService : IRubberQuickTestRecordService, ISing return result.GetProperty("records").Deserialize>(_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 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 ApplyFilters( List source, string? filterRecordNo, diff --git a/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdService.cs b/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdService.cs index a2cdbf11..edb4d526 100644 --- a/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdService.cs +++ b/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdService.cs @@ -54,35 +54,20 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD LoadCacheFromDisk(); _logger.Information($"[快检实验标准] 服务初始化,缓存={_localCache.Count},在线={_networkMonitor.IsOnline}"); - - _networkMonitor.StatusChanged += OnNetworkStatusChanged; - if (_networkMonitor.IsOnline) - _ = Task.Run(() => SyncFromRemoteAsync(CancellationToken.None)); } private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); private int DefaultTenantId => (int?)_configuration.GetValue("JeecgIntegration:DefaultTenantId") ?? 1002; private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi"); - public async Task PageAsync( + public Task PageAsync( int pageNo, int pageSize, string? stdName = null, string? rubberMaterialName = null, string? enableStatus = null, CancellationToken ct = default) { - if (_networkMonitor.IsOnline) - { - try - { - await SyncFromRemoteAsync(ct).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.Warning($"[快检实验标准] 列表拉取失败,使用本地缓存:{ex.Message}"); - } - } - + ct.ThrowIfCancellationRequested(); List source; lock (_cacheLock) source = _localCache.Select(CloneMain).ToList(); @@ -93,7 +78,7 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD .Skip(Math.Max(0, (pageNo - 1) * pageSize)) .Take(pageSize) .ToList(); - return new RubberQuickTestStdPageResult(records, total, pageNo, pageSize); + return Task.FromResult(new RubberQuickTestStdPageResult(records, total, pageNo, pageSize)); } public async Task GetByIdAsync(string id, CancellationToken ct = default) @@ -199,6 +184,8 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD lock (_cacheLock) localSnapshot = _localCache.Select(CloneMain).ToList(); + await HydrateStdLinesAsync(records, localSnapshot, ct).ConfigureAwait(false); + var (merged, stats) = MesReadOnlyCacheMergeHelper.Merge( localSnapshot, records, @@ -233,18 +220,76 @@ public class RubberQuickTestStdService : IRubberQuickTestStdService, ISingletonD } } - private void OnNetworkStatusChanged(bool isOnline) + private async Task HydrateStdLinesAsync( + List remoteRecords, + List localSnapshot, + CancellationToken ct) { - if (!isOnline) return; - _ = Task.Run(async () => - { - if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false)) - return; - _eventAggregator.GetEvent() - .Publish(new RubberQuickTestStdChangedPayload { Action = "reconnect" }); - }); + var localById = localSnapshot + .Where(x => !string.IsNullOrWhiteSpace(x.Id)) + .ToDictionary(x => x.Id!, StringComparer.OrdinalIgnoreCase); + + using var gate = new SemaphoreSlim(8); + var tasks = remoteRecords + .Where(r => !string.IsNullOrWhiteSpace(r.Id)) + .Select(async record => + { + await gate.WaitAsync(ct).ConfigureAwait(false); + try + { + localById.TryGetValue(record.Id!, out var local); + if (local != null + && IsStdListContentEqual(record, local) + && local.LineList is { Count: > 0 }) + { + record.LineList = local.LineList.Select(CloneStdLine).ToList(); + return; + } + + var lines = await FetchLinesByStdIdAsync(record.Id!, ct).ConfigureAwait(false); + record.LineList = lines.Count > 0 + ? lines + : local?.LineList?.Select(CloneStdLine).ToList(); + } + finally + { + gate.Release(); + } + }); + + await Task.WhenAll(tasks).ConfigureAwait(false); } + private async Task> FetchLinesByStdIdAsync(string stdId, CancellationToken ct) + { + var url = $"{BaseUrl}/xslmes/mesXslRubberQuickTestStd/anon/queryLineListByStdId?id={Uri.EscapeDataString(stdId)}"; + using var client = CreateClient(); + using var resp = await client.GetAsync(url, ct).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) + return []; + + var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("result", out var resultEl)) + return []; + + return resultEl.Deserialize>(_jsonOpts) ?? []; + } + + private static MesXslRubberQuickTestStdLine CloneStdLine(MesXslRubberQuickTestStdLine l) => new() + { + Id = l.Id, + StdId = l.StdId, + DataPointId = l.DataPointId, + PointName = l.PointName, + LowerLimit = l.LowerLimit, + UpperLimit = l.UpperLimit, + LowerWarn = l.LowerWarn, + UpperWarn = l.UpperWarn, + TargetValue = l.TargetValue, + SortNo = l.SortNo + }; + private static List ApplyFilters( List source, string? stdName, diff --git a/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdSyncCoordinator.cs b/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdSyncCoordinator.cs index b396f3d0..a1b41046 100644 --- a/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdSyncCoordinator.cs +++ b/yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdSyncCoordinator.cs @@ -2,33 +2,52 @@ using Prism.Events; using System.Text.Json; using YY.Admin.Core; using YY.Admin.Core.Events; +using YY.Admin.Core.Services; +using YY.Admin.Services.Service; namespace YY.Admin.Services.Service.RubberQuickTestStd; public class RubberQuickTestStdSyncCoordinator : ISingletonDependency { private readonly IEventAggregator _eventAggregator; + private readonly IRubberQuickTestStdService _stdService; private readonly ILoggerService _logger; public RubberQuickTestStdSyncCoordinator( IEventAggregator eventAggregator, - SyncPollManager pollManager, + IRubberQuickTestStdService stdService, + MesQuickTestDataPollManager pollManager, ILoggerService logger) { _eventAggregator = eventAggregator; + _stdService = stdService; _logger = logger; _eventAggregator.GetEvent() .Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread); - pollManager.Register("胶料快检实验标准", () => - { - _eventAggregator.GetEvent() - .Publish(new RubberQuickTestStdChangedPayload { Action = "poll" }); - return Task.CompletedTask; - }); + _eventAggregator.GetEvent() + .Subscribe(OnNetworkStatusChanged, ThreadOption.BackgroundThread); + + pollManager.Register("胶料快检实验标准", () => PollSyncAsync("poll")); _logger.Information("[快检实验标准] RubberQuickTestStdSyncCoordinator 已启动"); + _ = PollSyncAsync("startup"); + } + + private async Task PollSyncAsync(string action) + { + if (!await _stdService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false)) + return; + + _eventAggregator.GetEvent() + .Publish(new RubberQuickTestStdChangedPayload { Action = action }); + } + + private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload) + { + if (!payload.IsOnline) return; + _ = Task.Run(() => PollSyncAsync("reconnect")); } private void OnRemoteCommand(RemoteCommandPayload payload) @@ -52,7 +71,11 @@ public class RubberQuickTestStdSyncCoordinator : ISingletonDependency StdId = idEl.ValueKind == JsonValueKind.String ? idEl.GetString() : null }; _logger.Information($"[快检实验标准] STOMP action={changed.Action}, stdId={changed.StdId}"); - _eventAggregator.GetEvent().Publish(changed); + _ = Task.Run(async () => + { + if (await _stdService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false)) + _eventAggregator.GetEvent().Publish(changed); + }); } catch (Exception ex) { diff --git a/yy-admin-master/YY.Admin/Event/TabCloseRequestEvent.cs b/yy-admin-master/YY.Admin/Event/TabCloseRequestEvent.cs new file mode 100644 index 00000000..dbc667d4 --- /dev/null +++ b/yy-admin-master/YY.Admin/Event/TabCloseRequestEvent.cs @@ -0,0 +1,18 @@ +namespace YY.Admin.Event +{ + /// + /// 按 ViewName 关闭 Tab 并可激活指定列表页 + /// + public class TabCloseRequestPayload + { + public string ViewNameToClose { get; set; } = string.Empty; + + public string? ActivateViewName { get; set; } + + public string? ActivateTabName { get; set; } + } + + public class TabCloseRequestEvent : PubSubEvent + { + } +} diff --git a/yy-admin-master/YY.Admin/Infrastructure/Network/NetworkMonitor.cs b/yy-admin-master/YY.Admin/Infrastructure/Network/NetworkMonitor.cs index 6dea0bde..7e90900e 100644 --- a/yy-admin-master/YY.Admin/Infrastructure/Network/NetworkMonitor.cs +++ b/yy-admin-master/YY.Admin/Infrastructure/Network/NetworkMonitor.cs @@ -62,10 +62,10 @@ public class NetworkMonitor : INetworkMonitor private async Task MonitorLoopAsync(CancellationToken cancellationToken) { - // 启动后立即探活一次,避免首屏 10 秒内 IsOnline 恒为 false + // 启动后立即探活一次,避免首屏数秒内 IsOnline 恒为 false await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false); - using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10)); + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5)); while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false)) { await RunHttpProbeAndRecomputeAsync(cancellationToken).ConfigureAwait(false); diff --git a/yy-admin-master/YY.Admin/Module/SyncModule.cs b/yy-admin-master/YY.Admin/Module/SyncModule.cs index 67473158..45a81f89 100644 --- a/yy-admin-master/YY.Admin/Module/SyncModule.cs +++ b/yy-admin-master/YY.Admin/Module/SyncModule.cs @@ -93,6 +93,8 @@ public class SyncModule : IModule containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); + // 快检相关 MES 只读数据 2 秒轮询(须在协调器之前注册) + containerRegistry.RegisterSingleton(); // 胶料快检实验标准(MES 只读同步) containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); @@ -171,6 +173,8 @@ public class SyncModule : IModule // 强制实例化打印模板同步协调器 _ = containerProvider.Resolve(); _ = containerProvider.Resolve(); + // 快检数据轮询须在协调器之前实例化,确保 Register 能正常接收 + _ = containerProvider.Resolve(); // 胶料快检实验标准只读同步协调器 _ = containerProvider.Resolve(); // 密炼计划只读同步协调器 diff --git a/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs index c7fa391a..228ce71d 100644 --- a/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs +++ b/yy-admin-master/YY.Admin/ViewModels/MainWindowViewModel.cs @@ -195,6 +195,7 @@ namespace YY.Admin.ViewModels private SubscriptionToken? _openOrActivateTabToken; private SubscriptionToken? _tabClosedToken; + private SubscriptionToken? _tabCloseRequestToken; private SubscriptionToken? _refreshTabToken; private SubscriptionToken? _loginOutToken; @@ -235,6 +236,7 @@ namespace YY.Admin.ViewModels // 订阅事件 _openOrActivateTabToken = _eventAggregator.GetEvent().Subscribe(OnOpenOrActivateTab); _tabClosedToken = _eventAggregator.GetEvent().Subscribe(OnTabClosed); + _tabCloseRequestToken = _eventAggregator.GetEvent().Subscribe(OnTabCloseRequest, ThreadOption.UIThread); _refreshTabToken = _eventAggregator.GetEvent().Subscribe(OnRefreshTab); _loginOutToken = _eventAggregator.GetEvent().Subscribe(Destroy); @@ -489,6 +491,48 @@ namespace YY.Admin.ViewModels _ = NavigateToViewAsync(CommonConst.ContentRegion, tabItemModel.ViewName, tabItemModel.TabSource.NavigationParameter); } + /// + /// 按 ViewName 关闭 Tab,并可选激活指定页面 + /// + private void OnTabCloseRequest(TabCloseRequestPayload payload) + { + if (payload == null || string.IsNullOrWhiteSpace(payload.ViewNameToClose)) + { + return; + } + + var tabsToClose = OpenTabs + .Where(t => t.IsClosable + && string.Equals(t.ViewName, payload.ViewNameToClose, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var tab in tabsToClose) + { + _eventAggregator.GetEvent().Publish(tab); + OpenTabs.Remove(tab); + } + + if (string.IsNullOrWhiteSpace(payload.ActivateViewName)) + { + return; + } + + var existingTab = OpenTabs.FirstOrDefault(t => + string.Equals(t.ViewName, payload.ActivateViewName, StringComparison.OrdinalIgnoreCase)); + if (existingTab != null) + { + SelectedTab = existingTab; + return; + } + + OnOpenOrActivateTab(new TabSource + { + Name = payload.ActivateTabName ?? payload.ActivateViewName, + Icon = "\ue7de", + ViewName = payload.ActivateViewName + }); + } + /// /// TabItem关闭回调 /// @@ -764,6 +808,15 @@ namespace YY.Admin.ViewModels _tabClosedToken = null; } + if (_tabCloseRequestToken != null) + { + _eventAggregator + .GetEvent() + .Unsubscribe(_tabCloseRequestToken); + + _tabCloseRequestToken = null; + } + if (_refreshTabToken != null) { _eventAggregator diff --git a/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestOperationViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestOperationViewModel.cs index e0856f2f..369cbb09 100644 --- a/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestOperationViewModel.cs +++ b/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestOperationViewModel.cs @@ -27,7 +27,7 @@ using YY.Admin.Core.Services; using YY.Admin.Core.Session; - +using YY.Admin.Event; namespace YY.Admin.ViewModels.RubberQuickTest; @@ -178,12 +178,17 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware private readonly IRubberQuickTestStdService _stdService; + private readonly INetworkMonitor _networkMonitor; + private readonly Random _rnd = new(); private bool _saveInProgress; private string? _loadedDetailLocalId; private string? _loadedDetailMesId; + private string? _editingLocalId; + private DateTime? _originalCreateTime; + private bool _isLoadingForm; private int _navigationApplyVersion; private List _allPlans = new(); @@ -196,6 +201,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware private SubscriptionToken? _stdChangedToken; + private SubscriptionToken? _networkStatusToken; + private const int ChartPointCount = 5; /// 曲线横坐标时间点(min):0、0.5、1、1.5、2 @@ -215,6 +222,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware IRubberQuickTestStdService stdService, + INetworkMonitor networkMonitor, + IContainerExtension container, IRegionManager regionManager) : base(container, regionManager) @@ -227,20 +236,20 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware _stdService = stdService; + _networkMonitor = networkMonitor; + _inspectTimesText = "1"; - AddInspectRowCommand = new DelegateCommand(AddInspectRow, () => DataPointColumns.Count > 0); + AddInspectRowCommand = new DelegateCommand(AddInspectRow, () => DataPointColumns.Count > 0 && !IsReadOnly); - RemoveInspectRowCommand = new DelegateCommand(() => RemoveInspectRow(SelectedInspectRow), () => SelectedInspectRow != null); + RemoveInspectRowCommand = new DelegateCommand(() => RemoveInspectRow(SelectedInspectRow), () => SelectedInspectRow != null && !IsReadOnly); SaveCommand = new DelegateCommand(async () => await SaveAsync(), () => CanSave); - RefreshPlansCommand = new DelegateCommand(async () => await LoadLocalDataAsync(showSuccess: true)); - RefreshChartDemoCommand = new DelegateCommand(FillRandomChartData); @@ -268,9 +277,45 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware _stdChangedToken = _eventAggregator.GetEvent() .Subscribe(async _ => await ReloadLocalDataQuietAsync(), ThreadOption.UIThread); + _networkStatusToken = _eventAggregator.GetEvent() + .Subscribe(OnNetworkStatusChanged, ThreadOption.UIThread); + + IsOfflineMode = !_networkMonitor.IsOnline; + RecalculateOverallInspectResult(); } + private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload) + { + IsOfflineMode = !payload.IsOnline; + if (IsOfflineMode) + RefreshRubberMaterialOptions(); + } + + private bool _isOfflineMode; + /// 断网时自动进入离线模式 + public bool IsOfflineMode + { + get => _isOfflineMode; + private set + { + if (!SetProperty(ref _isOfflineMode, value)) return; + RaisePropertyChanged(nameof(PlanFieldsRequired)); + RaisePropertyChanged(nameof(IsOnlineMode)); + RaisePropertyChanged(nameof(RubberMaterialName)); + if (value) + RefreshRubberMaterialOptions(); + } + } + + public bool IsOnlineMode => !IsOfflineMode; + + public bool PlanFieldsRequired => !IsOfflineMode; + + public string OfflineModeHint => IsOfflineMode + ? "当前离线:密炼日期、机台、班次、密炼计划可选填;请手动选择胶料名称,保存后待联网同步" + : string.Empty; + private bool _isReadOnly; public bool IsReadOnly { @@ -281,12 +326,16 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware RaisePropertyChanged(nameof(IsEditable)); RaisePropertyChanged(nameof(ShowSaveButton)); NotifySaveStateChanged(); + AddInspectRowCommand.RaiseCanExecuteChanged(); + RemoveInspectRowCommand.RaiseCanExecuteChanged(); InspectColumnsChanged?.Invoke(); } } public bool IsEditable => !IsReadOnly; public bool ShowSaveButton => !IsReadOnly; + public bool IsEditMode => !string.IsNullOrWhiteSpace(_editingLocalId); + public string SaveButtonText => IsEditMode ? "保存修改" : "保存胶料快检记录"; public bool CanSave => InspectRows.Count > 0 && !_saveInProgress && !IsReadOnly; private void NotifySaveStateChanged() @@ -314,6 +363,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware { if (parameters.TryGetValue("localId", out var localId) && !string.IsNullOrWhiteSpace(localId)) { + _editingLocalId = null; _loadedDetailLocalId = localId; _loadedDetailMesId = null; await LoadFromLocalItemAsync(localId); @@ -322,6 +372,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware if (parameters.TryGetValue("mesId", out var mesId) && !string.IsNullOrWhiteSpace(mesId)) { + _editingLocalId = null; _loadedDetailMesId = mesId; _loadedDetailLocalId = null; await LoadFromMesIdAsync(mesId); @@ -329,10 +380,22 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware } } + if (parameters.TryGetValue("editLocalId", out var editLocalId) && !string.IsNullOrWhiteSpace(editLocalId)) + { + _editingLocalId = editLocalId; + _loadedDetailLocalId = null; + _loadedDetailMesId = null; + IsReadOnly = false; + await LoadForEditAsync(editLocalId); + return; + } + if (version != _navigationApplyVersion) return; _loadedDetailLocalId = null; _loadedDetailMesId = null; + _editingLocalId = null; + _originalCreateTime = null; IsReadOnly = false; ResetFormForNewEntry(); await LoadLocalDataAsync(showSuccess: false); @@ -352,7 +415,10 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware return false; } - return !IsReadOnly && _loadedDetailLocalId == null && _loadedDetailMesId == null; + if (parameters.TryGetValue("editLocalId", out var editLocalId) && !string.IsNullOrWhiteSpace(editLocalId)) + return !IsReadOnly && string.Equals(_editingLocalId, editLocalId, StringComparison.OrdinalIgnoreCase); + + return !IsReadOnly && _editingLocalId == null && _loadedDetailLocalId == null && _loadedDetailMesId == null; } private void ResetFormForNewEntry() @@ -360,6 +426,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware RecordNo = null; TrainNo = null; InspectTimesText = "1"; + MixingDate = DateTime.Today; ClearPlanAndStdSelection(); RecalculateOverallInspectResult(); NotifySaveStateChanged(); @@ -420,6 +487,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware public ObservableCollection StdOptions { get; } = new(); + public ObservableCollection RubberMaterialOptions { get; } = new(); + public ObservableCollection DataPointColumns { get; } = new(); public ObservableCollection InspectRows { get; } = new(); @@ -448,9 +517,9 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware - private DateTime _mixingDate = DateTime.Today; + private DateTime? _mixingDate = DateTime.Today; - public DateTime MixingDate + public DateTime? MixingDate { @@ -464,7 +533,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware RefreshMachineOptions(); - ClearPlanAndStdSelection(); + if (!_isLoadingForm) + OnPlanFilterChanged(); } @@ -488,7 +558,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware RefreshShiftOptions(); - ClearPlanAndStdSelection(); + if (!_isLoadingForm) + OnPlanFilterChanged(); } @@ -512,7 +583,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware RefreshPlanOptions(); - ClearPlanAndStdSelection(); + if (!_isLoadingForm) + OnPlanFilterChanged(); } @@ -568,7 +640,30 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware public string? WorkShiftDisplay => SelectedShift?.Name ?? string.Empty; - public string? RubberMaterialName => _selectedPlan?.MaterialName ?? _selectedPlan?.FormulaName; + private string? _selectedRubberMaterial; + + /// 离线模式下手动选择的胶料名称 + public string? SelectedRubberMaterial + { + get => _selectedRubberMaterial; + set + { + if (!SetProperty(ref _selectedRubberMaterial, value)) return; + RaisePropertyChanged(nameof(RubberMaterialName)); + if (IsOfflineMode && _selectedPlan == null) + { + ClearStdSelection(); + RefreshStdOptions(); + } + } + } + + public string? RubberMaterialName => + (IsEditMode && !string.IsNullOrWhiteSpace(SelectedRubberMaterial)) + ? SelectedRubberMaterial + : _selectedPlan?.MaterialName + ?? _selectedPlan?.FormulaName + ?? (IsOfflineMode ? SelectedRubberMaterial : null); @@ -699,8 +794,6 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware public DelegateCommand SaveCommand { get; } - public DelegateCommand RefreshPlansCommand { get; } - public DelegateCommand RefreshChartDemoCommand { get; } @@ -720,6 +813,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware _allStds = await _stdService.GetAllCachedAsync(); RefreshMachineOptions(); + RefreshRubberMaterialOptions(); if (showSuccess) @@ -760,8 +854,12 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware _allStds = await _stdService.GetAllCachedAsync(); RefreshMachineOptions(); + RefreshRubberMaterialOptions(); - RefreshStdOptions(); + if (IsEditMode) + EnsureCurrentStdInOptions(); + else + RefreshStdOptions(); } @@ -777,9 +875,27 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware - private IEnumerable FilteredByDate => + private void RefreshRubberMaterialOptions() + { + RubberMaterialOptions.Clear(); + foreach (var name in _allStds + .Where(s => s.EnableStatus == "1" && s.AuditStatus == "1") + .Select(s => s.RubberMaterialName?.Trim()) + .Where(n => !string.IsNullOrWhiteSpace(n)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(n => n, StringComparer.OrdinalIgnoreCase)) + RubberMaterialOptions.Add(name!); - _allPlans.Where(p => p.PlanDate?.Date == MixingDate.Date); + if (SelectedRubberMaterial != null && !RubberMaterialOptions.Contains(SelectedRubberMaterial)) + SelectedRubberMaterial = null; + } + + + + private IEnumerable FilteredByDate => + MixingDate == null + ? Enumerable.Empty() + : _allPlans.Where(p => p.PlanDate?.Date == MixingDate.Value.Date); @@ -797,7 +913,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware SelectedMachine = null; - if (SelectedMachine == null && MachineOptions.Count > 0) + if (SelectedMachine == null && MachineOptions.Count > 0 && !IsEditMode && !_isLoadingForm) SelectedMachine = MachineOptions[0]; @@ -835,7 +951,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware SelectedShift = null; - if (SelectedShift == null && ShiftOptions.Count > 0) + if (SelectedShift == null && ShiftOptions.Count > 0 && !IsEditMode && !_isLoadingForm) SelectedShift = ShiftOptions[0]; @@ -873,7 +989,7 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware SelectedPlan = null; - if (SelectedPlan == null && PlanOptions.Count > 0) + if (SelectedPlan == null && PlanOptions.Count > 0 && !IsEditMode && !_isLoadingForm) SelectedPlan = PlanOptions[0]; @@ -881,6 +997,26 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware + /// 密炼日期/机台/班次变更:编辑模式仅重置计划,不清空实验标准与检验明细 + private void OnPlanFilterChanged() + { + if (IsEditMode) + ResetPlanSelectionOnly(); + else + ClearPlanAndStdSelection(); + } + + private void ResetPlanSelectionOnly() + { + _selectedPlan = null; + RaisePropertyChanged(nameof(SelectedPlan)); + RaisePropertyChanged(nameof(MachineName)); + RaisePropertyChanged(nameof(WorkShiftDisplay)); + RaisePropertyChanged(nameof(RubberMaterialName)); + } + + + private void ClearPlanAndStdSelection() { @@ -893,6 +1029,11 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware RaisePropertyChanged(nameof(WorkShiftDisplay)); + if (!IsOfflineMode) + { + SelectedRubberMaterial = null; + } + RaisePropertyChanged(nameof(RubberMaterialName)); ClearStdSelection(); @@ -920,19 +1061,34 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware private void ApplySelectedPlan() - { - RaisePropertyChanged(nameof(MachineName)); - RaisePropertyChanged(nameof(WorkShiftDisplay)); - RaisePropertyChanged(nameof(RubberMaterialName)); - ClearStdSelection(); + if (IsEditMode) + { + EnsureCurrentStdInOptions(); + return; + } RefreshStdOptions(); + } + /// 编辑模式下保留已选实验标准,避免下拉选项刷新导致标准与明细被清空 + private void EnsureCurrentStdInOptions() + { + if (_selectedStd == null || string.IsNullOrWhiteSpace(_selectedStd.StdName)) + { + return; + } + + if (!StdOptions.Any(s => string.Equals(s.Id, _selectedStd.Id, StringComparison.OrdinalIgnoreCase))) + { + StdOptions.Insert(0, _selectedStd); + } + + RaisePropertyChanged(nameof(SelectedStd)); } @@ -986,6 +1142,24 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware { + if (SelectedStd == null || string.IsNullOrWhiteSpace(SelectedStd.Id)) + { + if (_currentStd != null || DataPointColumns.Count > 0 || InspectRows.Count > 0) + { + DataPointColumns.Clear(); + InspectRows.Clear(); + _currentStd = null; + TestMethodName = null; + InspectColumnsChanged?.Invoke(); + RecalculateOverallInspectResult(); + } + return; + } + + if (string.Equals(SelectedStd.Id, _currentStd?.Id, StringComparison.OrdinalIgnoreCase) + && DataPointColumns.Count > 0) + return; + DataPointColumns.Clear(); InspectRows.Clear(); @@ -998,10 +1172,6 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware RecalculateOverallInspectResult(); - if (SelectedStd == null || string.IsNullOrWhiteSpace(SelectedStd.Id)) return; - - - IsLoading = true; try @@ -1213,15 +1383,23 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware return; } - if (SelectedPlan == null) + if (!IsOfflineMode) { - Growl.Warning("请选择密炼计划"); - return; - } + if (SelectedPlan == null) + { + Growl.Warning("请选择密炼计划"); + return; + } - if (string.IsNullOrWhiteSpace(RubberMaterialName)) + if (string.IsNullOrWhiteSpace(RubberMaterialName)) + { + Growl.Warning("请先选择密炼计划以带出胶料名称"); + return; + } + } + else if (string.IsNullOrWhiteSpace(RubberMaterialName)) { - Growl.Warning("请先选择密炼计划以带出胶料名称"); + Growl.Warning("请选择胶料名称"); return; } @@ -1262,16 +1440,25 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware IsLoading = true; var record = BuildSaveRecord(stdLineList, rawLineList, chartPointList, inspectTimes, user); - var saved = await _recordService.SaveAsync(record); + RubberQuickTestRecordSaveResult saved; + if (!string.IsNullOrWhiteSpace(_editingLocalId)) + saved = await _recordService.UpdateLocalAsync(_editingLocalId, record); + else + saved = await _recordService.SaveAsync(record); RecordNo = saved.Record.RecordNo; - var syncHint = saved.SyncStatus == "Synced" ? "并已同步到 MES" : "(MES 同步待重试)"; + var syncHint = saved.SyncStatus switch + { + "Synced" => "并已同步到 MES", + "Pending" => "(未同步,联网后可在列表中手动同步)", + _ => "(MES 同步失败,可在列表中重试同步)" + }; + var actionText = IsEditMode ? "修改已保存" : "胶料快检记录已保存"; Growl.Success(string.IsNullOrWhiteSpace(RecordNo) - ? $"胶料快检记录已保存到本地{syncHint}" - : $"胶料快检记录已保存,快检记录号:{RecordNo}{syncHint}"); + ? $"{actionText}到本地{syncHint}" + : $"{actionText},快检记录号:{RecordNo}{syncHint}"); - InspectRows.Clear(); - NotifySaveStateChanged(); + NavigateBackToRecordList(); } catch (Exception ex) { @@ -1287,6 +1474,16 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware + private void NavigateBackToRecordList() + { + _eventAggregator.GetEvent().Publish(new TabCloseRequestPayload + { + ViewNameToClose = "RubberQuickTestOperationView", + ActivateViewName = "RubberQuickTestRecordListView", + ActivateTabName = "胶料快检记录" + }); + } + private bool TryParseInspectTimes(out int value, out string error) { @@ -1501,11 +1698,13 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware InspectTime = DateTime.Now, - CreateTime = DateTime.Now, - InspectResult = OverallInspectResultCode, - RecordNo = _recordService.GenerateRecordNo(RubberMaterialName ?? string.Empty) + RecordNo = string.IsNullOrWhiteSpace(RecordNo) + ? _recordService.GenerateRecordNo(RubberMaterialName ?? string.Empty) + : RecordNo, + + CreateTime = _originalCreateTime ?? DateTime.Now }; @@ -1545,17 +1744,25 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware - if (!string.IsNullOrWhiteSpace(_selectedPlan?.MachineId)) + var hasPlanContext = _selectedPlan != null || !IsOfflineMode; - record.ProdEquipmentLedgerId = _selectedPlan.MachineId; + if (hasPlanContext) + { + if (!string.IsNullOrWhiteSpace(_selectedPlan?.MachineId)) + record.ProdEquipmentLedgerId = _selectedPlan.MachineId; - if (!string.IsNullOrWhiteSpace(MachineName)) + if (!string.IsNullOrWhiteSpace(MachineName)) + record.ProdEquipmentName = MachineName; - record.ProdEquipmentName = MachineName; + if (SelectedShift != null && !string.IsNullOrWhiteSpace(SelectedShift.Code)) + record.WorkShift = SelectedShift.Code; + if (!string.IsNullOrWhiteSpace(_selectedPlan?.PlanNo)) + record.ProductionPlanNo = _selectedPlan.PlanNo; + } - - record.ProductionDate = MixingDate; + if (MixingDate != null) + record.ProductionDate = MixingDate; @@ -1565,22 +1772,10 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware - if (SelectedShift != null && !string.IsNullOrWhiteSpace(SelectedShift.Code)) - - record.WorkShift = SelectedShift.Code; - - - record.InspectTimes = inspectTimes; - if (!string.IsNullOrWhiteSpace(_selectedPlan?.PlanNo)) - - record.ProductionPlanNo = _selectedPlan.PlanNo; - - - var inspectorId = user?.JeecgBizUserId ?? (user != null ? user.Id.ToString() : null); if (!string.IsNullOrWhiteSpace(inspectorId)) @@ -1649,7 +1844,32 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware return; } - await ApplyRecordDetailAsync(item.Record); + await ApplyRecordDetailAsync(item.Record, readOnly: true); + } + + private async Task LoadForEditAsync(string localId) + { + var item = _recordService.GetByLocalId(localId); + if (item == null) + { + Growl.Warning("未找到快检记录"); + return; + } + + if (string.Equals(item.SyncStatus, "Synced", StringComparison.OrdinalIgnoreCase)) + { + Growl.Warning("已同步记录不可编辑"); + return; + } + + _editingLocalId = localId; + _originalCreateTime = item.Record.CreateTime; + IsReadOnly = false; + RaisePropertyChanged(nameof(IsEditMode)); + RaisePropertyChanged(nameof(SaveButtonText)); + + await LoadLocalDataAsync(showSuccess: false); + await ApplyRecordDetailAsync(item.Record, readOnly: false); } private async Task LoadFromMesIdAsync(string mesId) @@ -1661,105 +1881,181 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware return; } - await ApplyRecordDetailAsync(record); + await ApplyRecordDetailAsync(record, readOnly: true); } - private async Task ApplyRecordDetailAsync(MesXslRubberQuickTestRecord r) + private async Task ApplyRecordDetailAsync(MesXslRubberQuickTestRecord r, bool readOnly) { - 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 + _isLoadingForm = true; + try { - PlanNo = r.ProductionPlanNo, - MaterialName = r.RubberMaterialName, - MachineName = r.ProdEquipmentName - }; - PlanOptions.Add(plan); - _selectedPlan = plan; - RaisePropertyChanged(nameof(SelectedPlan)); - RaisePropertyChanged(nameof(MachineName)); - RaisePropertyChanged(nameof(RubberMaterialName)); + IsReadOnly = readOnly; + RecordNo = r.RecordNo; + MixingDate = r.ProductionDate ?? r.CreateTime?.Date; + 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; - StdOptions.Clear(); - var std = new MesXslRubberQuickTestStd { Id = r.StdId, StdName = r.StdName, TestMethodName = r.TestMethodName }; - StdOptions.Add(std); - _selectedStd = std; - RaisePropertyChanged(nameof(SelectedStd)); + ClearPlanAndStdSelection(); + SelectedRubberMaterial = r.RubberMaterialName; - DataPointColumns.Clear(); - InspectRows.Clear(); - foreach (var sl in (r.StdLineList ?? new List()).OrderBy(x => x.SortNo ?? 0)) - { - DataPointColumns.Add(new MesXslRubberQuickTestStdLine + MesXslMixingProductionPlan? matchedPlan = null; + if (!string.IsNullOrWhiteSpace(r.ProductionPlanNo)) { - 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()) - .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); + matchedPlan = _allPlans.FirstOrDefault(p => + string.Equals(p.PlanNo, r.ProductionPlanNo, StringComparison.OrdinalIgnoreCase)); } - row.RecalculateResult(); - InspectRows.Add(row); - } - UpperTempValues.Clear(); - LowerTempValues.Clear(); - TorqueValues.Clear(); - foreach (var pt in (r.ChartPointList ?? new List()).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 (matchedPlan != null) + { + RefreshMachineOptions(); + SelectedMachine = matchedPlan.MachineName ?? r.ProdEquipmentName; + RefreshShiftOptions(); + if (!string.IsNullOrWhiteSpace(r.WorkShift)) + { + SelectedShift = ShiftOptions.FirstOrDefault(s => s.Code == r.WorkShift) + ?? ShiftOptions.FirstOrDefault(); + } + RefreshPlanOptions(); + SelectedPlan = PlanOptions.FirstOrDefault(p => p.Id == matchedPlan.Id) + ?? PlanOptions.FirstOrDefault(p => string.Equals(p.PlanNo, matchedPlan.PlanNo, StringComparison.OrdinalIgnoreCase)); + } + else if (!string.IsNullOrWhiteSpace(r.ProdEquipmentName) || !string.IsNullOrWhiteSpace(r.ProductionPlanNo)) + { + MachineOptions.Clear(); + if (!string.IsNullOrWhiteSpace(r.ProdEquipmentName)) + MachineOptions.Add(r.ProdEquipmentName); + SelectedMachine = r.ProdEquipmentName; + + ShiftOptions.Clear(); + var shift = new WorkShiftOption(r.WorkShift ?? "", r.WorkShiftText ?? 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)); + } + else if (!string.IsNullOrWhiteSpace(r.RubberMaterialName)) + { + RefreshStdOptions(); + } + + _currentStd = null; + if (!string.IsNullOrWhiteSpace(r.StdId)) + { + var cachedStd = await _stdService.GetWithLinesAsync(r.StdId); + if (cachedStd != null) + { + _currentStd = cachedStd; + StdOptions.Clear(); + StdOptions.Add(cachedStd); + _selectedStd = cachedStd; + RaisePropertyChanged(nameof(SelectedStd)); + TestMethodName = cachedStd.TestMethodName ?? r.TestMethodName; + } + } + + if (_currentStd == null && !string.IsNullOrWhiteSpace(r.StdName)) + { + var std = new MesXslRubberQuickTestStd { Id = r.StdId, StdName = r.StdName, TestMethodName = r.TestMethodName }; + StdOptions.Clear(); + StdOptions.Add(std); + _selectedStd = std; + RaisePropertyChanged(nameof(SelectedStd)); + } + + DataPointColumns.Clear(); + InspectRows.Clear(); + if (_currentStd?.LineList != null && _currentStd.LineList.Count > 0) + { + foreach (var line in _currentStd.LineList.OrderBy(x => x.SortNo ?? 0)) + DataPointColumns.Add(line); + } + else + { + foreach (var sl in (r.StdLineList ?? new List()).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()) + .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 + }; + if (!readOnly) + { + cell.ValueChanged += _ => + { + row.RecalculateResult(); + RecalculateOverallInspectResult(); + }; + } + row.Cells.Add(cell); + } + row.RecalculateResult(); + InspectRows.Add(row); + } + + UpperTempValues.Clear(); + LowerTempValues.Clear(); + TorqueValues.Clear(); + foreach (var pt in (r.ChartPointList ?? new List()).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; + + RecalculateOverallInspectResult(); + NotifySaveStateChanged(); + } + finally + { + _isLoadingForm = false; } - 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; } @@ -1859,6 +2155,12 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware } + if (_networkStatusToken != null) + { + _eventAggregator.GetEvent().Unsubscribe(_networkStatusToken); + _networkStatusToken = null; + } + base.CleanUp(); } diff --git a/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestRecordListViewModel.cs b/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestRecordListViewModel.cs index f0b8dcc7..34ccf4b6 100644 --- a/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestRecordListViewModel.cs +++ b/yy-admin-master/YY.Admin/ViewModels/RubberQuickTest/RubberQuickTestRecordListViewModel.cs @@ -49,7 +49,9 @@ public class RubberQuickTestRecordListViewModel : BaseViewModel public DelegateCommand ResetCommand { get; } public DelegateCommand AddCommand { get; } public DelegateCommand ViewDetailCommand { get; } + public DelegateCommand EditCommand { get; } public DelegateCommand DeleteCommand { get; } + public DelegateCommand SyncCommand { get; } public DelegateCommand PrevPageCommand { get; } public DelegateCommand NextPageCommand { get; } @@ -73,9 +75,13 @@ public class RubberQuickTestRecordListViewModel : BaseViewModel }); AddCommand = new DelegateCommand(OpenAddPage); ViewDetailCommand = new DelegateCommand(async r => await ShowDetailAsync(r)); + EditCommand = new DelegateCommand(OpenEditPage, r => r != null && r.CanEdit); DeleteCommand = new DelegateCommand( async r => await DeleteAsync(r), r => r != null && r.CanDelete); + SyncCommand = new DelegateCommand( + async r => await SyncAsync(r), + r => r != null && r.CanSync); PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } }); NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } }); @@ -137,6 +143,27 @@ public class RubberQuickTestRecordListViewModel : BaseViewModel }); } + private void OpenEditPage(RubberQuickTestRecordListRow? row) + { + if (row == null || !row.CanEdit || string.IsNullOrWhiteSpace(row.LocalId)) + return; + + if (_recordService.GetByLocalId(row.LocalId) == null) + { + Growl.Warning("未找到快检记录"); + return; + } + + var label = row.RecordNo ?? row.LocalId; + _eventAggregator.GetEvent().Publish(new TabSource + { + Name = $"编辑快检记录 {label}", + Icon = "\ue7de", + ViewName = "RubberQuickTestOperationView", + NavigationParameter = new NavigationParameters { { "editLocalId", row.LocalId } } + }); + } + private async Task DeleteAsync(RubberQuickTestRecordListRow? row) { if (row == null || !row.CanDelete || string.IsNullOrWhiteSpace(row.LocalId)) @@ -144,7 +171,7 @@ public class RubberQuickTestRecordListViewModel : BaseViewModel var label = row.RecordNo ?? row.LocalId; var confirm = System.Windows.MessageBox.Show( - $"确定删除同步失败的快检记录「{label}」?\n此操作仅删除本地记录,不可恢复。", + $"确定删除未同步的快检记录「{label}」?\n此操作仅删除本地记录,不可恢复。", "确认删除", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Warning); @@ -153,14 +180,14 @@ public class RubberQuickTestRecordListViewModel : BaseViewModel try { - if (_recordService.DeleteFailedLocal(row.LocalId)) + if (_recordService.DeleteUnsyncedLocal(row.LocalId)) { Growl.Success("删除成功"); await LoadAsync(); } else { - Growl.Warning("仅同步失败的本地记录可删除"); + Growl.Warning("仅未同步的本地记录可删除"); } } catch (Exception ex) @@ -169,6 +196,36 @@ public class RubberQuickTestRecordListViewModel : BaseViewModel } } + private async Task SyncAsync(RubberQuickTestRecordListRow? row) + { + if (row == null || !row.CanSync || string.IsNullOrWhiteSpace(row.LocalId)) + return; + + try + { + IsLoading = true; + var result = await _recordService.SyncLocalAsync(row.LocalId); + if (result.Success) + { + var no = result.RecordNo ?? row.RecordNo ?? row.LocalId; + Growl.Success($"同步成功,快检记录号:{no}"); + await LoadAsync(); + } + else + { + Growl.Warning(result.Message ?? "同步失败"); + } + } + catch (Exception ex) + { + Growl.Error($"同步失败:{ex.Message}"); + } + finally + { + IsLoading = false; + } + } + private async Task ShowDetailAsync(RubberQuickTestRecordListRow? row) { if (row == null) return; diff --git a/yy-admin-master/YY.Admin/Views/MixingProductionPlan/MixingProductionPlanListView.xaml b/yy-admin-master/YY.Admin/Views/MixingProductionPlan/MixingProductionPlanListView.xaml index 82f5d295..23f681ce 100644 --- a/yy-admin-master/YY.Admin/Views/MixingProductionPlan/MixingProductionPlanListView.xaml +++ b/yy-admin-master/YY.Admin/Views/MixingProductionPlan/MixingProductionPlanListView.xaml @@ -90,7 +90,7 @@ - diff --git a/yy-admin-master/YY.Admin/Views/RubberQuickTest/RubberQuickTestOperationView.xaml b/yy-admin-master/YY.Admin/Views/RubberQuickTest/RubberQuickTestOperationView.xaml index 00a8bca6..90fb6545 100644 --- a/yy-admin-master/YY.Admin/Views/RubberQuickTest/RubberQuickTestOperationView.xaml +++ b/yy-admin-master/YY.Admin/Views/RubberQuickTest/RubberQuickTestOperationView.xaml @@ -50,7 +50,14 @@ - + + + + + + @@ -87,7 +94,8 @@ - -