Files
sharp-basic/examples/11-FullStackDemo

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

🚀 快速运行

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 - 完整启动配置

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 测试页 ⭐

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 - 串口测试页 ⭐

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 - 配置文件

{
  "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 框架文档

这是一个完整的工业监控系统模板,可以直接用于实际项目开发! 🏭