P膩rl奴kot izejas kodu

娣诲姞涓婚鍒囨崲(娣辫壊/娴呰壊)鍜岃瑷鍒囨崲(涓/鑻)

纾 鏇 1 ned膿募u atpaka募
vec膩ks
rev墨zija
53dc6c25d3

+ 1 - 0
src/YZWater.Avalonia/App.axaml

@@ -11,6 +11,7 @@
     <Application.Resources>
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
+                <!-- 榛樿鍔犺浇娣辫壊涓婚锛岃繍琛屾椂鍙垏鎹 -->
                 <ResourceInclude Source="Themes/IndustrialTheme.axaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>

+ 24 - 3
src/YZWater.Avalonia/App.axaml.cs

@@ -1,6 +1,7 @@
 using Avalonia;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Styling;
 using YZWater.Avalonia.Views;
 using YZWater.Core.Services;
 
@@ -8,17 +9,26 @@ namespace YZWater.Avalonia;
 
 public partial class App : Application
 {
+    private ResourceInclude? _darkTheme;
+    private ResourceInclude? _lightTheme;
+
     public override void Initialize()
     {
         AvaloniaXamlLoader.Load(this);
+
+        // 棰勫姞杞戒袱涓富棰樿祫婧
+        _darkTheme = new ResourceInclude(new Uri("avares://YZWater.Avalonia/Themes/IndustrialTheme.axaml"))
+            { Source = new Uri("avares://YZWater.Avalonia/Themes/IndustrialTheme.axaml") };
+        _lightTheme = new ResourceInclude(new Uri("avares://YZWater.Avalonia/Themes/IndustrialThemeLight.axaml"))
+            { Source = new Uri("avares://YZWater.Avalonia/Themes/IndustrialThemeLight.axaml") };
+
+        // 璁㈤槄涓婚鍒囨崲浜嬩欢
+        ThemeService.Instance.ThemeChanged += ApplyTheme;
     }
 
     public override void OnFrameworkInitializationCompleted()
     {
-        // 鍒濆鍖栨暟鎹簱鏈嶅姟
         DatabaseService.Initialize();
-
-        // 鍒濆鍖 PLC 鏈嶅姟
         PlcService.Initialize();
 
         if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
@@ -28,4 +38,15 @@ public partial class App : Application
 
         base.OnFrameworkInitializationCompleted();
     }
+
+    /// <summary>
+    /// 搴旂敤褰撳墠涓婚
+    /// </summary>
+    private void ApplyTheme()
+    {
+        var resources = Resources.MergedDictionaries;
+        if (resources.Count == 0 || _darkTheme == null || _lightTheme == null) return;
+
+        resources[0] = ThemeService.Instance.IsDarkTheme ? _darkTheme : _lightTheme;
+    }
 }

+ 17 - 21
src/YZWater.Avalonia/Themes/IndustrialStyles.axaml

@@ -2,7 +2,7 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
     <!-- 鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺
-         YZWater3 宸ヤ笟椋庢牸鎺т欢鏍峰紡
+         YZWater3 宸ヤ笟椋庢牸鎺т欢鏍峰紡 (浣跨敤 DynamicResource 璺熼殢涓婚)
          鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺 -->
 
     <!-- 鈹鈹鈹 鍏ㄥ眬鎸夐挳: 鎵嬪瀷鍏夋爣 鈹鈹鈹 -->
@@ -12,14 +12,13 @@
 
     <!-- 鈹鈹鈹 鎸夐挳: Success 鈹鈹鈹 -->
     <Style Selector="Button.btn-success">
-        <Setter Property="Background" Value="#00897B"/>
-        <Setter Property="Foreground" Value="#E6EDF3"/>
-        <Setter Property="BorderBrush" Value="#00897B"/>
-        <Setter Property="FontFamily" Value="Consolas, monospace"/>
+        <Setter Property="Background" Value="{DynamicResource SuccessBrush}"/>
+        <Setter Property="Foreground" Value="{DynamicResource TextPrimaryBrush}"/>
+        <Setter Property="BorderBrush" Value="{DynamicResource SuccessBrush}"/>
+        <Setter Property="FontFamily" Value="{DynamicResource MonoFont}"/>
         <Setter Property="FontSize" Value="11"/>
         <Setter Property="Padding" Value="12,6"/>
         <Setter Property="CornerRadius" Value="2"/>
-        <Setter Property="Cursor" Value="Hand"/>
         <Setter Property="MinHeight" Value="32"/>
     </Style>
     <Style Selector="Button.btn-success:pointerover /template/ ContentPresenter">
@@ -34,14 +33,13 @@
 
     <!-- 鈹鈹鈹 鎸夐挳: Danger 鈹鈹鈹 -->
     <Style Selector="Button.btn-danger">
