You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
406 lines
13 KiB
C#
406 lines
13 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json.Serialization;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Sln.Wcs.Model.Domain;
|
|
using Sln.Wcs.Repository.service;
|
|
using Sln.Wcs.Serilog;
|
|
|
|
namespace Sln.Wcs.UI.ViewModels.Task;
|
|
|
|
public partial class ManualTaskViewModel : ObservableObject
|
|
{
|
|
private const string HoistBaseUrl = "http://localhost:5100";
|
|
|
|
private readonly ILiveTaskQueueService _taskQueueService;
|
|
private readonly ILiveTaskDetailService _taskDetailService;
|
|
private readonly HttpClient _http;
|
|
private readonly SerilogHelper _logger;
|
|
public string PageTitle => "手动执行任务";
|
|
|
|
[ObservableProperty]
|
|
private ObservableCollection<LiveTaskQueue> _tasks = new();
|
|
|
|
[ObservableProperty]
|
|
private LiveTaskQueue? _selectedTask;
|
|
|
|
[ObservableProperty]
|
|
private ObservableCollection<LiveTaskDetail> _details = new();
|
|
|
|
[ObservableProperty]
|
|
private LiveTaskDetail? _selectedDetail;
|
|
|
|
[ObservableProperty]
|
|
private string _statusText = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _isExecuting;
|
|
|
|
[ObservableProperty]
|
|
private bool _isMaterialReady;
|
|
|
|
[ObservableProperty]
|
|
private bool _taskExecuted;
|
|
|
|
[ObservableProperty]
|
|
private string _hoistInfo = "--";
|
|
|
|
[ObservableProperty]
|
|
private string _dockingPoint = "--";
|
|
|
|
public ObservableCollection<HostOption> HostOptions { get; } = new()
|
|
{
|
|
new() { Name = "15栋入库提升机", Code = "1#Host" },
|
|
new() { Name = "15栋出库提升机", Code = "2#Host" },
|
|
new() { Name = "14栋提升机", Code = "3#Host" },
|
|
new() { Name = "13栋提升机", Code = "4#Host" }
|
|
};
|
|
|
|
[ObservableProperty]
|
|
private HostOption _selectedHostOption;
|
|
|
|
public ManualTaskViewModel(
|
|
ILiveTaskQueueService taskQueueService,
|
|
ILiveTaskDetailService taskDetailService,
|
|
SerilogHelper logger)
|
|
{
|
|
_taskQueueService = taskQueueService;
|
|
_taskDetailService = taskDetailService;
|
|
_http = new HttpClient { Timeout = System.TimeSpan.FromSeconds(5) };
|
|
_logger = logger;
|
|
_selectedHostOption = HostOptions[0];
|
|
}
|
|
|
|
public Avalonia.Controls.Control CreateView() => new Views.Task.ManualTaskView();
|
|
|
|
[RelayCommand]
|
|
private void Load()
|
|
{
|
|
RefreshTaskList();
|
|
}
|
|
|
|
private void RefreshTaskList()
|
|
{
|
|
List<LiveTaskQueue> list = _taskQueueService.getLiveTaskQueues(x=>x.executionMode == 1 && x.isFlag == 1);
|
|
for (int i = 0; i < list.Count; i++) list[i].RowIndex = i + 1;
|
|
Tasks = new ObservableCollection<LiveTaskQueue>(list);
|
|
StatusText = list.Count > 0
|
|
? $"查询到 {list.Count} 条待执行手动任务"
|
|
: "暂无待执行的手动任务";
|
|
}
|
|
|
|
partial void OnSelectedTaskChanged(LiveTaskQueue? value)
|
|
{
|
|
TaskExecuted = false;
|
|
SelectedDetail = null;
|
|
HoistInfo = "--";
|
|
DockingPoint = "--";
|
|
if (value is not null)
|
|
LoadDetails(value.taskCode);
|
|
}
|
|
|
|
partial void OnSelectedDetailChanged(LiveTaskDetail? value)
|
|
{
|
|
if (value is not null && !string.IsNullOrWhiteSpace(value.execDevice))
|
|
{
|
|
var parts = value.execDevice.Split('_');
|
|
if (parts.Length == 2)
|
|
{
|
|
HoistInfo = parts[0];
|
|
DockingPoint = parts[1];
|
|
return;
|
|
}
|
|
}
|
|
HoistInfo = "--";
|
|
DockingPoint = "--";
|
|
}
|
|
|
|
private void LoadDetails(string taskCode)
|
|
{
|
|
var detailList = _taskDetailService.Query(x => x.taskCode == taskCode)
|
|
.OrderBy(d => d.objId)
|
|
.ToList();
|
|
for (int i = 0; i < detailList.Count; i++) detailList[i].RowIndex = i + 1;
|
|
Details = new ObservableCollection<LiveTaskDetail>(detailList);
|
|
//StatusText = $"任务 {taskCode},共 {detailList.Count} 条明细";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 任务执行:调用提升机调度中心 API 获取空闲提升机后下发任务
|
|
/// </summary>
|
|
[RelayCommand]
|
|
private async System.Threading.Tasks.Task ExecuteTaskAsync()
|
|
{
|
|
if (SelectedTask is null)
|
|
{
|
|
StatusText = "请先选择一条任务";
|
|
return;
|
|
}
|
|
|
|
if (SelectedTask.taskStatus != 1)
|
|
{
|
|
StatusText = "当前任务状态不允许执行";
|
|
return;
|
|
}
|
|
|
|
if (SelectedDetail is null)
|
|
{
|
|
StatusText = "请先在明细列表中选择一条提升机明细";
|
|
return;
|
|
}
|
|
|
|
var hoistDetail = SelectedDetail;
|
|
if (hoistDetail.deviceType != 2)
|
|
{
|
|
StatusText = "所选明细不是提升机步骤,请选择设备类型为提升机的明细";
|
|
return;
|
|
}
|
|
|
|
IsExecuting = true;
|
|
StatusText = "正在获取空闲提升机...";
|
|
|
|
try
|
|
{
|
|
|
|
// 1. 获取空闲提升机
|
|
string hostCode = SelectedHostOption?.Code ?? "1#Host";
|
|
if (hoistDetail.taskType == 2 && hoistDetail.taskCategory == 2 && hoistDetail.startPoint.Contains("15#"))
|
|
{
|
|
hostCode = "2#Host";
|
|
}
|
|
else
|
|
{
|
|
if (hoistDetail.startPoint.Contains("13#"))
|
|
{
|
|
hostCode = "4#Host";
|
|
}else if (hoistDetail.startPoint.Contains("14#"))
|
|
{
|
|
hostCode = "3#Host";
|
|
}else if (hoistDetail.startPoint.Contains("15#"))
|
|
{
|
|
hostCode = "1#Host";
|
|
}
|
|
}
|
|
var freeUrl = $"{HoistBaseUrl}/api/hoist/free?hostCode={Uri.EscapeDataString(hostCode)}";
|
|
FreeHoistResponse? freeRes;
|
|
while (true)
|
|
{
|
|
freeRes = await _http.GetFromJsonAsync<FreeHoistResponse>(freeUrl);
|
|
|
|
if (freeRes is { found: true })
|
|
break;
|
|
|
|
StatusText = "暂无空闲提升机,等待中...";
|
|
HoistInfo = "忙碌";
|
|
await System.Threading.Tasks.Task.Delay(1000);
|
|
}
|
|
|
|
StatusText = $"已获取空闲提升机 {freeRes.deviceName ?? freeRes.deviceCode},正在下发任务...";
|
|
|
|
// 2. 下发提升机调度任务
|
|
var dispatchBody = new
|
|
{
|
|
hostCode = freeRes.hostCode,
|
|
serialNo = freeRes.deviceSerialNo,
|
|
taskCode = hoistDetail.taskCode,
|
|
startPoint = hoistDetail.startPoint,
|
|
endPoint = hoistDetail.endPoint
|
|
};
|
|
|
|
var dispatchRes = await _http.PostAsJsonAsync($"{HoistBaseUrl}/api/task/dispatch", dispatchBody);
|
|
var dispatchResult = await dispatchRes.Content.ReadFromJsonAsync<ApiResult>();
|
|
|
|
if (dispatchResult is not { success: true })
|
|
{
|
|
StatusText = $"任务下发失败: {dispatchResult?.msg ?? "未知错误"}";
|
|
_logger.Error($"手动任务 {SelectedTask.taskCode} 下发失败");
|
|
return;
|
|
}
|
|
|
|
// 3. 更新任务状态为执行中
|
|
await System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
SelectedTask.taskStatus = 2;
|
|
_taskQueueService.Update(SelectedTask);
|
|
|
|
hoistDetail.taskStatus = 2;
|
|
hoistDetail.execDevice = $"{freeRes.hostCode}_{freeRes.deviceSerialNo}";
|
|
_taskDetailService.Update(hoistDetail);
|
|
|
|
HoistInfo = freeRes.hostCode.ToString(); // HoistInfo = freeRes.hostCode;
|
|
DockingPoint = freeRes.deviceSerialNo.ToString();
|
|
});
|
|
|
|
TaskExecuted = true;
|
|
_logger.Info($"手动任务 {SelectedTask.taskCode} 已下发到提升机 {freeRes.deviceCode}");
|
|
StatusText = $"任务 {SelectedTask.taskCode} 已下发到提升机 {freeRes.deviceName ?? freeRes.deviceCode}";
|
|
LoadDetails(SelectedTask.taskCode);
|
|
RefreshTaskList();
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
TaskExecuted = false;
|
|
HoistInfo = "--";
|
|
StatusText = $"与提升机调度中心通信失败: {ex.Message}";
|
|
_logger.Error($"HoistServer 通信失败: {ex.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
TaskExecuted = false;
|
|
HoistInfo = "--";
|
|
StatusText = $"任务执行失败: {ex.Message}";
|
|
_logger.Error($"手动任务执行失败: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
IsExecuting = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 物料到位确认:调用提升机调度中心 receive-pallet 接口
|
|
/// </summary>
|
|
[RelayCommand]
|
|
private async System.Threading.Tasks.Task MaterialReadyAsync()
|
|
{
|
|
if (SelectedTask is null)
|
|
{
|
|
StatusText = "请先选择一条任务";
|
|
return;
|
|
}
|
|
|
|
if (SelectedDetail is null)
|
|
{
|
|
StatusText = "请先在明细列表中选择一条明细";
|
|
return;
|
|
}
|
|
|
|
var hoistDetail = SelectedDetail;
|
|
|
|
if (hoistDetail.deviceType != 2)
|
|
{
|
|
StatusText = "所选明细不是提升机步骤,请选择设备类型为提升机的明细";
|
|
return;
|
|
}
|
|
|
|
if (SelectedTask.taskStatus != 2)
|
|
{
|
|
StatusText = "当前任务未在执行中,请先执行任务";
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(hoistDetail.execDevice))
|
|
{
|
|
StatusText = "当前任务未分配执行设备,请先执行任务";
|
|
return;
|
|
}
|
|
|
|
IsMaterialReady = true;
|
|
StatusText = "正在确认物料到位...";
|
|
|
|
try
|
|
{
|
|
var execParts = hoistDetail.execDevice!.Split('_');
|
|
var execHostCode = execParts[0];
|
|
var execSerialNo = int.Parse(execParts[1]);
|
|
|
|
var body = new
|
|
{
|
|
hostCode = execHostCode,
|
|
serialNo = execSerialNo,
|
|
taskCode = hoistDetail.taskCode,
|
|
palletBarcode = hoistDetail.palletBarcode ?? "",
|
|
startPoint = hoistDetail.startPoint,
|
|
endPoint = hoistDetail.endPoint
|
|
};
|
|
|
|
var res = await _http.PostAsJsonAsync($"{HoistBaseUrl}/api/hoist/receive-pallet", body);
|
|
var result = await res.Content.ReadFromJsonAsync<ApiResult>();
|
|
|
|
if (result is not { success: true })
|
|
{
|
|
StatusText = $"receive-pallet 调用失败: {result?.msg ?? "未知错误"}";
|
|
_logger.Error($"receive-pallet 失败: {result?.msg}");
|
|
return;
|
|
}
|
|
|
|
// 更新备注
|
|
await System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
SelectedTask.remark = "物料已到位";
|
|
_taskQueueService.Update(SelectedTask);
|
|
});
|
|
|
|
_logger.Info($"手动任务 {SelectedTask.taskCode} 物料到位确认,已调用 receive-pallet");
|
|
StatusText = $"任务 {SelectedTask.taskCode} 物料已到位,提升机已启动,等待完成...";
|
|
|
|
// 轮询等待提升机任务完成
|
|
var waitUrl = $"{HoistBaseUrl}/api/hoist/wait-complete?hostCode={Uri.EscapeDataString(execHostCode)}&deviceSerialNo={execSerialNo}";
|
|
while (true)
|
|
{
|
|
var waitRes = await _http.GetFromJsonAsync<WaitCompleteResponse>(waitUrl);
|
|
|
|
if (waitRes is { isComplete: true })
|
|
break;
|
|
|
|
await System.Threading.Tasks.Task.Delay(2000);
|
|
}
|
|
|
|
await System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
hoistDetail.taskStatus = 3;
|
|
_taskDetailService.Update(hoistDetail);
|
|
});
|
|
|
|
StatusText = $"任务 {SelectedTask.taskCode} 提升机已完成";
|
|
_logger.Info($"手动任务 {SelectedTask.taskCode} 提升机任务完成");
|
|
LoadDetails(SelectedTask.taskCode);
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
StatusText = $"与提升机调度中心通信失败: {ex.Message}";
|
|
_logger.Error($"HoistServer 通信失败: {ex.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusText = $"物料到位确认失败: {ex.Message}";
|
|
_logger.Error($"物料到位确认失败: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
IsMaterialReady = false;
|
|
}
|
|
}
|
|
|
|
// ---- API 响应 DTO ----
|
|
|
|
private class FreeHoistResponse
|
|
{
|
|
public bool found { get; set; }
|
|
public string? deviceCode { get; set; }
|
|
public string? deviceName { get; set; }
|
|
public string? hostCode { get; set; }
|
|
public int deviceSerialNo { get; set; }
|
|
}
|
|
|
|
private class WaitCompleteResponse
|
|
{
|
|
public bool isComplete { get; set; }
|
|
}
|
|
|
|
private class ApiResult
|
|
{
|
|
public bool success { get; set; }
|
|
public string? msg { get; set; }
|
|
}
|
|
|
|
public class HostOption
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public string Code { get; set; } = string.Empty;
|
|
}
|
|
}
|