# 软件开发文档 - 06. 实现步骤与示例

本文档提供关键功能的具体实现步骤和代码示例，以指导开发过程。

## 1. 添加新设备 (End-to-End)

这是一个从UI到数据库的完整流程示例。

**步骤 1: View (`DeviceListView.xaml`)**

用户点击“添加”按钮，该按钮绑定到ViewModel的 `AddDeviceCommand`。

```xml
<Button Content="添加设备" Command="{Binding AddDeviceCommand}" />
```

**步骤 2: ViewModel (`DeviceListViewModel.cs`)**

`AddDeviceCommand` 执行，调用对话框服务来显示一个用于输入新设备信息的窗口。

```csharp
public ICommand AddDeviceCommand { get; }

public DeviceListViewModel(IDeviceAppService deviceAppService, IDialogService dialogService)
{
    _deviceAppService = deviceAppService;
    _dialogService = dialogService;
    AddDeviceCommand = new RelayCommand(async () => await ExecuteAddDevice());
}

private async Task ExecuteAddDevice()
{
    var vm = new AddDeviceDialogViewModel(); // 一个专门用于对话框的ViewModel
    var result = await _dialogService.ShowDialogAsync(vm);

    if (result != null) // 假设对话框返回创建的DTO
    {
        await _deviceAppService.CreateDeviceAsync(result);
        await LoadDevicesAsync(); // 刷新列表
    }
}
```

**步骤 3: Application Service (`DeviceAppService.cs`)**

应用服务接收DTO，将其映射为领域模型，并调用仓储来保存。

```csharp
public async Task CreateDeviceAsync(CreateDeviceDto deviceDto)
{
    // 参数校验
    if (string.IsNullOrWhiteSpace(deviceDto.Name) || string.IsNullOrWhiteSpace(deviceDto.IpAddress))
    {
        throw new ArgumentException("设备名称和IP地址不能为空");
    }

    var device = _mapper.Map<Device>(deviceDto);
    device.IsActive = true; // 设置默认值

    await _deviceRepository.AddAsync(device);
}
```

**步骤 4: Repository (`DeviceRepository.cs`)**

仓储将领域模型映射为数据库实体，并使用 `SqlSugar` 将其插入数据库。

```csharp
public async Task AddAsync(Device entity)
{
    var dbEntity = _mapper.Map<DbDevice>(entity);
    await _db.Insertable(dbEntity).ExecuteCommandAsync();
}
```

## 2. S7数据采集后台服务

后台服务将轮询所有激活的S7设备，读取变量值，并启动数据处理链。

**步骤 1: 创建后台服务 (`S7BackgroundService.cs`)**

这个服务应该是一个长时间运行的服务，可以使用 `IHostedService` (如果项目是.NET Core) 或一个简单的 `Task.Run` 循环。

```csharp
// 文件: DMS.WPF/Services/S7BackgroundService.cs
public class S7BackgroundService : IDisposable
{
    private readonly IDeviceRepository _deviceRepo;
    private readonly IS7CommunicationService _s7Service;
    private readonly DataAcquisitionService _acquisitionService; // 包含处理链逻辑的服务
    private CancellationTokenSource _cts;

    public S7BackgroundService(IDeviceRepository deviceRepo, IS7CommunicationService s7Service, DataAcquisitionService acquisitionService)
    {
        // ... 注入依赖
    }

    public void Start()
    {
        _cts = new CancellationTokenSource();
        Task.Run(async () => await ExecuteAsync(_cts.Token));
    }

    protected async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var activeDevices = await _deviceRepo.GetActiveDevicesWithDetailsAsync();

            foreach (var device in activeDevices.Where(d => d.Protocol == ProtocolType.S7))
            {
                await _s7Service.ConnectAsync(device); // 确保连接

                foreach (var table in device.VariableTables.Where(t => t.IsActive))
                {
                    foreach (var variable in table.Variables.Where(v => v.IsActive))
                    {
                        try
                        {
                            var value = await _s7Service.ReadVariableAsync(device, variable);
                            // 触发数据处理链
                            await _acquisitionService.OnDataReceived(variable, value);
                        }
                        catch (Exception ex)
                        {
                            // Log error
                        }
                    }
                }
            }

            await Task.Delay(1000, stoppingToken); // 轮询间隔
        }
    }

    public void Stop()
    {
        _cts?.Cancel();
    }
}
```

**步骤 2: 在 `App.xaml.cs` 中启动服务**

在应用程序启动时，从DI容器获取服务实例并启动它。

```csharp
// 在 App.xaml.cs OnStartup
var s7Service = _serviceProvider.GetRequiredService<S7BackgroundService>();
s7Service.Start();
```

## 3. 菜单导航实现

**步骤 1: `MainViewModel`**

```csharp
public class MainViewModel : BaseViewModel
{
    private readonly INavigationService _navigationService;
    public ICommand NavigateCommand { get; }

    public MainViewModel(INavigationService navigationService)
    {
        _navigationService = navigationService;
        NavigateCommand = new RelayCommand<Type>(OnNavigate);
    }

    private void OnNavigate(Type viewModelType)
    {
        _navigationService.NavigateTo(viewModelType);
    }
}
```

**步骤 2: `NavigationService`**

这个服务负责实际的ViewModel切换，它会更新 `MainViewModel` 的 `CurrentViewModel` 属性。

```csharp
public class NavigationService : INavigationService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly MainViewModel _mainViewModel; // 持有MainViewModel的引用

    public NavigationService(IServiceProvider serviceProvider, MainViewModel mainViewModel)
    {
        _serviceProvider = serviceProvider;
        _mainViewModel = mainViewModel;
    }

    public void NavigateTo(Type viewModelType)
    {
        var viewModel = (BaseViewModel)_serviceProvider.GetRequiredService(viewModelType);
        _mainViewModel.CurrentViewModel = viewModel;
    }
}
```

**步骤 3: View (`MainWindow.xaml`)**

`ContentControl` 绑定到 `CurrentViewModel`，并使用 `DataTemplate` 来根据ViewModel的类型选择对应的View。

```xml
<ContentControl Content="{Binding CurrentViewModel}">
    <ContentControl.Resources>
        <DataTemplate DataType="{x:Type vm:DashboardViewModel}">
            <views:DashboardView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:DeviceListViewModel}">
            <views:DeviceListView />
        </DataTemplate>
        <!-- 其他模板 -->
    </ContentControl.Resources>
</ContentControl>
```
