Kayna臒a G枚zat

浼樺寲棣栭〉UI - 娣诲姞浠〃鐩樸佺姸鎬佸崱鐗囩瓑鐜颁唬鎺т欢

纾 鏇 1 hafta 枚nce
ebeveyn
i艧leme
e3f5710412

+ 182 - 0
src/YZWater.Avalonia/Controls/GaugeControl.cs

@@ -0,0 +1,182 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using System;
+using System.Globalization;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 浠〃鐩樻帶浠讹紙绠鍖栫増锛
+/// </summary>
+public class GaugeControl : Control
+{
+    public static readonly StyledProperty<double> ValueProperty =
+        AvaloniaProperty.Register<GaugeControl, double>(nameof(Value), 0.0);
+
+    public static readonly StyledProperty<double> MinValueProperty =
+        AvaloniaProperty.Register<GaugeControl, double>(nameof(MinValue), 0.0);
+
+    public static readonly StyledProperty<double> MaxValueProperty =
+        AvaloniaProperty.Register<GaugeControl, double>(nameof(MaxValue), 100.0);
+
+    public static readonly StyledProperty<string> TitleProperty =
+        AvaloniaProperty.Register<GaugeControl, string>(nameof(Title), "娴侀噺");
+
+    public static readonly StyledProperty<string> UnitProperty =
+        AvaloniaProperty.Register<GaugeControl, string>(nameof(Unit), "m鲁/h");
+
+    public static readonly StyledProperty<IBrush> ValueColorProperty =
+        AvaloniaProperty.Register<GaugeControl, IBrush>(nameof(ValueColor), new SolidColorBrush(Color.Parse("#00BCD4")));
+
+    public double Value
+    {
+        get => GetValue(ValueProperty);
+        set => SetValue(ValueProperty, value);
+    }
+
+    public double MinValue
+    {
+        get => GetValue(MinValueProperty);
+        set => SetValue(MinValueProperty, value);
+    }
+
+    public double MaxValue
+    {
+        get => GetValue(MaxValueProperty);
+        set => SetValue(MaxValueProperty, value);
+    }
+
+    public string Title
+    {
+        get => GetValue(TitleProperty);
+        set => SetValue(TitleProperty, value);
+    }
+
+    public string Unit
+    {
+        get => GetValue(UnitProperty);
+        set => SetValue(UnitProperty, value);
+    }
+
+    public IBrush ValueColor
+    {
+        get => GetValue(ValueColorProperty);
+        set => SetValue(ValueColorProperty, value);
+    }
+
+    static GaugeControl()
+    {
+        AffectsRender<GaugeControl>(ValueProperty, MinValueProperty, MaxValueProperty,
+            TitleProperty, UnitProperty, ValueColorProperty);
+    }
+
+    public override void Render(DrawingContext context)
+    {
+        base.Render(context);
+
+        var bounds = new Rect(Bounds.Size);
+        var centerX = bounds.Width / 2;
+        var centerY = bounds.Height / 2 + 10;
+        var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 20;
+        var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
+
+        // 缁樺埗鑳屾櫙鍦
+        var bgBrush = new SolidColorBrush(Color.Parse("#1E272E"));
+        context.DrawEllipse(bgBrush, null, new Rect(
+            centerX - radius - 5,
+            centerY - radius - 5,
+            (radius + 5) * 2,
+            (radius + 5) * 2));
+
+        // 缁樺埗鍒诲害寮х嚎鑳屾櫙
+        var arcPenBg = new Pen(new SolidColorBrush(Color.Parse("#37474F")), 8);
+        DrawArc(context, arcPenBg, centerX, centerY, radius, 135, 270);
+
+        // 璁$畻褰撳墠鍊煎搴旂殑瑙掑害
+        var normalizedValue = (Value - MinValue) / (MaxValue - MinValue);
+        normalizedValue = Math.Max(0, Math.Min(1, normalizedValue));
+        var valueAngle = 135 + normalizedValue * 270;
+
+        // 缁樺埗褰撳墠鍊煎姬绾
+        var valueColor = ValueColor as SolidColorBrush ?? new SolidColorBrush(Color.Parse("#00BCD4"));
+        var arcPen = new Pen(valueColor, 8);
+        DrawArc(context, arcPen, centerX, centerY, radius, 135, valueAngle - 135);
+
+        // 缁樺埗鍒诲害绾
+        var tickCount = 10;
+        for (int i = 0; i <= tickCount; i++)
+        {
+            var angle = 135 + (270.0 * i / tickCount);
+            var radians = angle * Math.PI / 180;
+            var isMajor = i % 5 == 0;
+            var tickLength = isMajor ? 12 : 6;
+
+            var innerX = centerX + Math.Cos(radians) * (radius - tickLength);
+            var innerY = centerY + Math.Sin(radians) * (radius - tickLength);
+            var outerX = centerX + Math.Cos(radians) * radius;
+            var outerY = centerY + Math.Sin(radians) * radius;
+
+            var tickPen = new Pen(new SolidColorBrush(isMajor ? Colors.White : Colors.Gray), isMajor ? 2 : 1);
+            context.DrawLine(tickPen, new Point(innerX, innerY), new Point(outerX, outerY));
+        }
+
+        // 缁樺埗鎸囬拡
+        var pointerAngle = valueAngle * Math.PI / 180;
+        var pointerLength = radius * 0.7;
+        var pointerX = centerX + Math.Cos(pointerAngle) * pointerLength;
+        var pointerY = centerY + Math.Sin(pointerAngle) * pointerLength;
+
+        var pointerPen = new Pen(valueColor, 3);
+        context.DrawLine(pointerPen, new Point(centerX, centerY), new Point(pointerX, pointerY));
+
+        // 缁樺埗涓績鍦
+        context.DrawEllipse(valueColor, null, new Rect(centerX - 6, centerY - 6, 12, 12));
+
+        // 缁樺埗鏍囬
+        var titleText = new FormattedText(Title, CultureInfo.CurrentCulture,
+            FlowDirection.LeftToRight, typeface, 12, new SolidColorBrush(Colors.White));
+        context.DrawText(titleText, new Point(centerX - titleText.Width / 2, bounds.Y + 5));
+
+        // 缁樺埗鏁板
+        var valueStr = $"{Value:F1}";
+        var valueText = new FormattedText(valueStr, CultureInfo.CurrentCulture,
+            FlowDirection.LeftToRight, new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold),
+            20, new SolidColorBrush(Colors.White));
+        context.DrawText(valueText, new Point(centerX - valueText.Width / 2, centerY + radius * 0.3));
+
+        // 缁樺埗鍗曚綅
+        var unitText = new FormattedText(Unit, CultureInfo.CurrentCulture,
+            FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"), 10,
+            new SolidColorBrush(Colors.Gray));
+        context.DrawText(unitText, new Point(centerX - unitText.Width / 2, centerY + radius * 0.3 + valueText.Height + 2));
+    }
+
+    private void DrawArc(DrawingContext context, IPen pen, double centerX, double centerY, double radius, double startAngle, double sweepAngle)
+    {
+        // 绠鍖栫粯鍒讹細浣跨敤澶氫釜灏忕嚎娈佃繎浼煎姬绾
+        var startRadians = startAngle * Math.PI / 180;
+        var endRadians = (startAngle + sweepAngle) * Math.PI / 180;
+        var steps = 36;
+
+        var prevX = centerX + Math.Cos(startRadians) * radius;
+        var prevY = centerY + Math.Sin(startRadians) * radius;
+
+        for (int i = 1; i <= steps; i++)
+        {
+            var t = (double)i / steps;
+            var angle = startRadians + (endRadians - startRadians) * t;
+            var x = centerX + Math.Cos(angle) * radius;
+            var y = centerY + Math.Sin(angle) * radius;
+
+            context.DrawLine(pen, new Point(prevX, prevY), new Point(x, y));
+            prevX = x;
+            prevY = y;
+        }
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        return new Size(120, 140);
+    }
+}

+ 135 - 46
src/YZWater.Avalonia/Controls/PumpControl.cs

@@ -8,7 +8,7 @@ using System.Globalization;
 namespace YZWater.Avalonia.Controls;
 
 /// <summary>
-/// 娉垫帶浠 - 妯℃嫙 HslPumpOne
+/// 娉垫帶浠 - 妯℃嫙 HslPumpOne锛堜紭鍖栫増锛
 /// </summary>
 public class PumpControl : Control
 {
@@ -18,18 +18,21 @@ public class PumpControl : Control
     public static readonly StyledProperty<double> SpeedProperty =
         AvaloniaProperty.Register<PumpControl, double>(nameof(Speed), 0.0);
 
-    public static readonly StyledProperty<IBrush> Color1Property =
-        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(Color1), new SolidColorBrush(Color.Parse("#9b9b9b")));
-
-    public static readonly StyledProperty<IBrush> Color2Property =
-        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(Color2), new SolidColorBrush(Color.Parse("#9b9b9b")));
-
-    public static readonly StyledProperty<IBrush> Color3Property =
-        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(Color3), new SolidColorBrush(Color.Parse("#9b9b9b")));
+    public static readonly StyledProperty<double> FrequencyProperty =
+        AvaloniaProperty.Register<PumpControl, double>(nameof(Frequency), 50.0);
 
     public static readonly StyledProperty<string> TextProperty =
         AvaloniaProperty.Register<PumpControl, string>(nameof(Text), string.Empty);
 
+    public static readonly StyledProperty<IBrush> RunningColorProperty =
+        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(RunningColor), new SolidColorBrush(Color.Parse("#4CAF50")));
+
+    public static readonly StyledProperty<IBrush> StoppedColorProperty =
+        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(StoppedColor), new SolidColorBrush(Color.Parse("#607D8B")));
+
+    public static readonly StyledProperty<IBrush> FaultColorProperty =
+        AvaloniaProperty.Register<PumpControl, IBrush>(nameof(FaultColor), new SolidColorBrush(Color.Parse("#F44336")));
+
     public bool IsRunning
     {
         get => GetValue(IsRunningProperty);
@@ -42,28 +45,34 @@ public class PumpControl : Control
         set => SetValue(SpeedProperty, value);
     }
 