-        <Setter Property="Background" Value="#1C2333"/>
-        <Setter Property="Foreground" Value="#EF4444"/>
-        <Setter Property="BorderBrush" Value="#EF4444"/>
-        <Setter Property="FontFamily" Value="Consolas, monospace"/>
+        <Setter Property="Background" Value="{DynamicResource SurfaceBgBrush}"/>
+        <Setter Property="Foreground" Value="{DynamicResource DangerBrush}"/>
+        <Setter Property="BorderBrush" Value="{DynamicResource DangerBrush}"/>
+        <Setter Property="FontFamily" Value="{DynamicResource MonoFont}"/>
         <Setter Property="FontSize" Value="11"/>
         <Setter Property="Padding" Value="12,6"/>
         <Setter Property="CornerRadius" Value="2"/>
-        <Setter Property="Cursor" Value="Hand"/>
         <Setter Property="MinHeight" Value="32"/>
     </Style>
     <Style Selector="Button.btn-danger:pointerover /template/ ContentPresenter">
@@ -56,14 +54,13 @@
 
     <!-- 鈹鈹鈹 鎸夐挳: Info 鈹鈹鈹 -->
     <Style Selector="Button.btn-info">
-        <Setter Property="Background" Value="#1C2333"/>
-        <Setter Property="Foreground" Value="#3B82F6"/>
-        <Setter Property="BorderBrush" Value="#3B82F6"/>
-        <Setter Property="FontFamily" Value="Consolas, monospace"/>
+        <Setter Property="Background" Value="{DynamicResource SurfaceBgBrush}"/>
+        <Setter Property="Foreground" Value="{DynamicResource InfoBrush}"/>
+        <Setter Property="BorderBrush" Value="{DynamicResource InfoBrush}"/>
+        <Setter Property="FontFamily" Value="{DynamicResource MonoFont}"/>
         <Setter Property="FontSize" Value="11"/>
         <Setter Property="Padding" Value="12,6"/>
         <Setter Property="CornerRadius" Value="2"/>
-        <Setter Property="Cursor" Value="Hand"/>
         <Setter Property="MinHeight" Value="32"/>
     </Style>
     <Style Selector="Button.btn-info:pointerover /template/ ContentPresenter">
@@ -78,14 +75,13 @@
 
     <!-- 鈹鈹鈹 鎸夐挳: Warning 鈹鈹鈹 -->
     <Style Selector="Button.btn-warning">
-        <Setter Property="Background" Value="#1C2333"/>
-        <Setter Property="Foreground" Value="#F59E0B"/>
-        <Setter Property="BorderBrush" Value="#F59E0B"/>
-        <Setter Property="FontFamily" Value="Consolas, monospace"/>
+        <Setter Property="Background" Value="{DynamicResource SurfaceBgBrush}"/>
+        <Setter Property="Foreground" Value="{DynamicResource WarningBrush}"/>
+        <Setter Property="BorderBrush" Value="{DynamicResource WarningBrush}"/>
+        <Setter Property="FontFamily" Value="{DynamicResource MonoFont}"/>
         <Setter Property="FontSize" Value="11"/>
         <Setter Property="Padding" Value="12,6"/>
         <Setter Property="CornerRadius" Value="2"/>
-        <Setter Property="Cursor" Value="Hand"/>
         <Setter Property="MinHeight" Value="32"/>
     </Style>
     <Style Selector="Button.btn-warning:pointerover /template/ ContentPresenter">

+ 70 - 0
src/YZWater.Avalonia/Themes/IndustrialThemeLight.axaml

