refactor: 插件窗体由 static 单例状态改为实例字段(对齐 TextViewer)
将 10 个 CycleGUI 窗体/管理器从进程级 static 单例状态改为实例字段 + 实例方法, 消除本地端/Web 端共享同一可变状态的隐患(对齐 TextViewer 正例)。 每个类保留一个 private static _instance 单实例持有者;静态入口 Open()/OpenViewer() 与实例 Show() 均转发到该实例,调用方零改动、单实例“置前”行为不变。 涉及:ChargeStationManagementForm、ChargeStrategyConfigForm、CommunicationMonitorForm、 AlarmConfigManagementForm、ButtonBoxManager、DoorManager、DoorMonitor、 LoopViewer、DeliveryViewer、TrafficInterlockViewer。 构建:dotnet build StandardScene.sln --no-incremental → 0 错误,30 警告(与基线一致,无新增)。
This commit is contained in:
@@ -28,26 +28,30 @@ namespace StandardScene.Chained
|
||||
private const int OverdueMinutesThreshold = 10000; // 约 7 天视为超时
|
||||
private const string TableId = "delivery-task-list";
|
||||
|
||||
private static readonly HttpClient SharedHttpClient = new HttpClient();
|
||||
private readonly HttpClient SharedHttpClient = new HttpClient();
|
||||
/// <summary>超时任务整行底色(深色主题下的暗红,醒目但不刺眼)。</summary>
|
||||
private static readonly Color OverdueRowColor = Color.FromArgb(255, 90, 36, 36);
|
||||
private readonly Color OverdueRowColor = Color.FromArgb(255, 90, 36, 36);
|
||||
|
||||
private static Panel _panel;
|
||||
private static bool _showFinished = true; // 显示已完成任务
|
||||
private static bool _showAbolished = true; // 显示废止任务(Error / Canceled / Terminated)
|
||||
private Panel _panel;
|
||||
private bool _showFinished = true; // 显示已完成任务
|
||||
private bool _showAbolished = true; // 显示废止任务(Error / Canceled / Terminated)
|
||||
|
||||
// 渲染快照:由后台线程按 FlushInterval 刷新,渲染线程只读引用;锁/文件 IO 绝不放在渲染线程,避免界面卡死。
|
||||
private static volatile List<Delivery> _snapshot = new List<Delivery>();
|
||||
private static volatile bool _refreshing;
|
||||
private static DateTime _lastFlush = DateTime.MinValue;
|
||||
private static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(1);
|
||||
private static volatile string _status = "";
|
||||
private volatile List<Delivery> _snapshot = new List<Delivery>();
|
||||
private volatile bool _refreshing;
|
||||
private DateTime _lastFlush = DateTime.MinValue;
|
||||
private readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(1);
|
||||
private volatile string _status = "";
|
||||
|
||||
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new DeliveryViewer().Show()</c> 调用方式。</summary>
|
||||
public void Show() => Open();
|
||||
|
||||
/// <summary>打开(或置前)任务管理面板。</summary>
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new DeliveryViewer()).OpenCore();
|
||||
|
||||
private static DeliveryViewer _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -127,14 +131,14 @@ namespace StandardScene.Chained
|
||||
});
|
||||
}
|
||||
|
||||
private static bool IsOverdue(Delivery dd) =>
|
||||
private bool IsOverdue(Delivery dd) =>
|
||||
(DateTime.Now - dd.CreateTime).TotalMinutes > OverdueMinutesThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// 渲染线程调用:到达刷新间隔且无在途刷新时,<b>在后台线程</b>重新拉取任务快照(超时任务置顶)。
|
||||
/// 业务侧的锁与文件 IO 一律放到后台,渲染线程只读 <see cref="_snapshot"/> 引用,避免界面卡死。
|
||||
/// </summary>
|
||||
private static void EnsureSnapshotFresh()
|
||||
private void EnsureSnapshotFresh()
|
||||
{
|
||||
if (_refreshing) return;
|
||||
if (DateTime.Now - _lastFlush < FlushInterval) return;
|
||||
@@ -165,14 +169,14 @@ namespace StandardScene.Chained
|
||||
});
|
||||
}
|
||||
|
||||
private static string SafeSiteName(int siteId)
|
||||
private string SafeSiteName(int siteId)
|
||||
{
|
||||
try { return SimpleLib.GetSite(siteId)?.name ?? ""; }
|
||||
catch { return ""; }
|
||||
}
|
||||
|
||||
/// <summary>将任务标记为已取消(Canceled)。</summary>
|
||||
private static void MarkDeliveryCanceled(Delivery d)
|
||||
private void MarkDeliveryCanceled(Delivery d)
|
||||
{
|
||||
if (d == null) return;
|
||||
lock (d.SyncStatus)
|
||||
@@ -187,7 +191,7 @@ namespace StandardScene.Chained
|
||||
/// 当 clearCarForChange=true 时,仅当状态为 Suspended 或 Waiting 且未处于放货阶段时,
|
||||
/// 才会清空 UsingCar 并返回 true;否则返回 false。
|
||||
/// </summary>
|
||||
private static bool MarkDeliveryWaiting(Delivery d, bool clearCarForChange)
|
||||
private bool MarkDeliveryWaiting(Delivery d, bool clearCarForChange)
|
||||
{
|
||||
if (d == null) return false;
|
||||
lock (d.SyncStatus)
|
||||
@@ -217,7 +221,7 @@ namespace StandardScene.Chained
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResendDelivery(string taskCode)
|
||||
private void ResendDelivery(string taskCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -247,7 +251,7 @@ namespace StandardScene.Chained
|
||||
}
|
||||
}
|
||||
|
||||
private static void CancelDelivery(string taskCode)
|
||||
private void CancelDelivery(string taskCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -286,7 +290,7 @@ namespace StandardScene.Chained
|
||||
}
|
||||
}
|
||||
|
||||
private static void ChangeCarResendDelivery(string taskCode)
|
||||
private void ChangeCarResendDelivery(string taskCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -27,24 +27,28 @@ namespace LoopViewerApp
|
||||
private const string TableId = "loop-task-list";
|
||||
|
||||
// 与 AbstractLoopMission 完全一致的读取路径,保证“写哪儿、它就读哪儿”。
|
||||
private static string JsonPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json");
|
||||
private string JsonPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json");
|
||||
|
||||
private static readonly object SaveLock = new object();
|
||||
private readonly object SaveLock = new object();
|
||||
// 直接取自枚举,自动与 TaskKind / TaskStartType 保持同步(含 Charge),无需手写列表。
|
||||
private static readonly string[] KindNames = Enum.GetNames(typeof(TaskKind));
|
||||
private static readonly string[] StartTypeNames = Enum.GetNames(typeof(TaskStartType));
|
||||
private readonly string[] KindNames = Enum.GetNames(typeof(TaskKind));
|
||||
private readonly string[] StartTypeNames = Enum.GetNames(typeof(TaskStartType));
|
||||
|
||||
private static Panel _panel;
|
||||
private static Panel _dialog; // 新增/编辑对话框,限单实例
|
||||
private static List<LoopTask> _tasks = new List<LoopTask>(); // 仅渲染线程读写
|
||||
private static readonly HashSet<int> _selected = new HashSet<int>(); // 仅渲染线程读写,存被勾选任务的 Id
|
||||
private static volatile string _status = "";
|
||||
private Panel _panel;
|
||||
private Panel _dialog; // 新增/编辑对话框,限单实例
|
||||
private List<LoopTask> _tasks = new List<LoopTask>(); // 仅渲染线程读写
|
||||
private readonly HashSet<int> _selected = new HashSet<int>(); // 仅渲染线程读写,存被勾选任务的 Id
|
||||
private volatile string _status = "";
|
||||
|
||||
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new LoopViewer().Show()</c> 调用方式。</summary>
|
||||
public void Show() => Open();
|
||||
|
||||
/// <summary>打开(或置前)任务管理面板。</summary>
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new LoopViewer()).OpenCore();
|
||||
|
||||
private static LoopViewer _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -126,7 +130,7 @@ namespace LoopViewerApp
|
||||
}
|
||||
|
||||
/// <summary>对选中项发起二次确认后删除(保留原多选删除的提示文案)。</summary>
|
||||
private static void ConfirmDeleteSelected()
|
||||
private void ConfirmDeleteSelected()
|
||||
{
|
||||
if (_selected.Count == 0)
|
||||
{
|
||||
@@ -145,7 +149,7 @@ namespace LoopViewerApp
|
||||
CycleUiHelper.ConfirmThen(prompt, DeleteSelected);
|
||||
}
|
||||
|
||||
private static void DeleteSelected()
|
||||
private void DeleteSelected()
|
||||
{
|
||||
var removed = _tasks.RemoveAll(t => _selected.Contains(t.Id));
|
||||
_selected.Clear();
|
||||
@@ -158,7 +162,7 @@ namespace LoopViewerApp
|
||||
/// 打开「新增 / 编辑」对话框(置顶非模态、限单实例)。<paramref name="existing"/> 为 null 表示新增,否则编辑该任务(保留其 Id)。
|
||||
/// 每次打开都是全新面板:<c>defaultText</c> 能正确初始化,规避立即模式下文本框缓冲难以重置的问题。
|
||||
/// </summary>
|
||||
private static void OpenEditDialog(LoopTask existing)
|
||||
private void OpenEditDialog(LoopTask existing)
|
||||
{
|
||||
if (_dialog != null)
|
||||
{
|
||||
@@ -270,9 +274,9 @@ namespace LoopViewerApp
|
||||
}
|
||||
|
||||
/// <summary>下一个可用任务 Id(当前最大 Id + 1,空表则为 1)。</summary>
|
||||
private static int GetNextTaskId() => _tasks.Count == 0 ? 1 : _tasks.Max(t => t.Id) + 1;
|
||||
private int GetNextTaskId() => _tasks.Count == 0 ? 1 : _tasks.Max(t => t.Id) + 1;
|
||||
|
||||
private static void LoadTasks()
|
||||
private void LoadTasks()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -292,7 +296,7 @@ namespace LoopViewerApp
|
||||
}
|
||||
|
||||
/// <summary>序列化在渲染线程完成(极快),文件写入放后台线程,避免阻塞渲染线程。</summary>
|
||||
private static void SaveTasks()
|
||||
private void SaveTasks()
|
||||
{
|
||||
string json;
|
||||
try
|
||||
@@ -323,9 +327,9 @@ namespace LoopViewerApp
|
||||
});
|
||||
}
|
||||
|
||||
private static int Clamp(int v, int min, int max) => v < min ? min : (v > max ? max : v);
|
||||
private int Clamp(int v, int min, int max) => v < min ? min : (v > max ? max : v);
|
||||
|
||||
private static bool TryParseClamp(string s, int min, int max, out int value)
|
||||
private bool TryParseClamp(string s, int min, int max, out int value)
|
||||
{
|
||||
if (int.TryParse((s ?? "").Trim(), out value))
|
||||
{
|
||||
|
||||
@@ -14,28 +14,32 @@ namespace StandardScene.Charge
|
||||
{
|
||||
private const string TableId = "alarm-config-list";
|
||||
|
||||
private static readonly string[] LevelNames = { "无", "低", "中", "高", "严重" };
|
||||
private static readonly string[] LevelFilterNames = { "全部", "无", "低", "中", "高", "严重" };
|
||||
private readonly string[] LevelNames = { "无", "低", "中", "高", "严重" };
|
||||
private readonly string[] LevelFilterNames = { "全部", "无", "低", "中", "高", "严重" };
|
||||
|
||||
private static readonly Color CriticalRowColor = Color.FromArgb(255, 235, 238);
|
||||
private static readonly Color HighRowColor = Color.FromArgb(255, 243, 224);
|
||||
private static readonly Color MediumRowColor = Color.FromArgb(255, 249, 196);
|
||||
private static readonly Color LowRowColor = Color.FromArgb(232, 245, 233);
|
||||
private static readonly Color DisabledRowColor = Color.FromArgb(238, 238, 238);
|
||||
private readonly Color CriticalRowColor = Color.FromArgb(255, 235, 238);
|
||||
private readonly Color HighRowColor = Color.FromArgb(255, 243, 224);
|
||||
private readonly Color MediumRowColor = Color.FromArgb(255, 249, 196);
|
||||
private readonly Color LowRowColor = Color.FromArgb(232, 245, 233);
|
||||
private readonly Color DisabledRowColor = Color.FromArgb(238, 238, 238);
|
||||
|
||||
private static readonly AlarmConfigDataService DataService = AlarmConfigDataService.Instance;
|
||||
private readonly AlarmConfigDataService DataService = AlarmConfigDataService.Instance;
|
||||
|
||||
private static Panel _panel;
|
||||
private static Panel _dialog;
|
||||
private static List<AlarmConfig> _allAlarms = new List<AlarmConfig>();
|
||||
private static int _levelFilterIdx;
|
||||
private static string _status = "";
|
||||
private Panel _panel;
|
||||
private Panel _dialog;
|
||||
private List<AlarmConfig> _allAlarms = new List<AlarmConfig>();
|
||||
private int _levelFilterIdx;
|
||||
private string _status = "";
|
||||
|
||||
/// <summary>打开(或置前)报警配置管理面板。兼容原 <c>new AlarmConfigManagementForm().Show()</c> 调用方式。</summary>
|
||||
public void Show() => Open();
|
||||
|
||||
/// <summary>打开(或置前)报警配置管理面板。</summary>
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new AlarmConfigManagementForm()).OpenCore();
|
||||
|
||||
private static AlarmConfigManagementForm _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -132,7 +136,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static void OpenEditDialog(AlarmConfig existing)
|
||||
private void OpenEditDialog(AlarmConfig existing)
|
||||
{
|
||||
if (_dialog != null)
|
||||
{
|
||||
@@ -276,7 +280,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static void LoadAlarms()
|
||||
private void LoadAlarms()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -289,7 +293,7 @@ namespace StandardScene.Charge
|
||||
}
|
||||
}
|
||||
|
||||
private static List<AlarmConfig> GetFilteredAlarms()
|
||||
private List<AlarmConfig> GetFilteredAlarms()
|
||||
{
|
||||
IEnumerable<AlarmConfig> query = _allAlarms;
|
||||
if (_levelFilterIdx > 0)
|
||||
@@ -297,7 +301,7 @@ namespace StandardScene.Charge
|
||||
return query.ToList();
|
||||
}
|
||||
|
||||
private static string GetStatisticsText()
|
||||
private string GetStatisticsText()
|
||||
{
|
||||
var total = _allAlarms.Count;
|
||||
var enabled = _allAlarms.Count(a => a.Enabled);
|
||||
@@ -307,7 +311,7 @@ namespace StandardScene.Charge
|
||||
return $"启用: {enabled} | 禁用: {disabled} | 严重: {critical} | 高级: {high}";
|
||||
}
|
||||
|
||||
private static string GetLevelText(AlarmLevel level)
|
||||
private string GetLevelText(AlarmLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
|
||||
@@ -18,29 +18,33 @@ namespace StandardScene.Charge
|
||||
public class ChargeStationManagementForm
|
||||
{
|
||||
private const string TableId = "charge-station-list";
|
||||
private static readonly string[] StatusFilterNames = { "全部状态", "空闲", "充电中", "故障", "离线" };
|
||||
private static readonly string[] TypeNames = { "FRLD高款充电桩", "FRLD矮款充电桩", "牧星充电桩" };
|
||||
private static readonly string[] MethodNames = { "地充", "尾充", "侧充" };
|
||||
private static readonly string[] CarTypeNames = { "FRLD充电", "牧星充电桩充电" };
|
||||
private readonly string[] StatusFilterNames = { "全部状态", "空闲", "充电中", "故障", "离线" };
|
||||
private readonly string[] TypeNames = { "FRLD高款充电桩", "FRLD矮款充电桩", "牧星充电桩" };
|
||||
private readonly string[] MethodNames = { "地充", "尾充", "侧充" };
|
||||
private readonly string[] CarTypeNames = { "FRLD充电", "牧星充电桩充电" };
|
||||
|
||||
private static readonly ChargeStationDataService DataService = ChargeStationDataService.Instance;
|
||||
private static readonly Ping Ping = new Ping();
|
||||
private readonly ChargeStationDataService DataService = ChargeStationDataService.Instance;
|
||||
private readonly Ping Ping = new Ping();
|
||||
|
||||
private static Panel _panel;
|
||||
private static Panel _editDialog;
|
||||
private static List<ChargeStation> _snapshot = new List<ChargeStation>();
|
||||
private static volatile bool _refreshing;
|
||||
private static DateTime _lastFlush = DateTime.MinValue;
|
||||
private static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(3);
|
||||
private Panel _panel;
|
||||
private Panel _editDialog;
|
||||
private List<ChargeStation> _snapshot = new List<ChargeStation>();
|
||||
private volatile bool _refreshing;
|
||||
private DateTime _lastFlush = DateTime.MinValue;
|
||||
private readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(3);
|
||||
|
||||
private static int _statusFilterIdx;
|
||||
private static string _searchText = "";
|
||||
private static string _statsText = "";
|
||||
private static string _status = "";
|
||||
private int _statusFilterIdx;
|
||||
private string _searchText = "";
|
||||
private string _statsText = "";
|
||||
private string _status = "";
|
||||
|
||||
public void Show() => Open();
|
||||
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new ChargeStationManagementForm()).OpenCore();
|
||||
|
||||
private static ChargeStationManagementForm _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -137,7 +141,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static void EnsureSnapshotFresh()
|
||||
private void EnsureSnapshotFresh()
|
||||
{
|
||||
if (_refreshing) return;
|
||||
if (DateTime.Now - _lastFlush < FlushInterval) return;
|
||||
@@ -164,7 +168,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static List<ChargeStation> FilterSnapshot(List<ChargeStation> src)
|
||||
private List<ChargeStation> FilterSnapshot(List<ChargeStation> src)
|
||||
{
|
||||
IEnumerable<ChargeStation> q = src;
|
||||
if (_statusFilterIdx > 0)
|
||||
@@ -183,7 +187,7 @@ namespace StandardScene.Charge
|
||||
return q.ToList();
|
||||
}
|
||||
|
||||
private static void UpdateStats(List<ChargeStation> all)
|
||||
private void UpdateStats(List<ChargeStation> all)
|
||||
{
|
||||
_statsText = $"总数: {all.Count} | 空闲: {all.Count(s => s.Status == ChargeStationStatus.Idle)} | " +
|
||||
$"充电中: {all.Count(s => s.Status == ChargeStationStatus.Charging)} | " +
|
||||
@@ -191,7 +195,7 @@ namespace StandardScene.Charge
|
||||
$"AGV电池已接入: {all.Count(s => s.Status == ChargeStationStatus.Battery)}";
|
||||
}
|
||||
|
||||
private static void OpenEditDialog(ChargeStation existing)
|
||||
private void OpenEditDialog(ChargeStation existing)
|
||||
{
|
||||
if (_editDialog != null)
|
||||
{
|
||||
@@ -296,7 +300,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TryBuildStation(bool isAdd, ChargeStation existing, string stationId, string name,
|
||||
private bool TryBuildStation(bool isAdd, ChargeStation existing, string stationId, string name,
|
||||
int typeIdx, int methodIdx, int carTypeIdx, string ip, string port, string voltage, string current,
|
||||
string siteIdText, string remarks, bool enabled, bool shield,
|
||||
out ChargeStation station, out string err)
|
||||
@@ -335,7 +339,7 @@ namespace StandardScene.Charge
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ConfirmDelete(ChargeStation station)
|
||||
private void ConfirmDelete(ChargeStation station)
|
||||
{
|
||||
CycleUiHelper.ConfirmThen($"确定删除充电桩 [{station.StationId}] {station.Name}?", () =>
|
||||
{
|
||||
@@ -359,7 +363,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static void ExportData(PanelBuilder pb)
|
||||
private void ExportData(PanelBuilder pb)
|
||||
{
|
||||
if (!pb.SaveFile("导出充电桩", "*.json;*.csv", out var path) || string.IsNullOrEmpty(path)) return;
|
||||
try
|
||||
@@ -385,7 +389,7 @@ namespace StandardScene.Charge
|
||||
catch (Exception ex) { CycleUiHelper.Alert("错误", $"导出失败: {ex.Message}"); }
|
||||
}
|
||||
|
||||
private static void ApplyRowColor(PanelBuilder.Row row, ChargeStation s)
|
||||
private void ApplyRowColor(PanelBuilder.Row row, ChargeStation s)
|
||||
{
|
||||
if (s.HasAlarm) row.SetColor(Color.FromArgb(255, 205, 210));
|
||||
else if (s.Status == ChargeStationStatus.Idle) row.SetColor(Color.FromArgb(232, 245, 233));
|
||||
@@ -394,7 +398,7 @@ namespace StandardScene.Charge
|
||||
else if (s.Status == ChargeStationStatus.Battery) row.SetColor(Color.FromArgb(238, 238, 238));
|
||||
}
|
||||
|
||||
private static ChargeStationStatus GetStatusFromFilterIndex(int idx) => idx switch
|
||||
private ChargeStationStatus GetStatusFromFilterIndex(int idx) => idx switch
|
||||
{
|
||||
1 => ChargeStationStatus.Idle,
|
||||
2 => ChargeStationStatus.Charging,
|
||||
@@ -403,7 +407,7 @@ namespace StandardScene.Charge
|
||||
_ => ChargeStationStatus.Idle
|
||||
};
|
||||
|
||||
private static string GetStatusText(ChargeStationStatus status) => status switch
|
||||
private string GetStatusText(ChargeStationStatus status) => status switch
|
||||
{
|
||||
ChargeStationStatus.Idle => "空闲",
|
||||
ChargeStationStatus.Charging => "充电中",
|
||||
@@ -412,7 +416,7 @@ namespace StandardScene.Charge
|
||||
_ => "未知"
|
||||
};
|
||||
|
||||
private static string GetTypeText(ChargeStationType type) => type switch
|
||||
private string GetTypeText(ChargeStationType type) => type switch
|
||||
{
|
||||
ChargeStationType.FRLDTall => "FRLD高款充电桩",
|
||||
ChargeStationType.FRLDShort => "FRLD矮款充电桩",
|
||||
@@ -420,7 +424,7 @@ namespace StandardScene.Charge
|
||||
_ => "未知"
|
||||
};
|
||||
|
||||
private static string GetMethodText(ChargeMethodType m) => m switch
|
||||
private string GetMethodText(ChargeMethodType m) => m switch
|
||||
{
|
||||
ChargeMethodType.Ground => "地充",
|
||||
ChargeMethodType.Rear => "尾充",
|
||||
@@ -428,7 +432,7 @@ namespace StandardScene.Charge
|
||||
_ => "未知"
|
||||
};
|
||||
|
||||
private static string FormatComm(CommunicationStatus s) => s switch
|
||||
private string FormatComm(CommunicationStatus s) => s switch
|
||||
{
|
||||
CommunicationStatus.Normal => "✓ 正常",
|
||||
CommunicationStatus.Delayed => "⚠ 延迟",
|
||||
@@ -438,7 +442,7 @@ namespace StandardScene.Charge
|
||||
_ => "? 未知"
|
||||
};
|
||||
|
||||
private static string FormatMech(MechanismStatus s) => s switch
|
||||
private string FormatMech(MechanismStatus s) => s switch
|
||||
{
|
||||
MechanismStatus.Extended => "◆ 伸出",
|
||||
MechanismStatus.Retracted => "◇ 缩回",
|
||||
@@ -446,7 +450,7 @@ namespace StandardScene.Charge
|
||||
_ => "? 未知"
|
||||
};
|
||||
|
||||
private static string FormatAlarm(ChargeStation s)
|
||||
private string FormatAlarm(ChargeStation s)
|
||||
{
|
||||
if (!s.HasAlarm) return "正常";
|
||||
return string.IsNullOrWhiteSpace(s.AlarmMessage) ? $"【{s.AlarmLevel}】" : s.AlarmMessage;
|
||||
|
||||
@@ -9,39 +9,43 @@ namespace StandardScene.Charge
|
||||
/// </summary>
|
||||
public class ChargeStrategyConfigForm
|
||||
{
|
||||
private static Panel _panel;
|
||||
private static readonly ChargeStrategyConfigService ConfigService = ChargeStrategyConfigService.Instance;
|
||||
private Panel _panel;
|
||||
private readonly ChargeStrategyConfigService ConfigService = ChargeStrategyConfigService.Instance;
|
||||
|
||||
private static ChargeStrategyConfig _config;
|
||||
private static string _status = "";
|
||||
private ChargeStrategyConfig _config;
|
||||
private string _status = "";
|
||||
|
||||
// SOC 参数
|
||||
private static float _mustChargeSoc;
|
||||
private static float _idleChargeSoc;
|
||||
private static float _taskAvailableSoc;
|
||||
private static float _fullChargeSoc;
|
||||
private static float _allowInterruptSoc;
|
||||
private float _mustChargeSoc;
|
||||
private float _idleChargeSoc;
|
||||
private float _taskAvailableSoc;
|
||||
private float _fullChargeSoc;
|
||||
private float _allowInterruptSoc;
|
||||
|
||||
// 时间参数
|
||||
private static float _idleChargeSeconds;
|
||||
private static float _idleSeconds;
|
||||
private static float _mustChargeSeconds;
|
||||
private static float _topUpMinutes;
|
||||
private float _idleChargeSeconds;
|
||||
private float _idleSeconds;
|
||||
private float _mustChargeSeconds;
|
||||
private float _topUpMinutes;
|
||||
|
||||
// 任务参数
|
||||
private static int _minAllowFreeCarToChargeTaskCnt;
|
||||
private int _minAllowFreeCarToChargeTaskCnt;
|
||||
|
||||
// 开关参数
|
||||
private static bool _allowInterruptTask;
|
||||
private static bool _useLowerSocForCharge;
|
||||
private static bool _enableErrorChargeDetection;
|
||||
private static bool _useChargeSiteFilter;
|
||||
private bool _allowInterruptTask;
|
||||
private bool _useLowerSocForCharge;
|
||||
private bool _enableErrorChargeDetection;
|
||||
private bool _useChargeSiteFilter;
|
||||
|
||||
/// <summary>打开(或置前)充电策略配置面板。兼容原 <c>new ChargeStrategyConfigForm().Show()</c> 调用方式。</summary>
|
||||
public void Show() => Open();
|
||||
|
||||
/// <summary>打开(或置前)充电策略配置面板。</summary>
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new ChargeStrategyConfigForm()).OpenCore();
|
||||
|
||||
private static ChargeStrategyConfigForm _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -120,7 +124,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TryLoadConfig()
|
||||
private bool TryLoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -137,7 +141,7 @@ namespace StandardScene.Charge
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyConfigToUi(ChargeStrategyConfig config)
|
||||
private void ApplyConfigToUi(ChargeStrategyConfig config)
|
||||
{
|
||||
_mustChargeSoc = (float)config.MustChargeSoc;
|
||||
_idleChargeSoc = (float)config.IdleChargeSoc;
|
||||
@@ -158,7 +162,7 @@ namespace StandardScene.Charge
|
||||
_useChargeSiteFilter = config.UseChargeSiteFilter;
|
||||
}
|
||||
|
||||
private static void ApplyUiToConfig()
|
||||
private void ApplyUiToConfig()
|
||||
{
|
||||
_config.MustChargeSoc = _mustChargeSoc;
|
||||
_config.IdleChargeSoc = _idleChargeSoc;
|
||||
@@ -180,7 +184,7 @@ namespace StandardScene.Charge
|
||||
}
|
||||
|
||||
/// <summary>验证 SOC 阈值之间的逻辑关系(保存前)。</summary>
|
||||
private static bool ValidateSocRanges(out string errorMessage)
|
||||
private bool ValidateSocRanges(out string errorMessage)
|
||||
{
|
||||
if (_mustChargeSoc >= _idleChargeSoc)
|
||||
{
|
||||
@@ -210,7 +214,7 @@ namespace StandardScene.Charge
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TrySave(bool closeAfterSave)
|
||||
private void TrySave(bool closeAfterSave)
|
||||
{
|
||||
ApplyUiToConfig();
|
||||
|
||||
@@ -245,7 +249,7 @@ namespace StandardScene.Charge
|
||||
}
|
||||
}
|
||||
|
||||
private static void RestoreDefaults()
|
||||
private void RestoreDefaults()
|
||||
{
|
||||
CycleUiHelper.ConfirmThen("确定要恢复默认配置吗?当前配置将被覆盖。", () =>
|
||||
{
|
||||
|
||||
@@ -24,33 +24,37 @@ namespace StandardScene.Charge
|
||||
private const int StatsRefreshMs = 500;
|
||||
private const string TableId = "comm-monitor-msgs";
|
||||
|
||||
private static readonly Color SendRowColor = Color.FromArgb(232, 245, 233);
|
||||
private static readonly Color ReceiveRowColor = Color.FromArgb(227, 242, 253);
|
||||
private static readonly Color SelectedRowColor = Color.FromArgb(255, 249, 196);
|
||||
private readonly Color SendRowColor = Color.FromArgb(232, 245, 233);
|
||||
private readonly Color ReceiveRowColor = Color.FromArgb(227, 242, 253);
|
||||
private readonly Color SelectedRowColor = Color.FromArgb(255, 249, 196);
|
||||
|
||||
private static readonly CommunicationMessageService MessageService = CommunicationMessageService.Instance;
|
||||
private readonly CommunicationMessageService MessageService = CommunicationMessageService.Instance;
|
||||
|
||||
private static Panel _panel;
|
||||
private static bool _subscribed;
|
||||
private static bool _paused;
|
||||
private static int _selectedIpIndex;
|
||||
private static string[] _ipOptions = { "全部" };
|
||||
private static int _selectedRowIndex = -1;
|
||||
private static string _parsedText = "";
|
||||
private static string _statsText = "";
|
||||
private Panel _panel;
|
||||
private bool _subscribed;
|
||||
private bool _paused;
|
||||
private int _selectedIpIndex;
|
||||
private string[] _ipOptions = { "全部" };
|
||||
private int _selectedRowIndex = -1;
|
||||
private string _parsedText = "";
|
||||
private string _statsText = "";
|
||||
|
||||
private static List<CommunicationMessage> _displayMessages = new List<CommunicationMessage>();
|
||||
private static readonly Queue<CommunicationMessage> PendingMessages = new Queue<CommunicationMessage>();
|
||||
private static readonly object PendingLock = new object();
|
||||
private List<CommunicationMessage> _displayMessages = new List<CommunicationMessage>();
|
||||
private readonly Queue<CommunicationMessage> PendingMessages = new Queue<CommunicationMessage>();
|
||||
private readonly object PendingLock = new object();
|
||||
|
||||
private static DateTime _lastStatsRefresh = DateTime.MinValue;
|
||||
private static bool _pendingStatsRefresh;
|
||||
private DateTime _lastStatsRefresh = DateTime.MinValue;
|
||||
private bool _pendingStatsRefresh;
|
||||
|
||||
/// <summary>打开(或置前)通讯监控面板。兼容原 <c>new CommunicationMonitorForm().Show()</c> 调用方式。</summary>
|
||||
public void Show() => Open();
|
||||
|
||||
/// <summary>打开(或置前)通讯监控面板。</summary>
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new CommunicationMonitorForm()).OpenCore();
|
||||
|
||||
private static CommunicationMonitorForm _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -169,7 +173,7 @@ namespace StandardScene.Charge
|
||||
});
|
||||
}
|
||||
|
||||
private static void Subscribe()
|
||||
private void Subscribe()
|
||||
{
|
||||
if (_subscribed)
|
||||
return;
|
||||
@@ -177,7 +181,7 @@ namespace StandardScene.Charge
|
||||
_subscribed = true;
|
||||
}
|
||||
|
||||
private static void Unsubscribe()
|
||||
private void Unsubscribe()
|
||||
{
|
||||
if (!_subscribed)
|
||||
return;
|
||||
@@ -187,7 +191,7 @@ namespace StandardScene.Charge
|
||||
PendingMessages.Clear();
|
||||
}
|
||||
|
||||
private static void OnMessageAdded(object sender, CommunicationMessage message)
|
||||
private void OnMessageAdded(object sender, CommunicationMessage message)
|
||||
{
|
||||
if (!_subscribed || message == null)
|
||||
return;
|
||||
@@ -199,7 +203,7 @@ namespace StandardScene.Charge
|
||||
}
|
||||
|
||||
/// <summary>定时批量刷新 UI,避免每条报文都抢占渲染线程。</summary>
|
||||
private static void FlushPendingBatch()
|
||||
private void FlushPendingBatch()
|
||||
{
|
||||
if (_paused)
|
||||
return;
|
||||
@@ -236,7 +240,7 @@ namespace StandardScene.Charge
|
||||
RequestStatisticsRefresh();
|
||||
}
|
||||
|
||||
private static void InsertMessageAtTop(CommunicationMessage msg)
|
||||
private void InsertMessageAtTop(CommunicationMessage msg)
|
||||
{
|
||||
_displayMessages.Insert(0, msg);
|
||||
while (_displayMessages.Count > MaxDisplayRows)
|
||||
@@ -246,7 +250,7 @@ namespace StandardScene.Charge
|
||||
_selectedRowIndex++;
|
||||
}
|
||||
|
||||
private static void ReloadFromService()
|
||||
private void ReloadFromService()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -267,7 +271,7 @@ namespace StandardScene.Charge
|
||||
}
|
||||
}
|
||||
|
||||
private static void RefreshIpFilter()
|
||||
private void RefreshIpFilter()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -302,7 +306,7 @@ namespace StandardScene.Charge
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureIpInFilter(string ipAddress)
|
||||
private void EnsureIpInFilter(string ipAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||
return;
|
||||
@@ -315,7 +319,7 @@ namespace StandardScene.Charge
|
||||
_ipOptions = list.ToArray();
|
||||
}
|
||||
|
||||
private static string SelectedIpFilter()
|
||||
private string SelectedIpFilter()
|
||||
{
|
||||
if (_ipOptions == null || _ipOptions.Length == 0)
|
||||
return "全部";
|
||||
@@ -324,13 +328,13 @@ namespace StandardScene.Charge
|
||||
return _ipOptions[_selectedIpIndex];
|
||||
}
|
||||
|
||||
private static void RequestStatisticsRefresh()
|
||||
private void RequestStatisticsRefresh()
|
||||
{
|
||||
_pendingStatsRefresh = true;
|
||||
}
|
||||
|
||||
/// <summary>统计信息低频刷新(500ms)。</summary>
|
||||
private static void MaybeRefreshStatistics()
|
||||
private void MaybeRefreshStatistics()
|
||||
{
|
||||
if (!_pendingStatsRefresh)
|
||||
return;
|
||||
@@ -342,7 +346,7 @@ namespace StandardScene.Charge
|
||||
UpdateStatistics(_displayMessages.Count);
|
||||
}
|
||||
|
||||
private static void UpdateStatistics(int displayCount)
|
||||
private void UpdateStatistics(int displayCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -364,14 +368,14 @@ namespace StandardScene.Charge
|
||||
}
|
||||
}
|
||||
|
||||
private static string TruncateRawData(string rawData, int maxLen = 48)
|
||||
private string TruncateRawData(string rawData, int maxLen = 48)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rawData))
|
||||
return "";
|
||||
return rawData.Length <= maxLen ? rawData : rawData.Substring(0, maxLen) + "…";
|
||||
}
|
||||
|
||||
private static string BuildParsedText(CommunicationMessage message)
|
||||
private string BuildParsedText(CommunicationMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
return "";
|
||||
|
||||
@@ -17,32 +17,36 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
public class ButtonBoxManager
|
||||
{
|
||||
private const string DataFileName = "ButtonBoxConfig.json";
|
||||
private static string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName);
|
||||
private static readonly object SaveLock = new object();
|
||||
private string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName);
|
||||
private readonly object SaveLock = new object();
|
||||
|
||||
private static Panel _panel;
|
||||
private static List<ButtonBoxModel> _boxes = new List<ButtonBoxModel>();
|
||||
private static int _selectedBoxIdx = -1;
|
||||
private static int _selectedBtnIdx = -1;
|
||||
private static string _status = "";
|
||||
private Panel _panel;
|
||||
private List<ButtonBoxModel> _boxes = new List<ButtonBoxModel>();
|
||||
private int _selectedBoxIdx = -1;
|
||||
private int _selectedBtnIdx = -1;
|
||||
private string _status = "";
|
||||
|
||||
private static string _boxIp = "";
|
||||
private static string _boxPort = "502";
|
||||
private static string _boxIndex = "";
|
||||
private static int _typeIdx;
|
||||
private static string[] _typeNames = Array.Empty<string>();
|
||||
private string _boxIp = "";
|
||||
private string _boxPort = "502";
|
||||
private string _boxIndex = "";
|
||||
private int _typeIdx;
|
||||
private string[] _typeNames = Array.Empty<string>();
|
||||
|
||||
private static string _btnIndex = "";
|
||||
private static string _triggerMission = "";
|
||||
private static string _triggerMethod = "";
|
||||
private static string _triggerParams = "";
|
||||
private static string _triggerDelay = "0";
|
||||
private static int _triggerStateIdx;
|
||||
private static readonly string[] _triggerStateNames = Enum.GetNames(typeof(ButtonState));
|
||||
private string _btnIndex = "";
|
||||
private string _triggerMission = "";
|
||||
private string _triggerMethod = "";
|
||||
private string _triggerParams = "";
|
||||
private string _triggerDelay = "0";
|
||||
private int _triggerStateIdx;
|
||||
private readonly string[] _triggerStateNames = Enum.GetNames(typeof(ButtonState));
|
||||
|
||||
private static ButtonBoxManager _instance;
|
||||
|
||||
public static void OpenViewer() => Open();
|
||||
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new ButtonBoxManager()).OpenCore();
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -155,17 +159,17 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
});
|
||||
}
|
||||
|
||||
private static ButtonBoxModel GetSelectedBox() =>
|
||||
private ButtonBoxModel GetSelectedBox() =>
|
||||
_selectedBoxIdx >= 0 && _selectedBoxIdx < _boxes.Count ? _boxes[_selectedBoxIdx] : null;
|
||||
|
||||
private static ButtonModel GetSelectedButton()
|
||||
private ButtonModel GetSelectedButton()
|
||||
{
|
||||
var box = GetSelectedBox();
|
||||
return box != null && _selectedBtnIdx >= 0 && _selectedBtnIdx < box.Buttons.Count
|
||||
? box.Buttons[_selectedBtnIdx] : null;
|
||||
}
|
||||
|
||||
private static string[] DiscoverTypes()
|
||||
private string[] DiscoverTypes()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -184,7 +188,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadData()
|
||||
private void LoadData()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -201,7 +205,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveData()
|
||||
private void SaveData()
|
||||
{
|
||||
var json = _boxes.ToJson();
|
||||
var path = DataFilePath;
|
||||
@@ -220,7 +224,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
});
|
||||
}
|
||||
|
||||
private static void LoadBoxFields(ButtonBoxModel b)
|
||||
private void LoadBoxFields(ButtonBoxModel b)
|
||||
{
|
||||
_boxIp = b.Ip;
|
||||
_boxPort = b.Port.ToString();
|
||||
@@ -229,7 +233,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
if (_typeIdx < 0) _typeIdx = 0;
|
||||
}
|
||||
|
||||
private static void ClearBoxFields()
|
||||
private void ClearBoxFields()
|
||||
{
|
||||
_boxIp = "";
|
||||
_boxPort = "502";
|
||||
@@ -237,7 +241,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
_typeIdx = 0;
|
||||
}
|
||||
|
||||
private static void LoadButtonFields(ButtonModel b)
|
||||
private void LoadButtonFields(ButtonModel b)
|
||||
{
|
||||
_btnIndex = b.Index.ToString();
|
||||
_triggerMission = b.TriggerMission ?? "";
|
||||
@@ -247,7 +251,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
_triggerStateIdx = Math.Max(0, Array.IndexOf(_triggerStateNames, b.TriggerState));
|
||||
}
|
||||
|
||||
private static void ClearButtonFields()
|
||||
private void ClearButtonFields()
|
||||
{
|
||||
_btnIndex = "";
|
||||
_triggerMission = "";
|
||||
@@ -257,7 +261,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
_triggerStateIdx = 0;
|
||||
}
|
||||
|
||||
private static void AddBox()
|
||||
private void AddBox()
|
||||
{
|
||||
if (!TryParseBoxInput(out var index, out var ip, out var port, out var type, false, out var err))
|
||||
{ CycleUiHelper.Alert("错误", err); return; }
|
||||
@@ -272,7 +276,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void SaveBox()
|
||||
private void SaveBox()
|
||||
{
|
||||
var cur = GetSelectedBox();
|
||||
if (cur == null) { CycleUiHelper.Alert("提示", "请先选择按钮盒"); return; }
|
||||
@@ -287,7 +291,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void ConfirmDeleteBox()
|
||||
private void ConfirmDeleteBox()
|
||||
{
|
||||
var cur = GetSelectedBox();
|
||||
if (cur == null) { CycleUiHelper.Alert("提示", "请选择要删除的按钮盒"); return; }
|
||||
@@ -304,7 +308,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddButton()
|
||||
private void AddButton()
|
||||
{
|
||||
var box = GetSelectedBox();
|
||||
if (box == null) { CycleUiHelper.Alert("提示", "请先选择按钮盒"); return; }
|
||||
@@ -327,7 +331,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void SaveButton()
|
||||
private void SaveButton()
|
||||
{
|
||||
var box = GetSelectedBox();
|
||||
var btn = GetSelectedButton();
|
||||
@@ -347,7 +351,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void ConfirmDeleteButton()
|
||||
private void ConfirmDeleteButton()
|
||||
{
|
||||
var box = GetSelectedBox();
|
||||
var btn = GetSelectedButton();
|
||||
@@ -363,7 +367,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TryParseBoxInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err)
|
||||
private bool TryParseBoxInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err)
|
||||
{
|
||||
index = 0; ip = ""; port = 502; type = ""; err = "";
|
||||
ip = string.IsNullOrWhiteSpace(_boxIp) ? "192.168.1.100" : _boxIp.Trim();
|
||||
@@ -379,7 +383,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseButtonInput(out int index, out ushort delay, out string err)
|
||||
private bool TryParseButtonInput(out int index, out ushort delay, out string err)
|
||||
{
|
||||
index = 0; delay = 0; err = "";
|
||||
var box = GetSelectedBox();
|
||||
@@ -393,7 +397,7 @@ namespace StandardScene.ExtendDevice.ButtonBox
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidIp(string ip)
|
||||
private bool IsValidIp(string ip)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ip)) return false;
|
||||
var pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
|
||||
|
||||
@@ -18,29 +18,33 @@ namespace StandardScene.ExtendDevice.Door
|
||||
public class DoorManager
|
||||
{
|
||||
private const string DataFileName = "DoorConfig.json";
|
||||
private static string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName);
|
||||
private static readonly object SaveLock = new object();
|
||||
private string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName);
|
||||
private readonly object SaveLock = new object();
|
||||
|
||||
private static Panel _panel;
|
||||
private static List<DoorControllerModel> _controllers = new List<DoorControllerModel>();
|
||||
private static int _selectedCtrlIdx = -1;
|
||||
private static int _selectedDoorIdx = -1;
|
||||
private static string _status = "";
|
||||
private Panel _panel;
|
||||
private List<DoorControllerModel> _controllers = new List<DoorControllerModel>();
|
||||
private int _selectedCtrlIdx = -1;
|
||||
private int _selectedDoorIdx = -1;
|
||||
private string _status = "";
|
||||
|
||||
private static string _ctrlIp = "";
|
||||
private static string _ctrlPort = "502";
|
||||
private static string _ctrlIndex = "";
|
||||
private static int _typeIdx;
|
||||
private static string[] _typeNames = Array.Empty<string>();
|
||||
private string _ctrlIp = "";
|
||||
private string _ctrlPort = "502";
|
||||
private string _ctrlIndex = "";
|
||||
private int _typeIdx;
|
||||
private string[] _typeNames = Array.Empty<string>();
|
||||
|
||||
private static string _doorIndex = "";
|
||||
private static string _doorCtrlAddr = "";
|
||||
private static string _doorOpenAddr = "";
|
||||
private static bool _doorNoControl;
|
||||
private string _doorIndex = "";
|
||||
private string _doorCtrlAddr = "";
|
||||
private string _doorOpenAddr = "";
|
||||
private bool _doorNoControl;
|
||||
|
||||
public static void OpenViewer() => Open();
|
||||
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new DoorManager()).OpenCore();
|
||||
|
||||
private static DoorManager _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -148,17 +152,17 @@ namespace StandardScene.ExtendDevice.Door
|
||||
});
|
||||
}
|
||||
|
||||
private static DoorControllerModel GetSelectedController() =>
|
||||
private DoorControllerModel GetSelectedController() =>
|
||||
_selectedCtrlIdx >= 0 && _selectedCtrlIdx < _controllers.Count ? _controllers[_selectedCtrlIdx] : null;
|
||||
|
||||
private static DoorModel GetSelectedDoor()
|
||||
private DoorModel GetSelectedDoor()
|
||||
{
|
||||
var ctrl = GetSelectedController();
|
||||
return ctrl != null && _selectedDoorIdx >= 0 && _selectedDoorIdx < ctrl.Doors.Count
|
||||
? ctrl.Doors[_selectedDoorIdx] : null;
|
||||
}
|
||||
|
||||
private static string[] DiscoverTypes()
|
||||
private string[] DiscoverTypes()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -179,7 +183,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadData()
|
||||
private void LoadData()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -196,7 +200,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveData()
|
||||
private void SaveData()
|
||||
{
|
||||
var json = _controllers.ToJson();
|
||||
var path = DataFilePath;
|
||||
@@ -215,7 +219,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
});
|
||||
}
|
||||
|
||||
private static void LoadControllerFields(DoorControllerModel c)
|
||||
private void LoadControllerFields(DoorControllerModel c)
|
||||
{
|
||||
_ctrlIp = c.Ip;
|
||||
_ctrlPort = c.Port.ToString();
|
||||
@@ -224,7 +228,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
if (_typeIdx < 0) _typeIdx = 0;
|
||||
}
|
||||
|
||||
private static void ClearControllerFields()
|
||||
private void ClearControllerFields()
|
||||
{
|
||||
_ctrlIp = "";
|
||||
_ctrlPort = "502";
|
||||
@@ -232,7 +236,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
_typeIdx = 0;
|
||||
}
|
||||
|
||||
private static void LoadDoorFields(DoorModel d)
|
||||
private void LoadDoorFields(DoorModel d)
|
||||
{
|
||||
_doorIndex = d.Index.ToString();
|
||||
_doorCtrlAddr = d.ControlAddress.ToString();
|
||||
@@ -240,7 +244,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
_doorNoControl = d.NoControl;
|
||||
}
|
||||
|
||||
private static void ClearDoorFields()
|
||||
private void ClearDoorFields()
|
||||
{
|
||||
_doorIndex = "";
|
||||
_doorCtrlAddr = "";
|
||||
@@ -248,7 +252,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
_doorNoControl = false;
|
||||
}
|
||||
|
||||
private static void AddController()
|
||||
private void AddController()
|
||||
{
|
||||
if (!TryParseControllerInput(out var index, out var ip, out var port, out var type, false, out var err))
|
||||
{
|
||||
@@ -266,7 +270,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void SaveController()
|
||||
private void SaveController()
|
||||
{
|
||||
var cur = GetSelectedController();
|
||||
if (cur == null) { CycleUiHelper.Alert("提示", "请先选择要保存的门控制器"); return; }
|
||||
@@ -288,7 +292,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void ConfirmDeleteController()
|
||||
private void ConfirmDeleteController()
|
||||
{
|
||||
var cur = GetSelectedController();
|
||||
if (cur == null) { CycleUiHelper.Alert("提示", "请选择要删除的门控制器"); return; }
|
||||
@@ -305,7 +309,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddDoor()
|
||||
private void AddDoor()
|
||||
{
|
||||
var ctrl = GetSelectedController();
|
||||
if (ctrl == null) { CycleUiHelper.Alert("提示", "请先选择门控制器"); return; }
|
||||
@@ -330,7 +334,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void SaveDoor()
|
||||
private void SaveDoor()
|
||||
{
|
||||
var ctrl = GetSelectedController();
|
||||
var door = GetSelectedDoor();
|
||||
@@ -352,7 +356,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void ConfirmDeleteDoor()
|
||||
private void ConfirmDeleteDoor()
|
||||
{
|
||||
var ctrl = GetSelectedController();
|
||||
var door = GetSelectedDoor();
|
||||
@@ -368,7 +372,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TryParseControllerInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err)
|
||||
private bool TryParseControllerInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err)
|
||||
{
|
||||
index = 0; ip = ""; port = 502; type = ""; err = "";
|
||||
ip = string.IsNullOrWhiteSpace(_ctrlIp) ? "192.168.1.100" : _ctrlIp.Trim();
|
||||
@@ -384,7 +388,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseDoorInput(out int index, out ushort cAddr, out ushort oAddr, bool requireSelection, out string err)
|
||||
private bool TryParseDoorInput(out int index, out ushort cAddr, out ushort oAddr, bool requireSelection, out string err)
|
||||
{
|
||||
index = 0; cAddr = 0; oAddr = 0; err = "";
|
||||
var ctrl = GetSelectedController();
|
||||
@@ -401,7 +405,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidIp(string ip)
|
||||
private bool IsValidIp(string ip)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ip)) return false;
|
||||
var pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
|
||||
|
||||
@@ -20,13 +20,17 @@ namespace StandardScene.ExtendDevice.Door
|
||||
{
|
||||
private const string TableId = "door-monitor-list";
|
||||
|
||||
private static readonly Color SelectedRowColor = Color.FromArgb(230, 240, 255);
|
||||
private readonly Color SelectedRowColor = Color.FromArgb(230, 240, 255);
|
||||
|
||||
private static Panel _panel;
|
||||
private static (int ControllerIndex, int DoorIndex)? _selectedDoor;
|
||||
private Panel _panel;
|
||||
private (int ControllerIndex, int DoorIndex)? _selectedDoor;
|
||||
|
||||
/// <summary>打开(或置前)门控监控面板。</summary>
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new DoorMonitor()).OpenCore();
|
||||
|
||||
private static DoorMonitor _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -105,21 +109,21 @@ namespace StandardScene.ExtendDevice.Door
|
||||
});
|
||||
}
|
||||
|
||||
private static void LabelCell(PanelBuilder.Row row, DoorMission.DoorMonitorSnapshotItem door, string text)
|
||||
private void LabelCell(PanelBuilder.Row row, DoorMission.DoorMonitorSnapshotItem door, string text)
|
||||
{
|
||||
if (row.Label(text))
|
||||
_selectedDoor = (door.ControllerIndex, door.DoorIndex);
|
||||
}
|
||||
|
||||
private static DoorMission GetMission() =>
|
||||
private DoorMission GetMission() =>
|
||||
SimpleProject.proj?.Missions?.OfType<DoorMission>().FirstOrDefault();
|
||||
|
||||
private static bool IsRowSelected(DoorMission.DoorMonitorSnapshotItem door) =>
|
||||
private bool IsRowSelected(DoorMission.DoorMonitorSnapshotItem door) =>
|
||||
_selectedDoor.HasValue
|
||||
&& _selectedDoor.Value.ControllerIndex == door.ControllerIndex
|
||||
&& _selectedDoor.Value.DoorIndex == door.DoorIndex;
|
||||
|
||||
private static string GetDoorInfoText(IReadOnlyList<DoorMission.DoorMonitorSnapshotItem> snapshot)
|
||||
private string GetDoorInfoText(IReadOnlyList<DoorMission.DoorMonitorSnapshotItem> snapshot)
|
||||
{
|
||||
if (!_selectedDoor.HasValue)
|
||||
return "请选择要控制的门";
|
||||
@@ -133,7 +137,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
return $"控制器编码: {door.ControllerIndex}, 门编码: {door.DoorIndex}";
|
||||
}
|
||||
|
||||
private static bool CanCloseSelected(DoorMission mission)
|
||||
private bool CanCloseSelected(DoorMission mission)
|
||||
{
|
||||
if (!_selectedDoor.HasValue || mission == null)
|
||||
return true;
|
||||
@@ -142,18 +146,18 @@ namespace StandardScene.ExtendDevice.Door
|
||||
return cars.Count == 0;
|
||||
}
|
||||
|
||||
private static string FormatState(DoorState state) =>
|
||||
private string FormatState(DoorState state) =>
|
||||
state == DoorState.Open ? "打开" : state == DoorState.Closed ? "关闭" : "未知";
|
||||
|
||||
private static string FormatManualRemain(DoorMission.DoorMonitorSnapshotItem door) =>
|
||||
private string FormatManualRemain(DoorMission.DoorMonitorSnapshotItem door) =>
|
||||
door.Source == DoorMission.ControlSource.Manual && door.ManualRemainingSeconds.HasValue
|
||||
? Math.Ceiling(door.ManualRemainingSeconds.Value).ToString()
|
||||
: "-";
|
||||
|
||||
private static string FormatCars(DoorMission.DoorMonitorSnapshotItem door) =>
|
||||
private string FormatCars(DoorMission.DoorMonitorSnapshotItem door) =>
|
||||
door.CarsInArea.Count > 0 ? string.Join(", ", door.CarsInArea) : "无";
|
||||
|
||||
private static void OpenSelectedDoor()
|
||||
private void OpenSelectedDoor()
|
||||
{
|
||||
if (!_selectedDoor.HasValue)
|
||||
{
|
||||
@@ -180,7 +184,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
}
|
||||
}
|
||||
|
||||
private static void CloseSelectedDoor()
|
||||
private void CloseSelectedDoor()
|
||||
{
|
||||
if (!_selectedDoor.HasValue)
|
||||
{
|
||||
@@ -213,7 +217,7 @@ namespace StandardScene.ExtendDevice.Door
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClearSelectedCars()
|
||||
private void ClearSelectedCars()
|
||||
{
|
||||
if (!_selectedDoor.HasValue)
|
||||
{
|
||||
|
||||
@@ -25,29 +25,33 @@ namespace LoopViewerApp
|
||||
{
|
||||
private const string TableId = "traffic-area-list";
|
||||
|
||||
private static string JsonPath =>
|
||||
private string JsonPath =>
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "traffic.json");
|
||||
|
||||
private static readonly object SaveLock = new object();
|
||||
private readonly object SaveLock = new object();
|
||||
|
||||
private static Panel _panel;
|
||||
private static string _editingAreaId = "";
|
||||
private static readonly HashSet<string> _selected = new HashSet<string>(StringComparer.Ordinal);
|
||||
private Panel _panel;
|
||||
private string _editingAreaId = "";
|
||||
private readonly HashSet<string> _selected = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
private static string _areaName = "";
|
||||
private static string _stationIds = "";
|
||||
private static string _controlRight = "";
|
||||
private static bool _isOccupied;
|
||||
private static bool _isEnabled = true;
|
||||
private static string _editingHint = "新增区域";
|
||||
private static string _editErr = "";
|
||||
private static volatile string _status = "";
|
||||
private string _areaName = "";
|
||||
private string _stationIds = "";
|
||||
private string _controlRight = "";
|
||||
private bool _isOccupied;
|
||||
private bool _isEnabled = true;
|
||||
private string _editingHint = "新增区域";
|
||||
private string _editErr = "";
|
||||
private volatile string _status = "";
|
||||
|
||||
/// <summary>打开(或置前)区域管理面板。兼容原 <c>new TrafficInterlockViewer().Show()</c> 调用方式。</summary>
|
||||
public void Show() => Open();
|
||||
|
||||
/// <summary>打开(或置前)区域管理面板。</summary>
|
||||
public static void Open()
|
||||
public static void Open() => (_instance ??= new TrafficInterlockViewer()).OpenCore();
|
||||
|
||||
private static TrafficInterlockViewer _instance;
|
||||
|
||||
private void OpenCore()
|
||||
{
|
||||
if (_panel != null)
|
||||
{
|
||||
@@ -171,7 +175,7 @@ namespace LoopViewerApp
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfirmDeleteSelected()
|
||||
private void ConfirmDeleteSelected()
|
||||
{
|
||||
if (_selected.Count == 0)
|
||||
{
|
||||
@@ -187,7 +191,7 @@ namespace LoopViewerApp
|
||||
CycleUiHelper.ConfirmThen(prompt, DeleteSelected);
|
||||
}
|
||||
|
||||
private static void DeleteSelected()
|
||||
private void DeleteSelected()
|
||||
{
|
||||
var selectedIds = _selected.ToHashSet(StringComparer.Ordinal);
|
||||
var removed = 0;
|
||||
@@ -203,7 +207,7 @@ namespace LoopViewerApp
|
||||
_panel?.Repaint();
|
||||
}
|
||||
|
||||
private static void SaveArea()
|
||||
private void SaveArea()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -270,7 +274,7 @@ namespace LoopViewerApp
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadAreaToFields(TrafficArea a)
|
||||
private void LoadAreaToFields(TrafficArea a)
|
||||
{
|
||||
if (a == null) return;
|
||||
_editingHint = $"编辑:{a.AreaName}";
|
||||
@@ -284,7 +288,7 @@ namespace LoopViewerApp
|
||||
_editErr = "";
|
||||
}
|
||||
|
||||
private static void ClearPanelInputs()
|
||||
private void ClearPanelInputs()
|
||||
{
|
||||
_editingAreaId = "";
|
||||
_editingHint = "新增区域";
|
||||
@@ -296,7 +300,7 @@ namespace LoopViewerApp
|
||||
_editErr = "";
|
||||
}
|
||||
|
||||
private static void SaveToConfig()
|
||||
private void SaveToConfig()
|
||||
{
|
||||
string json;
|
||||
try
|
||||
@@ -331,7 +335,7 @@ namespace LoopViewerApp
|
||||
}
|
||||
|
||||
/// <summary>解析站点集合字符串,如 "1,2,3" → <see cref="List{T}"/> of int。</summary>
|
||||
private static List<int> ParseStationIds(string text)
|
||||
private List<int> ParseStationIds(string text)
|
||||
{
|
||||
var list = new List<int>();
|
||||
if (string.IsNullOrWhiteSpace(text)) return list;
|
||||
@@ -343,19 +347,19 @@ namespace LoopViewerApp
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void EnsureAreaIdsLocked()
|
||||
private void EnsureAreaIdsLocked()
|
||||
{
|
||||
foreach (var area in TrafficInterlockMission.TrafficAreaList)
|
||||
EnsureAreaId(area);
|
||||
}
|
||||
|
||||
private static string EnsureAreaId(TrafficArea area)
|
||||
private string EnsureAreaId(TrafficArea area)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(area.Id))
|
||||
area.Id = NewAreaId();
|
||||
return area.Id;
|
||||
}
|
||||
|
||||
private static string NewAreaId() => Guid.NewGuid().ToString("N");
|
||||
private string NewAreaId() => Guid.NewGuid().ToString("N");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user