Files
sharp-basic/examples/11-FullStackDemo/README.md
T

570 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 框架文档
---
**这是一个完整的工业监控系统模板,可以直接用于实际项目开发!** 🏭