@@ -0,0 +1,70 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+    <!-- 鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺
+         YZWater3 宸ヤ笟椋庢牸璁捐绯荤粺 - 娴呰壊涓婚
+         鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺 -->
+
+    <!-- 鑳屾櫙灞傜骇 -->
+    <Color x:Key="AppBgColor">#F0F2F5</Color>
+    <Color x:Key="SurfaceBgColor">#FFFFFF</Color>
+    <Color x:Key="PanelBgColor">#F8F9FA</Color>
+    <Color x:Key="CardBgColor">#FFFFFF</Color>
+
+    <!-- 杈规/鍒嗛殧绾 -->
+    <Color x:Key="BorderColor">#D0D5DD</Color>
+    <Color x:Key="DividerColor">#E5E7EB</Color>
+
+    <!-- 鏂囧瓧灞傜骇 -->
+    <Color x:Key="TextPrimaryColor">#101828</Color>
+    <Color x:Key="TextSecondaryColor">#667085</Color>
+    <Color x:Key="TextTertiaryColor">#98A2B3</Color>
+    <Color x:Key="TextDisabledColor">#B0B8C4</Color>
+
+    <!-- 璇箟鐘舵佽壊 (淇濇寔涓嶅彉) -->
+    <Color x:Key="SuccessColor">#00897B</Color>
+    <Color x:Key="WarningColor">#D97706</Color>
+    <Color x:Key="DangerColor">#DC2626</Color>
+    <Color x:Key="InfoColor">#2563EB</Color>
+    <Color x:Key="WaterColor">#0284C7</Color>
+
+    <!-- 璁惧鐘舵佽壊 -->
+    <Color x:Key="DeviceRunningColor">#00897B</Color>
+    <Color x:Key="DeviceStoppedColor">#98A2B3</Color>
+
+    <!-- 姘寸鑹 -->
+    <Color x:Key="TankInletColor">#00897B</Color>
+    <Color x:Key="TankBioColor">#0284C7</Color>
+    <Color x:Key="TankSedimentColor">#7C3AED</Color>
+    <Color x:Key="TankOutletColor">#16A34A</Color>
+
+    <!-- 鎶ヨ鑳屾櫙 -->
+    <Color x:Key="AlarmBgColor">#FEF2F2</Color>
+
+    <!-- 鐢诲埛 -->
+    <SolidColorBrush x:Key="AppBgBrush" Color="{StaticResource AppBgColor}"/>
+    <SolidColorBrush x:Key="SurfaceBgBrush" Color="{StaticResource SurfaceBgColor}"/>
+    <SolidColorBrush x:Key="PanelBgBrush" Color="{StaticResource PanelBgColor}"/>
+    <SolidColorBrush x:Key="CardBgBrush" Color="{StaticResource CardBgColor}"/>
+    <SolidColorBrush x:Key="BorderBrush" Color="{StaticResource BorderColor}"/>
+    <SolidColorBrush x:Key="DividerBrush" Color="{StaticResource DividerColor}"/>
+    <SolidColorBrush x:Key="TextPrimaryBrush" Color="{StaticResource TextPrimaryColor}"/>
+    <SolidColorBrush x:Key="TextSecondaryBrush" Color="{StaticResource TextSecondaryColor}"/>
+    <SolidColorBrush x:Key="TextTertiaryBrush" Color="{StaticResource TextTertiaryColor}"/>
+    <SolidColorBrush x:Key="TextDisabledBrush" Color="{StaticResource TextDisabledColor}"/>
+    <SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColor}"/>
+    <SolidColorBrush x:Key="WarningBrush" Color="{StaticResource WarningColor}"/>
+    <SolidColorBrush x:Key="DangerBrush" Color="{StaticResource DangerColor}"/>
+    <SolidColorBrush x:Key="InfoBrush" Color="{StaticResource InfoColor}"/>
+    <SolidColorBrush x:Key="WaterBrush" Color="{StaticResource WaterColor}"/>
+    <SolidColorBrush x:Key="TankInletBrush" Color="{StaticResource TankInletColor}"/>
+    <SolidColorBrush x:Key="TankBioBrush" Color="{StaticResource TankBioColor}"/>
+    <SolidColorBrush x:Key="TankSedimentBrush" Color="{StaticResource TankSedimentColor}"/>
+    <SolidColorBrush x:Key="TankOutletBrush" Color="{StaticResource TankOutletColor}"/>
+    <SolidColorBrush x:Key="AlarmBgBrush" Color="{StaticResource AlarmBgColor}"/>
+
+    <!-- 瀛椾綋 -->
+    <FontFamily x:Key="MonoFont">Consolas, Courier New, monospace</FontFamily>
+    <FontFamily x:Key="UIFont">Microsoft YaHei, Segoe UI, sans-serif</FontFamily>
+
+</ResourceDictionary>

+ 67 - 50
src/YZWater.Avalonia/Views/MainWindow.axaml

@@ -5,7 +5,7 @@
         xmlns:conv="using:YZWater.Avalonia.Converters"
         x:Class="YZWater.Avalonia.Views.MainWindow"
         x:DataType="vm:MainViewModel"
-        Title="YZ WATER - 姹℃按澶勭悊鐩戞帶绯荤粺"
+        Title="{Binding Title}"
         Width="1280" Height="800"
         MinWidth="1024" MinHeight="700"
         WindowStartupLocation="CenterScreen"
@@ -19,57 +19,74 @@
         <!-- 鈺愨晲鈺 搴曢儴瀵艰埅鏍 鈺愨晲鈺 -->
         <Border DockPanel.Dock="Bottom" Background="{DynamicResource SurfaceBgBrush}"
                 BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0" Padding="4,0">
-            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="0">
-                <Button Command="{Binding ShowViewACommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
-                    <StackPanel Orientation="Horizontal" Spacing="6">
-                        <Border Width="3" Height="14" CornerRadius="1"
-                                Background="{Binding IsTabAActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                        <TextBlock Text="PROCESS" FontFamily="Consolas, monospace" FontSize="11"
-                                   FontWeight="{Binding IsTabAActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
-                                   Foreground="{Binding IsTabAActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                    </StackPanel>
-                </Button>
-                <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
-                <Button Command="{Binding ShowViewBCommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
-                    <StackPanel Orientation="Horizontal" Spacing="6">
-                        <Border Width="3" Height="14" CornerRadius="1"
-                                Background="{Binding IsTabBActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                        <TextBlock Text="PARAMS" FontFamily="Consolas, monospace" FontSize="11"
-                                   FontWeight="{Binding IsTabBActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
-                                   Foreground="{Binding IsTabBActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                    </StackPanel>
-                </Button>
-                <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
-                <Button Command="{Binding ShowViewCCommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
-                    <StackPanel Orientation="Horizontal" Spacing="6">
-                        <Border Width="3" Height="14" CornerRadius="1"
-                                Background="{Binding IsTabCActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                        <TextBlock Text="FLOW" FontFamily="Consolas, monospace" FontSize="11"
-                                   FontWeight="{Binding IsTabCActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
-                                   Foreground="{Binding IsTabCActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                    </StackPanel>
-                </Button>
-                <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
-                <Button Command="{Binding ShowViewDCommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
-                    <StackPanel Orientation="Horizontal" Spacing="6">
-                        <Border Width="3" Height="14" CornerRadius="1"
-                                Background="{Binding IsTabDActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                        <TextBlock Text="ALARM" FontFamily="Consolas, monospace" FontSize="11"
-                                   FontWeight="{Binding IsTabDActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
-                                   Foreground="{Binding IsTabDActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                    </StackPanel>
+            <Grid ColumnDefinitions="*,Auto,Auto">
+                <!-- 瀵艰埅鎸夐挳 -->
+                <StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Center" Spacing="0">
+                    <Button Command="{Binding ShowViewACommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
+                        <StackPanel Orientation="Horizontal" Spacing="6">
+                            <Border Width="3" Height="14" CornerRadius="1"
+                                    Background="{Binding IsTabAActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                            <TextBlock Text="{Binding NavProcess}" FontFamily="Consolas, monospace" FontSize="11"
+                                       FontWeight="{Binding IsTabAActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
+                                       Foreground="{Binding IsTabAActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                        </StackPanel>
+                    </Button>
+                    <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
+                    <Button Command="{Binding ShowViewBCommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
+                        <StackPanel Orientation="Horizontal" Spacing="6">
+                            <Border Width="3" Height="14" CornerRadius="1"
+                                    Background="{Binding IsTabBActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                            <TextBlock Text="{Binding NavParams}" FontFamily="Consolas, monospace" FontSize="11"
+                                       FontWeight="{Binding IsTabBActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
+                                       Foreground="{Binding IsTabBActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                        </StackPanel>
+                    </Button>
+                    <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
+                    <Button Command="{Binding ShowViewCCommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
+                        <StackPanel Orientation="Horizontal" Spacing="6">
+                            <Border Width="3" Height="14" CornerRadius="1"
+                                    Background="{Binding IsTabCActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                            <TextBlock Text="{Binding NavFlow}" FontFamily="Consolas, monospace" FontSize="11"
+                                       FontWeight="{Binding IsTabCActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
+                                       Foreground="{Binding IsTabCActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                        </StackPanel>
+                    </Button>
+                    <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
+                    <Button Command="{Binding ShowViewDCommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
+                        <StackPanel Orientation="Horizontal" Spacing="6">
+                            <Border Width="3" Height="14" CornerRadius="1"
+                                    Background="{Binding IsTabDActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                            <TextBlock Text="{Binding NavAlarm}" FontFamily="Consolas, monospace" FontSize="11"
+                                       FontWeight="{Binding IsTabDActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
+                                       Foreground="{Binding IsTabDActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                        </StackPanel>
+                    </Button>
+                    <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
+                    <Button Command="{Binding ShowViewECommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
+                        <StackPanel Orientation="Horizontal" Spacing="6">
+                            <Border Width="3" Height="14" CornerRadius="1"
+                                    Background="{Binding IsTabEActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                            <TextBlock Text="{Binding NavAbout}" FontFamily="Consolas, monospace" FontSize="11"
+                                       FontWeight="{Binding IsTabEActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
+                                       Foreground="{Binding IsTabEActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
+                        </StackPanel>
+                    </Button>
+                </StackPanel>
+
+                <!-- 涓婚鍒囨崲 -->
+                <Button Grid.Column="1" Command="{Binding ToggleThemeCommand}" Padding="10,6"
+                        BorderThickness="0" Background="Transparent" Margin="8,0">
+                    <TextBlock Text="{Binding ThemeIcon}" FontSize="14"/>
                 </Button>
-                <Border Background="{DynamicResource BorderBrush}" Width="1" Height="20" VerticalAlignment="Center"/>
-                <Button Command="{Binding ShowViewECommand}" Padding="20,8" BorderThickness="0" Background="Transparent">
-                    <StackPanel Orientation="Horizontal" Spacing="6">
-                        <Border Width="3" Height="14" CornerRadius="1"
-                                Background="{Binding IsTabEActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                        <TextBlock Text="ABOUT" FontFamily="Consolas, monospace" FontSize="11"
-                                   FontWeight="{Binding IsTabEActive, Converter={x:Static conv:BoolConverters.ToFontWeight}}"
-                                   Foreground="{Binding IsTabEActive, Converter={x:Static conv:BoolConverters.ToBrush}}"/>
-                    </StackPanel>
+
+                <!-- 璇█鍒囨崲 -->
+                <Button Grid.Column="2" Command="{Binding ToggleLanguageCommand}" Padding="10,6"
+                        BorderThickness="1" Background="Transparent" BorderBrush="{DynamicResource BorderBrush}"
+                        CornerRadius="4" Margin="0,0,8,0">
+                    <TextBlock Text="{Binding LangIcon}" FontSize="11" FontWeight="Bold"
+                               Foreground="{DynamicResource TextSecondaryBrush}"/>
                 </Button>
-            </StackPanel>
+            </Grid>
         </Border>
 
         <!-- 鈺愨晲鈺 涓诲唴瀹瑰尯鍩 鈺愨晲鈺 -->

+ 275 - 0
src/YZWater.Core/Services/LanguageService.cs

@@ -0,0 +1,275 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace YZWater.Core.Services;
+
+/// <summary>
+/// 璇█鍒囨崲鏈嶅姟
+/// </summary>
+public partial class LanguageService : ObservableObject
+{
+    private static LanguageService? _instance;
+    public static LanguageService Instance => _instance ??= new LanguageService();
+
+    [ObservableProperty]
+    private bool _isChinese = true;
+
+    /// <summary>
+    /// 璇█鍙樻洿浜嬩欢
+    /// </summary>
+    public event Action? LanguageChanged;
+
+    private LanguageService() { }
+
+    /// <summary>
+    /// 鍒囨崲璇█
+    /// </summary>
+    public void ToggleLanguage()
+    {
+        IsChinese = !IsChinese;
+        LanguageChanged?.Invoke();
+    }
+
+    /// <summary>
+    /// 鑾峰彇缈昏瘧
+    /// </summary>
+    public string Get(string key)
+    {
+        return IsChinese ? _zh.GetValueOrDefault(key, key) : _en.GetValueOrDefault(key, key);
+    }
+
+    // 鈹鈹鈹 缈昏瘧瀛楀吀 鈹鈹鈹
+
+    private static readonly Dictionary<string, string> _zh = new()
+    {
+        // 閫氱敤
+        ["AppName"] = "姹℃按澶勭悊鐩戞帶绯荤粺",
+        ["Version"] = "鐗堟湰",
+        // 瀵艰埅
+        ["NavProcess"] = "宸ヨ壓娴佺▼",
+        ["NavParams"] = "鍙傛暟璁剧疆",
+        ["NavFlow"] = "娴侀噺璁板綍",
+        ["NavAlarm"] = "鎶ヨ璁板綍",
+        ["NavAbout"] = "鍏充簬",
+        // ViewA 鏍囬鏍
+        ["ProcessFlow"] = "宸ヨ壓娴佺▼",
+        ["PlcConnected"] = "PLC 宸茶繛鎺",
+        ["PlcDisconnected"] = "PLC 鏈繛鎺",
+        ["Connect"] = "杩炴帴",
+        ["Disconnect"] = "鏂紑",
+        ["Refresh"] = "鍒锋柊",
+        // ViewA 姘寸
+        ["TankInlet"] = "鍏ュ彛姹",
+        ["TankBio"] = "鐢熷寲姹",
+        ["TankSediment"] = "娌夋穩姹",
+        ["TankOutlet"] = "鍑哄彛姹",
+        ["Level"] = "娑蹭綅",
+        // ViewA 娉/椋庢満
+        ["Pump1"] = "杩涙按娉1",
+        ["Pump2"] = "杩涙按娉2",
+        ["Pump3"] = "鍥炴祦娉",
+        ["Pump4"] = "鎺掓偿娉",
+        ["Pump5"] = "鍔犺嵂娉",
+        ["Fan1"] = "椋庢満1",
+        ["Fan2"] = "椋庢満2",
+        // ViewA 闃闂
+        ["ValveInlet"] = "杩涙按",
+        ["ValveAeration"] = "鏇濇皵",
+        ["ValveReflux"] = "鍥炴祦",
+        ["ValveOutlet"] = "鍑烘按",
+        // ViewA 鏁版嵁闈㈡澘
+        ["Mode"] = "杩愯妯″紡",
+        ["Realtime"] = "瀹炴椂鏁版嵁",
+        ["Inflow"] = "杩涙按娴侀噺",
+        ["Outflow"] = "鍑烘按娴侀噺",
+        ["Delta"] = "宸",
+        ["Statistics"] = "杩愯缁熻",
+        ["Runtime"] = "杩愯鏃堕棿",
+        ["Devices"] = "璁惧",
+        ["Alarm"] = "鎶ヨ",
+        ["Efficiency"] = "鏁堢巼",
+        ["Active"] = "杩愯涓",
+        ["Stopped"] = "宸插仠姝",
+        // ViewA 蹇嵎鎿嶄綔
+        ["QuickOps"] = "蹇嵎鎿嶄綔",
+        ["StartAll"] = "鍏ㄩ儴鍚姩",
+        ["StopAll"] = "鍏ㄩ儴鍋滄",
+        ["AckAlarm"] = "鎶ヨ纭",
+        ["Export"] = "瀵煎嚭",
+        // ViewA 搴曢儴鐘舵佹爮
+        ["FlowMeters"] = "娴侀噺鐩戞祴",
+        ["DeviceStatus"] = "璁惧鐘舵",
+        // ViewB
+        ["Parameters"] = "鍙傛暟璁剧疆",
+        ["SysConfig"] = "绯荤粺鍙傛暟閰嶇疆",
+        ["PlcConnection"] = "PLC 杩炴帴璁剧疆",
+        ["IpAddress"] = "IP 鍦板潃",
+        ["Port"] = "绔彛",
+        ["AutoConnect"] = "鑷姩杩炴帴",
+        ["Status"] = "鐘舵",
+        ["Test"] = "娴嬭瘯",
+        ["AlarmThresholds"] = "鎶ヨ闃堝艰缃",
+        ["LevelHigh"] = "娑蹭綅楂橀檺",
+        ["LevelLow"] = "娑蹭綅浣庨檺",
+        ["FlowHigh"] = "娴侀噺楂橀檺",
+        ["PumpSettings"] = "娉佃缃",
+        ["Frequency"] = "棰戠巼",
+        ["SaveConfig"] = "淇濆瓨閰嶇疆",
+        ["ResetDefault"] = "鎭㈠榛樿",
+        ["Connected"] = "宸茶繛鎺",
+        ["Disconnected"] = "鏈繛鎺",
+        ["Connecting"] = "杩炴帴涓...",
+        // ViewC
+        ["FlowRecords"] = "娴侀噺璁板綍",
+        ["FlowHistory"] = "娴侀噺鍘嗗彶鏁版嵁",
+        ["From"] = "浠",
+        ["To"] = "鍒",
+        ["Query"] = "鏌ヨ",
+        ["ExportCsv"] = "瀵煎嚭CSV",
+        ["PurgeOld"] = "娓呴櫎鏃ф暟鎹",
+        ["InflowTrend"] = "杩涙按娴侀噺瓒嬪娍",
+        ["OutflowTrend"] = "鍑烘按娴侀噺瓒嬪娍",
+        // ViewD
+        ["AlarmLog"] = "鎶ヨ璁板綍",
+        ["AlarmHistory"] = "鎶ヨ鍘嗗彶璁板綍",
+        ["Unconfirmed"] = "鏈‘璁",
+        ["AckAll"] = "鍏ㄩ儴纭",
+        ["Purge"] = "娓呴櫎",
+        ["Time"] = "鏃堕棿",
+        ["Type"] = "绫诲瀷",
+        ["Message"] = "娑堟伅",
+        ["Value"] = "鏁板",
+        ["Level2"] = "绾у埆",
+        ["AckTime"] = "纭鏃堕棿",
+        ["AckBy"] = "纭浜",
+        ["AlarmDetail"] = "鎶ヨ璇︽儏",
+        // ViewE
+        ["About"] = "鍏充簬",
+        ["SystemInfo"] = "绯荤粺淇℃伅",
+        ["Description"] = "鍩轰簬 Avalonia UI 鐨勮法骞冲彴姹℃按澶勭悊鍘傜洃鎺х郴缁",
+        ["TechStack"] = "鎶鏈爤",
+        ["Company"] = "鍏徃淇℃伅",
+        ["CompanyName"] = "鍏徃鍚嶇О",
+        ["Contact"] = "鑱旂郴浜",
+        ["Phone"] = "鑱旂郴鐢佃瘽",
+        ["VisitWebsite"] = "璁块棶瀹樼綉",
+        ["CheckUpdate"] = "妫鏌ユ洿鏂",
+        ["Copyright"] = "漏 鎵窞鏃僵绉戞妧鏈夐檺鍏徃",
+        // 鎺т欢榛樿鍊
+        ["DefaultTank"] = "姘寸",
+        ["DefaultPump"] = "娉",
+        ["DefaultFan"] = "椋庢墖",
+        ["DefaultValve"] = "闃闂",
+        ["DefaultGauge"] = "浠〃",
+        ["DefaultDevice"] = "璁惧",
+    };
+
+    private static readonly Dictionary<string, string> _en = new()
+    {
+        ["AppName"] = "Wastewater Treatment System",
+        ["Version"] = "Version",
+        ["NavProcess"] = "Process",
+        ["NavParams"] = "Params",
+        ["NavFlow"] = "Flow",
+        ["NavAlarm"] = "Alarm",
+        ["NavAbout"] = "About",
+        ["ProcessFlow"] = "PROCESS FLOW",
+        ["PlcConnected"] = "PLC CONNECTED",
+        ["PlcDisconnected"] = "PLC DISCONNECTED",
+        ["Connect"] = "CONNECT",
+        ["Disconnect"] = "DISCONNECT",
+        ["Refresh"] = "REFRESH",
+        ["TankInlet"] = "Inlet",
+        ["TankBio"] = "Bio Tank",
+        ["TankSediment"] = "Sediment",
+        ["TankOutlet"] = "Outlet",
+        ["Level"] = "Level",
+        ["Pump1"] = "Inlet Pump 1",
+        ["Pump2"] = "Inlet Pump 2",
+        ["Pump3"] = "Reflux Pump",
+        ["Pump4"] = "Sludge Pump",
+        ["Pump5"] = "Dosing Pump",
+        ["Fan1"] = "Fan 1",
+        ["Fan2"] = "Fan 2",
+        ["ValveInlet"] = "Inlet",
+        ["ValveAeration"] = "Aeration",
+        ["ValveReflux"] = "Reflux",
+        ["ValveOutlet"] = "Outlet",
+        ["Mode"] = "MODE",
+        ["Realtime"] = "REALTIME",
+        ["Inflow"] = "INFLOW",
+        ["Outflow"] = "OUTFLOW",
+        ["Delta"] = "DELTA",
+        ["Statistics"] = "STATISTICS",
+        ["Runtime"] = "RUNTIME",
+        ["Devices"] = "DEVICES",
+        ["Alarm"] = "ALARM",
+        ["Efficiency"] = "EFFICIENCY",
+        ["Active"] = "ACTIVE",
+        ["Stopped"] = "STOPPED",
+        ["QuickOps"] = "QUICK OPS",
+        ["StartAll"] = "START ALL",
+        ["StopAll"] = "STOP ALL",
+        ["AckAlarm"] = "ACK ALARM",
+        ["Export"] = "EXPORT",
+        ["FlowMeters"] = "FLOW METERS",
+        ["DeviceStatus"] = "DEVICE STATUS",
+        ["Parameters"] = "PARAMETERS",
+        ["SysConfig"] = "System Configuration",
+        ["PlcConnection"] = "PLC CONNECTION",
+        ["IpAddress"] = "IP ADDRESS",
+        ["Port"] = "PORT",
+        ["AutoConnect"] = "AUTO CONNECT",
+        ["Status"] = "STATUS",
+        ["Test"] = "TEST",
+        ["AlarmThresholds"] = "ALARM THRESHOLDS",
+        ["LevelHigh"] = "LEVEL HIGH",
+        ["LevelLow"] = "LEVEL LOW",
+        ["FlowHigh"] = "FLOW HIGH",
+        ["PumpSettings"] = "PUMP SETTINGS",
+        ["Frequency"] = "FREQUENCY",
+        ["SaveConfig"] = "SAVE CONFIG",
+        ["ResetDefault"] = "RESET DEFAULT",
+        ["Connected"] = "Connected",
+        ["Disconnected"] = "Disconnected",
+        ["Connecting"] = "Connecting...",
+        ["FlowRecords"] = "FLOW RECORDS",
+        ["FlowHistory"] = "Flow History Data",
+        ["From"] = "FROM",
+        ["To"] = "TO",
+        ["Query"] = "QUERY",
+        ["ExportCsv"] = "EXPORT CSV",
+        ["PurgeOld"] = "PURGE OLD",
+        ["InflowTrend"] = "INFLOW TREND",
+        ["OutflowTrend"] = "OUTFLOW TREND",
+        ["AlarmLog"] = "ALARM LOG",
+        ["AlarmHistory"] = "Alarm History",
+        ["Unconfirmed"] = "Unconfirmed",
+        ["AckAll"] = "ACK ALL",
+        ["Purge"] = "PURGE",
+        ["Time"] = "TIME",
+        ["Type"] = "TYPE",
+        ["Message"] = "MESSAGE",
+        ["Value"] = "VALUE",
+        ["Level2"] = "LEVEL",
+        ["AckTime"] = "ACK TIME",
+        ["AckBy"] = "ACK BY",
+        ["AlarmDetail"] = "ALARM DETAIL",
+        ["About"] = "ABOUT",
+        ["SystemInfo"] = "SYSTEM INFO",
+        ["Description"] = "Cross-platform wastewater treatment plant monitoring system based on Avalonia UI",
+        ["TechStack"] = "TECH STACK",
+        ["Company"] = "COMPANY",
+        ["CompanyName"] = "COMPANY NAME",
+        ["Contact"] = "CONTACT",
+        ["Phone"] = "PHONE",
+        ["VisitWebsite"] = "VISIT WEBSITE",
+        ["CheckUpdate"] = "CHECK UPDATE",
+        ["Copyright"] = "漏 Yangzhou Xuxuan Technology Co., Ltd.",
+        ["DefaultTank"] = "Tank",
+        ["DefaultPump"] = "Pump",
+        ["DefaultFan"] = "Fan",
+        ["DefaultValve"] = "Valve",
+        ["DefaultGauge"] = "Gauge",
+        ["DefaultDevice"] = "Device",
+    };
+}

+ 31 - 0
src/YZWater.Core/Services/ThemeService.cs

@@ -0,0 +1,31 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace YZWater.Core.Services;
+
+/// <summary>
+/// 涓婚鍒囨崲鏈嶅姟锛堢函鐘舵佺鐞嗭紝涓嶄緷璧 Avalonia锛
+/// </summary>
+public partial class ThemeService : ObservableObject
+{
+    private static ThemeService? _instance;
+    public static ThemeService Instance => _instance ??= new ThemeService();
+
+    [ObservableProperty]
+    private bool _isDarkTheme = true;
+
+    /// <summary>
+    /// 涓婚鍙樻洿浜嬩欢锛堢敱 App.axaml.cs 璁㈤槄鎵ц瀹為檯鍒囨崲锛
+    /// </summary>
+    public event Action? ThemeChanged;
+
+    private ThemeService() { }
+
+    /// <summary>
+    /// 鍒囨崲涓婚
+    /// </summary>
+    public void ToggleTheme()
+    {
+        IsDarkTheme = !IsDarkTheme;
+        ThemeChanged?.Invoke();
+    }
+}

+ 75 - 5
src/YZWater.Core/ViewModels/MainViewModel.cs

@@ -16,11 +16,14 @@ public partial class MainViewModel : ObservableObject
     private readonly ViewDViewModel _viewDViewModel;
     private readonly ViewEViewModel _viewEViewModel;
 
+    private readonly ThemeService _themeService = ThemeService.Instance;
+    private readonly LanguageService _langService = LanguageService.Instance;
+
     [ObservableProperty]
     private ObservableObject _currentView;
 
     [ObservableProperty]
-    private string _title = "姹℃按澶勭悊鐩戞帶绯荤粺";
+    private string _title;
 
     [ObservableProperty]
     private string _currentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
@@ -39,9 +42,20 @@ public partial class MainViewModel : ObservableObject
     [ObservableProperty] private bool _isTabEActive;
 
     // 褰撳墠 Tab 鏍囬
-    [ObservableProperty] private string _tabTitle = "PROCESS FLOW";
+    [ObservableProperty] private string _tabTitle;
+
+    // 涓婚/璇█鐘舵
+    [ObservableProperty] private bool _isDarkTheme = true;
+    [ObservableProperty] private bool _isChinese = true;
+    [ObservableProperty] private string _themeIcon = "馃寵";
+    [ObservableProperty] private string _langIcon = "涓";
 
-    private readonly string[] _tabTitles = { "PROCESS FLOW", "PARAMETERS", "FLOW RECORDS", "ALARM LOG", "ABOUT" };
+    // 瀵艰埅鏍囩
+    [ObservableProperty] private string _navProcess;
+    [ObservableProperty] private string _navParams;
+    [ObservableProperty] private string _navFlow;
+    [ObservableProperty] private string _navAlarm;
+    [ObservableProperty] private string _navAbout;
 
     public MainViewModel()
     {
@@ -52,10 +66,63 @@ public partial class MainViewModel : ObservableObject
         _viewEViewModel = new ViewEViewModel();
 
         _currentView = _viewAViewModel;
+
+        // 鍒濆鍖栨枃瀛
+        UpdateAllTexts();
+
+        // 鐩戝惉璇█鍙樻洿
+        _langService.LanguageChanged += () =>
+        {
+            IsChinese = _langService.IsChinese;
+            UpdateAllTexts();
+        };
+
+        // 鐩戝惉涓婚鍙樻洿
+        _themeService.PropertyChanged += (s, e) =>
+        {
+            if (e.PropertyName == nameof(ThemeService.IsDarkTheme))
+            {
+                IsDarkTheme = _themeService.IsDarkTheme;
+                ThemeIcon = IsDarkTheme ? "馃寵" : "鈽锔";
+            }
+        };
+
         StartTimer();
     }
 
