U:调整调用方式和兼容性
This commit is contained in:
1 parent
433e206d45
commit
4ec76b7855
50 files changed
+5592
-47
No files matched your search
@@ -0,0 +1,569 @@
|
||||
# 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 框架文档
|
||||
|
||||
---
|
||||
|
||||
**这是一个完整的工业监控系统模板,可以直接用于实际项目开发!** 🏭
|
||||
Reference in new issue
Block a user