Преглед на файлове

添加解析度自适应功能

磊 曹 преди 1 седмица
ревизия
845655b463
променени са 46 файла, в които са добавени 4707 реда и са изтрити 0 реда
  1. 432 0
      .gitignore
  2. 354 0
      README.md
  3. 54 0
      YZWater3.sln
  4. 11 0
      src/YZWater.Avalonia/App.axaml
  5. 31 0
      src/YZWater.Avalonia/App.axaml.cs
  6. BIN
      src/YZWater.Avalonia/Assets/污水处理厂.ico
  7. 146 0
      src/YZWater.Avalonia/Controls/AdaptiveContainer.cs
  8. 173 0
      src/YZWater.Avalonia/Controls/FanControl.cs
  9. 167 0
      src/YZWater.Avalonia/Controls/PipeLineControl.cs
  10. 191 0
      src/YZWater.Avalonia/Controls/PumpControl.cs
  11. 147 0
      src/YZWater.Avalonia/Controls/ValveControl.cs
  12. 113 0
      src/YZWater.Avalonia/Controls/WaterTankControl.cs
  13. 85 0
      src/YZWater.Avalonia/Converters/BoolToBrushConverter.cs
  14. 21 0
      src/YZWater.Avalonia/Program.cs
  15. 86 0
      src/YZWater.Avalonia/Views/MainWindow.axaml
  16. 11 0
      src/YZWater.Avalonia/Views/MainWindow.axaml.cs
  17. 175 0
      src/YZWater.Avalonia/Views/ViewAView.axaml
  18. 11 0
      src/YZWater.Avalonia/Views/ViewAView.axaml.cs
  19. 77 0
      src/YZWater.Avalonia/Views/ViewBView.axaml
  20. 11 0
      src/YZWater.Avalonia/Views/ViewBView.axaml.cs
  21. 72 0
      src/YZWater.Avalonia/Views/ViewCView.axaml
  22. 11 0
      src/YZWater.Avalonia/Views/ViewCView.axaml.cs
  23. 93 0
      src/YZWater.Avalonia/Views/ViewDView.axaml
  24. 11 0
      src/YZWater.Avalonia/Views/ViewDView.axaml.cs
  25. 65 0
      src/YZWater.Avalonia/Views/ViewEView.axaml
  26. 11 0
      src/YZWater.Avalonia/Views/ViewEView.axaml.cs
  27. 35 0
      src/YZWater.Avalonia/YZWater.Avalonia.csproj
  28. 25 0
      src/YZWater.Avalonia/app.manifest
  29. 63 0
      src/YZWater.Core/Models/AlarmRecord.cs
  30. 83 0
      src/YZWater.Core/Models/EquipmentStatus.cs
  31. 52 0
      src/YZWater.Core/Models/FlowRecord.cs
  32. 39 0
      src/YZWater.Core/Models/Person.cs
  33. 75 0
      src/YZWater.Core/Models/SystemConfig.cs
  34. 94 0
      src/YZWater.Core/Services/ConfigService.cs
  35. 58 0
      src/YZWater.Core/Services/DatabaseService.cs
  36. 193 0
      src/YZWater.Core/Services/PlcService.cs
  37. 65 0
      src/YZWater.Core/Utils/Nlogger.cs
  38. 127 0
      src/YZWater.Core/Utils/ResolutionManager.cs
  39. 90 0
      src/YZWater.Core/Utils/WinSize.cs
  40. 134 0
      src/YZWater.Core/ViewModels/MainViewModel.cs
  41. 416 0
      src/YZWater.Core/ViewModels/ViewAViewModel.cs
  42. 140 0
      src/YZWater.Core/ViewModels/ViewBViewModel.cs
  43. 186 0
      src/YZWater.Core/ViewModels/ViewCViewModel.cs
  44. 171 0
      src/YZWater.Core/ViewModels/ViewDViewModel.cs
  45. 78 0
      src/YZWater.Core/ViewModels/ViewEViewModel.cs
  46. 24 0
      src/YZWater.Core/YZWater.Core.csproj

+ 432 - 0
.gitignore

@@ -0,0 +1,432 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog/
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (Sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure settings, but sensitive information contained in these scripts
+# will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server Files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Pair with TFS gI
+tools/**
+!tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU Debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (often used with SSDT)
+.vshistory/
+
+# BeatPulse healthance liveness check folder
+health/
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+# Common IDE files
+*.swp
+*.swo
+*~
+
+# macOS
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.Network
+.TemporaryItems
+.fseventsd
+
+# Auto-generanted files
+*.g.cs
+*.g.i.cs
+
+# Avalonia
+*.axaml.cs.g.cs
+
+# Database files
+*.db
+*.db-journal
+*.db-shm
+*.db-wal
+
+# Log files
+Logs/
+*.log
+
+# Config files with sensitive data
+yzwater-config.json

+ 354 - 0
README.md

@@ -0,0 +1,354 @@
+# YZWater3 - 跨平台污水处理监控系统
+
+基于 Avalonia UI 的跨平台污水处理厂监控系统,支持 Windows、Linux 和 macOS。
+
+## 🚀 技术栈
+
+| 组件 | 技术方案 |
+|------|---------|
+| **UI 框架** | Avalonia UI 11.2.3 |
+| **运行时** | .NET 8.0 |
+| **MVVM 框架** | CommunityToolkit.Mvvm |
+| **PLC 通信** | HslCommunication |
+| **数据库** | FreeSql + SQLite |
+| **图表** | LiveCharts2 |
+| **日志** | Serilog |
+
+## 📁 项目结构
+
+```
+YZWater3/
+├── src/
+│   ├── YZWater.Core/              # 核心业务逻辑(共享)
+│   │   ├── Models/                # 数据模型
+│   │   │   ├── Person.cs         # 人员模型
+│   │   │   ├── AlarmRecord.cs    # 报警记录
+│   │   │   ├── FlowRecord.cs     # 流量记录
+│   │   │   ├── EquipmentStatus.cs # 设备状态
+│   │   │   └── SystemConfig.cs   # 系统配置
+│   │   ├── Services/              # 服务层
+│   │   │   ├── DatabaseService.cs # 数据库服务
+│   │   │   ├── PlcService.cs     # PLC 通信服务
+│   │   │   └── ConfigService.cs  # 配置服务
+│   │   ├── ViewModels/            # ViewModel 层
+│   │   │   ├── MainViewModel.cs  # 主窗口
+│   │   │   ├── ViewAViewModel.cs # 主工艺
+│   │   │   ├── ViewBViewModel.cs # 参数设置
+│   │   │   ├── ViewCViewModel.cs # 流量记录
+│   │   │   ├── ViewDViewModel.cs # 报警记录
+│   │   │   └── ViewEViewModel.cs # 关于
+│   │   └── Utils/                 # 工具类
+│   │       ├── Nlogger.cs        # 日志工具
+│   │       └── WinSize.cs        # 窗口尺寸工具
+│   │
+│   └── YZWater.Avalonia/          # Avalonia UI 项目
+│       ├── Views/                 # XAML 视图
+│       │   ├── MainWindow.axaml  # 主窗口
+│       │   ├── ViewAView.axaml   # 主工艺视图(工业控件)
+│       │   ├── ViewBView.axaml   # 参数设置视图
+│       │   ├── ViewCView.axaml   # 流量记录视图
+│       │   ├── ViewDView.axaml   # 报警记录视图
+│       │   └── ViewEView.axaml   # 关于视图
+│       ├── Controls/              # 自定义工业控件
+│       │   ├── WaterTankControl.cs # 水箱控件
+│       │   ├── PumpControl.cs    # 泵控件
+│       │   ├── FanControl.cs     # 风扇控件
+│       │   ├── PipeLineControl.cs # 管道控件
+│       │   └── ValveControl.cs   # 阀门控件
+│       ├── Converters/            # 值转换器
+│       └── Assets/                # 资源文件
+│
+└── YZWater3.sln                   # 解决方案文件
+```
+
+## 🛠️ 开发环境
+
+### 前置要求
+
+- .NET 8.0 SDK 或更高版本
+- Visual Studio 2022 或 JetBrains Rider(推荐)
+- Git
+
+### 安装步骤
+
+1. **克隆仓库**
+   ```bash
+   git clone <repository-url>
+   cd YZWater3
+   ```
+
+2. **还原依赖**
+   ```bash
+   dotnet restore
+   ```
+
+3. **构建项目**
+   ```bash
+   dotnet build
+   ```
+
+4. **运行应用**
+   ```bash
+   dotnet run --project src/YZWater.Avalonia
+   ```
+
+## 🎯 功能特性
+
+### 主工艺视图 (ViewA)
+- **工业控件展示** - 使用自定义控件模拟真实工业界面
+  - WaterTankControl - 水箱液位显示
+  - PumpControl - 泵运行状态动画
+  - FanControl - 风扇运行状态动画
+  - PipeLineControl - 管道流动效果
+  - ValveControl - 阀门状态显示
+- 实时显示 4 个水池液位
+- 进水/出水流量监测
+- 泵、风机、阀门控制
+- 自动/手动模式切换
+- 报警状态显示
+- 设备状态面板
+
+### 参数设置 (ViewB)
+- PLC 连接配置(IP、端口)
+- 报警阈值设置(液位、流量)
+- 泵频率设置
+- 配置保存/加载
+
+### 流量记录 (ViewC)
+- 进水/出水流量图表
+- 历史数据查询
+- 数据导出 CSV
+- 清除旧数据
+
+### 报警记录 (ViewD)
+- 实时报警列表
+- 报警确认功能
+- 批量确认
+- 报警详情查看
+
+### 关于 (ViewE)
+- 系统信息
+- 公司信息
+- 版本信息
+
+## 🔧 配置说明
+
+### PLC 配置
+
+默认配置文件:`yzwater-config.json`
+
+```json
+{
+  "PlcIp": "192.168.0.150",
+  "PlcPort": 5000,
+  "AutoConnect": true,
+  "LevelHighAlarm": 80.0,
+  "LevelLowAlarm": 20.0,
+  "FlowHighAlarm": 100.0,
+  "PumpFrequency": 50.0,
+  "SystemName": "污水处理监控系统",
+  "CompanyName": "扬州旭轩科技有限公司",
+  "ContactPerson": "赵经理",
+  "ContactPhone": "18115099090"
+}
+```
+
+### 数据库配置
+
+数据库文件:`yzwater.db`(SQLite)
+
+自动创建以下表:
+- `persons` - 人员表
+- `alarm_records` - 报警记录表
+- `flow_records` - 流量记录表
+- `system_config` - 系统配置表
+
+## 📦 依赖包
+
+### Core 项目
+- CommunityToolkit.Mvvm 8.2.2
+- FreeSql.Provider.Sqlite 3.2.800
+- FreeSql.Repository 3.2.800
+- HslCommunication 12.0.1
+- Serilog 4.0.2
+- Serilog.Sinks.Console 6.0.0
+- Serilog.Sinks.File 6.0.0
+
+### Avalonia 项目
+- Avalonia 11.2.3
+- Avalonia.Desktop 11.2.3
+- Avalonia.Themes.Fluent 11.2.3
+- Avalonia.Fonts.Inter 11.2.3
+- LiveChartsCore.SkiaSharpView.Avalonia 2.0.0-rc4.5
+- Semi.Avalonia 11.2.1.4
+
+## 📐 解析度自适应
+
+### 自适应机制
+
+项目使用 `AdaptiveContainer` 控件实现解析度自适应:
+
+1. **设计基准**: 1280 x 800
+2. **缩放方式**: Uniform(等比缩放,保持宽高比)
+3. **缩放范围**: 0.6x - 1.5x
+4. **自动居中**: 内容在可用空间内自动居中
+
+### 工作原理
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│  窗口实际大小 (1920 x 1080)                                  │
+│  ┌─────────────────────────────────────────────────────┐   │
+│  │  缩放后的内容区域 (1280 x 800 * 1.25)                │   │
+│  │  ┌─────────────────────────────────────────────┐   │   │
+│  │  │  设计内容 (1280 x 800)                       │   │   │
+│  │  │  - 所有控件使用固定坐标                       │   │   │
+│  │  │  - 由容器统一缩放                            │   │   │
+│  │  └─────────────────────────────────────────────┘   │   │
+│  └─────────────────────────────────────────────────────┘   │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 使用方式
+
+```xml
+<!-- 在 MainWindow.axaml 中 -->
+<controls:AdaptiveContainer DesignWidth="1280" DesignHeight="800"
+                             Stretch="Uniform" MinScale="0.6" MaxScale="1.5">
+    <!-- 所有内容放在这里 -->
+</controls:AdaptiveContainer>
+```
+
+### 分辨率管理器
+
+`ResolutionManager` 提供全局分辨率信息:
+
+```csharp
+// 获取当前缩放比例
+var scale = ResolutionManager.Instance.UniformScale;
+
+// 计算缩放后的尺寸
+var scaledWidth = ResolutionManager.Instance.Scale(100);
+
+// 计算缩放后的字体大小
+var fontSize = ResolutionManager.Instance.ScaleFontSize(14);
+```
+
+## 🎨 自定义工业控件
+
+### WaterTankControl - 水箱控件
+- 显示液位百分比
+- 可配置水颜色和边框颜色
+- 支持文字标签
+
+```xml
+<controls:WaterTankControl WaterLevel="75" Text="入口池" WaterColor="#178187"/>
+```
+
+### PumpControl - 泵控件
+- 运行时显示旋转动画
+- 可配置旋转速度
+- 支持多颜色配置
+
+```xml
+<controls:PumpControl IsRunning="{Binding Pump1Running}" Speed="2" Text="泵1"/>
+```
+
+### FanControl - 风扇控件
+- 运行时显示风扇旋转动画
+- 可配置旋转速度
+- 支持文字标签
+
+```xml
+<controls:FanControl IsRunning="{Binding Fan1Running}" Speed="1" Text="风扇1"/>
+```
+
+### PipeLineControl - 管道控件
+- 显示流动效果(虚线动画)
+- 支持水平/垂直方向
+- 可配置管道颜色和水流颜色
+
+```xml
+<controls:PipeLineControl IsFlow="True" IsHorizontal="True"/>
+```
+
+### ValveControl - 阀门控件
+- 三种状态:关闭/中间/打开
+- 状态颜色指示(红/黄/绿)
+- 支持文字标签
+
+```xml
+<controls:ValveControl Status="{Binding Valve1Status}" Text="进水阀"/>
+```
+
+## 🔄 与 YZWater2 的对比
+
+| 特性 | YZWater2 (WPF) | YZWater3 (Avalonia) |
+|------|---------------|---------------------|
+| **跨平台** | ❌ 仅 Windows | ✅ Windows/Linux/macOS |
+| **UI 框架** | WPF | Avalonia UI 11 |
+| **运行时** | .NET Framework 4.8 | .NET 8.0 |
+| **MVVM** | Caliburn.Micro | CommunityToolkit.Mvvm |
+| **图表** | LiveCharts.Wpf | LiveCharts2 |
+| **控件库** | HandyControl | Semi.Avalonia |
+| **日志** | NLog | Serilog |
+| **工业控件** | HslControls | 自定义控件 |
+
+## 📝 开发说明
+
+### 添加新视图
+
+1. 在 `YZWater.Core/ViewModels` 中创建 ViewModel
+2. 在 `YZWater.Avalonia/Views` 中创建对应的 View
+3. 在 `MainViewModel` 中添加视图切换逻辑
+4. 在 `MainWindow.axaml` 中添加 TabItem
+
+### 自定义控件
+
+在 `YZWater.Avalonia/Controls` 目录中创建自定义控件,例如:
+- 水箱控件 (WaterTankControl)
+- 管道控件 (PipeLineControl)
+- 阀门控件 (ValveControl)
+
+### 数据绑定
+
+使用 CommunityToolkit.Mvvm 的源生成器:
+
+```csharp
+public partial class MyViewModel : ObservableObject
+{
+    [ObservableProperty]
+    private string _name = string.Empty;
+
+    [RelayCommand]
+    private async Task SaveAsync()
+    {
+        // 保存逻辑
+    }
+}
+```
+
+## 🐛 常见问题
+
+### 1. PLC 连接失败
+- 检查 PLC IP 地址是否正确
+- 确认网络连接正常
+- 检查防火墙设置
+
+### 2. 数据库错误
+- 删除 `yzwater.db` 文件重新启动
+- 检查磁盘空间
+
+### 3. UI 显示异常
+- 更新显卡驱动
+- 检查 .NET 8.0 运行时是否安装
+
+## 📄 许可证
+
+© 2024 扬州旭轩科技有限公司
+
+## 👥 联系方式
+
+- **公司**: 扬州旭轩科技有限公司
+- **联系人**: 赵经理
+- **电话**: 18115099090

+ 54 - 0
YZWater3.sln

@@ -0,0 +1,54 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YZWater.Core", "src\YZWater.Core\YZWater.Core.csproj", "{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YZWater.Avalonia", "src\YZWater.Avalonia\YZWater.Avalonia.csproj", "{A8DAD3B1-F889-43F1-814B-C56911ED84D4}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Debug|x64.Build.0 = Debug|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Debug|x86.Build.0 = Debug|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Release|x64.ActiveCfg = Release|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Release|x64.Build.0 = Release|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Release|x86.ActiveCfg = Release|Any CPU
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0}.Release|x86.Build.0 = Release|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Debug|x64.Build.0 = Debug|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Debug|x86.Build.0 = Debug|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Release|x64.ActiveCfg = Release|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Release|x64.Build.0 = Release|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Release|x86.ActiveCfg = Release|Any CPU
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4}.Release|x86.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{57C35C51-105D-4D70-8AF0-E3FA6FD89BB0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+		{A8DAD3B1-F889-43F1-814B-C56911ED84D4} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+	EndGlobalSection
+EndGlobal

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

@@ -0,0 +1,11 @@
+<Application xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="YZWater.Avalonia.App"
+             RequestedThemeVariant="Default">
+    <!-- "Default" ThemeVariant follows system theme, "Light" or "Dark" are other options -->
+
+    <Application.Styles>
+        <FluentTheme />
+    </Application.Styles>
+
+</Application>

+ 31 - 0
src/YZWater.Avalonia/App.axaml.cs

@@ -0,0 +1,31 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using YZWater.Avalonia.Views;
+using YZWater.Core.Services;
+
+namespace YZWater.Avalonia;
+
+public partial class App : Application
+{
+    public override void Initialize()
+    {
+        AvaloniaXamlLoader.Load(this);
+    }
+
+    public override void OnFrameworkInitializationCompleted()
+    {
+        // 初始化数据库服务
+        DatabaseService.Initialize();
+
+        // 初始化 PLC 服务
+        PlcService.Initialize();
+
+        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            desktop.MainWindow = new MainWindow();
+        }
+
+        base.OnFrameworkInitializationCompleted();
+    }
+}

BIN
src/YZWater.Avalonia/Assets/污水处理厂.ico


+ 146 - 0
src/YZWater.Avalonia/Controls/AdaptiveContainer.cs

@@ -0,0 +1,146 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using YZWater.Core.Utils;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 自适应容器控件 - 根据窗口大小自动缩放内容
+/// </summary>
+public class AdaptiveContainer : Decorator
+{
+    public static readonly StyledProperty<double> DesignWidthProperty =
+        AvaloniaProperty.Register<AdaptiveContainer, double>(nameof(DesignWidth), ResolutionManager.DesignWidth);
+
+    public static readonly StyledProperty<double> DesignHeightProperty =
+        AvaloniaProperty.Register<AdaptiveContainer, double>(nameof(DesignHeight), ResolutionManager.DesignHeight);
+
+    public static readonly StyledProperty<bool> MaintainAspectRatioProperty =
+        AvaloniaProperty.Register<AdaptiveContainer, bool>(nameof(MaintainAspectRatio), true);
+
+    public static readonly StyledProperty<Stretch> StretchProperty =
+        AvaloniaProperty.Register<AdaptiveContainer, Stretch>(nameof(Stretch), Stretch.Uniform);
+
+    public static readonly StyledProperty<double> MinScaleProperty =
+        AvaloniaProperty.Register<AdaptiveContainer, double>(nameof(MinScale), 0.5);
+
+    public static readonly StyledProperty<double> MaxScaleProperty =
+        AvaloniaProperty.Register<AdaptiveContainer, double>(nameof(MaxScale), 2.0);
+
+    public double DesignWidth
+    {
+        get => GetValue(DesignWidthProperty);
+        set => SetValue(DesignWidthProperty, value);
+    }
+
+    public double DesignHeight
+    {
+        get => GetValue(DesignHeightProperty);
+        set => SetValue(DesignHeightProperty, value);
+    }
+
+    public bool MaintainAspectRatio
+    {
+        get => GetValue(MaintainAspectRatioProperty);
+        set => SetValue(MaintainAspectRatioProperty, value);
+    }
+
+    public Stretch Stretch
+    {
+        get => GetValue(StretchProperty);
+        set => SetValue(StretchProperty, value);
+    }
+
+    public double MinScale
+    {
+        get => GetValue(MinScaleProperty);
+        set => SetValue(MinScaleProperty, value);
+    }
+
+    public double MaxScale
+    {
+        get => GetValue(MaxScaleProperty);
+        set => SetValue(MaxScaleProperty, value);
+    }
+
+    static AdaptiveContainer()
+    {
+        AffectsMeasure<AdaptiveContainer>(DesignWidthProperty, DesignHeightProperty, MaintainAspectRatioProperty, StretchProperty, MinScaleProperty, MaxScaleProperty);
+        AffectsArrange<AdaptiveContainer>(DesignWidthProperty, DesignHeightProperty, MaintainAspectRatioProperty, StretchProperty, MinScaleProperty, MaxScaleProperty);
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        var child = Child;
+        if (child == null)
+        {
+            return new Size(0, 0);
+        }
+
+        // 让子元素按照设计尺寸测量
+        var designSize = new Size(DesignWidth, DesignHeight);
+        child.Measure(designSize);
+
+        // 计算可用空间
+        var availableWidth = availableSize.Width;
+        var availableHeight = availableSize.Height;
+
+        // 如果可用空间是无限的,使用设计尺寸
+        if (double.IsInfinity(availableWidth))
+        {
+            availableWidth = DesignWidth;
+        }
+        if (double.IsInfinity(availableHeight))
+        {
+            availableHeight = DesignHeight;
+        }
+
+        // 返回可用空间(实际缩放在 Arrange 阶段处理)
+        return new Size(availableWidth, availableHeight);
+    }
+
+    protected override Size ArrangeOverride(Size finalSize)
+    {
+        var child = Child;
+        if (child == null)
+        {
+            return finalSize;
+        }
+
+        // 计算缩放比例
+        var scaleX = finalSize.Width / DesignWidth;
+        var scaleY = finalSize.Height / DesignHeight;
+
+        var scale = Stretch switch
+        {
+            Stretch.Fill => new ScaleTransform(scaleX, scaleY),
+            Stretch.Uniform => new ScaleTransform(Math.Min(scaleX, scaleY), Math.Min(scaleX, scaleY)),
+            Stretch.UniformToFill => new ScaleTransform(Math.Max(scaleX, scaleY), Math.Max(scaleX, scaleY)),
+            _ => new ScaleTransform(1, 1)
+        };
+
+        // 限制缩放范围
+        var uniformScale = Math.Max(MinScale, Math.Min(MaxScale, scale.ScaleX));
+
+        // 更新分辨率管理器
+        ResolutionManager.Instance.UpdateResolution(finalSize.Width, finalSize.Height);
+
+        // 计算实际尺寸
+        var actualWidth = DesignWidth * uniformScale;
+        var actualHeight = DesignHeight * uniformScale;
+
+        // 计算居中偏移
+        var offsetX = (finalSize.Width - actualWidth) / 2;
+        var offsetY = (finalSize.Height - actualHeight) / 2;
+
+        // 应用变换
+        child.RenderTransform = new ScaleTransform(uniformScale, uniformScale);
+        child.RenderTransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
+
+        // 排列子元素
+        child.Arrange(new Rect(offsetX, offsetY, DesignWidth, DesignHeight));
+
+        return finalSize;
+    }
+}

+ 173 - 0
src/YZWater.Avalonia/Controls/FanControl.cs

@@ -0,0 +1,173 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Threading;
+using System;
+using System.Globalization;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 风扇控件 - 模拟 HslCoolFan
+/// </summary>
+public class FanControl : Control
+{
+    public static readonly StyledProperty<bool> IsRunningProperty =
+        AvaloniaProperty.Register<FanControl, bool>(nameof(IsRunning), false);
+
+    public static readonly StyledProperty<double> SpeedProperty =
+        AvaloniaProperty.Register<FanControl, double>(nameof(Speed), 1.0);
+
+    public static readonly StyledProperty<IBrush> FanColorProperty =
+        AvaloniaProperty.Register<FanControl, IBrush>(nameof(FanColor), new SolidColorBrush(Color.Parse("#607D8B")));
+
+    public static readonly StyledProperty<string> TextProperty =
+        AvaloniaProperty.Register<FanControl, string>(nameof(Text), "风扇");
+
+    public bool IsRunning
+    {
+        get => GetValue(IsRunningProperty);
+        set => SetValue(IsRunningProperty, value);
+    }
+
+    public double Speed
+    {
+        get => GetValue(SpeedProperty);
+        set => SetValue(SpeedProperty, value);
+    }
+
+    public IBrush FanColor
+    {
+        get => GetValue(FanColorProperty);
+        set => SetValue(FanColorProperty, value);
+    }
+
+    public string Text
+    {
+        get => GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
+    }
+
+    private double _rotationAngle = 0;
+    private IDisposable? _timerSubscription;
+
+    static FanControl()
+    {
+        AffectsRender<FanControl>(IsRunningProperty, SpeedProperty, FanColorProperty, TextProperty);
+    }
+
+    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnAttachedToVisualTree(e);
+        if (IsRunning)
+        {
+            StartAnimation();
+        }
+    }
+
+    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnDetachedFromVisualTree(e);
+        StopAnimation();
+    }
+
+    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+    {
+        base.OnPropertyChanged(change);
+        if (change.Property == IsRunningProperty)
+        {
+            if (IsRunning)
+            {
+                StartAnimation();
+            }
+            else
+            {
+                StopAnimation();
+                _rotationAngle = 0;
+            }
+        }
+    }
+
+    private void StartAnimation()
+    {
+        StopAnimation();
+        _timerSubscription = DispatcherTimer.Run(() =>
+        {
+            _rotationAngle += Speed * 2;
+            if (_rotationAngle >= 360) _rotationAngle -= 360;
+            InvalidateVisual();
+            return true;
+        }, TimeSpan.FromMilliseconds(50));
+    }
+
+    private void StopAnimation()
+    {
+        _timerSubscription?.Dispose();
+        _timerSubscription = null;
+    }
+
+    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;
+        var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 5;
+        var typeface = new Typeface("Microsoft YaHei");
+
+        // 绘制外框
+        var outerPen = new Pen(FanColor, 2);
+        context.DrawRectangle(null, outerPen, new Rect(
+            centerX - radius,
+            centerY - radius,
+            radius * 2,
+            radius * 2));
+
+        // 绘制风扇叶片
+        var bladeCount = 4;
+        var bladeLength = radius * 0.8;
+        var bladeWidth = radius * 0.25;
+
+        for (int i = 0; i < bladeCount; i++)
+        {
+            var angle = _rotationAngle + (360.0 / bladeCount) * i;
+            var radians = angle * Math.PI / 180;
+
+            var bladeX = centerX + Math.Cos(radians) * bladeLength * 0.4;
+            var bladeY = centerY + Math.Sin(radians) * bladeLength * 0.4;
+
+            var bladeRect = new Rect(
+                bladeX - bladeWidth / 2,
+                bladeY - bladeLength / 2,
+                bladeWidth,
+                bladeLength);
+
+            context.DrawRectangle(FanColor, null, bladeRect);
+        }
+
+        // 绘制中心圆
+        var centerRadius = radius * 0.15;
+        context.DrawEllipse(FanColor, null, new Rect(
+            centerX - centerRadius,
+            centerY - centerRadius,
+            centerRadius * 2,
+            centerRadius * 2));
+
+        // 绘制文字
+        if (!string.IsNullOrEmpty(Text))
+        {
+            var foreground = new SolidColorBrush(Colors.Black);
+            var text = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, foreground);
+            var textPoint = new Point(
+                centerX - text.Width / 2,
+                bounds.Bottom - text.Height - 2);
+            context.DrawText(text, textPoint);
+        }
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        return new Size(50, 50);
+    }
+}

+ 167 - 0
src/YZWater.Avalonia/Controls/PipeLineControl.cs

@@ -0,0 +1,167 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Threading;
+using System;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 管道控件 - 模拟 PipeLine
+/// </summary>
+public class PipeLineControl : Control
+{
+    public static readonly StyledProperty<bool> IsFlowProperty =
+        AvaloniaProperty.Register<PipeLineControl, bool>(nameof(IsFlow), false);
+
+    public static readonly StyledProperty<double> FlowSpeedProperty =
+        AvaloniaProperty.Register<PipeLineControl, double>(nameof(FlowSpeed), 1.0);
+
+    public static readonly StyledProperty<IBrush> PipeColorProperty =
+        AvaloniaProperty.Register<PipeLineControl, IBrush>(nameof(PipeColor), new SolidColorBrush(Color.Parse("#4CAF50")));
+
+    public static readonly StyledProperty<IBrush> WaterColorProperty =
+        AvaloniaProperty.Register<PipeLineControl, IBrush>(nameof(WaterColor), new SolidColorBrush(Color.Parse("#2196F3")));
+
+    public static readonly StyledProperty<double> PipeWidthProperty =
+        AvaloniaProperty.Register<PipeLineControl, double>(nameof(PipeWidth), 20.0);
+
+    public static readonly StyledProperty<bool> IsHorizontalProperty =
+        AvaloniaProperty.Register<PipeLineControl, bool>(nameof(IsHorizontal), true);
+
+    public bool IsFlow
+    {
+        get => GetValue(IsFlowProperty);
+        set => SetValue(IsFlowProperty, value);
+    }
+
+    public double FlowSpeed
+    {
+        get => GetValue(FlowSpeedProperty);
+        set => SetValue(FlowSpeedProperty, value);
+    }
+
+    public IBrush PipeColor
+    {
+        get => GetValue(PipeColorProperty);
+        set => SetValue(PipeColorProperty, value);
+    }
+
+    public IBrush WaterColor
+    {
+        get => GetValue(WaterColorProperty);
+        set => SetValue(WaterColorProperty, value);
+    }
+
+    public double PipeWidth
+    {
+        get => GetValue(PipeWidthProperty);
+        set => SetValue(PipeWidthProperty, value);
+    }
+
+    public bool IsHorizontal
+    {
+        get => GetValue(IsHorizontalProperty);
+        set => SetValue(IsHorizontalProperty, value);
+    }
+
+    private double _flowOffset = 0;
+    private IDisposable? _timerSubscription;
+
+    static PipeLineControl()
+    {
+        AffectsRender<PipeLineControl>(IsFlowProperty, FlowSpeedProperty, PipeColorProperty, WaterColorProperty, PipeWidthProperty, IsHorizontalProperty);
+    }
+
+    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnAttachedToVisualTree(e);
+        if (IsFlow)
+        {
+            StartAnimation();
+        }
+    }
+
+    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnDetachedFromVisualTree(e);
+        StopAnimation();
+    }
+
+    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+    {
+        base.OnPropertyChanged(change);
+        if (change.Property == IsFlowProperty)
+        {
+            if (IsFlow)
+            {
+                StartAnimation();
+            }
+            else
+            {
+                StopAnimation();
+                _flowOffset = 0;
+            }
+        }
+    }
+
+    private void StartAnimation()
+    {
+        StopAnimation();
+        _timerSubscription = DispatcherTimer.Run(() =>
+        {
+            _flowOffset += FlowSpeed;
+            if (_flowOffset >= 20) _flowOffset -= 20;
+            InvalidateVisual();
+            return true; // 继续运行
+        }, TimeSpan.FromMilliseconds(50));
+    }
+
+    private void StopAnimation()
+    {
+        _timerSubscription?.Dispose();
+        _timerSubscription = null;
+    }
+
+    public override void Render(DrawingContext context)
+    {
+        base.Render(context);
+
+        var bounds = new Rect(Bounds.Size);
+        var pipeWidth = PipeWidth;
+
+        if (IsHorizontal)
+        {
+            // 绘制水平管道
+            var pipeRect = new Rect(0, (bounds.Height - pipeWidth) / 2, bounds.Width, pipeWidth);
+            context.DrawRectangle(PipeColor, null, pipeRect);
+
+            // 绘制流动效果
+            if (IsFlow)
+            {
+                var dashPen = new Pen(WaterColor, pipeWidth * 0.6, new DashStyle(new double[] { 8, 12 }, _flowOffset));
+                var y = bounds.Height / 2;
+                context.DrawLine(dashPen, new Point(0, y), new Point(bounds.Width, y));
+            }
+        }
+        else
+        {
+            // 绘制垂直管道
+            var pipeRect = new Rect((bounds.Width - pipeWidth) / 2, 0, pipeWidth, bounds.Height);
+            context.DrawRectangle(PipeColor, null, pipeRect);
+
+            // 绘制流动效果
+            if (IsFlow)
+            {
+                var dashPen = new Pen(WaterColor, pipeWidth * 0.6, new DashStyle(new double[] { 8, 12 }, _flowOffset));
+                var x = bounds.Width / 2;
+                context.DrawLine(dashPen, new Point(x, 0), new Point(x, bounds.Height));
+            }
+        }
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        return IsHorizontal ? new Size(80, 30) : new Size(30, 80);
+    }
+}

+ 191 - 0
src/YZWater.Avalonia/Controls/PumpControl.cs

@@ -0,0 +1,191 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Threading;
+using System;
+using System.Globalization;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 泵控件 - 模拟 HslPumpOne
+/// </summary>
+public class PumpControl : Control
+{
+    public static readonly StyledProperty<bool> IsRunningProperty =
+        AvaloniaProperty.Register<PumpControl, bool>(nameof(IsRunning), false);
+
+    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<string> TextProperty =
+        AvaloniaProperty.Register<PumpControl, string>(nameof(Text), string.Empty);
+
+    public bool IsRunning
+    {
+        get => GetValue(IsRunningProperty);
+        set => SetValue(IsRunningProperty, value);
+    }
+
+    public double Speed
+    {
+        get => GetValue(SpeedProperty);
+        set => SetValue(SpeedProperty, value);
+    }
+
+    public IBrush Color1
+    {
+        get => GetValue(Color1Property);
+        set => SetValue(Color1Property, value);
+    }
+
+    public IBrush Color2
+    {
+        get => GetValue(Color2Property);
+        set => SetValue(Color2Property, value);
+    }
+
+    public IBrush Color3
+    {
+        get => GetValue(Color3Property);
+        set => SetValue(Color3Property, value);
+    }
+
+    public string Text
+    {
+        get => GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
+    }
+
+    private double _rotationAngle = 0;
+    private IDisposable? _timerSubscription;
+
+    static PumpControl()
+    {
+        AffectsRender<PumpControl>(IsRunningProperty, SpeedProperty, Color1Property, Color2Property, Color3Property, TextProperty);
+    }
+
+    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnAttachedToVisualTree(e);
+        if (IsRunning)
+        {
+            StartAnimation();
+        }
+    }
+
+    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+    {
+        base.OnDetachedFromVisualTree(e);
+        StopAnimation();
+    }
+
+    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+    {
+        base.OnPropertyChanged(change);
+        if (change.Property == IsRunningProperty)
+        {
+            if (IsRunning)
+            {
+                StartAnimation();
+            }
+            else
+            {
+                StopAnimation();
+                _rotationAngle = 0;
+            }
+        }
+    }
+
+    private void StartAnimation()
+    {
+        StopAnimation();
+        _timerSubscription = DispatcherTimer.Run(() =>
+        {
+            _rotationAngle += Speed;
+            if (_rotationAngle >= 360) _rotationAngle -= 360;
+            InvalidateVisual();
+            return true;
+        }, TimeSpan.FromMilliseconds(50));
+    }
+
+    private void StopAnimation()
+    {
+        _timerSubscription?.Dispose();
+        _timerSubscription = null;
+    }
+
+    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;
+        var radius = Math.Min(bounds.Width, bounds.Height) / 2 - 5;
+        var typeface = new Typeface("Microsoft YaHei");
+
+        // 绘制泵外壳
+        var outerPen = new Pen(Color1, 3);
+        context.DrawEllipse(null, outerPen, new Rect(
+            centerX - radius,
+            centerY - radius,
+            radius * 2,
+            radius * 2));
+
+        // 绘制泵叶片
+        var bladeCount = 3;
+        var bladeLength = radius * 0.7;
+        var bladeWidth = radius * 0.3;
+
+        for (int i = 0; i < bladeCount; i++)
+        {
+            var angle = _rotationAngle + (360.0 / bladeCount) * i;
+            var radians = angle * Math.PI / 180;
+
+            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);
+
+            context.DrawRectangle(Color2, null, bladeRect);
+        }
+
+        // 绘制中心圆
+        var centerRadius = radius * 0.2;
+        context.DrawEllipse(Color3, null, new Rect(
+            centerX - centerRadius,
+            centerY - centerRadius,
+            centerRadius * 2,
+            centerRadius * 2));
+
+        // 绘制文字
+        if (!string.IsNullOrEmpty(Text))
+        {
+            var foreground = new SolidColorBrush(Colors.Black);
+            var text = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, foreground);
+            var textPoint = new Point(
+                centerX - text.Width / 2,
+                bounds.Bottom - text.Height - 2);
+            context.DrawText(text, textPoint);
+        }
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        return new Size(60, 60);
+    }
+}

+ 147 - 0
src/YZWater.Avalonia/Controls/ValveControl.cs

@@ -0,0 +1,147 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using System;
+using System.Globalization;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 阀门状态枚举
+/// </summary>
+public enum ValveStatus
+{
+    /// <summary>
+    /// 关闭
+    /// </summary>
+    Closed,
+
+    /// <summary>
+    /// 中间位置
+    /// </summary>
+    Middle,
+
+    /// <summary>
+    /// 打开
+    /// </summary>
+    Open
+}
+
+/// <summary>
+/// 阀门控件 - 模拟 HslSwitch
+/// </summary>
+public class ValveControl : Control
+{
+    public static readonly StyledProperty<ValveStatus> StatusProperty =
+        AvaloniaProperty.Register<ValveControl, ValveStatus>(nameof(Status), ValveStatus.Middle);
+
+    public static readonly StyledProperty<string> TextProperty =
+        AvaloniaProperty.Register<ValveControl, string>(nameof(Text), "阀门");
+
+    public static readonly StyledProperty<IBrush> ValveColorProperty =
+        AvaloniaProperty.Register<ValveControl, IBrush>(nameof(ValveColor), new SolidColorBrush(Color.Parse("#FF5722")));
+
+    public static readonly StyledProperty<IBrush> BackgroundColorProperty =
+        AvaloniaProperty.Register<ValveControl, IBrush>(nameof(BackgroundColor), new SolidColorBrush(Color.Parse("#333333")));
+
+    public ValveStatus Status
+    {
+        get => GetValue(StatusProperty);
+        set => SetValue(StatusProperty, value);
+    }
+
+    public string Text
+    {
+        get => GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
+    }
+
+    public IBrush ValveColor
+    {
+        get => GetValue(ValveColorProperty);
+        set => SetValue(ValveColorProperty, value);
+    }
+
+    public IBrush BackgroundColor
+    {
+        get => GetValue(BackgroundColorProperty);
+        set => SetValue(BackgroundColorProperty, value);
+    }
+
+    static ValveControl()
+    {
+        AffectsRender<ValveControl>(StatusProperty, TextProperty, ValveColorProperty, BackgroundColorProperty);
+    }
+
+    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;
+        var size = Math.Min(bounds.Width, bounds.Height) * 0.8;
+        var typeface = new Typeface("Microsoft YaHei");
+
+        // 绘制背景圆
+        var backgroundRadius = size / 2;
+        context.DrawEllipse(BackgroundColor, null, new Rect(
+            centerX - backgroundRadius,
+            centerY - backgroundRadius,
+            backgroundRadius * 2,
+            backgroundRadius * 2));
+
+        // 绘制阀门手柄
+        var handleLength = size * 0.35;
+        var handleWidth = size * 0.15;
+
+        var angle = Status switch
+        {
+            ValveStatus.Open => 0,
+            ValveStatus.Middle => 45,
+            ValveStatus.Closed => 90,
+            _ => 45
+        };
+
+        // 简化绘制 - 不使用旋转,直接绘制矩形
+        var handleRect = new Rect(
+            centerX - handleWidth / 2,
+            centerY - handleLength,
+            handleWidth,
+            handleLength * 2);
+
+        context.DrawRectangle(ValveColor, null, handleRect);
+
+        // 绘制状态指示器
+        var indicatorSize = size * 0.15;
+        var indicatorColor = Status switch
+        {
+            ValveStatus.Open => new SolidColorBrush(Colors.Green),
+            ValveStatus.Middle => new SolidColorBrush(Colors.Yellow),
+            ValveStatus.Closed => new SolidColorBrush(Colors.Red),
+            _ => new SolidColorBrush(Colors.Gray)
+        };
+
+        context.DrawEllipse(indicatorColor, null, new Rect(
+            centerX - indicatorSize / 2,
+            centerY - backgroundRadius - indicatorSize - 5,
+            indicatorSize,
+            indicatorSize));
+
+        // 绘制文字
+        if (!string.IsNullOrEmpty(Text))
+        {
+            var foreground = new SolidColorBrush(Colors.White);
+            var text = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 10, foreground);
+            var textPoint = new Point(
+                centerX - text.Width / 2,
+                bounds.Bottom - text.Height - 2);
+            context.DrawText(text, textPoint);
+        }
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        return new Size(50, 70);
+    }
+}

+ 113 - 0
src/YZWater.Avalonia/Controls/WaterTankControl.cs

@@ -0,0 +1,113 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using System.Globalization;
+
+namespace YZWater.Avalonia.Controls;
+
+/// <summary>
+/// 水箱控件 - 模拟 HslWaterBox
+/// </summary>
+public class WaterTankControl : Control
+{
+    public static readonly StyledProperty<double> WaterLevelProperty =
+        AvaloniaProperty.Register<WaterTankControl, double>(nameof(WaterLevel), 50.0);
+
+    public static readonly StyledProperty<string> TextProperty =
+        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")));
+
+    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);
+
+    public double WaterLevel
+    {
+        get => GetValue(WaterLevelProperty);
+        set => SetValue(WaterLevelProperty, value);
+    }
+
+    public string Text
+    {
+        get => GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
+    }
+
+    public IBrush WaterColor
+    {
+        get => GetValue(WaterColorProperty);
+        set => SetValue(WaterColorProperty, value);
+    }
+
+    public IBrush BorderColor
+    {
+        get => GetValue(BorderColorProperty);
+        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);
+    }
+
+    public override void Render(DrawingContext context)
+    {
+        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 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);
+
+        // 绘制水
+        if (waterHeight > edgeWidth)
+        {
+            context.DrawRectangle(WaterColor, 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 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);
+    }
+
+    protected override Size MeasureOverride(Size availableSize)
+    {
+        return new Size(100, 120);
+    }
+}

+ 85 - 0
src/YZWater.Avalonia/Converters/BoolToBrushConverter.cs

@@ -0,0 +1,85 @@
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+using System;
+using System.Globalization;
+
+namespace YZWater.Avalonia.Converters;
+
+/// <summary>
+/// 布尔值转画刷转换器
+/// </summary>
+public class BoolToBrushConverter : IValueConverter
+{
+    /// <summary>
+    /// 真值时的画刷
+    /// </summary>
+    public IBrush TrueBrush { get; set; } = new SolidColorBrush(Colors.Green);
+
+    /// <summary>
+    /// 假值时的画刷
+    /// </summary>
+    public IBrush FalseBrush { get; set; } = new SolidColorBrush(Colors.Red);
+
+    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (value is bool boolValue)
+        {
+            return boolValue ? TrueBrush : FalseBrush;
+        }
+        return FalseBrush;
+    }
+
+    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}
+
+/// <summary>
+/// 报警状态转画刷转换器
+/// </summary>
+public class AlarmToBrushConverter : IValueConverter
+{
+    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (value is bool hasAlarm)
+        {
+            return hasAlarm
+                ? new SolidColorBrush(Color.Parse("#F44336"))
+                : new SolidColorBrush(Color.Parse("#4CAF50"));
+        }
+        return new SolidColorBrush(Colors.Gray);
+    }
+
+    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}
+
+/// <summary>
+/// 设备状态转画刷转换器
+/// </summary>
+public class DeviceStatusToBrushConverter : IValueConverter
+{
+    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (value is Core.Models.DeviceStatus status)
+        {
+            return status switch
+            {
+                Core.Models.DeviceStatus.Running => new SolidColorBrush(Color.Parse("#4CAF50")),
+                Core.Models.DeviceStatus.Stopped => new SolidColorBrush(Color.Parse("#9E9E9E")),
+                Core.Models.DeviceStatus.Fault => new SolidColorBrush(Color.Parse("#F44336")),
+                Core.Models.DeviceStatus.Maintenance => new SolidColorBrush(Color.Parse("#FF9800")),
+                _ => new SolidColorBrush(Colors.Gray)
+            };
+        }
+        return new SolidColorBrush(Colors.Gray);
+    }
+
+    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 21 - 0
src/YZWater.Avalonia/Program.cs

@@ -0,0 +1,21 @@
+using Avalonia;
+using System;
+
+namespace YZWater.Avalonia;
+
+class Program
+{
+    // Initialization code. Don't use any Avalonia, third-party APIs or any
+    // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+    // yet and stuff might break.
+    [STAThread]
+    public static void Main(string[] args) => BuildAvaloniaApp()
+        .StartWithClassicDesktopLifetime(args);
+
+    // Avalonia configuration, don't remove; also used by visual designer.
+    public static AppBuilder BuildAvaloniaApp()
+        => AppBuilder.Configure<App>()
+            .UsePlatformDetect()
+            .WithInterFont()
+            .LogToTrace();
+}

+ 86 - 0
src/YZWater.Avalonia/Views/MainWindow.axaml

@@ -0,0 +1,86 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:vm="using:YZWater.Core.ViewModels"
+        xmlns:views="using:YZWater.Avalonia.Views"
+        xmlns:controls="using:YZWater.Avalonia.Controls"
+        x:Class="YZWater.Avalonia.Views.MainWindow"
+        x:DataType="vm:MainViewModel"
+        Title="{Binding Title}"
+        Width="1280"
+        Height="800"
+        MinWidth="800"
+        MinHeight="600"
+        WindowStartupLocation="CenterScreen"
+        Background="#1a1a2e">
+
+    <Window.DataContext>
+        <vm:MainViewModel/>
+    </Window.DataContext>
+
+    <!-- 使用自适应容器包裹整个内容 -->
+    <controls:AdaptiveContainer DesignWidth="1280" DesignHeight="800"
+                                 Stretch="Uniform" MinScale="0.6" MaxScale="1.5">
+        <DockPanel>
+            <!-- 顶部标题栏 -->
+            <Border DockPanel.Dock="Top" Background="#1E3A5F" Padding="10">
+                <Grid ColumnDefinitions="Auto,*,Auto,Auto">
+                    <!-- Logo 和标题 -->
+                    <StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="10">
+                        <TextBlock Text="🏭" FontSize="24" VerticalAlignment="Center"/>
+                        <TextBlock Text="{Binding Title}"
+                                   FontSize="20"
+                                   FontWeight="Bold"
+                                   Foreground="White"
+                                   VerticalAlignment="Center"/>
+                    </StackPanel>
+
+                    <!-- PLC 连接状态 -->
+                    <StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="10" Margin="20,0">
+                        <Ellipse Width="12" Height="12" Fill="Green"/>
+                        <TextBlock Text="{Binding IsPlcConnected, StringFormat='PLC: {0}'}"
+                                   Foreground="White"
+                                   VerticalAlignment="Center"/>
+                    </StackPanel>
+
+                    <!-- 时间显示 -->
+                    <TextBlock Grid.Column="3"
+                               Text="{Binding CurrentTime}"
+                               Foreground="White"
+                               FontSize="16"
+                               VerticalAlignment="Center"/>
+                </Grid>
+            </Border>
+
+            <!-- 底部导航栏 -->
+            <Border DockPanel.Dock="Bottom" Background="#2C5F8A" Padding="5">
+                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20">
+                    <Button Content="📊 主工艺" Command="{Binding ShowViewACommand}" Padding="15,8"/>
+                    <Button Content="⚙️ 参数设置" Command="{Binding ShowViewBCommand}" Padding="15,8"/>
+                    <Button Content="📈 流量记录" Command="{Binding ShowViewCCommand}" Padding="15,8"/>
+                    <Button Content="🔔 报警记录" Command="{Binding ShowViewDCommand}" Padding="15,8"/>
+                    <Button Content="ℹ️ 关于" Command="{Binding ShowViewECommand}" Padding="15,8"/>
+                </StackPanel>
+            </Border>
+
+            <!-- 主内容区域 -->
+            <TabControl SelectedIndex="{Binding SelectedTabIndex}" Padding="10"
+                        Background="Transparent">
+                <TabItem Header="主工艺" IsVisible="False">
+                    <views:ViewAView/>
+                </TabItem>
+                <TabItem Header="参数设置" IsVisible="False">
+                    <views:ViewBView/>
+                </TabItem>
+                <TabItem Header="流量记录" IsVisible="False">
+                    <views:ViewCView/>
+                </TabItem>
+                <TabItem Header="报警记录" IsVisible="False">
+                    <views:ViewDView/>
+                </TabItem>
+                <TabItem Header="关于" IsVisible="False">
+                    <views:ViewEView/>
+                </TabItem>
+            </TabControl>
+        </DockPanel>
+    </controls:AdaptiveContainer>
+</Window>

+ 11 - 0
src/YZWater.Avalonia/Views/MainWindow.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace YZWater.Avalonia.Views;
+
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+}

+ 175 - 0
src/YZWater.Avalonia/Views/ViewAView.axaml

@@ -0,0 +1,175 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:vm="using:YZWater.Core.ViewModels"
+             xmlns:controls="using:YZWater.Avalonia.Controls"
+             x:Class="YZWater.Avalonia.Views.ViewAView"
+             x:DataType="vm:ViewAViewModel">
+
+    <UserControl.DataContext>
+        <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"/>
+                    </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>
+                    <Separator Background="#444"/>
+                    <StackPanel Orientation="Horizontal" Spacing="5">
+                        <Ellipse Width="10" Height="10" Fill="{Binding Fan1StatusColor}"/>
+                        <TextBlock Text="风扇1" Foreground="White"/>
+                    </StackPanel>
+                    <StackPanel Orientation="Horizontal" Spacing="5">
+                        <Ellipse Width="10" Height="10" Fill="{Binding Fan2StatusColor}"/>
+                        <TextBlock Text="风扇2" Foreground="White"/>
+                    </StackPanel>
+                </StackPanel>
+            </Border>
+        </Canvas>
+    </Border>
+</UserControl>

+ 11 - 0
src/YZWater.Avalonia/Views/ViewAView.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace YZWater.Avalonia.Views;
+
+public partial class ViewAView : UserControl
+{
+    public ViewAView()
+    {
+        InitializeComponent();
+    }
+}

+ 77 - 0
src/YZWater.Avalonia/Views/ViewBView.axaml

@@ -0,0 +1,77 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:vm="using:YZWater.Core.ViewModels"
+             x:Class="YZWater.Avalonia.Views.ViewBView"
+             x:DataType="vm:ViewBViewModel">
+
+    <UserControl.DataContext>
+        <vm:ViewBViewModel/>
+    </UserControl.DataContext>
+
+    <Border Background="#1a1a2e" Width="1024" Height="613">
+        <ScrollViewer>
+            <Grid RowDefinitions="Auto,Auto,Auto" Margin="20">
+                <!-- PLC 连接设置 -->
+                <Border Grid.Row="0" Background="#2d2d44" CornerRadius="8" Padding="15" Margin="0,0,0,15">
+                    <StackPanel>
+                        <TextBlock Text="PLC 连接设置" FontWeight="Bold" FontSize="18" Foreground="White" Margin="0,0,0,15"/>
+                        <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto,Auto" Margin="10">
+                            <!-- IP 地址 -->
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="IP 地址:" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding PlcIp}" Margin="5"/>
+
+                            <!-- 端口 -->
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="端口:" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                            <NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding PlcPort}"
+                                           Minimum="1" Maximum="65535" Margin="5"/>
+
+                            <!-- 自动连接 -->
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="自动连接:" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                            <CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding AutoConnect}" Margin="5"/>
+
+                            <!-- 连接状态 -->
+                            <TextBlock Grid.Row="3" Grid.Column="0" Text="状态:" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                            <StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal" Spacing="10" Margin="5">
+                                <TextBlock Text="{Binding ConnectionStatus}" VerticalAlignment="Center" Foreground="White"/>
+                                <Button Content="测试连接" Command="{Binding TestConnectionCommand}"
+                                        IsEnabled="{Binding !IsConnecting}"/>
+                            </StackPanel>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 报警阈值设置 -->
+                <Border Grid.Row="1" Background="#2d2d44" CornerRadius="8" Padding="15" Margin="0,0,0,15">
+                    <StackPanel>
+                        <TextBlock Text="报警阈值设置" FontWeight="Bold" FontSize="18" Foreground="White" Margin="0,0,0,15"/>
+                        <Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto,Auto,Auto" Margin="10">
+                            <!-- 液位高报警 -->
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="液位高报警:" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                            <NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding LevelHighAlarm}"
+                                           Minimum="0" Maximum="100" FormatString="F1" Margin="5"/>
+                            <TextBlock Grid.Row="0" Grid.Column="2" Text="%" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+
+                            <!-- 液位低报警 -->
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="液位低报警:" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                            <NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding LevelLowAlarm}"
+                                           Minimum="0" Maximum="100" FormatString="F1" Margin="5"/>
+                            <TextBlock Grid.Row="1" Grid.Column="2" Text="%" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+
+                            <!-- 流量高报警 -->
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="流量高报警:" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                            <NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding FlowHighAlarm}"
+                                           Minimum="0" Maximum="1000" FormatString="F1" Margin="5"/>
+                            <TextBlock Grid.Row="2" Grid.Column="2" Text="m³/h" VerticalAlignment="Center" Margin="5" Foreground="White"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 操作按钮 -->
+                <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20">
+                    <Button Content="保存设置" Command="{Binding SaveConfigCommand}" Width="120" Height="35"/>
+                    <Button Content="恢复默认" Command="{Binding ResetToDefaultCommand}" Width="120" Height="35"/>
+                </StackPanel>
+            </Grid>
+        </ScrollViewer>
+    </Border>
+</UserControl>

+ 11 - 0
src/YZWater.Avalonia/Views/ViewBView.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace YZWater.Avalonia.Views;
+
+public partial class ViewBView : UserControl
+{
+    public ViewBView()
+    {
+        InitializeComponent();
+    }
+}

+ 72 - 0
src/YZWater.Avalonia/Views/ViewCView.axaml

@@ -0,0 +1,72 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:vm="using:YZWater.Core.ViewModels"
+             xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
+             x:Class="YZWater.Avalonia.Views.ViewCView"
+             x:DataType="vm:ViewCViewModel">
+
+    <UserControl.DataContext>
+        <vm:ViewCViewModel/>
+    </UserControl.DataContext>
+
+    <Border Background="#1a1a2e" Width="1024" Height="613">
+        <Grid RowDefinitions="Auto,*,Auto" Margin="20">
+            <!-- 日期筛选 -->
+            <Border Grid.Row="0" Background="#2d2d44" CornerRadius="8" Padding="15" Margin="0,0,0,15">
+                <StackPanel>
+                    <TextBlock Text="流量记录查询" FontWeight="Bold" FontSize="18" Foreground="White" Margin="0,0,0,15"/>
+                    <StackPanel Orientation="Horizontal" Spacing="20">
+                        <StackPanel Orientation="Horizontal" Spacing="5">
+                            <TextBlock Text="开始日期:" VerticalAlignment="Center" Foreground="White"/>
+                            <DatePicker SelectedDate="{Binding StartDate}" Width="150"/>
+                        </StackPanel>
+                        <StackPanel Orientation="Horizontal" Spacing="5">
+                            <TextBlock Text="结束日期:" VerticalAlignment="Center" Foreground="White"/>
+                            <DatePicker SelectedDate="{Binding EndDate}" Width="150"/>
+                        </StackPanel>
+                        <Button Content="查询" Command="{Binding LoadFlowRecordsCommand}"/>
+                        <Button Content="导出数据" Command="{Binding ExportDataCommand}"/>
+                        <Button Content="清除旧数据" Command="{Binding ClearOldDataCommand}"/>
+                    </StackPanel>
+                </StackPanel>
+            </Border>
+
+            <!-- 图表区域 -->
+            <Border Grid.Row="1" Background="#2d2d44" CornerRadius="8" Padding="15" Margin="0,0,0,15">
+                <ScrollViewer>
+                    <StackPanel>
+                        <TextBlock Text="进水流量趋势" FontWeight="Bold" Foreground="White" Margin="0,0,0,10"/>
+                        <lvc:CartesianChart Series="{Binding InflowSeries}"
+                                            XAxes="{Binding XAxes}"
+                                            YAxes="{Binding YAxes}"
+                                            Height="200"/>
+
+                        <TextBlock Text="出水流量趋势" FontWeight="Bold" Foreground="White" Margin="0,15,0,10"/>
+                        <lvc:CartesianChart Series="{Binding OutflowSeries}"
+                                            XAxes="{Binding XAxes}"
+                                            YAxes="{Binding YAxes}"
+                                            Height="200"/>
+                    </StackPanel>
+                </ScrollViewer>
+            </Border>
+
+            <!-- 数据表格 -->
+            <Border Grid.Row="2" Background="#2d2d44" CornerRadius="8" Padding="15">
+                <DataGrid ItemsSource="{Binding FlowRecords}"
+                          AutoGenerateColumns="False"
+                          IsReadOnly="True"
+                          Height="150"
+                          Background="#1a1a2e"
+                          Foreground="White">
+                    <DataGrid.Columns>
+                        <DataGridTextColumn Header="时间" Binding="{Binding RecordTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150"/>
+                        <DataGridTextColumn Header="进水流量(m³/h)" Binding="{Binding InflowRate, StringFormat='{}{0:F1}'}" Width="120"/>
+                        <DataGridTextColumn Header="出水流量(m³/h)" Binding="{Binding OutflowRate, StringFormat='{}{0:F1}'}" Width="120"/>
+                        <DataGridTextColumn Header="累计进水量(m³)" Binding="{Binding TotalInflow, StringFormat='{}{0:F1}'}" Width="120"/>
+                        <DataGridTextColumn Header="累计出水量(m³)" Binding="{Binding TotalOutflow, StringFormat='{}{0:F1}'}" Width="120"/>
+                    </DataGrid.Columns>
+                </DataGrid>
+            </Border>
+        </Grid>
+    </Border>
+</UserControl>

+ 11 - 0
src/YZWater.Avalonia/Views/ViewCView.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace YZWater.Avalonia.Views;
+
+public partial class ViewCView : UserControl
+{
+    public ViewCView()
+    {
+        InitializeComponent();
+    }
+}

+ 93 - 0
src/YZWater.Avalonia/Views/ViewDView.axaml

@@ -0,0 +1,93 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:vm="using:YZWater.Core.ViewModels"
+             x:Class="YZWater.Avalonia.Views.ViewDView"
+             x:DataType="vm:ViewDViewModel">
+
+    <UserControl.DataContext>
+        <vm:ViewDViewModel/>
+    </UserControl.DataContext>
+
+    <Border Background="#1a1a2e" Width="1024" Height="613">
+        <Grid RowDefinitions="Auto,*,Auto" Margin="20">
+            <!-- 查询条件 -->
+            <Border Grid.Row="0" Background="#2d2d44" CornerRadius="8" Padding="15" Margin="0,0,0,15">
+                <StackPanel>
+                    <TextBlock Text="报警记录查询" FontWeight="Bold" FontSize="18" Foreground="White" Margin="0,0,0,15"/>
+                    <StackPanel Orientation="Horizontal" Spacing="20">
+                        <StackPanel Orientation="Horizontal" Spacing="5">
+                            <TextBlock Text="开始日期:" VerticalAlignment="Center" Foreground="White"/>
+                            <DatePicker SelectedDate="{Binding StartDate}" Width="150"/>
+                        </StackPanel>
+                        <StackPanel Orientation="Horizontal" Spacing="5">
+                            <TextBlock Text="结束日期:" VerticalAlignment="Center" Foreground="White"/>
+                            <DatePicker SelectedDate="{Binding EndDate}" Width="150"/>
+                        </StackPanel>
+                        <Button Content="查询" Command="{Binding LoadAlarmRecordsCommand}"/>
+                        <Button Content="导出记录" Command="{Binding ExportAlarmsCommand}"/>
+                        <Button Content="清除历史" Command="{Binding ClearHistoryCommand}"/>
+                    </StackPanel>
+
+                    <!-- 未确认报警数 -->
+                    <Border Background="#F44336" CornerRadius="5" Padding="10" Margin="0,10,0,0"
+                            IsVisible="{Binding UnconfirmedCount}">
+                        <StackPanel Orientation="Horizontal" Spacing="10">
+                            <TextBlock Text="⚠️" FontSize="16"/>
+                            <TextBlock Text="{Binding UnconfirmedCount, StringFormat='未确认报警: {0} 条'}"
+                                       Foreground="White" FontWeight="Bold"/>
+                            <Button Content="全部确认" Command="{Binding ConfirmAllAlarmsCommand}"
+                                    Margin="20,0,0,0"/>
+                        </StackPanel>
+                    </Border>
+                </StackPanel>
+            </Border>
+
+            <!-- 报警列表 -->
+            <Border Grid.Row="1" Background="#2d2d44" CornerRadius="8" Padding="15" Margin="0,0,0,15">
+                <DataGrid ItemsSource="{Binding AlarmRecords}"
+                          AutoGenerateColumns="False"
+                          IsReadOnly="True"
+                          SelectedItem="{Binding SelectedRecord}"
+                          Background="#1a1a2e"
+                          Foreground="White">
+                    <DataGrid.Columns>
+                        <DataGridTextColumn Header="时间" Binding="{Binding AlarmTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150"/>
+                        <DataGridTextColumn Header="类型" Binding="{Binding AlarmType}" Width="100"/>
+                        <DataGridTextColumn Header="内容" Binding="{Binding AlarmMessage}" Width="200"/>
+                        <DataGridTextColumn Header="报警值" Binding="{Binding AlarmValue, StringFormat='{}{0:F1}'}" Width="80"/>
+                        <DataGridTextColumn Header="级别" Binding="{Binding AlarmLevel}" Width="60"/>
+                        <DataGridCheckBoxColumn Header="已确认" Binding="{Binding IsConfirmed}" Width="80"/>
+                        <DataGridTextColumn Header="确认时间" Binding="{Binding ConfirmedTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150"/>
+                        <DataGridTextColumn Header="确认人" Binding="{Binding ConfirmedBy}" Width="80"/>
+                    </DataGrid.Columns>
+                </DataGrid>
+            </Border>
+
+            <!-- 报警详情 -->
+            <Border Grid.Row="2" Background="#2d2d44" CornerRadius="8" Padding="15"
+                    IsVisible="{Binding SelectedRecord, Converter={x:Static ObjectConverters.IsNotNull}}">
+                <StackPanel>
+                    <TextBlock Text="报警详情" FontWeight="Bold" FontSize="16" Foreground="White" Margin="0,0,0,10"/>
+                    <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto,Auto">
+                        <TextBlock Grid.Row="0" Grid.Column="0" Text="时间:" Margin="5" Foreground="White"/>
+                        <TextBlock Grid.Row="0" Grid.Column="1"
+                                   Text="{Binding SelectedRecord.AlarmTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}"
+                                   Margin="5" Foreground="White"/>
+
+                        <TextBlock Grid.Row="1" Grid.Column="0" Text="类型:" Margin="5" Foreground="White"/>
+                        <TextBlock Grid.Row="1" Grid.Column="1"
+                                   Text="{Binding SelectedRecord.AlarmType}" Margin="5" Foreground="White"/>
+
+                        <TextBlock Grid.Row="2" Grid.Column="0" Text="内容:" Margin="5" Foreground="White"/>
+                        <TextBlock Grid.Row="2" Grid.Column="1"
+                                   Text="{Binding SelectedRecord.AlarmMessage}" Margin="5" Foreground="White"/>
+
+                        <TextBlock Grid.Row="3" Grid.Column="0" Text="级别:" Margin="5" Foreground="White"/>
+                        <TextBlock Grid.Row="3" Grid.Column="1"
+                                   Text="{Binding SelectedRecord.AlarmLevel, StringFormat='{}Level {0}'}" Margin="5" Foreground="White"/>
+                    </Grid>
+                </StackPanel>
+            </Border>
+        </Grid>
+    </Border>
+</UserControl>

+ 11 - 0
src/YZWater.Avalonia/Views/ViewDView.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace YZWater.Avalonia.Views;
+
+public partial class ViewDView : UserControl
+{
+    public ViewDView()
+    {
+        InitializeComponent();
+    }
+}

+ 65 - 0
src/YZWater.Avalonia/Views/ViewEView.axaml

@@ -0,0 +1,65 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:vm="using:YZWater.Core.ViewModels"
+             x:Class="YZWater.Avalonia.Views.ViewEView"
+             x:DataType="vm:ViewEViewModel">
+
+    <UserControl.DataContext>
+        <vm:ViewEViewModel/>
+    </UserControl.DataContext>
+
+    <Border Background="#1a1a2e" Width="1024" Height="613">
+        <ScrollViewer>
+            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="30" Margin="50">
+                <!-- Logo 和系统名称 -->
+                <StackPanel HorizontalAlignment="Center" Spacing="15">
+                    <TextBlock Text="🏭" FontSize="64" HorizontalAlignment="Center"/>
+                    <TextBlock Text="{Binding SystemName}"
+                               FontSize="32" FontWeight="Bold"
+                               HorizontalAlignment="Center"
+                               Foreground="White"/>
+                    <TextBlock Text="{Binding Version, StringFormat='Version {0}'}"
+                               FontSize="16" Foreground="Gray"
+                               HorizontalAlignment="Center"/>
+                </StackPanel>
+
+                <!-- 系统描述 -->
+                <Border Background="#2d2d44" CornerRadius="10" Padding="20" MaxWidth="500">
+                    <StackPanel Spacing="10">
+                        <TextBlock Text="系统简介" FontWeight="Bold" FontSize="16" Foreground="White"/>
+                        <TextBlock Text="{Binding Description}" TextWrapping="Wrap" Foreground="#cccccc"/>
+                        <TextBlock Text="{Binding TechStack}" Foreground="Gray"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 公司信息 -->
+                <Border Background="#2d2d44" CornerRadius="10" Padding="20" MaxWidth="500">
+                    <StackPanel Spacing="15">
+                        <TextBlock Text="公司信息" FontWeight="Bold" FontSize="16" Foreground="White"/>
+                        <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto">
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="公司名称:" Margin="0,0,10,0" Foreground="White"/>
+                            <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding CompanyName}" Foreground="#cccccc"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="联系人:" Margin="0,0,10,0" Foreground="White"/>
+                            <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding ContactPerson}" Foreground="#cccccc"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="联系电话:" Margin="0,0,10,0" Foreground="White"/>
+                            <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ContactPhone}" Foreground="#cccccc"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 操作按钮 -->
+                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20">
+                    <Button Content="访问官网" Command="{Binding OpenWebsiteCommand}" Width="120" Height="35"/>
+                    <Button Content="检查更新" Command="{Binding CheckUpdateCommand}" Width="120" Height="35"/>
+                </StackPanel>
+
+                <!-- 版权信息 -->
+                <TextBlock Text="{Binding Copyright}"
+                           Foreground="Gray" FontSize="12"
+                           HorizontalAlignment="Center" Margin="0,20,0,0"/>
+            </StackPanel>
+        </ScrollViewer>
+    </Border>
+</UserControl>

+ 11 - 0
src/YZWater.Avalonia/Views/ViewEView.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace YZWater.Avalonia.Views;
+
+public partial class ViewEView : UserControl
+{
+    public ViewEView()
+    {
+        InitializeComponent();
+    }
+}

+ 35 - 0
src/YZWater.Avalonia/YZWater.Avalonia.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+    <ApplicationIcon>Assets\污水处理厂.ico</ApplicationIcon>
+    <RootNamespace>YZWater.Avalonia</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Avalonia" Version="11.2.3" />
+    <PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
+    <PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
+    <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
+    <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
+    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.3" />
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
+    <PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0-rc4.5" />
+    <PackageReference Include="Semi.Avalonia" Version="11.2.1.4" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\YZWater.Core\YZWater.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Resource Include="Assets\污水处理厂.ico" />
+  </ItemGroup>
+
+</Project>

+ 25 - 0
src/YZWater.Avalonia/app.manifest

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="YZWater.Avalonia.Desktop"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 and Windows 11 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+    </application>
+  </compatibility>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+    </windowsSettings>
+  </application>
+</assembly>

+ 63 - 0
src/YZWater.Core/Models/AlarmRecord.cs

@@ -0,0 +1,63 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using FreeSql.DataAnnotations;
+
+namespace YZWater.Core.Models;
+
+/// <summary>
+/// 报警记录模型
+/// </summary>
+[Table(Name = "alarm_records")]
+public partial class AlarmRecord : ObservableObject
+{
+    /// <summary>
+    /// 主键 ID
+    /// </summary>
+    [Column(IsIdentity = true, IsPrimary = true)]
+    public int Id { get; set; }
+
+    /// <summary>
+    /// 报警时间
+    /// </summary>
+    public DateTime AlarmTime { get; set; } = DateTime.Now;
+
+    /// <summary>
+    /// 报警类型
+    /// </summary>
+    [ObservableProperty]
+    private string _alarmType = string.Empty;
+
+    /// <summary>
+    /// 报警内容
+    /// </summary>
+    [ObservableProperty]
+    private string _alarmMessage = string.Empty;
+
+    /// <summary>
+    /// 报警值
+    /// </summary>
+    [ObservableProperty]
+    private float _alarmValue;
+
+    /// <summary>
+    /// 报警级别 (1-5)
+    /// </summary>
+    [ObservableProperty]
+    private int _alarmLevel = 1;
+
+    /// <summary>
+    /// 是否已确认
+    /// </summary>
+    [ObservableProperty]
+    private bool _isConfirmed;
+
+    /// <summary>
+    /// 确认时间
+    /// </summary>
+    public DateTime? ConfirmedTime { get; set; }
+
+    /// <summary>
+    /// 确认人
+    /// </summary>
+    [ObservableProperty]
+    private string? _confirmedBy;
+}

+ 83 - 0
src/YZWater.Core/Models/EquipmentStatus.cs

@@ -0,0 +1,83 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace YZWater.Core.Models;
+
+/// <summary>
+/// 设备状态枚举
+/// </summary>
+public enum DeviceStatus
+{
+    /// <summary>
+    /// 停止
+    /// </summary>
+    Stopped,
+
+    /// <summary>
+    /// 运行
+    /// </summary>
+    Running,
+
+    /// <summary>
+    /// 故障
+    /// </summary>
+    Fault,
+
+    /// <summary>
+    /// 维护
+    /// </summary>
+    Maintenance
+}
+
+/// <summary>
+/// 设备状态模型
+/// </summary>
+public partial class EquipmentStatus : ObservableObject
+{
+    /// <summary>
+    /// 设备 ID
+    /// </summary>
+    [ObservableProperty]
+    private string _deviceId = string.Empty;
+
+    /// <summary>
+    /// 设备名称
+    /// </summary>
+    [ObservableProperty]
+    private string _deviceName = string.Empty;
+
+    /// <summary>
+    /// 设备状态
+    /// </summary>
+    [ObservableProperty]
+    private DeviceStatus _status = DeviceStatus.Stopped;
+
+    /// <summary>
+    /// 运行时间 (小时)
+    /// </summary>
+    [ObservableProperty]
+    private float _runningHours;
+
+    /// <summary>
+    /// 是否在线
+    /// </summary>
+    [ObservableProperty]
+    private bool _isOnline;
+
+    /// <summary>
+    /// 最后更新时间
+    /// </summary>
+    [ObservableProperty]
+    private DateTime _lastUpdateTime = DateTime.Now;
+
+    /// <summary>
+    /// 读数/数值
+    /// </summary>
+    [ObservableProperty]
+    private float _value;
+
+    /// <summary>
+    /// 单位
+    /// </summary>
+    [ObservableProperty]
+    private string _unit = string.Empty;
+}

+ 52 - 0
src/YZWater.Core/Models/FlowRecord.cs

@@ -0,0 +1,52 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using FreeSql.DataAnnotations;
+
+namespace YZWater.Core.Models;
+
+/// <summary>
+/// 流量记录模型
+/// </summary>
+[Table(Name = "flow_records")]
+public partial class FlowRecord : ObservableObject
+{
+    /// <summary>
+    /// 主键 ID
+    /// </summary>
+    [Column(IsIdentity = true, IsPrimary = true)]
+    public int Id { get; set; }
+
+    /// <summary>
+    /// 记录时间
+    /// </summary>
+    public DateTime RecordTime { get; set; } = DateTime.Now;
+
+    /// <summary>
+    /// 进水流量 (m³/h)
+    /// </summary>
+    [ObservableProperty]
+    private float _inflowRate;
+
+    /// <summary>
+    /// 出水流量 (m³/h)
+    /// </summary>
+    [ObservableProperty]
+    private float _outflowRate;
+
+    /// <summary>
+    /// 累计进水量 (m³)
+    /// </summary>
+    [ObservableProperty]
+    private float _totalInflow;
+
+    /// <summary>
+    /// 累计出水量 (m³)
+    /// </summary>
+    [ObservableProperty]
+    private float _totalOutflow;
+
+    /// <summary>
+    /// 备注
+    /// </summary>
+    [ObservableProperty]
+    private string? _remark;
+}

+ 39 - 0
src/YZWater.Core/Models/Person.cs

@@ -0,0 +1,39 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using FreeSql.DataAnnotations;
+
+namespace YZWater.Core.Models;
+
+/// <summary>
+/// 人员模型
+/// </summary>
+[Table(Name = "persons")]
+public partial class Person : ObservableObject
+{
+    /// <summary>
+    /// 主键 ID
+    /// </summary>
+    [Column(IsIdentity = true, IsPrimary = true)]
+    public int Id { get; set; }
+
+    /// <summary>
+    /// 姓
+    /// </summary>
+    [ObservableProperty]
+    private string _firstName = string.Empty;
+
+    /// <summary>
+    /// 名
+    /// </summary>
+    [ObservableProperty]
+    private string _lastName = string.Empty;
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    public DateTime CreatedTime { get; set; } = DateTime.Now;
+
+    /// <summary>
+    /// 全名
+    /// </summary>
+    public string FullName => $"{FirstName} {LastName}";
+}

+ 75 - 0
src/YZWater.Core/Models/SystemConfig.cs

@@ -0,0 +1,75 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace YZWater.Core.Models;
+
+/// <summary>
+/// 系统配置模型
+/// </summary>
+public partial class SystemConfig : ObservableObject
+{
+    /// <summary>
+    /// PLC IP 地址
+    /// </summary>
+    [ObservableProperty]
+    private string _plcIp = "192.168.0.150";
+
+    /// <summary>
+    /// PLC 端口
+    /// </summary>
+    [ObservableProperty]
+    private int _plcPort = 5000;
+
+    /// <summary>
+    /// 是否自动连接
+    /// </summary>
+    [ObservableProperty]
+    private bool _autoConnect = true;
+
+    /// <summary>
+    /// 液位高报警值
+    /// </summary>
+    [ObservableProperty]
+    private float _levelHighAlarm = 80f;
+
+    /// <summary>
+    /// 液位低报警值
+    /// </summary>
+    [ObservableProperty]
+    private float _levelLowAlarm = 20f;
+
+    /// <summary>
+    /// 流量高报警值
+    /// </summary>
+    [ObservableProperty]
+    private float _flowHighAlarm = 100f;
+
+    /// <summary>
+    /// 泵频率设置
+    /// </summary>
+    [ObservableProperty]
+    private float _pumpFrequency = 50f;
+
+    /// <summary>
+    /// 系统名称
+    /// </summary>
+    [ObservableProperty]
+    private string _systemName = "污水处理监控系统";
+
+    /// <summary>
+    /// 公司名称
+    /// </summary>
+    [ObservableProperty]
+    private string _companyName = "扬州旭轩科技有限公司";
+
+    /// <summary>
+    /// 联系人
+    /// </summary>
+    [ObservableProperty]
+    private string _contactPerson = "赵经理";
+
+    /// <summary>
+    /// 联系电话
+    /// </summary>
+    [ObservableProperty]
+    private string _contactPhone = "18115099090";
+}

+ 94 - 0
src/YZWater.Core/Services/ConfigService.cs

@@ -0,0 +1,94 @@
+using System.Text.Json;
+using Serilog;
+using YZWater.Core.Models;
+
+namespace YZWater.Core.Services;
+
+/// <summary>
+/// 配置服务 - 管理应用程序配置
+/// </summary>
+public static class ConfigService
+{
+    private static readonly string ConfigFilePath = "yzwater-config.json";
+    private static SystemConfig? _config;
+
+    /// <summary>
+    /// 获取配置
+    /// </summary>
+    public static SystemConfig? GetConfig()
+    {
+        if (_config == null)
+        {
+            LoadConfig();
+        }
+        return _config;
+    }
+
+    /// <summary>
+    /// 加载配置文件
+    /// </summary>
+    public static void LoadConfig()
+    {
+        try
+        {
+            if (File.Exists(ConfigFilePath))
+            {
+                var json = File.ReadAllText(ConfigFilePath);
+                _config = JsonSerializer.Deserialize<SystemConfig>(json);
+                Log.Information("配置文件加载成功");
+            }
+            else
+            {
+                // 创建默认配置
+                _config = new SystemConfig
+                {
+                    PlcIp = "192.168.0.150",
+                    PlcPort = 5000,
+                    AutoConnect = true,
+                    LevelHighAlarm = 80f,
+                    LevelLowAlarm = 20f,
+                    FlowHighAlarm = 100f
+                };
+                SaveConfig();
+                Log.Information("已创建默认配置文件");
+            }
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "加载配置文件失败");
+            _config = new SystemConfig();
+        }
+    }
+
+    /// <summary>
+    /// 保存配置文件
+    /// </summary>
+    public static void SaveConfig()
+    {
+        try
+        {
+            if (_config == null) return;
+
+            var options = new JsonSerializerOptions
+            {
+                WriteIndented = true
+            };
+            var json = JsonSerializer.Serialize(_config, options);
+            File.WriteAllText(ConfigFilePath, json);
+            Log.Information("配置文件保存成功");
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "保存配置文件失败");
+        }
+    }
+
+    /// <summary>
+    /// 更新配置
+    /// </summary>
+    public static void UpdateConfig(SystemConfig config)
+    {
+        _config = config;
+        SaveConfig();
+    }
+}

+ 58 - 0
src/YZWater.Core/Services/DatabaseService.cs

@@ -0,0 +1,58 @@
+using FreeSql;
+using Serilog;
+using YZWater.Core.Models;
+
+namespace YZWater.Core.Services;
+
+/// <summary>
+/// 数据库服务 - 使用 FreeSql + SQLite
+/// </summary>
+public static class DatabaseService
+{
+    private static IFreeSql? _db;
+
+    /// <summary>
+    /// 数据库实例
+    /// </summary>
+    public static IFreeSql Db => _db ?? throw new InvalidOperationException("数据库未初始化,请先调用 Initialize()");
+
+    /// <summary>
+    /// 初始化数据库
+    /// </summary>
+    public static void Initialize()
+    {
+        // 配置 Serilog
+        Log.Logger = new LoggerConfiguration()
+            .MinimumLevel.Debug()
+            .WriteTo.Console()
+            .WriteTo.File("Logs/log-.txt", rollingInterval: RollingInterval.Day)
+            .CreateLogger();
+
+        // 初始化 FreeSql
+        _db = new FreeSqlBuilder()
+            .UseConnectionString(DataType.Sqlite, "Data Source=yzwater.db")
+            .UseAutoSyncStructure(true)
+            .UseMonitorCommand(cmd =>
+            {
+                Log.Debug("SQL: {Sql}", cmd.CommandText);
+            })
+            .Build();
+
+        // 确保数据库已创建
+        _db.CodeFirst.SyncStructure(typeof(Person));
+        _db.CodeFirst.SyncStructure(typeof(AlarmRecord));
+        _db.CodeFirst.SyncStructure(typeof(FlowRecord));
+        _db.CodeFirst.SyncStructure(typeof(SystemConfig));
+
+        Log.Information("数据库初始化完成");
+    }
+
+    /// <summary>
+    /// 释放资源
+    /// </summary>
+    public static void Dispose()
+    {
+        _db?.Dispose();
+        Log.CloseAndFlush();
+    }
+}

+ 193 - 0
src/YZWater.Core/Services/PlcService.cs

@@ -0,0 +1,193 @@
+using HslCommunication;
+using HslCommunication.Profinet.Siemens;
+using Serilog;
+
+namespace YZWater.Core.Services;
+
+/// <summary>
+/// PLC 通信服务 - 使用 HslCommunication
+/// </summary>
+public static class PlcService
+{
+    private static SiemensS7Net? _plc;
+    private static bool _isConnected;
+
+    /// <summary>
+    /// PLC 连接状态
+    /// </summary>
+    public static bool IsConnected => _isConnected;
+
+    /// <summary>
+    /// PLC 实例
+    /// </summary>
+    public static SiemensS7Net Plc => _plc ?? throw new InvalidOperationException("PLC 未初始化,请先调用 Initialize()");
+
+    /// <summary>
+    /// 初始化 PLC 连接
+    /// </summary>
+    public static void Initialize()
+    {
+        try
+        {
+            // 从配置读取 PLC IP,默认为 192.168.0.150
+            var config = ConfigService.GetConfig();
+            var plcIp = config?.PlcIp ?? "192.168.0.150";
+
+            _plc = new SiemensS7Net(SiemensPLCS.S200Smart, plcIp)
+            {
+                ConnectTimeOut = 3000,
+                ReceiveTimeOut = 3000
+            };
+
+            Log.Information("PLC 服务初始化完成,IP: {Ip}", plcIp);
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "PLC 服务初始化失败");
+        }
+    }
+
+    /// <summary>
+    /// 连接 PLC
+    /// </summary>
+    public static async Task<bool> ConnectAsync()
+    {
+        if (_plc == null)
+        {
+            Log.Warning("PLC 未初始化");
+            return false;
+        }
+
+        try
+        {
+            var result = await _plc.ConnectServerAsync();
+            _isConnected = result.IsSuccess;
+            if (_isConnected)
+            {
+                Log.Information("PLC 连接成功");
+            }
+            else
+            {
+                Log.Warning("PLC 连接失败: {Message}", result.Message);
+            }
+            return _isConnected;
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "PLC 连接异常");
+            _isConnected = false;
+            return false;
+        }
+    }
+
+    /// <summary>
+    /// 断开 PLC 连接
+    /// </summary>
+    public static void Disconnect()
+    {
+        _plc?.ConnectClose();
+        _isConnected = false;
+        Log.Information("PLC 已断开连接");
+    }
+
+    /// <summary>
+    /// 读取浮点数
+    /// </summary>
+    public static async Task<float> ReadFloatAsync(string address)
+    {
+        if (_plc == null || !_isConnected)
+        {
+            Log.Warning("PLC 未连接,无法读取数据");
+            return 0f;
+        }
+
+        try
+        {
+            var result = await _plc.ReadFloatAsync(address);
+            return result.IsSuccess ? result.Content : 0f;
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "读取 PLC 数据失败,地址: {Address}", address);
+            return 0f;
+        }
+    }
+
+    /// <summary>
+    /// 写入浮点数
+    /// </summary>
+    public static async Task<bool> WriteFloatAsync(string address, float value)
+    {
+        if (_plc == null || !_isConnected)
+        {
+            Log.Warning("PLC 未连接,无法写入数据");
+            return false;
+        }
+
+        try
+        {
+            var result = await _plc.WriteAsync(address, value);
+            return result.IsSuccess;
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "写入 PLC 数据失败,地址: {Address}, 值: {Value}", address, value);
+            return false;
+        }
+    }
+
+    /// <summary>
+    /// 读取布尔值
+    /// </summary>
+    public static async Task<bool> ReadBoolAsync(string address)
+    {
+        if (_plc == null || !_isConnected)
+        {
+            Log.Warning("PLC 未连接,无法读取数据");
+            return false;
+        }
+
+        try
+        {
+            var result = await _plc.ReadBoolAsync(address);
+            return result.IsSuccess && result.Content;
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "读取 PLC 数据失败,地址: {Address}", address);
+            return false;
+        }
+    }
+
+    /// <summary>
+    /// 写入布尔值
+    /// </summary>
+    public static async Task<bool> WriteBoolAsync(string address, bool value)
+    {
+        if (_plc == null || !_isConnected)
+        {
+            Log.Warning("PLC 未连接,无法写入数据");
+            return false;
+        }
+
+        try
+        {
+            var result = await _plc.WriteAsync(address, value);
+            return result.IsSuccess;
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "写入 PLC 数据失败,地址: {Address}, 值: {Value}", address, value);
+            return false;
+        }
+    }
+
+    /// <summary>
+    /// 释放资源
+    /// </summary>
+    public static void Dispose()
+    {
+        Disconnect();
+        _plc = null;
+    }
+}

+ 65 - 0
src/YZWater.Core/Utils/Nlogger.cs

@@ -0,0 +1,65 @@
+using Serilog;
+
+namespace YZWater.Core.Utils;
+
+/// <summary>
+/// 日志工具类 - 封装 Serilog
+/// </summary>
+public static class Nlogger
+{
+    /// <summary>
+    /// 记录调试信息
+    /// </summary>
+    public static void Debug(string message, params object[] args)
+    {
+        Log.Debug(message, args);
+    }
+
+    /// <summary>
+    /// 记录普通信息
+    /// </summary>
+    public static void Info(string message, params object[] args)
+    {
+        Log.Information(message, args);
+    }
+
+    /// <summary>
+    /// 记录警告信息
+    /// </summary>
+    public static void Warn(string message, params object[] args)
+    {
+        Log.Warning(message, args);
+    }
+
+    /// <summary>
+    /// 记录错误信息
+    /// </summary>
+    public static void Error(string message, params object[] args)
+    {
+        Log.Error(message, args);
+    }
+
+    /// <summary>
+    /// 记录错误信息(带异常)
+    /// </summary>
+    public static void Error(Exception ex, string message, params object[] args)
+    {
+        Log.Error(ex, message, args);
+    }
+
+    /// <summary>
+    /// 记录致命错误
+    /// </summary>
+    public static void Fatal(string message, params object[] args)
+    {
+        Log.Fatal(message, args);
+    }
+
+    /// <summary>
+    /// 记录致命错误(带异常)
+    /// </summary>
+    public static void Fatal(Exception ex, string message, params object[] args)
+    {
+        Log.Fatal(ex, message, args);
+    }
+}

+ 127 - 0
src/YZWater.Core/Utils/ResolutionManager.cs

@@ -0,0 +1,127 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System;
+
+namespace YZWater.Core.Utils;
+
+/// <summary>
+/// 分辨率管理器 - 提供解析度自适应支持
+/// </summary>
+public partial class ResolutionManager : ObservableObject
+{
+    private static ResolutionManager? _instance;
+
+    /// <summary>
+    /// 单例实例
+    /// </summary>
+    public static ResolutionManager Instance => _instance ??= new ResolutionManager();
+
+    /// <summary>
+    /// 设计基准宽度
+    /// </summary>
+    public const double DesignWidth = 1024;
+
+    /// <summary>
+    /// 设计基准高度
+    /// </summary>
+    public const double DesignHeight = 768;
+
+    /// <summary>
+    /// 最小缩放比例
+    /// </summary>
+    public const double MinScale = 0.5;
+
+    /// <summary>
+    /// 最大缩放比例
+    /// </summary>
+    public const double MaxScale = 2.0;
+
+    [ObservableProperty]
+    private double _currentWidth = DesignWidth;
+
+    [ObservableProperty]
+    private double _currentHeight = DesignHeight;
+
+    [ObservableProperty]
+    private double _scaleX = 1.0;
+
+    [ObservableProperty]
+    private double _scaleY = 1.0;
+
+    [ObservableProperty]
+    private double _uniformScale = 1.0;
+
+    [ObservableProperty]
+    private double _fontSizeScale = 1.0;
+
+    [ObservableProperty]
+    private double _marginScale = 1.0;
+
+    /// <summary>
+    /// 更新分辨率
+    /// </summary>
+    public void UpdateResolution(double width, double height)
+    {
+        CurrentWidth = width;
+        CurrentHeight = height;
+
+        // 计算缩放比例
+        ScaleX = width / DesignWidth;
+        ScaleY = height / DesignHeight;
+
+        // 使用统一缩放比例(取较小值,保证内容完全显示)
+        UniformScale = Math.Min(ScaleX, ScaleY);
+        UniformScale = Math.Max(MinScale, Math.Min(MaxScale, UniformScale));
+
+        // 字体缩放比例(稍微保守一点,避免文字过大)
+        FontSizeScale = Math.Max(0.8, Math.Min(1.3, UniformScale));
+
+        // 边距缩放比例
+        MarginScale = Math.Max(0.7, Math.Min(1.5, UniformScale));
+    }
+
+    /// <summary>
+    /// 根据基准尺寸计算实际尺寸
+    /// </summary>
+    public double Scale(double baseSize)
+    {
+        return baseSize * UniformScale;
+    }
+
+    /// <summary>
+    /// 计算字体大小
+    /// </summary>
+    public double ScaleFontSize(double baseFontSize)
+    {
+        return baseFontSize * FontSizeScale;
+    }
+
+    /// <summary>
+    /// 计算边距
+    /// </summary>
+    public double ScaleMargin(double baseMargin)
+    {
+        return baseMargin * MarginScale;
+    }
+
+    /// <summary>
+    /// 获取缩放后的尺寸
+    /// </summary>
+    public (double Width, double Height) GetScaledSize(double baseWidth, double baseHeight)
+    {
+        return (baseWidth * UniformScale, baseHeight * UniformScale);
+    }
+
+    /// <summary>
+    /// 获取适配后的窗口尺寸
+    /// </summary>
+    public (double Width, double Height) GetFittedWindowSize(double screenWidth, double screenHeight, double padding = 50)
+    {
+        var availableWidth = screenWidth - padding * 2;
+        var availableHeight = screenHeight - padding * 2;
+
+        var scale = Math.Min(availableWidth / DesignWidth, availableHeight / DesignHeight);
+        scale = Math.Max(MinScale, Math.Min(MaxScale, scale));
+
+        return (DesignWidth * scale, DesignHeight * scale);
+    }
+}

+ 90 - 0
src/YZWater.Core/Utils/WinSize.cs

@@ -0,0 +1,90 @@
+namespace YZWater.Core.Utils;
+
+/// <summary>
+/// 窗口尺寸工具类
+/// </summary>
+public static class WinSize
+{
+    /// <summary>
+    /// 基准宽度
+    /// </summary>
+    public const double BaseWidth = 1024;
+
+    /// <summary>
+    /// 基准高度
+    /// </summary>
+    public const double BaseHeight = 768;
+
+    /// <summary>
+    /// 计算缩放比例
+    /// </summary>
+    public static double GetScaleFactor(double actualWidth, double actualHeight)
+    {
+        var scaleX = actualWidth / BaseWidth;
+        var scaleY = actualHeight / BaseHeight;
+        return Math.Min(scaleX, scaleY);
+    }
+
+    /// <summary>
+    /// 根据基准尺寸计算实际尺寸
+    /// </summary>
+    public static double CalculateSize(double baseSize, double scaleFactor)
+    {
+        return baseSize * scaleFactor;
+    }
+
+    /// <summary>
+    /// 计算字体大小
+    /// </summary>
+    public static double CalculateFontSize(double baseFontSize, double scaleFactor)
+    {
+        var fontSize = baseFontSize * scaleFactor;
+        return Math.Max(12, Math.Min(24, fontSize)); // 限制在 12-24 之间
+    }
+
+    /// <summary>
+    /// 计算边距
+    /// </summary>
+    public static Thickness CalculateMargin(double baseMargin, double scaleFactor)
+    {
+        var margin = baseMargin * scaleFactor;
+        return new Thickness(margin);
+    }
+
+    /// <summary>
+    /// 计算边距(分别指定四个方向)
+    /// </summary>
+    public static Thickness CalculateMargin(double left, double top, double right, double bottom, double scaleFactor)
+    {
+        return new Thickness(
+            left * scaleFactor,
+            top * scaleFactor,
+            right * scaleFactor,
+            bottom * scaleFactor
+        );
+    }
+}
+
+/// <summary>
+/// Thickness 结构体(简化版)
+/// </summary>
+public struct Thickness
+{
+    public double Left { get; }
+    public double Top { get; }
+    public double Right { get; }
+    public double Bottom { get; }
+
+    public Thickness(double uniformLength)
+    {
+        Left = Top = Right = Bottom = uniformLength;
+    }
+
+    public Thickness(double left, double top, double right, double bottom)
+    {
+        Left = left;
+        Top = top;
+        Right = right;
+        Bottom = bottom;
+    }
+}

+ 134 - 0
src/YZWater.Core/ViewModels/MainViewModel.cs

@@ -0,0 +1,134 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Serilog;
+using YZWater.Core.Services;
+
+namespace YZWater.Core.ViewModels;
+
+/// <summary>
+/// 主窗口 ViewModel
+/// </summary>
+public partial class MainViewModel : ObservableObject
+{
+    private readonly ViewAViewModel _viewAViewModel;
+    private readonly ViewBViewModel _viewBViewModel;
+    private readonly ViewCViewModel _viewCViewModel;
+    private readonly ViewDViewModel _viewDViewModel;
+    private readonly ViewEViewModel _viewEViewModel;
+
+    [ObservableProperty]
+    private ObservableObject _currentView;
+
+    [ObservableProperty]
+    private string _title = "污水处理监控系统";
+
+    [ObservableProperty]
+    private string _currentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+    [ObservableProperty]
+    private bool _isPlcConnected;
+
+    [ObservableProperty]
+    private int _selectedTabIndex;
+
+    public MainViewModel()
+    {
+        _viewAViewModel = new ViewAViewModel();
+        _viewBViewModel = new ViewBViewModel();
+        _viewCViewModel = new ViewCViewModel();
+        _viewDViewModel = new ViewDViewModel();
+        _viewEViewModel = new ViewEViewModel();
+
+        _currentView = _viewAViewModel;
+
+        // 启动定时器更新时间
+        StartTimer();
+    }
+
+    /// <summary>
+    /// 切换到视图 A (主工艺)
+    /// </summary>
+    [RelayCommand]
+    private void ShowViewA()
+    {
+        CurrentView = _viewAViewModel;
+        SelectedTabIndex = 0;
+        Log.Debug("切换到主工艺视图");
+    }
+
+    /// <summary>
+    /// 切换到视图 B (参数设置)
+    /// </summary>
+    [RelayCommand]
+    private void ShowViewB()
+    {
+        CurrentView = _viewBViewModel;
+        SelectedTabIndex = 1;
+        Log.Debug("切换到参数设置视图");
+    }
+
+    /// <summary>
+    /// 切换到视图 C (流量记录)
+    /// </summary>
+    [RelayCommand]
+    private void ShowViewC()
+    {
+        CurrentView = _viewCViewModel;
+        SelectedTabIndex = 2;
+        Log.Debug("切换到流量记录视图");
+    }
+
+    /// <summary>
+    /// 切换到视图 D (报警记录)
+    /// </summary>
+    [RelayCommand]
+    private void ShowViewD()
+    {
+        CurrentView = _viewDViewModel;
+        SelectedTabIndex = 3;
+        Log.Debug("切换到报警记录视图");
+    }
+
+    /// <summary>
+    /// 切换到视图 E (关于)
+    /// </summary>
+    [RelayCommand]
+    private void ShowViewE()
+    {
+        CurrentView = _viewEViewModel;
+        SelectedTabIndex = 4;
+        Log.Debug("切换到关于视图");
+    }
+
+    /// <summary>
+    /// 连接 PLC
+    /// </summary>
+    [RelayCommand]
+    private async Task ConnectPlcAsync()
+    {
+        IsPlcConnected = await PlcService.ConnectAsync();
+    }
+
+    /// <summary>
+    /// 断开 PLC
+    /// </summary>
+    [RelayCommand]
+    private void DisconnectPlc()
+    {
+        PlcService.Disconnect();
+        IsPlcConnected = false;
+    }
+
+    /// <summary>
+    /// 启动定时器
+    /// </summary>
+    private void StartTimer()
+    {
+        var timer = new System.Timers.Timer(1000);
+        timer.Elapsed += (s, e) =>
+        {
+            CurrentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+        };
+        timer.Start();
+    }
+}

+ 416 - 0
src/YZWater.Core/ViewModels/ViewAViewModel.cs

@@ -0,0 +1,416 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Serilog;
+using YZWater.Core.Models;
+using YZWater.Core.Services;
+
+namespace YZWater.Core.ViewModels;
+
+/// <summary>
+/// 主工艺视图 ViewModel
+/// </summary>
+public partial class ViewAViewModel : ObservableObject
+{
+    // 液位数据
+    [ObservableProperty]
+    private float _tank1Level = 50f;
+
+    [ObservableProperty]
+    private float _tank2Level = 60f;
+
+    [ObservableProperty]
+    private float _tank3Level = 70f;
+
+    [ObservableProperty]
+    private float _tank4Level = 45f;
+
+    // 流量数据
+    [ObservableProperty]
+    private float _inflowRate = 25.5f;
+
+    [ObservableProperty]
+    private float _outflowRate = 22.3f;
+
+    // 泵状态
+    [ObservableProperty]
+    private bool _pump1Running;
+
+    [ObservableProperty]
+    private bool _pump2Running;
+
+    [ObservableProperty]
+    private bool _pump3Running;
+
+    [ObservableProperty]
+    private bool _pump4Running;
+
+    [ObservableProperty]
+    private bool _pump5Running;
+
+    // 风扇状态
+    [ObservableProperty]
+    private bool _fan1Running;
+
+    [ObservableProperty]
+    private bool _fan2Running;
+
+    // 阀门状态
+    [ObservableProperty]
+    private ValveStatus _valve1Status = ValveStatus.Middle;
+
+    [ObservableProperty]
+    private ValveStatus _valve2Status = ValveStatus.Middle;
+
+    [ObservableProperty]
+    private ValveStatus _valve3Status = ValveStatus.Middle;
+
+    [ObservableProperty]
+    private ValveStatus _valve4Status = ValveStatus.Middle;
+
+    [ObservableProperty]
+    private ValveStatus _valve5Status = ValveStatus.Middle;
+
+    // 模式状态
+    [ObservableProperty]
+    private ValveStatus _modeStatus = ValveStatus.Middle;
+
+    // 流动状态
+    [ObservableProperty]
+    private bool _isInflowRunning = true;
+
+    [ObservableProperty]
+    private bool _isOutflowRunning = true;
+
+    // 设备状态颜色
+    [ObservableProperty]
+    private string _pump1StatusColor = "#4CAF50";
+
+    [ObservableProperty]
+    private string _pump2StatusColor = "#4CAF50";
+
+    [ObservableProperty]
+    private string _pump3StatusColor = "#4CAF50";
+
+    [ObservableProperty]
+    private string _pump4StatusColor = "#4CAF50";
+
+    [ObservableProperty]
+    private string _pump5StatusColor = "#4CAF50";
+
+    [ObservableProperty]
+    private string _fan1StatusColor = "#4CAF50";
+
+    [ObservableProperty]
+    private string _fan2StatusColor = "#4CAF50";
+
+    // 报警状态
+    [ObservableProperty]
+    private bool _hasAlarm;
+
+    [ObservableProperty]
+    private string _alarmMessage = string.Empty;
+
+    // 连接状态
+    [ObservableProperty]
+    private bool _isPlcConnected;
+
+    public ViewAViewModel()
+    {
+        StartDataRefresh();
+    }
+
+    /// <summary>
+    /// 启动数据刷新
+    /// </summary>
+    private void StartDataRefresh()
+    {
+        var timer = new System.Timers.Timer(1000);
+        timer.Elapsed += async (s, e) =>
+        {
+            await RefreshDataAsync();
+        };
+        timer.Start();
+    }
+
+    /// <summary>
+    /// 刷新 PLC 数据
+    /// </summary>
+    [RelayCommand]
+    private async Task RefreshDataAsync()
+    {
+        if (!PlcService.IsConnected) return;
+
+        try
+        {
+            // 读取液位数据
+            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");
+            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";
+    }
+
+    /// <summary>
+    /// 检查报警条件
+    /// </summary>
+    private void CheckAlarms()
+    {
+        var config = ConfigService.GetConfig();
+        if (config == null) return;
+
+        HasAlarm = false;
+        AlarmMessage = string.Empty;
+
+        if (Tank1Level > config.LevelHighAlarm)
+        {
+            HasAlarm = true;
+            AlarmMessage = "1号水池液位过高";
+        }
+        else if (Tank1Level < config.LevelLowAlarm)
+        {
+            HasAlarm = true;
+            AlarmMessage = "1号水池液位过低";
+        }
+
+        if (InflowRate > config.FlowHighAlarm)
+        {
+            HasAlarm = true;
+            AlarmMessage = "进水流量过高";
+        }
+    }
+
+    /// <summary>
+    /// 连接 PLC
+    /// </summary>
+    [RelayCommand]
+    private async Task ConnectPlcAsync()
+    {
+        IsPlcConnected = await PlcService.ConnectAsync();
+    }
+
+    /// <summary>
+    /// 断开 PLC
+    /// </summary>
+    [RelayCommand]
+    private void DisconnectPlc()
+    {
+        PlcService.Disconnect();
+        IsPlcConnected = false;
+    }
+
+    /// <summary>
+    /// 控制泵
+    /// </summary>
+    [RelayCommand]
+    private async Task TogglePumpAsync(string pumpId)
+    {
+        if (!PlcService.IsConnected)
+        {
+            Log.Warning("PLC 未连接,无法控制泵");
+            return;
+        }
+
+        var address = pumpId switch
+        {
+            "P001" => "Q0.0",
+            "P002" => "Q0.1",
+            "P003" => "Q0.2",
+            "P004" => "Q0.3",
+            "P005" => "Q0.4",
+            _ => throw new ArgumentException($"未知的泵 ID: {pumpId}")
+        };
+
+        var currentState = pumpId switch
+        {
+            "P001" => Pump1Running,
+            "P002" => Pump2Running,
+            "P003" => Pump3Running,
+            "P004" => Pump4Running,
+            "P005" => Pump5Running,
+            _ => false
+        };
+
+        var success = await PlcService.WriteBoolAsync(address, !currentState);
+        if (success)
+        {
+            Log.Information("泵 {PumpId} 状态已切换", pumpId);
+        }
+    }
+
+    /// <summary>
+    /// 控制风扇
+    /// </summary>
+    [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,
+            "F002" => Fan2Running,
+            _ => false
+        };
+
+        var success = await PlcService.WriteBoolAsync(address, !currentState);
+        if (success)
+        {
+            Log.Information("风扇 {FanId} 状态已切换", fanId);
+        }
+    }
+
+    /// <summary>
+    /// 控制阀门
+    /// </summary>
+    [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,
+            ValveStatus.Middle => ValveStatus.Open,
+            ValveStatus.Open => ValveStatus.Closed,
+            _ => ValveStatus.Middle
+        };
+
+        var success = await PlcService.WriteBoolAsync(address, newStatus == ValveStatus.Open);
+        if (success)
+        {
+            switch (valveId)
+            {
+                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);
+        }
+    }
+
+    /// <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)
+        {
+            ModeStatus = newStatus;
+            Log.Information("模式已切换为: {Status}", newStatus);
+        }
+    }
+}
+
+/// <summary>
+/// 阀门状态枚举
+/// </summary>
+public enum ValveStatus
+{
+    /// <summary>
+    /// 关闭
+    /// </summary>
+    Closed,
+
+    /// <summary>
+    /// 中间位置
+    /// </summary>
+    Middle,
+
+    /// <summary>
+    /// 打开
+    /// </summary>
+    Open
+}

+ 140 - 0
src/YZWater.Core/ViewModels/ViewBViewModel.cs

@@ -0,0 +1,140 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Serilog;
+using YZWater.Core.Models;
+using YZWater.Core.Services;
+
+namespace YZWater.Core.ViewModels;
+
+/// <summary>
+/// 参数设置视图 ViewModel
+/// </summary>
+public partial class ViewBViewModel : ObservableObject
+{
+    [ObservableProperty]
+    private string _plcIp = "192.168.0.150";
+
+    [ObservableProperty]
+    private int _plcPort = 5000;
+
+    [ObservableProperty]
+    private bool _autoConnect = true;
+
+    [ObservableProperty]
+    private float _levelHighAlarm = 80f;
+
+    [ObservableProperty]
+    private float _levelLowAlarm = 20f;
+
+    [ObservableProperty]
+    private float _flowHighAlarm = 100f;
+
+    [ObservableProperty]
+    private float _pumpFrequency = 50f;
+
+    [ObservableProperty]
+    private string _connectionStatus = "未连接";
+
+    [ObservableProperty]
+    private bool _isConnecting;
+
+    public ViewBViewModel()
+    {
+        LoadConfig();
+    }
+
+    /// <summary>
+    /// 加载配置
+    /// </summary>
+    [RelayCommand]
+    private void LoadConfig()
+    {
+        var config = ConfigService.GetConfig();
+        if (config != null)
+        {
+            PlcIp = config.PlcIp;
+            PlcPort = config.PlcPort;
+            AutoConnect = config.AutoConnect;
+            LevelHighAlarm = config.LevelHighAlarm;
+            LevelLowAlarm = config.LevelLowAlarm;
+            FlowHighAlarm = config.FlowHighAlarm;
+            PumpFrequency = config.PumpFrequency;
+        }
+        Log.Debug("配置已加载");
+    }
+
+    /// <summary>
+    /// 保存配置
+    /// </summary>
+    [RelayCommand]
+    private void SaveConfig()
+    {
+        var config = new SystemConfig
+        {
+            PlcIp = PlcIp,
+            PlcPort = PlcPort,
+            AutoConnect = AutoConnect,
+            LevelHighAlarm = LevelHighAlarm,
+            LevelLowAlarm = LevelLowAlarm,
+            FlowHighAlarm = FlowHighAlarm,
+            PumpFrequency = PumpFrequency
+        };
+
+        ConfigService.UpdateConfig(config);
+        Log.Information("配置已保存");
+    }
+
+    /// <summary>
+    /// 测试连接
+    /// </summary>
+    [RelayCommand]
+    private async Task TestConnectionAsync()
+    {
+        IsConnecting = true;
+        ConnectionStatus = "连接中...";
+
+        try
+        {
+            // 更新 PLC IP
+            PlcService.Initialize();
+
+            var success = await PlcService.ConnectAsync();
+            ConnectionStatus = success ? "连接成功" : "连接失败";
+
+            if (success)
+            {
+                Log.Information("PLC 连接测试成功");
+            }
+            else
+            {
+                Log.Warning("PLC 连接测试失败");
+            }
+        }
+        catch (Exception ex)
+        {
+            ConnectionStatus = "连接异常";
+            Log.Error(ex, "PLC 连接测试异常");
+        }
+        finally
+        {
+            IsConnecting = false;
+        }
+    }
+
+    /// <summary>
+    /// 恢复默认设置
+    /// </summary>
+    [RelayCommand]
+    private void ResetToDefault()
+    {
+        PlcIp = "192.168.0.150";
+        PlcPort = 5000;
+        AutoConnect = true;
+        LevelHighAlarm = 80f;
+        LevelLowAlarm = 20f;
+        FlowHighAlarm = 100f;
+        PumpFrequency = 50f;
+
+        Log.Information("已恢复默认设置");
+    }
+}

+ 186 - 0
src/YZWater.Core/ViewModels/ViewCViewModel.cs

@@ -0,0 +1,186 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using LiveChartsCore;
+using LiveChartsCore.Defaults;
+using LiveChartsCore.SkiaSharpView;
+using LiveChartsCore.SkiaSharpView.Painting;
+using SkiaSharp;
+using Serilog;
+using YZWater.Core.Models;
+using YZWater.Core.Services;
+
+namespace YZWater.Core.ViewModels;
+
+/// <summary>
+/// 流量记录视图 ViewModel
+/// </summary>
+public partial class ViewCViewModel : ObservableObject
+{
+    [ObservableProperty]
+    private ISeries[] _inflowSeries;
+
+    [ObservableProperty]
+    private ISeries[] _outflowSeries;
+
+    [ObservableProperty]
+    private Axis[] _xAxes;
+
+    [ObservableProperty]
+    private Axis[] _yAxes;
+
+    [ObservableProperty]
+    private DateTime _startDate = DateTime.Now.AddDays(-7);
+
+    [ObservableProperty]
+    private DateTime _endDate = DateTime.Now;
+
+    [ObservableProperty]
+    private List<FlowRecord> _flowRecords = new();
+
+    public ViewCViewModel()
+    {
+        InitializeChart();
+        _ = LoadFlowRecordsAsync();
+    }
+
+    /// <summary>
+    /// 初始化图表
+    /// </summary>
+    private void InitializeChart()
+    {
+        InflowSeries = new ISeries[]
+        {
+            new ColumnSeries<DateTimePoint>
+            {
+                Name = "进水流量",
+                Values = new List<DateTimePoint>(),
+                Fill = new SolidColorPaint(SKColors.CornflowerBlue),
+                Stroke = null
+            }
+        };
+
+        OutflowSeries = new ISeries[]
+        {
+            new ColumnSeries<DateTimePoint>
+            {
+                Name = "出水流量",
+                Values = new List<DateTimePoint>(),
+                Fill = new SolidColorPaint(SKColors.MediumSeaGreen),
+                Stroke = null
+            }
+        };
+
+        XAxes = new Axis[]
+        {
+            new Axis
+            {
+                Labeler = value => new DateTime((long)value).ToString("MM-dd"),
+                UnitWidth = TimeSpan.FromDays(1).Ticks,
+                LabelsRotation = 45
+            }
+        };
+
+        YAxes = new Axis[]
+        {
+            new Axis
+            {
+                Name = "流量 (m³/h)",
+                MinLimit = 0
+            }
+        };
+    }
+
+    /// <summary>
+    /// 加载流量记录
+    /// </summary>
+    [RelayCommand]
+    private async Task LoadFlowRecordsAsync()
+    {
+        try
+        {
+            var records = await DatabaseService.Db.Select<FlowRecord>()
+                .Where(r => r.RecordTime >= StartDate && r.RecordTime <= EndDate)
+                .OrderByDescending(r => r.RecordTime)
+                .ToListAsync();
+
+            FlowRecords = records;
+            UpdateChart();
+            Log.Debug("加载了 {Count} 条流量记录", records.Count);
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "加载流量记录失败");
+        }
+    }
+
+    /// <summary>
+    /// 更新图表数据
+    /// </summary>
+    private void UpdateChart()
+    {
+        var inflowData = FlowRecords
+            .GroupBy(r => r.RecordTime.Date)
+            .Select(g => new DateTimePoint(g.Key, g.Average(r => r.InflowRate)))
+            .OrderBy(p => p.DateTime)
+            .ToList();
+
+        var outflowData = FlowRecords
+            .GroupBy(r => r.RecordTime.Date)
+            .Select(g => new DateTimePoint(g.Key, g.Average(r => r.OutflowRate)))
+            .OrderBy(p => p.DateTime)
+            .ToList();
+
+        InflowSeries[0].Values = inflowData;
+        OutflowSeries[0].Values = outflowData;
+    }
+
+    /// <summary>
+    /// 导出数据
+    /// </summary>
+    [RelayCommand]
+    private async Task ExportDataAsync()
+    {
+        try
+        {
+            var filePath = $"FlowRecords_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
+            var lines = new List<string>
+            {
+                "时间,进水流量(m³/h),出水流量(m³/h),累计进水量(m³),累计出水量(m³),备注"
+            };
+
+            foreach (var record in FlowRecords)
+            {
+                lines.Add($"{record.RecordTime:yyyy-MM-dd HH:mm:ss},{record.InflowRate},{record.OutflowRate},{record.TotalInflow},{record.TotalOutflow},{record.Remark}");
+            }
+
+            await File.WriteAllLinesAsync(filePath, lines);
+            Log.Information("流量记录已导出到: {FilePath}", filePath);
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "导出流量记录失败");
+        }
+    }
+
+    /// <summary>
+    /// 清除旧数据
+    /// </summary>
+    [RelayCommand]
+    private async Task ClearOldDataAsync()
+    {
+        try
+        {
+            var cutoffDate = DateTime.Now.AddDays(-30);
+            await DatabaseService.Db.Delete<FlowRecord>()
+                .Where(r => r.RecordTime < cutoffDate)
+                .ExecuteAffrowsAsync();
+
+            await LoadFlowRecordsAsync();
+            Log.Information("已清除 30 天前的流量记录");
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "清除旧数据失败");
+        }
+    }
+}

+ 171 - 0
src/YZWater.Core/ViewModels/ViewDViewModel.cs

@@ -0,0 +1,171 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Serilog;
+using YZWater.Core.Models;
+using YZWater.Core.Services;
+
+namespace YZWater.Core.ViewModels;
+
+/// <summary>
+/// 报警记录视图 ViewModel
+/// </summary>
+public partial class ViewDViewModel : ObservableObject
+{
+    [ObservableProperty]
+    private List<AlarmRecord> _alarmRecords = new();
+
+    [ObservableProperty]
+    private AlarmRecord? _selectedRecord;
+
+    [ObservableProperty]
+    private int _unconfirmedCount;
+
+    [ObservableProperty]
+    private DateTime _startDate = DateTime.Now.AddDays(-7);
+
+    [ObservableProperty]
+    private DateTime _endDate = DateTime.Now;
+
+    [ObservableProperty]
+    private string _filterType = "全部";
+
+    public ViewDViewModel()
+    {
+        _ = LoadAlarmRecordsAsync();
+    }
+
+    /// <summary>
+    /// 加载报警记录
+    /// </summary>
+    [RelayCommand]
+    private async Task LoadAlarmRecordsAsync()
+    {
+        try
+        {
+            var query = DatabaseService.Db.Select<AlarmRecord>()
+                .Where(r => r.AlarmTime >= StartDate && r.AlarmTime <= EndDate);
+
+            if (FilterType != "全部")
+            {
+                query = query.Where(r => r.AlarmType == FilterType);
+            }
+
+            var records = await query
+                .OrderByDescending(r => r.AlarmTime)
+                .ToListAsync();
+
+            AlarmRecords = records;
+            UnconfirmedCount = records.Count(r => !r.IsConfirmed);
+
+            Log.Debug("加载了 {Count} 条报警记录", records.Count);
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "加载报警记录失败");
+        }
+    }
+
+    /// <summary>
+    /// 确认报警
+    /// </summary>
+    [RelayCommand]
+    private async Task ConfirmAlarmAsync(AlarmRecord? record)
+    {
+        if (record == null) return;
+
+        try
+        {
+            record.IsConfirmed = true;
+            record.ConfirmedTime = DateTime.Now;
+            record.ConfirmedBy = "操作员"; // 实际应从登录用户获取
+
+            await DatabaseService.Db.Update<AlarmRecord>()
+                .Set(r => r.IsConfirmed, true)
+                .Set(r => r.ConfirmedTime, record.ConfirmedTime)
+                .Set(r => r.ConfirmedBy, record.ConfirmedBy)
+                .Where(r => r.Id == record.Id)
+                .ExecuteAffrowsAsync();
+
+            await LoadAlarmRecordsAsync();
+            Log.Information("报警 {Id} 已确认", record.Id);
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "确认报警失败");
+        }
+    }
+
+    /// <summary>
+    /// 批量确认所有报警
+    /// </summary>
+    [RelayCommand]
+    private async Task ConfirmAllAlarmsAsync()
+    {
+        try
+        {
+            await DatabaseService.Db.Update<AlarmRecord>()
+                .Set(r => r.IsConfirmed, true)
+                .Set(r => r.ConfirmedTime, DateTime.Now)
+                .Set(r => r.ConfirmedBy, "操作员")
+                .Where(r => !r.IsConfirmed)
+                .ExecuteAffrowsAsync();
+
+            await LoadAlarmRecordsAsync();
+            Log.Information("所有报警已确认");
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "批量确认报警失败");
+        }
+    }
+
+    /// <summary>
+    /// 导出报警记录
+    /// </summary>
+    [RelayCommand]
+    private async Task ExportAlarmsAsync()
+    {
+        try
+        {
+            var filePath = $"AlarmRecords_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
+            var lines = new List<string>
+            {
+                "时间,类型,内容,报警值,级别,是否已确认,确认时间,确认人"
+            };
+
+            foreach (var record in AlarmRecords)
+            {
+                lines.Add($"{record.AlarmTime:yyyy-MM-dd HH:mm:ss},{record.AlarmType},{record.AlarmMessage},{record.AlarmValue},{record.AlarmLevel},{(record.IsConfirmed ? "是" : "否")},{record.ConfirmedTime:yyyy-MM-dd HH:mm:ss},{record.ConfirmedBy}");
+            }
+
+            await File.WriteAllLinesAsync(filePath, lines);
+            Log.Information("报警记录已导出到: {FilePath}", filePath);
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "导出报警记录失败");
+        }
+    }
+
+    /// <summary>
+    /// 清除历史记录
+    /// </summary>
+    [RelayCommand]
+    private async Task ClearHistoryAsync()
+    {
+        try
+        {
+            var cutoffDate = DateTime.Now.AddDays(-90);
+            await DatabaseService.Db.Delete<AlarmRecord>()
+                .Where(r => r.AlarmTime < cutoffDate && r.IsConfirmed)
+                .ExecuteAffrowsAsync();
+
+            await LoadAlarmRecordsAsync();
+            Log.Information("已清除 90 天前的已确认报警记录");
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "清除历史记录失败");
+        }
+    }
+}

+ 78 - 0
src/YZWater.Core/ViewModels/ViewEViewModel.cs

@@ -0,0 +1,78 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using YZWater.Core.Services;
+
+namespace YZWater.Core.ViewModels;
+
+/// <summary>
+/// 关于视图 ViewModel
+/// </summary>
+public partial class ViewEViewModel : ObservableObject
+{
+    [ObservableProperty]
+    private string _systemName = "污水处理监控系统";
+
+    [ObservableProperty]
+    private string _version = "3.0.0";
+
+    [ObservableProperty]
+    private string _companyName = "扬州旭轩科技有限公司";
+
+    [ObservableProperty]
+    private string _contactPerson = "赵经理";
+
+    [ObservableProperty]
+    private string _contactPhone = "18115099090";
+
+    [ObservableProperty]
+    private string _copyright = $"© {DateTime.Now.Year} 扬州旭轩科技有限公司";
+
+    [ObservableProperty]
+    private string _description = "基于 Avalonia UI 的跨平台污水处理厂监控系统";
+
+    [ObservableProperty]
+    private string _techStack = "Avalonia UI 11 | .NET 8 | FreeSql | HslCommunication";
+
+    public ViewEViewModel()
+    {
+        LoadCompanyInfo();
+    }
+
+    /// <summary>
+    /// 加载公司信息
+    /// </summary>
+    private void LoadCompanyInfo()
+    {
+        var config = ConfigService.GetConfig();
+        if (config != null)
+        {
+            CompanyName = config.CompanyName;
+            ContactPerson = config.ContactPerson;
+            ContactPhone = config.ContactPhone;
+        }
+    }
+
+    /// <summary>
+    /// 打开官网
+    /// </summary>
+    [RelayCommand]
+    private void OpenWebsite()
+    {
+        // 实际实现中应该打开浏览器
+        // System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
+        // {
+        //     FileName = "https://www.example.com",
+        //     UseShellExecute = true
+        // });
+    }
+
+    /// <summary>
+    /// 检查更新
+    /// </summary>
+    [RelayCommand]
+    private async Task CheckUpdateAsync()
+    {
+        // 实际实现中应该检查服务器是否有新版本
+        await Task.Delay(1000); // 模拟网络请求
+    }
+}

+ 24 - 0
src/YZWater.Core/YZWater.Core.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <RootNamespace>YZWater.Core</RootNamespace>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
+    <PackageReference Include="FreeSql.Provider.Sqlite" Version="3.2.800" />
+    <PackageReference Include="FreeSql.Repository" Version="3.2.800" />
+    <PackageReference Include="HslCommunication" Version="12.0.1" />
+    <PackageReference Include="LiveChartsCore" Version="2.0.0-rc4.5" />
+    <PackageReference Include="LiveChartsCore.SkiaSharpView" Version="2.0.0-rc4.5" />
+    <PackageReference Include="NLog" Version="5.3.4" />
+    <PackageReference Include="Serilog" Version="4.0.2" />
+    <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
+    <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
+    <PackageReference Include="SkiaSharp" Version="2.88.9" />
+  </ItemGroup>
+
+</Project>