diff --git a/PetWashControl/App.xaml b/PetWashControl/App.xaml
index 0a28f88..764e29d 100644
--- a/PetWashControl/App.xaml
+++ b/PetWashControl/App.xaml
@@ -2,8 +2,16 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PetWashControl"
- StartupUri="MainWindow.xaml">
+ xmlns:converters="clr-namespace:PetWashControl.Converters"
+ StartupUri="Views/MainWindow.xaml">
-
+
+
+
+
+
+
+
+
diff --git a/PetWashControl/Converters/BoolToStatusConverter.cs b/PetWashControl/Converters/BoolToStatusConverter.cs
new file mode 100644
index 0000000..a63d58f
--- /dev/null
+++ b/PetWashControl/Converters/BoolToStatusConverter.cs
@@ -0,0 +1,38 @@
+using System.Globalization;
+using System.Windows.Data;
+
+namespace PetWashControl.Converters;
+
+public class BoolToStatusConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool isOpen)
+ {
+ return isOpen ? "已打开" : "已关闭";
+ }
+ return "未知";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
+
+public class BoolToWashingConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool isWashing)
+ {
+ return isWashing ? "清洗中" : "空闲";
+ }
+ return "未知";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/PetWashControl/MainWindow.xaml b/PetWashControl/MainWindow.xaml
deleted file mode 100644
index 6032a7e..0000000
--- a/PetWashControl/MainWindow.xaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
diff --git a/PetWashControl/MainWindow.xaml.cs b/PetWashControl/MainWindow.xaml.cs
deleted file mode 100644
index e39e4fd..0000000
--- a/PetWashControl/MainWindow.xaml.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Text;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-
-namespace PetWashControl
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- public MainWindow()
- {
- InitializeComponent();
- }
- }
-}
\ No newline at end of file
diff --git a/PetWashControl/Models/Order.cs b/PetWashControl/Models/Order.cs
new file mode 100644
index 0000000..827ad82
--- /dev/null
+++ b/PetWashControl/Models/Order.cs
@@ -0,0 +1,23 @@
+namespace PetWashControl.Models;
+
+public class Order
+{
+ public int Id { get; set; }
+ public int PackageId { get; set; }
+ public Package? Package { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public OrderStatus Status { get; set; }
+ public bool IsPaid { get; set; }
+}
+
+public enum OrderStatus
+{
+ Created,
+ WaitingPayment,
+ Paid,
+ DoorOpened,
+ DoorClosed,
+ Washing,
+ Completed,
+ Cancelled
+}
diff --git a/PetWashControl/Models/Package.cs b/PetWashControl/Models/Package.cs
new file mode 100644
index 0000000..64f7b56
--- /dev/null
+++ b/PetWashControl/Models/Package.cs
@@ -0,0 +1,10 @@
+namespace PetWashControl.Models;
+
+public class Package
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public decimal Price { get; set; }
+ public int DurationMinutes { get; set; }
+ public string Description { get; set; } = string.Empty;
+}
diff --git a/PetWashControl/PetWashControl.csproj b/PetWashControl/PetWashControl.csproj
index e3e33e3..76e59f8 100644
--- a/PetWashControl/PetWashControl.csproj
+++ b/PetWashControl/PetWashControl.csproj
@@ -8,4 +8,12 @@
true
+
+
+
+
+
+
+
+
diff --git a/PetWashControl/Resources/Styles.xaml b/PetWashControl/Resources/Styles.xaml
new file mode 100644
index 0000000..8279cd0
--- /dev/null
+++ b/PetWashControl/Resources/Styles.xaml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PetWashControl/Services/ApiService.cs b/PetWashControl/Services/ApiService.cs
new file mode 100644
index 0000000..d3b38ec
--- /dev/null
+++ b/PetWashControl/Services/ApiService.cs
@@ -0,0 +1,88 @@
+using System.Net.Http;
+using System.Net.Http.Json;
+using PetWashControl.Models;
+
+namespace PetWashControl.Services;
+
+public class ApiService
+{
+ private readonly HttpClient _httpClient;
+ private readonly ConfigurationService _config;
+
+ public ApiService(ConfigurationService? config = null)
+ {
+ _config = config ?? new ConfigurationService();
+ _httpClient = new HttpClient
+ {
+ BaseAddress = new Uri(_config.ApiBaseUrl),
+ Timeout = TimeSpan.FromSeconds(30)
+ };
+ }
+
+ public async Task> GetPackagesAsync()
+ {
+ try
+ {
+ return await _httpClient.GetFromJsonAsync>("api/packages")
+ ?? new List();
+ }
+ catch (HttpRequestException ex)
+ {
+ throw new Exception($"无法连接到服务器: {ex.Message}", ex);
+ }
+ }
+
+ public async Task CreateOrderAsync(int packageId)
+ {
+ try
+ {
+ var response = await _httpClient.PostAsJsonAsync("api/orders", new { packageId });
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadFromJsonAsync();
+ }
+ catch (HttpRequestException ex)
+ {
+ throw new Exception($"创建订单失败: {ex.Message}", ex);
+ }
+ }
+
+ public async Task ConfirmPaymentAsync(int orderId)
+ {
+ try
+ {
+ var response = await _httpClient.PostAsync($"api/orders/{orderId}/payment", null);
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadFromJsonAsync();
+ }
+ catch (HttpRequestException ex)
+ {
+ throw new Exception($"确认支付失败: {ex.Message}", ex);
+ }
+ }
+
+ public async Task GetOrderAsync(int orderId)
+ {
+ try
+ {
+ return await _httpClient.GetFromJsonAsync($"api/orders/{orderId}");
+ }
+ catch (HttpRequestException ex)
+ {
+ throw new Exception($"获取订单失败: {ex.Message}", ex);
+ }
+ }
+
+ public async Task UpdateOrderStatusAsync(int orderId, OrderStatus status)
+ {
+ try
+ {
+ var response = await _httpClient.PutAsJsonAsync($"api/orders/{orderId}/status", new { status });
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadFromJsonAsync();
+ }
+ catch (HttpRequestException ex)
+ {
+ throw new Exception($"更新订单状态失败: {ex.Message}", ex);
+ }
+ }
+}
diff --git a/PetWashControl/Services/ConfigurationService.cs b/PetWashControl/Services/ConfigurationService.cs
new file mode 100644
index 0000000..689e6d6
--- /dev/null
+++ b/PetWashControl/Services/ConfigurationService.cs
@@ -0,0 +1,12 @@
+namespace PetWashControl.Services;
+
+public class ConfigurationService
+{
+ public string ApiBaseUrl { get; set; } = "https://localhost:7001/";
+ public string MqttBrokerHost { get; set; } = "localhost";
+ public int MqttBrokerPort { get; set; } = 1883;
+ public string MqttClientId { get; set; } = "PetWashControl";
+
+ public int PaymentCheckIntervalSeconds { get; set; } = 2;
+ public int WashSimulationSeconds { get; set; } = 10;
+}
diff --git a/PetWashControl/Services/LogService.cs b/PetWashControl/Services/LogService.cs
new file mode 100644
index 0000000..62a9f05
--- /dev/null
+++ b/PetWashControl/Services/LogService.cs
@@ -0,0 +1,50 @@
+using System.Diagnostics;
+using System.IO;
+
+namespace PetWashControl.Services;
+
+public class LogService
+{
+ private readonly string _logFilePath;
+
+ public LogService()
+ {
+ var logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
+ Directory.CreateDirectory(logDirectory);
+ _logFilePath = Path.Combine(logDirectory, $"log_{DateTime.Now:yyyyMMdd}.txt");
+ }
+
+ public void LogInfo(string message)
+ {
+ Log("INFO", message);
+ }
+
+ public void LogWarning(string message)
+ {
+ Log("WARN", message);
+ }
+
+ public void LogError(string message, Exception? ex = null)
+ {
+ var fullMessage = ex != null ? $"{message}\n{ex}" : message;
+ Log("ERROR", fullMessage);
+ }
+
+ private void Log(string level, string message)
+ {
+ var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
+
+ // 输出到调试窗口
+ Debug.WriteLine(logMessage);
+
+ // 写入文件
+ try
+ {
+ File.AppendAllText(_logFilePath, logMessage + Environment.NewLine);
+ }
+ catch
+ {
+ // 忽略日志写入失败
+ }
+ }
+}
diff --git a/PetWashControl/Services/MqttClientService.cs b/PetWashControl/Services/MqttClientService.cs
new file mode 100644
index 0000000..93f3feb
--- /dev/null
+++ b/PetWashControl/Services/MqttClientService.cs
@@ -0,0 +1,109 @@
+using MQTTnet;
+using MQTTnet.Client;
+using System.Text;
+using System.Text.Json;
+
+namespace PetWashControl.Services;
+
+public class MqttClientService
+{
+ private readonly IMqttClient _mqttClient;
+ private readonly MqttClientOptions _options;
+ private readonly ConfigurationService _config;
+
+ public event Action? MessageReceived;
+ public bool IsConnected => _mqttClient.IsConnected;
+
+ public MqttClientService(ConfigurationService? config = null)
+ {
+ _config = config ?? new ConfigurationService();
+
+ var factory = new MqttFactory();
+ _mqttClient = factory.CreateMqttClient();
+
+ _options = new MqttClientOptionsBuilder()
+ .WithTcpServer(_config.MqttBrokerHost, _config.MqttBrokerPort)
+ .WithClientId(_config.MqttClientId)
+ .WithCleanSession()
+ .Build();
+
+ _mqttClient.ApplicationMessageReceivedAsync += OnMessageReceived;
+ _mqttClient.DisconnectedAsync += OnDisconnected;
+ }
+
+ public async Task ConnectAsync()
+ {
+ if (_mqttClient.IsConnected)
+ return;
+
+ try
+ {
+ await _mqttClient.ConnectAsync(_options, CancellationToken.None);
+
+ // 订阅设备状态和命令主题
+ var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
+ .WithTopicFilter("device/status")
+ .WithTopicFilter("device/command")
+ .Build();
+
+ await _mqttClient.SubscribeAsync(subscribeOptions, CancellationToken.None);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"MQTT连接失败: {ex.Message}", ex);
+ }
+ }
+
+ public async Task DisconnectAsync()
+ {
+ if (_mqttClient.IsConnected)
+ {
+ await _mqttClient.DisconnectAsync();
+ }
+ }
+
+ public async Task PublishAsync(string topic, object payload)
+ {
+ if (!_mqttClient.IsConnected)
+ {
+ throw new InvalidOperationException("MQTT客户端未连接");
+ }
+
+ var json = JsonSerializer.Serialize(payload);
+ var message = new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(json)
+ .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
+ .Build();
+
+ await _mqttClient.PublishAsync(message, CancellationToken.None);
+ }
+
+ private Task OnMessageReceived(MqttApplicationMessageReceivedEventArgs args)
+ {
+ var topic = args.ApplicationMessage.Topic;
+ var payloadBytes = args.ApplicationMessage.PayloadSegment.ToArray();
+ var payload = Encoding.UTF8.GetString(payloadBytes);
+
+ MessageReceived?.Invoke(topic, payload);
+ return Task.CompletedTask;
+ }
+
+ private async Task OnDisconnected(MqttClientDisconnectedEventArgs args)
+ {
+ // 自动重连
+ if (!args.ClientWasConnected)
+ return;
+
+ await Task.Delay(TimeSpan.FromSeconds(5));
+
+ try
+ {
+ await ConnectAsync();
+ }
+ catch
+ {
+ // 重连失败,等待下次重试
+ }
+ }
+}
diff --git a/PetWashControl/ViewModels/MainViewModel.cs b/PetWashControl/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..d0753b2
--- /dev/null
+++ b/PetWashControl/ViewModels/MainViewModel.cs
@@ -0,0 +1,262 @@
+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 MainViewModel : ObservableObject
+{
+ private readonly ApiService _apiService;
+ private readonly MqttClientService _mqttService;
+ private readonly ConfigurationService _config;
+ private readonly LogService _logger;
+
+ [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;
+
+ public MainViewModel()
+ {
+ _config = new ConfigurationService();
+ _logger = new LogService();
+ _apiService = new ApiService(_config);
+ _mqttService = new MqttClientService(_config);
+ _mqttService.MessageReceived += OnMqttMessageReceived;
+ }
+
+ 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 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}");
+ CurrentOrder = await _apiService.ConfirmPaymentAsync(CurrentOrder.Id);
+ StatusMessage = "支付成功!设备门已打开,请将宠物放入";
+ IsDoorOpen = true;
+ _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 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("门已关闭,等待清洗开始");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("关门失败", ex);
+ StatusMessage = $"关门失败: {ex.Message}";
+ MessageBox.Show($"关门失败\n\n{ex.Message}", "错误",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ 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}分钟");
+
+ // 模拟清洗完成
+ Task.Delay(TimeSpan.FromSeconds(_config.WashSimulationSeconds)).ContinueWith(async _ =>
+ {
+ await SimulateWashCompleteAsync();
+ });
+ }
+ }
+ 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 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
new file mode 100644
index 0000000..6959cf7
--- /dev/null
+++ b/PetWashControl/Views/MainWindow.xaml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PetWashControl/Views/MainWindow.xaml.cs b/PetWashControl/Views/MainWindow.xaml.cs
new file mode 100644
index 0000000..9d27867
--- /dev/null
+++ b/PetWashControl/Views/MainWindow.xaml.cs
@@ -0,0 +1,22 @@
+using PetWashControl.ViewModels;
+using System.Windows;
+
+namespace PetWashControl.Views;
+
+public partial class MainWindow : Window
+{
+ private readonly MainViewModel _viewModel;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ _viewModel = new MainViewModel();
+ DataContext = _viewModel;
+ Loaded += MainWindow_Loaded;
+ }
+
+ private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
+ {
+ await _viewModel.InitializeAsync();
+ }
+}
\ No newline at end of file
diff --git a/无人自动洗宠机.sln b/无人自动洗宠机.sln
index 1a6ef8c..3043874 100644
--- a/无人自动洗宠机.sln
+++ b/无人自动洗宠机.sln
@@ -1,10 +1,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.14.37012.4 d17.14
+VisualStudioVersion = 17.14.37012.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PetWashControl", "E:\无人自动洗宠机\PetWashControl\PetWashControl.csproj", "{7129BAC3-0F8E-4F33-96A7-11196D7D7787}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PetWash.Api", "PetWash.Api\PetWash.Api.csproj", "{E4834569-8D50-4D98-8CD3-89BA25C914BC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{7129BAC3-0F8E-4F33-96A7-11196D7D7787}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7129BAC3-0F8E-4F33-96A7-11196D7D7787}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7129BAC3-0F8E-4F33-96A7-11196D7D7787}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E4834569-8D50-4D98-8CD3-89BA25C914BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E4834569-8D50-4D98-8CD3-89BA25C914BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E4834569-8D50-4D98-8CD3-89BA25C914BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E4834569-8D50-4D98-8CD3-89BA25C914BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE