更新
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PetWash.Api.Data;
|
||||
using PetWash.Api.Models;
|
||||
|
||||
namespace PetWash.Api.Controllers;
|
||||
|
||||
@@ -18,7 +19,9 @@ public class PackagesController : ControllerBase
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPackages()
|
||||
{
|
||||
var packages = await _context.Packages.ToListAsync();
|
||||
var packages = await _context.Packages
|
||||
.OrderBy(x => x.Id)
|
||||
.ToListAsync();
|
||||
return Ok(packages);
|
||||
}
|
||||
|
||||
@@ -31,4 +34,71 @@ public class PackagesController : ControllerBase
|
||||
|
||||
return Ok(package);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> UpdatePackage(int id, [FromBody] UpdatePackageRequest request)
|
||||
{
|
||||
var package = await _context.Packages.FindAsync(id);
|
||||
if (package == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (request.Price <= 0)
|
||||
{
|
||||
return BadRequest("套餐金额必须大于 0。");
|
||||
}
|
||||
|
||||
if (request.FirstSprayWaterTime <= 0 ||
|
||||
request.AfterShampoo1SprayTime <= 0 ||
|
||||
request.AfterShampoo2SprayTime <= 0 ||
|
||||
request.AfterShampoo3SprayTime <= 0 ||
|
||||
request.SprayShampoo1Time <= 0 ||
|
||||
request.SprayShampoo2Time <= 0 ||
|
||||
request.SprayShampoo3Time <= 0 ||
|
||||
request.HotAirTime <= 0 ||
|
||||
request.ColdAirTime <= 0 ||
|
||||
request.UvSterilizationTime <= 0)
|
||||
{
|
||||
return BadRequest("套餐流程时间必须全部大于 0。");
|
||||
}
|
||||
|
||||
package.Price = request.Price;
|
||||
package.FirstSprayWaterTime = request.FirstSprayWaterTime;
|
||||
package.AfterShampoo1SprayTime = request.AfterShampoo1SprayTime;
|
||||
package.AfterShampoo2SprayTime = request.AfterShampoo2SprayTime;
|
||||
package.AfterShampoo3SprayTime = request.AfterShampoo3SprayTime;
|
||||
package.SprayShampoo1Time = request.SprayShampoo1Time;
|
||||
package.SprayShampoo2Time = request.SprayShampoo2Time;
|
||||
package.SprayShampoo3Time = request.SprayShampoo3Time;
|
||||
package.HotAirTime = request.HotAirTime;
|
||||
package.ColdAirTime = request.ColdAirTime;
|
||||
package.UvSterilizationTime = request.UvSterilizationTime;
|
||||
package.DurationMinutes = request.FirstSprayWaterTime
|
||||
+ request.AfterShampoo1SprayTime
|
||||
+ request.AfterShampoo2SprayTime
|
||||
+ request.AfterShampoo3SprayTime
|
||||
+ request.SprayShampoo1Time
|
||||
+ request.SprayShampoo2Time
|
||||
+ request.SprayShampoo3Time
|
||||
+ request.HotAirTime
|
||||
+ request.ColdAirTime
|
||||
+ request.UvSterilizationTime;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok(package);
|
||||
}
|
||||
}
|
||||
|
||||
public record UpdatePackageRequest(
|
||||
decimal Price,
|
||||
int FirstSprayWaterTime,
|
||||
int AfterShampoo1SprayTime,
|
||||
int AfterShampoo2SprayTime,
|
||||
int AfterShampoo3SprayTime,
|
||||
int SprayShampoo1Time,
|
||||
int SprayShampoo2Time,
|
||||
int SprayShampoo3Time,
|
||||
int HotAirTime,
|
||||
int ColdAirTime,
|
||||
int UvSterilizationTime);
|
||||
|
||||
@@ -13,12 +13,6 @@ public class PetWashDbContext : DbContext
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// 初始化套餐数据
|
||||
modelBuilder.Entity<Package>().HasData(
|
||||
new Package { Id = 1, Name = "套餐1", Price = 50, DurationMinutes = 38, Description = "适用于小型犬" },
|
||||
new Package { Id = 2, Name = "套餐2", Price = 80, DurationMinutes = 48, Description = "适用于中型犬" },
|
||||
new Package { Id = 3, Name = "套餐3", Price = 120, DurationMinutes = 60, Description = "适用于大型犬" }
|
||||
);
|
||||
modelBuilder.Entity<Package>().HasData(PackageCatalog.DefaultPackages.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,14 @@ public class Package
|
||||
public decimal Price { get; set; }
|
||||
public int DurationMinutes { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public int FirstSprayWaterTime { get; set; }
|
||||
public int AfterShampoo1SprayTime { get; set; }
|
||||
public int AfterShampoo2SprayTime { get; set; }
|
||||
public int AfterShampoo3SprayTime { get; set; }
|
||||
public int SprayShampoo1Time { get; set; }
|
||||
public int SprayShampoo2Time { get; set; }
|
||||
public int SprayShampoo3Time { get; set; }
|
||||
public int HotAirTime { get; set; }
|
||||
public int ColdAirTime { get; set; }
|
||||
public int UvSterilizationTime { get; set; }
|
||||
}
|
||||
|
||||
62
PetWash.Api/Models/PackageCatalog.cs
Normal file
62
PetWash.Api/Models/PackageCatalog.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
namespace PetWash.Api.Models;
|
||||
|
||||
public static class PackageCatalog
|
||||
{
|
||||
public static IReadOnlyList<Package> DefaultPackages { get; } =
|
||||
[
|
||||
new Package
|
||||
{
|
||||
Id = 1,
|
||||
Name = "小型犬套餐",
|
||||
Price = 0.01m,
|
||||
DurationMinutes = 20,
|
||||
Description = "适用于小型犬,洗护流程较短",
|
||||
FirstSprayWaterTime = 2,
|
||||
AfterShampoo1SprayTime = 2,
|
||||
AfterShampoo2SprayTime = 2,
|
||||
AfterShampoo3SprayTime = 2,
|
||||
SprayShampoo1Time = 1,
|
||||
SprayShampoo2Time = 1,
|
||||
SprayShampoo3Time = 1,
|
||||
HotAirTime = 5,
|
||||
ColdAirTime = 2,
|
||||
UvSterilizationTime = 2
|
||||
},
|
||||
new Package
|
||||
{
|
||||
Id = 2,
|
||||
Name = "中型犬套餐",
|
||||
Price = 0.02m,
|
||||
DurationMinutes = 26,
|
||||
Description = "适用于中型犬,洗护流程适中",
|
||||
FirstSprayWaterTime = 3,
|
||||
AfterShampoo1SprayTime = 3,
|
||||
AfterShampoo2SprayTime = 3,
|
||||
AfterShampoo3SprayTime = 3,
|
||||
SprayShampoo1Time = 1,
|
||||
SprayShampoo2Time = 1,
|
||||
SprayShampoo3Time = 1,
|
||||
HotAirTime = 6,
|
||||
ColdAirTime = 3,
|
||||
UvSterilizationTime = 2
|
||||
},
|
||||
new Package
|
||||
{
|
||||
Id = 3,
|
||||
Name = "大型犬套餐",
|
||||
Price = 0.03m,
|
||||
DurationMinutes = 37,
|
||||
Description = "适用于大型犬,洗护流程更长",
|
||||
FirstSprayWaterTime = 4,
|
||||
AfterShampoo1SprayTime = 4,
|
||||
AfterShampoo2SprayTime = 4,
|
||||
AfterShampoo3SprayTime = 4,
|
||||
SprayShampoo1Time = 2,
|
||||
SprayShampoo2Time = 2,
|
||||
SprayShampoo3Time = 2,
|
||||
HotAirTime = 8,
|
||||
ColdAirTime = 4,
|
||||
UvSterilizationTime = 3
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PetWash.Api.Data;
|
||||
using PetWash.Api.Models;
|
||||
using PetWash.Api.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// 配置数据库(支持 SQLite 和 MySQL)
|
||||
var dbProvider = builder.Configuration.GetValue<string>("DatabaseProvider") ?? "Sqlite";
|
||||
var connectionString = dbProvider == "MySql"
|
||||
var connectionString = dbProvider == "MySql"
|
||||
? builder.Configuration.GetConnectionString("MySqlConnection")
|
||||
: builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
|
||||
@@ -23,7 +25,6 @@ builder.Services.AddDbContext<PetWashDbContext>(options =>
|
||||
}
|
||||
});
|
||||
|
||||
// 添加服务
|
||||
builder.Services.AddSingleton<MqttService>();
|
||||
builder.Services.AddHostedService(provider => provider.GetRequiredService<MqttService>());
|
||||
builder.Services.AddScoped<OrderService>();
|
||||
@@ -34,7 +35,6 @@ builder.Services.AddHttpClient<WeChatPayService>(client =>
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
});
|
||||
|
||||
// 添加CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
@@ -51,36 +51,79 @@ builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// 初始化数据库
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<PetWashDbContext>();
|
||||
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogInformation("开始初始化数据库...");
|
||||
|
||||
// 确保数据库已创建
|
||||
logger.LogInformation("Starting database initialization.");
|
||||
|
||||
var created = db.Database.EnsureCreated();
|
||||
|
||||
if (created)
|
||||
{
|
||||
logger.LogInformation("数据库创建成功,种子数据已插入");
|
||||
logger.LogInformation("Database created and seed data inserted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("数据库已存在");
|
||||
logger.LogInformation("Database already exists.");
|
||||
}
|
||||
|
||||
EnsurePackageTimingColumns(db, logger);
|
||||
|
||||
foreach (var package in PackageCatalog.DefaultPackages)
|
||||
{
|
||||
var existingPackage = db.Packages.FirstOrDefault(x => x.Id == package.Id);
|
||||
if (existingPackage is null)
|
||||
{
|
||||
db.Packages.Add(new Package
|
||||
{
|
||||
Id = package.Id,
|
||||
Name = package.Name,
|
||||
Price = package.Price,
|
||||
DurationMinutes = package.DurationMinutes,
|
||||
Description = package.Description,
|
||||
FirstSprayWaterTime = package.FirstSprayWaterTime,
|
||||
AfterShampoo1SprayTime = package.AfterShampoo1SprayTime,
|
||||
AfterShampoo2SprayTime = package.AfterShampoo2SprayTime,
|
||||
AfterShampoo3SprayTime = package.AfterShampoo3SprayTime,
|
||||
SprayShampoo1Time = package.SprayShampoo1Time,
|
||||
SprayShampoo2Time = package.SprayShampoo2Time,
|
||||
SprayShampoo3Time = package.SprayShampoo3Time,
|
||||
HotAirTime = package.HotAirTime,
|
||||
ColdAirTime = package.ColdAirTime,
|
||||
UvSterilizationTime = package.UvSterilizationTime
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
existingPackage.Name = package.Name;
|
||||
existingPackage.Price = package.Price;
|
||||
existingPackage.DurationMinutes = package.DurationMinutes;
|
||||
existingPackage.Description = package.Description;
|
||||
existingPackage.FirstSprayWaterTime = package.FirstSprayWaterTime;
|
||||
existingPackage.AfterShampoo1SprayTime = package.AfterShampoo1SprayTime;
|
||||
existingPackage.AfterShampoo2SprayTime = package.AfterShampoo2SprayTime;
|
||||
existingPackage.AfterShampoo3SprayTime = package.AfterShampoo3SprayTime;
|
||||
existingPackage.SprayShampoo1Time = package.SprayShampoo1Time;
|
||||
existingPackage.SprayShampoo2Time = package.SprayShampoo2Time;
|
||||
existingPackage.SprayShampoo3Time = package.SprayShampoo3Time;
|
||||
existingPackage.HotAirTime = package.HotAirTime;
|
||||
existingPackage.ColdAirTime = package.ColdAirTime;
|
||||
existingPackage.UvSterilizationTime = package.UvSterilizationTime;
|
||||
}
|
||||
}
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "数据库初始化失败");
|
||||
logger.LogError(ex, "Database initialization failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// 启用 Swagger(所有环境)
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
@@ -90,3 +133,56 @@ app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
|
||||
static void EnsurePackageTimingColumns(PetWashDbContext db, ILogger logger)
|
||||
{
|
||||
var providerName = db.Database.ProviderName ?? string.Empty;
|
||||
var isSqlite = providerName.Contains("Sqlite", StringComparison.OrdinalIgnoreCase);
|
||||
var missingColumns = GetMissingPackageTimingColumns(db.Database.GetDbConnection(), isSqlite);
|
||||
|
||||
foreach (var column in missingColumns)
|
||||
{
|
||||
var sql = isSqlite
|
||||
? $"ALTER TABLE Packages ADD COLUMN {column.Name} INTEGER NOT NULL DEFAULT {column.DefaultValue};"
|
||||
: $"ALTER TABLE Packages ADD COLUMN {column.Name} INT NOT NULL DEFAULT {column.DefaultValue};";
|
||||
|
||||
db.Database.ExecuteSqlRaw(sql);
|
||||
logger.LogInformation("Added missing package timing column {ColumnName}", column.Name);
|
||||
}
|
||||
}
|
||||
|
||||
static List<(string Name, int DefaultValue)> GetMissingPackageTimingColumns(DbConnection connection, bool isSqlite)
|
||||
{
|
||||
var expectedColumns = new List<(string Name, int DefaultValue)>
|
||||
{
|
||||
("FirstSprayWaterTime", 2),
|
||||
("AfterShampoo1SprayTime", 2),
|
||||
("AfterShampoo2SprayTime", 2),
|
||||
("AfterShampoo3SprayTime", 2),
|
||||
("SprayShampoo1Time", 1),
|
||||
("SprayShampoo2Time", 1),
|
||||
("SprayShampoo3Time", 1),
|
||||
("HotAirTime", 5),
|
||||
("ColdAirTime", 2),
|
||||
("UvSterilizationTime", 2)
|
||||
};
|
||||
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = isSqlite ? "PRAGMA table_info(Packages);" : "SHOW COLUMNS FROM Packages;";
|
||||
|
||||
using var reader = command.ExecuteReader();
|
||||
var existingColumns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
while (reader.Read())
|
||||
{
|
||||
existingColumns.Add(reader.GetString(isSqlite ? 1 : 0));
|
||||
}
|
||||
|
||||
return expectedColumns
|
||||
.Where(column => !existingColumns.Contains(column.Name))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
},
|
||||
"DatabaseProvider": "Sqlite",
|
||||
"WeChatPay": {
|
||||
"AppId": "",
|
||||
"AppId": "wxa27a3e3cfce7ae19",
|
||||
"MerchantId": "1107066208",
|
||||
"CertificateSerialNumber": "",
|
||||
"CertificateSerialNumber": "3243AE8427384A692FBAA92C5EC5887BEF1988FD",
|
||||
"PrivateKeyPath": "",
|
||||
"PrivateKeyPem": "",
|
||||
"NotifyUrl": "",
|
||||
"ApiV3Key": "",
|
||||
"ApiV3Key": "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMlj1LkO9Cfg3LWBYpe9GBn7vWgLE6kqEG1ohaxbaPxA6OwuGn0XQZfRBbJmncSXGLYahQ7T0OvFBIp8SyYm6q9kol8c9naxd+KxjMrx/qSWqwEJ76meBNK6LBYBVFTobg47cexpyR1TOZK0EFBGJQU2yQ1nsuQczVvq+WaSn4+kVENWf+o2g2nFS1VXNBIjL0/C8vXbz/0Y8k6ecH5mbmy/t+YR6X4TsiIAzIxIcfMMNhVCwqKLsu3D20N0ViYbKToHWIXi8wS8dyruHqQ1lZVJV/fF7pdI36HFI94enksCZrDb1LVFjL+4ccE04MJLIEZSH73RrOFkLaRzn8pwBbAgMBAAECggEAY7kD7baa+XVKMgkg3F2vVJjQzZDzUpKwjQ27b0uaXl95nRrfNZcCGX59n4CM70SZZRBYJAJP1cP",
|
||||
"PlatformPublicKeyPath": "",
|
||||
"PlatformPublicKeyPem": "",
|
||||
"PlatformPublicKeySerial": "",
|
||||
|
||||
@@ -7,4 +7,49 @@ public class Package
|
||||
public decimal Price { get; set; }
|
||||
public int DurationMinutes { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public int FirstSprayWaterTime { get; set; }
|
||||
public int AfterShampoo1SprayTime { get; set; }
|
||||
public int AfterShampoo2SprayTime { get; set; }
|
||||
public int AfterShampoo3SprayTime { get; set; }
|
||||
public int SprayShampoo1Time { get; set; }
|
||||
public int SprayShampoo2Time { get; set; }
|
||||
public int SprayShampoo3Time { get; set; }
|
||||
public int HotAirTime { get; set; }
|
||||
public int ColdAirTime { get; set; }
|
||||
public int UvSterilizationTime { get; set; }
|
||||
|
||||
public string DisplayPrice => Price.ToString("F2");
|
||||
public string DisplayDuration => $"{DurationMinutes} 分钟";
|
||||
public string DisplaySummary => $"{Description} | 总时长 {DurationMinutes} 分钟";
|
||||
public string DisplayTag => Id switch
|
||||
{
|
||||
1 => "小型犬",
|
||||
2 => "中型犬",
|
||||
3 => "大型犬",
|
||||
_ => "宠物套餐"
|
||||
};
|
||||
|
||||
public string CardBackground => Id switch
|
||||
{
|
||||
1 => "#2E7D32",
|
||||
2 => "#1565C0",
|
||||
3 => "#6A1B9A",
|
||||
_ => "#D84315"
|
||||
};
|
||||
|
||||
public string CardDetailBackground => Id switch
|
||||
{
|
||||
1 => "#1B5E20",
|
||||
2 => "#0D47A1",
|
||||
3 => "#4A148C",
|
||||
_ => "#BF360C"
|
||||
};
|
||||
|
||||
public string CardPriceBackground => Id switch
|
||||
{
|
||||
1 => "#43A047",
|
||||
2 => "#1E88E5",
|
||||
3 => "#8E24AA",
|
||||
_ => "#FF6F00"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,4 +27,10 @@
|
||||
<Resource Include="Images\qrcode.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
70
PetWashControl/Services/AdminAccessService.cs
Normal file
70
PetWashControl/Services/AdminAccessService.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace PetWashControl.Services;
|
||||
|
||||
public sealed class AdminAccessService
|
||||
{
|
||||
private readonly ConfigurationService _config;
|
||||
private int _failedAttempts;
|
||||
private DateTimeOffset? _lockedUntil;
|
||||
|
||||
public AdminAccessService(ConfigurationService config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public AdminAuthResult TryAuthenticate(string username, string password)
|
||||
{
|
||||
if (_lockedUntil is { } lockedUntil && lockedUntil > DateTimeOffset.Now)
|
||||
{
|
||||
return AdminAuthResult.Locked(GetLockoutMessage(lockedUntil));
|
||||
}
|
||||
|
||||
if (username == _config.AdminUsername && password == _config.AdminPassword)
|
||||
{
|
||||
_failedAttempts = 0;
|
||||
_lockedUntil = null;
|
||||
return AdminAuthResult.Success();
|
||||
}
|
||||
|
||||
_failedAttempts++;
|
||||
var maxAttempts = Math.Max(1, _config.AdminMaxFailedAttempts);
|
||||
if (_failedAttempts >= maxAttempts)
|
||||
{
|
||||
_failedAttempts = 0;
|
||||
_lockedUntil = DateTimeOffset.Now.AddMinutes(Math.Max(1, _config.AdminLockoutMinutes));
|
||||
return AdminAuthResult.Locked(GetLockoutMessage(_lockedUntil.Value));
|
||||
}
|
||||
|
||||
var remainingAttempts = maxAttempts - _failedAttempts;
|
||||
return AdminAuthResult.Failed($"账号或密码错误,还可尝试 {remainingAttempts} 次。");
|
||||
}
|
||||
|
||||
public string? GetCurrentLockoutMessage()
|
||||
{
|
||||
if (_lockedUntil is not { } lockedUntil || lockedUntil <= DateTimeOffset.Now)
|
||||
{
|
||||
_lockedUntil = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetLockoutMessage(lockedUntil);
|
||||
}
|
||||
|
||||
private static string GetLockoutMessage(DateTimeOffset lockedUntil)
|
||||
{
|
||||
var remaining = lockedUntil - DateTimeOffset.Now;
|
||||
if (remaining < TimeSpan.Zero)
|
||||
{
|
||||
remaining = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
var minutes = Math.Max(0, (int)Math.Ceiling(remaining.TotalMinutes));
|
||||
return $"登录失败次数过多,请 {minutes} 分钟后再试。";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct AdminAuthResult(bool IsSuccess, bool IsLocked, string Message)
|
||||
{
|
||||
public static AdminAuthResult Success() => new(true, false, string.Empty);
|
||||
public static AdminAuthResult Failed(string message) => new(false, false, message);
|
||||
public static AdminAuthResult Locked(string message) => new(false, true, message);
|
||||
}
|
||||
@@ -98,4 +98,30 @@ public class ApiService
|
||||
throw new Exception($"更新订单状态失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
public async Task<Package?> UpdatePackageAsync(Package package)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PutAsJsonAsync($"api/packages/{package.Id}", new
|
||||
{
|
||||
package.Price,
|
||||
package.FirstSprayWaterTime,
|
||||
package.AfterShampoo1SprayTime,
|
||||
package.AfterShampoo2SprayTime,
|
||||
package.AfterShampoo3SprayTime,
|
||||
package.SprayShampoo1Time,
|
||||
package.SprayShampoo2Time,
|
||||
package.SprayShampoo3Time,
|
||||
package.HotAirTime,
|
||||
package.ColdAirTime,
|
||||
package.UvSterilizationTime
|
||||
});
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<Package>();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
throw new Exception($"保存套餐配置失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,176 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PetWashControl.Services;
|
||||
|
||||
public class ConfigurationService
|
||||
{
|
||||
// https://localhost:7203/ 本地
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
private static readonly string ConfigFilePath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||
|
||||
public string ApiBaseUrl { get; set; } = "http://101.132.182.216:8080/";
|
||||
public string MqttBrokerHost { get; set; } = "101.132.182.216";
|
||||
public int MqttBrokerPort { get; set; } = 1883;
|
||||
public string MqttApiKey { get; set; } = "dc240ab5ec";
|
||||
public string MqttId { get; set; } = "13064";
|
||||
public string MqttClientId { get; set; } = "PetWashControl";
|
||||
|
||||
// Modbus TCP 配置
|
||||
public string ModbusIpAddress { get; set; } = "127.0.0.1";
|
||||
public int ModbusPort { get; set; } = 502;
|
||||
public byte ModbusSlaveId { get; set; } = 1;
|
||||
public int ModbusConnectTimeoutMs { get; set; } = 5000;
|
||||
public int ModbusReadTimeoutMs { get; set; } = 3000;
|
||||
|
||||
public int PaymentCheckIntervalSeconds { get; set; } = 2;
|
||||
public int WashSimulationSeconds { get; set; } = 10;
|
||||
|
||||
// 洗护流程时间参数(单位:分钟)
|
||||
public int FirstSprayWaterTime { get; set; } = 2; // 首次喷水时间
|
||||
public int AfterShampoo1SprayTime { get; set; } = 2; // 沐浴1后喷水时间
|
||||
public int AfterShampoo2SprayTime { get; set; } = 2; // 沐浴2后喷水时间
|
||||
public int AfterShampoo3SprayTime { get; set; } = 2; // 沐浴3后喷水时间
|
||||
public int SprayShampoo1Time { get; set; } = 1; // 喷沐浴露1时间
|
||||
public int SprayShampoo2Time { get; set; } = 1; // 喷沐浴露2时间
|
||||
public int SprayShampoo3Time { get; set; } = 1; // 喷沐浴露3时间
|
||||
public int ColdAirTime { get; set; } = 2; // 冷风机时间
|
||||
public int HotAirTime { get; set; } = 5; // 热风机时间
|
||||
public int UvSterilizationTime { get; set; } = 3; // 紫外线杀菌时间
|
||||
public int FirstSprayWaterTime { get; set; } = 2;
|
||||
public int AfterShampoo1SprayTime { get; set; } = 2;
|
||||
public int AfterShampoo2SprayTime { get; set; } = 2;
|
||||
public int AfterShampoo3SprayTime { get; set; } = 2;
|
||||
public int SprayShampoo1Time { get; set; } = 1;
|
||||
public int SprayShampoo2Time { get; set; } = 1;
|
||||
public int SprayShampoo3Time { get; set; } = 1;
|
||||
public int ColdAirTime { get; set; } = 2;
|
||||
public int HotAirTime { get; set; } = 5;
|
||||
public int UvSterilizationTime { get; set; } = 3;
|
||||
public string AdminUsername { get; set; } = "admin";
|
||||
public string AdminPassword { get; set; } = "123456";
|
||||
public int AdminMaxFailedAttempts { get; set; } = 5;
|
||||
public int AdminLockoutMinutes { get; set; } = 15;
|
||||
|
||||
public ConfigurationService()
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var fileContent = JsonSerializer.Serialize(ToModel(), JsonOptions);
|
||||
File.WriteAllText(ConfigFilePath, fileContent);
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
if (!File.Exists(ConfigFilePath))
|
||||
{
|
||||
Save();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fileContent = File.ReadAllText(ConfigFilePath);
|
||||
var model = JsonSerializer.Deserialize<ConfigurationModel>(fileContent, JsonOptions);
|
||||
if (model == null)
|
||||
{
|
||||
Save();
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyModel(model);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyModel(ConfigurationModel model)
|
||||
{
|
||||
ApiBaseUrl = model.ApiBaseUrl;
|
||||
MqttBrokerHost = model.MqttBrokerHost;
|
||||
MqttBrokerPort = model.MqttBrokerPort;
|
||||
MqttApiKey = model.MqttApiKey;
|
||||
MqttId = model.MqttId;
|
||||
MqttClientId = model.MqttClientId;
|
||||
ModbusIpAddress = model.ModbusIpAddress;
|
||||
ModbusPort = model.ModbusPort;
|
||||
ModbusSlaveId = model.ModbusSlaveId;
|
||||
ModbusConnectTimeoutMs = model.ModbusConnectTimeoutMs;
|
||||
ModbusReadTimeoutMs = model.ModbusReadTimeoutMs;
|
||||
PaymentCheckIntervalSeconds = model.PaymentCheckIntervalSeconds;
|
||||
WashSimulationSeconds = model.WashSimulationSeconds;
|
||||
FirstSprayWaterTime = model.FirstSprayWaterTime;
|
||||
AfterShampoo1SprayTime = model.AfterShampoo1SprayTime;
|
||||
AfterShampoo2SprayTime = model.AfterShampoo2SprayTime;
|
||||
AfterShampoo3SprayTime = model.AfterShampoo3SprayTime;
|
||||
SprayShampoo1Time = model.SprayShampoo1Time;
|
||||
SprayShampoo2Time = model.SprayShampoo2Time;
|
||||
SprayShampoo3Time = model.SprayShampoo3Time;
|
||||
ColdAirTime = model.ColdAirTime;
|
||||
HotAirTime = model.HotAirTime;
|
||||
UvSterilizationTime = model.UvSterilizationTime;
|
||||
AdminUsername = model.AdminUsername;
|
||||
AdminPassword = model.AdminPassword;
|
||||
AdminMaxFailedAttempts = Math.Max(1, model.AdminMaxFailedAttempts);
|
||||
AdminLockoutMinutes = Math.Max(1, model.AdminLockoutMinutes);
|
||||
}
|
||||
|
||||
private ConfigurationModel ToModel()
|
||||
{
|
||||
return new ConfigurationModel
|
||||
{
|
||||
ApiBaseUrl = ApiBaseUrl,
|
||||
MqttBrokerHost = MqttBrokerHost,
|
||||
MqttBrokerPort = MqttBrokerPort,
|
||||
MqttApiKey = MqttApiKey,
|
||||
MqttId = MqttId,
|
||||
MqttClientId = MqttClientId,
|
||||
ModbusIpAddress = ModbusIpAddress,
|
||||
ModbusPort = ModbusPort,
|
||||
ModbusSlaveId = ModbusSlaveId,
|
||||
ModbusConnectTimeoutMs = ModbusConnectTimeoutMs,
|
||||
ModbusReadTimeoutMs = ModbusReadTimeoutMs,
|
||||
PaymentCheckIntervalSeconds = PaymentCheckIntervalSeconds,
|
||||
WashSimulationSeconds = WashSimulationSeconds,
|
||||
FirstSprayWaterTime = FirstSprayWaterTime,
|
||||
AfterShampoo1SprayTime = AfterShampoo1SprayTime,
|
||||
AfterShampoo2SprayTime = AfterShampoo2SprayTime,
|
||||
AfterShampoo3SprayTime = AfterShampoo3SprayTime,
|
||||
SprayShampoo1Time = SprayShampoo1Time,
|
||||
SprayShampoo2Time = SprayShampoo2Time,
|
||||
SprayShampoo3Time = SprayShampoo3Time,
|
||||
ColdAirTime = ColdAirTime,
|
||||
HotAirTime = HotAirTime,
|
||||
UvSterilizationTime = UvSterilizationTime,
|
||||
AdminUsername = AdminUsername,
|
||||
AdminPassword = AdminPassword,
|
||||
AdminMaxFailedAttempts = Math.Max(1, AdminMaxFailedAttempts),
|
||||
AdminLockoutMinutes = Math.Max(1, AdminLockoutMinutes)
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class ConfigurationModel
|
||||
{
|
||||
public string ApiBaseUrl { get; set; } = "http://101.132.182.216:8080/";
|
||||
public string MqttBrokerHost { get; set; } = "101.132.182.216";
|
||||
public int MqttBrokerPort { get; set; } = 1883;
|
||||
public string MqttApiKey { get; set; } = "dc240ab5ec";
|
||||
public string MqttId { get; set; } = "13064";
|
||||
public string MqttClientId { get; set; } = "PetWashControl";
|
||||
public string ModbusIpAddress { get; set; } = "127.0.0.1";
|
||||
public int ModbusPort { get; set; } = 502;
|
||||
public byte ModbusSlaveId { get; set; } = 1;
|
||||
public int ModbusConnectTimeoutMs { get; set; } = 5000;
|
||||
public int ModbusReadTimeoutMs { get; set; } = 3000;
|
||||
public int PaymentCheckIntervalSeconds { get; set; } = 2;
|
||||
public int WashSimulationSeconds { get; set; } = 10;
|
||||
public int FirstSprayWaterTime { get; set; } = 2;
|
||||
public int AfterShampoo1SprayTime { get; set; } = 2;
|
||||
public int AfterShampoo2SprayTime { get; set; } = 2;
|
||||
public int AfterShampoo3SprayTime { get; set; } = 2;
|
||||
public int SprayShampoo1Time { get; set; } = 1;
|
||||
public int SprayShampoo2Time { get; set; } = 1;
|
||||
public int SprayShampoo3Time { get; set; } = 1;
|
||||
public int ColdAirTime { get; set; } = 2;
|
||||
public int HotAirTime { get; set; } = 5;
|
||||
public int UvSterilizationTime { get; set; } = 3;
|
||||
public string AdminUsername { get; set; } = "admin";
|
||||
public string AdminPassword { get; set; } = "123456";
|
||||
public int AdminMaxFailedAttempts { get; set; } = 5;
|
||||
public int AdminLockoutMinutes { get; set; } = 15;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
// 根据不同步骤模拟温度变化
|
||||
|
||||
97
PetWashControl/Views/AdminLoginWindow.xaml
Normal file
97
PetWashControl/Views/AdminLoginWindow.xaml
Normal file
@@ -0,0 +1,97 @@
|
||||
<Window x:Class="PetWashControl.Views.AdminLoginWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="管理员验证"
|
||||
Width="420"
|
||||
Height="340"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
Background="#F5F7FA">
|
||||
<Grid Margin="28">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="系统设置"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#1F2937"/>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Margin="0,8,0,0"
|
||||
Text="请输入管理员账号和密码"
|
||||
FontSize="15"
|
||||
Foreground="#6B7280"/>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Margin="0,28,0,0">
|
||||
<TextBlock Text="管理员账号"
|
||||
FontSize="14"
|
||||
Foreground="#374151"/>
|
||||
<TextBox x:Name="UsernameTextBox"
|
||||
Margin="0,8,0,0"
|
||||
Height="42"
|
||||
Padding="10"
|
||||
FontSize="16"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="3"
|
||||
Margin="0,18,0,0">
|
||||
<TextBlock Text="管理员密码"
|
||||
FontSize="14"
|
||||
Foreground="#374151"/>
|
||||
<PasswordBox x:Name="PasswordInput"
|
||||
Margin="0,8,0,0"
|
||||
Height="42"
|
||||
Padding="10"
|
||||
FontSize="16"
|
||||
KeyDown="PasswordInput_KeyDown"/>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="4"
|
||||
x:Name="ErrorContainer"
|
||||
Margin="0,18,0,0"
|
||||
Background="#FEF2F2"
|
||||
CornerRadius="12"
|
||||
Padding="12"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock x:Name="ErrorTextBlock"
|
||||
Text="账号或密码错误"
|
||||
Foreground="#B91C1C"
|
||||
FontSize="14"/>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="5"
|
||||
Margin="0,24,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Content="取消"
|
||||
Height="44"
|
||||
Background="#CBD5E1"
|
||||
Foreground="#0F172A"
|
||||
BorderThickness="0"
|
||||
IsCancel="True"
|
||||
Click="CancelButton_Click"/>
|
||||
|
||||
<Button Grid.Column="3"
|
||||
Content="确定"
|
||||
Height="44"
|
||||
Background="#1976D2"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
IsDefault="True"
|
||||
Click="LoginButton_Click"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
69
PetWashControl/Views/AdminLoginWindow.xaml.cs
Normal file
69
PetWashControl/Views/AdminLoginWindow.xaml.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using PetWashControl.Services;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace PetWashControl.Views;
|
||||
|
||||
public partial class AdminLoginWindow : Window
|
||||
{
|
||||
private readonly AdminAccessService _adminAccessService;
|
||||
|
||||
public bool IsAuthenticated { get; private set; }
|
||||
|
||||
public AdminLoginWindow(AdminAccessService adminAccessService)
|
||||
{
|
||||
_adminAccessService = adminAccessService;
|
||||
InitializeComponent();
|
||||
Loaded += (_, _) =>
|
||||
{
|
||||
UsernameTextBox.Focus();
|
||||
ShowMessage(_adminAccessService.GetCurrentLockoutMessage());
|
||||
};
|
||||
}
|
||||
|
||||
private void LoginButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var result = _adminAccessService.TryAuthenticate(UsernameTextBox.Text, PasswordInput.Password);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
IsAuthenticated = true;
|
||||
DialogResult = true;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
IsAuthenticated = false;
|
||||
ShowMessage(result.Message);
|
||||
PasswordInput.Clear();
|
||||
PasswordInput.Focus();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void PasswordInput_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
LoginButton_Click(sender, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowMessage(string? message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
ErrorContainer.Visibility = Visibility.Collapsed;
|
||||
ErrorTextBlock.Visibility = Visibility.Collapsed;
|
||||
ErrorTextBlock.Text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorContainer.Visibility = Visibility.Visible;
|
||||
ErrorTextBlock.Visibility = Visibility.Visible;
|
||||
ErrorTextBlock.Text = message;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Cursor="Hand"
|
||||
Command="{Binding ShowSettingsCommand}">
|
||||
Command="{Binding ShowSettingsCommand}" Visibility="Collapsed">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
@@ -451,6 +451,35 @@
|
||||
</TextBlock>
|
||||
|
||||
<!-- 警告提示 -->
|
||||
<Border Background="#E8F5E9"
|
||||
CornerRadius="18"
|
||||
Padding="18,14"
|
||||
Margin="0,0,0,20">
|
||||
<StackPanel>
|
||||
<TextBlock Text="本次洗护套餐"
|
||||
FontSize="15"
|
||||
Foreground="#2E7D32"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,6"/>
|
||||
<TextBlock Text="{Binding SelectedPackage.Name}"
|
||||
FontSize="22"
|
||||
FontWeight="Bold"
|
||||
Foreground="#1B5E20"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,6"/>
|
||||
<TextBlock Text="{Binding SelectedPackage.DisplaySummary}"
|
||||
FontSize="14"
|
||||
Foreground="#33691E"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
Margin="0,0,0,4"/>
|
||||
<TextBlock HorizontalAlignment="Center">
|
||||
<Run Text="支付金额 ¥" Foreground="#2E7D32" FontSize="14"/>
|
||||
<Run Text="{Binding SelectedPackage.DisplayPrice}" Foreground="#2E7D32" FontSize="16" FontWeight="Bold"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="#FFF3E0"
|
||||
CornerRadius="18"
|
||||
Padding="20,15"
|
||||
@@ -567,7 +596,7 @@
|
||||
Height="70"
|
||||
Margin="5"
|
||||
Style="{StaticResource RoundButton}"
|
||||
Command="{Binding ShowSettingsCommand}"/>
|
||||
Command="{Binding ShowSettingsCommand}" Visibility="Collapsed"/>
|
||||
</UniformGrid>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -649,6 +678,7 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Style="{StaticResource PackageCard}"
|
||||
Background="{Binding CardBackground}"
|
||||
MouseLeftButtonDown="Package_Click">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -667,8 +697,20 @@
|
||||
Margin="0,0,0,15"/>
|
||||
|
||||
<!-- 适用犬型和时长 -->
|
||||
<Border Grid.Row="0"
|
||||
Background="#33FFFFFF"
|
||||
CornerRadius="10"
|
||||
Padding="10,4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top">
|
||||
<TextBlock Text="{Binding DisplayTag}"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Background="#BF360C"
|
||||
Background="{Binding CardDetailBackground}"
|
||||
CornerRadius="10"
|
||||
Padding="15,10"
|
||||
Margin="0,0,0,15">
|
||||
@@ -693,13 +735,13 @@
|
||||
|
||||
<!-- 价格 -->
|
||||
<Border Grid.Row="2"
|
||||
Background="#FF6F00"
|
||||
Background="{Binding CardPriceBackground}"
|
||||
CornerRadius="15"
|
||||
Padding="20,12">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Foreground="White">
|
||||
<Run Text="¥" FontSize="28" FontWeight="Bold"/>
|
||||
<Run Text="{Binding Price}" FontSize="42" FontWeight="Bold"/>
|
||||
<Run Text="{Binding DisplayPrice}" FontSize="42" FontWeight="Bold"/>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -786,7 +828,14 @@
|
||||
Foreground="#2E7D32"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,12"/>
|
||||
|
||||
|
||||
<TextBlock Text="{Binding SelectedPackage.DisplaySummary}"
|
||||
FontSize="15"
|
||||
Foreground="#5D4037"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
Margin="0,0,0,16"/>
|
||||
|
||||
<!-- 支付金额 -->
|
||||
<Border Background="#FFE0B2"
|
||||
CornerRadius="15"
|
||||
@@ -803,7 +852,7 @@
|
||||
Foreground="#FF6F00"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"/>
|
||||
<Run Text="{Binding SelectedPackage.Price}"
|
||||
<Run Text="{Binding SelectedPackage.DisplayPrice}"
|
||||
Foreground="#FF6F00"
|
||||
FontSize="44"
|
||||
FontWeight="Bold"/>
|
||||
@@ -856,6 +905,8 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="15"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="15"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0"
|
||||
@@ -881,6 +932,28 @@
|
||||
</Button>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Content="刷新二维码"
|
||||
Height="50"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Background="#1976D2"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Cursor="Hand"
|
||||
Command="{Binding RegeneratePaymentQrCommand}">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="12">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="6" ShadowDepth="2" Opacity="0.3"/>
|
||||
</Border.Effect>
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
|
||||
<Button Grid.Column="4"
|
||||
Content="取消支付"
|
||||
Height="50"
|
||||
FontSize="16"
|
||||
@@ -948,7 +1021,7 @@
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Cursor="Hand"
|
||||
Command="{Binding ShowSettingsCommand}">
|
||||
Command="{Binding ShowSettingsCommand}" Visibility="Collapsed">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
@@ -1986,6 +2059,29 @@
|
||||
</Grid>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<Button Content="保存设置"
|
||||
FontSize="24"
|
||||
FontWeight="Bold"
|
||||
Height="60"
|
||||
Background="#4CAF50"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Margin="0,20,0,0"
|
||||
Cursor="Hand"
|
||||
Command="{Binding OpenPackageManagementCommand}">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="8" ShadowDepth="3" Opacity="0.3"/>
|
||||
</Border.Effect>
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
|
||||
<Button Content="保存设置"
|
||||
FontSize="24"
|
||||
FontWeight="Bold"
|
||||
@@ -2016,6 +2112,13 @@
|
||||
</Grid>
|
||||
|
||||
<!-- 使用说明弹窗 -->
|
||||
<Border Width="120"
|
||||
Height="120"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
MouseLeftButtonDown="AdminHotspot_MouseLeftButtonDown"/>
|
||||
|
||||
<Grid Visibility="{Binding IsInstructionDialogOpen, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Background="#80000000">
|
||||
<Border Background="White"
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
using PetWashControl.ViewModels;
|
||||
using PetWashControl.Models;
|
||||
using PetWashControl.Services;
|
||||
using PetWashControl.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace PetWashControl.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly MainViewModel _viewModel;
|
||||
private readonly AdminAccessService _adminAccessService;
|
||||
private PackageManagementWindow? _packageManagementWindow;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_adminAccessService = new AdminAccessService(new ConfigurationService());
|
||||
_viewModel = new MainViewModel();
|
||||
_viewModel.ViewChanged += OnViewChanged;
|
||||
_viewModel.OpenPackageManagementRequested += OnOpenPackageManagementRequested;
|
||||
DataContext = _viewModel;
|
||||
Loaded += MainWindow_Loaded;
|
||||
}
|
||||
@@ -21,18 +28,17 @@ public partial class MainWindow : Window
|
||||
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _viewModel.InitializeAsync();
|
||||
ConfigureSettingsButtons();
|
||||
}
|
||||
|
||||
private void OnViewChanged(string viewName)
|
||||
{
|
||||
// 隐藏所有视图
|
||||
IdleView.Visibility = Visibility.Collapsed;
|
||||
PaymentView.Visibility = Visibility.Collapsed;
|
||||
QRCodeView.Visibility = Visibility.Collapsed;
|
||||
WashingView.Visibility = Visibility.Collapsed;
|
||||
SettingsView.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 显示指定视图
|
||||
switch (viewName)
|
||||
{
|
||||
case "Idle":
|
||||
@@ -60,4 +66,73 @@ public partial class MainWindow : Window
|
||||
_viewModel.SelectPackageCommand.Execute(package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AdminHotspot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var loginWindow = new AdminLoginWindow(_adminAccessService)
|
||||
{
|
||||
Owner = this
|
||||
};
|
||||
|
||||
var dialogResult = loginWindow.ShowDialog();
|
||||
if (dialogResult == true && loginWindow.IsAuthenticated)
|
||||
{
|
||||
_viewModel.ShowSettingsFromAdmin();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenPackageManagementRequested()
|
||||
{
|
||||
if (_packageManagementWindow?.IsVisible == true)
|
||||
{
|
||||
_packageManagementWindow.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
_packageManagementWindow = new PackageManagementWindow(_viewModel)
|
||||
{
|
||||
Owner = this
|
||||
};
|
||||
_packageManagementWindow.Closed += (_, _) => _packageManagementWindow = null;
|
||||
_packageManagementWindow.ShowDialog();
|
||||
}
|
||||
|
||||
private void ConfigureSettingsButtons()
|
||||
{
|
||||
foreach (var button in FindVisualChildren<Button>(SettingsView))
|
||||
{
|
||||
if (ReferenceEquals(button.Command, _viewModel.OpenPackageManagementCommand))
|
||||
{
|
||||
button.Content = "套餐管理";
|
||||
button.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1976D2"));
|
||||
}
|
||||
else if (ReferenceEquals(button.Command, _viewModel.SaveSettingsCommand))
|
||||
{
|
||||
button.Content = "保存设置";
|
||||
button.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject root) where T : DependencyObject
|
||||
{
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(root, i);
|
||||
if (child is T typedChild)
|
||||
{
|
||||
yield return typedChild;
|
||||
}
|
||||
|
||||
foreach (var nestedChild in FindVisualChildren<T>(child))
|
||||
{
|
||||
yield return nestedChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
350
PetWashControl/Views/PackageManagementWindow.xaml
Normal file
350
PetWashControl/Views/PackageManagementWindow.xaml
Normal file
@@ -0,0 +1,350 @@
|
||||
<Window x:Class="PetWashControl.Views.PackageManagementWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="套餐管理"
|
||||
Width="920"
|
||||
Height="780"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="CanMinimize"
|
||||
Background="#F3F6FB">
|
||||
<Grid Margin="24">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0"
|
||||
Background="#1976D2"
|
||||
CornerRadius="20"
|
||||
Padding="24"
|
||||
Margin="0,0,0,20">
|
||||
<StackPanel>
|
||||
<TextBlock Text="套餐管理"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
Foreground="White"/>
|
||||
<TextBlock Text="维护三套犬型套餐的金额和每个洗护流程时间,保存后前台下次选中套餐会自动写入寄存器。"
|
||||
Margin="0,8,0,0"
|
||||
FontSize="14"
|
||||
Foreground="#DCEBFF"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<Border Background="White"
|
||||
CornerRadius="18"
|
||||
Padding="24"
|
||||
Margin="0,0,0,20">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="20"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="选择套餐"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#1F2937"
|
||||
Margin="0,0,0,10"/>
|
||||
<ComboBox ItemsSource="{Binding Packages}"
|
||||
SelectedItem="{Binding EditingPackage}"
|
||||
DisplayMemberPath="Name"
|
||||
FontSize="16"
|
||||
Padding="10"
|
||||
Height="42"/>
|
||||
|
||||
<Grid Margin="0,20,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0"
|
||||
Background="#EEF7FF"
|
||||
CornerRadius="14"
|
||||
Padding="18">
|
||||
<StackPanel>
|
||||
<TextBlock Text="套餐金额"
|
||||
FontSize="14"
|
||||
Foreground="#4B5563"/>
|
||||
<TextBox Text="{Binding EditingPackagePrice, UpdateSourceTrigger=LostFocus, StringFormat=F2}"
|
||||
Margin="0,10,0,0"
|
||||
Padding="10"
|
||||
FontSize="22"
|
||||
FontWeight="Bold"
|
||||
BorderBrush="#1976D2"
|
||||
BorderThickness="2"/>
|
||||
<TextBlock Text="单位:元"
|
||||
Margin="0,10,0,0"
|
||||
FontSize="13"
|
||||
Foreground="#6B7280"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="2"
|
||||
Background="#F0FDF4"
|
||||
CornerRadius="14"
|
||||
Padding="18">
|
||||
<StackPanel>
|
||||
<TextBlock Text="套餐总时长"
|
||||
FontSize="14"
|
||||
Foreground="#4B5563"/>
|
||||
<TextBlock Text="{Binding EditingPackageTotalDuration, StringFormat={}{0} 分钟}"
|
||||
Margin="0,12,0,0"
|
||||
FontSize="22"
|
||||
FontWeight="Bold"
|
||||
Foreground="#15803D"/>
|
||||
<TextBlock Text="保存时自动同步到套餐总时长"
|
||||
Margin="0,10,0,0"
|
||||
FontSize="13"
|
||||
Foreground="#6B7280"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Column="2"
|
||||
Background="#FFF7ED"
|
||||
CornerRadius="14"
|
||||
Padding="18">
|
||||
<StackPanel>
|
||||
<TextBlock Text="保存说明"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#9A3412"/>
|
||||
<TextBlock Text="1. 当前有未支付订单时禁止修改。"
|
||||
Margin="0,14,0,0"
|
||||
FontSize="14"
|
||||
Foreground="#7C2D12"/>
|
||||
<TextBlock Text="2. 总时长由各流程分钟数自动累加。"
|
||||
Margin="0,8,0,0"
|
||||
FontSize="14"
|
||||
Foreground="#7C2D12"/>
|
||||
<TextBlock Text="3. 若当前已选中这个套餐,保存后会立即重写当前设备流程参数。"
|
||||
Margin="0,8,0,0"
|
||||
FontSize="14"
|
||||
Foreground="#7C2D12"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Background="White"
|
||||
CornerRadius="18"
|
||||
Padding="24">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="24"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="清洗流程"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
Foreground="#1F2937"
|
||||
Margin="0,0,0,14"/>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="首次冲水"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageFirstSprayWaterTime, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="沐浴1后冲水"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageAfterShampoo1SprayTime, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="沐浴2后冲水"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageAfterShampoo2SprayTime, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="沐浴3后冲水"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageAfterShampoo3SprayTime, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="沐浴液 1"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageSprayShampoo1Time, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="烘干与护理"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
Foreground="#1F2937"
|
||||
Margin="0,0,0,14"/>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="沐浴液 2"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageSprayShampoo2Time, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="护理液"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageSprayShampoo3Time, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="热风"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageHotAirTime, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="冷风"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageColdAirTime, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="紫外杀菌"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#374151"/>
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding EditingPackageUvSterilizationTime, UpdateSourceTrigger=LostFocus}"
|
||||
Padding="10"
|
||||
FontSize="15"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid Grid.Row="2"
|
||||
Margin="0,20,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="160"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="160"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Content="关闭"
|
||||
Height="52"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Background="#CBD5E1"
|
||||
Foreground="#0F172A"
|
||||
BorderThickness="0"
|
||||
Click="CloseButton_Click"/>
|
||||
|
||||
<Button Grid.Column="3"
|
||||
Content="保存套餐"
|
||||
Height="52"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Background="#16A34A"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Command="{Binding SavePackageConfigCommand}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
18
PetWashControl/Views/PackageManagementWindow.xaml.cs
Normal file
18
PetWashControl/Views/PackageManagementWindow.xaml.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using PetWashControl.ViewModels;
|
||||
using System.Windows;
|
||||
|
||||
namespace PetWashControl.Views;
|
||||
|
||||
public partial class PackageManagementWindow : Window
|
||||
{
|
||||
public PackageManagementWindow(MainViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
29
PetWashControl/appsettings.json
Normal file
29
PetWashControl/appsettings.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"apiBaseUrl": "http://101.132.182.216:8080/",
|
||||
"mqttBrokerHost": "101.132.182.216",
|
||||
"mqttBrokerPort": 1883,
|
||||
"mqttApiKey": "dc240ab5ec",
|
||||
"mqttId": "13064",
|
||||
"mqttClientId": "PetWashControl",
|
||||
"modbusIpAddress": "127.0.0.1",
|
||||
"modbusPort": 502,
|
||||
"modbusSlaveId": 1,
|
||||
"modbusConnectTimeoutMs": 5000,
|
||||
"modbusReadTimeoutMs": 3000,
|
||||
"paymentCheckIntervalSeconds": 2,
|
||||
"washSimulationSeconds": 10,
|
||||
"firstSprayWaterTime": 2,
|
||||
"afterShampoo1SprayTime": 2,
|
||||
"afterShampoo2SprayTime": 2,
|
||||
"afterShampoo3SprayTime": 2,
|
||||
"sprayShampoo1Time": 1,
|
||||
"sprayShampoo2Time": 1,
|
||||
"sprayShampoo3Time": 1,
|
||||
"coldAirTime": 2,
|
||||
"hotAirTime": 5,
|
||||
"uvSterilizationTime": 3,
|
||||
"adminUsername": "admin",
|
||||
"adminPassword": "123456",
|
||||
"adminMaxFailedAttempts": 5,
|
||||
"adminLockoutMinutes": 15
|
||||
}
|
||||
@@ -1,24 +1,29 @@
|
||||
-- PetWash 数据库初始化脚本
|
||||
-- PetWash database initialization script
|
||||
|
||||
-- 删除已存在的数据库(如果存在)
|
||||
DROP DATABASE IF EXISTS petwash;
|
||||
|
||||
-- 创建数据库
|
||||
CREATE DATABASE petwash CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 使用数据库
|
||||
USE petwash;
|
||||
|
||||
-- 创建套餐表
|
||||
CREATE TABLE Packages (
|
||||
Id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
Id INT PRIMARY KEY,
|
||||
Name VARCHAR(100) NOT NULL,
|
||||
Price DECIMAL(10, 2) NOT NULL,
|
||||
DurationMinutes INT NOT NULL,
|
||||
Description VARCHAR(500)
|
||||
Description VARCHAR(500),
|
||||
FirstSprayWaterTime INT NOT NULL,
|
||||
AfterShampoo1SprayTime INT NOT NULL,
|
||||
AfterShampoo2SprayTime INT NOT NULL,
|
||||
AfterShampoo3SprayTime INT NOT NULL,
|
||||
SprayShampoo1Time INT NOT NULL,
|
||||
SprayShampoo2Time INT NOT NULL,
|
||||
SprayShampoo3Time INT NOT NULL,
|
||||
HotAirTime INT NOT NULL,
|
||||
ColdAirTime INT NOT NULL,
|
||||
UvSterilizationTime INT NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- 创建订单表
|
||||
CREATE TABLE Orders (
|
||||
Id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
PackageId INT NOT NULL,
|
||||
@@ -31,12 +36,26 @@ CREATE TABLE Orders (
|
||||
FOREIGN KEY (PackageId) REFERENCES Packages(Id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- 插入初始套餐数据
|
||||
INSERT INTO Packages (Id, Name, Price, DurationMinutes, Description) VALUES
|
||||
(1, '套餐1', 50.00, 38, '适用于小型犬'),
|
||||
(2, '套餐2', 80.00, 48, '适用于中型犬'),
|
||||
(3, '套餐3', 120.00, 60, '适用于大型犬');
|
||||
INSERT INTO Packages (
|
||||
Id,
|
||||
Name,
|
||||
Price,
|
||||
DurationMinutes,
|
||||
Description,
|
||||
FirstSprayWaterTime,
|
||||
AfterShampoo1SprayTime,
|
||||
AfterShampoo2SprayTime,
|
||||
AfterShampoo3SprayTime,
|
||||
SprayShampoo1Time,
|
||||
SprayShampoo2Time,
|
||||
SprayShampoo3Time,
|
||||
HotAirTime,
|
||||
ColdAirTime,
|
||||
UvSterilizationTime
|
||||
) VALUES
|
||||
(1, '小型犬套餐', 0.01, 20, '适用于小型犬,洗护流程较短', 2, 2, 2, 2, 1, 1, 1, 5, 2, 2),
|
||||
(2, '中型犬套餐', 0.02, 26, '适用于中型犬,洗护流程适中', 3, 3, 3, 3, 1, 1, 1, 6, 3, 2),
|
||||
(3, '大型犬套餐', 0.03, 37, '适用于大型犬,洗护流程更长', 4, 4, 4, 4, 2, 2, 2, 8, 4, 3);
|
||||
|
||||
-- 显示创建结果
|
||||
SELECT '数据库初始化完成' AS Status;
|
||||
SELECT * FROM Packages;
|
||||
|
||||
Reference in New Issue
Block a user