胶料快检添加离线模式
This commit is contained in:
@@ -18,12 +18,25 @@ public interface IRubberQuickTestRecordService
|
||||
|
||||
Task<RubberQuickTestRecordSaveResult> SaveAsync(MesXslRubberQuickTestRecord entity, CancellationToken ct = default);
|
||||
|
||||
/// <summary>删除本地同步失败的快检记录(已同步或待同步不可删)</summary>
|
||||
bool DeleteFailedLocal(string localId);
|
||||
/// <summary>更新本地未同步快检记录</summary>
|
||||
Task<RubberQuickTestRecordSaveResult> UpdateLocalAsync(string localId, MesXslRubberQuickTestRecord entity, CancellationToken ct = default);
|
||||
|
||||
/// <summary>删除本地未同步的快检记录(已同步不可删)</summary>
|
||||
bool DeleteUnsyncedLocal(string localId);
|
||||
|
||||
/// <summary>将本地未同步记录推送到 MES(须已联网且密炼计划信息完整)</summary>
|
||||
Task<RubberQuickTestRecordSyncResult> 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<RubberQuickTestRecordListRow> Records,
|
||||
long Total,
|
||||
|
||||
@@ -23,10 +23,16 @@ public class RubberQuickTestRecordListRow
|
||||
public string SyncStatusDisplay => SyncStatus switch
|
||||
{
|
||||
"Synced" => "已同步",
|
||||
"Failed" => "失败",
|
||||
_ => "待同步"
|
||||
"Failed" => "同步失败",
|
||||
_ => "未同步"
|
||||
};
|
||||
|
||||
/// <summary>仅本地同步失败记录可删除</summary>
|
||||
public bool CanDelete => SyncStatus == "Failed" && !string.IsNullOrWhiteSpace(LocalId);
|
||||
/// <summary>本地未同步记录可删除</summary>
|
||||
public bool CanDelete => SyncStatus != "Synced" && !string.IsNullOrWhiteSpace(LocalId);
|
||||
|
||||
/// <summary>本地未同步记录可手动同步</summary>
|
||||
public bool CanSync => SyncStatus != "Synced" && !string.IsNullOrWhiteSpace(LocalId);
|
||||
|
||||
/// <summary>本地未同步记录可编辑</summary>
|
||||
public bool CanEdit => CanSync;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using YY.Admin.Core;
|
||||
|
||||
namespace YY.Admin.Services.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 胶料快检相关 MES 只读数据(实验标准、密炼计划)专用轮询管理器。
|
||||
/// </summary>
|
||||
public class MesQuickTestDataPollManager : ISingletonDependency
|
||||
{
|
||||
public static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(2);
|
||||
|
||||
private readonly List<(string Name, Func<Task> 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<Task> 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> 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<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<MixingProductionPlanPageResult> PageAsync(
|
||||
public Task<MixingProductionPlanPageResult> 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<MesXslMixingProductionPlan> 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<List<MesXslMixingProductionPlan>> GetAllCachedAsync(CancellationToken ct = default)
|
||||
public Task<List<MesXslMixingProductionPlan>> 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<bool> 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<MixingProductionPlanChangedEvent>()
|
||||
.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);
|
||||
|
||||
|
||||
@@ -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<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("密炼计划", () =>
|
||||
{
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>()
|
||||
.Publish(new MixingProductionPlanChangedPayload { Action = "poll" });
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_eventAggregator.GetEvent<NetworkStatusChangedEvent>()
|
||||
.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<MixingProductionPlanChangedEvent>()
|
||||
.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<MixingProductionPlanChangedEvent>().Publish(changed);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await _planService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
_eventAggregator.GetEvent<MixingProductionPlanChangedEvent>().Publish(changed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[密炼计划] STOMP 同步失败:{ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -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<string>("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<RubberQuickTestRecordSaveResult> 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<RubberQuickTestRecordChangedEvent>()
|
||||
.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<RubberQuickTestRecordChangedEvent>()
|
||||
.Publish(new RubberQuickTestRecordChangedPayload { Action = "delete", RecordId = localId });
|
||||
_logger.Information($"[快检记录删除] 已删除同步失败本地记录 localId={localId}");
|
||||
_logger.Information($"[快检记录删除] 已删除本地未同步记录 localId={localId}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<RubberQuickTestRecordSyncResult> 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<RubberQuickTestRecordChangedEvent>()
|
||||
.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<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,
|
||||
|
||||
@@ -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<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<RubberQuickTestStdPageResult> PageAsync(
|
||||
public Task<RubberQuickTestStdPageResult> 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<MesXslRubberQuickTestStd> 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<MesXslRubberQuickTestStd?> 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<MesXslRubberQuickTestStd> remoteRecords,
|
||||
List<MesXslRubberQuickTestStd> localSnapshot,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!isOnline) return;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (!await SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
return;
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
|
||||
.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<List<MesXslRubberQuickTestStdLine>> 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<List<MesXslRubberQuickTestStdLine>>(_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<MesXslRubberQuickTestStd> ApplyFilters(
|
||||
List<MesXslRubberQuickTestStd> source,
|
||||
string? stdName,
|
||||
|
||||
@@ -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<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
|
||||
pollManager.Register("胶料快检实验标准", () =>
|
||||
{
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>()
|
||||
.Publish(new RubberQuickTestStdChangedPayload { Action = "poll" });
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
_eventAggregator.GetEvent<NetworkStatusChangedEvent>()
|
||||
.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<RubberQuickTestStdChangedEvent>()
|
||||
.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<RubberQuickTestStdChangedEvent>().Publish(changed);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (await _stdService.SyncFromRemoteAsync(CancellationToken.None).ConfigureAwait(false))
|
||||
_eventAggregator.GetEvent<RubberQuickTestStdChangedEvent>().Publish(changed);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
18
yy-admin-master/YY.Admin/Event/TabCloseRequestEvent.cs
Normal file
18
yy-admin-master/YY.Admin/Event/TabCloseRequestEvent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace YY.Admin.Event
|
||||
{
|
||||
/// <summary>
|
||||
/// 按 ViewName 关闭 Tab 并可激活指定列表页
|
||||
/// </summary>
|
||||
public class TabCloseRequestPayload
|
||||
{
|
||||
public string ViewNameToClose { get; set; } = string.Empty;
|
||||
|
||||
public string? ActivateViewName { get; set; }
|
||||
|
||||
public string? ActivateTabName { get; set; }
|
||||
}
|
||||
|
||||
public class TabCloseRequestEvent : PubSubEvent<TabCloseRequestPayload>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -93,6 +93,8 @@ public class SyncModule : IModule
|
||||
containerRegistry.RegisterSingleton<IRubberQuickTestRecordService, RubberQuickTestRecordService>();
|
||||
containerRegistry.RegisterSingleton<IRubberQuickTestOperationService, RubberQuickTestOperationService>();
|
||||
|
||||
// 快检相关 MES 只读数据 2 秒轮询(须在协调器之前注册)
|
||||
containerRegistry.RegisterSingleton<MesQuickTestDataPollManager>();
|
||||
// 胶料快检实验标准(MES 只读同步)
|
||||
containerRegistry.RegisterSingleton<IRubberQuickTestStdService, RubberQuickTestStdService>();
|
||||
containerRegistry.RegisterSingleton<RubberQuickTestStdSyncCoordinator>();
|
||||
@@ -171,6 +173,8 @@ public class SyncModule : IModule
|
||||
// 强制实例化打印模板同步协调器
|
||||
_ = containerProvider.Resolve<PrintTemplateSyncCoordinator>();
|
||||
_ = containerProvider.Resolve<PrintBizTemplateBindSyncCoordinator>();
|
||||
// 快检数据轮询须在协调器之前实例化,确保 Register 能正常接收
|
||||
_ = containerProvider.Resolve<MesQuickTestDataPollManager>();
|
||||
// 胶料快检实验标准只读同步协调器
|
||||
_ = containerProvider.Resolve<RubberQuickTestStdSyncCoordinator>();
|
||||
// 密炼计划只读同步协调器
|
||||
|
||||
@@ -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<TabSourceSelectedEvent>().Subscribe(OnOpenOrActivateTab);
|
||||
_tabClosedToken = _eventAggregator.GetEvent<TabClosedEvent>().Subscribe(OnTabClosed);
|
||||
_tabCloseRequestToken = _eventAggregator.GetEvent<TabCloseRequestEvent>().Subscribe(OnTabCloseRequest, ThreadOption.UIThread);
|
||||
_refreshTabToken = _eventAggregator.GetEvent<TabRefreshEvent>().Subscribe(OnRefreshTab);
|
||||
_loginOutToken = _eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Subscribe(Destroy);
|
||||
|
||||
@@ -489,6 +491,48 @@ namespace YY.Admin.ViewModels
|
||||
_ = NavigateToViewAsync(CommonConst.ContentRegion, tabItemModel.ViewName, tabItemModel.TabSource.NavigationParameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按 ViewName 关闭 Tab,并可选激活指定页面
|
||||
/// </summary>
|
||||
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<TabClosedEvent>().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
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TabItem关闭回调
|
||||
/// </summary>
|
||||
@@ -764,6 +808,15 @@ namespace YY.Admin.ViewModels
|
||||
_tabClosedToken = null;
|
||||
}
|
||||
|
||||
if (_tabCloseRequestToken != null)
|
||||
{
|
||||
_eventAggregator
|
||||
.GetEvent<TabCloseRequestEvent>()
|
||||
.Unsubscribe(_tabCloseRequestToken);
|
||||
|
||||
_tabCloseRequestToken = null;
|
||||
}
|
||||
|
||||
if (_refreshTabToken != null)
|
||||
{
|
||||
_eventAggregator
|
||||
|
||||
@@ -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<MesXslMixingProductionPlan> _allPlans = new();
|
||||
@@ -196,6 +201,8 @@ public class RubberQuickTestOperationViewModel : BaseViewModel, INavigationAware
|
||||
|
||||
private SubscriptionToken? _stdChangedToken;
|
||||
|
||||
private SubscriptionToken? _networkStatusToken;
|
||||
|
||||
private const int ChartPointCount = 5;
|
||||
|
||||
/// <summary>曲线横坐标时间点(min):0、0.5、1、1.5、2</summary>
|
||||
@@ -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<RubberQuickTestStdChangedEvent>()
|
||||
.Subscribe(async _ => await ReloadLocalDataQuietAsync(), ThreadOption.UIThread);
|
||||
|
||||
_networkStatusToken = _eventAggregator.GetEvent<NetworkStatusChangedEvent>()
|
||||
.Subscribe(OnNetworkStatusChanged, ThreadOption.UIThread);
|
||||
|
||||
IsOfflineMode = !_networkMonitor.IsOnline;
|
||||
|
||||
RecalculateOverallInspectResult();
|
||||
}
|
||||
|
||||
private void OnNetworkStatusChanged(NetworkStatusChangedPayload payload)
|
||||
{
|
||||
IsOfflineMode = !payload.IsOnline;
|
||||
if (IsOfflineMode)
|
||||
RefreshRubberMaterialOptions();
|
||||
}
|
||||
|
||||
private bool _isOfflineMode;
|
||||
/// <summary>断网时自动进入离线模式</summary>
|
||||
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<string>("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<string>("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<string>("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<string>("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<MesXslRubberQuickTestStd> StdOptions { get; } = new();
|
||||
|
||||
public ObservableCollection<string> RubberMaterialOptions { get; } = new();
|
||||
|
||||
public ObservableCollection<MesXslRubberQuickTestStdLine> DataPointColumns { get; } = new();
|
||||
|
||||
public ObservableCollection<QuickTestInspectRowViewModel> 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;
|
||||
|
||||
/// <summary>离线模式下手动选择的胶料名称</summary>
|
||||
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<MesXslMixingProductionPlan> 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<MesXslMixingProductionPlan> FilteredByDate =>
|
||||
MixingDate == null
|
||||
? Enumerable.Empty<MesXslMixingProductionPlan>()
|
||||
: _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
|
||||
|
||||
|
||||
|
||||
/// <summary>密炼日期/机台/班次变更:编辑模式仅重置计划,不清空实验标准与检验明细</summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>编辑模式下保留已选实验标准,避免下拉选项刷新导致标准与明细被清空</summary>
|
||||
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<TabCloseRequestEvent>().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<MesXslRubberQuickTestRecordStdLine>()).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<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);
|
||||
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<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 (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<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
|
||||
};
|
||||
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<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;
|
||||
|
||||
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<NetworkStatusChangedEvent>().Unsubscribe(_networkStatusToken);
|
||||
_networkStatusToken = null;
|
||||
}
|
||||
|
||||
base.CleanUp();
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ public class RubberQuickTestRecordListViewModel : BaseViewModel
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<RubberQuickTestRecordListRow> ViewDetailCommand { get; }
|
||||
public DelegateCommand<RubberQuickTestRecordListRow> EditCommand { get; }
|
||||
public DelegateCommand<RubberQuickTestRecordListRow> DeleteCommand { get; }
|
||||
public DelegateCommand<RubberQuickTestRecordListRow> 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<RubberQuickTestRecordListRow>(async r => await ShowDetailAsync(r));
|
||||
EditCommand = new DelegateCommand<RubberQuickTestRecordListRow>(OpenEditPage, r => r != null && r.CanEdit);
|
||||
DeleteCommand = new DelegateCommand<RubberQuickTestRecordListRow>(
|
||||
async r => await DeleteAsync(r),
|
||||
r => r != null && r.CanDelete);
|
||||
SyncCommand = new DelegateCommand<RubberQuickTestRecordListRow>(
|
||||
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<TabSourceSelectedEvent>().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;
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock Text="数据来自 MES 密炼生产计划维护,桌面端只读;断网时显示本地缓存,联网后自动刷新"
|
||||
<TextBlock Text="数据来自 MES 密炼生产计划维护,每 2 秒自动同步;断网时显示本地缓存"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
FontSize="12"/>
|
||||
|
||||
@@ -50,7 +50,14 @@
|
||||
<md:PackIcon Kind="Flask" Width="18" Height="18" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="无转子流变仪 MDR S3L" FontSize="18" FontWeight="Bold"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="无转子流变仪 MDR S3L" FontSize="18" FontWeight="Bold"/>
|
||||
<Border Background="#fff7e6" BorderBrush="#ffa940" BorderThickness="1" CornerRadius="4"
|
||||
Padding="8,2" Margin="12,0,0,0"
|
||||
Visibility="{Binding IsOfflineMode, Converter={StaticResource Boolean2VisibilityConverter}}">
|
||||
<TextBlock Text="离线模式" FontSize="12" FontWeight="SemiBold" Foreground="#d46b08"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<TextBlock Text="胶料快检记录 · 密炼快检试验操作台" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
@@ -87,7 +94,8 @@
|
||||
<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="26" Padding="8,0" Margin="10,0,0,0"/>
|
||||
<Button Content="刷新演示" Command="{Binding RefreshChartDemoCommand}" Style="{StaticResource ButtonDefault}" Height="26" Padding="8,0" Margin="10,0,0,0"
|
||||
Visibility="{Binding IsEditable, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="1" Text="{Binding ChartDemoHint}" FontSize="10" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,0,0,4"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
@@ -132,7 +140,8 @@
|
||||
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
|
||||
<TextBlock Text="试验结果" Style="{StaticResource SectionTitleStyle}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
|
||||
Visibility="{Binding IsEditable, Converter={StaticResource Boolean2VisibilityConverter}}">
|
||||
<Button Content="新增检验行" Command="{Binding AddInspectRowCommand}" Style="{StaticResource ButtonPrimary}" Height="28" Padding="10,0" Margin="0,0,8,0"/>
|
||||
<Button Content="删除选中行" Command="{Binding RemoveInspectRowCommand}" Style="{StaticResource ButtonDanger}" Height="28" Padding="10,0"/>
|
||||
</StackPanel>
|
||||
@@ -145,7 +154,7 @@
|
||||
CanUserDeleteRows="False"
|
||||
HeadersVisibility="Column"
|
||||
SelectionMode="Single"
|
||||
IsReadOnly="False"
|
||||
IsReadOnly="{Binding IsReadOnly}"
|
||||
RowHeight="30"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
@@ -153,7 +162,8 @@
|
||||
Style="{StaticResource CusDataGridStyle}"
|
||||
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}">
|
||||
</DataGrid>
|
||||
<TextBlock Grid.Row="2" Text="请手填各数据点检测值,系统将根据数据标准上下限自动判定合格/不合格" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,6,0,0"/>
|
||||
<TextBlock Grid.Row="2" Text="请手填各数据点检测值,系统将根据数据标准上下限自动判定合格/不合格" FontSize="11" Foreground="{DynamicResource SecondaryTextBrush}" Margin="0,6,0,0"
|
||||
Visibility="{Binding IsEditable, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -212,9 +222,6 @@
|
||||
<Border Width="4" Height="18" CornerRadius="2" Background="#1890ff"/>
|
||||
<TextBlock Text="试验信息" Style="{StaticResource SectionTitleStyle}"/>
|
||||
</StackPanel>
|
||||
<Button Content="刷新本地数据" Command="{Binding RefreshPlansCommand}"
|
||||
Style="{StaticResource ButtonDefault}" Height="26" Padding="8,0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
|
||||
<hc:Row Gutter="10">
|
||||
@@ -234,6 +241,7 @@
|
||||
<!-- 密炼日期 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:DatePicker SelectedDate="{Binding MixingDate}"
|
||||
IsEnabled="{Binding IsEditable}"
|
||||
hc:InfoElement.Title="密炼日期"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
@@ -245,6 +253,7 @@
|
||||
<hc:Col Span="12">
|
||||
<hc:ComboBox ItemsSource="{Binding MachineOptions}"
|
||||
SelectedItem="{Binding SelectedMachine}"
|
||||
IsEnabled="{Binding IsEditable}"
|
||||
hc:InfoElement.Title="密炼机台"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
@@ -258,6 +267,7 @@
|
||||
<hc:ComboBox ItemsSource="{Binding ShiftOptions}"
|
||||
SelectedItem="{Binding SelectedShift}"
|
||||
DisplayMemberPath="Name"
|
||||
IsEnabled="{Binding IsEditable}"
|
||||
hc:InfoElement.Title="班次"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
@@ -271,6 +281,7 @@
|
||||
<hc:ComboBox ItemsSource="{Binding PlanOptions}"
|
||||
SelectedItem="{Binding SelectedPlan}"
|
||||
DisplayMemberPath="PlanMaterialNo"
|
||||
IsEnabled="{Binding IsEditable}"
|
||||
hc:InfoElement.Title="密炼计划"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
@@ -279,21 +290,34 @@
|
||||
hc:InfoElement.Symbol="*"
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
<!-- 胶料名称 -->
|
||||
<!-- 胶料名称(在线:计划带出;离线:手动选择) -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding RubberMaterialName, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
hc:InfoElement.Title="胶料名称"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="选择计划后自动带出"
|
||||
Margin="0,0,0,8"/>
|
||||
<Grid Margin="0,0,0,8">
|
||||
<hc:TextBox Text="{Binding RubberMaterialName, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
hc:InfoElement.Title="胶料名称"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="选择计划后自动带出"
|
||||
Visibility="{Binding IsOnlineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
<hc:ComboBox ItemsSource="{Binding RubberMaterialOptions}"
|
||||
SelectedItem="{Binding SelectedRubberMaterial}"
|
||||
IsEnabled="{Binding IsEditable}"
|
||||
hc:InfoElement.Title="胶料名称"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="从本地标准选择胶料"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
Visibility="{Binding IsOfflineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
</Grid>
|
||||
</hc:Col>
|
||||
<!-- 实验标准 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:ComboBox ItemsSource="{Binding StdOptions}"
|
||||
SelectedItem="{Binding SelectedStd}"
|
||||
DisplayMemberPath="StdName"
|
||||
IsEnabled="{Binding IsEditable}"
|
||||
hc:InfoElement.Title="实验标准"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
@@ -315,25 +339,27 @@
|
||||
<!-- 车次 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding TrainNo, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsReadOnly="{Binding IsReadOnly}"
|
||||
hc:InfoElement.Title="车次"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="手填车次"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
hc:InfoElement.ShowClearButton="{Binding IsEditable}"
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
<!-- 试验次数 -->
|
||||
<hc:Col Span="12">
|
||||
<hc:TextBox Text="{Binding InspectTimesText, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsReadOnly="{Binding IsReadOnly}"
|
||||
hc:InfoElement.Title="试验次数"
|
||||
hc:InfoElement.TitleWidth="90"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.Placeholder="默认 1,可修改"
|
||||
hc:InfoElement.Necessary="True"
|
||||
hc:InfoElement.Symbol="*"
|
||||
hc:InfoElement.ShowClearButton="True"
|
||||
hc:InfoElement.ShowClearButton="{Binding IsEditable}"
|
||||
Margin="0,0,0,8"/>
|
||||
</hc:Col>
|
||||
<!-- 检验结果(由试验结果区域自动判定,只读) -->
|
||||
@@ -358,11 +384,18 @@
|
||||
</hc:Col>
|
||||
</hc:Row>
|
||||
|
||||
<TextBlock Text="密炼计划、实验标准均来自桌面端本地缓存;按密炼日期筛选计划,再按胶料名称选择实验标准"
|
||||
<TextBlock Text="{Binding OfflineModeHint}"
|
||||
FontSize="11"
|
||||
Foreground="#d46b08"
|
||||
Margin="0,8,0,0"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding IsOfflineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
<TextBlock Text="密炼计划、实验标准每 2 秒自动从 MES 同步到本地缓存;断网时使用本地数据"
|
||||
FontSize="11"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
Margin="0,8,0,0"
|
||||
TextWrapping="Wrap"/>
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding IsOnlineMode, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -406,7 +439,7 @@
|
||||
<!-- 底部操作 -->
|
||||
<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="{Binding SaveButtonText}" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Height="36" MinWidth="160" Padding="12,0"
|
||||
IsEnabled="{Binding CanSave}"
|
||||
Visibility="{Binding ShowSaveButton, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
|
||||
@@ -118,6 +118,8 @@ public partial class RubberQuickTestOperationView : UserControl
|
||||
Width = 90,
|
||||
ElementStyle = centerTextStyle
|
||||
});
|
||||
|
||||
InspectResultGrid.IsReadOnly = vm.IsReadOnly;
|
||||
}
|
||||
|
||||
private void InspectResultGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
|
||||
@@ -82,12 +82,18 @@
|
||||
<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 Header="操作" Width="270">
|
||||
<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.EditCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
CommandParameter="{Binding}" Style="{StaticResource ButtonDefault}" Height="26" Padding="8,0" Margin="0,0,6,0"
|
||||
Visibility="{Binding CanEdit, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
<Button Content="同步" Command="{Binding DataContext.SyncCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
CommandParameter="{Binding}" Style="{StaticResource ButtonSuccess}" Height="26" Padding="8,0" Margin="0,0,6,0"
|
||||
Visibility="{Binding CanSync, Converter={StaticResource Boolean2VisibilityConverter}}"/>
|
||||
<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}}"/>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock Text="数据来自 MES,桌面端只读;断网时显示本地缓存"
|
||||
<TextBlock Text="数据来自 MES,每 2 秒自动同步(含标准明细);断网时显示本地缓存"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
FontSize="12"/>
|
||||
|
||||
Reference in New Issue
Block a user