# 11 - Full Stack Demo - 完整功能演示 这是一个综合示例,集成了 Sharp 框架的几乎所有功能,演示如何构建一个完整的工业监控系统。 ## 🎯 功能列表 ### 核心功能 - ✅ 用户登录(数据库验证) - ✅ 多页面导航 - ✅ 主题切换(浅色/深色) - ✅ 多语言支持(中英文) - ✅ 表单验证 ### 数据管理 - ✅ 产品 CRUD 操作 - ✅ 数据分页显示 - ✅ 缓存优化 ### 工业IoT - ✅ **PLC 通信测试** - 可配置 IP 和参数进行测试 - ✅ **串口通信测试** - 可选择串口和配置参数 ### 其他 - ✅ 配置热重载 - ✅ 日志记录 - ✅ HTTP 客户端调用 ## 📦 使用的模块 - Sharp.Common - Sharp.Config - Sharp.Wpf - Sharp.Data - Sharp.Cache - Sharp.Log - Sharp.Plc.Siemens - Sharp.Ports - Sharp.NetClient ## 🚀 快速运行 ```bash cd examples/11-FullStackDemo dotnet run ``` ## 📝 项目结构 ``` 11-FullStackDemo/ ├── App.xaml.cs # 应用启动配置 ├── appsettings.json # 配置文件 ├── Models/ │ ├── User.cs # 用户实体 │ └── Product.cs # 产品实体 ├── ViewModels/ │ ├── LoginViewModel.cs # 登录页 │ ├── MainViewModel.cs # 主窗口 │ ├── HomeViewModel.cs # 首页(仪表板) │ ├── ProductsViewModel.cs # 产品管理 │ ├── PlcTestViewModel.cs # PLC 测试页 ⭐ │ ├── PortsTestViewModel.cs# 串口测试页 ⭐ │ └── SettingsViewModel.cs # 设置页 ├── Views/ │ ├── LoginWindow.xaml │ ├── MainWindow.xaml │ ├── HomeView.xaml │ ├── ProductsView.xaml │ ├── PlcTestView.xaml # PLC 测试界面 ⭐ │ ├── PortsTestView.xaml # 串口测试界面 ⭐ │ └── SettingsView.xaml └── Data/ └── AppDbContext.cs ``` ## 🔧 核心代码讲解 ### 1. App.xaml.cs - 完整启动配置 ```csharp protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var services = new ServiceCollection(); // 1. 配置管理 var config = ConfigBuilder.CreateDefault().Configuration; services.AddSingleton(config); // 2. 数据库 services.AddSharpDatabase(config); // 3. WPF 服务(包括导航、主题、国际化) services.AddSharpWpf(); services.AddLocalization(Resources.Strings.ResourceManager, new CultureInfo("zh-CN"), new CultureInfo("en-US")); // 4. 缓存 services.AddSharpCache(); // 5. 日志 services.AddLogging(builder => { builder.AddConsole(); builder.AddDebug(); }); // 6. PLC 服务(可选 - 如果配置中启用) var plcEnabled = config.GetValue("Plc:Enabled", false); if (plcEnabled) { services.AddSiemensPlc(config.GetSection("Plc:Siemens")); } // 7. 串口服务 services.AddSingleton(); // 8. HTTP 客户端 services.AddHttpClient(); // 9. 注册 ViewModels services.AddViewModel(); services.AddViewModel(); services.AddViewModel(); services.AddViewModel(); services.AddViewModel(); services.AddViewModel(); services.AddViewModel(); // 10. 注册 Windows services.AddTransient(); services.AddTransient(); _serviceProvider = services.BuildServiceProvider(); // 11. 初始化数据库 using (var scope = _serviceProvider.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureCreated(); SeedData(db); // 初始化测试数据 } // 12. 应用主题 var themeService = _serviceProvider.GetRequiredService(); var savedTheme = config.GetValue("App:Theme", "Light"); themeService.ApplyTheme(Enum.Parse(savedTheme)); // 13. 显示登录窗口 ShowLoginWindow(); } ``` ### 2. PlcTestViewModel.cs - PLC 测试页 ⭐ ```csharp public class PlcTestViewModel : ViewModelBase { private readonly IDialogService _dialogService; private IPlcService? _plcService; // 连接参数 private string _ipAddress = "192.168.1.100"; private int _rack = 0; private int _slot = 1; private bool _isConnected; public string IpAddress { get => _ipAddress; set => SetProperty(ref _ipAddress, value); } public int Rack { get => _rack; set => SetProperty(ref _rack, value); } public int Slot { get => _slot; set => SetProperty(ref _slot, value); } public bool IsConnected { get => _isConnected; set => SetProperty(ref _isConnected, value); } // 读写测试 private string _address = "DB1.DBD0"; private string _value = "0"; private string _readResult = ""; public string Address { get => _address; set => SetProperty(ref _address, value); } public string Value { get => _value; set => SetProperty(ref _value, value); } public string ReadResult { get => _readResult; set => SetProperty(ref _readResult, value); } public ICommand ConnectCommand { get; } public ICommand DisconnectCommand { get; } public ICommand ReadCommand { get; } public ICommand WriteCommand { get; } public PlcTestViewModel(IDialogService dialogService) { _dialogService = dialogService; ConnectCommand = new AsyncCommand(async (ct) => { try { IsBusy = true; // 动态创建 PLC 服务 var options = new SiemensPlcOptions { IpAddress = IpAddress, Rack = Rack, Slot = Slot, CpuType = CpuType.S71500 }; _plcService = new PlcMonitorService( new SiemensDriver(options), new SiemensOptimizer(), new BigEndianConverter(), Options.Create(new PlcMonitorOptions()), null); await _plcService.StartAsync(ct); IsConnected = true; _dialogService.ShowMessage("PLC 连接成功!", "成功"); } catch (Exception ex) { _dialogService.ShowMessage($"连接失败: {ex.Message}", "错误"); } finally { IsBusy = false; } }); DisconnectCommand = new AsyncCommand(async (ct) => { if (_plcService != null) { await _plcService.StopAsync(); IsConnected = false; _dialogService.ShowMessage("已断开连接", "提示"); } }); ReadCommand = new AsyncCommand(async (ct) => { if (_plcService == null) { _dialogService.ShowMessage("请先连接 PLC", "提示"); return; } try { IsBusy = true; var result = await _plcService.ReadAsync(Address); if (result.IsSuccess) { ReadResult = $"读取成功: {result.Value}"; } else { ReadResult = $"读取失败: {result.ErrorMessage}"; } } catch (Exception ex) { ReadResult = $"读取异常: {ex.Message}"; } finally { IsBusy = false; } }); WriteCommand = new AsyncCommand(async (ct) => { if (_plcService == null) { _dialogService.ShowMessage("请先连接 PLC", "提示"); return; } try { IsBusy = true; var intValue = int.Parse(Value); var result = await _plcService.WriteAsync(Address, intValue); if (result.IsSuccess) { _dialogService.ShowMessage("写入成功!", "成功"); } else { _dialogService.ShowMessage($"写入失败: {result.ErrorMessage}", "错误"); } } catch (Exception ex) { _dialogService.ShowMessage($"写入异常: {ex.Message}", "错误"); } finally { IsBusy = false; } }); } } ``` ### 3. PortsTestViewModel.cs - 串口测试页 ⭐ ```csharp public class PortsTestViewModel : ViewModelBase { private readonly ISerialPortService _serialPortService; private readonly IDialogService _dialogService; // 可用串口列表 public ObservableCollection AvailablePorts { get; } = new(); private string? _selectedPort; private int _baudRate = 9600; private bool _isConnected; public string? SelectedPort { get => _selectedPort; set => SetProperty(ref _selectedPort, value); } public int BaudRate { get => _baudRate; set => SetProperty(ref _baudRate, value); } public bool IsConnected { get => _isConnected; set => SetProperty(ref _isConnected, value); } // 发送/接收 private string _sendData = ""; private string _receiveData = ""; public string SendData { get => _sendData; set => SetProperty(ref _sendData, value); } public string ReceiveData { get => _receiveData; set => SetProperty(ref _receiveData, value); } public ICommand RefreshPortsCommand { get; } public ICommand ConnectCommand { get; } public ICommand DisconnectCommand { get; } public ICommand SendCommand { get; } public ICommand ClearCommand { get; } public PortsTestViewModel( ISerialPortService serialPortService, IDialogService dialogService) { _serialPortService = serialPortService; _dialogService = dialogService; // 监听数据接收 _serialPortService.DataReceived += (s, e) => { Application.Current.Dispatcher.Invoke(() => { ReceiveData += $"[{DateTime.Now:HH:mm:ss}] 接收: {BitConverter.ToString(e.Data)}\n"; }); }; RefreshPortsCommand = new RelayCommand(() => { AvailablePorts.Clear(); var ports = SerialPortDiscovery.GetAvailablePorts(); foreach (var port in ports) { AvailablePorts.Add(port.PortName); } if (AvailablePorts.Count > 0) { SelectedPort = AvailablePorts[0]; } }); ConnectCommand = new AsyncCommand(async (ct) => { if (string.IsNullOrEmpty(SelectedPort)) { _dialogService.ShowMessage("请选择串口", "提示"); return; } try { var options = new SerialPortOptions { PortName = SelectedPort, BaudRate = BaudRate }; var result = await _serialPortService.StartListeningAsync(options, autoReconnect: false); if (result.IsSuccess) { IsConnected = true; _dialogService.ShowMessage("串口连接成功!", "成功"); } else { _dialogService.ShowMessage($"连接失败: {result.ErrorMessage}", "错误"); } } catch (Exception ex) { _dialogService.ShowMessage($"连接异常: {ex.Message}", "错误"); } }); DisconnectCommand = new AsyncCommand(async (ct) => { await _serialPortService.StopListeningAsync(); IsConnected = false; _dialogService.ShowMessage("已断开连接", "提示"); }); SendCommand = new AsyncCommand(async (ct) => { if (!IsConnected) { _dialogService.ShowMessage("请先连接串口", "提示"); return; } try { // 将字符串转换为字节数组 var data = Encoding.UTF8.GetBytes(SendData); var result = await _serialPortService.SendAsync(data); if (result.IsSuccess) { ReceiveData += $"[{DateTime.Now:HH:mm:ss}] 发送: {SendData}\n"; } else { _dialogService.ShowMessage($"发送失败: {result.ErrorMessage}", "错误"); } } catch (Exception ex) { _dialogService.ShowMessage($"发送异常: {ex.Message}", "错误"); } }); ClearCommand = new RelayCommand(() => { ReceiveData = ""; }); // 初始化:刷新串口列表 RefreshPortsCommand.Execute(null); } } ``` ### 4. appsettings.json - 配置文件 ```json { "App": { "Theme": "Light", "Language": "zh-CN" }, "Database": { "Provider": "Sqlite", "ConnectionString": "Data Source=demo.db" }, "Plc": { "Enabled": false, "Siemens": { "IpAddress": "192.168.1.100", "Port": 102, "CpuType": "S71500", "Rack": 0, "Slot": 1 } }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning" } } } ``` ## 🎓 使用说明 ### 测试 PLC 连接 1. 进入"PLC 测试"页面 2. 输入 PLC 的 IP 地址、Rack、Slot 3. 点击"连接"按钮 4. 连接成功后,输入地址(如 `DB1.DBD0`) 5. 点击"读取"或"写入"测试通信 ### 测试串口通信 1. 进入"串口测试"页面 2. 点击"刷新端口"查看可用串口 3. 选择串口并设置波特率 4. 点击"连接" 5. 在发送框输入数据,点击"发送" 6. 接收框会显示收到的数据 ## 💡 提示 - PLC 和串口功能可以独立测试,无需实际设备(会显示连接失败,但不影响其他功能) - 数据库使用 SQLite,无需安装 - 配置文件支持热重载,修改后自动生效 - 所有功能都有错误处理,不会导致程序崩溃 ## 📚 延伸阅读 - 所有示例项目的 README - Sharp 框架文档 --- **这是一个完整的工业监控系统模板,可以直接用于实际项目开发!** 🏭