æµè§ˆä»£ç 

添加主题切æ¢(深色/浅色)和语言切æ¢(中/英)

磊 曹 1 周之å‰
父节点
å½“å‰æäº¤
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 æ“作代ç†å‘½ä»¤ï¼ˆå§”托给 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 æ“ä½œä»£ç† â”€â”€â”€
     [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]