feat: 自定义站台占用图车次颜色

This commit is contained in:
denglihong2007
2026-01-01 18:59:24 +08:00
parent a25a047f14
commit 8e6f70a67d
31 changed files with 290 additions and 82 deletions

View File

@@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Downloader" Version="4.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,22 @@
using System.Drawing;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CRSim.Core.Converters
{
public class ColorJsonConverter : JsonConverter<Color>
{
public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// 从十六进制字符串(如 "#FF00BE")读取并转换回 Color
string? hex = reader.GetString();
return hex == null ? Color.Empty : ColorTranslator.FromHtml(hex);
}
public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
{
// 将 Color 转换为十六进制字符串格式写入 JSON
if (value.IsEmpty) writer.WriteNullValue();
else writer.WriteStringValue($"#{value.R:X2}{value.G:X2}{value.B:X2}");
}
}
}

View File

@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CRSim.Core.Models
namespace CRSim.Core.Models
{
public class Json
{

View File

@@ -0,0 +1,14 @@
using CRSim.Core.Converters;
using System.Drawing;
using System.Text.Json.Serialization;
namespace CRSim.Core.Models.PlatformDiagram
{
public class TrainColor
{
public required string Prefix { get; set; }
[JsonConverter(typeof(ColorJsonConverter))]
public required Color Color { get; set; }
}
}

View File

@@ -1,5 +1,8 @@
using CRSim.Core.Abstractions;
using CRSim.Core.Converters;
using CRSim.Core.Models.PlatformDiagram;
using CRSim.Core.Services;
using System.Drawing;
using System.Text.Json.Serialization;
namespace CRSim.Core.Models
@@ -29,5 +32,18 @@ namespace CRSim.Core.Models
public string UserKey { get; set; } = "";
public bool LoadTodayOnly { get; set; } = false;
public bool ReopenUnclosedScreensOnLoad { get; set; } = true;
public List<TrainColor> TrainColors { get; set; } =
[
new TrainColor { Prefix = "G", Color = Color.Magenta },
new TrainColor { Prefix = "D", Color = Color.SteelBlue },
new TrainColor { Prefix = "C", Color = Color.Teal },
new TrainColor { Prefix = "T", Color = Color.Blue },
new TrainColor { Prefix = "Z", Color = Color.SaddleBrown },
new TrainColor { Prefix = "K", Color = Color.Red },
new TrainColor { Prefix = "Y", Color = Color.Red },
new TrainColor { Prefix = "L", Color = Color.Green },
new TrainColor { Prefix = "默认", Color = Color.Green }
];
}
}

View File

