diff --git a/CRSim.Core/Abstractions/IApi.cs b/CRSim.Core/Abstractions/IApi.cs new file mode 100644 index 0000000..cf43460 --- /dev/null +++ b/CRSim.Core/Abstractions/IApi.cs @@ -0,0 +1,9 @@ +namespace CRSim.Core.Abstractions +{ + public interface IApi + { + string Name { get; } + string BaseApi { get; } + string UpdateApi { get; } + } +} diff --git a/CRSim.Core/Abstractions/IApiFactory.cs b/CRSim.Core/Abstractions/IApiFactory.cs new file mode 100644 index 0000000..2917389 --- /dev/null +++ b/CRSim.Core/Abstractions/IApiFactory.cs @@ -0,0 +1,15 @@ +namespace CRSim.Core.Abstractions +{ + /// + /// 定义 API 客户端工厂的合约,用于创建 IApi 实例。 + /// + public interface IApiFactory + { + /// + /// 根据客户端类型名称和基础 URL 创建 IApi 实例。 + /// + /// 客户端的唯一名称。 + /// 返回具体的 IApi 实现。 + IApi CreateApi(string clientName); + } +} diff --git a/CRSim.Core/Models/Settings.cs b/CRSim.Core/Models/Settings.cs index 4b4b8ef..2156207 100644 --- a/CRSim.Core/Models/Settings.cs +++ b/CRSim.Core/Models/Settings.cs @@ -1,5 +1,5 @@ -using CRSim.Core.Converters; -using System.Text.Json.Serialization; +using CRSim.Core.Abstractions; +using CRSim.Core.Services; namespace CRSim.Core.Models { @@ -18,8 +18,9 @@ namespace CRSim.Core.Models /// 直到终到后多久停止显示 /// public TimeSpan StopDisplayFromArrivalDuration { get; set; } = TimeSpan.FromMinutes(10); + public TimeSpan StopCheckInAdvanceDuration { get; set; } = TimeSpan.FromMinutes(2); - public string ApiUri { get; set; } = "http://47.122.74.193:25565"; + public IApi Api { get; set; } = new ApiFactory().CreateApi("镜像站"); public int MaxPages { get; set; } = 3; public int SwitchPageSeconds { get; set; } = 20; public string UserKey { get; set; } = ""; diff --git a/CRSim.Core/Services/ApiFactory.cs b/CRSim.Core/Services/ApiFactory.cs new file mode 100644 index 0000000..c41ea78 --- /dev/null +++ b/CRSim.Core/Services/ApiFactory.cs @@ -0,0 +1,34 @@ +using CRSim.Core.Abstractions; + +namespace CRSim.Core.Services +{ + /// + /// IApiFactory 的具体实现,负责根据配置实例化正确的 IApi 客户端。 + /// + public class ApiFactory : IApiFactory + { + public IApi CreateApi(string clientName) + { + clientName ??= string.Empty; + return clientName switch + { + "镜像站" => new MirrorApi(), + "官方站" => new OfficialApi(), + _ => new OfficialApi() + }; + } + } + public class OfficialApi : IApi + { + public string Name => "官方站"; + public string BaseApi => "https://47.122.74.193:25565/"; + public string UpdateApi => "https://api.github.com/repos/denglihong2007/CRSim/releases/latest"; + } + + public class MirrorApi : IApi + { + public string Name => "镜像站"; + public string BaseApi => "https://crsim.com.cn/api"; + public string UpdateApi => "https://crsim.com.cn/api/version"; + } +} diff --git a/CRSim.Core/Services/PluginService.cs b/CRSim.Core/Services/PluginService.cs index 15e834c..c0f1e60 100644 --- a/CRSim.Core/Services/PluginService.cs +++ b/CRSim.Core/Services/PluginService.cs @@ -13,12 +13,12 @@ using Downloader; namespace CRSim.Core.Services; -public class PluginService : IPluginService +public class PluginService(INetworkService networkService, ISettingsService settingsService) : IPluginService { public static readonly string PluginManifestFileName = "manifest.json"; public static readonly string StyleInfoFileName = "style.json"; - private string IndexUrl => $"{_settings.ApiUri}/GetFile?fileName=plugins.json"; + private string IndexUrl => $"{_settings.Api.BaseApi}/GetFile?fileName=plugins.json"; public static void InitializePlugins(HostBuilderContext context, IServiceCollection services,string externalPluginPath) { @@ -121,16 +121,11 @@ public class PluginService : IPluginService } } } - private readonly Models.Settings _settings; - private readonly INetworkService _networkService; - public PluginService(INetworkService networkService,ISettingsService settingsService) - { - _networkService = networkService; - _settings = settingsService.GetSettings(); - } + private readonly Settings _settings = settingsService.GetSettings(); + public async Task LoadOnlinePluginsAsync() { - var pluginManifests = await _networkService.GetOnlinePluginsAsync(IndexUrl); + var pluginManifests = await networkService.GetOnlinePluginsAsync(IndexUrl); IPluginService.OnlinePluginsInternal.Clear(); foreach (var Manifest in pluginManifests ?? []) { @@ -138,7 +133,7 @@ public class PluginService : IPluginService var info = new PluginInfo { Manifest = Manifest, - RealIconPath = $"{_settings.ApiUri}/GetFile?fileName=icons/{Manifest.Id}.png", + RealIconPath = $"{_settings.Api.BaseApi}/GetFile?fileName=icons/{Manifest.Id}.png", LoadStatus = localInfo?.LoadStatus ?? PluginLoadStatus.NotLoaded, PluginFolderPath = localInfo?.PluginFolderPath ?? string.Empty, }; @@ -150,7 +145,7 @@ public class PluginService : IPluginService public async Task InstallPluginOnlineAsync(PluginInfo plugin) { var id = plugin.Manifest.Id; - var packageUrl = $"{_settings.ApiUri}/GetFile?fileName=plugins/{id}{IPluginService.PluginPackageExtension}"; + var packageUrl = $"{_settings.Api.BaseApi}/GetFile?fileName=plugins/{id}{IPluginService.PluginPackageExtension}"; var tempDir = Path.Combine(AppPaths.TempPath, "Plugins", Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); var packagePath = Path.Combine(tempDir, $"{id}{IPluginService.PluginPackageExtension}"); diff --git a/CRSim.Core/Services/SettingsService.cs b/CRSim.Core/Services/SettingsService.cs index 385ff77..d08dae6 100644 --- a/CRSim.Core/Services/SettingsService.cs +++ b/CRSim.Core/Services/SettingsService.cs @@ -1,7 +1,6 @@ using CRSim.Core.Abstractions; using CRSim.Core.Models; using Microsoft.Win32; -using System.Text.Json; namespace CRSim.Core.Services { @@ -12,7 +11,7 @@ namespace CRSim.Core.Services public void SaveSettings() { _key.SetValue("SwitchPageSeconds", _settings.SwitchPageSeconds); - _key.SetValue("ApiUri", _settings.ApiUri); + _key.SetValue("Api", _settings.Api.Name); _key.SetValue("MaxPages", _settings.MaxPages); _key.SetValue("StopCheckInAdvanceDuration", (int)_settings.StopCheckInAdvanceDuration.TotalMinutes); _key.SetValue("StopDisplayUntilDepartureDuration", (int)_settings.StopDisplayUntilDepartureDuration.TotalMinutes); @@ -36,7 +35,7 @@ namespace CRSim.Core.Services { _settings = new Settings(); if (_key.GetValue("SwitchPageSeconds") != null) _settings.SwitchPageSeconds = (int)_key.GetValue("SwitchPageSeconds"); - if (_key.GetValue("ApiUri") != null) _settings.ApiUri = (string)_key.GetValue("ApiUri"); + _settings.Api = new ApiFactory().CreateApi((string)_key.GetValue("Api")); if (_key.GetValue("MaxPages") != null) _settings.MaxPages = (int)_key.GetValue("MaxPages"); if (_key.GetValue("StopCheckInAdvanceDuration") != null) _settings.StopCheckInAdvanceDuration = TimeSpan.FromMinutes((int)_key.GetValue("StopCheckInAdvanceDuration")); if (_key.GetValue("StopDisplayUntilDepartureDuration") != null) _settings.StopDisplayUntilDepartureDuration = TimeSpan.FromMinutes((int)_key.GetValue("StopDisplayUntilDepartureDuration")); diff --git a/CRSim.PlatformDiagram/PlatformDiagram.cs b/CRSim.PlatformDiagram/PlatformDiagram.cs index 463c888..e53c393 100644 --- a/CRSim.PlatformDiagram/PlatformDiagram.cs +++ b/CRSim.PlatformDiagram/PlatformDiagram.cs @@ -8,14 +8,14 @@ using iText.Kernel.Pdf.Canvas; using iText.Layout; using iText.Layout.Element; using iText.Layout.Properties; +using System.Reflection; namespace CRSim.PlatformDiagram { public class Generator { - public static void Generate(Station station, string savePath) - { - float PageWidth = 5000; + public static void Generate(Station station, string savePath, float pageWidth) + { float Padding = 40; float HeaderHeight = 16; float PlatformTableVerticalMargin = 9; @@ -31,7 +31,7 @@ namespace CRSim.PlatformDiagram int platformCount = station.Platforms.Count; float pageHeight = (Padding + TimelineHeight + PlatformTableVerticalMargin) * 2 + platformCount * PlatformHeight + HeaderHeight; - Document doc = new(pdf, new PageSize(PageWidth, pageHeight)); doc.SetMargins(Padding, Padding, Padding, Padding); + Document doc = new(pdf, new PageSize(pageWidth, pageHeight)); doc.SetMargins(Padding, Padding, Padding, Padding); var canvas = new PdfCanvas(pdf.AddNewPage()); TimeSpan startTime = TimeSpan.FromHours(0); TimeSpan endTime = TimeSpan.FromHours(24); @@ -44,7 +44,7 @@ namespace CRSim.PlatformDiagram // 绘制整条横线 canvas.SetLineWidth(1f); var xStart = Padding + InfoBarWidth + PlatformTableHorizontalMargin - TimelineExtLength; - var xEnd = PageWidth - Padding; + var xEnd = pageWidth - Padding; var yTop = pageHeight - Padding - HeaderHeight - TimelineHeight; var yBottom = Padding + TimelineHeight; canvas.MoveTo(xStart, yTop).LineTo(xEnd, yTop).Stroke(); @@ -54,7 +54,7 @@ namespace CRSim.PlatformDiagram var xBase = Padding + InfoBarWidth + PlatformTableHorizontalMargin; yTop = pageHeight - Padding - HeaderHeight - TimelineHeight - PlatformTableVerticalMargin; yBottom = Padding + TimelineHeight + PlatformTableVerticalMargin; - var PlatformTableLength = PageWidth - Padding * 2 - InfoBarWidth - PlatformTableHorizontalMargin - TimelineExtLength; + var PlatformTableLength = pageWidth - Padding * 2 - InfoBarWidth - PlatformTableHorizontalMargin - TimelineExtLength; for (TimeSpan t = startTime; t <= endTime; t += TimeSpan.FromMinutes(intervalMin)) { float x = xBase + MapTime(t, startTime, endTime, PlatformTableLength); @@ -221,7 +221,19 @@ namespace CRSim.PlatformDiagram .SetFontSize(18) .SetFontColor(ColorConstants.BLUE) .SetMargins(0,0,0,0)); - foreach(var kvp in platformHeights) + + string text = $"使用 CRSim v{Assembly.GetExecutingAssembly().GetName().Version} 绘制"; + float fontSize = 12f; + var para = new Paragraph(text) + .SetFont(font) + .SetFontSize(fontSize) + .SetFontColor(ColorConstants.BLUE) + .SetMargins(0, 0, 0, 0); + rect1 = new(pageWidth - Padding - font.GetWidth(text, fontSize), pageHeight - Padding, 500, HeaderHeight); + layoutCanvas = new Canvas(canvas, rect1); + layoutCanvas.Add(para); + + foreach (var kvp in platformHeights) { rect1 = new(Padding, kvp.Value + PlatformHeight / 2 - 10, InfoBarWidth, 20); layoutCanvas = new Canvas(canvas, rect1); diff --git a/CRSim/ViewModels/DashboardPageViewModel.cs b/CRSim/ViewModels/DashboardPageViewModel.cs index 5410ba3..bb46e97 100644 --- a/CRSim/ViewModels/DashboardPageViewModel.cs +++ b/CRSim/ViewModels/DashboardPageViewModel.cs @@ -1,6 +1,4 @@ -using System.Windows; - -namespace CRSim.ViewModels; +namespace CRSim.ViewModels; public partial class DashboardPageViewModel : ObservableObject { [ObservableProperty] @@ -17,12 +15,7 @@ public partial class DashboardPageViewModel : ObservableObject } private async void InitializeAsync() { - var url = _settingsService.GetSettings().ApiUri.Contains("47") - ? "https://api.github.com/repos/denglihong2007/CRSim/releases/latest" - : "https://crsim.com.cn/api/version"; - - var updateInfo = await _networkService.GetUpdateAsync(url); - + var updateInfo = await _networkService.GetUpdateAsync(_settingsService.GetSettings().Api.UpdateApi); if (updateInfo is not null && updateInfo.Name != Assembly.GetExecutingAssembly().GetName().Version.ToString()) { UpdateMessage = $"有新版本 {updateInfo.Name} 可用,请前往“设置”下载安装更新!"; diff --git a/CRSim/ViewModels/PlatformDiagramPageViewModel.cs b/CRSim/ViewModels/PlatformDiagramPageViewModel.cs index bed237d..2ef27f7 100644 --- a/CRSim/ViewModels/PlatformDiagramPageViewModel.cs +++ b/CRSim/ViewModels/PlatformDiagramPageViewModel.cs @@ -9,7 +9,10 @@ public partial class PlatformDiagramPageViewModel(IDialogService _dialogService, public List Stations => _databaseService.GetAllStations(); [ObservableProperty] - public partial bool IsSelected { get; set; } = false; + public partial bool Validated { get; set; } = false; + + [ObservableProperty] + public partial int PageWidth { get; set; } = 5000; [RelayCommand] public void StationSelected(object s) @@ -17,10 +20,14 @@ public partial class PlatformDiagramPageViewModel(IDialogService _dialogService, if(s is Station station) { SelectedStation = station; - IsSelected = true; } + Validate(); + } + [RelayCommand] + public void Validate() + { + Validated = SelectedStation != null && PageWidth > 0; } - [RelayCommand] public async Task Generate() { @@ -30,7 +37,7 @@ public partial class PlatformDiagramPageViewModel(IDialogService _dialogService, } var path = _dialogService.SaveFile(".pdf", $"{SelectedStation.Name}站台占用图"); if(string.IsNullOrEmpty(path) || SelectedStation == null) return; - await Task.Run(() => Generator.Generate(SelectedStation, path)); + await Task.Run(() => Generator.Generate(SelectedStation, path,PageWidth)); } public static bool CheckStation(Station station,out string detail) diff --git a/CRSim/ViewModels/SettingsPageViewModel.cs b/CRSim/ViewModels/SettingsPageViewModel.cs index 1700494..667d0f6 100644 --- a/CRSim/ViewModels/SettingsPageViewModel.cs +++ b/CRSim/ViewModels/SettingsPageViewModel.cs @@ -13,10 +13,10 @@ namespace CRSim.ViewModels [ObservableProperty] public partial string AppVersion { get; set; } = ""; - public ObservableCollection Apis { get; } = + public ObservableCollection Apis { get; } = [ - new InfoItem { Title = "官方源", Detail = "http://47.122.74.193:25565" }, - new InfoItem { Title = "镜像站源", Detail = "https://crsim.com.cn/api" }, + new ApiFactory().CreateApi("官方站"), + new ApiFactory().CreateApi("镜像站"), ]; private Settings _settings; private readonly ISettingsService _settingsService; @@ -50,7 +50,7 @@ namespace CRSim.ViewModels public partial string SwitchPageSeconds { get; set; } [ObservableProperty] - public partial InfoItem ApiUri { get; set; } + public partial IApi Api { get; set; } [ObservableProperty] public partial string UserKey { get; set; } @@ -81,7 +81,7 @@ namespace CRSim.ViewModels StopDisplayUntilDepartureDuration = _settings.StopDisplayUntilDepartureDuration.TotalMinutes.ToString(); StopDisplayFromArrivalDuration = _settings.StopDisplayFromArrivalDuration.TotalMinutes.ToString(); StopCheckInAdvanceDuration = _settings.StopCheckInAdvanceDuration.TotalMinutes.ToString(); - ApiUri = Apis.Where(x => x.Detail == _settings.ApiUri).FirstOrDefault(); + Api = Apis.First(x => x.Name == _settings.Api.Name); MaxPages = _settings.MaxPages.ToString(); SwitchPageSeconds = _settings.SwitchPageSeconds.ToString(); UserKey = _settings.UserKey; @@ -102,7 +102,7 @@ namespace CRSim.ViewModels _settings.UserKey = UserKey; _settings.LoadTodayOnly = LoadTodayOnly; _settings.ReopenUnclosedScreensOnLoad = ReopenUnclosedScreensOnLoad; - _settings.ApiUri = ApiUri.Detail; + _settings.Api = Api; _settingsService.SaveSettings(); } private static void UpdateSettings(string input, bool allowNegative, Action updateAction) @@ -123,9 +123,7 @@ namespace CRSim.ViewModels [RelayCommand] public async Task CheckUpdate() { - var update = ApiUri.Title == "官方源" ? - await _networkService.GetUpdateAsync("https://api.github.com/repos/denglihong2007/CRSim/releases/latest") : - await _networkService.GetUpdateAsync("https://crsim.com.cn/api/version"); + var update = await _networkService.GetUpdateAsync(Api.UpdateApi); if (update is null) { await _dialogService.ShowMessageAsync("错误", "检查更新失败。"); @@ -169,7 +167,7 @@ namespace CRSim.ViewModels } var programDirectory = AppDomain.CurrentDomain.BaseDirectory; - var appExePath = Process.GetCurrentProcess().MainModule.FileName; + var appExePath = Environment.ProcessPath; var appName = Path.GetFileName(appExePath); string batchScript = $@" @echo off @@ -197,7 +195,7 @@ namespace CRSim.ViewModels } catch (Exception e) { - await _dialogService.ShowTextAsync("错误", "更新失败。\n" + e); + await _dialogService.ShowTextAsync("错误", "更新失败。若使用“官方站”,可尝试更换到“镜像站”。\n" + e); UpdateProgress = 0; return; } diff --git a/CRSim/ViewModels/StationManagementPageViewModel.cs b/CRSim/ViewModels/StationManagementPageViewModel.cs index dd58f13..8eb3c0b 100644 --- a/CRSim/ViewModels/StationManagementPageViewModel.cs +++ b/CRSim/ViewModels/StationManagementPageViewModel.cs @@ -1,4 +1,5 @@ using CRSim.Converters; +using CRSim.Core.Models.Plugin; using System.Text.RegularExpressions; namespace CRSim.ViewModels; @@ -11,10 +12,14 @@ public partial class StationManagementPageViewModel : ObservableObject public partial string PageTitle { get; set; } = "车站管理"; [ObservableProperty] - public partial bool IsSelected { get; set; } = false; + public partial string SearchText { get; set; } = ""; [ObservableProperty] - public partial List StationNames { get; set; } = []; + public partial bool IsSelected { get; set; } = false; + + public List StationNames { get; set; } = []; + + public List FilteredStationNames => [.. StationNames.Where(x => x.Contains(SearchText))]; [ObservableProperty] public partial Station SelectedStation { get; set; } = new(); @@ -74,6 +79,7 @@ public partial class StationManagementPageViewModel : ObservableObject { var stationsList = _databaseService.GetAllStations(); StationNames = [.. stationsList.Select(s => s.Name)]; + OnPropertyChanged(nameof(FilteredStationNames)); } [RelayCommand] public void StationSelected(object args) @@ -826,4 +832,9 @@ public partial class StationManagementPageViewModel : ObservableObject return false; } + [RelayCommand] + public void Search() + { + OnPropertyChanged(nameof(FilteredStationNames)); + } } \ No newline at end of file diff --git a/CRSim/ViewModels/TrainNumberManagementPageViewModel.cs b/CRSim/ViewModels/TrainNumberManagementPageViewModel.cs index e647a1b..78ebfaf 100644 --- a/CRSim/ViewModels/TrainNumberManagementPageViewModel.cs +++ b/CRSim/ViewModels/TrainNumberManagementPageViewModel.cs @@ -5,9 +5,15 @@ public partial class TrainNumberManagementPageViewModel : ObservableObject [ObservableProperty] public partial string PageTitle { get; set; } = "车次管理"; + [ObservableProperty] + public partial string SearchText { get; set; } = ""; + [ObservableProperty] public partial bool IsSelected { get; set; } = false; - public ObservableCollection TrainNumbers { get; set; } = []; + + public List TrainNumbers { get; set; } = []; + + public List FilteredTrainNumbers => [.. TrainNumbers.Where(x => x.Number.Contains(SearchText))]; [ObservableProperty] public partial TrainNumber SelectedTrainNumber { get; set; } = new(); @@ -72,6 +78,7 @@ public partial class TrainNumberManagementPageViewModel : ObservableObject { TrainNumbers.Add(t); } + OnPropertyChanged(nameof(FilteredTrainNumbers)); } [RelayCommand] @@ -827,4 +834,10 @@ public partial class TrainNumberManagementPageViewModel : ObservableObject return TimeSpan.FromMinutes(Math.Round(time.TotalMinutes)); return default; } + + [RelayCommand] + public void Search() + { + OnPropertyChanged(nameof(FilteredTrainNumbers)); + } } \ No newline at end of file diff --git a/CRSim/Views/PlatformDiagramPage.xaml b/CRSim/Views/PlatformDiagramPage.xaml index a99b91b..d8160c5 100644 --- a/CRSim/Views/PlatformDiagramPage.xaml +++ b/CRSim/Views/PlatformDiagramPage.xaml @@ -22,18 +22,30 @@ Style="{StaticResource TitleTextBlockStyle}"/> + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - -