Files
petwash/PetWashControl/ViewModels/MainViewModel.cs
2026-02-26 14:30:48 +08:00

625 lines
21 KiB
C#
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.
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<string>? ViewChanged;
[ObservableProperty]
private ObservableCollection<Package> _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<WashStep> _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<JsonElement>(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));
}
}