Files
petwash/PetWashControl/ViewModels/MainViewModel.cs

544 lines
18 KiB
C#
Raw Normal View History

2026-02-25 15:43:47 +08:00
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;
2026-02-25 18:30:24 +08:00
public partial class WashStep : ObservableObject
{
[ObservableProperty]
private string _name = "";
[ObservableProperty]
private string _status = "等待中";
[ObservableProperty]
private bool _isActive;
}
2026-02-25 15:43:47 +08:00
public partial class MainViewModel : ObservableObject
{
private readonly ApiService _apiService;
private readonly MqttClientService _mqttService;
private readonly ConfigurationService _config;
private readonly LogService _logger;
2026-02-25 18:30:24 +08:00
public event Action<string>? ViewChanged;
2026-02-25 15:43:47 +08:00
[ObservableProperty]
private ObservableCollection<Package> _packages = new();
[ObservableProperty]
private Package? _selectedPackage;
[ObservableProperty]
private Order? _currentOrder;
[ObservableProperty]
2026-02-25 18:30:24 +08:00
private string _statusMessage = "系统就绪";
2026-02-25 15:43:47 +08:00
[ObservableProperty]
private bool _isDoorOpen;
[ObservableProperty]
private bool _isWashing;
[ObservableProperty]
private bool _isConnected;
2026-02-25 18:30:24 +08:00
[ObservableProperty]
private string _currentView = "Idle";
[ObservableProperty]
private int _washProgress;
[ObservableProperty]
private string _currentStep = "";
[ObservableProperty]
private string _remainingTime = "00:00";
[ObservableProperty]
private ObservableCollection<WashStep> _washSteps = new();
2026-02-26 11:30:31 +08:00
[ObservableProperty]
private double _waterTemperature = 40.0;
[ObservableProperty]
private double _roomTemperature = 25.0;
2026-02-25 15:43:47 +08:00
public MainViewModel()
{
_config = new ConfigurationService();
_logger = new LogService();
_apiService = new ApiService(_config);
_mqttService = new MqttClientService(_config);
_mqttService.MessageReceived += OnMqttMessageReceived;
2026-02-25 18:30:24 +08:00
// 初始化洗护步骤
InitializeWashSteps();
}
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 = "等待中" });
2026-02-25 15:43:47 +08:00
}
public async Task InitializeAsync()
{
try
{
_logger.LogInfo("开始初始化系统...");
await _mqttService.ConnectAsync();
IsConnected = _mqttService.IsConnected;
_logger.LogInfo("MQTT连接成功");
await LoadPackagesAsync();
2026-02-25 18:30:24 +08:00
StatusMessage = "系统就绪,请点击开始";
2026-02-25 15:43:47 +08:00
_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}";
}
}
2026-02-25 18:30:24 +08:00
[RelayCommand]
private void ShowPayment()
{
CurrentView = "Payment";
ViewChanged?.Invoke("Payment");
StatusMessage = "请选择套餐";
_logger.LogInfo("切换到支付界面");
}
2026-02-26 11:30:31 +08:00
[RelayCommand]
private void ShowSettings()
{
CurrentView = "Settings";
ViewChanged?.Invoke("Settings");
StatusMessage = "系统设置";
_logger.LogInfo("切换到设置界面");
}
2026-02-25 18:30:24 +08:00
[RelayCommand]
private void BackToIdle()
{
CurrentView = "Idle";
ViewChanged?.Invoke("Idle");
StatusMessage = "系统就绪,请点击开始";
SelectedPackage = null;
CurrentOrder = null;
IsDoorOpen = false;
// 重置洗护步骤状态
foreach (var step in WashSteps)
{
step.Status = "等待中";
step.IsActive = false;
}
_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 = "已取消支付,请重新选择套餐";
}
2026-02-25 15:43:47 +08:00
[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}");
2026-02-25 18:30:24 +08:00
// 模拟支付处理延迟
StatusMessage = "正在处理支付...";
await Task.Delay(1500);
// 确认支付
2026-02-25 15:43:47 +08:00
CurrentOrder = await _apiService.ConfirmPaymentAsync(CurrentOrder.Id);
StatusMessage = "支付成功!设备门已打开,请将宠物放入";
IsDoorOpen = true;
_logger.LogInfo("支付成功,门已打开");
2026-02-25 18:30:24 +08:00
// 显示支付成功提示
MessageBox.Show("支付成功!\n\n设备门已自动打开\n请将宠物放入设备后关闭门开始洗护",
"支付成功", MessageBoxButton.OK, MessageBoxImage.Information);
// 返回待机界面,显示关门按钮
CurrentView = "Idle";
ViewChanged?.Invoke("Idle");
2026-02-25 15:43:47 +08:00
}
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("门已关闭,等待清洗开始");
2026-02-25 18:30:24 +08:00
// 切换到洗护界面
CurrentView = "Washing";
ViewChanged?.Invoke("Washing");
IsWashing = true;
CurrentStep = "第一次冲水";
WashProgress = 0;
// 开始模拟洗护流程
_ = SimulateWashingProcessAsync();
2026-02-25 15:43:47 +08:00
}
catch (Exception ex)
{
_logger.LogError("关门失败", ex);
StatusMessage = $"关门失败: {ex.Message}";
MessageBox.Show($"关门失败\n\n{ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
2026-02-25 18:30:24 +08:00
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}";
2026-02-26 11:30:31 +08:00
// 模拟温度变化
UpdateTemperatures(stepName, i, duration);
2026-02-25 18:30:24 +08:00
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}";
}
}
2026-02-25 15:43:47 +08:00
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}";
}
});
}
2026-02-26 11:30:31 +08:00
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));
}
2026-02-25 15:43:47 +08:00
}
2026-02-26 11:30:31 +08:00