@@ -1,11 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace CRSim.Core.Models
namespace CRSim.Core.Models
{
public class Station
{

View File

@@ -1,5 +1,6 @@
// Trigger CI build
using CRSim.Core.Abstractions;
using CRSim.Core.Models;
using CRSim.Core.Models.PlatformDiagram;
using iText.IO.Font;
using iText.Kernel.Colors;
using iText.Kernel.Font;
@@ -15,7 +16,7 @@ namespace CRSim.PlatformDiagram
{
public class Generator
{
public static void Generate(Station station, string savePath, float pageWidth)
public static void Generate(Station station, string savePath, float pageWidth,List<TrainColor> trainColors)
{
float Padding = 40;
float HeaderHeight = 16;
@@ -168,27 +169,14 @@ namespace CRSim.PlatformDiagram
TimeSpan arrival = (TimeSpan)(train.ArrivalTime ?? train.DepartureTime - fixedDuration);
TimeSpan departure = (TimeSpan)(train.DepartureTime ?? train.ArrivalTime + fixedDuration);
var trainColors = new Dictionary<string, string>
{
{ "G", "FF00BE" }, // pink
{ "D", "004C99" }, // deep blue
{ "C", "009999" }, // cyan
{ "T", "0000FF" }, // blue
{ "Z", "804000" }, // brown
{ "K", "FF0000" }, // red
{ "Y", "FF0000" }, // red
{ "L", "008000" }, // dark green
{ "default", "008000" } // dark green
};
string firstChar = train.Number[..1].ToUpper();
string colorHex = trainColors.TryGetValue(firstChar, out string? value) ? value : trainColors["default"];
var color = new DeviceRgb(
Convert.ToInt32(colorHex[..2], 16),
Convert.ToInt32(colorHex.Substring(2, 2), 16),
Convert.ToInt32(colorHex.Substring(4, 2), 16)
);
canvas.SetFillColor(color);
canvas.SetStrokeColor(color);
var matchedColor = trainColors
.Where(tc => train.Number.StartsWith(tc.Prefix))
.OrderByDescending(tc => tc.Prefix.Length)
.FirstOrDefault();
var color = matchedColor?.Color ?? trainColors.FirstOrDefault(x => x.Prefix == "默认").Color;
Color itextColor = new DeviceRgb(color.R, color.G, color.B);
canvas.SetFillColor(itextColor);
canvas.SetStrokeColor(itextColor);
// 检查是否跨零点
if (departure < arrival)

View File

@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageReference Include="PinYinConverterCore" Version="1.0.2" />
<PackageReference Include="PP.Wpf" Version="1.0.8" />
</ItemGroup>

View File

@@ -12,6 +12,7 @@
<EnableMsixTooling>true</EnableMsixTooling>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@@ -40,13 +41,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Animations" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Animations" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.ColorPicker" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.251219" />
<PackageReference Include="CRSim.WebsiteSimulator" Version="3.1.5" />
<PackageReference Include="EPPlus" Version="8.3.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="EPPlus" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
</ItemGroup>

View File

@@ -0,0 +1,24 @@
using Microsoft.UI.Xaml.Data;
namespace CRSim.Converters
{
public partial class DrawingToUIColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is System.Drawing.Color sColor)
{
return Windows.UI.Color.FromArgb(sColor.A, sColor.R, sColor.G, sColor.B);
}
return Windows.UI.Color.FromArgb(0,0,0,0);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Windows.UI.Color wColor)
{
return System.Drawing.Color.FromArgb(wColor.A, wColor.R, wColor.G, wColor.B);
}
return System.Drawing.Color.FromArgb(0, 0, 0, 0);
}
}
}

View File

@@ -28,6 +28,7 @@ global using CRSim.PlatformDiagram;
global using CRSim.WebsiteSimulator;
global using CRSim.Core.Utils;
global using CRSim.Core.Models;
global using CRSim.Core.Models.PlatformDiagram;
global using CRSim.Core.Services;
global using CRSim.Core.Abstractions;

View File

@@ -1,7 +1,5 @@
using CRSim.Core.Models;
using CRSim.Views.DialogContents;
using Microsoft.Win32;
using Windows.Storage;
using Windows.Storage.Pickers;
namespace CRSim.Services
{
@@ -275,5 +273,28 @@ namespace CRSim.Services
}
return null;
}
public async Task<List<TrainColor>?> EditTrainColorsAsync(List<TrainColor> trainColors)
{
ContentDialog dialog = new()
{
XamlRoot = XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
Title = "编辑颜色配置",
PrimaryButtonText = "确定",
CloseButtonText = "取消",
DefaultButton = ContentDialogButton.Primary,
IsPrimaryButtonEnabled = false
};
var trainColorsDialog = new TrainColorsDialog(trainColors, isValid => dialog.IsPrimaryButtonEnabled = isValid);
dialog.Content = trainColorsDialog;
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
var inputDialog = dialog.Content as TrainColorsDialog;
return [.. inputDialog.TrainColors];
}
return null;
}
}
}

View File

@@ -14,6 +14,7 @@
Task<TrainStop?> GetInputTrainStopAsync(List<WaitingArea> waitingAreas, List<string> platforms);
Task<TrainStop?> EditInputTrainStopAsync(List<WaitingArea> waitingAreas, List<string> platforms, TrainStop trainStop);
Task<List<Platform>?> GetInputPlatformAsync();
Task<List<TrainColor>?> EditTrainColorsAsync(List<TrainColor> trainColors);
XamlRoot? XamlRoot { get; set; }
}
}

View File

@@ -3,6 +3,8 @@
public partial class PlatformDiagramPageViewModel(IDialogService _dialogService, IDatabaseService _databaseService) : ObservableObject
{
public string PageTitle = "生成站台占用图";
private readonly ISettingsService _settingsService = App.GetService<ISettingsService>();
public Station? SelectedStation;
public List<Station> Stations => _databaseService.GetAllStations();
@@ -35,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,PageWidth));
await Task.Run(() => Generator.Generate(SelectedStation, path,PageWidth, _settingsService.GetSettings().TrainColors));
}
public static bool CheckStation(Station station,out string detail)

View File

@@ -1,10 +1,4 @@
using Downloader;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.IO.Packaging;
using System.Reflection;
using System.Security.Policy;
using Windows.System;
using Windows.System;
namespace CRSim.ViewModels
{
@@ -62,6 +56,10 @@ namespace CRSim.ViewModels
[ObservableProperty]
public partial bool ReopenUnclosedScreensOnLoad { get; set; }
[ObservableProperty]
public partial List<TrainColor> TrainColors { get; set; }
#endregion
public SettingsPageViewModel(ISettingsService settingsService, IDatabaseService databaseService, IDialogService dialogService, INetworkService networkService)
{
@@ -69,10 +67,8 @@ namespace CRSim.ViewModels
_databaseService = databaseService;
_dialogService = dialogService;
_networkService = networkService;
var version = Assembly.GetExecutingAssembly().GetName().Version;
AppVersion = $"Version {version}";
AppVersion = $"Version {App.AppVersion}";
LoadSettings();
}
private void LoadSettings()
@@ -89,6 +85,7 @@ namespace CRSim.ViewModels
UserKey = _settings.UserKey;
LoadTodayOnly = _settings.LoadTodayOnly;
ReopenUnclosedScreensOnLoad = _settings.ReopenUnclosedScreensOnLoad;
TrainColors = _settings.TrainColors;
}
[RelayCommand]
@@ -105,6 +102,7 @@ namespace CRSim.ViewModels
_settings.LoadTodayOnly = LoadTodayOnly;
_settings.ReopenUnclosedScreensOnLoad = ReopenUnclosedScreensOnLoad;
_settings.Api = Api;
_settings.TrainColors = TrainColors;
_settingsService.SaveSettings();
}
private static void UpdateSettings(string input, bool allowNegative, Action<int> updateAction)
@@ -155,5 +153,14 @@ namespace CRSim.ViewModels
_databaseService.ImportData(path);
await _databaseService.SaveData();
}
[RelayCommand]
public async Task EditTrainColors()
{
var trainColors = await _dialogService.EditTrainColorsAsync(TrainColors);
if (trainColors is not null)
{
TrainColors = trainColors;
}
}
}
}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="CRSim.Views.CreateStationDialog"
x:Class="CRSim.Views.DialogContents.CreateStationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CRSim.Views"
xmlns:local="using:CRSim.Views.DialogContents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

View File

