570 lines
15 KiB
Markdown
570 lines
15 KiB
Markdown
# 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<AppDbContext>(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<bool>("Plc:Enabled", false);
|
||
if (plcEnabled)
|
||
{
|
||
services.AddSiemensPlc(config.GetSection("Plc:Siemens"));
|
||
}
|
||
|
||
// 7. 串口服务
|
||
services.AddSingleton<ISerialPortService, SerialPortService>();
|
||
|
||
// 8. HTTP 客户端
|
||
services.AddHttpClient();
|
||
|
||
// 9. 注册 ViewModels
|
||
services.AddViewModel<LoginViewModel>();
|
||
services.AddViewModel<MainViewModel>();
|
||
services.AddViewModel<HomeViewModel>();
|
||
services.AddViewModel<ProductsViewModel>();
|
||
services.AddViewModel<PlcTestViewModel>();
|
||
services.AddViewModel<PortsTestViewModel>();
|
||
services.AddViewModel<SettingsViewModel>();
|
||
|
||
// 10. 注册 Windows
|
||
services.AddTransient<LoginWindow>();
|
||
services.AddTransient<MainWindow>();
|
||
|
||
_serviceProvider = services.BuildServiceProvider();
|
||
|
||
// 11. 初始化数据库
|
||
using (var scope = _serviceProvider.CreateScope())
|
||
{
|
||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||
db.Database.EnsureCreated();
|
||
SeedData(db); // 初始化测试数据
|
||
}
|
||
|
||
// 12. 应用主题
|
||
var themeService = _serviceProvider.GetRequiredService<IThemeService>();
|
||
var savedTheme = config.GetValue<string>("App:Theme", "Light");
|
||
themeService.ApplyTheme(Enum.Parse<Theme>(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<int>(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<string> 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 框架文档
|
||
|
||
---
|
||
|
||
**这是一个完整的工业监控系统模板,可以直接用于实际项目开发!** 🏭
|