-    public IBrush Color1
+    public double Frequency
     {
-        get => GetValue(Color1Property);
-        set => SetValue(Color1Property, value);
+        get => GetValue(FrequencyProperty);
+        set => SetValue(FrequencyProperty, value);
     }
 
-    public IBrush Color2
+    public string Text
     {
-        get => GetValue(Color2Property);
-        set => SetValue(Color2Property, value);
+        get => GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
     }
 
-    public IBrush Color3
+    public IBrush RunningColor
     {
-        get => GetValue(Color3Property);
-        set => SetValue(Color3Property, value);
+        get => GetValue(RunningColorProperty);
+        set => SetValue(RunningColorProperty, value);
     }
 
-    public string Text
+    public IBrush StoppedColor
     {
-        get => GetValue(TextProperty);
-        set => SetValue(TextProperty, value);
+        get => GetValue(StoppedColorProperty);
+        set => SetValue(StoppedColorProperty, value);
+    }
+
+    public IBrush FaultColor
+    {
+        get => GetValue(FaultColorProperty);
+        set => SetValue(FaultColorProperty, value);
     }
 
     private double _rotationAngle = 0;
@@ -71,7 +80,8 @@ public class PumpControl : Control
 
     static PumpControl()
     {
-        AffectsRender<PumpControl>(IsRunningProperty, SpeedProperty, Color1Property, Color2Property, Color3Property, TextProperty);
+        AffectsRender<PumpControl>(IsRunningProperty, SpeedProperty, FrequencyProperty,
+            TextProperty, RunningColorProperty, StoppedColorProperty, FaultColorProperty);
     }
 
     protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
@@ -103,6 +113,7 @@ public class PumpControl : Control
                 StopAnimation();
                 _rotationAngle = 0;
             }
+            InvalidateVisual();
         }
     }
 
@@ -111,11 +122,11 @@ public class PumpControl : Control
         StopAnimation();
         _timerSubscription = DispatcherTimer.Run(() =>
         {
-            _rotationAngle += Speed;
+            _rotationAngle += Frequency / 20.0;
             if (_rotationAngle >= 360) _rotationAngle -= 360;
             InvalidateVisual();
             return true;
-        }, TimeSpan.FromMilliseconds(50));
+        }, TimeSpan.FromMilliseconds(30));
     }
 
     private void StopAnimation()
@@ -130,22 +141,45 @@ public class PumpControl : Control
 
         var bounds = new Rect(Bounds.Size);
         var centerX = bounds.Width / 2;
-        var centerY = bounds.Height / 2;
-        var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 5;
-        var typeface = new Typeface("Microsoft YaHei");
+        var centerY = bounds.Height / 2 - 5;
+        var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 15;
+        var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
 
-        // 缁樺埗娉靛澹
-        var outerPen = new Pen(Color1, 3);
+        // 纭畾鐘舵侀鑹
+        var statusColor = IsRunning ? RunningColor : StoppedColor;
+        var statusBrush = statusColor as SolidColorBrush ?? new SolidColorBrush(Colors.Gray);
+
+        // 缁樺埗澶栧湀鍙戝厜鏁堟灉锛堣繍琛屾椂锛
+        if (IsRunning)
+        {
+            var glowBrush = new SolidColorBrush(statusBrush.Color, 0.2);
+            context.DrawEllipse(glowBrush, null, new Rect(
+                centerX - radius - 8,
+                centerY - radius - 8,
+                (radius + 8) * 2,
+                (radius + 8) * 2));
+        }
+
+        // 缁樺埗澶栧3
+        var outerPen = new Pen(new SolidColorBrush(Color.Parse("#455A64")), 3);
         context.DrawEllipse(null, outerPen, new Rect(
             centerX - radius,
             centerY - radius,
             radius * 2,
             radius * 2));
 
+        // 缁樺埗鍐呴儴鑳屾櫙
+        var innerBg = new SolidColorBrush(Color.Parse("#1E272E"));
+        context.DrawEllipse(innerBg, null, new Rect(
+            centerX - radius + 3,
+            centerY - radius + 3,
+            (radius - 3) * 2,
+            (radius - 3) * 2));
+
         // 缁樺埗娉靛彾鐗
-        var bladeCount = 3;
-        var bladeLength = radius * 0.7;
-        var bladeWidth = radius * 0.3;
+        var bladeCount = 6;
+        var bladeLength = radius * 0.65;
+        var bladeWidth = radius * 0.18;
 
         for (int i = 0; i < bladeCount; i++)
         {
@@ -155,37 +189,92 @@ public class PumpControl : Control
             var bladeX = centerX + Math.Cos(radians) * bladeLength * 0.5;
             var bladeY = centerY + Math.Sin(radians) * bladeLength * 0.5;
 
-            var bladeRect = new Rect(
-                bladeX - bladeWidth / 2,
-                bladeY - bladeLength / 2,
-                bladeWidth,
-                bladeLength);
+            // 缁樺埗鍙剁墖锛堝甫娓愬彉鏁堟灉锛
+            var bladeBrush = new SolidColorBrush(IsRunning
+                ? Color.FromArgb(200, statusBrush.Color.R, statusBrush.Color.G, statusBrush.Color.B)
+                : Color.Parse("#546E7A"));
+
+            var bladePath = new StreamGeometry();
+            using (var ctx = bladePath.Open())
+            {
+                ctx.BeginFigure(new Point(bladeX - bladeWidth / 2, bladeY - bladeLength / 2), true);
+                ctx.LineTo(new Point(bladeX + bladeWidth / 2, bladeY - bladeLength / 2));
+                ctx.LineTo(new Point(bladeX + bladeWidth / 2, bladeY + bladeLength / 2));
+                ctx.LineTo(new Point(bladeX - bladeWidth / 2, bladeY + bladeLength / 2));
+                ctx.EndFigure(true);
+            }
 
-            context.DrawRectangle(Color2, null, bladeRect);
+            // 鏃嬭浆鍙剁墖 - 浣跨敤鐭╅樀鍙樻崲
+            var rotationRadians = angle * Math.PI / 180;
+            var cos = Math.Cos(rotationRadians);
+            var sin = Math.Sin(rotationRadians);
+            var transform = new Matrix(cos, sin, -sin, cos, bladeX - bladeX * cos + bladeY * sin, bladeY - bladeX * sin - bladeY * cos);
+            using (context.PushTransform(transform))
+            {
+                context.DrawGeometry(bladeBrush, null, bladePath);
+            }
         }
 
-        // 缁樺埗涓績鍦
-        var centerRadius = radius * 0.2;
-        context.DrawEllipse(Color3, null, new Rect(
+        // 缁樺埗涓績鍦嗭紙甯︽笎鍙橈級
+        var centerRadius = radius * 0.25;
+        var centerBrush = new SolidColorBrush(IsRunning ? statusBrush.Color : Color.Parse("#455A64"));
+        context.DrawEllipse(centerBrush, null, new Rect(
             centerX - centerRadius,
             centerY - centerRadius,
             centerRadius * 2,
             centerRadius * 2));
 
-        // 缁樺埗鏂囧瓧
+        // 涓績楂樺厜
+        var highlightBrush = new SolidColorBrush(Colors.White, 0.3);
+        context.DrawEllipse(highlightBrush, null, new Rect(
+            centerX - centerRadius * 0.5,
+            centerY - centerRadius * 0.5,
+            centerRadius,
+            centerRadius));
+
+        // 缁樺埗鐘舵佹寚绀虹伅
+        var indicatorSize = 8;
+        var indicatorX = bounds.Width - 20;
+        var indicatorY = 10;
+        var indicatorBrush = IsRunning
+            ? new SolidColorBrush(Color.Parse("#4CAF50"))
+            : new SolidColorBrush(Color.Parse("#78909C"));
+
+        // 鎸囩ず鐏彂鍏
+        if (IsRunning)
+        {
+            context.DrawEllipse(new SolidColorBrush(indicatorBrush.Color, 0.3), null,
+                new Rect(indicatorX - 4, indicatorY - 4, 16, 16));
+        }
+
+        context.DrawEllipse(indicatorBrush, null,
+            new Rect(indicatorX, indicatorY, indicatorSize, indicatorSize));
+
+        // 缁樺埗鏂囧瓧鏍囩
         if (!string.IsNullOrEmpty(Text))
         {
-            var foreground = new SolidColorBrush(Colors.Black);
-            var text = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, foreground);
+            var textBrush = new SolidColorBrush(Colors.White);
+            var text = new FormattedText(Text, CultureInfo.CurrentCulture,
+                FlowDirection.LeftToRight, typeface, 11, textBrush);
             var textPoint = new Point(
                 centerX - text.Width / 2,
-                bounds.Bottom - text.Height - 2);
+                bounds.Bottom - text.Height - 5);
             context.DrawText(text, textPoint);
         }
+
+        // 缁樺埗棰戠巼鏄剧ず
+        if (IsRunning)
+        {
+            var freqStr = $"{Frequency:F0}Hz";
+            var freqText = new FormattedText(freqStr, CultureInfo.CurrentCulture,
+                FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"), 9,
+                new SolidColorBrush(statusBrush.Color));
+            context.DrawText(freqText, new Point(centerX - freqText.Width / 2, centerY + radius + 5));
+        }
     }
 
     protected override Size MeasureOverride(Size availableSize)
     {
-        return new Size(60, 60);
+        return new Size(70, 80);
     }
 }

+ 136 - 0
src/YZWater.Avalonia/Controls/StatusCard.cs

