diff --git a/CRSim.Core/Models/Simulator/TrainStop.cs b/CRSim.Core/Models/Simulator/TrainStop.cs index dcd516f..087d376 100644 --- a/CRSim.Core/Models/Simulator/TrainStop.cs +++ b/CRSim.Core/Models/Simulator/TrainStop.cs @@ -15,6 +15,9 @@ namespace CRSim.Core.Models [JsonConverter(typeof(TimeSpanJsonConverter))] public TimeSpan? DepartureTime { get; set; } + [JsonConverter(typeof(TimeSpanJsonConverter))] + public TimeSpan? Status { get; set; } = TimeSpan.Zero; + public List? TicketCheckIds { get; set; } public string? Origin { get; set; } diff --git a/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs b/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs index afccfd6..702888d 100644 --- a/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs +++ b/CRSim.ScreenSimulator/Converters/TrainStateColorConverter.cs @@ -1,84 +1,88 @@ using CRSim.Core.Abstractions; using CRSim.Core.Models; using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; using System.Globalization; -using System.Windows; using System.Windows.Data; using System.Windows.Media; + namespace CRSim.ScreenSimulator.Converters { public class TrainStateColorConverter : IMultiValueConverter, IHasTimeService { public ITimeService TimeService { get; set; } private Settings _settings; + public string DisplayMode { get; set; } = "Normal"; - /* - Normal: 标准显示。 - Alternating_Row_Colors: 候车状态隔行异色显示(第4个参数控制行号)。 - Arrive: 到达屏显示 - */ - - public List WaitingColorList { get; set; } = []; + public List WaitingColorList { get; set; } = new(); public SolidColorBrush ArrivedText { get; set; } = new(Colors.LightGreen); public SolidColorBrush ArrivingText { get; set; } = new(Colors.White); public SolidColorBrush WaitingColor { get; set; } = new(Colors.White); public SolidColorBrush CheckInColor { get; set; } = new(Colors.LightGreen); public SolidColorBrush StopCheckInColor { get; set; } = new(Colors.Red); - object IMultiValueConverter.Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - var serviceProvider = StyleManager.ServiceProvider; - _settings = serviceProvider.GetRequiredService().GetSettings(); - if (DisplayMode == "Arrive" || ( values.Length > 1 && values[1] == null)) + _settings ??= StyleManager.ServiceProvider + .GetRequiredService() + .GetSettings(); + + var now = TimeService.GetDateTimeNow(); + + // 到达模式或缺少关键数据 + if (DisplayMode == "Arrive" || (values.Length > 1 && values[1] == null)) { if (values[0] is DateTime arriveTime) - { - return TimeService.GetDateTimeNow() >= arriveTime ? ArrivedText : ArrivingText; - } - return string.Empty; + return now >= arriveTime ? ArrivedText : ArrivingText; + return new SolidColorBrush(Colors.Transparent); } - if (values[0] != null && values[1] == null) + + // 列车停运 + if (values[2] is null) + return StopCheckInColor; + + // 正点/晚点判断 + if (values[0] != null && values[1] == null && values[2] is TimeSpan status) + return GetStatusColor(status); + + // 有出发时间 + if (values[1] is DateTime departureTime && departureTime != DateTime.MinValue) { - return WaitingColor; - } - if (values[1] is DateTime departureTime && departureTime !=new DateTime()) - { - if (values[0] is DateTime) - { - //过路站 - if (TimeService.GetDateTimeNow() > departureTime.Subtract(_settings.PassingCheckInAdvanceDuration) && TimeService.GetDateTimeNow() < departureTime.Subtract(_settings.StopCheckInAdvanceDuration)) - { - return CheckInColor; - } - } - else - { - //始发站 - if (TimeService.GetDateTimeNow() > departureTime.Subtract(_settings.DepartureCheckInAdvanceDuration) && TimeService.GetDateTimeNow() < departureTime.Subtract(_settings.StopCheckInAdvanceDuration)) - { - return CheckInColor; - } - } - if (TimeService.GetDateTimeNow() > departureTime.Subtract(_settings.StopCheckInAdvanceDuration)) - { + bool isPassingStation = values[0] is DateTime; + var checkInDuration = isPassingStation ? _settings.PassingCheckInAdvanceDuration : _settings.DepartureCheckInAdvanceDuration; + + // 候车中/检票中 + if (now > departureTime - checkInDuration && now < departureTime - _settings.StopCheckInAdvanceDuration) + return CheckInColor; + + // 停止检票 + if (now >= departureTime - _settings.StopCheckInAdvanceDuration) return StopCheckInColor; - } - if (values[2] is TimeSpan state && state.TotalMinutes > 0) - { - return StopCheckInColor; - } - if (DisplayMode == "Alternating_Row_Colors" && values.Length > 3 && values[3] is int rowNumber){ - return WaitingColorList[rowNumber % WaitingColorList.Count]; - } - return WaitingColor; + + // 正点/晚点 + if (values[2] is TimeSpan status2) + return GetStatusColor(status2, values.Length > 3 && values[3] is int row ? row : -1); } + return new SolidColorBrush(Colors.Transparent); } - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + private SolidColorBrush GetStatusColor(TimeSpan status, int rowNumber = -1) { - // 通常不需要实现 ConvertBack - return null; + if (status == TimeSpan.Zero) + { + if (DisplayMode == "Alternating_Row_Colors" && rowNumber >= 0 && WaitingColorList.Count > 0) + return WaitingColorList[rowNumber % WaitingColorList.Count]; + return WaitingColor; // 正点 + } + else + { + return StopCheckInColor; // 晚点 + } } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => null; } } diff --git a/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs b/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs index 6513ef3..a2bb742 100644 --- a/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs +++ b/CRSim.ScreenSimulator/Converters/TrainStateConverter.cs @@ -16,48 +16,65 @@ namespace CRSim.ScreenSimulator.Converters public string WaitingText { get; set; } = "候车"; public string CheckInText { get; set; } = "正在检票"; public string StopCheckInText { get; set; } = "停止检票"; - object IMultiValueConverter.Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) + public string SuspendText { get; set; } = "列车停运"; + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - var serviceProvider = StyleManager.ServiceProvider; - _settings = serviceProvider.GetRequiredService().GetSettings(); - if (DisplayMode == "Arrive" || ( values.Length > 1 && values[1] == null)) + _settings ??= StyleManager.ServiceProvider + .GetRequiredService() + .GetSettings(); + + var now = TimeService.GetDateTimeNow(); + + // 到达模式或关键数据缺失 + if (DisplayMode == "Arrive" || (values.Length > 1 && values[1] == null)) { if (values[0] is DateTime arriveTime) - { - return TimeService.GetDateTimeNow() >= arriveTime ? ArrivedText : ArrivingText; - } + return now >= arriveTime ? ArrivedText : ArrivingText; return string.Empty; } - if (values[1] is DateTime departureTime && departureTime != new DateTime()) + + // 停运列车 + if (values[2] is null) + return SuspendText; + + // 有出发时间 + if (values[1] is DateTime departureTime && departureTime != DateTime.MinValue) { - if (values[0] is DateTime) - { - //过路站 - if (TimeService.GetDateTimeNow() > departureTime.Subtract(_settings.PassingCheckInAdvanceDuration) && TimeService.GetDateTimeNow() < departureTime.Subtract(_settings.StopCheckInAdvanceDuration)) - { - return CheckInText; - } - } - else - { - //始发站 - if (TimeService.GetDateTimeNow() > departureTime.Subtract(_settings.DepartureCheckInAdvanceDuration) && TimeService.GetDateTimeNow() < departureTime.Subtract(_settings.StopCheckInAdvanceDuration)) - { - return CheckInText; - } - } - if (TimeService.GetDateTimeNow() > departureTime.Subtract(_settings.StopCheckInAdvanceDuration)) - { + bool isPassingStation = values[0] is DateTime; + var checkInDuration = isPassingStation ? _settings.PassingCheckInAdvanceDuration : _settings.DepartureCheckInAdvanceDuration; + + // 检票中 + if (now > departureTime - checkInDuration && now < departureTime - _settings.StopCheckInAdvanceDuration) + return CheckInText; + + // 停止检票 + if (now >= departureTime - _settings.StopCheckInAdvanceDuration) return StopCheckInText; - } - if (values[2] is TimeSpan state && state.TotalMinutes > 0) + + // 晚点 / 正点 + if (values[2] is TimeSpan state) { - return $"预计开点{departureTime.Add(state):HH:mm}";//晚点列车 + if (state.TotalMinutes > 0) + return $"晚点约{ToHourMinuteString(state)}"; // 晚点 + return WaitingText; // 正点 } - return WaitingText; } + return string.Empty; } + public static string ToHourMinuteString(TimeSpan ts) + { + int totalHours = (int)ts.TotalHours; // 可以超过24 + int minutes = ts.Minutes; + + if (totalHours > 0 && minutes > 0) + return $"{totalHours}小时{minutes}分钟"; + if (totalHours > 0) + return $"{totalHours}小时"; + if (minutes > 0) + return $"{minutes}分钟"; + return "0分钟"; // 特殊情况,完全为0 + } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { diff --git a/CRSim.ScreenSimulator/Models/TrainInfo.cs b/CRSim.ScreenSimulator/Models/TrainInfo.cs index 1d01f87..6283130 100644 --- a/CRSim.ScreenSimulator/Models/TrainInfo.cs +++ b/CRSim.ScreenSimulator/Models/TrainInfo.cs @@ -5,12 +5,34 @@ public string TrainNumber { get; set; } public string Origin { get; set; } public string Terminal { get; set; } - public DateTime? ArrivalTime { get; set; } - public DateTime? DepartureTime { get; set; } + private DateTime? arrivalTime { get; set; } + private DateTime? departureTime { get; set; } + public DateTime? ArrivalTime + { + get + { + return State is null ? arrivalTime : arrivalTime + State; + } + set + { + arrivalTime = value; + } + } + public DateTime? DepartureTime + { + get + { + return State is null ? departureTime : departureTime + State; + } + set + { + departureTime = value; + } + } public List TicketChecks { get; set; } public string WaitingArea { get; set; } public string Platform { get; set; } - public TimeSpan State { get; set; } + public TimeSpan? State { get; set; } public string? Landmark { get; set; } public int Length { get; set; } } diff --git a/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs b/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs index 5a6ee1d..abc3803 100644 --- a/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs +++ b/CRSim.ScreenSimulator/ViewModels/BaseScreenViewModel.cs @@ -147,7 +147,7 @@ namespace CRSim.ScreenSimulator.ViewModels Platform = trainNumber.Platform, Length = trainNumber.Length, Landmark = trainNumber.Landmark, - State = TimeSpan.Zero + State = trainNumber.Status }); } diff --git a/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs b/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs index b0db7f8..d420efd 100644 --- a/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs +++ b/CRSim.ScreenSimulator/ViewModels/MetroPlatformScreenViewModel.cs @@ -66,7 +66,7 @@ namespace CRSim.ScreenSimulator.ViewModels Terminal = trainNumber.Terminal, ArrivalTime = AdjustTime(trainNumber.ArrivalTime), DepartureTime = AdjustTime(trainNumber.DepartureTime), - State = TimeSpan.Zero + State = trainNumber.Status }); } } diff --git a/CRSim/Converters/TimeSpanToStringConverter.cs b/CRSim/Converters/TimeSpanToStringConverter.cs index 77b4438..a28c49c 100644 --- a/CRSim/Converters/TimeSpanToStringConverter.cs +++ b/CRSim/Converters/TimeSpanToStringConverter.cs @@ -8,13 +8,23 @@ namespace CRSim.Converters public string Format { get; set; } = @"hh\:mm"; public string Culture { get; set; } = "zh-CN"; + public string NullString { get; set; } = string.Empty; + public string ZeroString { get; set; } = string.Empty; public object Convert(object value, Type targetType, object parameter, string language) { - if (value is TimeSpan dateTime && dateTime != new TimeSpan()) + if (value is TimeSpan dateTime) { + if(dateTime == new TimeSpan()) + { + return ZeroString; + } var CultureInfo = new CultureInfo(Culture); return (dateTime-dateTime.Days*TimeSpan.FromHours(24)).ToString(Format,CultureInfo); } + if(value is null) + { + return NullString; + } return string.Empty; } public object ConvertBack(object value, Type targetType, object parameter, string language) diff --git a/CRSim/ViewModels/StationManagementPageViewModel.cs b/CRSim/ViewModels/StationManagementPageViewModel.cs index dddca9c..cc20eaf 100644 --- a/CRSim/ViewModels/StationManagementPageViewModel.cs +++ b/CRSim/ViewModels/StationManagementPageViewModel.cs @@ -575,7 +575,13 @@ public partial class StationManagementPageViewModel : ObservableObject Platform = worksheet.Cells[row, 6].Text.Trim(), Landmark = worksheet.Cells[row, 7].Text.Trim() == "" ? "无" : worksheet.Cells[row, 8].Text.Trim(), Origin = worksheet.Cells[row, 8].Text.Trim(), - Terminal = worksheet.Cells[row, 9].Text.Trim() + Terminal = worksheet.Cells[row, 9].Text.Trim(), + Status = worksheet.Cells[row, 10].Text switch + { + "停运" => null, + "正点" => TimeSpan.Zero, + var t => TimeSpan.TryParseExact(t, @"hh\:mm", null, out TimeSpan ts) ? ts : TimeSpan.Zero + } }); } } @@ -602,6 +608,7 @@ public partial class StationManagementPageViewModel : ObservableObject worksheet.Cells[1, 7].Value = "地标"; worksheet.Cells[1, 8].Value = "始发站"; worksheet.Cells[1, 9].Value = "终到站"; + worksheet.Cells[1, 10].Value = "终到站"; for (int i = 0; i < TrainStops.Count; i++) { worksheet.Cells[i + 2, 1].Value = TrainStops[i].Number; @@ -613,6 +620,12 @@ public partial class StationManagementPageViewModel : ObservableObject worksheet.Cells[i + 2, 7].Value = TrainStops[i].Landmark; worksheet.Cells[i + 2, 8].Value = TrainStops[i].Origin; worksheet.Cells[i + 2, 9].Value = TrainStops[i].Terminal; + worksheet.Cells[i + 2, 10].Value = TrainStops[i].Status switch + { + null => "停运", + var ts when ts == TimeSpan.Zero => "正点", + var ts => ts.Value.ToString(@"hh\:mm") + }; } await package.SaveAsAsync(new FileInfo(path)); } diff --git a/CRSim/Views/DialogContents/TrainStopDialog.xaml b/CRSim/Views/DialogContents/TrainStopDialog.xaml index c771c82..cc90a7c 100644 --- a/CRSim/Views/DialogContents/TrainStopDialog.xaml +++ b/CRSim/Views/DialogContents/TrainStopDialog.xaml @@ -36,6 +36,7 @@ + @@ -43,6 +44,8 @@ + + diff --git a/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs b/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs index 3a1c330..2532489 100644 --- a/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs +++ b/CRSim/Views/DialogContents/TrainStopDialog.xaml.cs @@ -61,6 +61,14 @@ public sealed partial class TrainStopDialog : Page EndMinute.IsEnabled = false; StationKindPanelRadioButtons.SelectedIndex = 2; } + if (trainStop.Status is null) + { + SuspendToggleSwitch.IsOn = true; + } + else + { + StatusTextBox.Text = trainStop.Status.Value.TotalMinutes.ToString(); + } if (trainStop.TicketCheckIds != null) { foreach (var id in trainStop.TicketCheckIds) @@ -81,6 +89,7 @@ public sealed partial class TrainStopDialog : Page } private void Validate(object sender, object e) { + if (StartHour is null) return; bool areTextBoxesFilled = (!StartHour.IsEnabled || ValidateTime(StartHour.Text, 24)) && (!StartMinute.IsEnabled || ValidateTime(StartMinute.Text, 60)) && @@ -94,7 +103,8 @@ public sealed partial class TrainStopDialog : Page !string.IsNullOrWhiteSpace(ArrivalTextBox.Text) && !string.IsNullOrWhiteSpace(DepartureTextBox.Text) && int.TryParse(LengthTextBox.Text, out int i) && i > 0 && - PlatformComboBox.SelectedItem != null; + PlatformComboBox.SelectedItem != null && + int.TryParse(StatusTextBox.Text, out int m) && m >= 0; _onValidityChanged?.Invoke(isValid); } @@ -130,6 +140,7 @@ public sealed partial class TrainStopDialog : Page Platform = (string)PlatformComboBox.SelectedItem, Length = int.Parse(LengthTextBox.Text), Landmark = (string)LandmarkComboBox.SelectedItem == "" ? null : (string)LandmarkComboBox.SelectedItem, + Status = SuspendToggleSwitch.IsOn ? null : TimeSpan.FromMinutes(int.Parse(StatusTextBox.Text)) }; } diff --git a/CRSim/Views/StationManagementPage.xaml b/CRSim/Views/StationManagementPage.xaml index d5239ff..41f0756 100644 --- a/CRSim/Views/StationManagementPage.xaml +++ b/CRSim/Views/StationManagementPage.xaml @@ -18,7 +18,8 @@ Loaded="Page_Loaded" d:DataContext="{d:DesignInstance Type=viewmodels:StationManagementPageViewModel}"> - + + @@ -166,6 +167,7 @@ + @@ -181,6 +183,7 @@ + @@ -191,6 +194,7 @@ + @@ -206,6 +210,7 @@ + diff --git a/Directory.Build.props b/Directory.Build.props index 49591e6..161a921 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 2.3.108.0 + 2.3.109.0 电排骨 https://github.com/denglihong2007/CRSim