using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using PetWashControl.Models; using PetWashControl.Services; using System.Collections.ObjectModel; using System.Text.Json; using System.Windows; namespace PetWashControl.ViewModels; public partial class WashStep : ObservableObject { [ObservableProperty] private string _name = ""; [ObservableProperty] private string _status = "等待中"; [ObservableProperty] private bool _isActive; } public partial class MainViewModel : ObservableObject { private readonly ApiService _apiService; private readonly MqttClientService _mqttService; private readonly ConfigurationService _config; private readonly LogService _logger; public event Action? ViewChanged; [ObservableProperty] private ObservableCollection _packages = new(); [ObservableProperty] private Package? _selectedPackage; [ObservableProperty] private Order? _currentOrder; [ObservableProperty] private string _statusMessage = "系统就绪"; [ObservableProperty] private bool _isDoorOpen; [ObservableProperty] private bool _isWashing; [ObservableProperty] private bool _isConnected; [ObservableProperty] private string _currentView = "Idle"; private string _previousView = "Idle"; // 保存进入设置前的视图 [ObservableProperty] private int _washProgress; [ObservableProperty] private string _currentStep = ""; [ObservableProperty] private string _remainingTime = "00:00"; [ObservableProperty] private ObservableCollection _washSteps = new(); [ObservableProperty] private double _waterTemperature = 40.0; [ObservableProperty] private double _roomTemperature = 25.0; [ObservableProperty] private string _currentCarouselImage = "/Images/dog.png"; [ObservableProperty] private string _deviceNumber = "设备编号: PW-001"; [ObservableProperty] private string _currentDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); [ObservableProperty] private string _currentDayOfWeek = DateTime.Now.ToString("dddd", new System.Globalization.CultureInfo("zh-CN")); private readonly System.Timers.Timer _carouselTimer; private readonly System.Timers.Timer _clockTimer; private readonly string[] _carouselImages = { "/Images/dog.png", "/Images/dog1.jpg" }; private int _currentImageIndex = 0; public MainViewModel() { _config = new ConfigurationService(); _logger = new LogService(); _apiService = new ApiService(_config); _mqttService = new MqttClientService(_config); _mqttService.MessageReceived += OnMqttMessageReceived; // 初始化洗护步骤 InitializeWashSteps(); // 初始化图片轮播定时器(每5秒切换) _carouselTimer = new System.Timers.Timer(5000); _carouselTimer.Elapsed += (s, e) => { Application.Current.Dispatcher.Invoke(() => { _currentImageIndex = (_currentImageIndex + 1) % _carouselImages.Length; CurrentCarouselImage = _carouselImages[_currentImageIndex]; }); }; _carouselTimer.Start(); // 初始化时钟定时器(每秒更新) _clockTimer = new System.Timers.Timer(1000); _clockTimer.Elapsed += (s, e) => { Application.Current.Dispatcher.Invoke(() => { var now = DateTime.Now; CurrentDateTime = now.ToString("yyyy-MM-dd HH:mm:ss"); CurrentDayOfWeek = now.ToString("dddd", new System.Globalization.CultureInfo("zh-CN")); }); }; _clockTimer.Start(); } private void InitializeWashSteps() { WashSteps.Add(new WashStep { Name = "第一次冲水", Status = "等待中" }); WashSteps.Add(new WashStep { Name = "沐浴露喷洒", Status = "等待中" }); WashSteps.Add(new WashStep { Name = "第二次冲水", Status = "等待中" }); WashSteps.Add(new WashStep { Name = "香波喷洒", Status = "等待中" }); WashSteps.Add(new WashStep { Name = "第三次冲水", Status = "等待中" }); WashSteps.Add(new WashStep { Name = "热风吹毛", Status = "等待中" }); WashSteps.Add(new WashStep { Name = "冷热风混合", Status = "等待中" }); } public async Task InitializeAsync() { try { _logger.LogInfo("开始初始化系统..."); await _mqttService.ConnectAsync(); IsConnected = _mqttService.IsConnected; _logger.LogInfo("MQTT连接成功"); await LoadPackagesAsync(); StatusMessage = "系统就绪,请点击开始"; _logger.LogInfo("系统初始化完成"); } catch (Exception ex) { _logger.LogError("初始化失败", ex); StatusMessage = $"初始化失败: {ex.Message}"; MessageBox.Show($"系统初始化失败,请检查后端服务是否启动。\n\n错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } [RelayCommand] private async Task LoadPackagesAsync() { try { _logger.LogInfo("加载套餐列表..."); var packages = await _apiService.GetPackagesAsync(); Packages.Clear(); foreach (var package in packages) { Packages.Add(package); } _logger.LogInfo($"成功加载 {packages.Count} 个套餐"); } catch (Exception ex) { _logger.LogError("加载套餐失败", ex); StatusMessage = $"加载套餐失败: {ex.Message}"; } } [RelayCommand] private void ShowPayment() { CurrentView = "Payment"; ViewChanged?.Invoke("Payment"); StatusMessage = "请选择套餐"; _logger.LogInfo("切换到支付界面"); } [RelayCommand] private void ShowSettings() { // 保存当前视图状态 _previousView = CurrentView; CurrentView = "Settings"; ViewChanged?.Invoke("Settings"); StatusMessage = "系统设置"; _logger.LogInfo($"切换到设置界面,之前的视图: {_previousView}"); } [RelayCommand] private void BackToIdle() { // 如果从设置页面返回,且之前不是待机界面,则返回到之前的视图 if (CurrentView == "Settings" && _previousView != "Idle" && _previousView != "Settings") { CurrentView = _previousView; ViewChanged?.Invoke(_previousView); // 根据返回的视图设置相应的状态消息 switch (_previousView) { case "Washing": StatusMessage = "洗护进行中..."; _logger.LogInfo("从设置返回到洗护界面"); break; case "Payment": StatusMessage = "请选择套餐"; _logger.LogInfo("从设置返回到支付界面"); break; case "QRCode": StatusMessage = $"请扫码支付 ¥{SelectedPackage?.Price}"; _logger.LogInfo("从设置返回到二维码支付界面"); break; default: StatusMessage = "系统就绪,请点击开始"; _logger.LogInfo("从设置返回到待机界面"); break; } // 重置之前的视图状态 _previousView = "Idle"; return; } // 默认返回到待机界面 CurrentView = "Idle"; ViewChanged?.Invoke("Idle"); StatusMessage = "系统就绪,请点击开始"; SelectedPackage = null; CurrentOrder = null; IsDoorOpen = false; // 重置洗护步骤状态 foreach (var step in WashSteps) { step.Status = "等待中"; step.IsActive = false; } _previousView = "Idle"; _logger.LogInfo("返回待机界面"); } [RelayCommand] private async Task SelectPackage(Package package) { if (package == null) return; SelectedPackage = package; _logger.LogInfo($"选择套餐: {package.Name}"); try { // 创建订单 CurrentOrder = await _apiService.CreateOrderAsync(package.Id); StatusMessage = $"订单创建成功,请扫码支付 ¥{package.Price}"; _logger.LogInfo($"订单创建成功,订单ID: {CurrentOrder?.Id}"); // 切换到二维码支付界面 CurrentView = "QRCode"; ViewChanged?.Invoke("QRCode"); } catch (Exception ex) { _logger.LogError("创建订单失败", ex); StatusMessage = $"创建订单失败: {ex.Message}"; MessageBox.Show($"创建订单失败\n\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } [RelayCommand] private void CancelPayment() { _logger.LogInfo("取消支付"); CurrentOrder = null; SelectedPackage = null; CurrentView = "Payment"; ViewChanged?.Invoke("Payment"); StatusMessage = "已取消支付,请重新选择套餐"; } [RelayCommand] private async Task CreateOrderAsync() { if (SelectedPackage == null) { MessageBox.Show("请先选择套餐", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } try { _logger.LogInfo($"创建订单,套餐ID: {SelectedPackage.Id}"); CurrentOrder = await _apiService.CreateOrderAsync(SelectedPackage.Id); StatusMessage = $"订单创建成功,订单号: {CurrentOrder?.Id},请支付 ¥{SelectedPackage.Price}"; _logger.LogInfo($"订单创建成功,订单ID: {CurrentOrder?.Id}"); } catch (Exception ex) { _logger.LogError("创建订单失败", ex); StatusMessage = $"创建订单失败: {ex.Message}"; MessageBox.Show($"创建订单失败\n\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } [RelayCommand] private async Task SimulatePaymentAsync() { if (CurrentOrder == null) { MessageBox.Show("请先创建订单", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } try { _logger.LogInfo($"模拟支付,订单ID: {CurrentOrder.Id}"); // 模拟支付处理延迟 StatusMessage = "正在处理支付..."; await Task.Delay(1500); // 确认支付 CurrentOrder = await _apiService.ConfirmPaymentAsync(CurrentOrder.Id); StatusMessage = "支付成功!设备门已打开,请将宠物放入"; IsDoorOpen = true; _logger.LogInfo("支付成功,门已打开"); // 显示支付成功提示 MessageBox.Show("支付成功!\n\n设备门已自动打开\n请将宠物放入设备后关闭门开始洗护", "支付成功", MessageBoxButton.OK, MessageBoxImage.Information); // 返回待机界面,显示关门按钮 CurrentView = "Idle"; ViewChanged?.Invoke("Idle"); } catch (Exception ex) { _logger.LogError("支付失败", ex); StatusMessage = $"支付失败: {ex.Message}"; MessageBox.Show($"支付失败\n\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } [RelayCommand] private async Task CloseDoorAsync() { if (CurrentOrder == null || !IsDoorOpen) { MessageBox.Show("门未打开", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } try { _logger.LogInfo($"关门,订单ID: {CurrentOrder.Id}"); // 通过MQTT发送关门状态 await _mqttService.PublishAsync("device/status", new { status = "door_closed", orderId = CurrentOrder.Id, timestamp = DateTime.Now }); // 更新订单状态 CurrentOrder = await _apiService.UpdateOrderStatusAsync(CurrentOrder.Id, OrderStatus.DoorClosed); IsDoorOpen = false; StatusMessage = "门已关闭,清洗即将开始..."; _logger.LogInfo("门已关闭,等待清洗开始"); // 切换到洗护界面 CurrentView = "Washing"; ViewChanged?.Invoke("Washing"); IsWashing = true; CurrentStep = "第一次冲水"; WashProgress = 0; // 开始模拟洗护流程 _ = SimulateWashingProcessAsync(); } catch (Exception ex) { _logger.LogError("关门失败", ex); StatusMessage = $"关门失败: {ex.Message}"; MessageBox.Show($"关门失败\n\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private async Task SimulateWashingProcessAsync() { try { var steps = new[] { ("第一次冲水", 120), ("沐浴露喷洒", 30), ("第二次冲水", 180), ("香波喷洒", 30), ("第三次冲水", 180), ("热风吹毛", 300), ("冷热风混合", 120) }; int totalDuration = steps.Sum(s => s.Item2); int elapsed = 0; for (int stepIndex = 0; stepIndex < steps.Length; stepIndex++) { var (stepName, duration) = steps[stepIndex]; CurrentStep = stepName; _logger.LogInfo($"开始步骤: {stepName}"); // 更新步骤状态 Application.Current.Dispatcher.Invoke(() => { // 标记之前的步骤为已完成 for (int i = 0; i < stepIndex; i++) { WashSteps[i].Status = "已完成"; WashSteps[i].IsActive = false; } // 标记当前步骤为进行中 WashSteps[stepIndex].Status = "进行中"; WashSteps[stepIndex].IsActive = true; // 标记后续步骤为等待中 for (int i = stepIndex + 1; i < WashSteps.Count; i++) { WashSteps[i].Status = "等待中"; WashSteps[i].IsActive = false; } }); for (int i = 0; i <= duration; i++) { if (!IsWashing) return; // 如果被停止,退出 elapsed++; WashProgress = (int)((double)elapsed / totalDuration * 100); int remaining = totalDuration - elapsed; int minutes = remaining / 60; int seconds = remaining % 60; RemainingTime = $"{minutes:D2}:{seconds:D2}"; // 模拟温度变化 UpdateTemperatures(stepName, i, duration); await Task.Delay(100); // 加速模拟,实际应为1000ms } } // 标记所有步骤为已完成 Application.Current.Dispatcher.Invoke(() => { foreach (var step in WashSteps) { step.Status = "已完成"; step.IsActive = false; } }); // 洗护完成 await CompleteWashingAsync(); } catch (Exception ex) { _logger.LogError("洗护流程失败", ex); StatusMessage = $"洗护流程失败: {ex.Message}"; } } private async Task CompleteWashingAsync() { if (CurrentOrder == null) return; try { _logger.LogInfo($"清洗完成,订单ID: {CurrentOrder.Id}"); // 发送清洗完成状态 await _mqttService.PublishAsync("device/status", new { status = "completed", orderId = CurrentOrder.Id, timestamp = DateTime.Now }); CurrentOrder = await _apiService.UpdateOrderStatusAsync(CurrentOrder.Id, OrderStatus.Completed); IsWashing = false; IsDoorOpen = true; StatusMessage = "清洗完成!请取出宠物"; MessageBox.Show("清洗完成!\n\n感谢使用无人自动洗宠机", "完成", MessageBoxButton.OK, MessageBoxImage.Information); _logger.LogInfo("订单流程完成"); // 返回待机界面 CurrentOrder = null; SelectedPackage = null; BackToIdle(); } catch (Exception ex) { _logger.LogError("完成流程失败", ex); StatusMessage = $"完成流程失败: {ex.Message}"; } } private void OnMqttMessageReceived(string topic, string payload) { Application.Current.Dispatcher.Invoke(() => { try { _logger.LogInfo($"收到MQTT消息 - Topic: {topic}, Payload: {payload}"); var message = JsonSerializer.Deserialize(payload); if (topic == "device/command") { var command = message.GetProperty("command").GetString(); if (command == "open_door") { IsDoorOpen = true; StatusMessage = "设备门已打开,请将宠物放入后点击关门"; _logger.LogInfo("收到开门指令"); } else if (command == "start_wash") { IsWashing = true; var duration = message.GetProperty("durationMinutes").GetInt32(); StatusMessage = $"清洗已开始,预计 {duration} 分钟完成"; _logger.LogInfo($"收到开始清洗指令,时长: {duration}分钟"); } } else if (topic == "device/status") { var status = message.GetProperty("status").GetString(); StatusMessage = $"设备状态: {status}"; _logger.LogInfo($"设备状态更新: {status}"); } } catch (Exception ex) { _logger.LogError("处理MQTT消息失败", ex); StatusMessage = $"处理消息失败: {ex.Message}"; } }); } private void UpdateTemperatures(string stepName, int currentTime, int totalTime) { // 根据不同步骤模拟温度变化 var random = new Random(); double progress = (double)currentTime / totalTime; switch (stepName) { case "第一次冲水": case "第二次冲水": case "第三次冲水": // 冲水阶段:水温在38-42度之间波动 WaterTemperature = 40.0 + (random.NextDouble() - 0.5) * 4; RoomTemperature = 25.0 + (random.NextDouble() - 0.5) * 2; break; case "沐浴露喷洒": case "香波喷洒": // 喷洒阶段:水温略低 WaterTemperature = 38.0 + (random.NextDouble() - 0.5) * 2; RoomTemperature = 25.0 + (random.NextDouble() - 0.5) * 2; break; case "热风吹毛": // 热风阶段:水温降低,室温升高 WaterTemperature = 35.0 - progress * 10 + (random.NextDouble() - 0.5) * 2; RoomTemperature = 25.0 + progress * 8 + (random.NextDouble() - 0.5) * 2; break; case "冷热风混合": // 混合风阶段:温度逐渐降低 WaterTemperature = 25.0 + (random.NextDouble() - 0.5) * 2; RoomTemperature = 30.0 - progress * 5 + (random.NextDouble() - 0.5) * 2; break; default: // 默认温度 WaterTemperature = 40.0 + (random.NextDouble() - 0.5) * 2; RoomTemperature = 25.0 + (random.NextDouble() - 0.5) * 2; break; } // 确保温度在合理范围内 WaterTemperature = Math.Max(20, Math.Min(45, WaterTemperature)); RoomTemperature = Math.Max(20, Math.Min(35, RoomTemperature)); } }