diff --git a/PetWash.Api/petwash.db b/PetWash.Api/petwash.db
new file mode 100644
index 0000000..a6d9baf
Binary files /dev/null and b/PetWash.Api/petwash.db differ
diff --git a/PetWashControl/App.xaml b/PetWashControl/App.xaml
index 764e29d..1c0ac61 100644
--- a/PetWashControl/App.xaml
+++ b/PetWashControl/App.xaml
@@ -12,6 +12,8 @@
+
+
diff --git a/PetWashControl/Converters/BoolToStatusConverter.cs b/PetWashControl/Converters/BoolToStatusConverter.cs
index a63d58f..1df3073 100644
--- a/PetWashControl/Converters/BoolToStatusConverter.cs
+++ b/PetWashControl/Converters/BoolToStatusConverter.cs
@@ -36,3 +36,37 @@ public class BoolToWashingConverter : IValueConverter
throw new NotImplementedException();
}
}
+
+public class BoolToVisibilityConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return boolValue ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
+ }
+ return System.Windows.Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
+
+public class InverseBoolToVisibilityConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return boolValue ? System.Windows.Visibility.Collapsed : System.Windows.Visibility.Visible;
+ }
+ return System.Windows.Visibility.Visible;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/PetWashControl/PetWashControl.csproj b/PetWashControl/PetWashControl.csproj
index 76e59f8..d6e6372 100644
--- a/PetWashControl/PetWashControl.csproj
+++ b/PetWashControl/PetWashControl.csproj
@@ -1,4 +1,4 @@
-
+
WinExe
diff --git a/PetWashControl/Resources/Styles.xaml b/PetWashControl/Resources/Styles.xaml
index 8279cd0..87ee0c0 100644
--- a/PetWashControl/Resources/Styles.xaml
+++ b/PetWashControl/Resources/Styles.xaml
@@ -1,44 +1,67 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PetWashControl/Services/ConfigurationService.cs b/PetWashControl/Services/ConfigurationService.cs
index 689e6d6..e65a5db 100644
--- a/PetWashControl/Services/ConfigurationService.cs
+++ b/PetWashControl/Services/ConfigurationService.cs
@@ -2,7 +2,7 @@ namespace PetWashControl.Services;
public class ConfigurationService
{
- public string ApiBaseUrl { get; set; } = "https://localhost:7001/";
+ public string ApiBaseUrl { get; set; } = "https://localhost:7203/";
public string MqttBrokerHost { get; set; } = "localhost";
public int MqttBrokerPort { get; set; } = 1883;
public string MqttClientId { get; set; } = "PetWashControl";
diff --git a/PetWashControl/ViewModels/MainViewModel.cs b/PetWashControl/ViewModels/MainViewModel.cs
index d0753b2..7ca081b 100644
--- a/PetWashControl/ViewModels/MainViewModel.cs
+++ b/PetWashControl/ViewModels/MainViewModel.cs
@@ -8,6 +8,18 @@ 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;
@@ -15,6 +27,8 @@ public partial class MainViewModel : ObservableObject
private readonly ConfigurationService _config;
private readonly LogService _logger;
+ public event Action? ViewChanged;
+
[ObservableProperty]
private ObservableCollection _packages = new();
@@ -25,7 +39,7 @@ public partial class MainViewModel : ObservableObject
private Order? _currentOrder;
[ObservableProperty]
- private string _statusMessage = "请选择套餐";
+ private string _statusMessage = "系统就绪";
[ObservableProperty]
private bool _isDoorOpen;
@@ -36,6 +50,21 @@ public partial class MainViewModel : ObservableObject
[ObservableProperty]
private bool _isConnected;
+ [ObservableProperty]
+ private string _currentView = "Idle";
+
+ [ObservableProperty]
+ private int _washProgress;
+
+ [ObservableProperty]
+ private string _currentStep = "";
+
+ [ObservableProperty]
+ private string _remainingTime = "00:00";
+
+ [ObservableProperty]
+ private ObservableCollection _washSteps = new();
+
public MainViewModel()
{
_config = new ConfigurationService();
@@ -43,6 +72,20 @@ public partial class MainViewModel : ObservableObject
_apiService = new ApiService(_config);
_mqttService = new MqttClientService(_config);
_mqttService.MessageReceived += OnMqttMessageReceived;
+
+ // 初始化洗护步骤
+ 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 = "等待中" });
}
public async Task InitializeAsync()
@@ -56,7 +99,7 @@ public partial class MainViewModel : ObservableObject
_logger.LogInfo("MQTT连接成功");
await LoadPackagesAsync();
- StatusMessage = "系统就绪,请选择套餐";
+ StatusMessage = "系统就绪,请点击开始";
_logger.LogInfo("系统初始化完成");
}
catch (Exception ex)
@@ -89,6 +132,74 @@ public partial class MainViewModel : ObservableObject
}
}
+ [RelayCommand]
+ private void ShowPayment()
+ {
+ CurrentView = "Payment";
+ ViewChanged?.Invoke("Payment");
+ StatusMessage = "请选择套餐";
+ _logger.LogInfo("切换到支付界面");
+ }
+
+ [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 = "已取消支付,请重新选择套餐";
+ }
+
[RelayCommand]
private async Task CreateOrderAsync()
{
@@ -126,10 +237,24 @@ public partial class MainViewModel : ObservableObject
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)
{
@@ -167,6 +292,16 @@ public partial class MainViewModel : ObservableObject
IsDoorOpen = false;
StatusMessage = "门已关闭,清洗即将开始...";
_logger.LogInfo("门已关闭,等待清洗开始");
+
+ // 切换到洗护界面
+ CurrentView = "Washing";
+ ViewChanged?.Invoke("Washing");
+ IsWashing = true;
+ CurrentStep = "第一次冲水";
+ WashProgress = 0;
+
+ // 开始模拟洗护流程
+ _ = SimulateWashingProcessAsync();
}
catch (Exception ex)
{
@@ -177,6 +312,127 @@ public partial class MainViewModel : ObservableObject
}
}
+ 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}";
+
+ 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(() =>
@@ -202,12 +458,6 @@ public partial class MainViewModel : ObservableObject
var duration = message.GetProperty("durationMinutes").GetInt32();
StatusMessage = $"清洗已开始,预计 {duration} 分钟完成";
_logger.LogInfo($"收到开始清洗指令,时长: {duration}分钟");
-
- // 模拟清洗完成
- Task.Delay(TimeSpan.FromSeconds(_config.WashSimulationSeconds)).ContinueWith(async _ =>
- {
- await SimulateWashCompleteAsync();
- });
}
}
else if (topic == "device/status")
@@ -224,39 +474,4 @@ public partial class MainViewModel : ObservableObject
}
});
}
-
- private async Task SimulateWashCompleteAsync()
- {
- if (CurrentOrder == null) return;
-
- await Application.Current.Dispatcher.InvokeAsync(async () =>
- {
- 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;
- StatusMessage = "清洗完成!请取出宠物";
-
- MessageBox.Show("清洗完成!感谢使用", "完成",
- MessageBoxButton.OK, MessageBoxImage.Information);
- _logger.LogInfo("订单流程完成");
- }
- catch (Exception ex)
- {
- _logger.LogError("完成流程失败", ex);
- StatusMessage = $"完成流程失败: {ex.Message}";
- }
- });
- }
}
diff --git a/PetWashControl/Views/MainWindow.xaml b/PetWashControl/Views/MainWindow.xaml
index 6959cf7..ffc6bdd 100644
--- a/PetWashControl/Views/MainWindow.xaml
+++ b/PetWashControl/Views/MainWindow.xaml
@@ -1,153 +1,601 @@
-
-
-
-
-
-
-
-
+ Title="无人自动洗宠机智能控制系统"
+ Height="800" Width="1024"
+ WindowStyle="None"
+ ResizeMode="NoResize"
+ WindowStartupLocation="CenterScreen"
+ Background="{StaticResource BackgroundGradient}">
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PetWashControl/Views/MainWindow.xaml.cs b/PetWashControl/Views/MainWindow.xaml.cs
index 9d27867..4a38d15 100644
--- a/PetWashControl/Views/MainWindow.xaml.cs
+++ b/PetWashControl/Views/MainWindow.xaml.cs
@@ -1,5 +1,7 @@
using PetWashControl.ViewModels;
+using PetWashControl.Models;
using System.Windows;
+using System.Windows.Input;
namespace PetWashControl.Views;
@@ -11,6 +13,7 @@ public partial class MainWindow : Window
{
InitializeComponent();
_viewModel = new MainViewModel();
+ _viewModel.ViewChanged += OnViewChanged;
DataContext = _viewModel;
Loaded += MainWindow_Loaded;
}
@@ -19,4 +22,38 @@ public partial class MainWindow : Window
{
await _viewModel.InitializeAsync();
}
+
+ private void OnViewChanged(string viewName)
+ {
+ // 隐藏所有视图
+ IdleView.Visibility = Visibility.Collapsed;
+ PaymentView.Visibility = Visibility.Collapsed;
+ QRCodeView.Visibility = Visibility.Collapsed;
+ WashingView.Visibility = Visibility.Collapsed;
+
+ // 显示指定视图
+ switch (viewName)
+ {
+ case "Idle":
+ IdleView.Visibility = Visibility.Visible;
+ break;
+ case "Payment":
+ PaymentView.Visibility = Visibility.Visible;
+ break;
+ case "QRCode":
+ QRCodeView.Visibility = Visibility.Visible;
+ break;
+ case "Washing":
+ WashingView.Visibility = Visibility.Visible;
+ break;
+ }
+ }
+
+ private void Package_Click(object sender, MouseButtonEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.DataContext is Package package)
+ {
+ _viewModel.SelectPackageCommand.Execute(package);
+ }
+ }
}
\ No newline at end of file