@@ -1,4 +1,4 @@
namespace CRSim.Views;
namespace CRSim.Views.DialogContents;
public sealed partial class CreateStationDialog : Page
{

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="CRSim.Views.InputDialog"
x:Class="CRSim.Views.DialogContents.InputDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CRSim.Views"
xmlns:local="using:CRSim.Views.DialogContents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

View File

@@ -1,4 +1,4 @@
namespace CRSim.Views;
namespace CRSim.Views.DialogContents;
public sealed partial class InputDialog : Page
{
public string InputText { get; set; }

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="CRSim.Views.PlatformDialog"
x:Class="CRSim.Views.DialogContents.PlatformDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CRSim.Views"
xmlns:local="using:CRSim.Views.DialogContents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

View File

@@ -1,4 +1,4 @@
namespace CRSim.Views;
namespace CRSim.Views.DialogContents;
public sealed partial class PlatformDialog : Page
{
private readonly Action<bool> _onValidityChanged;

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="CRSim.Views.TextDialog"
x:Class="CRSim.Views.DialogContents.TextDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CRSim.Views"
xmlns:local="using:CRSim.Views.DialogContents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

View File

@@ -1,4 +1,4 @@
namespace CRSim.Views;
namespace CRSim.Views.DialogContents;
public sealed partial class TextDialog : Page
{
private string _text { get; set; }

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="CRSim.Views.DialogContents.TrainColorsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:local="using:CRSim.Views.DialogContents"
xmlns:converters="using:CRSim.Converters"
xmlns:models="using:CRSim.Core.Models.PlatformDiagram"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<converters:DrawingToUIColorConverter x:Key="DrawingToUIColorConverter"/>
</Page.Resources>
<StackPanel
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<CommandBar
DefaultLabelPosition="Right"
Margin="0,0,0,8"
HorizontalAlignment="Right">
<AppBarButton
Icon="Add"
Label="添加"
Click="Add"/>
<AppBarButton
Icon="Clear"
Label="删除"
Click="Delete"/>
<CommandBar.SecondaryCommands>
<AppBarButton
Label="恢复默认设置"
Click="Reset"/>
</CommandBar.SecondaryCommands>
</CommandBar>
<ScrollViewer MaxHeight="350">
<GridView
x:Name="TrainColorsGrid"
ItemsSource="{x:Bind TrainColors,Mode=OneWay}"
SelectionMode="Single"
FlowDirection="LeftToRight">
<GridView.ItemTemplate>
<DataTemplate x:DataType="models:TrainColor">
<Grid Padding="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox
Grid.Column="0"
Width="100"
Text="{x:Bind Prefix,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,8,0"
TextChanged="Validate"/>
<controls:ColorPickerButton
Grid.Column="2"
SelectedColor="{x:Bind Color,Mode=TwoWay,Converter={StaticResource DrawingToUIColorConverter}}"/>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</ScrollViewer>
</StackPanel>
</Page>

View File

@@ -0,0 +1,55 @@
using System.Drawing;
namespace CRSim.Views.DialogContents;
public sealed partial class TrainColorsDialog : Page
{
public ObservableCollection<TrainColor> TrainColors = [];
private readonly Action<bool> _onValidityChanged;
public TrainColorsDialog(List<TrainColor> trainColors, Action<bool> onValidityChanged)
{
TrainColors = new ObservableCollection<TrainColor>(trainColors);
InitializeComponent();
_onValidityChanged = onValidityChanged;
Validate(this, EventArgs.Empty);
}
private void Add(object sender, RoutedEventArgs e)
{
TrainColors.Add(new TrainColor() { Color = Color.Red, Prefix = string.Empty });
Validate(this, EventArgs.Empty);
}
private void Delete(object sender, RoutedEventArgs e)
{
if (TrainColorsGrid.SelectedValue is TrainColor trainColor && trainColor.Prefix != "默认")
{
TrainColors.Remove(trainColor);
}
Validate(this, EventArgs.Empty);
}
private void Reset(object sender, RoutedEventArgs e)
{
TrainColors.Clear();
foreach (var trainColor in new Settings().TrainColors)
{
TrainColors.Add(trainColor);
}
Validate(this, EventArgs.Empty);
}
private void Validate(object sender, object e)
{
if(TrainColorsGrid is null) { return; } //检查是否初始化完成
if (TrainColors.Any(x => x.Prefix == string.Empty) || TrainColors.GroupBy(x => x.Prefix).Any(g => g.Count() > 1) || !TrainColors.Any(x=>x.Prefix=="默认"))
{
_onValidityChanged(false);
}
else
{
_onValidityChanged(true);
}
}
}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="CRSim.Views.TrainNumberStopDialog"
x:Class="CRSim.Views.DialogContents.TrainNumberStopDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CRSim.Views"
xmlns:local="using:CRSim.Views.DialogContents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:coremodels="using:CRSim.Core.Models"

View File

@@ -1,6 +1,4 @@
using CRSim.Core.Models;
namespace CRSim.Views;
namespace CRSim.Views.DialogContents;
public sealed partial class TrainNumberStopDialog : Page
{
public TrainStop GeneratedTrainStop;

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="CRSim.Views.TrainStopDialog"
x:Class="CRSim.Views.DialogContents.TrainStopDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CRSim.Views"
xmlns:local="using:CRSim.Views.DialogContents"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:coremodels="using:CRSim.Core.Models"

View File

@@ -1,6 +1,4 @@
using CRSim.Core.Models;
namespace CRSim.Views;
namespace CRSim.Views.DialogContents;
public sealed partial class TrainStopDialog : Page
{
public TrainStop GeneratedTrainStop;

View File

@@ -22,6 +22,7 @@
Style="{StaticResource TitleTextBlockStyle}"/>
<ScrollViewer Grid.Row="1" Padding="0,0,36,0">
<StackPanel>
<InfoBar Severity="Informational" IsOpen="True" Title="使用技巧" Message="您可以在“设置”配置车次颜色!" Margin="0,0,0,12"/>
<TextBlock Text="参数配置" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Margin="0,0,0,12"/>
<StackPanel Spacing="4" Orientation="Vertical" Margin="0,0,0,24">
<controls:SettingsCard Header="车站" Description="选择目标车站。" HeaderIcon="{winui:FontIcon Glyph=&#xE825;}">

View File

@@ -85,6 +85,12 @@
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel>
<TextBlock Text="站台占用图" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Margin="0,0,0,12"/>
<StackPanel Spacing="4" Margin="0,0,0,24">
<controls:SettingsCard Header="编辑颜色配置" Description="以车次号前缀识别车次颜色" HeaderIcon="{winui:FontIcon Glyph=&#xE790;}">
<Button Style="{StaticResource AccentButtonStyle}" Content="编辑" Command="{x:Bind ViewModel.EditTrainColorsCommand}"/>
</controls:SettingsCard>
</StackPanel>
<TextBlock Text="备份与恢复" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Margin="0,0,0,12"/>
<StackPanel Spacing="4" Margin="0,0,0,24">
<controls:SettingsCard Header="导入数据文件" Description="从本地文件加载数据" HeaderIcon="{winui:FontIcon Glyph=&#xE8B6;}">