using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using CycleGUI; using Newtonsoft.Json; using StandardScene.InterLock; using StandardScene.Utils; namespace LoopViewerApp { /// /// 交通联锁区域管理界面(CycleGUI 版,替代原 WinForms TrafficInterlockViewer 窗体)。 /// /// 维护 的增 / 改 / 删,并写入 Config/traffic.json /// 单实例:再次打开则把已有面板置前。 /// 勾选多行后「删除选中」可批量删除;每行「编辑」按钮加载下方编辑区。 /// 文件写入放后台线程,绝不阻塞渲染线程。 /// /// 沿用 LoopViewer / DeliveryViewer 同套模式(单实例面板、pb.TableCycleUiHelper.ConfirmThen)。 /// 保留可实例化 + 以兼容既有调用 new TrafficInterlockViewer().Show()。 /// public class TrafficInterlockViewer { private const string TableId = "traffic-area-list"; private static string JsonPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "traffic.json"); private static readonly object SaveLock = new object(); private static Panel _panel; private static string _editingAreaId = ""; private static readonly HashSet _selected = new HashSet(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 = ""; /// 打开(或置前)区域管理面板。兼容原 new TrafficInterlockViewer().Show() 调用方式。 public void Show() => Open(); /// 打开(或置前)区域管理面板。 public static void Open() { if (_panel != null) { try { _panel.BringToFront(); return; } catch { _panel = null; } } ClearPanelInputs(); var panel = GUI.DeclarePanel() .ShowTitle("交通联锁区域管理") .SetDefaultDocking(Panel.Docking.None) .InitSize(800, 680) .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); _panel = panel; panel.IfTerminalQuit(() => { if (_panel == panel) _panel = null; }); panel.Define(pb => { if (pb.Closing()) { panel.Exit(); _panel = null; return; } List areas; lock (TrafficInterlockMission.TrafficAreaList) { EnsureAreaIdsLocked(); areas = TrafficInterlockMission.TrafficAreaList.ToList(); } _selected.RemoveWhere(id => areas.All(a => a.Id != id)); if (!string.IsNullOrEmpty(_editingAreaId) && areas.All(a => a.Id != _editingAreaId)) ClearPanelInputs(); if (pb.Button("删除选中", distinct: "traffic-del-selected")) ConfirmDeleteSelected(); pb.SameLine(12); if (pb.Button("刷新", distinct: "traffic-refresh")) { _status = ""; _editErr = ""; } pb.SameLine(12); pb.Label($"共 {areas.Count} 个区域,已选 {_selected.Count} 个"); pb.Table(TableId, new[] { "选择", "区域名称", "区域站点集合", "控制权", "是否被占用", "是否启用", "操作" }, areas.Count, (row, i) => { var a = areas[i]; var areaId = EnsureAreaId(a); var sel = _selected.Contains(areaId); if (row.Checkbox(ref sel, "勾选以批量删除")) { if (sel) _selected.Add(areaId); else _selected.Remove(areaId); } row.Label(a.AreaName ?? ""); row.Label(a.SiteList != null && a.SiteList.Count > 0 ? string.Join(", ", a.SiteList) : ""); row.Label(a.ControllerName ?? ""); row.Label(a.IsOccupy ? "是" : "否"); row.Label(a.IsEnable ? "是" : "否"); if (row.ButtonGroup(new[] { "编辑" }, new[] { "编辑该区域" }) == 0) { _editingAreaId = areaId; LoadAreaToFields(a); } }, height: 14, enableSearch: true); pb.Separator(); pb.Label("数据新增/编辑(点击表格行「编辑」可在此查看并修改该行数据)"); pb.Label(_editingHint); var (name, _) = pb.TextInput("1. 区域名称", _areaName, alwaysReturnString: true); _areaName = name; var (sites, _) = pb.TextInput("2. 站点集合 (逗号/分号/空格分隔)", _stationIds, alwaysReturnString: true); _stationIds = sites; var (ctrl, _) = pb.TextInput("3. 控制权", _controlRight, alwaysReturnString: true); _controlRight = ctrl; pb.CheckBox("4. 是否被占用", ref _isOccupied); pb.SameLine(16); pb.CheckBox("5. 是否启用", ref _isEnabled); if (!string.IsNullOrEmpty(_editErr)) { pb.Separator(); pb.Label(_editErr); } pb.Separator(); if (pb.Button("保存", distinct: "traffic-save")) SaveArea(); pb.SameLine(12); if (pb.Button("新增", distinct: "traffic-new")) { _selected.Clear(); ClearPanelInputs(); } if (!string.IsNullOrEmpty(_status)) { pb.Separator(); pb.Label(_status); } pb.Panel.Repaint(repaintTimeMs: 500); }); } private static void ConfirmDeleteSelected() { if (_selected.Count == 0) { _status = "请先在上方列表中选择要删除的区域。"; _panel?.Repaint(); return; } string prompt = _selected.Count == 1 ? "确定要删除选中的区域吗?" : $"确定要删除所选 {_selected.Count} 个区域吗?"; CycleUiHelper.ConfirmThen(prompt, DeleteSelected); } private static void DeleteSelected() { var selectedIds = _selected.ToHashSet(StringComparer.Ordinal); var removed = 0; lock (TrafficInterlockMission.TrafficAreaList) { removed = TrafficInterlockMission.TrafficAreaList.RemoveAll(a => selectedIds.Contains(a.Id)); } _selected.Clear(); SaveToConfig(); ClearPanelInputs(); _status = $"已删除 {removed} 个区域"; _panel?.Repaint(); } private static void SaveArea() { try { string areaName = (_areaName ?? "").Trim(); if (string.IsNullOrEmpty(areaName)) { _editErr = "请输入区域名称。"; _panel?.Repaint(); return; } var stationIds = ParseStationIds(_stationIds ?? ""); string controlRight = (_controlRight ?? "").Trim(); bool isOccupied = _isOccupied; bool isEnabled = _isEnabled; if (!string.IsNullOrEmpty(_editingAreaId)) { lock (TrafficInterlockMission.TrafficAreaList) { var existing = TrafficInterlockMission.TrafficAreaList .FirstOrDefault(a => string.Equals(a.Id, _editingAreaId, StringComparison.Ordinal)); if (existing == null) { _editErr = "正在编辑的区域已不存在,请刷新后重试。"; _panel?.Repaint(); return; } existing.AreaName = areaName; existing.SiteList = stationIds; existing.ControllerName = controlRight; existing.IsOccupy = isOccupied; existing.IsEnable = isEnabled; } _status = $"已保存区域:{areaName}"; } else { lock (TrafficInterlockMission.TrafficAreaList) { TrafficInterlockMission.TrafficAreaList.Add(new TrafficArea { Id = NewAreaId(), AreaName = areaName, SiteList = stationIds, ControllerName = controlRight, IsOccupy = isOccupied, IsEnable = isEnabled }); } _status = "已新增区域"; } SaveToConfig(); ClearPanelInputs(); _editErr = ""; _panel?.Repaint(); } catch (Exception ex) { _editErr = "操作失败:" + ex.Message; _panel?.Repaint(); } } private static void LoadAreaToFields(TrafficArea a) { if (a == null) return; _editingHint = $"编辑:{a.AreaName}"; _areaName = a.AreaName ?? ""; _stationIds = a.SiteList != null && a.SiteList.Count > 0 ? string.Join(", ", a.SiteList) : ""; _controlRight = a.ControllerName ?? ""; _isOccupied = a.IsOccupy; _isEnabled = a.IsEnable; _editErr = ""; } private static void ClearPanelInputs() { _editingAreaId = ""; _editingHint = "新增区域"; _areaName = ""; _stationIds = ""; _controlRight = ""; _isOccupied = false; _isEnabled = true; _editErr = ""; } private static void SaveToConfig() { string json; try { lock (TrafficInterlockMission.TrafficAreaList) json = JsonConvert.SerializeObject(TrafficInterlockMission.TrafficAreaList, Formatting.Indented); } catch (Exception ex) { _status = "保存失败:" + ex.Message; _panel?.Repaint(); return; } var path = JsonPath; var dir = Path.GetDirectoryName(path); Task.Run(() => { try { if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir); lock (SaveLock) File.WriteAllText(path, json); } catch (Exception ex) { _status = "保存失败:" + ex.Message; _panel?.Repaint(); } }); } /// 解析站点集合字符串,如 "1,2,3" → of int。 private static List ParseStationIds(string text) { var list = new List(); if (string.IsNullOrWhiteSpace(text)) return list; foreach (var part in text.Split(new[] { ',', ';', ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries)) { if (int.TryParse(part.Trim(), out int id)) list.Add(id); } return list; } private static void EnsureAreaIdsLocked() { foreach (var area in TrafficInterlockMission.TrafficAreaList) EnsureAreaId(area); } private static string EnsureAreaId(TrafficArea area) { if (string.IsNullOrWhiteSpace(area.Id)) area.Id = NewAreaId(); return area.Id; } private static string NewAreaId() => Guid.NewGuid().ToString("N"); } }