@@ -0,0 +1,136 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using System;
+using System.Globalization;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 鐘舵佸崱鐗囨帶浠 - 鏄剧ず璁惧鐘舵佷俊鎭
+/// </summary>
+public class StatusCard : Control
+{
+    public static readonly StyledProperty<string> TitleProperty =
+        AvaloniaProperty.Register<StatusCard, string>(nameof(Title), "璁惧");
+
+    public static readonly StyledProperty<string> StatusProperty =
+        AvaloniaProperty.Register<StatusCard, string>(nameof(Status), "杩愯涓");
+
+    public static readonly StyledProperty<string> IconProperty =
+        AvaloniaProperty.Register<StatusCard, string>(nameof(Icon), "鈿欙笍");
+
+    public static readonly StyledProperty<bool> IsActiveProperty =
+        AvaloniaProperty.Register<StatusCard, bool>(nameof(IsActive), false);
+
+    public static readonly StyledProperty<IBrush> ActiveColorProperty =
+        AvaloniaProperty.Register<StatusCard, IBrush>(nameof(ActiveColor), new SolidColorBrush(Color.Parse("#4CAF50")));
+
+    public static readonly StyledProperty<IBrush> InactiveColorProperty =
+        AvaloniaProperty.Register<StatusCard, IBrush>(nameof(InactiveColor), new SolidColorBrush(Color.Parse("#607D8B")));
+
+    public string Title
+    {
+        get => GetValue(TitleProperty);
+        set => SetValue(TitleProperty, value);
+    }
+
+    public string Status
+    {
+        get => GetValue(StatusProperty);
+        set => SetValue(StatusProperty, value);
+    }
+
+    public string Icon
+    {
+        get => GetValue(IconProperty);
+        set => SetValue(IconProperty, value);
+    }
+
+    public bool IsActive
+    {
+        get => GetValue(IsActiveProperty);
+        set => SetValue(IsActiveProperty, value);
+    }
+
+    public IBrush ActiveColor
+    {
+        get => GetValue(ActiveColorProperty);
+        set => SetValue(ActiveColorProperty, value);
+    }
+
+    public IBrush InactiveColor
+    {
+        get => GetValue(InactiveColorProperty);
+        set => SetValue(InactiveColorProperty, value);
+    }
+
+    static StatusCard()
+    {
+        AffectsRender<StatusCard>(TitleProperty, StatusProperty, IconProperty,
+            IsActiveProperty, ActiveColorProperty, InactiveColorProperty);
+    }
+
+    public override void Render(DrawingContext context)
+    {
+        base.Render(context);
+
+        var bounds = new Rect(Bounds.Size);
+        var typeface = new Typeface("Microsoft YaHei");
+        var statusColor = IsActive ? ActiveColor : InactiveColor;
+        var statusBrush = statusColor as SolidColorBrush ?? new SolidColorBrush(Colors.Gray);
+
+        // 缁樺埗鍗$墖鑳屾櫙
+        var cardBg = new SolidColorBrush(Color.Parse("#1E272E"));
+        var cardPen = new Pen(new SolidColorBrush(Color.Parse("#37474F")), 1);
+        context.DrawRectangle(cardBg, cardPen, new Rect(0, 0, bounds.Width, bounds.Height));
+
+        // 缁樺埗宸︿晶鐘舵佹潯
+        context.DrawRectangle(statusColor, null, new Rect(0, 0, 4, bounds.Height));
+
+        // 缁樺埗鐘舵佹寚绀虹伅
+        var indicatorSize = 10;
+        var indicatorX = 15;
+        var indicatorY = bounds.Height / 2 - indicatorSize / 2;
+
+        // 鎸囩ず鐏彂鍏夋晥鏋
+        if (IsActive)
+        {
+            context.DrawEllipse(new SolidColorBrush(statusBrush.Color, 0.3), null,
+                new Rect(indicatorX - 3, indicatorY - 3, indicatorSize + 6, indicatorSize + 6));
+        }
+
+        context.DrawEllipse(statusColor, null,
+            new Rect(indicatorX, indicatorY, indicatorSize, indicatorSize));
+
+        // 缁樺埗鏍囬
+        var titleText = new FormattedText(Title, CultureInfo.CurrentCulture,
+            FlowDirection.LeftToRight, new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold),
+            12, new SolidColorBrush(Colors.White));
+        context.DrawText(titleText, new Point(35, 8));
+
+        // 缁樺埗鐘舵佹枃鏈
+        var statusText = new FormattedText(Status, CultureInfo.CurrentCulture,
+            FlowDirection.LeftToRight, typeface, 10, new SolidColorBrush(statusBrush.Color));
+        context.DrawText(statusText, new Point(35, 25));
+
+        // 缁樺埗鍒嗛殧绾
+        var separatorY = bounds.Height - 25;
+        context.DrawLine(new Pen(new SolidColorBrush(Color.Parse("#37474F")), 1),
+            new Point(10, separatorY), new Point(bounds.Width - 10, separatorY));
+
+        // 缁樺埗鍥炬爣
+        if (!string.IsNullOrEmpty(Icon))
+        {
+            var iconText = new FormattedText(Icon, CultureInfo.CurrentCulture,
+                FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"), 20,
+                new SolidColorBrush(Colors.White));
+            context.DrawText(iconText, new Point(bounds.Width - 35, 10));
+        }
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        return new Size(150, 60);
+    }
+}

+ 47 - 38
src/YZWater.Avalonia/Controls/WaterTankControl.cs

@@ -1,12 +1,13 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Media;
+using System;
 using System.Globalization;
 
 namespace YZWater.Avalonia.Controls;
 
 /// <summary>
-/// 姘寸鎺т欢 - 妯℃嫙 HslWaterBox
+/// 姘寸鎺т欢锛堢畝鍖栫増锛
 /// </summary>
 public class WaterTankControl : Control
 {
@@ -17,13 +18,10 @@ public class WaterTankControl : Control
         AvaloniaProperty.Register<WaterTankControl, string>(nameof(Text), string.Empty);
 
     public static readonly StyledProperty<IBrush> WaterColorProperty =
-        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(WaterColor), new SolidColorBrush(Color.Parse("#178187")));
+        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(WaterColor), new SolidColorBrush(Color.Parse("#00BCD4")));
 
     public static readonly StyledProperty<IBrush> BorderColorProperty =
-        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(BorderColor), new SolidColorBrush(Colors.LightGray));
-
-    public static readonly StyledProperty<double> EdgeWidthProperty =
-        AvaloniaProperty.Register<WaterTankControl, double>(nameof(EdgeWidth), 2.0);
+        AvaloniaProperty.Register<WaterTankControl, IBrush>(nameof(BorderColor), new SolidColorBrush(Color.Parse("#37474F")));
 
     public double WaterLevel
     {
@@ -49,16 +47,9 @@ public class WaterTankControl : Control
         set => SetValue(BorderColorProperty, value);
     }
 
-    public double EdgeWidth
-    {
-        get => GetValue(EdgeWidthProperty);
-        set => SetValue(EdgeWidthProperty, value);
-    }
-
     static WaterTankControl()
     {
-        AffectsRender<WaterTankControl>(WaterLevelProperty, TextProperty, WaterColorProperty, BorderColorProperty, EdgeWidthProperty);
-        AffectsMeasure<WaterTankControl>(WaterLevelProperty, TextProperty, WaterColorProperty, BorderColorProperty, EdgeWidthProperty);
+        AffectsRender<WaterTankControl>(WaterLevelProperty, TextProperty, WaterColorProperty, BorderColorProperty);
     }
 
     public override void Render(DrawingContext context)
@@ -66,48 +57,66 @@ public class WaterTankControl : Control
         base.Render(context);
 
         var bounds = new Rect(Bounds.Size);
-        var edgeWidth = EdgeWidth;
-        var typeface = new Typeface("Microsoft YaHei");
-        var foreground = new SolidColorBrush(Colors.White);
+        var typeface = new Typeface("Microsoft YaHei", FontStyle.Normal, FontWeight.Bold);
+        var edgeWidth = 3.0;
+
+        // 缁樺埗鑳屾櫙
+        var bgBrush = new SolidColorBrush(Color.Parse("#263238"));
+        context.DrawRectangle(bgBrush, null, bounds);
 
         // 缁樺埗杈规
         var borderPen = new Pen(BorderColor, edgeWidth);
         context.DrawRectangle(null, borderPen, bounds);
 
         // 璁$畻姘翠綅楂樺害
-        var waterHeight = bounds.Height * (WaterLevel / 100.0);
-        var waterRect = new Rect(
-            bounds.X + edgeWidth,
-            bounds.Y + bounds.Height - waterHeight,
-            bounds.Width - edgeWidth * 2,
-            waterHeight - edgeWidth);
+        var waterHeight = (bounds.Height - edgeWidth * 2) * Math.Max(0, Math.Min(100, WaterLevel)) / 100.0;
+        var waterTop = bounds.Y + bounds.Height - edgeWidth - waterHeight;
 
         // 缁樺埗姘
-        if (waterHeight > edgeWidth)
+        if (waterHeight > 0)
         {
-            context.DrawRectangle(WaterColor, null, waterRect);
+            var waterColor = WaterColor as SolidColorBrush;
+            var waterBrush = waterColor != null
+                ? new SolidColorBrush(waterColor.Color, 0.8)
+                : WaterColor;
+
+            var waterRect = new Rect(
+                bounds.X + edgeWidth,
+                waterTop,
+                bounds.Width - edgeWidth * 2,
+                waterHeight);
+            context.DrawRectangle(waterBrush, null, waterRect);
         }
 
