317 lines
12 KiB
C#
317 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using StandardScene.Model;
|
|
using SimpleLite;
|
|
using SimpleCore;
|
|
using SimpleCore.Library;
|
|
using StandardScene.Utils;
|
|
using static StandardScene.Chained.ChainedDeliveryMission;
|
|
|
|
namespace StandardScene.Chained
|
|
{
|
|
public partial class DeliveryViewer : Form
|
|
{
|
|
private const int OverdueMinutesThreshold = 10000; // 约7天视为超时
|
|
private const int DisplayColumnIndexOverdueFlag = 9;
|
|
private static readonly HttpClient SharedHttpClient = new HttpClient();
|
|
/// <summary>选中行的背景色</summary>
|
|
private static readonly Color SelectedRowBackColor = Color.FromArgb(220, 230, 250);
|
|
/// <summary>缓存选中行索引,避免在 RetrieveVirtualItem 中访问 SelectedIndices 引发递归</summary>
|
|
private readonly HashSet<int> _selectedIndicesCache = new HashSet<int>();
|
|
|
|
private ListViewItem _item = null;
|
|
|
|
public DeliveryViewer()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
private readonly ContextMenuStrip strip = new ContextMenuStrip();
|
|
|
|
private void DeliveryViewer_Load(object sender, EventArgs e)
|
|
{
|
|
strip.Items.Clear();
|
|
strip.Items.Add("取消任务", null, CancelClick);
|
|
strip.Items.Add("重发任务", null, ResendClick);
|
|
strip.Items.Add("换车重发任务", null, ChangeCarResendClick);
|
|
currentTaskList.ContextMenuStrip = strip;
|
|
}
|
|
|
|
private List<string[]> _listDeliveries = new List<string[]>();
|
|
|
|
/// <summary>将任务标记为已取消(Canceled)。</summary>
|
|
private static void MarkDeliveryCanceled(Delivery d)
|
|
{
|
|
if (d == null) return;
|
|
lock (d.SyncStatus)
|
|
{
|
|
d.Canceled = true;
|
|
d.Active = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将任务状态重置为 Waiting。
|
|
/// 当 clearCarForChange=true 时,仅当状态为 Suspended 或 Waiting 且未处于放货阶段时,
|
|
/// 才会清空 UsingCar 并返回 true;否则返回 false。
|
|
/// </summary>
|
|
private static bool MarkDeliveryWaiting(Delivery d, bool clearCarForChange)
|
|
{
|
|
if (d == null) return false;
|
|
lock (d.SyncStatus)
|
|
{
|
|
var status = d.GetStatus();
|
|
if (clearCarForChange)
|
|
{
|
|
if ((status is not DeliveryStatus.Suspended and not DeliveryStatus.Waiting) || d.Putting)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
d.UsingCar = null;
|
|
}
|
|
else
|
|
{
|
|
d.SkipFetch = d.Putting;
|
|
}
|
|
|
|
d.Active = false;
|
|
d.Finished = false;
|
|
d.Error = false;
|
|
d.Canceled = false;
|
|
d.Terminated = false;
|
|
d.Suspended = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
protected virtual string[] GetDisplayContent(Delivery dd)
|
|
{
|
|
var srcName =SimpleLib.GetSite(dd.Src).name;
|
|
var dstName =SimpleLib.GetSite(dd.Dst).name;
|
|
var now = DateTime.Now;
|
|
var usingCar = dd.UsingCar == null ? string.Empty : dd.UsingCar.name;
|
|
return
|
|
[
|
|
$"{dd.Id}",
|
|
$"{usingCar}",
|
|
$"{dd.Src}-{srcName}",
|
|
$"{dd.Dst}-{dstName}",
|
|
$"{dd.GetStatus()}",
|
|
$"{dd.CreateTime:yyyy-mm-dd HH:mm:ss:fff}",
|
|
$"{dd.StartTime:yyyy-mm-dd HH:mm:ss:fff}",
|
|
$"{dd.FinishTime:yyyy-mm-dd HH:mm:ss:fff}",
|
|
|
|
$"{dd.Priority}",
|
|
$"{((now - dd.CreateTime).TotalMinutes > OverdueMinutesThreshold ? 1 : 0)}",
|
|
$"{dd.Id}"
|
|
];
|
|
}
|
|
|
|
private void TaskFlush()
|
|
{
|
|
_listDeliveries.Clear();
|
|
try
|
|
{
|
|
foreach (var cdm in SimpleProject.proj.Missions.OfType<ChainedDeliveryMission>())
|
|
foreach (var dd in cdm.GetDeliveries(checkBox1.Checked, checkBox2.Checked,checkBox2.Checked,checkBox2.Checked))
|
|
_listDeliveries.Add(GetDisplayContent(dd));
|
|
|
|
if (_listDeliveries.Count > 0)
|
|
{
|
|
var len = _listDeliveries[0].Length;
|
|
if (len > 0) _listDeliveries = _listDeliveries.OrderByDescending(p => int.Parse(p[len - 2])).ToList();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Post($"TaskFlush 异常: {ExceptionFormatter.FormatEx(ex)}");
|
|
}
|
|
|
|
}
|
|
|
|
private void timer1_Tick(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
TaskFlush();
|
|
currentTaskList.VirtualListSize = _listDeliveries.Count;
|
|
currentTaskList.Invalidate();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Post($"timer1_Tick 异常: {ExceptionFormatter.FormatEx(ex)}");
|
|
}
|
|
|
|
}
|
|
|
|
private void currentTaskList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
var n = e.ItemIndex;
|
|
e.Item = new ListViewItem(_listDeliveries[n]);
|
|
if (_listDeliveries[n].Length > DisplayColumnIndexOverdueFlag && _listDeliveries[n][DisplayColumnIndexOverdueFlag] == "1")
|
|
e.Item.ForeColor = Color.Red;
|
|
if (_selectedIndicesCache.Contains(n))
|
|
e.Item.BackColor = SelectedRowBackColor;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
e.Item = new ListViewItem(["", "", "", "", "", "", "", "", ""]);
|
|
}
|
|
}
|
|
|
|
private void currentTaskList_MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button != MouseButtons.Right) return;
|
|
_item = currentTaskList.GetItemAt(e.X, e.Y);
|
|
}
|
|
|
|
private void ResendClick(object sender, EventArgs e)
|
|
{
|
|
if (_item == null) return;
|
|
string taskCode = _item.Text;
|
|
try
|
|
{
|
|
var cdm = SimpleProject.proj.Missions.OfType<TransportMission>().FirstOrDefault();
|
|
if (cdm == null) return;
|
|
var d = cdm.GetDeliveries(true, true, true, true)
|
|
.OfType<TransportDelivery>()
|
|
.FirstOrDefault(s => s.Id == taskCode);
|
|
if (d == null)
|
|
{
|
|
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
var ms = MessageBox.Show($"是否重发任务--{taskCode}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
|
|
if (ms != System.Windows.Forms.DialogResult.OK) return;
|
|
if (!MarkDeliveryWaiting(d, clearCarForChange: false))
|
|
{
|
|
MessageBox.Show("重发任务失败:当前状态不允许重发", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
|
|
// 状态已改为 Waiting,持久化
|
|
cdm.PersistDelivery(d);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
}
|
|
}
|
|
|
|
private void CancelClick(object sender, EventArgs e)
|
|
{
|
|
if (_item == null) return;
|
|
string str = _item.Text;
|
|
try
|
|
{
|
|
var cdm = SimpleProject.proj.Missions.OfType<TransportMission>().FirstOrDefault();
|
|
if (cdm == null) return;
|
|
var d = cdm.GetDeliveries(true, true, true, true)
|
|
.OfType<TransportDelivery>()
|
|
.FirstOrDefault(s => s.Id == str);
|
|
if (d == null)
|
|
{
|
|
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
var ms = MessageBox.Show($"是否结束任务--{str}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
|
|
if (ms != System.Windows.Forms.DialogResult.OK || d.IsFinished()) return;
|
|
|
|
// 1) 状态上将任务标记为已取消
|
|
MarkDeliveryCanceled(d);
|
|
|
|
// 2) 若小车当前正在执行该任务,则下发 reset 指令
|
|
bool excutingTask = d.UsingCar != null && d.UsingCar.tags.IsEqual("taskCode", d.TaskId);
|
|
if (excutingTask && d.UsingCar != null
|
|
&& (d.UsingCar.tags?.Contains("occupied") == true || (d.UsingCar.status?.pendingLocks?.Length ?? 0) != 0))
|
|
{
|
|
_ = SharedHttpClient.GetStringAsync($"http://{d.UsingCar.address}:8008/reset");
|
|
Diagnosis.Log($"手动结束任务;{d.TaskId}", "task", true);
|
|
}
|
|
|
|
// 3) 持久化已取消状态
|
|
cdm.PersistDelivery(d);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Post($"结束任务 {str} 异常: {ExceptionFormatter.FormatEx(ex)}");
|
|
}
|
|
}
|
|
|
|
private void ChangeCarResendClick(object sender, EventArgs e)
|
|
{
|
|
if (_item == null) return;
|
|
string taskCode = _item.Text;
|
|
try
|
|
{
|
|
var cdm = SimpleProject.proj.Missions.OfType<TransportMission>().FirstOrDefault();
|
|
if (cdm == null) return;
|
|
var d = cdm.GetDeliveries(true, true, true, true)
|
|
.OfType<TransportDelivery>()
|
|
.FirstOrDefault(s => s.Id == taskCode);
|
|
if (d == null)
|
|
{
|
|
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
|
|
var ms = MessageBox.Show($"是否换车重发任务--{taskCode}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
|
|
if (ms != System.Windows.Forms.DialogResult.OK) return;
|
|
|
|
if (!MarkDeliveryWaiting(d, clearCarForChange: true))
|
|
{
|
|
MessageBox.Show("换车重发失败:仅当任务状态为 Suspended 或 Waiting 且未处于放货阶段时才允许换车重发", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
|
|
// 状态已改为 Waiting 且 UsingCar 已清空,持久化
|
|
cdm.PersistDelivery(d);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnosis.Post($"换车重发任务 {taskCode} 异常: {ExceptionFormatter.FormatEx(ex)}");
|
|
MessageBox.Show("换车重发任务异常,请查看日志", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
}
|
|
}
|
|
|
|
private void DeliveryViewer_FormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
if (e.CloseReason == CloseReason.UserClosing)
|
|
{
|
|
e.Cancel = true;
|
|
this.Visible = false;
|
|
timer1.Stop();
|
|
}
|
|
}
|
|
|
|
protected override void SetVisibleCore(bool value)
|
|
{
|
|
if (!IsHandleCreated && value)
|
|
CreateHandle();
|
|
bool wasVisible = Visible;
|
|
base.SetVisibleCore(value);
|
|
if (value && !wasVisible)
|
|
timer1.Start();
|
|
}
|
|
|
|
private void currentTaskList_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
_selectedIndicesCache.Clear();
|
|
foreach (int i in currentTaskList.SelectedIndices)
|
|
_selectedIndicesCache.Add(i);
|
|
this.BeginInvoke(() => currentTaskList.Invalidate());
|
|
}
|
|
}
|
|
}
|