This commit is contained in:
GukSang.Jin
2026-03-18 13:53:44 +08:00
parent 54b3448e31
commit 0a884fa6cb
20 changed files with 1700 additions and 110 deletions

View File

@@ -25,6 +25,18 @@ public partial class WashStep : ObservableObject
private bool _isActive;
}
public sealed record PackageTimingProfile(
int FirstSprayWaterTime,
int AfterShampoo1SprayTime,
int AfterShampoo2SprayTime,
int AfterShampoo3SprayTime,
int SprayShampoo1Time,
int SprayShampoo2Time,
int SprayShampoo3Time,
int HotAirTime,
int ColdAirTime,
int UvSterilizationTime);
public partial class MainViewModel : ObservableObject
{
private readonly ApiService _apiService;
@@ -34,6 +46,7 @@ public partial class MainViewModel : ObservableObject
private readonly LogService _logger;
public event Action<string>? ViewChanged;
public event Action? OpenPackageManagementRequested;
[ObservableProperty]
private ObservableCollection<Package> _packages = new();
@@ -121,6 +134,42 @@ public partial class MainViewModel : ObservableObject
[ObservableProperty]
private bool _isCustomerServiceDialogOpen;
[ObservableProperty]
private Package? _editingPackage;
[ObservableProperty]
private decimal _editingPackagePrice = 0.01m;
[ObservableProperty]
private int _editingPackageFirstSprayWaterTime = 2;
[ObservableProperty]
private int _editingPackageAfterShampoo1SprayTime = 2;
[ObservableProperty]
private int _editingPackageAfterShampoo2SprayTime = 2;
[ObservableProperty]
private int _editingPackageAfterShampoo3SprayTime = 2;
[ObservableProperty]
private int _editingPackageSprayShampoo1Time = 1;
[ObservableProperty]
private int _editingPackageSprayShampoo2Time = 1;
[ObservableProperty]
private int _editingPackageSprayShampoo3Time = 1;
[ObservableProperty]
private int _editingPackageColdAirTime = 2;
[ObservableProperty]
private int _editingPackageHotAirTime = 5;
[ObservableProperty]
private int _editingPackageUvSterilizationTime = 2;
[ObservableProperty]
private int _firstSprayWaterTime = 2;
@@ -179,6 +228,18 @@ public partial class MainViewModel : ObservableObject
private bool _isPaymentPolling;
private bool _isCheckingPaymentStatus;
public int EditingPackageTotalDuration =>
EditingPackageFirstSprayWaterTime +
EditingPackageAfterShampoo1SprayTime +
EditingPackageAfterShampoo2SprayTime +
EditingPackageAfterShampoo3SprayTime +
EditingPackageSprayShampoo1Time +
EditingPackageSprayShampoo2Time +
EditingPackageSprayShampoo3Time +
EditingPackageHotAirTime +
EditingPackageColdAirTime +
EditingPackageUvSterilizationTime;
public MainViewModel()
{
_config = new ConfigurationService();
@@ -458,6 +519,11 @@ public partial class MainViewModel : ObservableObject
_logger.LogInfo($"切换到设置界面,之前的视图: {_previousView}");
}
public void ShowSettingsFromAdmin()
{
ShowSettings();
}
[RelayCommand]
private void ShowInstruction()
{
@@ -504,6 +570,7 @@ public partial class MainViewModel : ObservableObject
_config.ColdAirTime = ColdAirTime;
_config.HotAirTime = HotAirTime;
_config.UvSterilizationTime = UvSterilizationTime;
_config.Save();
_logger.LogInfo($"本地参数已更新 - 首次喷水:{FirstSprayWaterTime}min, 沐浴1后:{AfterShampoo1SprayTime}min, " +
$"沐浴2后:{AfterShampoo2SprayTime}min, 沐浴3后:{AfterShampoo3SprayTime}min, " +
@@ -570,6 +637,75 @@ public partial class MainViewModel : ObservableObject
}
}
private PackageTimingProfile GetPackageTimingProfile(Package package)
{
if (package.FirstSprayWaterTime > 0 &&
package.AfterShampoo1SprayTime > 0 &&
package.AfterShampoo2SprayTime > 0 &&
package.AfterShampoo3SprayTime > 0 &&
package.SprayShampoo1Time > 0 &&
package.SprayShampoo2Time > 0 &&
package.SprayShampoo3Time > 0 &&
package.HotAirTime > 0 &&
package.ColdAirTime > 0 &&
package.UvSterilizationTime > 0)
{
return new PackageTimingProfile(
package.FirstSprayWaterTime,
package.AfterShampoo1SprayTime,
package.AfterShampoo2SprayTime,
package.AfterShampoo3SprayTime,
package.SprayShampoo1Time,
package.SprayShampoo2Time,
package.SprayShampoo3Time,
package.HotAirTime,
package.ColdAirTime,
package.UvSterilizationTime);
}
return new PackageTimingProfile(
_config.FirstSprayWaterTime,
_config.AfterShampoo1SprayTime,
_config.AfterShampoo2SprayTime,
_config.AfterShampoo3SprayTime,
_config.SprayShampoo1Time,
_config.SprayShampoo2Time,
_config.SprayShampoo3Time,
_config.HotAirTime,
_config.ColdAirTime,
_config.UvSterilizationTime);
}
private void ApplyPackageTimingProfile(PackageTimingProfile profile)
{
FirstSprayWaterTime = profile.FirstSprayWaterTime;
AfterShampoo1SprayTime = profile.AfterShampoo1SprayTime;
AfterShampoo2SprayTime = profile.AfterShampoo2SprayTime;
AfterShampoo3SprayTime = profile.AfterShampoo3SprayTime;
SprayShampoo1Time = profile.SprayShampoo1Time;
SprayShampoo2Time = profile.SprayShampoo2Time;
SprayShampoo3Time = profile.SprayShampoo3Time;
HotAirTime = profile.HotAirTime;
ColdAirTime = profile.ColdAirTime;
UvSterilizationTime = profile.UvSterilizationTime;
}
private async Task ApplyPackageTimingProfileAsync(Package package)
{
var profile = GetPackageTimingProfile(package);
ApplyPackageTimingProfile(profile);
_logger.LogInfo(
$"套餐参数已切换: PackageId={package.Id}, FirstSpray={FirstSprayWaterTime}, Shampoo1={SprayShampoo1Time}, " +
$"Rinse1={AfterShampoo1SprayTime}, Shampoo2={SprayShampoo2Time}, Rinse2={AfterShampoo2SprayTime}, " +
$"Conditioner={SprayShampoo3Time}, Rinse3={AfterShampoo3SprayTime}, HotAir={HotAirTime}, ColdAir={ColdAirTime}, UV={UvSterilizationTime}");
if (IsModbusConnected)
{
await SaveParametersToDeviceAsync();
}
}
// 参数调整命令
[RelayCommand]
private void IncreaseFirstSprayWater() => FirstSprayWaterTime = Math.Min(FirstSprayWaterTime + 1, 60);
@@ -695,30 +831,7 @@ public partial class MainViewModel : ObservableObject
try
{
// 创建订单
var createOrderResponse = await _apiService.CreateOrderAsync(package.Id);
CurrentOrder = createOrderResponse?.Order;
PaymentCodeUrl = createOrderResponse?.CodeUrl ?? "";
PaymentOutTradeNo = createOrderResponse?.OutTradeNo ?? "";
PaymentExpiresAt = createOrderResponse?.ExpiresAt;
IsPaymentExpired = false;
PaymentStatusText = "等待扫码支付";
RefreshPaymentCountdown();
PaymentQrCodeImage = string.IsNullOrWhiteSpace(PaymentCodeUrl)
? null
: BuildPaymentQrCodeImage(PaymentCodeUrl);
if (CurrentOrder == null || PaymentQrCodeImage == null || string.IsNullOrWhiteSpace(PaymentOutTradeNo))
{
throw new InvalidOperationException("Payment QR code was not returned by the server.");
}
StatusMessage = $"订单创建成功,请扫码支付 ¥{package.Price}";
_logger.LogInfo($"订单创建成功订单ID: {CurrentOrder?.Id}");
// 切换到二维码支付界面
CurrentView = "QRCode";
ViewChanged?.Invoke("QRCode");
StartPaymentStatusPolling();
await CreatePaymentOrderAsync(package, true);
}
catch (Exception ex)
{
@@ -748,28 +861,11 @@ public partial class MainViewModel : ObservableObject
try
{
_logger.LogInfo($"创建订单套餐ID: {SelectedPackage.Id}");
var createOrderResponse = await _apiService.CreateOrderAsync(SelectedPackage.Id);
CurrentOrder = createOrderResponse?.Order;
PaymentCodeUrl = createOrderResponse?.CodeUrl ?? "";
PaymentOutTradeNo = createOrderResponse?.OutTradeNo ?? "";
PaymentExpiresAt = createOrderResponse?.ExpiresAt;
IsPaymentExpired = false;
PaymentStatusText = "等待扫码支付";
RefreshPaymentCountdown();
PaymentQrCodeImage = string.IsNullOrWhiteSpace(PaymentCodeUrl)
? null
: BuildPaymentQrCodeImage(PaymentCodeUrl);
if (CurrentOrder == null || PaymentQrCodeImage == null || string.IsNullOrWhiteSpace(PaymentOutTradeNo))
{
throw new InvalidOperationException("Payment QR code was not returned by the server.");
}
StatusMessage = $"订单创建成功,订单号: {CurrentOrder?.Id},请支付 ¥{SelectedPackage.Price}";
_logger.LogInfo($"订单创建成功订单ID: {CurrentOrder?.Id}");
await CreatePaymentOrderAsync(SelectedPackage, false);
}
catch (Exception ex)
{
StopPaymentStatusPolling();
_logger.LogError("创建订单失败", ex);
StatusMessage = $"创建订单失败: {ex.Message}";
MessageBox.Show($"创建订单失败\n\n{ex.Message}", "错误",
@@ -808,6 +904,71 @@ public partial class MainViewModel : ObservableObject
}
}
[RelayCommand]
private async Task RegeneratePaymentQrAsync()
{
if (SelectedPackage == null)
{
return;
}
try
{
StopPaymentStatusPolling();
PaymentStatusText = "正在刷新二维码...";
StatusMessage = "正在刷新二维码...";
if (CurrentOrder != null && !CurrentOrder.IsPaid)
{
await _apiService.UpdateOrderStatusAsync(CurrentOrder.Id, OrderStatus.Cancelled);
}
await CreatePaymentOrderAsync(SelectedPackage, true);
}
catch (Exception ex)
{
_logger.LogError("刷新支付二维码失败", ex);
PaymentStatusText = "二维码刷新失败";
StatusMessage = $"二维码刷新失败: {ex.Message}";
MessageBox.Show($"二维码刷新失败\n\n{ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async Task CreatePaymentOrderAsync(Package package, bool navigateToQrCode)
{
_logger.LogInfo($"创建订单套餐ID: {package.Id}");
await ApplyPackageTimingProfileAsync(package);
var createOrderResponse = await _apiService.CreateOrderAsync(package.Id);
CurrentOrder = createOrderResponse?.Order;
PaymentCodeUrl = createOrderResponse?.CodeUrl ?? "";
PaymentOutTradeNo = createOrderResponse?.OutTradeNo ?? "";
PaymentExpiresAt = createOrderResponse?.ExpiresAt;
IsPaymentExpired = false;
PaymentStatusText = "等待扫码支付";
RefreshPaymentCountdown();
PaymentQrCodeImage = string.IsNullOrWhiteSpace(PaymentCodeUrl)
? null
: BuildPaymentQrCodeImage(PaymentCodeUrl);
if (CurrentOrder == null || PaymentQrCodeImage == null || string.IsNullOrWhiteSpace(PaymentOutTradeNo))
{
throw new InvalidOperationException("Payment QR code was not returned by the server.");
}
StatusMessage = $"订单创建成功,请扫码支付 ¥{package.DisplayPrice}";
_logger.LogInfo($"订单创建成功订单ID: {CurrentOrder?.Id}");
if (navigateToQrCode)
{
CurrentView = "QRCode";
ViewChanged?.Invoke("QRCode");
}
StartPaymentStatusPolling();
}
private static BitmapImage BuildPaymentQrCodeImage(string codeUrl)
{
using var generator = new QRCodeGenerator();
@@ -1379,6 +1540,154 @@ public partial class MainViewModel : ObservableObject
});
}
[RelayCommand]
private void OpenPackageManagement()
{
if (Packages.Count == 0)
{
StatusMessage = "套餐列表未加载";
return;
}
EditingPackage ??= Packages[0];
OpenPackageManagementRequested?.Invoke();
}
[RelayCommand]
private async Task SavePackageConfigAsync()
{
if (EditingPackage == null)
{
MessageBox.Show("请先选择要编辑的套餐。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
if (CurrentOrder != null && !CurrentOrder.IsPaid)
{
MessageBox.Show("当前有未完成支付订单,不能修改套餐配置。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (EditingPackagePrice <= 0 ||
EditingPackageFirstSprayWaterTime <= 0 ||
EditingPackageAfterShampoo1SprayTime <= 0 ||
EditingPackageAfterShampoo2SprayTime <= 0 ||
EditingPackageAfterShampoo3SprayTime <= 0 ||
EditingPackageSprayShampoo1Time <= 0 ||
EditingPackageSprayShampoo2Time <= 0 ||
EditingPackageSprayShampoo3Time <= 0 ||
EditingPackageHotAirTime <= 0 ||
EditingPackageColdAirTime <= 0 ||
EditingPackageUvSterilizationTime <= 0)
{
MessageBox.Show("套餐金额和每个流程时间都必须大于 0。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
try
{
StatusMessage = $"正在保存 {EditingPackage.Name} 配置...";
var packageToSave = new Package
{
Id = EditingPackage.Id,
Name = EditingPackage.Name,
Description = EditingPackage.Description,
Price = EditingPackagePrice,
DurationMinutes = EditingPackageTotalDuration,
FirstSprayWaterTime = EditingPackageFirstSprayWaterTime,
AfterShampoo1SprayTime = EditingPackageAfterShampoo1SprayTime,
AfterShampoo2SprayTime = EditingPackageAfterShampoo2SprayTime,
AfterShampoo3SprayTime = EditingPackageAfterShampoo3SprayTime,
SprayShampoo1Time = EditingPackageSprayShampoo1Time,
SprayShampoo2Time = EditingPackageSprayShampoo2Time,
SprayShampoo3Time = EditingPackageSprayShampoo3Time,
HotAirTime = EditingPackageHotAirTime,
ColdAirTime = EditingPackageColdAirTime,
UvSterilizationTime = EditingPackageUvSterilizationTime
};
var savedPackage = await _apiService.UpdatePackageAsync(packageToSave)
?? throw new InvalidOperationException("Server did not return the updated package.");
ReplacePackageInCollection(savedPackage);
EditingPackage = savedPackage;
if (SelectedPackage?.Id == savedPackage.Id)
{
SelectedPackage = savedPackage;
await ApplyPackageTimingProfileAsync(savedPackage);
}
if (CurrentOrder?.PackageId == savedPackage.Id && CurrentOrder.IsPaid)
{
CurrentOrder.Package = savedPackage;
}
StatusMessage = $"{savedPackage.Name} 配置已保存";
MessageBox.Show(
$"{savedPackage.Name} 已更新。\n\n金额: {savedPackage.DisplayPrice} 元\n总时长: {savedPackage.DurationMinutes} 分钟",
"保存成功",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
catch (Exception ex)
{
_logger.LogError("保存套餐配置失败", ex);
StatusMessage = $"保存套餐配置失败: {ex.Message}";
MessageBox.Show($"保存套餐配置失败\n\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
partial void OnEditingPackageChanged(Package? value)
{
if (value != null)
{
LoadEditingPackage(value);
}
}
partial void OnEditingPackageFirstSprayWaterTimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageAfterShampoo1SprayTimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageAfterShampoo2SprayTimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageAfterShampoo3SprayTimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageSprayShampoo1TimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageSprayShampoo2TimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageSprayShampoo3TimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageHotAirTimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageColdAirTimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
partial void OnEditingPackageUvSterilizationTimeChanged(int value) => OnPropertyChanged(nameof(EditingPackageTotalDuration));
private void ReplacePackageInCollection(Package updatedPackage)
{
for (var i = 0; i < Packages.Count; i++)
{
if (Packages[i].Id == updatedPackage.Id)
{
Packages[i] = updatedPackage;
return;
}
}
Packages.Add(updatedPackage);
}
private void LoadEditingPackage(Package package)
{
EditingPackagePrice = package.Price;
EditingPackageFirstSprayWaterTime = package.FirstSprayWaterTime;
EditingPackageAfterShampoo1SprayTime = package.AfterShampoo1SprayTime;
EditingPackageAfterShampoo2SprayTime = package.AfterShampoo2SprayTime;
EditingPackageAfterShampoo3SprayTime = package.AfterShampoo3SprayTime;
EditingPackageSprayShampoo1Time = package.SprayShampoo1Time;
EditingPackageSprayShampoo2Time = package.SprayShampoo2Time;
EditingPackageSprayShampoo3Time = package.SprayShampoo3Time;
EditingPackageHotAirTime = package.HotAirTime;
EditingPackageColdAirTime = package.ColdAirTime;
EditingPackageUvSterilizationTime = package.UvSterilizationTime;
OnPropertyChanged(nameof(EditingPackageTotalDuration));
}
private void UpdateTemperatures(string stepName, int currentTime, int totalTime)
{
// 根据不同步骤模拟温度变化