-        // 缁樺埗鏂囧瓧
+        // 缁樺埗鏍囬
         if (!string.IsNullOrEmpty(Text))
         {
-            var text = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 14, foreground);
-            var textPoint = new Point(
-                bounds.X + (bounds.Width - text.Width) / 2,
-                bounds.Y + (bounds.Height - text.Height) / 2);
-            context.DrawText(text, textPoint);
+            var titleBrush = new SolidColorBrush(Colors.White);
+            var titleText = new FormattedText(Text, CultureInfo.CurrentCulture,
+                FlowDirection.LeftToRight, typeface, 12, titleBrush);
+            var titlePoint = new Point(
+                bounds.X + (bounds.Width - titleText.Width) / 2,
+                bounds.Y + edgeWidth + 5);
+            context.DrawText(titleText, titlePoint);
         }
 
-        // 缁樺埗姘翠綅鐧惧垎姣
-        var levelText = new FormattedText($"{WaterLevel:F0}%", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12, foreground);
-        var levelPoint = new Point(
-            bounds.X + (bounds.Width - levelText.Width) / 2,
-            bounds.Y + 5);
-        context.DrawText(levelText, levelPoint);
+        // 缁樺埗娑蹭綅鐧惧垎姣
+        var levelStr = $"{WaterLevel:F1}%";
+        var levelText = new FormattedText(levelStr, CultureInfo.CurrentCulture,
+            FlowDirection.LeftToRight, typeface, 14, new SolidColorBrush(Colors.White));
+
+        var levelBgRect = new Rect(
+            bounds.X + (bounds.Width - levelText.Width - 10) / 2,
+            bounds.Y + bounds.Height - edgeWidth - levelText.Height - 10,
+            levelText.Width + 10,
+            levelText.Height + 6);
+
+        context.DrawRectangle(new SolidColorBrush(Colors.Black, 0.5), null, levelBgRect);
+        context.DrawText(levelText, new Point(levelBgRect.X + 5, levelBgRect.Y + 3));
     }
 
     protected override Size MeasureOverride(Size availableSize)
     {
-        return new Size(100, 120);
+        return new Size(120, 160);
     }
 }

+ 229 - 157
src/YZWater.Avalonia/Views/ViewAView.axaml

@@ -9,167 +9,239 @@
         <vm:ViewAViewModel/>
     </UserControl.DataContext>
 
-    <!-- 浣跨敤鍥哄畾灏哄璁捐锛岀敱澶栧眰瀹瑰櫒璐熻矗缂╂斁 -->
-    <Border Background="#1a1a2e" Width="1024" Height="613">
-        <Canvas Width="1024" Height="613">
-
-            <!-- 妯″紡鍒囨崲寮鍏 -->
-            <controls:ValveControl Canvas.Left="893" Canvas.Top="9" Width="129" Height="108"
-                                   Status="{Binding ModeStatus}" Text="杩滅▼/鏈湴"/>
-
-            <!-- 鍏ュ彛姘寸 -->
-            <controls:WaterTankControl Canvas.Left="44" Canvas.Top="113" Width="134" Height="100"
-                                       WaterLevel="{Binding Tank1Level}" Text="鍏ュ彛姹"
-                                       WaterColor="#178187" BorderColor="#cfcfcf"/>
-
-            <!-- 姹1 -->
-            <Border BorderBrush="#cfcfcf" BorderThickness="8" Height="135" Canvas.Left="248" Canvas.Top="113" Width="138"/>
-            <controls:WaterTankControl Canvas.Left="250" Canvas.Top="121" Width="134" Height="127"
-                                       WaterLevel="{Binding Tank1Level}" Text=""
-                                       WaterColor="#178187" BorderColor="#cfcfcf"/>
-
-            <!-- 姹2 -->
-            <controls:WaterTankControl Canvas.Left="250" Canvas.Top="248" Width="134" Height="87"
-                                       WaterLevel="{Binding Tank2Level}" Text=""
-                                       WaterColor="#178187" BorderColor="#cfcfcf"/>
-
-            <!-- 澶ф按姹 -->
-            <controls:WaterTankControl Canvas.Left="250" Canvas.Top="335" Width="748" Height="183"
-                                       WaterLevel="{Binding Tank3Level}" Text=""
-                                       WaterColor="#178187" BorderColor="#cfcfcf"/>
-            <Border BorderBrush="#cfcfcf" BorderThickness="8" Height="191" Canvas.Left="248" Canvas.Top="327" Width="138"/>
-
-            <!-- 鍑哄彛姹 -->
-            <controls:WaterTankControl Canvas.Left="44" Canvas.Top="267" Width="134" Height="100"
-                                       WaterLevel="{Binding Tank4Level}" Text="鍑哄彛姹"
-                                       WaterColor="#178187" BorderColor="#cfcfcf"/>
-
-            <!-- 绠¢亾杩炴帴绾 -->
-            <Rectangle Fill="#178187" Height="12" Canvas.Left="358" Stroke="Transparent" Canvas.Top="480" Width="48"
-                       RenderTransformOrigin="0.5,0.5">
-                <Rectangle.RenderTransform>
-                    <RotateTransform Angle="-90"/>
-                </Rectangle.RenderTransform>
-            </Rectangle>
-
-            <!-- 娑蹭綅鏄剧ず -->
-            <TextBlock Canvas.Left="676" Canvas.Top="414" Width="70" Foreground="White" FontSize="12">
-                <Run Text="娑蹭綅3:"/><Run Text="{Binding Tank3Level, StringFormat='{}{0:F1}'}"/><Run Text="绫"/>
-            </TextBlock>
-            <TextBlock Canvas.Left="288" Canvas.Top="147" Width="70" Foreground="White" FontSize="12">
-                <Run Text="姹1"/><LineBreak/><Run Text="娑蹭綅1:"/><Run Text="{Binding Tank1Level, StringFormat='{}{0:F1}'}"/><Run Text="绫"/>
-            </TextBlock>
-            <TextBlock Canvas.Left="286" Canvas.Top="415" Width="78" Foreground="White" FontSize="12">
-                <Run Text="姹2"/><LineBreak/><Run Text="娑蹭綅2:"/><Run Text="{Binding Tank2Level, StringFormat='{}{0:F1}'}"/><Run Text="绫"/>
-            </TextBlock>
-
-            <!-- 闃闂ㄦ帶浠 -->
-            <controls:ValveControl Canvas.Left="147" Canvas.Top="168" Width="22" Height="22"
-                                   Status="{Binding Valve1Status}" Text=""/>
-            <controls:ValveControl Canvas.Left="279" Canvas.Top="205" Width="22" Height="22"
-                                   Status="{Binding Valve2Status}" Text=""/>
-            <controls:ValveControl Canvas.Left="336" Canvas.Top="206" Width="22" Height="22"
-                                   Status="{Binding Valve3Status}" Text=""/>
-            <controls:ValveControl Canvas.Left="278" Canvas.Top="298" Width="22" Height="22"
-                                   Status="{Binding Valve4Status}" Text=""/>
-            <controls:ValveControl Canvas.Left="337" Canvas.Top="298" Width="22" Height="22"
-                                   Status="{Binding Valve5Status}" Text=""/>
-
-            <!-- 绠¢亾 -->
-            <controls:PipeLineControl Canvas.Left="0" Canvas.Top="306" Width="52" Height="29"
-                                      IsFlow="{Binding IsInflowRunning}" IsHorizontal="True"/>
-            <controls:PipeLineControl Canvas.Left="0" Canvas.Top="164" Width="52" Height="29"
-                                      IsFlow="{Binding IsOutflowRunning}" IsHorizontal="True"
-                                      RenderTransformOrigin="0.5,0.5">
-                <controls:PipeLineControl.RenderTransform>
-                    <RotateTransform Angle="180"/>
-                </controls:PipeLineControl.RenderTransform>
-            </controls:PipeLineControl>
-
-            <!-- 娉1-5 -->
-            <controls:PumpControl Canvas.Left="394" Canvas.Top="427" Width="60" Height="63"
-                                  IsRunning="{Binding Pump1Running}" Speed="2" Text="娉1"/>
-            <controls:PumpControl Canvas.Left="504" Canvas.Top="426" Width="60" Height="63"
-                                  IsRunning="{Binding Pump2Running}" Speed="2" Text="娉2"/>
-            <controls:PumpControl Canvas.Left="617" Canvas.Top="426" Width="60" Height="63"
-                                  IsRunning="{Binding Pump3Running}" Speed="2" Text="娉3"/>
-            <controls:PumpControl Canvas.Left="730" Canvas.Top="427" Width="60" Height="63"
-                                  IsRunning="{Binding Pump4Running}" Speed="2" Text="娉4"/>
-            <controls:PumpControl Canvas.Left="843" Canvas.Top="426" Width="60" Height="63"
-                                  IsRunning="{Binding Pump5Running}" Speed="2" Text="娉5"/>
-
-            <!-- 椋庢墖 -->
-            <controls:FanControl Canvas.Left="412" Canvas.Top="441" Width="34" Height="32"
-                                 IsRunning="{Binding Fan1Running}" Speed="1" Text="椋庢墖1"/>
-            <controls:FanControl Canvas.Left="522" Canvas.Top="441" Width="34" Height="32"
-                                 IsRunning="{Binding Fan2Running}" Speed="1" Text="椋庢墖2"/>
-
-            <!-- 娴侀噺鏄剧ず -->
-            <Border Background="#2d2d44" CornerRadius="5" Canvas.Left="10" Canvas.Top="550" Padding="10">
-                <StackPanel Spacing="10">
-                    <TextBlock Foreground="#4CAF50" FontWeight="Bold" FontSize="14">
-                        <Run Text="杩涙按娴侀噺: "/>
-                        <Run Text="{Binding InflowRate, StringFormat='{}{0:F1}'}"/>
-                        <Run Text=" m鲁/h"/>
-                    </TextBlock>
-                    <TextBlock Foreground="#2196F3" FontWeight="Bold" FontSize="14">
-                        <Run Text="鍑烘按娴侀噺: "/>
-                        <Run Text="{Binding OutflowRate, StringFormat='{}{0:F1}'}"/>
-                        <Run Text=" m鲁/h"/>
-                    </TextBlock>
-                </StackPanel>
-            </Border>
-
-            <!-- 鎶ヨ淇℃伅 -->
-            <Border Background="#F44336" CornerRadius="5" Canvas.Left="259" Canvas.Top="544" Width="685" Height="43"
-                    IsVisible="{Binding HasAlarm}">
-                <TextBlock Text="{Binding AlarmMessage}" FontSize="20" Foreground="White"
-                           HorizontalAlignment="Center" VerticalAlignment="Center"/>
-            </Border>
-
-            <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
-            <StackPanel Canvas.Left="10" Canvas.Top="10" Spacing="5">
-                <Button Content="杩炴帴PLC" Command="{Binding ConnectPlcCommand}" Width="80" Height="30"/>
-                <Button Content="鏂紑PLC" Command="{Binding DisconnectPlcCommand}" Width="80" Height="30"/>
-                <Button Content="鍒锋柊鏁版嵁" Command="{Binding RefreshDataCommand}" Width="80" Height="30"/>
-            </StackPanel>
-
-            <!-- 璁惧鐘舵侀潰鏉 -->
-            <Border Background="#2d2d44" CornerRadius="5" Canvas.Left="850" Canvas.Top="130" Width="160" Height="300" Padding="10">
-                <StackPanel Spacing="5">
-                    <TextBlock Text="璁惧鐘舵" FontWeight="Bold" Foreground="White" FontSize="14"/>
-                    <Separator Background="#444"/>
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <Ellipse Width="10" Height="10" Fill="{Binding Pump1StatusColor}"/>
-                        <TextBlock Text="娉1" Foreground="White"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <Ellipse Width="10" Height="10" Fill="{Binding Pump2StatusColor}"/>
-                        <TextBlock Text="娉2" Foreground="White"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <Ellipse Width="10" Height="10" Fill="{Binding Pump3StatusColor}"/>
-                        <TextBlock Text="娉3" Foreground="White"/>
+    <Border Background="#0D1117" Width="1280" Height="800">
+        <Grid RowDefinitions="Auto,*,Auto" Margin="15">
+
+            <!-- 椤堕儴宸ュ叿鏍 -->
+            <Border Grid.Row="0" Background="#161B22" CornerRadius="8" Padding="12" Margin="0,0,0,10">
+                <Grid ColumnDefinitions="Auto,*,Auto,Auto">
+                    <!-- 宸︿晶鏍囬 -->
+                    <StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="10">
+                        <TextBlock Text="馃彮" FontSize="20" VerticalAlignment="Center"/>
+                        <StackPanel>
+                            <TextBlock Text="姹℃按澶勭悊鐩戞帶绯荤粺" FontWeight="Bold" FontSize="16" Foreground="White"/>
+                            <TextBlock Text="瀹炴椂宸ヨ壓娴佺▼" FontSize="11" Foreground="#8B949E"/>
+                        </StackPanel>
                     </StackPanel>
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <Ellipse Width="10" Height="10" Fill="{Binding Pump4StatusColor}"/>
-                        <TextBlock Text="娉4" Foreground="White"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <Ellipse Width="10" Height="10" Fill="{Binding Pump5StatusColor}"/>
-                        <TextBlock Text="娉5" Foreground="White"/>
+
+                    <!-- 涓棿杩炴帴鐘舵 -->
+                    <StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="15" Margin="20,0">
+                        <Border Background="#238636" CornerRadius="12" Padding="10,5">
+                            <StackPanel Orientation="Horizontal" Spacing="5">
+                                <Ellipse Width="8" Height="8" Fill="White"/>
+                                <TextBlock Text="PLC 宸茶繛鎺" Foreground="White" FontSize="11"/>
+                            </StackPanel>
+                        </Border>
+                        <Button Content="杩炴帴" Command="{Binding ConnectPlcCommand}" Padding="12,4"/>
+                        <Button Content="鏂紑" Command="{Binding DisconnectPlcCommand}" Padding="12,4"/>
+                        <Button Content="鍒锋柊" Command="{Binding RefreshDataCommand}" Padding="12,4"/>
                     </StackPanel>
