init commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SimpleCore.Library;
|
||||
|
||||
namespace StandardScene.Chained
|
||||
{
|
||||
/// <summary>
|
||||
/// 负责根据 Delivery 上记录的回调 key 列表,通过回调注册表统一挂载所有事件回调。
|
||||
/// </summary>
|
||||
public static class DeliveryCallbackAttacher
|
||||
{
|
||||
public static void AttachAll(ChainedDeliveryMission.Delivery d)
|
||||
{
|
||||
if (d == null) return;
|
||||
|
||||
// OnStart
|
||||
foreach (var key in d.OnStartCallbackKeys.Distinct())
|
||||
{
|
||||
var cb = DeliveryCallbackRegistry.ResolveOnStart(key);
|
||||
if (cb != null) d.OnStart += cb;
|
||||
else Diagnosis.Log($"Unknown OnStart callback key: {key}", "DeliveryCallbackAttacher");
|
||||
}
|
||||
|
||||
// DoneFetch
|
||||
foreach (var key in d.DoneFetchCallbackKeys.Distinct())
|
||||
{
|
||||
var cb = DeliveryCallbackRegistry.ResolveDoneFetch(key);
|
||||
if (cb != null) d.DoneFetch += cb;
|
||||
else Diagnosis.Log($"Unknown DoneFetch callback key: {key}", "DeliveryCallbackAttacher");
|
||||
}
|
||||
|
||||
// DonePut
|
||||
foreach (var key in d.DonePutCallbackKeys.Distinct())
|
||||
{
|
||||
var cb = DeliveryCallbackRegistry.ResolveDonePut(key);
|
||||
if (cb != null) d.DonePut += cb;
|
||||
else Diagnosis.Log($"Unknown DonePut callback key: {key}", "DeliveryCallbackAttacher");
|
||||
}
|
||||
|
||||
// DoneMission
|
||||
foreach (var key in d.DoneMissionCallbackKeys.Distinct())
|
||||
{
|
||||
var cb = DeliveryCallbackRegistry.ResolveDoneMission(key);
|
||||
if (cb != null) d.DoneMission += cb;
|
||||
else Diagnosis.Log($"Unknown DoneMission callback key: {key}", "DeliveryCallbackAttacher");
|
||||
}
|
||||
|
||||
// Failed
|
||||
foreach (var key in d.FailedCallbackKeys.Distinct())
|
||||
{
|
||||
var cb = DeliveryCallbackRegistry.ResolveFailed(key);
|
||||
if (cb != null) d.Failed += cb;
|
||||
else Diagnosis.Log($"Unknown Failed callback key: {key}", "DeliveryCallbackAttacher");
|
||||
}
|
||||
|
||||
// OnTerminated
|
||||
foreach (var key in d.OnTerminatedCallbackKeys.Distinct())
|
||||
{
|
||||
var cb = DeliveryCallbackRegistry.ResolveOnTerminated(key);
|
||||
if (cb != null) d.OnTerminated += cb;
|
||||
else Diagnosis.Log($"Unknown OnTerminated callback key: {key}", "DeliveryCallbackAttacher");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StandardScene.Chained
|
||||
{
|
||||
/// <summary>
|
||||
/// 可用于区分 Delivery 各生命周期事件的枚举。
|
||||
/// </summary>
|
||||
public enum DeliveryEventType
|
||||
{
|
||||
OnStart,
|
||||
DoneFetch,
|
||||
DonePut,
|
||||
DoneMission,
|
||||
Failed,
|
||||
OnTerminated
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局任务回调注册表:通过 (事件类型, key) 注册/解析各阶段回调,便于持久化和恢复。
|
||||
/// </summary>
|
||||
public static class DeliveryCallbackRegistry
|
||||
{
|
||||
private static readonly Dictionary<(DeliveryEventType, string), Delegate> _callbacks = new();
|
||||
|
||||
private static void RegisterInternal(DeliveryEventType type, string key, Delegate callback)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key) || callback == null) return;
|
||||
_callbacks[(type, key)] = callback;
|
||||
}
|
||||
|
||||
private static T ResolveInternal<T>(DeliveryEventType type, string key) where T : class
|
||||
{
|
||||
if (key == null) return null;
|
||||
return _callbacks.TryGetValue((type, key), out var d) ? d as T : null;
|
||||
}
|
||||
|
||||
public static void RegisterOnStart(string key, Action<ChainedDeliveryMission.Delivery> callback) =>
|
||||
RegisterInternal(DeliveryEventType.OnStart, key, callback);
|
||||
|
||||
public static void RegisterDoneFetch(string key, Action<ChainedDeliveryMission.Delivery> callback) =>
|
||||
RegisterInternal(DeliveryEventType.DoneFetch, key, callback);
|
||||
|
||||
public static void RegisterDonePut(string key, Action<ChainedDeliveryMission.Delivery> callback) =>
|
||||
RegisterInternal(DeliveryEventType.DonePut, key, callback);
|
||||
|
||||
public static void RegisterDoneMission(string key, Action<ChainedDeliveryMission.Delivery> callback) =>
|
||||
RegisterInternal(DeliveryEventType.DoneMission, key, callback);
|
||||
|
||||
public static void RegisterFailed(string key, Action<ChainedDeliveryMission.Delivery> callback) =>
|
||||
RegisterInternal(DeliveryEventType.Failed, key, callback);
|
||||
|
||||
public static void RegisterOnTerminated(string key, Func<ChainedDeliveryMission.Delivery, string, Task<int>> callback) =>
|
||||
RegisterInternal(DeliveryEventType.OnTerminated, key, callback);
|
||||
|
||||
public static Action<ChainedDeliveryMission.Delivery> ResolveOnStart(string key) =>
|
||||
ResolveInternal<Action<ChainedDeliveryMission.Delivery>>(DeliveryEventType.OnStart, key);
|
||||
|
||||
public static Action<ChainedDeliveryMission.Delivery> ResolveDoneFetch(string key) =>
|
||||
ResolveInternal<Action<ChainedDeliveryMission.Delivery>>(DeliveryEventType.DoneFetch, key);
|
||||
|
||||
public static Action<ChainedDeliveryMission.Delivery> ResolveDonePut(string key) =>
|
||||
ResolveInternal<Action<ChainedDeliveryMission.Delivery>>(DeliveryEventType.DonePut, key);
|
||||
|
||||
public static Action<ChainedDeliveryMission.Delivery> ResolveDoneMission(string key) =>
|
||||
ResolveInternal<Action<ChainedDeliveryMission.Delivery>>(DeliveryEventType.DoneMission, key);
|
||||
|
||||
public static Action<ChainedDeliveryMission.Delivery> ResolveFailed(string key) =>
|
||||
ResolveInternal<Action<ChainedDeliveryMission.Delivery>>(DeliveryEventType.Failed, key);
|
||||
|
||||
public static Func<ChainedDeliveryMission.Delivery, string, Task<int>> ResolveOnTerminated(string key) =>
|
||||
ResolveInternal<Func<ChainedDeliveryMission.Delivery, string, Task<int>>>(DeliveryEventType.OnTerminated, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace StandardScene.Chained
|
||||
{
|
||||
partial class DeliveryViewer
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
if (disposing)
|
||||
{
|
||||
strip?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.currentTaskList = new System.Windows.Forms.ListView();
|
||||
this.columnHeader8 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader5 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader9 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader7 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.timer1 = new System.Windows.Forms.Timer(this.components);
|
||||
this.checkBox1 = new System.Windows.Forms.CheckBox();
|
||||
this.checkBox2 = new System.Windows.Forms.CheckBox();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// currentTaskList
|
||||
//
|
||||
this.currentTaskList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.currentTaskList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.columnHeader8,
|
||||
this.columnHeader1,
|
||||
this.columnHeader4,
|
||||
this.columnHeader5,
|
||||
this.columnHeader9,
|
||||
this.columnHeader6,
|
||||
this.columnHeader2,
|
||||
this.columnHeader7,
|
||||
this.columnHeader3});
|
||||
this.currentTaskList.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
|
||||
this.currentTaskList.FullRowSelect = true;
|
||||
this.currentTaskList.GridLines = true;
|
||||
this.currentTaskList.HideSelection = false;
|
||||
this.currentTaskList.Location = new System.Drawing.Point(38, 62);
|
||||
this.currentTaskList.Name = "currentTaskList";
|
||||
this.currentTaskList.Size = new System.Drawing.Size(1146, 429);
|
||||
this.currentTaskList.TabIndex = 2;
|
||||
this.currentTaskList.UseCompatibleStateImageBehavior = false;
|
||||
this.currentTaskList.View = System.Windows.Forms.View.Details;
|
||||
this.currentTaskList.VirtualMode = true;
|
||||
this.currentTaskList.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.currentTaskList_RetrieveVirtualItem);
|
||||
this.currentTaskList.SelectedIndexChanged += new System.EventHandler(this.currentTaskList_SelectedIndexChanged);
|
||||
this.currentTaskList.MouseClick += new System.Windows.Forms.MouseEventHandler(this.currentTaskList_MouseClick);
|
||||
//
|
||||
// columnHeader8
|
||||
//
|
||||
this.columnHeader8.Text = "任务号";
|
||||
this.columnHeader8.Width = 130;
|
||||
//
|
||||
// columnHeader1
|
||||
//
|
||||
this.columnHeader1.Text = "小车";
|
||||
this.columnHeader1.Width = 100;
|
||||
//
|
||||
// columnHeader4
|
||||
//
|
||||
this.columnHeader4.Text = "取货点";
|
||||
this.columnHeader4.Width = 130;
|
||||
//
|
||||
// columnHeader5
|
||||
//
|
||||
this.columnHeader5.Text = "放货点";
|
||||
this.columnHeader5.Width = 130;
|
||||
//
|
||||
// columnHeader9
|
||||
//
|
||||
this.columnHeader9.Text = "任务状态";
|
||||
this.columnHeader9.Width = 100;
|
||||
//
|
||||
// columnHeader6
|
||||
//
|
||||
this.columnHeader6.Text = "下发时间";
|
||||
this.columnHeader6.Width = 130;
|
||||
//
|
||||
// columnHeader2
|
||||
//
|
||||
this.columnHeader2.Text = "执行时间";
|
||||
this.columnHeader2.Width = 130;
|
||||
//
|
||||
// columnHeader7
|
||||
//
|
||||
this.columnHeader7.Text = "结束时间";
|
||||
this.columnHeader7.Width = 130;
|
||||
//
|
||||
// columnHeader3
|
||||
//
|
||||
this.columnHeader3.Text = "优先级";
|
||||
this.columnHeader3.Width = 83;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
|
||||
this.label2.Location = new System.Drawing.Point(33, 7);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(88, 26);
|
||||
this.label2.TabIndex = 3;
|
||||
this.label2.Text = "任务列表";
|
||||
//
|
||||
// timer1
|
||||
//
|
||||
this.timer1.Enabled = true;
|
||||
this.timer1.Interval = 1000;
|
||||
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
|
||||
//
|
||||
// checkBox1
|
||||
//
|
||||
this.checkBox1.AutoSize = true;
|
||||
this.checkBox1.Checked = true;
|
||||
this.checkBox1.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.checkBox1.Location = new System.Drawing.Point(127, 15);
|
||||
this.checkBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
|
||||
this.checkBox1.Name = "checkBox1";
|
||||
this.checkBox1.Size = new System.Drawing.Size(108, 16);
|
||||
this.checkBox1.TabIndex = 4;
|
||||
this.checkBox1.Text = "显示已完成任务";
|
||||
this.checkBox1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkBox2
|
||||
//
|
||||
this.checkBox2.AutoSize = true;
|
||||
this.checkBox2.Checked = true;
|
||||
this.checkBox2.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.checkBox2.Location = new System.Drawing.Point(239, 14);
|
||||
this.checkBox2.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
|
||||
this.checkBox2.Name = "checkBox2";
|
||||
this.checkBox2.Size = new System.Drawing.Size(318, 16);
|
||||
this.checkBox2.TabIndex = 5;
|
||||
this.checkBox2.Text = "显示废止的任务(包括Error、Canceled、Terminated)";
|
||||
this.checkBox2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// DeliveryViewer
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1199, 551);
|
||||
this.Controls.Add(this.checkBox2);
|
||||
this.Controls.Add(this.checkBox1);
|
||||
this.Controls.Add(this.label2);
|
||||
this.Controls.Add(this.currentTaskList);
|
||||
this.Name = "DeliveryViewer";
|
||||
this.Text = "DeliveryViewer";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DeliveryViewer_FormClosing);
|
||||
this.Load += new System.EventHandler(this.DeliveryViewer_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader4;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader5;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader6;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader7;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader8;
|
||||
private System.Windows.Forms.Timer timer1;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader1;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader2;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader9;
|
||||
private System.Windows.Forms.CheckBox checkBox1;
|
||||
private System.Windows.Forms.CheckBox checkBox2;
|
||||
public System.Windows.Forms.ListView currentTaskList;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -0,0 +1,58 @@
|
||||
using StandardScene.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static StandardScene.Chained.AbstractLoopMission;
|
||||
|
||||
namespace StandardScene.Chained.Loop
|
||||
{
|
||||
/// <summary>
|
||||
/// 进入点规则:返回是否允许进入。
|
||||
/// </summary>
|
||||
public interface IEnterRule
|
||||
{
|
||||
bool CanEnter(LoopPoint point, LoopTask task, object context = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 离开点规则:返回是否允许离开。
|
||||
/// </summary>
|
||||
public interface IExitRule
|
||||
{
|
||||
bool CanExit(LoopPoint point, LoopTask task, object context = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合流规则:从候选任务中选择一个
|
||||
/// </summary>
|
||||
public interface IJoinRule
|
||||
{
|
||||
LoopTask SelectJoin(IEnumerable<LoopTask> candidates, LoopPoint point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分流规则:为任务选取下一分支标识
|
||||
/// </summary>
|
||||
public interface IBranchRule
|
||||
{
|
||||
string SelectBranch(LoopPoint point, LoopTask task, object context = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务策略接口:提供任务数据来源与基础策略决策(由 LoopViewer 或其它组件实现/替换)。
|
||||
/// </summary>
|
||||
public interface ITaskStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回当前策略下的任务集合(策略负责数据来源,例如读取 LoopViewer 的 JSON)。
|
||||
/// </summary>
|
||||
IEnumerable<LoopTask> GetTasks();
|
||||
|
||||
/// <summary>
|
||||
/// 刷新策略数据(例如重新加载 JSON)。
|
||||
/// </summary>
|
||||
void Refresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StandardScene.Chained.Loop
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发器适配器统一接口:外部设备(PLC、按钮盒、API 网关等)实现此接口并触发事件。
|
||||
/// </summary>
|
||||
public interface ITriggerAdapter : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 外部触发事件:Source 用于区分来源("PLC","ButtonBox","API"等),Key/Value 为业务自定义负载。
|
||||
/// </summary>
|
||||
event EventHandler<TriggerEventArgs> TriggerRaised;
|
||||
|
||||
/// <summary>
|
||||
/// 可选:启动适配器(开启轮询、建立连接等)。
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// 可选:停止适配器。
|
||||
/// </summary>
|
||||
void Stop();
|
||||
}
|
||||
|
||||
public class TriggerEventArgs : EventArgs
|
||||
{
|
||||
public string Source { get; set; } = string.Empty;
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public object Value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using IoTClient.Clients.PLC;
|
||||
using IoTClient.Common.Enums;
|
||||
using LessokajiWeaverUtilities.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using SimpleLite.RCS;
|
||||
using SimpleLite.RCS.CarTypes;
|
||||
using SimpleCore.Library;
|
||||
using StandardScene.Chained;
|
||||
using StandardScene.Model;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using static StandardScene.Chained.AbstractLoopMission;
|
||||
|
||||
namespace StandardScene
|
||||
{
|
||||
|
||||
|
||||
public class LoopMissionStatus : AbstractLoopMissionStatus
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[MissionType(Name = "环线进程", editor = typeof(LoopMission))]
|
||||
[I18N.DocumentTranslation(Name = "Loop Mission", locale = "en")]
|
||||
public class LoopMission : AbstractLoopMission
|
||||
{
|
||||
[JsonIgnore] public override MissionStatus status { get; set; } = new LoopMissionStatus();
|
||||
[JsonIgnore] public Thread myThread;
|
||||
[JsonIgnore] public bool started = false;
|
||||
[JsonIgnore] private readonly ConcurrentDictionary<int, byte> _buttonTriggeredSiteIds = new ConcurrentDictionary<int, byte>();
|
||||
public float ChargesocStandard = 30;
|
||||
|
||||
public float NoChargesocStandard = 80;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// API 触发:检查站点物料状态
|
||||
/// </summary>
|
||||
protected override ExternalTriggerResult OnApiTrigger(int currentSiteId, LoopTask task, Car car)
|
||||
{
|
||||
/* var site = SimpleLib.GetSite(currentSiteId);
|
||||
if (site != null && site.fields.TryGetValue("hasMaterial", out var val) && val == "true")
|
||||
{
|
||||
// 物料存在,使用配置目标
|
||||
return ExternalTriggerResult.UseConfigTarget();
|
||||
}
|
||||
return ExternalTriggerResult.Fail();*/
|
||||
if (car.status.enums.TryGetValue("Soc", out var soc))
|
||||
{
|
||||
if (float.Parse(soc) <= ChargesocStandard)
|
||||
{
|
||||
//使用配置目标
|
||||
return ExternalTriggerResult.UseConfigTarget();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* var plcTarget = 17;
|
||||
if (plcTarget > 0)
|
||||
{
|
||||
// PLC 指定了目标站点
|
||||
return ExternalTriggerResult.UseTarget(plcTarget);
|
||||
}*/
|
||||
return ExternalTriggerResult.Fail();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PLC 触发:根据 PLC 信号决定目标
|
||||
/// </summary>
|
||||
protected override ExternalTriggerResult OnPlcTrigger(int currentSiteId, LoopTask task, Car car)
|
||||
{
|
||||
//var plcTarget = ReadPlcTargetSite(currentSiteId);
|
||||
SiemensClient client = new SiemensClient(SiemensVersion.S7_200Smart, "127.0.0.1", 103);
|
||||
var M100 = client.ReadBoolean("M100").Value;
|
||||
if (M100)
|
||||
{
|
||||
//使用配置目标
|
||||
return ExternalTriggerResult.UseConfigTarget();
|
||||
|
||||
}
|
||||
|
||||
/* var plcTarget = 17;
|
||||
if (plcTarget > 0)
|
||||
{
|
||||
// PLC 指定了目标站点
|
||||
return ExternalTriggerResult.UseTarget(plcTarget);
|
||||
}*/
|
||||
return ExternalTriggerResult.Fail();
|
||||
}
|
||||
protected override ExternalTriggerResult OnChargeTrigger(int currentSiteId, LoopTask task, Car car)
|
||||
{
|
||||
|
||||
//使用配置目标
|
||||
return ExternalTriggerResult.UseConfigTarget();
|
||||
if (car.status.enums.TryGetValue("Soc", out var soc))
|
||||
{
|
||||
if (float.Parse(soc) >= NoChargesocStandard)
|
||||
{
|
||||
//使用配置目标
|
||||
return ExternalTriggerResult.UseConfigTarget();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 供 ButtonMission 反射调用:登记一个需要按钮放行的站点。
|
||||
/// 如果站点已经存在于字典中则忽略,避免重复放行信号堆积。
|
||||
/// </summary>
|
||||
/// <param name="siteId">需要放行的站点 ID,通常对应 LoopTask.CurrentStationId</param>
|
||||
/// <returns>始终返回 true,表示本次按钮触发已被接受</returns>
|
||||
public bool EnqueueButtonTriggerSite(int siteId)
|
||||
{
|
||||
if (siteId <= 0)
|
||||
{
|
||||
Diagnosis.Log($"按钮放行登记失败:无效站点 {siteId}", "LoopMission", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
var car = FindCarArrivedAtSite(siteId);
|
||||
if (car == null)
|
||||
{
|
||||
Diagnosis.Post($"按钮放行登记忽略:站点 {siteId} 当前没有到站车辆", "LoopMission", false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_buttonTriggeredSiteIds.TryAdd(siteId, 0))
|
||||
{
|
||||
Diagnosis.Post($"按钮放行登记成功:站点 {siteId},车辆 {car.name}", "LoopMission", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Diagnosis.Post($"按钮放行已存在,忽略重复登记:站点 {siteId}", "LoopMission", false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按钮盒触发:只有当前站点已被按钮登记过,才允许使用配置目标放行。
|
||||
/// 命中后立即消费并删除,保证一次按钮只放行一次。
|
||||
/// </summary>
|
||||
protected override ExternalTriggerResult OnButtonTrigger(int currentSiteId, LoopTask task, Car car)
|
||||
{
|
||||
if (_buttonTriggeredSiteIds.TryRemove(currentSiteId, out _))
|
||||
{
|
||||
Diagnosis.Post($"按钮放行消费成功:站点 {currentSiteId},车辆 {car?.name}", "LoopMission", false);
|
||||
return ExternalTriggerResult.UseConfigTarget();
|
||||
}
|
||||
|
||||
return ExternalTriggerResult.Fail();
|
||||
}
|
||||
|
||||
/* private int ReadPlcTargetSite(int siteId)
|
||||
{
|
||||
// 从 PLC 读取目标站点的业务逻辑
|
||||
// 返回 0 表示未获取到有效目标
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
+570
@@ -0,0 +1,570 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LoopViewerApp
|
||||
{
|
||||
partial class LoopViewer
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
private ComboBox cmbTaskKind;
|
||||
private NumericUpDown numCurrent;
|
||||
private NumericUpDown numTarget;
|
||||
private NumericUpDown numTraffic;
|
||||
private CheckBox chkViaPoint;
|
||||
private ComboBox cmbStartType;
|
||||
private NumericUpDown numPriority;
|
||||
private Button btnEdit; // 保留字段以供代码逻辑/样式使用(在界面上隐藏)
|
||||
private Button btnDelete; // 保留字段以供代码逻辑/样式使用(在界面上隐藏)
|
||||
private Button btnSave;
|
||||
private Button btnCancel;
|
||||
private ListView lstTasks;
|
||||
private GroupBox grpEdit;
|
||||
|
||||
// 布局控件
|
||||
private SplitContainer splitContainer;
|
||||
private TableLayoutPanel tlpEdit;
|
||||
private FlowLayoutPanel flpButtons;
|
||||
|
||||
// 中间竖向按钮(列表与编辑区之间)
|
||||
private Panel pnlMiddle;
|
||||
private FlowLayoutPanel flpMiddle;
|
||||
private Button btnMiddleEdit;
|
||||
private Button btnMiddleDelete;
|
||||
|
||||
// 列头
|
||||
private ColumnHeader colId;
|
||||
private ColumnHeader colTaskType;
|
||||
private ColumnHeader colCurrent;
|
||||
private ColumnHeader colTarget;
|
||||
private ColumnHeader colTraffic;
|
||||
private ColumnHeader colPriority;
|
||||
private ColumnHeader colViaPoint;
|
||||
private ColumnHeader colStartType;
|
||||
|
||||
// 标签字段(编辑区)
|
||||
private Label lblKind;
|
||||
private Label lblCurrent;
|
||||
private Label lblTarget;
|
||||
private Label lblTraffic;
|
||||
private Label lblPriority;
|
||||
private Label lblVia;
|
||||
private Label lblStartType;
|
||||
private Label lblEditingId; // 显示当前编辑的任务ID
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.splitContainer = new System.Windows.Forms.SplitContainer();
|
||||
this.pnlMiddle = new System.Windows.Forms.Panel();
|
||||
this.flpMiddle = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.btnMiddleEdit = new System.Windows.Forms.Button();
|
||||
this.btnMiddleDelete = new System.Windows.Forms.Button();
|
||||
this.lstTasks = new System.Windows.Forms.ListView();
|
||||
this.colId = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.colTaskType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.colCurrent = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.colTarget = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.colTraffic = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.colPriority = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.colViaPoint = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.colStartType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.grpEdit = new System.Windows.Forms.GroupBox();
|
||||
this.tlpEdit = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.lblEditingId = new System.Windows.Forms.Label();
|
||||
this.lblKind = new System.Windows.Forms.Label();
|
||||
this.cmbTaskKind = new System.Windows.Forms.ComboBox();
|
||||
this.lblCurrent = new System.Windows.Forms.Label();
|
||||
this.numCurrent = new System.Windows.Forms.NumericUpDown();
|
||||
this.lblTarget = new System.Windows.Forms.Label();
|
||||
this.numTarget = new System.Windows.Forms.NumericUpDown();
|
||||
this.lblTraffic = new System.Windows.Forms.Label();
|
||||
this.numTraffic = new System.Windows.Forms.NumericUpDown();
|
||||
this.lblPriority = new System.Windows.Forms.Label();
|
||||
this.numPriority = new System.Windows.Forms.NumericUpDown();
|
||||
this.lblVia = new System.Windows.Forms.Label();
|
||||
this.chkViaPoint = new System.Windows.Forms.CheckBox();
|
||||
this.lblStartType = new System.Windows.Forms.Label();
|
||||
this.cmbStartType = new System.Windows.Forms.ComboBox();
|
||||
this.flpButtons = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.btnSave = new System.Windows.Forms.Button();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.btnEdit = new System.Windows.Forms.Button();
|
||||
this.btnDelete = new System.Windows.Forms.Button();
|
||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
|
||||
this.splitContainer.Panel1.SuspendLayout();
|
||||
this.splitContainer.Panel2.SuspendLayout();
|
||||
this.splitContainer.SuspendLayout();
|
||||
this.pnlMiddle.SuspendLayout();
|
||||
this.flpMiddle.SuspendLayout();
|
||||
this.grpEdit.SuspendLayout();
|
||||
this.tlpEdit.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numCurrent)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numTarget)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numTraffic)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numPriority)).BeginInit();
|
||||
this.flpButtons.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// splitContainer
|
||||
//
|
||||
this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.splitContainer.Location = new System.Drawing.Point(0, 0);
|
||||
this.splitContainer.Name = "splitContainer";
|
||||
//
|
||||
// splitContainer.Panel1
|
||||
//
|
||||
this.splitContainer.Panel1.Controls.Add(this.pnlMiddle);
|
||||
this.splitContainer.Panel1.Controls.Add(this.lstTasks);
|
||||
//
|
||||
// splitContainer.Panel2
|
||||
//
|
||||
this.splitContainer.Panel2.Controls.Add(this.grpEdit);
|
||||
this.splitContainer.Size = new System.Drawing.Size(1200, 600);
|
||||
this.splitContainer.SplitterDistance = 680;
|
||||
this.splitContainer.SplitterWidth = 6;
|
||||
this.splitContainer.TabIndex = 0;
|
||||
//
|
||||
// pnlMiddle
|
||||
//
|
||||
this.pnlMiddle.Controls.Add(this.flpMiddle);
|
||||
this.pnlMiddle.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.pnlMiddle.Location = new System.Drawing.Point(614, 0);
|
||||
this.pnlMiddle.Name = "pnlMiddle";
|
||||
this.pnlMiddle.Padding = new System.Windows.Forms.Padding(6);
|
||||
this.pnlMiddle.Size = new System.Drawing.Size(66, 600);
|
||||
this.pnlMiddle.TabIndex = 0;
|
||||
//
|
||||
// flpMiddle
|
||||
//
|
||||
this.flpMiddle.Anchor = System.Windows.Forms.AnchorStyles.None;
|
||||
this.flpMiddle.Controls.Add(this.btnMiddleEdit);
|
||||
this.flpMiddle.Controls.Add(this.btnMiddleDelete);
|
||||
this.flpMiddle.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||
this.flpMiddle.Location = new System.Drawing.Point(0, 220);
|
||||
this.flpMiddle.Name = "flpMiddle";
|
||||
this.flpMiddle.Padding = new System.Windows.Forms.Padding(2);
|
||||
this.flpMiddle.Size = new System.Drawing.Size(63, 160);
|
||||
this.flpMiddle.TabIndex = 0;
|
||||
this.flpMiddle.WrapContents = false;
|
||||
//
|
||||
// btnMiddleEdit
|
||||
//
|
||||
this.btnMiddleEdit.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.btnMiddleEdit.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnMiddleEdit.Font = new System.Drawing.Font("微软雅黑", 9F);
|
||||
this.btnMiddleEdit.Location = new System.Drawing.Point(6, 12);
|
||||
this.btnMiddleEdit.Margin = new System.Windows.Forms.Padding(4, 10, 4, 4);
|
||||
this.btnMiddleEdit.Name = "btnMiddleEdit";
|
||||
this.btnMiddleEdit.Size = new System.Drawing.Size(50, 40);
|
||||
this.btnMiddleEdit.TabIndex = 0;
|
||||
this.btnMiddleEdit.Text = "编辑";
|
||||
this.btnMiddleEdit.UseVisualStyleBackColor = false;
|
||||
this.btnMiddleEdit.Click += new System.EventHandler(this.btnEdit_Click);
|
||||
//
|
||||
// btnMiddleDelete
|
||||
//
|
||||
this.btnMiddleDelete.BackColor = System.Drawing.Color.LightCoral;
|
||||
this.btnMiddleDelete.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnMiddleDelete.Font = new System.Drawing.Font("微软雅黑", 9F);
|
||||
this.btnMiddleDelete.Location = new System.Drawing.Point(6, 62);
|
||||
this.btnMiddleDelete.Margin = new System.Windows.Forms.Padding(4, 6, 4, 4);
|
||||
this.btnMiddleDelete.Name = "btnMiddleDelete";
|
||||
this.btnMiddleDelete.Size = new System.Drawing.Size(50, 40);
|
||||
this.btnMiddleDelete.TabIndex = 1;
|
||||
this.btnMiddleDelete.Text = "删除";
|
||||
this.btnMiddleDelete.UseVisualStyleBackColor = false;
|
||||
this.btnMiddleDelete.Click += new System.EventHandler(this.btnDelete_Click);
|
||||
//
|
||||
// lstTasks
|
||||
//
|
||||
this.lstTasks.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.colId,
|
||||
this.colTaskType,
|
||||
this.colCurrent,
|
||||
this.colTarget,
|
||||
this.colTraffic,
|
||||
this.colPriority,
|
||||
this.colViaPoint,
|
||||
this.colStartType});
|
||||
this.lstTasks.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lstTasks.FullRowSelect = true;
|
||||
this.lstTasks.HideSelection = false;
|
||||
this.lstTasks.Location = new System.Drawing.Point(0, 0);
|
||||
this.lstTasks.Name = "lstTasks";
|
||||
this.lstTasks.OwnerDraw = true;
|
||||
this.lstTasks.Size = new System.Drawing.Size(680, 600);
|
||||
this.lstTasks.TabIndex = 0;
|
||||
this.lstTasks.UseCompatibleStateImageBehavior = false;
|
||||
this.lstTasks.View = System.Windows.Forms.View.Details;
|
||||
this.lstTasks.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.lstTasks_DrawColumnHeader);
|
||||
this.lstTasks.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.lstTasks_DrawItem);
|
||||
this.lstTasks.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.lstTasks_DrawSubItem);
|
||||
this.lstTasks.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.lstTasks_MouseDoubleClick);
|
||||
//
|
||||
// colId
|
||||
//
|
||||
this.colId.Text = "ID";
|
||||
this.colId.Width = 40;
|
||||
//
|
||||
// colTaskType
|
||||
//
|
||||
this.colTaskType.Text = "任务类别";
|
||||
this.colTaskType.Width = 110;
|
||||
//
|
||||
// colCurrent
|
||||
//
|
||||
this.colCurrent.Text = "当前站点";
|
||||
this.colCurrent.Width = 90;
|
||||
//
|
||||
// colTarget
|
||||
//
|
||||
this.colTarget.Text = "目标站点";
|
||||
this.colTarget.Width = 90;
|
||||
//
|
||||
// colTraffic
|
||||
//
|
||||
this.colTraffic.Text = "流量控制";
|
||||
this.colTraffic.Width = 90;
|
||||
//
|
||||
// colPriority
|
||||
//
|
||||
this.colPriority.Text = "优先级";
|
||||
this.colPriority.Width = 80;
|
||||
//
|
||||
// colViaPoint
|
||||
//
|
||||
this.colViaPoint.Text = "途径点";
|
||||
this.colViaPoint.Width = 70;
|
||||
//
|
||||
// colStartType
|
||||
//
|
||||
this.colStartType.Text = "启动类型";
|
||||
this.colStartType.Width = 100;
|
||||
//
|
||||
// grpEdit
|
||||
//
|
||||
this.grpEdit.Controls.Add(this.tlpEdit);
|
||||
this.grpEdit.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.grpEdit.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.grpEdit.Location = new System.Drawing.Point(0, 0);
|
||||
this.grpEdit.Name = "grpEdit";
|
||||
this.grpEdit.Size = new System.Drawing.Size(514, 600);
|
||||
this.grpEdit.TabIndex = 1;
|
||||
this.grpEdit.TabStop = false;
|
||||
this.grpEdit.Text = "任务信息(选中列表项后可编辑)";
|
||||
//
|
||||
// tlpEdit
|
||||
//
|
||||
this.tlpEdit.ColumnCount = 2;
|
||||
this.tlpEdit.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 120F));
|
||||
this.tlpEdit.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tlpEdit.Controls.Add(this.lblEditingId, 0, 0);
|
||||
this.tlpEdit.Controls.Add(this.lblKind, 0, 1);
|
||||
this.tlpEdit.Controls.Add(this.cmbTaskKind, 1, 1);
|
||||
this.tlpEdit.Controls.Add(this.lblCurrent, 0, 2);
|
||||
this.tlpEdit.Controls.Add(this.numCurrent, 1, 2);
|
||||
this.tlpEdit.Controls.Add(this.lblTarget, 0, 3);
|
||||
this.tlpEdit.Controls.Add(this.numTarget, 1, 3);
|
||||
this.tlpEdit.Controls.Add(this.lblTraffic, 0, 4);
|
||||
this.tlpEdit.Controls.Add(this.numTraffic, 1, 4);
|
||||
this.tlpEdit.Controls.Add(this.lblPriority, 0, 5);
|
||||
this.tlpEdit.Controls.Add(this.numPriority, 1, 5);
|
||||
this.tlpEdit.Controls.Add(this.lblVia, 0, 6);
|
||||
this.tlpEdit.Controls.Add(this.chkViaPoint, 1, 6);
|
||||
this.tlpEdit.Controls.Add(this.lblStartType, 0, 7);
|
||||
this.tlpEdit.Controls.Add(this.cmbStartType, 1, 7);
|
||||
this.tlpEdit.Controls.Add(this.flpButtons, 1, 8);
|
||||
this.tlpEdit.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tlpEdit.Location = new System.Drawing.Point(3, 25);
|
||||
this.tlpEdit.Name = "tlpEdit";
|
||||
this.tlpEdit.Padding = new System.Windows.Forms.Padding(8);
|
||||
this.tlpEdit.RowCount = 9;
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
|
||||
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tlpEdit.Size = new System.Drawing.Size(508, 572);
|
||||
this.tlpEdit.TabIndex = 0;
|
||||
//
|
||||
// lblEditingId
|
||||
//
|
||||
this.lblEditingId.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||
this.lblEditingId.AutoSize = true;
|
||||
this.tlpEdit.SetColumnSpan(this.lblEditingId, 2);
|
||||
this.lblEditingId.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.lblEditingId.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215)))));
|
||||
this.lblEditingId.Location = new System.Drawing.Point(11, 14);
|
||||
this.lblEditingId.Name = "lblEditingId";
|
||||
this.lblEditingId.Size = new System.Drawing.Size(78, 24);
|
||||
this.lblEditingId.TabIndex = 0;
|
||||
this.lblEditingId.Text = "新增任务";
|
||||
//
|
||||
// lblKind
|
||||
//
|
||||
this.lblKind.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblKind.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.lblKind.Location = new System.Drawing.Point(11, 44);
|
||||
this.lblKind.Name = "lblKind";
|
||||
this.lblKind.Size = new System.Drawing.Size(114, 36);
|
||||
this.lblKind.TabIndex = 1;
|
||||
this.lblKind.Text = "任务类别:";
|
||||
this.lblKind.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
//
|
||||
// cmbTaskKind
|
||||
//
|
||||
this.cmbTaskKind.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.cmbTaskKind.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.cmbTaskKind.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.cmbTaskKind.Items.AddRange(new object[] {
|
||||
"Loop",
|
||||
"BranchPoint",
|
||||
"JoinPoint"});
|
||||
this.cmbTaskKind.Location = new System.Drawing.Point(131, 47);
|
||||
this.cmbTaskKind.Name = "cmbTaskKind";
|
||||
this.cmbTaskKind.Size = new System.Drawing.Size(366, 31);
|
||||
this.cmbTaskKind.TabIndex = 2;
|
||||
//
|
||||
// lblCurrent
|
||||
//
|
||||
this.lblCurrent.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblCurrent.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.lblCurrent.Location = new System.Drawing.Point(11, 80);
|
||||
this.lblCurrent.Name = "lblCurrent";
|
||||
this.lblCurrent.Size = new System.Drawing.Size(114, 36);
|
||||
this.lblCurrent.TabIndex = 3;
|
||||
this.lblCurrent.Text = "当前站点:";
|
||||
this.lblCurrent.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
//
|
||||
// numCurrent
|
||||
//
|
||||
this.numCurrent.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.numCurrent.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.numCurrent.Location = new System.Drawing.Point(131, 83);
|
||||
this.numCurrent.Maximum = new decimal(new int[] {
|
||||
1000000,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numCurrent.Name = "numCurrent";
|
||||
this.numCurrent.Size = new System.Drawing.Size(120, 29);
|
||||
this.numCurrent.TabIndex = 4;
|
||||
//
|
||||
// lblTarget
|
||||
//
|
||||
this.lblTarget.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblTarget.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.lblTarget.Location = new System.Drawing.Point(11, 116);
|
||||
this.lblTarget.Name = "lblTarget";
|
||||
this.lblTarget.Size = new System.Drawing.Size(114, 36);
|
||||
this.lblTarget.TabIndex = 5;
|
||||
this.lblTarget.Text = "目标站点:";
|
||||
this.lblTarget.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
//
|
||||
// numTarget
|
||||
//
|
||||
this.numTarget.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.numTarget.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.numTarget.Location = new System.Drawing.Point(131, 119);
|
||||
this.numTarget.Maximum = new decimal(new int[] {
|
||||
1000000,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numTarget.Name = "numTarget";
|
||||
this.numTarget.Size = new System.Drawing.Size(120, 29);
|
||||
this.numTarget.TabIndex = 6;
|
||||
//
|
||||
// lblTraffic
|
||||
//
|
||||
this.lblTraffic.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblTraffic.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.lblTraffic.Location = new System.Drawing.Point(11, 152);
|
||||
this.lblTraffic.Name = "lblTraffic";
|
||||
this.lblTraffic.Size = new System.Drawing.Size(114, 36);
|
||||
this.lblTraffic.TabIndex = 7;
|
||||
this.lblTraffic.Text = "流量控制:";
|
||||
this.lblTraffic.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
//
|
||||
// numTraffic
|
||||
//
|
||||
this.numTraffic.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.numTraffic.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.numTraffic.Location = new System.Drawing.Point(131, 155);
|
||||
this.numTraffic.Maximum = new decimal(new int[] {
|
||||
1000,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numTraffic.Name = "numTraffic";
|
||||
this.numTraffic.Size = new System.Drawing.Size(120, 29);
|
||||
this.numTraffic.TabIndex = 8;
|
||||
//
|
||||
// lblPriority
|
||||
//
|
||||
this.lblPriority.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblPriority.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.lblPriority.Location = new System.Drawing.Point(11, 188);
|
||||
this.lblPriority.Name = "lblPriority";
|
||||
this.lblPriority.Size = new System.Drawing.Size(114, 36);
|
||||
this.lblPriority.TabIndex = 9;
|
||||
this.lblPriority.Text = "优先级:";
|
||||
this.lblPriority.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
//
|
||||
// numPriority
|
||||
//
|
||||
this.numPriority.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.numPriority.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.numPriority.Location = new System.Drawing.Point(131, 191);
|
||||
this.numPriority.Name = "numPriority";
|
||||
this.numPriority.Size = new System.Drawing.Size(120, 29);
|
||||
this.numPriority.TabIndex = 10;
|
||||
this.numPriority.Value = new decimal(new int[] {
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
//
|
||||
// lblVia
|
||||
//
|
||||
this.lblVia.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblVia.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.lblVia.Location = new System.Drawing.Point(11, 224);
|
||||
this.lblVia.Name = "lblVia";
|
||||
this.lblVia.Size = new System.Drawing.Size(114, 36);
|
||||
this.lblVia.TabIndex = 11;
|
||||
this.lblVia.Text = "途径点:";
|
||||
this.lblVia.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
//
|
||||
// chkViaPoint
|
||||
//
|
||||
this.chkViaPoint.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.chkViaPoint.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.chkViaPoint.Location = new System.Drawing.Point(131, 227);
|
||||
this.chkViaPoint.Name = "chkViaPoint";
|
||||
this.chkViaPoint.Size = new System.Drawing.Size(104, 30);
|
||||
this.chkViaPoint.TabIndex = 12;
|
||||
this.chkViaPoint.Text = "是";
|
||||
//
|
||||
// lblStartType
|
||||
//
|
||||
this.lblStartType.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lblStartType.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.lblStartType.Location = new System.Drawing.Point(11, 260);
|
||||
this.lblStartType.Name = "lblStartType";
|
||||
this.lblStartType.Size = new System.Drawing.Size(114, 36);
|
||||
this.lblStartType.TabIndex = 13;
|
||||
this.lblStartType.Text = "启动类型:";
|
||||
this.lblStartType.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
//
|
||||
// cmbStartType
|
||||
//
|
||||
this.cmbStartType.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.cmbStartType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.cmbStartType.Font = new System.Drawing.Font("微软雅黑", 10F);
|
||||
this.cmbStartType.Items.AddRange(new object[] {
|
||||
"Api",
|
||||
"Plc",
|
||||
"ButtonBox",
|
||||
"AutoLoop",
|
||||
"Charge"});
|
||||
this.cmbStartType.Location = new System.Drawing.Point(131, 263);
|
||||
this.cmbStartType.Name = "cmbStartType";
|
||||
this.cmbStartType.Size = new System.Drawing.Size(366, 31);
|
||||
this.cmbStartType.TabIndex = 14;
|
||||
//
|
||||
// flpButtons
|
||||
//
|
||||
this.flpButtons.AutoSize = true;
|
||||
this.flpButtons.Controls.Add(this.btnSave);
|
||||
this.flpButtons.Controls.Add(this.btnCancel);
|
||||
this.flpButtons.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.flpButtons.Location = new System.Drawing.Point(131, 299);
|
||||
this.flpButtons.Name = "flpButtons";
|
||||
this.flpButtons.Size = new System.Drawing.Size(292, 262);
|
||||
this.flpButtons.TabIndex = 15;
|
||||
//
|
||||
// btnSave
|
||||
//
|
||||
this.btnSave.BackColor = System.Drawing.Color.LightBlue;
|
||||
this.btnSave.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnSave.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold);
|
||||
this.btnSave.Location = new System.Drawing.Point(3, 3);
|
||||
this.btnSave.Name = "btnSave";
|
||||
this.btnSave.Size = new System.Drawing.Size(140, 40);
|
||||
this.btnSave.TabIndex = 0;
|
||||
this.btnSave.Text = "保存";
|
||||
this.btnSave.UseVisualStyleBackColor = false;
|
||||
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 11F);
|
||||
this.btnCancel.Location = new System.Drawing.Point(149, 3);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Size = new System.Drawing.Size(140, 40);
|
||||
this.btnCancel.TabIndex = 1;
|
||||
this.btnCancel.Text = "取消";
|
||||
this.btnCancel.UseVisualStyleBackColor = false;
|
||||
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||
//
|
||||
// btnEdit
|
||||
//
|
||||
this.btnEdit.Location = new System.Drawing.Point(0, 0);
|
||||
this.btnEdit.Name = "btnEdit";
|
||||
this.btnEdit.Size = new System.Drawing.Size(75, 23);
|
||||
this.btnEdit.TabIndex = 0;
|
||||
this.btnEdit.Visible = false;
|
||||
//
|
||||
// btnDelete
|
||||
//
|
||||
this.btnDelete.Location = new System.Drawing.Point(0, 0);
|
||||
this.btnDelete.Name = "btnDelete";
|
||||
this.btnDelete.Size = new System.Drawing.Size(75, 23);
|
||||
this.btnDelete.TabIndex = 0;
|
||||
this.btnDelete.Visible = false;
|
||||
//
|
||||
// LoopViewer
|
||||
//
|
||||
this.ClientSize = new System.Drawing.Size(1200, 600);
|
||||
this.Controls.Add(this.splitContainer);
|
||||
this.Font = new System.Drawing.Font("微软雅黑", 9F);
|
||||
this.MinimumSize = new System.Drawing.Size(1000, 420);
|
||||
this.Name = "LoopViewer";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "任务列表管理器";
|
||||
this.splitContainer.Panel1.ResumeLayout(false);
|
||||
this.splitContainer.Panel2.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
|
||||
this.splitContainer.ResumeLayout(false);
|
||||
this.pnlMiddle.ResumeLayout(false);
|
||||
this.flpMiddle.ResumeLayout(false);
|
||||
this.grpEdit.ResumeLayout(false);
|
||||
this.tlpEdit.ResumeLayout(false);
|
||||
this.tlpEdit.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numCurrent)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numTarget)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numTraffic)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.numPriority)).EndInit();
|
||||
this.flpButtons.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
using Newtonsoft.Json;
|
||||
using StandardScene.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
||||
namespace LoopViewerApp
|
||||
{
|
||||
public partial class LoopViewer : Form
|
||||
{
|
||||
private readonly string jsonPath =
|
||||
Path.Combine(Application.StartupPath, "tasklist.json");
|
||||
|
||||
private List<LoopTask> tasks = new List<LoopTask>();
|
||||
|
||||
// -1 表示新增模式;>=0 表示正在编辑对应索引
|
||||
private int editingIndex = -1;
|
||||
|
||||
public LoopViewer()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
|
||||
return;
|
||||
|
||||
// 应用 ChargeStationManagementForm 风格的运行时样式调整
|
||||
ApplyChargeStyle();
|
||||
|
||||
EnsureComboItems();
|
||||
|
||||
// 启用多选并绑定右键菜单与 Delete 键删除功能
|
||||
try
|
||||
{
|
||||
if (lstTasks != null)
|
||||
{
|
||||
lstTasks.MultiSelect = true;
|
||||
|
||||
// 右键菜单:删除
|
||||
var ctx = new ContextMenuStrip();
|
||||
ctx.Items.Add("删除", null, (s, e) => OnDeleteSelectedTasks());
|
||||
lstTasks.ContextMenuStrip = ctx;
|
||||
|
||||
// 键盘删除键绑定
|
||||
lstTasks.KeyDown += lstTasks_KeyDown;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"LoopViewer context menu init error: {ex}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InitOrLoadJson();
|
||||
RenderListView();
|
||||
UpdateSaveButtonText();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"LoopViewer initialization error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void lstTasks_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.KeyCode == Keys.Delete)
|
||||
{
|
||||
OnDeleteSelectedTasks();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"lstTasks_KeyDown error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除 ListView 中选中的任务(支持多选)
|
||||
/// </summary>
|
||||
private void OnDeleteSelectedTasks()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lstTasks == null || lstTasks.SelectedIndices.Count == 0)
|
||||
return;
|
||||
|
||||
// 收集被选中的索引并按降序删除,避免索引移动问题
|
||||
var selectedIndices = lstTasks.SelectedIndices.Cast<int>().OrderByDescending(i => i).ToList();
|
||||
|
||||
// 构造确认提示
|
||||
string prompt;
|
||||
if (selectedIndices.Count == 1)
|
||||
{
|
||||
int idx = selectedIndices[0];
|
||||
if (idx >= 0 && idx < tasks.Count)
|
||||
prompt = $"确认删除任务 ID={tasks[idx].Id}?";
|
||||
else
|
||||
prompt = "确认删除选中任务?";
|
||||
}
|
||||
else
|
||||
{
|
||||
prompt = $"确认删除所选 {selectedIndices.Count} 个任务?";
|
||||
}
|
||||
|
||||
if (MessageBox.Show(prompt, "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
// 删除任务
|
||||
foreach (var idx in selectedIndices)
|
||||
{
|
||||
if (idx >= 0 && idx < tasks.Count)
|
||||
{
|
||||
tasks.RemoveAt(idx);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果被删除项包含当前正在编辑的项,退出编辑状态
|
||||
if (editingIndex >= 0)
|
||||
{
|
||||
if (editingIndex >= tasks.Count || selectedIndices.Any(i => i == editingIndex))
|
||||
{
|
||||
editingIndex = -1;
|
||||
UpdateSaveButtonText();
|
||||
ClearPanelInputs();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 重新计算编辑索引在删除后的新位置
|
||||
int removedBefore = selectedIndices.Count(i => i < editingIndex);
|
||||
editingIndex -= removedBefore;
|
||||
}
|
||||
}
|
||||
|
||||
// 持久化并刷新列表视图
|
||||
Save();
|
||||
RenderListView();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"OnDeleteSelectedTasks error: {ex}");
|
||||
MessageBox.Show("删除失败:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 LoopViewer 的运行时样式调整为与 ChargeStationManagementForm 接近的视觉风格:
|
||||
/// - 全局字体设为微软雅黑
|
||||
/// - 表头暖色替换为蓝色沉稳风格(和充电界面一致)
|
||||
/// - 按钮字号、背景色与充电界面保持一致(保存/删除/取消)
|
||||
/// - 列表视图设置为整行选择、无边框、交替背景等
|
||||
/// 注意:不修改 Designer 文件,仅在运行时统一控件表现,避免破坏设计器生成代码。
|
||||
/// </summary>
|
||||
private void ApplyChargeStyle()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 窗体级设置
|
||||
this.StartPosition = FormStartPosition.CenterScreen;
|
||||
this.MinimumSize = new System.Drawing.Size(1327, 738);
|
||||
this.Font = new Font("微软雅黑", 9F, FontStyle.Regular);
|
||||
|
||||
// 调整 ListView(如果存在)
|
||||
if (lstTasks != null)
|
||||
{
|
||||
lstTasks.View = View.Details;
|
||||
lstTasks.FullRowSelect = true;
|
||||
lstTasks.GridLines = false;
|
||||
lstTasks.HeaderStyle = ColumnHeaderStyle.Nonclickable;
|
||||
lstTasks.OwnerDraw = true; // 已有自定义绘制
|
||||
lstTasks.BackColor = Color.White;
|
||||
lstTasks.ForeColor = Color.FromArgb(33, 33, 33);
|
||||
// 多选由初始化时控制(这里不强制)
|
||||
}
|
||||
|
||||
// 下拉框统一字体
|
||||
if (cmbTaskKind != null) cmbTaskKind.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
if (cmbStartType != null) cmbStartType.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
|
||||
// 数值输入框统一字体
|
||||
if (numCurrent != null) numCurrent.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
if (numTarget != null) numTarget.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
if (numTraffic != null) numTraffic.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
if (numPriority != null) numPriority.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
|
||||
// 标签字体统一
|
||||
if (lblEditingId != null) lblEditingId.Font = new Font("微软雅黑", 10F, FontStyle.Bold);
|
||||
|
||||
// 按钮风格:与 ChargeStationManagementForm 保持一致的视觉优先级
|
||||
if (btnSave != null)
|
||||
{
|
||||
btnSave.BackColor = Color.LightBlue;
|
||||
btnSave.ForeColor = Color.Black;
|
||||
btnSave.Font = new Font("微软雅黑", 11F, FontStyle.Bold);
|
||||
btnSave.FlatStyle = FlatStyle.Flat;
|
||||
}
|
||||
if (btnDelete != null)
|
||||
{
|
||||
btnDelete.BackColor = Color.LightCoral;
|
||||
btnDelete.ForeColor = Color.Black;
|
||||
btnDelete.Font = new Font("微软雅黑", 11F, FontStyle.Bold);
|
||||
btnDelete.FlatStyle = FlatStyle.Flat;
|
||||
}
|
||||
if (btnCancel != null)
|
||||
{
|
||||
btnCancel.BackColor = SystemColors.Control;
|
||||
btnCancel.ForeColor = Color.Black;
|
||||
btnCancel.Font = new Font("微软雅黑", 11F, FontStyle.Regular);
|
||||
btnCancel.FlatStyle = FlatStyle.Flat;
|
||||
}
|
||||
|
||||
// 如果存在额外的操作按钮(例如在面板上),尝试统一风格(容错)
|
||||
foreach (Control ctrl in this.Controls)
|
||||
{
|
||||
if (ctrl is Panel pnl)
|
||||
{
|
||||
pnl.Padding = new Padding(12);
|
||||
}
|
||||
else if (ctrl is Button btn)
|
||||
{
|
||||
// 已设置主要按钮,其他按钮使用中性风格
|
||||
if (btn == btnSave || btn == btnDelete || btn == btnCancel) continue;
|
||||
btn.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ApplyChargeStyle error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureComboItems()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cmbTaskKind != null && cmbTaskKind.Items.Count == 0)
|
||||
{
|
||||
cmbTaskKind.Items.AddRange(new object[] { "Loop", "BranchPoint", "JoinPoint" });
|
||||
cmbTaskKind.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
if (cmbStartType != null && cmbStartType.Items.Count == 0)
|
||||
{
|
||||
cmbStartType.Items.AddRange(new object[] { "Api", "Plc", "ButtonBox", "AutoLoop" });
|
||||
cmbStartType.SelectedIndex = 3;
|
||||
}
|
||||
|
||||
// 确保下拉框字体一致(防止 Designer 未设置)
|
||||
if (cmbTaskKind != null) cmbTaskKind.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
if (cmbStartType != null) cmbStartType.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
#region 初始化/加载
|
||||
private void InitOrLoadJson()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(jsonPath))
|
||||
File.WriteAllText(jsonPath, "[]");
|
||||
|
||||
var text = File.ReadAllText(jsonPath);
|
||||
tasks = JsonConvert.DeserializeObject<List<LoopTask>>(text) ?? new List<LoopTask>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tasks = new List<LoopTask>();
|
||||
System.Diagnostics.Debug.WriteLine($"Load tasks failed: {ex}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ID 自增逻辑
|
||||
|
||||
/// <summary>
|
||||
/// 获取下一个可用的任务ID(当前最大ID + 1)
|
||||
/// </summary>
|
||||
/// <returns>新的任务ID</returns>
|
||||
private int GetNextTaskId()
|
||||
{
|
||||
if (tasks == null || tasks.Count == 0)
|
||||
return 1;
|
||||
|
||||
int maxId = tasks.Max(t => t.Id);
|
||||
return maxId + 1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OwnerDraw 绘制(已按要求:表头加粗黑字 + 醒目底色,选中行为另一种颜色)
|
||||
private void lstTasks_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 与 ChargeStationManagementForm 表头保持一致的深蓝背景与白色加粗字体
|
||||
using (var backBrush = new SolidBrush(Color.FromArgb(63, 81, 181))) // 深蓝(与 Charge 界面一致)
|
||||
using (var textBrush = new SolidBrush(Color.White)) // 白色文字
|
||||
using (var font = new Font("微软雅黑", 9, FontStyle.Bold))
|
||||
{
|
||||
e.Graphics.FillRectangle(backBrush, e.Bounds);
|
||||
var sf = new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near };
|
||||
var rect = e.Bounds;
|
||||
rect.Inflate(-8, 0);
|
||||
e.Graphics.DrawString(e.Header.Text, font, textBrush, rect, sf);
|
||||
|
||||
// 分隔线
|
||||
using (var pen = new Pen(Color.FromArgb(200, 200, 200)))
|
||||
{
|
||||
e.Graphics.DrawLine(pen, e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
e.DrawBackground();
|
||||
e.DrawText();
|
||||
}
|
||||
}
|
||||
|
||||
private void lstTasks_DrawItem(object sender, DrawListViewItemEventArgs e)
|
||||
{
|
||||
// 由 DrawSubItem 绘制全部内容以保证每列对齐
|
||||
}
|
||||
|
||||
private void lstTasks_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.Item;
|
||||
bool selected = item.Selected;
|
||||
Rectangle bounds = e.Bounds;
|
||||
|
||||
// 选中行颜色:与 ChargeStationManagementForm 保持一致的蓝色强调
|
||||
Color selectedBack = Color.FromArgb(0, 120, 215);
|
||||
Color selectedFore = Color.White;
|
||||
|
||||
// 非选中行交替背景
|
||||
Color evenBack = Color.White;
|
||||
Color oddBack = Color.FromArgb(250, 251, 253);
|
||||
Color normalFore = Color.FromArgb(33, 33, 33);
|
||||
|
||||
// 填充背景
|
||||
if (selected)
|
||||
{
|
||||
using (var selBrush = new SolidBrush(selectedBack))
|
||||
{
|
||||
e.Graphics.FillRectangle(selBrush, bounds);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var back = new SolidBrush(e.ItemIndex % 2 == 0 ? evenBack : oddBack))
|
||||
{
|
||||
e.Graphics.FillRectangle(back, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制文本(加一点内边距)
|
||||
string text = e.SubItem.Text ?? string.Empty;
|
||||
Color fore = selected ? selectedFore : normalFore;
|
||||
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
|
||||
Rectangle textRect = bounds;
|
||||
textRect.Inflate(-6, 0);
|
||||
|
||||
using (var font = new Font("微软雅黑", 9))
|
||||
{
|
||||
TextRenderer.DrawText(e.Graphics, text, font, textRect, fore, flags);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
e.DrawBackground();
|
||||
e.DrawText();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 渲染/保存
|
||||
private void RenderListView()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lstTasks == null) return;
|
||||
lstTasks.BeginUpdate();
|
||||
lstTasks.Items.Clear();
|
||||
foreach (var t in tasks)
|
||||
{
|
||||
var lvi = new ListViewItem(new[]
|
||||
{
|
||||
t.Id.ToString(), // ID 列
|
||||
t.Kind.ToString(),
|
||||
t.CurrentStationId.ToString(),
|
||||
t.TargetStationId.ToString(),
|
||||
t.TrafficControl.ToString(),
|
||||
t.Priority.ToString(),
|
||||
t.IsViaPoint ? "是" : "否",
|
||||
t.StartType.ToString()
|
||||
});
|
||||
lstTasks.Items.Add(lvi);
|
||||
}
|
||||
lstTasks.EndUpdate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RenderListView failed: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(jsonPath, JsonConvert.SerializeObject(tasks, Formatting.Indented));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("保存失败:" + ex.Message);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 按钮事件(在同一界面新增/编辑)
|
||||
private void UpdateSaveButtonText()
|
||||
{
|
||||
if (btnSave != null)
|
||||
{
|
||||
// 文案固定为"保存"
|
||||
btnSave.Text = "保存";
|
||||
}
|
||||
}
|
||||
|
||||
private void btnSave_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从面板读取值,直接在界面内编辑/新增
|
||||
Enum.TryParse<TaskKind>(cmbTaskKind?.SelectedItem?.ToString() ?? "Loop", out var kind);
|
||||
Enum.TryParse<TaskStartType>(cmbStartType?.SelectedItem?.ToString() ?? "AutoLoop", out var st);
|
||||
|
||||
if (editingIndex >= 0 && editingIndex < tasks.Count)
|
||||
{
|
||||
// 更新模式:保留原有ID
|
||||
var existingTask = tasks[editingIndex];
|
||||
existingTask.Kind = kind;
|
||||
existingTask.CurrentStationId = (int)(numCurrent?.Value ?? 0);
|
||||
existingTask.TargetStationId = (int)(numTarget?.Value ?? 0);
|
||||
existingTask.TrafficControl = (int)(numTraffic?.Value ?? 0);
|
||||
existingTask.Priority = (int)(numPriority?.Value ?? 1);
|
||||
existingTask.IsViaPoint = chkViaPoint?.Checked ?? false;
|
||||
existingTask.StartType = st;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 新增模式:自动分配新ID
|
||||
var t = new LoopTask
|
||||
{
|
||||
Id = GetNextTaskId(), // 自增ID
|
||||
Kind = kind,
|
||||
CurrentStationId = (int)(numCurrent?.Value ?? 0),
|
||||
TargetStationId = (int)(numTarget?.Value ?? 0),
|
||||
TrafficControl = (int)(numTraffic?.Value ?? 0),
|
||||
Priority = (int)(numPriority?.Value ?? 1),
|
||||
IsViaPoint = chkViaPoint?.Checked ?? false,
|
||||
StartType = st
|
||||
};
|
||||
tasks.Add(t);
|
||||
}
|
||||
|
||||
Save();
|
||||
RenderListView();
|
||||
// 恢复新增状态
|
||||
editingIndex = -1;
|
||||
UpdateSaveButtonText();
|
||||
ClearPanelInputs();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"btnSave_Click error: {ex}");
|
||||
MessageBox.Show("操作失败:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 取消编辑,清空面板并回到"添加"模式
|
||||
editingIndex = -1;
|
||||
UpdateSaveButtonText();
|
||||
ClearPanelInputs();
|
||||
}
|
||||
|
||||
private void btnEdit_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lstTasks == null || lstTasks.SelectedIndices.Count == 0) return;
|
||||
int idx = lstTasks.SelectedIndices[0];
|
||||
if (idx < 0 || idx >= tasks.Count) return;
|
||||
|
||||
editingIndex = idx;
|
||||
LoadTaskToPanel(tasks[idx]);
|
||||
UpdateSaveButtonText();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"btnEdit_Click error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void btnDelete_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 兼容旧的删除按钮:复用统一删除逻辑
|
||||
OnDeleteSelectedTasks();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 双击编辑(同面板)
|
||||
private void lstTasks_MouseDoubleClick(object sender, MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lstTasks == null) return;
|
||||
var item = lstTasks.GetItemAt(e.X, e.Y);
|
||||
if (item == null) return;
|
||||
int idx = item.Index;
|
||||
if (idx < 0 || idx >= tasks.Count) return;
|
||||
|
||||
editingIndex = idx;
|
||||
LoadTaskToPanel(tasks[idx]);
|
||||
UpdateSaveButtonText();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"lstTasks_MouseDoubleClick error: {ex}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 辅助:面板读写
|
||||
private void LoadTaskToPanel(LoopTask t)
|
||||
{
|
||||
if (t == null) return;
|
||||
try
|
||||
{
|
||||
// 显示当前编辑的任务ID(只读显示)
|
||||
if (lblEditingId != null) lblEditingId.Text = $"编辑任务 ID: {t.Id}";
|
||||
|
||||
if (cmbTaskKind != null) cmbTaskKind.SelectedItem = t.Kind.ToString();
|
||||
if (numCurrent != null) numCurrent.Value = Math.Max(numCurrent.Minimum, Math.Min(numCurrent.Maximum, t.CurrentStationId));
|
||||
if (numTarget != null) numTarget.Value = Math.Max(numTarget.Minimum, Math.Min(numTarget.Maximum, t.TargetStationId));
|
||||
if (numTraffic != null) numTraffic.Value = Math.Max(numTraffic.Minimum, Math.Min(numTraffic.Maximum, t.TrafficControl));
|
||||
if (numPriority != null) numPriority.Value = Math.Max(numPriority.Minimum, Math.Min(numPriority.Maximum, t.Priority));
|
||||
if (chkViaPoint != null) chkViaPoint.Checked = t.IsViaPoint;
|
||||
if (cmbStartType != null) cmbStartType.SelectedItem = t.StartType.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"LoadTaskToPanel error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearPanelInputs()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 清除编辑ID显示
|
||||
if (lblEditingId != null) lblEditingId.Text = "新增任务";
|
||||
|
||||
if (cmbTaskKind != null) cmbTaskKind.SelectedIndex = 0;
|
||||
if (numCurrent != null) numCurrent.Value = 0;
|
||||
if (numTarget != null) numTarget.Value = 0;
|
||||
if (numTraffic != null) numTraffic.Value = 0;
|
||||
if (numPriority != null) numPriority.Value = 1;
|
||||
if (chkViaPoint != null) chkViaPoint.Checked = false;
|
||||
if (cmbStartType != null) cmbStartType.SelectedIndex = 3;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ClearPanelInputs error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -0,0 +1,206 @@
|
||||
using SimpleCore;
|
||||
using SimpleCore.Library;
|
||||
using StandardScene.Utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using StandardScene.Model;
|
||||
|
||||
namespace StandardScene.Chained
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一管理 TransportDelivery 相关的任务回调方法,并通过回调注册表提供可恢复性。
|
||||
/// </summary>
|
||||
public static class TransportDeliveryCallbacks
|
||||
{
|
||||
public const string KeyOnStarted = "Transport.OnMissionStarted";
|
||||
public const string KeyOnFetched = "Transport.OnFetched";
|
||||
public const string KeyOnPut = "Transport.OnPut";
|
||||
public const string KeyOnFinished = "Transport.OnMissionFinished";
|
||||
public const string KeyOnFailed = "Transport.OnMissionFailed";
|
||||
public const string KeyOnTerminated = "Transport.OnMissionTerminated";
|
||||
|
||||
private static bool _initialized;
|
||||
private static readonly HttpClient _httpClient = new HttpClient();
|
||||
private static string _callbackUrl = "http://127.0.0.1:20101/api/v1/MDCS/State";
|
||||
|
||||
static TransportDeliveryCallbacks()
|
||||
{
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
DeliveryCallbackRegistry.RegisterOnStart (KeyOnStarted, OnMissionStarted);
|
||||
DeliveryCallbackRegistry.RegisterDoneFetch (KeyOnFetched, OnFetched);
|
||||
DeliveryCallbackRegistry.RegisterDonePut (KeyOnPut, OnPut);
|
||||
DeliveryCallbackRegistry.RegisterDoneMission(KeyOnFinished, OnMissionFinished);
|
||||
DeliveryCallbackRegistry.RegisterFailed (KeyOnFailed, OnMissionFailed);
|
||||
DeliveryCallbackRegistry.RegisterOnTerminated(KeyOnTerminated, OnMissionTerminated);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显式调用以确保静态构造函数已执行(注册所有默认回调)。
|
||||
/// </summary>
|
||||
public static void EnsureInitialized()
|
||||
{
|
||||
// 访问本类,确保静态构造已执行
|
||||
if (!_initialized)
|
||||
{
|
||||
// 触发 static ctor(CLR 保证线程安全)
|
||||
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(TransportDeliveryCallbacks).TypeHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置任务状态回调的完整 URL(例如 http://ip:port/api/v1/MDCS/State)。
|
||||
/// 建议在 TransportMission 启动或构造时调用一次。
|
||||
/// </summary>
|
||||
public static void ConfigureCallbackUrl(string url)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
_callbackUrl = url;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DispatchMissionState(TransportDelivery d, MissionState.MissionStateEnum state)
|
||||
{
|
||||
var ms = new MissionState
|
||||
{
|
||||
MissionId = d.TaskId,
|
||||
CarCode = d.UsingCar?.id.ToString() ?? "0",
|
||||
TriggerTime = DateTime.Now,
|
||||
State = state
|
||||
};
|
||||
MissionStatePost(ms);
|
||||
}
|
||||
|
||||
private static async void MissionStatePost(MissionState ms)
|
||||
{
|
||||
var retryCount = 10;
|
||||
var retryDelay = TimeSpan.FromSeconds(1);
|
||||
var content = new StringContent(ms.ToJson());
|
||||
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
|
||||
for (int i = 0; i < retryCount; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resp = await _httpClient.PostAsync(_callbackUrl, content);
|
||||
Diagnosis.Post($"{ms.State}回调结果 => {resp.Content.ReadAsStringAsync().Result}");
|
||||
var body = resp.Content.ReadAsStringAsync().Result.JsonTo<BaseRespose>();
|
||||
var result = body.Code == 200 ? "成功" : "失败";
|
||||
Diagnosis.Post($"车辆编号:{ms.CarCode} 任务id:{ms.MissionId} 状态回调执行{result}");
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Diagnosis.Log($"{ms.State}状态回调失败 => ex:{ex}");
|
||||
if (i == retryCount - 1)
|
||||
{
|
||||
Diagnosis.Log($"StringContent:{content}", "apiError");
|
||||
}
|
||||
await Task.Delay(retryDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async void OnMissionStarted(ChainedDeliveryMission.Delivery delivery)
|
||||
{
|
||||
var d = (TransportDelivery)delivery;
|
||||
Diagnosis.Log($"task(#{d.TaskId}) started");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(d.TaskIdsString))
|
||||
{
|
||||
Commons.AddOrUpdateTag(d.UsingCar.tags, "taskIdsString", d.TaskIdsString);
|
||||
Diagnosis.Log(
|
||||
$"holdCarTagAddOrUpdate--TaskId:{d.TaskId},task:[{d.ToJson()}].taskIdsString:{d.TaskIdsString}",
|
||||
"holdCarTag",
|
||||
true);
|
||||
}
|
||||
|
||||
Commons.DeleteTag(d.UsingCar.tags, "holdCar");
|
||||
if (!string.IsNullOrEmpty(d.HoldCarSite))
|
||||
{
|
||||
Commons.AddOrUpdateTag(d.UsingCar.tags, "holdCar", d.HoldCarSite);
|
||||
Diagnosis.Log(
|
||||
$"holdCarTagAddOrUpdate--TaskId:{d.TaskId},task:[{d.ToJson()}].holdCar:{d.HoldCarSite}",
|
||||
"holdCarTag",
|
||||
true);
|
||||
}
|
||||
|
||||
DispatchMissionState(d, MissionState.MissionStateEnum.Started);
|
||||
}
|
||||
|
||||
public static async void OnFetched(ChainedDeliveryMission.Delivery delivery)
|
||||
{
|
||||
var d = (TransportDelivery)delivery;
|
||||
if (d.Src == d.Dst) return;
|
||||
|
||||
Diagnosis.Log($"task(#{d.TaskId}) fetched");
|
||||
DispatchMissionState(d, MissionState.MissionStateEnum.Fetched);
|
||||
}
|
||||
|
||||
public static async void OnPut(ChainedDeliveryMission.Delivery delivery)
|
||||
{
|
||||
var d = (TransportDelivery)delivery;
|
||||
if (d.Src == d.Dst) return;
|
||||
|
||||
Diagnosis.Log($"task(#{d.TaskId}) put");
|
||||
DispatchMissionState(d, MissionState.MissionStateEnum.Put);
|
||||
}
|
||||
|
||||
public static async void OnMissionFailed(ChainedDeliveryMission.Delivery delivery)
|
||||
{
|
||||
var d = (TransportDelivery)delivery;
|
||||
Diagnosis.Log($"task(#{d.TaskId}) failed");
|
||||
DispatchMissionState(d, MissionState.MissionStateEnum.Failed);
|
||||
}
|
||||
|
||||
public static async void OnMissionFinished(ChainedDeliveryMission.Delivery delivery)
|
||||
{
|
||||
var d = (TransportDelivery)delivery;
|
||||
Diagnosis.Log($"task(#{d.TaskId}) finished");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(d.MGTaskCode!) && d.MGTaskCode.Contains("-"))
|
||||
{
|
||||
var phase = d.MGTaskCode.Split('-')[1];
|
||||
var last = int.Parse(phase) - 1;
|
||||
if (last > 0 && string.IsNullOrWhiteSpace(d.TaskIdsString))
|
||||
{
|
||||
var holdCar = SimpleLib.GetAllCars()
|
||||
.FirstOrDefault(e => e.tags.TryGetValue("taskIdsString", out var v) && v.Contains(d.TaskId));
|
||||
if (holdCar != null)
|
||||
{
|
||||
Commons.DeleteTag(holdCar.tags, "taskIdsString");
|
||||
Diagnosis.Log(
|
||||
$"holdCarTagDelete-2:OnMissionFinished--TaskId:{d.TaskId},task:[{d.ToJson()}].taskIdsString:{d.TaskIdsString}",
|
||||
"holdCarTag",
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchMissionState(d, MissionState.MissionStateEnum.Finished);
|
||||
}
|
||||
|
||||
public static async Task<int> OnMissionTerminated(ChainedDeliveryMission.Delivery delivery, string ISRelease)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var hc = new HttpClient();
|
||||
var task = await hc.GetAsync($"http://{delivery.UsingCar.address}:8008/car/startOrPause?ISRelease={ISRelease}");
|
||||
var resp = task.Content.ReadAsStringAsync().Result.JsonTo<BaseRespose>();
|
||||
var result = resp.Code == 200 ? "成功" : "失败";
|
||||
return 1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Diagnosis.Log($"暂停恢复失败 => ex:{ExceptionFormatter.FormatEx(ex)}");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,599 @@
|
||||
using Newtonsoft.Json;
|
||||
using SimpleLite;
|
||||
using SimpleLite.RCS;
|
||||
using SimpleLite.RCS.CarTypes;
|
||||
using SimpleLite.RCS.Signal;
|
||||
using SimpleLite.CADTools;
|
||||
using SimpleLite.Props;
|
||||
using SimpleLite.UI;
|
||||
using SimpleCore;
|
||||
using SimpleCore.Compiler;
|
||||
using SimpleCore.Library;
|
||||
using SimpleCore.PropType;
|
||||
using StandardScene.Model;
|
||||
using StandardScene;
|
||||
using StandardScene.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace StandardScene.Chained
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务类型枚举
|
||||
/// </summary>
|
||||
public enum TaskType
|
||||
{
|
||||
移动 = 1, // 移动任务(当前未使用)
|
||||
搬运 = 2, // 搬运任务
|
||||
装车 = 3, // 装车任务
|
||||
卸车 = 4, // 卸车任务
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运输任务类
|
||||
/// 继承自AbstractDelivery,实现具体的运输任务功能
|
||||
/// </summary>
|
||||
/// public class TransportDelivery : AbstractChainedDeliveryMission.AbstractDelivery
|
||||
public class TransportDelivery : ChainedDeliveryMission.Delivery
|
||||
{
|
||||
/// <summary>任务类型(枚举值)</summary>
|
||||
public TaskType Type;
|
||||
/// <summary>任务类型(字符串形式)</summary>
|
||||
public string TaskType;
|
||||
/// <summary>使用的小车名称</summary>
|
||||
public string UsingCarName = "/";
|
||||
/// <summary>物料信息</summary>
|
||||
public string Material = "";
|
||||
/// <summary>占车站点</summary>
|
||||
public string HoldCarSite = "";
|
||||
/// <summary>阶段任务代码(用于标记阶段任务)</summary>
|
||||
public string MGTaskCode = "";
|
||||
/// <summary>任务ID字符串(用于标记阶段任务,可包含多个任务ID,用逗号分隔)</summary>
|
||||
public string TaskIdsString = "";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// 注册任务生命周期回调函数
|
||||
/// </summary>
|
||||
public TransportDelivery()
|
||||
{
|
||||
// 注册任务开始回调
|
||||
OnStart += d => { TransportOnStart((TransportDelivery)d); };
|
||||
// 注册取货完成回调
|
||||
DoneFetch += d => { TransportDoneFetch((TransportDelivery)d); };
|
||||
// 注册放货完成回调
|
||||
DonePut += d => { TransportDonePut((TransportDelivery)d); };
|
||||
// 注册任务完成回调
|
||||
DoneMission += d => { TransportDoneMission((TransportDelivery)d); };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务开始时的回调处理
|
||||
/// </summary>
|
||||
/// <param name="d">运输任务对象</param>
|
||||
private void TransportOnStart(TransportDelivery d)
|
||||
{
|
||||
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-onStart");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取货完成时的回调处理
|
||||
/// </summary>
|
||||
/// <param name="d">运输任务对象</param>
|
||||
private void TransportDoneFetch(TransportDelivery d)
|
||||
{
|
||||
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-doneFetch");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 放货完成时的回调处理
|
||||
/// </summary>
|
||||
/// <param name="d">运输任务对象</param>
|
||||
private void TransportDonePut(TransportDelivery d)
|
||||
{
|
||||
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-donePut");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 任务完成时的回调处理
|
||||
/// </summary>
|
||||
/// <param name="d">运输任务对象</param>
|
||||
private void TransportDoneMission(TransportDelivery d)
|
||||
{
|
||||
Diagnosis.Post($"{d.TaskId}___{d.Src}->{d.Dst}: step-doneMission");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搬运任务进程类
|
||||
/// 继承自AbstractChainedDeliveryMission,实现具体的搬运任务调度
|
||||
/// </summary>
|
||||
[MissionType(Name = "搬运任务进程", editor = typeof(TransportMission))]
|
||||
internal class TransportMission : ChainedDeliveryMission
|
||||
{
|
||||
/// <summary>是否启用就近任务策略("1"=启用,"0"=禁用)</summary>
|
||||
private static readonly string NearestTask = "1";
|
||||
|
||||
/// <summary>任务查看器窗口对象</summary>
|
||||
private DeliveryViewer dv;
|
||||
|
||||
public TransportMission()
|
||||
{
|
||||
// 确保回调静态类已完成注册
|
||||
TransportDeliveryCallbacks.EnsureInitialized();
|
||||
|
||||
// 根据配置的 MissionCallbackURL 设置回调地址(若有)
|
||||
var p = Param;
|
||||
if (p != null && !string.IsNullOrWhiteSpace(p.MissionCallbackURL))
|
||||
{
|
||||
if (Commons.IsValidHttpUrl(p.MissionCallbackURL))
|
||||
{
|
||||
_lastCallbackUrl = p.MissionCallbackURL;
|
||||
TransportDeliveryCallbacks.ConfigureCallbackUrl(_lastCallbackUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Diagnosis.Log($"无效的 MissionCallbackURL 配置: {p.MissionCallbackURL}", "TransportMission");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TransportMissionParam
|
||||
{
|
||||
/// <summary>
|
||||
/// 递送任务状态回调地址。
|
||||
/// </summary>
|
||||
public string MissionCallbackURL;
|
||||
}
|
||||
|
||||
public TransportMissionParam Param => StringDictConvert<TransportMissionParam>.Convert(fields);
|
||||
|
||||
private readonly bool _onDisplay = true;
|
||||
private string _lastCallbackUrl;
|
||||
|
||||
protected override Delivery CreateDeliveryFromSnapshot(DeliveryStateSnapshot snap)
|
||||
{
|
||||
var usingCar = SimpleLib.GetCar(snap.UsingCarId);
|
||||
if (snap.UsingCarId != -1 && usingCar == null)
|
||||
{
|
||||
Diagnosis.Post($"CreateDeliveryFromSnapshot[{snap.Id}] failed:usingCar[{snap.UsingCarId}] not found");
|
||||
return null;
|
||||
}
|
||||
if (usingCar != null && usingCar is not Car)
|
||||
{
|
||||
Diagnosis.Post($"CreateDeliveryFromSnapshot[{snap.Id}] skipped:usingCar[{snap.UsingCarId}] is not Car type");
|
||||
return null;
|
||||
}
|
||||
|
||||
var d = new TransportDelivery
|
||||
{
|
||||
Id = snap.Id,
|
||||
TaskId = snap.TaskId ?? string.Empty,
|
||||
Src = snap.Src,
|
||||
Dst = snap.Dst,
|
||||
SkipFetch = snap.SkipFetch,
|
||||
SkipPut = snap.SkipPut,
|
||||
Putting = snap.Putting,
|
||||
CreateTime = snap.CreateTime,
|
||||
StartTime = snap.StartTime,
|
||||
FinishTime = snap.FinishTime,
|
||||
CarType = snap.CarType,
|
||||
UsingCar = (Car)usingCar
|
||||
};
|
||||
|
||||
if (usingCar != null && string.Equals(snap.Status, "putting", StringComparison.OrdinalIgnoreCase) && !CheckCarLoaded(usingCar))
|
||||
{
|
||||
d.Error = true;
|
||||
}
|
||||
|
||||
// 恢复回调配置与事件绑定
|
||||
d.ReportOnStarted = snap.ReportOnStarted;
|
||||
d.ReportOnFetched = snap.ReportOnFetched;
|
||||
d.ReportOnPut = snap.ReportOnPut;
|
||||
d.ReportOnFinished = snap.ReportOnFinished;
|
||||
d.ReportOnFailed = snap.ReportOnFailed;
|
||||
d.ReportOnTerminated = snap.ReportOnTerminated;
|
||||
|
||||
d.OnStartCallbackKeys = snap.OnStartCallbackKeys ?? new List<string>();
|
||||
d.DoneFetchCallbackKeys = snap.DoneFetchCallbackKeys ?? new List<string>();
|
||||
d.DonePutCallbackKeys = snap.DonePutCallbackKeys ?? new List<string>();
|
||||
d.DoneMissionCallbackKeys = snap.DoneMissionCallbackKeys ?? new List<string>();
|
||||
d.FailedCallbackKeys = snap.FailedCallbackKeys ?? new List<string>();
|
||||
d.OnTerminatedCallbackKeys = snap.OnTerminatedCallbackKeys ?? new List<string>();
|
||||
|
||||
// 统一通过 Attacher 挂载所有回调
|
||||
DeliveryCallbackAttacher.AttachAll(d);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
protected override bool CheckCarLoaded(AbstractCar car)
|
||||
{
|
||||
if(car is DummyCar)
|
||||
return true;
|
||||
var carLoaded = Commons.GetCarStatus((Car)car, "Loaded");
|
||||
if (!string.IsNullOrEmpty(carLoaded) && bool.TryParse(carLoaded, out var result))
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行方法(启动任务调度进程)
|
||||
/// 启动基类的任务调度循环,并启动状态显示线程
|
||||
/// </summary>
|
||||
public override void Execute()
|
||||
{
|
||||
base.Execute(); // 调用基类Execute方法,启动任务调度循环
|
||||
//HideSimpleConsole(); // 隐藏控制台窗口
|
||||
|
||||
// 启动状态显示线程,实时显示小车状态和任务执行情况
|
||||
new Thread(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(100); // 每100ms更新一次显示
|
||||
var painter = SimpleMonitor.getPainter("TransportMissionPainter");
|
||||
painter.clear();
|
||||
if (!_onDisplay) continue; // 如果未启用显示,跳过
|
||||
|
||||
// 检查并更新任务状态回调 URL(如果配置发生变化)
|
||||
var p = Param;
|
||||
var currentUrl = p?.MissionCallbackURL;
|
||||
if (!string.IsNullOrWhiteSpace(currentUrl) && currentUrl != _lastCallbackUrl)
|
||||
{
|
||||
// 使用 Commons 中的统一 URL 校验逻辑
|
||||
if (Commons.IsValidHttpUrl(currentUrl))
|
||||
{
|
||||
TransportDeliveryCallbacks.ConfigureCallbackUrl(currentUrl);
|
||||
_lastCallbackUrl = currentUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
Diagnosis.Log($"无效的 MissionCallbackURL 配置: {currentUrl}", "TransportMission");
|
||||
}
|
||||
}
|
||||
|
||||
var onMissionCnt = 0; // 正在执行任务的小车数量
|
||||
var carStr = $"{string.Join("\n", SimpleLib.GetAllCars().Select(cc =>
|
||||
{
|
||||
var status = "";
|
||||
if (cc.tags.TryGetValue("occupied", out var occupiedStr))
|
||||
{
|
||||
onMissionCnt++;
|
||||
status = occupiedStr;
|
||||
if (cc.tags.TryGetValue("idle", out var _))
|
||||
{
|
||||
Diagnosis.Post($"strange idle tag, {cc.name}({cc.id})");
|
||||
cc.tags.Remove("idle");
|
||||
|
||||
}
|
||||
}
|
||||
else if (cc.tags.TryGetValue("idle", out var idleStr)) status = DateTime.TryParse(idleStr, out var idleTime) ? $"idle:{idleTime}" : $"idle:{idleStr}";
|
||||
return $"{cc.name}({cc.id})\t{status}";
|
||||
}))}";
|
||||
//var waitingMissions = GetDeliveries()
|
||||
// .Where(dd => dd.GetStatus() == DeliveryStatus.Waiting).ToList();
|
||||
painter.drawTextFixed(
|
||||
$"小车:\t开动率:{onMissionCnt}/{SimpleLib.GetAllCars().Length}\n{carStr}\n\n",
|
||||
new SolidBrush(Color.Black),
|
||||
VirtualPainter.DrawPosition.LeftTop, Color.AliceBlue);
|
||||
painter = null;
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变更任务优先级(重写基类方法)
|
||||
/// 根据配置决定是否执行就近任务策略
|
||||
/// </summary>
|
||||
public override void ChangePriority()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (NearestTask == "1") // NearestTask 等于1 执行就近原则
|
||||
{
|
||||
// 就近任务执行(当前被注释,未启用)
|
||||
// NearestTaskExecute();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果未启用就近原则,移除所有小车的就近选车标记
|
||||
foreach (var car in SimpleLib.GetAllCars().OfType<Car>())
|
||||
{
|
||||
Commons.DeleteTag(car.tags, "changePriority");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("同一工序就近执行就近任务执行:" + ex.ToString());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一键初始化所有小车
|
||||
/// 将状态为"正常"或"上线"且未初始化的小车进行重置
|
||||
/// </summary>
|
||||
[MethodMember(Name = "一键初始化")]
|
||||
public void ResetAll()
|
||||
{
|
||||
foreach (var car in SimpleLib.GetAllCars().OfType<Car>().Where(c => c.lstatus.Contains("正常") || c.lstatus.Contains("上线")))
|
||||
{
|
||||
// 如果小车未初始化(GetLastSite返回-1),执行重置
|
||||
if (car.GetLastSite() == -1)
|
||||
((Car)car).Reset();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一键上线所有小车
|
||||
/// 将状态为"正常"或"上线"且已初始化的小车标记为在线状态
|
||||
/// </summary>
|
||||
[MethodMember(Name = "一键上线")]
|
||||
public void OnlineAll()
|
||||
{
|
||||
foreach (var car in SimpleLib.GetAllCars().OfType<Car>().Where(c => c.lstatus.Contains("正常") || c.lstatus.Contains("上线")))
|
||||
{
|
||||
// 如果小车已初始化,标记为在线
|
||||
if (car.GetLastSite() != -1)
|
||||
{
|
||||
Commons.AddOrUpdateTag(car.tags, "Online", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查看任务列表界面
|
||||
/// 打开任务查看器窗口,显示所有任务的状态
|
||||
/// </summary>
|
||||
[MethodMember(Name = "查看任务", Description = "显示界面")]
|
||||
public void Print()
|
||||
{
|
||||
dv = new DeliveryViewer();
|
||||
dv.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动创建单取货任务
|
||||
/// 通过UI交互选择小车和取货点,创建仅取货任务(跳过取货步骤,小车从当前位置移动到取货点)
|
||||
/// </summary>
|
||||
[MethodMember(Name = "单取货", Description = "增加并排队搬运任务链")]
|
||||
public async void ManualEnqueueFetch()
|
||||
{
|
||||
try
|
||||
{
|
||||
G.pushStatus("选择小车");
|
||||
var selected = SimpleMonitor.selected.ToArray();
|
||||
if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); return; }
|
||||
var obj = selected[0];
|
||||
if (obj is Car car)
|
||||
{
|
||||
G.pushStatus("选择取货位");
|
||||
// 等待用户在UI中选择取货点
|
||||
var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true });
|
||||
|
||||
// 创建运输任务:从小车当前位置到选择的取货点
|
||||
var d = new TransportDelivery
|
||||
{
|
||||
CarType = "Car",
|
||||
Src = car.GetLastSite(), // 起始点为小车当前位置
|
||||
Dst = pt1.site, // 目标点为选择的取货点
|
||||
SkipFetch = true, // 跳过取货步骤(因为小车已经在起始点)
|
||||
UsingCar = car, // 指定使用的小车
|
||||
PutPlanInfo = new() { { "action", "fetch" } }, // 路径动作设置为fetch
|
||||
};
|
||||
|
||||
G.pushStatus($"排序了一个{car.name}搬运任务{d.Id}: {car.GetLastSite()} -> {d.Dst}");
|
||||
Enqueue(d);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("请选择需要控制的小车!");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
G.pushStatus($"结束任务链");
|
||||
}
|
||||
}
|
||||
|
||||
//写一个冒泡排序的算法,按照距离排序
|
||||
|
||||
[MethodMember(Name = "切换后台显示")]
|
||||
public void SwitchBackgroundDisplay()
|
||||
{
|
||||
ShowSimpleConsole();
|
||||
}
|
||||
/// <summary>
|
||||
/// 手动创建单放货任务
|
||||
/// 通过UI交互选择小车和放货点,创建仅放货任务(小车从当前位置移动到放货点并放货)
|
||||
/// </summary>
|
||||
[MethodMember(Name = "单放货", Description = "增加并排队搬运任务链")]
|
||||
public async void ManualEnqueuePut()
|
||||
{
|
||||
try
|
||||
{
|
||||
G.pushStatus("选择小车");
|
||||
var selected = SimpleMonitor.selected.ToArray();
|
||||
if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); return; }
|
||||
var obj = selected[0];
|
||||
if (obj is Car car)
|
||||
{
|
||||
G.pushStatus("选择取货位");
|
||||
// 等待用户在UI中选择放货点(注释写的是"取货位"但实际是放货点)
|
||||
var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true });
|
||||
var srcSite = SimpleLib.GetSite(pt1.site);
|
||||
|
||||
// 创建运输任务:从小车当前位置到选择的放货点
|
||||
var d = new TransportDelivery
|
||||
{
|
||||
CarType = "Car",
|
||||
Src = car.GetLastSite(), // 起始点为小车当前位置
|
||||
Dst = pt1.site, // 目标点为选择的放货点
|
||||
UsingCar = car, // 指定使用的小车
|
||||
SkipFetch = true, // 跳过取货步骤
|
||||
PutPlanInfo = new() { { "action", "put" } }, // 路径动作设置为put
|
||||
|
||||
};
|
||||
G.pushStatus($"排序了一个{car.name}搬运任务{d.Id}: {srcSite.id} -> {d.Dst}");
|
||||
Enqueue(d);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("请选择需要控制的小车!");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
G.pushStatus($"结束任务链");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动创建完整的取放货任务
|
||||
/// 通过UI交互选择取货点和放货点,创建完整的搬运任务(取货->放货)
|
||||
/// </summary>
|
||||
[MethodMember(Name = "取放货", Description = "增加并排队搬运任务链")]
|
||||
public async void ManualEnqueue()
|
||||
{
|
||||
try
|
||||
{
|
||||
G.pushStatus("选择取货位");
|
||||
// 等待用户在UI中选择取货点
|
||||
var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true });
|
||||
var srcSite = SimpleLib.GetSite(pt1.site);
|
||||
|
||||
G.pushStatus("选择放货位");
|
||||
// 等待用户在UI中选择放货点
|
||||
var pt2 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true });
|
||||
var dstSite = SimpleLib.GetSite(pt2.site);
|
||||
|
||||
// 创建完整的运输任务:从取货点到放货点
|
||||
var d = new TransportDelivery
|
||||
{
|
||||
CarType = "Car",
|
||||
Src = srcSite.id, // 取货点
|
||||
Dst = dstSite.id, // 放货点
|
||||
TaskId = $"ManualTask-{DateTime.Now}" // 生成任务ID
|
||||
};
|
||||
|
||||
G.pushStatus($"排序了一个叉车搬运任务{d.Id}: {srcSite.id} -> {dstSite.id}");
|
||||
Enqueue(d);
|
||||
}
|
||||
catch
|
||||
{
|
||||
G.pushStatus($"结束任务链");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动创建移动任务(去某地)
|
||||
/// 通过UI交互选择目标站点,创建移动任务(起点和终点相同,只移动不放货)
|
||||
/// </summary>
|
||||
[MethodMember(Name = "去某地", Description = "去某地")]
|
||||
public async void ManualEnqueueGo()
|
||||
{
|
||||
try
|
||||
{
|
||||
G.pushStatus("选择前往的站点");
|
||||
// 等待用户在UI中选择目标站点
|
||||
var pt1 = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true });
|
||||
var srcSite = SimpleLib.GetSite(pt1.site);
|
||||
|
||||
// 创建移动任务:起点和终点相同(只移动到目标点,不执行取放货操作)
|
||||
var d = new TransportDelivery
|
||||
{
|
||||
CarType = "Car",
|
||||
Src = pt1.site, // 起点(实际不会取货)
|
||||
Dst = pt1.site, // 终点(实际不会放货)
|
||||
TaskId = $"ManualTask-{DateTime.Now}",
|
||||
SrcFindLoop = false, // 起点不查找回路
|
||||
DstFindLoop = false // 终点不查找回路
|
||||
};
|
||||
|
||||
G.pushStatus($"排序了一个叉车搬运任务{d.Id}: {srcSite.id} -> {srcSite.id}");
|
||||
Enqueue(d);
|
||||
}
|
||||
catch
|
||||
{
|
||||
G.pushStatus($"结束任务链");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 供按钮盒进程反射调用:ButtonMission.ExecuteButtonActionInternal 反射调用本方法后,
|
||||
/// 由 HandleMethodResult 解析返回值,驱动 OnActionExecuted 成功/失败反馈。
|
||||
/// 返回 true 表示已入队;false 表示站点无效。若 Enqueue 抛错,由按钮进程捕获后同样视为失败。
|
||||
/// 按钮配置示例:TriggerMission=TransportMission,TriggerMethod=EnqueueTransportByButton,TriggerMethodParams=取货站点ID,放货站点ID
|
||||
/// </summary>
|
||||
/// <param name="srcSiteId">取货站点 ID</param>
|
||||
/// <param name="dstSiteId">放货站点 ID</param>
|
||||
public bool EnqueueTransportByButton(int srcSiteId, int dstSiteId)
|
||||
{
|
||||
if (SimpleLib.GetSite(srcSiteId) == null || SimpleLib.GetSite(dstSiteId) == null)
|
||||
{
|
||||
Diagnosis.Log($"按钮排队搬运失败:站点不存在 src={srcSiteId}, dst={dstSiteId}", "TransportMission", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
var d = new TransportDelivery
|
||||
{
|
||||
CarType = "Car",
|
||||
Src = srcSiteId,
|
||||
Dst = dstSiteId,
|
||||
TaskId = $"Button-{DateTime.Now:yyyyMMddHHmmssfff}"
|
||||
};
|
||||
Enqueue(d);
|
||||
Diagnosis.Post($"按钮排队搬运 {d.Id}: {srcSiteId} -> {dstSiteId}");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========== 控制台窗口显示控制 ==========
|
||||
|
||||
/// <summary>Windows API:获取控制台窗口句柄</summary>
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern IntPtr GetConsoleWindow();
|
||||
|
||||
/// <summary>Windows API:显示/隐藏窗口</summary>
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
/// <summary>控制台显示状态标志</summary>
|
||||
private static bool ShowConsole = false;
|
||||
|
||||
/// <summary>
|
||||
/// 切换控制台窗口显示/隐藏
|
||||
/// </summary>
|
||||
public static void ShowSimpleConsole()
|
||||
{
|
||||
ShowConsole = !ShowConsole; // 切换显示状态
|
||||
var handle = GetConsoleWindow();
|
||||
int n = ShowConsole ? 0 : 5; // 0=显示,5=隐藏
|
||||
Console.WriteLine(n);
|
||||
ShowWindow(handle, n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏控制台窗口
|
||||
/// </summary>
|
||||
public static void HideSimpleConsole()
|
||||
{
|
||||
var handle = GetConsoleWindow();
|
||||
ShowWindow(handle, 0); // 0表示隐藏窗口
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user