-    // PLC 鎿嶄綔浠g悊鍛戒护锛堝鎵樼粰 ViewAViewModel锛
+    /// <summary>
+    /// 鏇存柊鎵鏈夌晫闈㈡枃瀛
+    /// </summary>
+    private void UpdateAllTexts()
+    {
+        Title = _langService.Get("AppName");
+        TabTitle = _langService.Get("ProcessFlow");
+        NavProcess = _langService.Get("NavProcess");
+        NavParams = _langService.Get("NavParams");
+        NavFlow = _langService.Get("NavFlow");
+        NavAlarm = _langService.Get("NavAlarm");
+        NavAbout = _langService.Get("NavAbout");
+        IsChinese = _langService.IsChinese;
+        LangIcon = IsChinese ? "涓" : "EN";
+        IsDarkTheme = _themeService.IsDarkTheme;
+        ThemeIcon = IsDarkTheme ? "馃寵" : "鈽锔";
+    }
+
+    // 鈹鈹鈹 涓婚鍒囨崲 鈹鈹鈹
+    [RelayCommand]
+    private void ToggleTheme()
+    {
+        _themeService.ToggleTheme();
+    }
+
+    // 鈹鈹鈹 璇█鍒囨崲 鈹鈹鈹
+    [RelayCommand]
+    private void ToggleLanguage()
+    {
+        _langService.ToggleLanguage();
+    }
+
+    // 鈹鈹鈹 PLC 鎿嶄綔浠g悊 鈹鈹鈹
     [RelayCommand]
     private async Task ConnectPlcAsync() => await _viewAViewModel.ConnectPlcCommand.ExecuteAsync(null);
 
@@ -65,6 +132,9 @@ public partial class MainViewModel : ObservableObject
     [RelayCommand]
     private async Task RefreshDataAsync() => await _viewAViewModel.RefreshDataCommand.ExecuteAsync(null);
 
+    // 鈹鈹鈹 Tab 鍒囨崲 鈹鈹鈹
+    private readonly string[] _tabTitleKeys = { "ProcessFlow", "Parameters", "FlowRecords", "AlarmLog", "About" };
+
     private void SetActiveTab(int index)
     {
         SelectedTabIndex = index;
@@ -73,7 +143,7 @@ public partial class MainViewModel : ObservableObject
         IsTabCActive = index == 2;
         IsTabDActive = index == 3;
         IsTabEActive = index == 4;
-        TabTitle = _tabTitles[index];
+        TabTitle = _langService.Get(_tabTitleKeys[index]);
     }
 
     [RelayCommand]