-                    <Separator Background="#444"/>
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <Ellipse Width="10" Height="10" Fill="{Binding Fan1StatusColor}"/>
-                        <TextBlock Text="椋庢墖1" Foreground="White"/>
+
+                    <!-- 鍙充晶鏃堕棿 -->
+                    <StackPanel Grid.Column="3" VerticalAlignment="Center">
+                        <TextBlock Text="{Binding CurrentTime, StringFormat='{}{0:HH:mm:ss}'}"
+                                   FontSize="18" FontWeight="Bold" Foreground="#58A6FF"/>
+                        <TextBlock Text="{Binding CurrentTime, StringFormat='{}{0:yyyy-MM-dd}'}"
+                                   FontSize="11" Foreground="#8B949E" HorizontalAlignment="Right"/>
                     </StackPanel>
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <Ellipse Width="10" Height="10" Fill="{Binding Fan2StatusColor}"/>
-                        <TextBlock Text="椋庢墖2" Foreground="White"/>
+                </Grid>
+            </Border>
+
+            <!-- 涓棿涓诲唴瀹瑰尯 -->
+            <Grid Grid.Row="1" ColumnDefinitions="*,280">
+
+                <!-- 宸︿晶宸ヨ壓娴佺▼鍥 -->
+                <Border Grid.Column="0" Background="#161B22" CornerRadius="8" Padding="10" Margin="0,0,10,0">
+                    <Canvas Width="900" Height="550">
+
+                        <!-- 姘寸缁 -->
+                        <StackPanel Orientation="Horizontal" Canvas.Left="20" Canvas.Top="20" Spacing="30">
+                            <!-- 鍏ュ彛姹 -->
+                            <StackPanel>
+                                <controls:WaterTankControl Width="120" Height="160"
+                                                           WaterLevel="{Binding Tank1Level}" Text="鍏ュ彛姹"
+                                                           WaterColor="#00BCD4"/>
+                                <TextBlock Text="{Binding Tank1Level, StringFormat='娑蹭綅: {0:F1}%'}"
+                                           Foreground="#8B949E" FontSize="11" HorizontalAlignment="Center" Margin="0,5,0,0"/>
+                            </StackPanel>
+
+                            <!-- 姹1 -->
+                            <StackPanel>
+                                <controls:WaterTankControl Width="120" Height="160"
+                                                           WaterLevel="{Binding Tank2Level}" Text="鐢熷寲姹"
+                                                           WaterColor="#26A69A"/>
+                                <TextBlock Text="{Binding Tank2Level, StringFormat='娑蹭綅: {0:F1}%'}"
+                                           Foreground="#8B949E" FontSize="11" HorizontalAlignment="Center" Margin="0,5,0,0"/>
+                            </StackPanel>
+
+                            <!-- 姹2 -->
+                            <StackPanel>
+                                <controls:WaterTankControl Width="120" Height="160"
+                                                           WaterLevel="{Binding Tank3Level}" Text="娌夋穩姹"
+                                                           WaterColor="#42A5F5"/>
+                                <TextBlock Text="{Binding Tank3Level, StringFormat='娑蹭綅: {0:F1}%'}"
+                                           Foreground="#8B949E" FontSize="11" HorizontalAlignment="Center" Margin="0,5,0,0"/>
+                            </StackPanel>
+
+                            <!-- 鍑哄彛姹 -->
+                            <StackPanel>
+                                <controls:WaterTankControl Width="120" Height="160"
+                                                           WaterLevel="{Binding Tank4Level}" Text="鍑哄彛姹"
+                                                           WaterColor="#66BB6A"/>
+                                <TextBlock Text="{Binding Tank4Level, StringFormat='娑蹭綅: {0:F1}%'}"
+                                           Foreground="#8B949E" FontSize="11" HorizontalAlignment="Center" Margin="0,5,0,0"/>
+                            </StackPanel>
+                        </StackPanel>
+
+                        <!-- 绠¢亾杩炴帴 -->
+                        <controls:PipeLineControl Canvas.Left="20" Canvas.Top="200" Width="540" Height="25"
+                                                  IsFlow="{Binding IsInflowRunning}" IsHorizontal="True"/>
+
+                        <!-- 闃闂ㄧ粍 -->
+                        <StackPanel Orientation="Horizontal" Canvas.Left="50" Canvas.Top="240" Spacing="80">
+                            <controls:ValveControl Width="40" Height="50" Status="{Binding Valve1Status}" Text="杩涙按"/>
+                            <controls:ValveControl Width="40" Height="50" Status="{Binding Valve2Status}" Text="鏇濇皵"/>
+                            <controls:ValveControl Width="40" Height="50" Status="{Binding Valve3Status}" Text="鍥炴祦"/>
+                            <controls:ValveControl Width="40" Height="50" Status="{Binding Valve4Status}" Text="鍑烘按"/>
+                        </StackPanel>
+
+                        <!-- 娉电粍 -->
+                        <StackPanel Orientation="Horizontal" Canvas.Left="30" Canvas.Top="310" Spacing="25">
+                            <controls:PumpControl Width="70" Height="80"
+                                                  IsRunning="{Binding Pump1Running}" Frequency="50" Text="杩涙按娉1"/>
+                            <controls:PumpControl Width="70" Height="80"
+                                                  IsRunning="{Binding Pump2Running}" Frequency="45" Text="杩涙按娉2"/>
+                            <controls:PumpControl Width="70" Height="80"
+                                                  IsRunning="{Binding Pump3Running}" Frequency="40" Text="鍥炴祦娉"/>
+                            <controls:PumpControl Width="70" Height="80"
+                                                  IsRunning="{Binding Pump4Running}" Frequency="35" Text="鎺掓偿娉"/>
+                            <controls:PumpControl Width="70" Height="80"
+                                                  IsRunning="{Binding Pump5Running}" Frequency="55" Text="鍔犺嵂娉"/>
+                        </StackPanel>
+
+                        <!-- 椋庢満缁 -->
+                        <StackPanel Orientation="Horizontal" Canvas.Left="120" Canvas.Top="410" Spacing="50">
+                            <controls:FanControl Width="50" Height="50" IsRunning="{Binding Fan1Running}" Speed="1" Text="椋庢満1"/>
+                            <controls:FanControl Width="50" Height="50" IsRunning="{Binding Fan2Running}" Speed="1.5" Text="椋庢満2"/>
+                        </StackPanel>
+
+                        <!-- 妯″紡鍒囨崲 -->
+                        <Border Canvas.Left="750" Canvas.Top="20" Width="120" Height="50"
+                                Background="#21262D" CornerRadius="8">
+                            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
+                                <TextBlock Text="杩愯妯″紡" FontSize="10" Foreground="#8B949E" HorizontalAlignment="Center"/>
+                                <controls:ValveControl Width="30" Height="30"
+                                                       Status="{Binding ModeStatus}" Text=""/>
+                            </StackPanel>
+                        </Border>
+
+                        <!-- 娴佺▼绠ご -->
+                        <Path Data="M 140 100 L 200 100" Stroke="#58A6FF" StrokeThickness="2">
+                            <Path.Data>
+                                <StreamGeometry>M 140 100 L 200 100</StreamGeometry>
+                            </Path.Data>
+                        </Path>
+                        <Path Data="M 320 100 L 380 100" Stroke="#58A6FF" StrokeThickness="2"/>
+                        <Path Data="M 500 100 L 560 100" Stroke="#58A6FF" StrokeThickness="2"/>
+                    </Canvas>
+                </Border>
+
+                <!-- 鍙充晶闈㈡澘 -->
+                <Grid Grid.Column="1" RowDefinitions="Auto,Auto,Auto,*,Auto">
+
+                    <!-- 娴侀噺浠〃 -->
+                    <Border Grid.Row="0" Background="#161B22" CornerRadius="8" Padding="10" Margin="0,0,0,10">
+                        <StackPanel>
+                            <TextBlock Text="娴侀噺鐩戞祴" FontWeight="Bold" Foreground="White" Margin="0,0,0,10"/>
+                            <Grid ColumnDefinitions="*,*">
+                                <controls:GaugeControl Grid.Column="0" Width="120" Height="140"
+                                                       Value="{Binding InflowRate}" Title="杩涙按" Unit="m鲁/h"
+                                                       MaxValue="100"/>
+                                <controls:GaugeControl Grid.Column="1" Width="120" Height="140"
+                                                       Value="{Binding OutflowRate}" Title="鍑烘按" Unit="m鲁/h"
+                                                       MaxValue="100"
+                                                       ValueColor="#66BB6A"/>
+                            </Grid>
+                        </StackPanel>
+                    </Border>
+
+                    <!-- 璁惧鐘舵 -->
+                    <Border Grid.Row="1" Background="#161B22" CornerRadius="8" Padding="10" Margin="0,0,0,10">
+                        <StackPanel>
+                            <TextBlock Text="璁惧鐘舵" FontWeight="Bold" Foreground="White" Margin="0,0,0,10"/>
+                            <StackPanel Spacing="5">
+                                <controls:StatusCard Title="杩涙按娉1" Status="{Binding Pump1Status}" Icon="馃攧"
+                                                     IsActive="{Binding Pump1Running}"/>
+                                <controls:StatusCard Title="杩涙按娉2" Status="{Binding Pump2Status}" Icon="馃攧"
+                                                     IsActive="{Binding Pump2Running}"/>
+                                <controls:StatusCard Title="鍥炴祦娉" Status="{Binding Pump3Status}" Icon="馃攧"
+                                                     IsActive="{Binding Pump3Running}"/>
+                                <controls:StatusCard Title="椋庢満1" Status="{Binding Fan1Status}" Icon="馃寑"
+                                                     IsActive="{Binding Fan1Running}"/>
+                                <controls:StatusCard Title="椋庢満2" Status="{Binding Fan2Status}" Icon="馃寑"
+                                                     IsActive="{Binding Fan2Running}"/>
+                            </StackPanel>
+                        </StackPanel>
+                    </Border>
+
+                    <!-- 鎶ヨ淇℃伅 -->
+                    <Border Grid.Row="2" Background="#161B22" CornerRadius="8" Padding="10" Margin="0,0,0,10"
+                            IsVisible="{Binding HasAlarm}">
+                        <StackPanel>
+                            <TextBlock Text="鈿狅笍 鎶ヨ淇℃伅" FontWeight="Bold" Foreground="#F85149" Margin="0,0,0,10"/>
+                            <Border Background="#F85149" CornerRadius="5" Padding="10">
+                                <TextBlock Text="{Binding AlarmMessage}" Foreground="White" TextWrapping="Wrap"/>
+                            </Border>
+                        </StackPanel>
+                    </Border>
+
+                    <!-- 蹇嵎鎿嶄綔 -->
+                    <Border Grid.Row="4" Background="#161B22" CornerRadius="8" Padding="10">
+                        <StackPanel>
+                            <TextBlock Text="蹇嵎鎿嶄綔" FontWeight="Bold" Foreground="White" Margin="0,0,0,10"/>
+                            <WrapPanel HorizontalAlignment="Center">
+                                <Button Content="鍏ㄩ儴鍚姩" Margin="5" Padding="12,6"/>
+                                <Button Content="鍏ㄩ儴鍋滄" Margin="5" Padding="12,6"/>
+                                <Button Content="鎶ヨ纭" Margin="5" Padding="12,6"/>
+                                <Button Content="鏁版嵁瀵煎嚭" Margin="5" Padding="12,6"/>
+                            </WrapPanel>
+                        </StackPanel>
+                    </Border>
+                </Grid>
+            </Grid>
+
+            <!-- 搴曢儴鐘舵佹爮 -->
+            <Border Grid.Row="2" Background="#161B22" CornerRadius="8" Padding="12" Margin="0,10,0,0">
+                <Grid ColumnDefinitions="Auto,*,Auto,Auto,Auto">
+                    <StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="20">
+                        <TextBlock Foreground="#8B949E" FontSize="11">
+                            <Run Text="杩涙按: "/>
+                            <Run Text="{Binding InflowRate, StringFormat='{}{0:F1} m鲁/h'}" Foreground="#58A6FF"/>
+                        </TextBlock>
+                        <TextBlock Foreground="#8B949E" FontSize="11">
+                            <Run Text="鍑烘按: "/>
+                            <Run Text="{Binding OutflowRate, StringFormat='{}{0:F1} m鲁/h'}" Foreground="#66BB6A"/>
+                        </TextBlock>
                     </StackPanel>
-                </StackPanel>
+
+                    <TextBlock Grid.Column="2" Foreground="#8B949E" FontSize="11">
+                        <Run Text="杩愯璁惧: "/>
+                        <Run Text="{Binding RunningDeviceCount}" Foreground="#58A6FF"/>
+                        <Run Text=" 鍙"/>
+                    </TextBlock>
+
+                    <TextBlock Grid.Column="3" Foreground="#8B949E" FontSize="11" Margin="20,0">
+                        <Run Text="鎶ヨ: "/>
+                        <Run Text="{Binding AlarmCount}" Foreground="#F85149"/>
+                        <Run Text=" 鏉"/>
+                    </TextBlock>
+
+                    <TextBlock Grid.Column="4" Foreground="#8B949E" FontSize="11">
+                        <Run Text="绯荤粺杩愯: "/>
+                        <Run Text="{Binding RunningTime}" Foreground="#58A6FF"/>
+                    </TextBlock>
+                </Grid>
             </Border>
-        </Canvas>
+        </Grid>
     </Border>
 </UserControl>

+ 193 - 179
src/YZWater.Core/ViewModels/ViewAViewModel.cs

@@ -7,186 +7,175 @@ using YZWater.Core.Services;
 namespace YZWater.Core.ViewModels;
 
 /// <summary>
-/// 涓诲伐鑹鸿鍥 ViewModel
+/// 涓诲伐鑹鸿鍥 ViewModel锛堜紭鍖栫増锛
 /// </summary>
 public partial class ViewAViewModel : ObservableObject
 {
+    private DateTime _startTime = DateTime.Now;
+
     // 娑蹭綅鏁版嵁
     [ObservableProperty]
-    private float _tank1Level = 50f;
+    private float _tank1Level = 65.5f;
 
     [ObservableProperty]
-    private float _tank2Level = 60f;
+    private float _tank2Level = 72.3f;
 
     [ObservableProperty]
-    private float _tank3Level = 70f;
+    private float _tank3Level = 58.8f;
 
     [ObservableProperty]
-    private float _tank4Level = 45f;
+    private float _tank4Level = 45.2f;
 
     // 娴侀噺鏁版嵁
     [ObservableProperty]
-    private float _inflowRate = 25.5f;
+    private float _inflowRate = 35.6f;
 
     [ObservableProperty]
-    private float _outflowRate = 22.3f;
+    private float _outflowRate = 32.1f;
 
     // 娉电姸鎬
     [ObservableProperty]
-    private bool _pump1Running;
+    private bool _pump1Running = true;
 
     [ObservableProperty]
-    private bool _pump2Running;
+    private bool _pump2Running = false;
 
     [ObservableProperty]
-    private bool _pump3Running;
+    private bool _pump3Running = true;
 
     [ObservableProperty]
-    private bool _pump4Running;
+    private bool _pump4Running = false;
 
     [ObservableProperty]
-    private bool _pump5Running;
+    private bool _pump5Running = true;
 
     // 椋庢墖鐘舵
     [ObservableProperty]
-    private bool _fan1Running;
+    private bool _fan1Running = true;
 
     [ObservableProperty]
-    private bool _fan2Running;
+    private bool _fan2Running = false;
 
-    // 闃闂ㄧ姸鎬
+    // 璁惧鐘舵佹枃鏈
     [ObservableProperty]
-    private ValveStatus _valve1Status = ValveStatus.Middle;
+    private string _pump1Status = "杩愯涓 - 50Hz";
 
     [ObservableProperty]
-    private ValveStatus _valve2Status = ValveStatus.Middle;
+    private string _pump2Status = "宸插仠姝";
 
     [ObservableProperty]
-    private ValveStatus _valve3Status = ValveStatus.Middle;
+    private string _pump3Status = "杩愯涓 - 40Hz";
 
     [ObservableProperty]
-    private ValveStatus _valve4Status = ValveStatus.Middle;
+    private string _pump4Status = "宸插仠姝";
 
     [ObservableProperty]
-    private ValveStatus _valve5Status = ValveStatus.Middle;
+    private string _pump5Status = "杩愯涓 - 55Hz";
 
-    // 妯″紡鐘舵
     [ObservableProperty]
-    private ValveStatus _modeStatus = ValveStatus.Middle;
+    private string _fan1Status = "杩愯涓 - 1500RPM";
 
-    // 娴佸姩鐘舵
     [ObservableProperty]
-    private bool _isInflowRunning = true;
+    private string _fan2Status = "宸插仠姝";
 
+    // 闃闂ㄧ姸鎬
     [ObservableProperty]
-    private bool _isOutflowRunning = true;
+    private ValveStatus _valve1Status = ValveStatus.Open;
 
-    // 璁惧鐘舵侀鑹
     [ObservableProperty]
-    private string _pump1StatusColor = "#4CAF50";
+    private ValveStatus _valve2Status = ValveStatus.Open;
 
     [ObservableProperty]
-    private string _pump2StatusColor = "#4CAF50";
+    private ValveStatus _valve3Status = ValveStatus.Middle;
 
     [ObservableProperty]
-    private string _pump3StatusColor = "#4CAF50";
+    private ValveStatus _valve4Status = ValveStatus.Closed;
 
+    // 妯″紡鐘舵
     [ObservableProperty]
-    private string _pump4StatusColor = "#4CAF50";
+    private ValveStatus _modeStatus = ValveStatus.Open;
 
+    // 娴佸姩鐘舵
     [ObservableProperty]
-    private string _pump5StatusColor = "#4CAF50";
+    private bool _isInflowRunning = true;
 
     [ObservableProperty]
-    private string _fan1StatusColor = "#4CAF50";
+    private bool _isOutflowRunning = true;
 
+    // 鎶ヨ鐘舵
     [ObservableProperty]
-    private string _fan2StatusColor = "#4CAF50";
+    private bool _hasAlarm = true;
 
-    // 鎶ヨ鐘舵
     [ObservableProperty]
-    private bool _hasAlarm;
+    private string _alarmMessage = "1鍙锋按姹犳恫浣嶆帴杩戦珮闄 (65.5%)";
 
     [ObservableProperty]
-    private string _alarmMessage = string.Empty;
+    private int _alarmCount = 1;
 
     // 杩炴帴鐘舵
     [ObservableProperty]
-    private bool _isPlcConnected;
+    private bool _isPlcConnected = true;
+
+    // 璁惧璁℃暟
+    [ObservableProperty]
+    private int _runningDeviceCount = 5;
+
+    // 杩愯鏃堕棿
+    [ObservableProperty]
+    private string _runningTime = "0澶 2灏忔椂 35鍒";
+
+    // 褰撳墠鏃堕棿
+    [ObservableProperty]
+    private DateTime _currentTime = DateTime.Now;
 
     public ViewAViewModel()
     {
-        StartDataRefresh();
+        StartTimers();
     }
 
     /// <summary>
-    /// 鍚姩鏁版嵁鍒锋柊
+    /// 鍚姩瀹氭椂鍣
     /// </summary>
-    private void StartDataRefresh()
+    private void StartTimers()
     {
-        var timer = new System.Timers.Timer(1000);
-        timer.Elapsed += async (s, e) =>
+        // 鏃堕棿鏇存柊瀹氭椂鍣
+        var timeTimer = new System.Timers.Timer(1000);
+        timeTimer.Elapsed += (s, e) =>
         {
-            await RefreshDataAsync();
+            CurrentTime = DateTime.Now;
+            var elapsed = DateTime.Now - _startTime;
+            RunningTime = $"{elapsed.Days}澶 {elapsed.Hours}灏忔椂 {elapsed.Minutes}鍒";
         };
-        timer.Start();
+        timeTimer.Start();
+
+        // 鏁版嵁妯℃嫙瀹氭椂鍣
+        var dataTimer = new System.Timers.Timer(3000);
+        dataTimer.Elapsed += (s, e) =>
+        {
+            SimulateDataChanges();
+        };
+        dataTimer.Start();
     }
 
     /// <summary>
-    /// 鍒锋柊 PLC 鏁版嵁
+    /// 妯℃嫙鏁版嵁鍙樺寲
     /// </summary>
-    [RelayCommand]
-    private async Task RefreshDataAsync()
+    private void SimulateDataChanges()
     {
-        if (!PlcService.IsConnected) return;
+        var random = new Random();
 
-        try
-        {
-            // 璇诲彇娑蹭綅鏁版嵁
-            Tank1Level = await PlcService.ReadFloatAsync("VD100");
-            Tank2Level = await PlcService.ReadFloatAsync("VD104");
-            Tank3Level = await PlcService.ReadFloatAsync("VD108");
-            Tank4Level = await PlcService.ReadFloatAsync("VD112");
+        // 妯℃嫙娑蹭綅娉㈠姩
+        Tank1Level = Math.Max(0, Math.Min(100, Tank1Level + (float)(random.NextDouble() * 2 - 1)));
+        Tank2Level = Math.Max(0, Math.Min(100, Tank2Level + (float)(random.NextDouble() * 2 - 1)));
+        Tank3Level = Math.Max(0, Math.Min(100, Tank3Level + (float)(random.NextDouble() * 2 - 1)));
+        Tank4Level = Math.Max(0, Math.Min(100, Tank4Level + (float)(random.NextDouble() * 2 - 1)));
 
-            // 璇诲彇娴侀噺鏁版嵁
-            InflowRate = await PlcService.ReadFloatAsync("VD200");
-            OutflowRate = await PlcService.ReadFloatAsync("VD204");
+        // 妯℃嫙娴侀噺娉㈠姩
+        InflowRate = Math.Max(0, InflowRate + (float)(random.NextDouble() * 4 - 2));
+        OutflowRate = Math.Max(0, OutflowRate + (float)(random.NextDouble() * 4 - 2));
 
-            // 璇诲彇娉电姸鎬
-            Pump1Running = await PlcService.ReadBoolAsync("Q0.0");
-            Pump2Running = await PlcService.ReadBoolAsync("Q0.1");
-            Pump3Running = await PlcService.ReadBoolAsync("Q0.2");
-            Pump4Running = await PlcService.ReadBoolAsync("Q0.3");
-            Pump5Running = await PlcService.ReadBoolAsync("Q0.4");
-
-            // 璇诲彇椋庢墖鐘舵
-            Fan1Running = await PlcService.ReadBoolAsync("Q0.5");
-            Fan2Running = await PlcService.ReadBoolAsync("Q0.6");
-
-            // 鏇存柊璁惧鐘舵侀鑹
-            UpdateDeviceStatusColors();
-
-            // 妫鏌ユ姤璀
-            CheckAlarms();
-        }
-        catch (Exception ex)
-        {
-            Log.Error(ex, "鍒锋柊 PLC 鏁版嵁澶辫触");
-        }
-    }
-
-    /// <summary>
-    /// 鏇存柊璁惧鐘舵侀鑹
-    /// </summary>
-    private void UpdateDeviceStatusColors()
-    {
-        Pump1StatusColor = Pump1Running ? "#4CAF50" : "#F44336";
-        Pump2StatusColor = Pump2Running ? "#4CAF50" : "#F44336";
-        Pump3StatusColor = Pump3Running ? "#4CAF50" : "#F44336";
-        Pump4StatusColor = Pump4Running ? "#4CAF50" : "#F44336";
-        Pump5StatusColor = Pump5Running ? "#4CAF50" : "#F44336";
-        Fan1StatusColor = Fan1Running ? "#4CAF50" : "#F44336";
-        Fan2StatusColor = Fan2Running ? "#4CAF50" : "#F44336";
+        // 妫鏌ユ姤璀
+        CheckAlarms();
     }
 
     /// <summary>
@@ -199,23 +188,57 @@ public partial class ViewAViewModel : ObservableObject
 
         HasAlarm = false;
         AlarmMessage = string.Empty;
+        AlarmCount = 0;
 
         if (Tank1Level > config.LevelHighAlarm)
         {
             HasAlarm = true;
-            AlarmMessage = "1鍙锋按姹犳恫浣嶈繃楂";
+            AlarmMessage = $"1鍙锋按姹犳恫浣嶆帴杩戦珮闄 ({Tank1Level:F1}%)";
+            AlarmCount++;
         }
         else if (Tank1Level < config.LevelLowAlarm)
         {
             HasAlarm = true;
-            AlarmMessage = "1鍙锋按姹犳恫浣嶈繃浣";
+            AlarmMessage = $"1鍙锋按姹犳恫浣嶈繃浣 ({Tank1Level:F1}%)";
+            AlarmCount++;
+        }
+
+        if (Tank2Level > config.LevelHighAlarm)
+        {
+            HasAlarm = true;
+            AlarmMessage += $"\n2鍙锋按姹犳恫浣嶆帴杩戦珮闄 ({Tank2Level:F1}%)";
+            AlarmCount++;
         }
 
         if (InflowRate > config.FlowHighAlarm)
         {
             HasAlarm = true;
-            AlarmMessage = "杩涙按娴侀噺杩囬珮";
+            AlarmMessage += $"\n杩涙按娴侀噺杩囬珮 ({InflowRate:F1} m鲁/h)";
+            AlarmCount++;
         }
+
+        // 鏇存柊杩愯璁惧鏁
+        RunningDeviceCount = (Pump1Running ? 1 : 0) + (Pump2Running ? 1 : 0) +
+                            (Pump3Running ? 1 : 0) + (Pump4Running ? 1 : 0) +
+                            (Pump5Running ? 1 : 0) + (Fan1Running ? 1 : 0) +
+                            (Fan2Running ? 1 : 0);
+
+        // 鏇存柊璁惧鐘舵佹枃鏈
+        UpdateDeviceStatusText();
+    }
+
+    /// <summary>
+    /// 鏇存柊璁惧鐘舵佹枃鏈
+    /// </summary>
+    private void UpdateDeviceStatusText()
+    {
+        Pump1Status = Pump1Running ? "杩愯涓 - 50Hz" : "宸插仠姝";
+        Pump2Status = Pump2Running ? "杩愯涓 - 45Hz" : "宸插仠姝";
+        Pump3Status = Pump3Running ? "杩愯涓 - 40Hz" : "宸插仠姝";
+        Pump4Status = Pump4Running ? "杩愯涓 - 35Hz" : "宸插仠姝";
+        Pump5Status = Pump5Running ? "杩愯涓 - 55Hz" : "宸插仠姝";
+        Fan1Status = Fan1Running ? "杩愯涓 - 1500RPM" : "宸插仠姝";
+        Fan2Status = Fan2Running ? "杩愯涓 - 1200RPM" : "宸插仠姝";
     }
 
     /// <summary>
@@ -238,27 +261,45 @@ public partial class ViewAViewModel : ObservableObject
     }
 
     /// <summary>
-    /// 鎺у埗娉
+    /// 鍒锋柊鏁版嵁
     /// </summary>
     [RelayCommand]
-    private async Task TogglePumpAsync(string pumpId)
+    private async Task RefreshDataAsync()
     {
         if (!PlcService.IsConnected)
         {
-            Log.Warning("PLC 鏈繛鎺ワ紝鏃犳硶鎺у埗娉");
+            Log.Warning("PLC 鏈繛鎺ワ紝浣跨敤妯℃嫙鏁版嵁");
             return;
         }
 
-        var address = pumpId switch
+        try
         {
-            "P001" => "Q0.0",
-            "P002" => "Q0.1",
-            "P003" => "Q0.2",
-            "P004" => "Q0.3",
-            "P005" => "Q0.4",
-            _ => throw new ArgumentException($"鏈煡鐨勬车 ID: {pumpId}")
-        };
+            Tank1Level = await PlcService.ReadFloatAsync("VD100");
+            Tank2Level = await PlcService.ReadFloatAsync("VD104");
+            Tank3Level = await PlcService.ReadFloatAsync("VD108");
+            Tank4Level = await PlcService.ReadFloatAsync("VD112");
+            InflowRate = await PlcService.ReadFloatAsync("VD200");
+            OutflowRate = await PlcService.ReadFloatAsync("VD204");
+            Pump1Running = await PlcService.ReadBoolAsync("Q0.0");
+            Pump2Running = await PlcService.ReadBoolAsync("Q0.1");
+            Pump3Running = await PlcService.ReadBoolAsync("Q0.2");
+            Fan1Running = await PlcService.ReadBoolAsync("Q0.3");
+            Fan2Running = await PlcService.ReadBoolAsync("Q0.4");
 
+            CheckAlarms();
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "鍒锋柊鏁版嵁澶辫触");
+        }
+    }
+
+    /// <summary>
+    /// 鎺у埗娉
+    /// </summary>
+    [RelayCommand]
+    private async Task TogglePumpAsync(string pumpId)
+    {
         var currentState = pumpId switch
         {
             "P001" => Pump1Running,
@@ -269,11 +310,31 @@ public partial class ViewAViewModel : ObservableObject
             _ => false
         };
 
-        var success = await PlcService.WriteBoolAsync(address, !currentState);
-        if (success)
+        var address = pumpId switch
+        {
+            "P001" => "Q0.0",
+            "P002" => "Q0.1",
+            "P003" => "Q0.2",
+            "P004" => "Q0.3",
+            "P005" => "Q0.4",
+            _ => ""
+        };
+
+        if (PlcService.IsConnected)
         {
-            Log.Information("娉 {PumpId} 鐘舵佸凡鍒囨崲", pumpId);
+            await PlcService.WriteBoolAsync(address, !currentState);
         }
+
+        switch (pumpId)
+        {
+            case "P001": Pump1Running = !currentState; break;
+            case "P002": Pump2Running = !currentState; break;
+            case "P003": Pump3Running = !currentState; break;
+            case "P004": Pump4Running = !currentState; break;
+            case "P005": Pump5Running = !currentState; break;
+        }
+
+        CheckAlarms();
     }
 
     /// <summary>
@@ -282,19 +343,6 @@ public partial class ViewAViewModel : ObservableObject
     [RelayCommand]
     private async Task ToggleFanAsync(string fanId)
     {
-        if (!PlcService.IsConnected)
-        {
-            Log.Warning("PLC 鏈繛鎺ワ紝鏃犳硶鎺у埗椋庢墖");
-            return;
-        }
-
-        var address = fanId switch
-        {
-            "F001" => "Q0.5",
-            "F002" => "Q0.6",
-            _ => throw new ArgumentException($"鏈煡鐨勯鎵 ID: {fanId}")
-        };
-
         var currentState = fanId switch
         {
             "F001" => Fan1Running,
@@ -302,11 +350,16 @@ public partial class ViewAViewModel : ObservableObject
             _ => false
         };
 
-        var success = await PlcService.WriteBoolAsync(address, !currentState);
-        if (success)
+        if (PlcService.IsConnected)
         {
-            Log.Information("椋庢墖 {FanId} 鐘舵佸凡鍒囨崲", fanId);
+            var address = fanId == "F001" ? "Q0.3" : "Q0.4";
+            await PlcService.WriteBoolAsync(address, !currentState);
         }
+
+        if (fanId == "F001") Fan1Running = !currentState;
+        else Fan2Running = !currentState;
+
+        CheckAlarms();
     }
 
     /// <summary>
@@ -315,33 +368,15 @@ public partial class ViewAViewModel : ObservableObject
     [RelayCommand]
     private async Task ToggleValveAsync(string valveId)
     {
-        if (!PlcService.IsConnected)
-        {
-            Log.Warning("PLC 鏈繛鎺ワ紝鏃犳硶鎺у埗闃闂");
-            return;
-        }
-
-        var address = valveId switch
-        {
-            "V001" => "Q1.0",
-            "V002" => "Q1.1",
-            "V003" => "Q1.2",
-            "V004" => "Q1.3",
-            "V005" => "Q1.4",
-            _ => throw new ArgumentException($"鏈煡鐨勯榾闂 ID: {valveId}")
-        };
-
         var currentStatus = valveId switch
         {
             "V001" => Valve1Status,
             "V002" => Valve2Status,
             "V003" => Valve3Status,
             "V004" => Valve4Status,
-            "V005" => Valve5Status,
             _ => ValveStatus.Middle
         };
 
-        // 寰幆鍒囨崲鐘舵: 鍏抽棴 -> 涓棿 -> 鎵撳紑 -> 鍏抽棴
         var newStatus = currentStatus switch
         {
             ValveStatus.Closed => ValveStatus.Middle,
@@ -350,46 +385,25 @@ public partial class ViewAViewModel : ObservableObject
             _ => ValveStatus.Middle
         };
 
-        var success = await PlcService.WriteBoolAsync(address, newStatus == ValveStatus.Open);
-        if (success)
+        if (PlcService.IsConnected)
         {
-            switch (valveId)
+            var address = valveId switch
             {
-                case "V001": Valve1Status = newStatus; break;
-                case "V002": Valve2Status = newStatus; break;
-                case "V003": Valve3Status = newStatus; break;
-                case "V004": Valve4Status = newStatus; break;
-                case "V005": Valve5Status = newStatus; break;
-            }
-            Log.Information("闃闂 {ValveId} 鐘舵佸凡鍒囨崲涓 {Status}", valveId, newStatus);
+                "V001" => "Q1.0",
+                "V002" => "Q1.1",
+                "V003" => "Q1.2",
+                "V004" => "Q1.3",
+                _ => ""
+            };
+            await PlcService.WriteBoolAsync(address, newStatus == ValveStatus.Open);
         }
-    }
-
-    /// <summary>
-    /// 鍒囨崲妯″紡
-    /// </summary>
-    [RelayCommand]
-    private async Task ToggleModeAsync()
-    {
-        if (!PlcService.IsConnected)
-        {
-            Log.Warning("PLC 鏈繛鎺ワ紝鏃犳硶鍒囨崲妯″紡");
-            return;
-        }
-
-        var newStatus = ModeStatus switch
-        {
-            ValveStatus.Closed => ValveStatus.Middle,
-            ValveStatus.Middle => ValveStatus.Open,
-            ValveStatus.Open => ValveStatus.Closed,
-            _ => ValveStatus.Middle
-        };
 
-        var success = await PlcService.WriteBoolAsync("M0.0", newStatus == ValveStatus.Open);
-        if (success)
+        switch (valveId)
         {
-            ModeStatus = newStatus;
-            Log.Information("妯″紡宸插垏鎹负: {Status}", newStatus);
+            case "V001": Valve1Status = newStatus; break;
+            case "V002": Valve2Status = newStatus; break;
+            case "V003": Valve3Status = newStatus; break;
+            case "V004": Valve4Status = newStatus; break;
         }
     }
 }