添加项目文件。
15
FootwearTest/App.axaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="FootwearTest.App"
|
||||
xmlns:local="using:FootwearTest"
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
61
FootwearTest/App.axaml.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FootwearTest.Services;
|
||||
using FootwearTest.ViewModels;
|
||||
using FootwearTest.Views;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FootwearTest
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
private ServiceProvider? _serviceProvider;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
_serviceProvider = ConfigureServices();
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>(),
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private static ServiceProvider ConfigureServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<DeviceSettings>();
|
||||
services.AddSingleton<TestFormulaService>();
|
||||
services.AddSingleton<TestRunRepository>();
|
||||
services.AddSingleton<ReportService>();
|
||||
services.AddSingleton<ExcelReportService>();
|
||||
services.AddSingleton<IDeviceClient>(provider =>
|
||||
{
|
||||
var settings = provider.GetRequiredService<DeviceSettings>();
|
||||
return settings.UseSimulator
|
||||
? new SimulatedDeviceClient(settings)
|
||||
: new ModbusTcpDeviceClient(settings);
|
||||
});
|
||||
|
||||
services.AddSingleton<DashboardViewModel>();
|
||||
services.AddSingleton<MethodAViewModel>();
|
||||
services.AddSingleton<MethodBViewModel>();
|
||||
services.AddSingleton<HistoryViewModel>();
|
||||
services.AddSingleton<ReportViewModel>();
|
||||
services.AddSingleton<SettingsViewModel>();
|
||||
services.AddSingleton<MainWindowViewModel>();
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
FootwearTest/Assets/avalonia-logo.ico
Normal file
|
After Width: | Height: | Size: 172 KiB |
28
FootwearTest/FootwearTest.csproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\" />
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="12.0.3" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="12.0.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.3" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.3" />
|
||||
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.1">
|
||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
34
FootwearTest/Models/DeviceSnapshot.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace FootwearTest.Models;
|
||||
|
||||
public sealed record DeviceSnapshot(
|
||||
DateTime Timestamp,
|
||||
double FootTemperatureC,
|
||||
double EnvironmentTemperatureC,
|
||||
double EnvironmentHumidityPercent,
|
||||
double AirSpeedMetersPerSecond,
|
||||
double PowerWatts,
|
||||
double EnergyKilojoules,
|
||||
double WaterLossGramsPerHour,
|
||||
double PumpSpeedCubicCentimetersPerHour,
|
||||
bool PumpRunning,
|
||||
bool FanRunning,
|
||||
bool HeaterRunning,
|
||||
string AlarmText)
|
||||
{
|
||||
public static DeviceSnapshot Initial { get; } = new(
|
||||
DateTime.Now,
|
||||
35.0,
|
||||
23.0,
|
||||
50.0,
|
||||
1.10,
|
||||
4.5,
|
||||
0.0,
|
||||
5.0,
|
||||
5.0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"无报警");
|
||||
}
|
||||
70
FootwearTest/Models/TestRecords.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
|
||||
namespace FootwearTest.Models;
|
||||
|
||||
public sealed record TestRunRecord(
|
||||
long Id,
|
||||
string Method,
|
||||
string SampleDescription,
|
||||
string Conditions,
|
||||
string ResultSummary,
|
||||
string DataJson,
|
||||
bool IsValid,
|
||||
DateTime CreatedAt);
|
||||
|
||||
public sealed record TestRunSummary(
|
||||
long Id,
|
||||
string Method,
|
||||
string SampleDescription,
|
||||
string ResultSummary,
|
||||
bool IsValid,
|
||||
DateTime CreatedAt);
|
||||
|
||||
public sealed record MethodASampleRecord(
|
||||
int Index,
|
||||
DateTime Timestamp,
|
||||
double FootTemperatureC,
|
||||
double EnvironmentTemperatureC,
|
||||
double RelativeHumidityPercent,
|
||||
double WaterLossGramsPerHour,
|
||||
double PowerWatts,
|
||||
double MoistureHeatWatts,
|
||||
double DryHeatWatts,
|
||||
double MoistureResistance,
|
||||
double ThermalResistance);
|
||||
|
||||
public sealed record MethodAResult(
|
||||
string Stage,
|
||||
double SkinMoistureResistance,
|
||||
double AverageMoistureResistance,
|
||||
double AverageThermalResistance,
|
||||
double MoistureCoefficientOfVariation,
|
||||
double ThermalCoefficientOfVariation,
|
||||
bool PassedDeviationCheck);
|
||||
|
||||
public sealed record MethodBMoistureResult(
|
||||
double M1,
|
||||
double M2,
|
||||
double M3,
|
||||
double M4,
|
||||
double M5,
|
||||
double M6,
|
||||
double M7,
|
||||
double M8,
|
||||
double M180,
|
||||
double T180,
|
||||
double M1Corrected,
|
||||
double M2Corrected,
|
||||
double M3Corrected,
|
||||
double M4Corrected,
|
||||
double T180Corrected,
|
||||
double XT180Corrected,
|
||||
bool IsValid);
|
||||
|
||||
public sealed record MethodBWarmthResult(
|
||||
double EnergyKilojoules,
|
||||
double Seconds,
|
||||
double HeatWatts,
|
||||
double FootTemperatureC,
|
||||
double ChamberTemperatureC,
|
||||
double ThermalResistance);
|
||||
25
FootwearTest/Program.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
|
||||
namespace FootwearTest
|
||||
{
|
||||
internal sealed class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
#if DEBUG
|
||||
.WithDeveloperTools()
|
||||
#endif
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
}
|
||||
19
FootwearTest/Services/DeviceSettings.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public sealed class DeviceSettings
|
||||
{
|
||||
public string Host { get; set; } = "127.0.0.1";
|
||||
public int Port { get; set; } = 502;
|
||||
public bool UseSimulator { get; set; } = true;
|
||||
public int PollIntervalMilliseconds { get; set; } = 1000;
|
||||
public double FootAreaSquareMeters { get; set; } = 0.055;
|
||||
public double MethodATargetTemperatureC { get; set; } = 35.0;
|
||||
public double MethodBMoistureTargetTemperatureC { get; set; } = 35.0;
|
||||
public double MethodBWarmthTargetTemperatureC { get; set; } = 38.0;
|
||||
public double EnvironmentTemperatureC { get; set; } = 23.0;
|
||||
public double EnvironmentHumidityPercent { get; set; } = 50.0;
|
||||
public double MethodAAirSpeedMetersPerSecond { get; set; } = 1.00;
|
||||
public double MethodBAirSpeedMetersPerSecond { get; set; } = 1.10;
|
||||
public double PumpSpeedCubicCentimetersPerHour { get; set; } = 5.0;
|
||||
public double CoefficientOfVariationLimitPercent { get; set; } = 8.0;
|
||||
}
|
||||
491
FootwearTest/Services/ExcelReportService.cs
Normal file
@@ -0,0 +1,491 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using FootwearTest.Models;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public sealed class ExcelReportService
|
||||
{
|
||||
public async Task<string> ExportAsync(TestRunRecord record)
|
||||
{
|
||||
var directory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
||||
"整鞋试验报告");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var fileName = $"{record.CreatedAt:yyyyMMdd_HHmmss}_{SanitizeFileName(record.Method)}_{record.Id}.xlsx";
|
||||
var path = Path.Combine(directory, fileName);
|
||||
await using var stream = File.Create(path);
|
||||
WriteWorkbook(stream, CreateSheets(record));
|
||||
return path;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<WorksheetData> CreateSheets(TestRunRecord record)
|
||||
{
|
||||
var sheets = new List<WorksheetData>
|
||||
{
|
||||
CreateCoverSheet(record),
|
||||
CreateStandardChecklistSheet(record),
|
||||
};
|
||||
|
||||
using var document = JsonDocument.Parse(record.DataJson);
|
||||
var root = document.RootElement;
|
||||
if (record.Method.StartsWith("方法 A", StringComparison.Ordinal))
|
||||
{
|
||||
sheets.Add(CreateMethodAResultSheet(root));
|
||||
sheets.Add(CreateMethodASampleSheet(root));
|
||||
}
|
||||
else if (record.Method.StartsWith("方法 B", StringComparison.Ordinal))
|
||||
{
|
||||
sheets.Add(CreateMethodBResultSheet(root, record.Method));
|
||||
sheets.Add(CreateMethodBMassSheet(root));
|
||||
sheets.Add(CreateProcedureSheet(root));
|
||||
}
|
||||
|
||||
sheets.Add(CreateRawJsonSheet(root));
|
||||
return sheets;
|
||||
}
|
||||
|
||||
private static WorksheetData CreateCoverSheet(TestRunRecord record)
|
||||
{
|
||||
return new WorksheetData(
|
||||
"报告封面",
|
||||
[
|
||||
Row("GB/T 33393-2023 整鞋试验报告"),
|
||||
Row("报告项目", "内容"),
|
||||
Row("记录编号", record.Id),
|
||||
Row("文件编号", "GB/T 33393-2023"),
|
||||
Row("使用的试验方法", record.Method),
|
||||
Row("试样描述", record.SampleDescription),
|
||||
Row("试验条件", record.Conditions),
|
||||
Row("试验结果", record.ResultSummary),
|
||||
Row("试验日期", record.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)),
|
||||
Row("与试验方法的偏差", record.IsValid ? "无已知偏差" : "结果被标记为需复核或重测"),
|
||||
]);
|
||||
}
|
||||
|
||||
private static WorksheetData CreateStandardChecklistSheet(TestRunRecord record)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
Row("GB/T 33393-2023 核对项"),
|
||||
Row("类别", "标准要求", "本次记录"),
|
||||
Row("报告", "报告包含文件编号、方法、试样描述、试验条件、试验结果、日期和偏差", "已写入报告封面"),
|
||||
};
|
||||
|
||||
if (record.Method.StartsWith("方法 A", StringComparison.Ordinal))
|
||||
{
|
||||
rows.Add(Row("方法 A 试样", "试样数量 2 只;试验前按 GB/T 22049 标准环境调节 24 h", record.SampleDescription));
|
||||
rows.Add(Row("方法 A 环境", "环境 (23±2) ℃、(50±5)% RH;空气流速 (1.00±0.15) m/s", record.Conditions));
|
||||
rows.Add(Row("方法 A 假脚", "假脚/模拟皮肤温度 (35.0±0.3) ℃;水汽化热 0.672 W·h/g", record.Conditions));
|
||||
rows.Add(Row("方法 A 采样", "每分钟记录一次,连续记录至少 30 次;结果取连续 30 次平均值", "见“方法A连续数据”"));
|
||||
rows.Add(Row("方法 A 偏差", "任一测定值与两只鞋测试结果平均值的偏差不应超过 ±10%", record.IsValid ? "通过" : "需复核或重测"));
|
||||
}
|
||||
else if (record.Method.Contains("吸湿透水汽", StringComparison.Ordinal))
|
||||
{
|
||||
rows.Add(Row("方法 B 试样", "满帮鞋同号两只;试验前在 (23±2) ℃、(50±5)% RH 标准环境调节", record.SampleDescription));
|
||||
rows.Add(Row("方法 B 供水", "泵流量 (5.0±0.3) cm3/h;测试周期 (180±1) min", record.Conditions));
|
||||
rows.Add(Row("方法 B 称重", "m11-m72 均精确到 0.01 g;m180 应在 (15±0.9) g 范围内", record.IsValid ? "m180 有效" : "m180 超限"));
|
||||
rows.Add(Row("方法 B 结果", "透水汽性能 T180*;吸湿透水汽性能 XT180*=T180*+m3*+m4*,保留两位小数", record.ResultSummary));
|
||||
}
|
||||
else
|
||||
{
|
||||
rows.Add(Row("方法 B 保暖", "假脚温度 (38±1) ℃;测试周期 (180±1) min,连续测试 2 个周期", record.Conditions));
|
||||
rows.Add(Row("方法 B 热阻", "Q=P/t×1000;R=S×(Tf-Tc)/Q,结果保留三位小数", record.ResultSummary));
|
||||
}
|
||||
|
||||
return new WorksheetData("标准核对", rows);
|
||||
}
|
||||
|
||||
private static WorksheetData CreateMethodAResultSheet(JsonElement root)
|
||||
{
|
||||
var result = root.GetProperty("Result");
|
||||
return new WorksheetData(
|
||||
"方法A结果",
|
||||
[
|
||||
Row("项目", "数值", "单位/说明"),
|
||||
Row("阶段", GetString(result, "Stage"), ""),
|
||||
Row("皮肤湿阻 Res", GetDouble(result, "SkinMoistureResistance"), "Pa·m2/W"),
|
||||
Row("整鞋平均湿阻 Re", GetDouble(result, "AverageMoistureResistance"), "Pa·m2/W"),
|
||||
Row("整鞋平均热阻 Rt", GetDouble(result, "AverageThermalResistance"), "m2·℃/W"),
|
||||
Row("湿阻变异系数", GetDouble(result, "MoistureCoefficientOfVariation"), "%"),
|
||||
Row("热阻变异系数", GetDouble(result, "ThermalCoefficientOfVariation"), "%"),
|
||||
Row("±10% 偏差检查", GetBool(result, "PassedDeviationCheck") ? "通过" : "需复核或重测", ""),
|
||||
Row("湿热量 He", "He=λ×Q,λ=0.672 W·h/g", "W"),
|
||||
Row("湿阻 Re", "Re=A×(Psi×RHsi-Pa×RHa)/He-Res", "Pa·m2/W"),
|
||||
Row("干热量 Hd", "Hd=Hs-He", "W"),
|
||||
Row("热阻 Rt", "Rt=A×(Ts-Ta)/Hd", "m2·℃/W"),
|
||||
]);
|
||||
}
|
||||
|
||||
private static WorksheetData CreateMethodASampleSheet(JsonElement root)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
Row("序号", "时间", "假脚温度 ℃", "环境温度 ℃", "相对湿度 %", "出汗量 g/h", "加热功率 W", "湿热量 He W", "干热量 Hd W", "湿阻 Pa·m2/W", "热阻 m2·℃/W"),
|
||||
};
|
||||
|
||||
if (root.TryGetProperty("Samples", out var samples) && samples.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var sample in samples.EnumerateArray())
|
||||
{
|
||||
var waterLoss = GetDouble(sample, "WaterLossGramsPerHour");
|
||||
var power = GetDouble(sample, "PowerWatts");
|
||||
var moistureHeat = sample.TryGetProperty("MoistureHeatWatts", out _) ? GetDouble(sample, "MoistureHeatWatts") : Math.Round(0.672 * Math.Max(waterLoss, 0.001), 3);
|
||||
var dryHeat = sample.TryGetProperty("DryHeatWatts", out _) ? GetDouble(sample, "DryHeatWatts") : Math.Round(Math.Max(power - moistureHeat, 0.001), 3);
|
||||
rows.Add(Row(
|
||||
GetDouble(sample, "Index"),
|
||||
GetString(sample, "Timestamp"),
|
||||
GetDouble(sample, "FootTemperatureC"),
|
||||
GetDouble(sample, "EnvironmentTemperatureC"),
|
||||
GetDouble(sample, "RelativeHumidityPercent"),
|
||||
waterLoss,
|
||||
power,
|
||||
moistureHeat,
|
||||
dryHeat,
|
||||
GetDouble(sample, "MoistureResistance"),
|
||||
GetDouble(sample, "ThermalResistance")));
|
||||
}
|
||||
}
|
||||
|
||||
return new WorksheetData("方法A连续数据", rows);
|
||||
}
|
||||
|
||||
private static WorksheetData CreateMethodBResultSheet(JsonElement root, string method)
|
||||
{
|
||||
var result = root.GetProperty("Result");
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
Row("项目", "数值", "单位/说明"),
|
||||
};
|
||||
|
||||
if (method.Contains("吸湿透水汽", StringComparison.Ordinal))
|
||||
{
|
||||
rows.Add(Row("m5", GetDouble(result, "M5"), "g,C1 消耗水量"));
|
||||
rows.Add(Row("m6", GetDouble(result, "M6"), "g,C2 蒸发水量"));
|
||||
rows.Add(Row("m7", GetDouble(result, "M7"), "g,C3 蒸发水量"));
|
||||
rows.Add(Row("m8", GetDouble(result, "M8"), "g,C2/C3 平均蒸发量"));
|
||||
rows.Add(Row("m180", GetDouble(result, "M180"), "g,泵进鞋腔内水总质量,15±0.9 g"));
|
||||
rows.Add(Row("m1", GetDouble(result, "M1"), "g,模拟皮肤质量变化"));
|
||||
rows.Add(Row("m2", GetDouble(result, "M2"), "g,标准长筒袜质量变化"));
|
||||
rows.Add(Row("m3", GetDouble(result, "M3"), "g,样品鞋质量变化"));
|
||||
rows.Add(Row("m4", GetDouble(result, "M4"), "g,鞋垫质量变化"));
|
||||
rows.Add(Row("T180", GetDouble(result, "T180"), "g,散发到环境中的水汽质量"));
|
||||
rows.Add(Row("m1*", GetDouble(result, "M1Corrected"), "g,按 15 g 修正"));
|
||||
rows.Add(Row("m2*", GetDouble(result, "M2Corrected"), "g,按 15 g 修正"));
|
||||
rows.Add(Row("m3*", GetDouble(result, "M3Corrected"), "g,按 15 g 修正"));
|
||||
rows.Add(Row("m4*", GetDouble(result, "M4Corrected"), "g,按 15 g 修正"));
|
||||
rows.Add(Row("T180*", GetDouble(result, "T180Corrected"), "g,透水汽性能"));
|
||||
rows.Add(Row("XT180*", GetDouble(result, "XT180Corrected"), "g,吸湿透水汽性能"));
|
||||
rows.Add(Row("有效性", GetBool(result, "IsValid") ? "有效" : "m180 超出 15±0.9 g", ""));
|
||||
}
|
||||
else
|
||||
{
|
||||
rows.Add(Row("P", GetDouble(result, "EnergyKilojoules"), "kJ,180 min 能量消耗"));
|
||||
rows.Add(Row("t", GetDouble(result, "Seconds"), "s,测试周期"));
|
||||
rows.Add(Row("Q", GetDouble(result, "HeatWatts"), "W,单位时间热量消耗"));
|
||||
rows.Add(Row("Tf", GetDouble(result, "FootTemperatureC"), "℃,假脚表面温度平均值"));
|
||||
rows.Add(Row("Tc", GetDouble(result, "ChamberTemperatureC"), "℃,测试箱环境温度平均值"));
|
||||
rows.Add(Row("R", GetDouble(result, "ThermalResistance"), "m2·℃/W,保暖性能热阻值"));
|
||||
}
|
||||
|
||||
return new WorksheetData("方法B结果", rows);
|
||||
}
|
||||
|
||||
private static WorksheetData CreateMethodBMassSheet(JsonElement root)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
Row("字段", "数值 g", "说明"),
|
||||
};
|
||||
|
||||
if (root.TryGetProperty("Masses", out var masses))
|
||||
{
|
||||
rows.Add(Row("m11", GetDouble(masses, "M11"), "模拟皮肤测试前质量"));
|
||||
rows.Add(Row("m12", GetDouble(masses, "M12"), "模拟皮肤测试后质量"));
|
||||
rows.Add(Row("m21", GetDouble(masses, "M21"), "标准长筒袜测试前质量"));
|
||||
rows.Add(Row("m22", GetDouble(masses, "M22"), "标准长筒袜测试后质量"));
|
||||
rows.Add(Row("m31", GetDouble(masses, "M31"), "样品鞋测试前质量"));
|
||||
rows.Add(Row("m32", GetDouble(masses, "M32"), "样品鞋测试后质量"));
|
||||
rows.Add(Row("m41", GetDouble(masses, "M41"), "鞋垫测试前质量"));
|
||||
rows.Add(Row("m42", GetDouble(masses, "M42"), "鞋垫测试后质量"));
|
||||
rows.Add(Row("m51", GetDouble(masses, "M51"), "C1 测试前质量"));
|
||||
rows.Add(Row("m52", GetDouble(masses, "M52"), "C1 测试后质量"));
|
||||
rows.Add(Row("m61", GetDouble(masses, "M61"), "C2 测试前质量"));
|
||||
rows.Add(Row("m62", GetDouble(masses, "M62"), "C2 测试后质量"));
|
||||
rows.Add(Row("m71", GetDouble(masses, "M71"), "C3 测试前质量"));
|
||||
rows.Add(Row("m72", GetDouble(masses, "M72"), "C3 测试后质量"));
|
||||
}
|
||||
|
||||
return new WorksheetData("方法B称重", rows);
|
||||
}
|
||||
|
||||
private static WorksheetData CreateProcedureSheet(JsonElement root)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
Row("序号", "流程记录"),
|
||||
};
|
||||
|
||||
if (root.TryGetProperty("ProcedureLog", out var log) && log.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var index = 1;
|
||||
foreach (var item in log.EnumerateArray())
|
||||
{
|
||||
rows.Add(Row(index++, item.GetString() ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
return new WorksheetData("流程记录", rows);
|
||||
}
|
||||
|
||||
private static WorksheetData CreateRawJsonSheet(JsonElement root)
|
||||
{
|
||||
var rows = new List<IReadOnlyList<object?>>
|
||||
{
|
||||
Row("路径", "值"),
|
||||
};
|
||||
FlattenJson(rows, "", root);
|
||||
return new WorksheetData("完整原始数据", rows);
|
||||
}
|
||||
|
||||
private static void FlattenJson(List<IReadOnlyList<object?>> rows, string path, JsonElement element)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
foreach (var property in element.EnumerateObject())
|
||||
{
|
||||
var nextPath = string.IsNullOrWhiteSpace(path) ? property.Name : $"{path}.{property.Name}";
|
||||
FlattenJson(rows, nextPath, property.Value);
|
||||
}
|
||||
break;
|
||||
case JsonValueKind.Array:
|
||||
var index = 0;
|
||||
foreach (var item in element.EnumerateArray())
|
||||
{
|
||||
FlattenJson(rows, $"{path}[{index++}]", item);
|
||||
}
|
||||
break;
|
||||
case JsonValueKind.Number:
|
||||
rows.Add(Row(path, element.TryGetDouble(out var number) ? number : element.GetRawText()));
|
||||
break;
|
||||
case JsonValueKind.True:
|
||||
case JsonValueKind.False:
|
||||
rows.Add(Row(path, element.GetBoolean() ? "true" : "false"));
|
||||
break;
|
||||
case JsonValueKind.String:
|
||||
rows.Add(Row(path, element.GetString() ?? ""));
|
||||
break;
|
||||
default:
|
||||
rows.Add(Row(path, ""));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteWorkbook(Stream stream, IReadOnlyList<WorksheetData> sheets)
|
||||
{
|
||||
using var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true);
|
||||
AddText(archive, "[Content_Types].xml", CreateContentTypes(sheets.Count));
|
||||
AddText(archive, "_rels/.rels", """
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
||||
</Relationships>
|
||||
""");
|
||||
AddText(archive, "xl/workbook.xml", CreateWorkbookXml(sheets));
|
||||
AddText(archive, "xl/_rels/workbook.xml.rels", CreateWorkbookRelationships(sheets.Count));
|
||||
AddText(archive, "xl/styles.xml", """
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
<fonts count="1"><font><sz val="11"/><name val="Microsoft YaHei"/></font></fonts>
|
||||
<fills count="1"><fill><patternFill patternType="none"/></fill></fills>
|
||||
<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
|
||||
<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
|
||||
<cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs>
|
||||
<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
|
||||
</styleSheet>
|
||||
""");
|
||||
|
||||
for (var i = 0; i < sheets.Count; i++)
|
||||
{
|
||||
AddText(archive, $"xl/worksheets/sheet{i + 1}.xml", CreateWorksheetXml(sheets[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private static string CreateContentTypes(int sheetCount)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||
<Default Extension="xml" ContentType="application/xml"/>
|
||||
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
||||
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
||||
""");
|
||||
for (var i = 1; i <= sheetCount; i++)
|
||||
{
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <Override PartName=\"/xl/worksheets/sheet{i}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>\n");
|
||||
}
|
||||
|
||||
builder.Append("</Types>");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string CreateWorkbookXml(IReadOnlyList<WorksheetData> sheets)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
<sheets>
|
||||
""");
|
||||
for (var i = 0; i < sheets.Count; i++)
|
||||
{
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <sheet name=\"{EscapeXml(TrimSheetName(sheets[i].Name))}\" sheetId=\"{i + 1}\" r:id=\"rId{i + 1}\"/>\n");
|
||||
}
|
||||
|
||||
builder.Append("""
|
||||
</sheets>
|
||||
</workbook>
|
||||
""");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string CreateWorkbookRelationships(int sheetCount)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
""");
|
||||
for (var i = 1; i <= sheetCount; i++)
|
||||
{
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <Relationship Id=\"rId{i}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet{i}.xml\"/>\n");
|
||||
}
|
||||
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <Relationship Id=\"rId{sheetCount + 1}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>\n");
|
||||
builder.Append("</Relationships>");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string CreateWorksheetXml(WorksheetData sheet)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
<sheetViews><sheetView workbookViewId="0"/></sheetViews>
|
||||
<sheetFormatPr defaultRowHeight="18"/>
|
||||
<cols>
|
||||
<col min="1" max="1" width="22" customWidth="1"/>
|
||||
<col min="2" max="20" width="24" customWidth="1"/>
|
||||
</cols>
|
||||
<sheetData>
|
||||
""");
|
||||
|
||||
for (var rowIndex = 0; rowIndex < sheet.Rows.Count; rowIndex++)
|
||||
{
|
||||
var rowNumber = rowIndex + 1;
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <row r=\"{rowNumber}\">\n");
|
||||
var row = sheet.Rows[rowIndex];
|
||||
for (var columnIndex = 0; columnIndex < row.Count; columnIndex++)
|
||||
{
|
||||
AppendCell(builder, row[columnIndex], rowNumber, columnIndex + 1);
|
||||
}
|
||||
|
||||
builder.Append(" </row>\n");
|
||||
}
|
||||
|
||||
builder.Append("""
|
||||
</sheetData>
|
||||
</worksheet>
|
||||
""");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void AppendCell(StringBuilder builder, object? value, int row, int column)
|
||||
{
|
||||
var reference = $"{ColumnName(column)}{row}";
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <c r=\"{reference}\"/>\n");
|
||||
break;
|
||||
case byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal:
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <c r=\"{reference}\"><v>{Convert.ToString(value, CultureInfo.InvariantCulture)}</v></c>\n");
|
||||
break;
|
||||
default:
|
||||
builder.Append(CultureInfo.InvariantCulture, $" <c r=\"{reference}\" t=\"inlineStr\"><is><t>{EscapeXml(Convert.ToString(value, CultureInfo.InvariantCulture) ?? "")}</t></is></c>\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddText(ZipArchive archive, string name, string content)
|
||||
{
|
||||
var entry = archive.CreateEntry(name, CompressionLevel.Optimal);
|
||||
using var writer = new StreamWriter(entry.Open(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
|
||||
writer.Write(content);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<object?> Row(params object?[] values) => values;
|
||||
|
||||
private static double GetDouble(JsonElement element, string property)
|
||||
{
|
||||
return element.TryGetProperty(property, out var value) && value.TryGetDouble(out var number) ? number : 0.0;
|
||||
}
|
||||
|
||||
private static string GetString(JsonElement element, string property)
|
||||
{
|
||||
return element.TryGetProperty(property, out var value) ? value.ToString() : "";
|
||||
}
|
||||
|
||||
private static bool GetBool(JsonElement element, string property)
|
||||
{
|
||||
return element.TryGetProperty(property, out var value) && value.ValueKind == JsonValueKind.True;
|
||||
}
|
||||
|
||||
private static string ColumnName(int column)
|
||||
{
|
||||
var name = "";
|
||||
while (column > 0)
|
||||
{
|
||||
column--;
|
||||
name = (char)('A' + column % 26) + name;
|
||||
column /= 26;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static string EscapeXml(string value)
|
||||
{
|
||||
return value
|
||||
.Replace("&", "&", StringComparison.Ordinal)
|
||||
.Replace("<", "<", StringComparison.Ordinal)
|
||||
.Replace(">", ">", StringComparison.Ordinal)
|
||||
.Replace("\"", """, StringComparison.Ordinal)
|
||||
.Replace("'", "'", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string TrimSheetName(string name)
|
||||
{
|
||||
return name.Length <= 31 ? name : name[..31];
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
{
|
||||
var invalid = Path.GetInvalidFileNameChars();
|
||||
return new string(value.Select(ch => invalid.Contains(ch) ? '_' : ch).ToArray());
|
||||
}
|
||||
|
||||
private sealed record WorksheetData(string Name, IReadOnlyList<IReadOnlyList<object?>> Rows);
|
||||
}
|
||||
19
FootwearTest/Services/IDeviceClient.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FootwearTest.Models;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public interface IDeviceClient
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
string ConnectionText { get; }
|
||||
DeviceSnapshot LastSnapshot { get; }
|
||||
event EventHandler<DeviceSnapshot>? SnapshotUpdated;
|
||||
|
||||
Task ConnectAsync(CancellationToken cancellationToken = default);
|
||||
Task DisconnectAsync(CancellationToken cancellationToken = default);
|
||||
Task<DeviceSnapshot> ReadSnapshotAsync(CancellationToken cancellationToken = default);
|
||||
Task SetOutputsAsync(bool pumpRunning, bool fanRunning, bool heaterRunning, CancellationToken cancellationToken = default);
|
||||
}
|
||||
60
FootwearTest/Services/ModbusTcpDeviceClient.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FootwearTest.Models;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public sealed class ModbusTcpDeviceClient : IDeviceClient
|
||||
{
|
||||
private readonly DeviceSettings _settings;
|
||||
private TcpClient? _tcpClient;
|
||||
|
||||
public ModbusTcpDeviceClient(DeviceSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
LastSnapshot = DeviceSnapshot.Initial with { AlarmText = "Modbus 点表未配置,当前仅验证连接" };
|
||||
}
|
||||
|
||||
public bool IsConnected => _tcpClient?.Connected == true;
|
||||
public string ConnectionText => IsConnected ? $"Modbus TCP {_settings.Host}:{_settings.Port} 已连接" : "Modbus TCP 未连接";
|
||||
public DeviceSnapshot LastSnapshot { get; private set; }
|
||||
public event EventHandler<DeviceSnapshot>? SnapshotUpdated;
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(_settings.Host, _settings.Port, cancellationToken);
|
||||
LastSnapshot = LastSnapshot with { Timestamp = DateTime.Now, AlarmText = "等待配置真实 Modbus 寄存器点表" };
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_tcpClient = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<DeviceSnapshot> ReadSnapshotAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
LastSnapshot = LastSnapshot with { Timestamp = DateTime.Now };
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
return Task.FromResult(LastSnapshot);
|
||||
}
|
||||
|
||||
public Task SetOutputsAsync(bool pumpRunning, bool fanRunning, bool heaterRunning, CancellationToken cancellationToken = default)
|
||||
{
|
||||
LastSnapshot = LastSnapshot with
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
PumpRunning = pumpRunning,
|
||||
FanRunning = fanRunning,
|
||||
HeaterRunning = heaterRunning
|
||||
};
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
26
FootwearTest/Services/ReportService.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Text;
|
||||
using FootwearTest.Models;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public sealed class ReportService
|
||||
{
|
||||
public string CreateTextReport(TestRunRecord record)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("GB/T 33393-2023 整鞋热阻和湿阻测定报告");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine($"记录编号: {record.Id}");
|
||||
builder.AppendLine($"文件编号: GB/T 33393-2023");
|
||||
builder.AppendLine($"使用的试验方法: {record.Method}");
|
||||
builder.AppendLine($"试样描述: {record.SampleDescription}");
|
||||
builder.AppendLine($"试验条件: {record.Conditions}");
|
||||
builder.AppendLine($"试验结果: {record.ResultSummary}");
|
||||
builder.AppendLine($"试验日期: {record.CreatedAt:yyyy-MM-dd HH:mm:ss}");
|
||||
builder.AppendLine($"与方法偏差: {(record.IsValid ? "无已知偏差" : "结果被标记为需复核或重测")}");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("原始数据摘要:");
|
||||
builder.AppendLine(record.DataJson);
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
83
FootwearTest/Services/SimulatedDeviceClient.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FootwearTest.Models;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public sealed class SimulatedDeviceClient : IDeviceClient
|
||||
{
|
||||
private readonly DeviceSettings _settings;
|
||||
private readonly Random _random = new();
|
||||
private bool _pumpRunning;
|
||||
private bool _fanRunning;
|
||||
private bool _heaterRunning;
|
||||
private double _energyKilojoules;
|
||||
|
||||
public SimulatedDeviceClient(DeviceSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
LastSnapshot = DeviceSnapshot.Initial;
|
||||
}
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
public string ConnectionText => IsConnected ? "模拟器已连接" : "未连接";
|
||||
public DeviceSnapshot LastSnapshot { get; private set; }
|
||||
public event EventHandler<DeviceSnapshot>? SnapshotUpdated;
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
IsConnected = true;
|
||||
return ReadSnapshotAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
IsConnected = false;
|
||||
_pumpRunning = false;
|
||||
_fanRunning = false;
|
||||
_heaterRunning = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetOutputsAsync(bool pumpRunning, bool fanRunning, bool heaterRunning, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_pumpRunning = pumpRunning;
|
||||
_fanRunning = fanRunning;
|
||||
_heaterRunning = heaterRunning;
|
||||
return ReadSnapshotAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task<DeviceSnapshot> ReadSnapshotAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var targetFoot = _heaterRunning ? _settings.MethodATargetTemperatureC : _settings.EnvironmentTemperatureC;
|
||||
var previous = LastSnapshot;
|
||||
var foot = previous.FootTemperatureC + (targetFoot - previous.FootTemperatureC) * 0.18 + Noise(0.08);
|
||||
var chamber = _settings.EnvironmentTemperatureC + Noise(0.12);
|
||||
var humidity = _settings.EnvironmentHumidityPercent + Noise(0.35);
|
||||
var airSpeed = (_fanRunning ? _settings.MethodBAirSpeedMetersPerSecond : 0.05) + Noise(0.03);
|
||||
var waterLoss = _pumpRunning ? _settings.PumpSpeedCubicCentimetersPerHour + Noise(0.12) : Math.Max(0, Noise(0.03));
|
||||
var power = _heaterRunning ? 4.2 + Math.Max(0, targetFoot - chamber) * 0.03 + Noise(0.12) : 0.0;
|
||||
|
||||
_energyKilojoules += power * _settings.PollIntervalMilliseconds / 1_000_000.0;
|
||||
LastSnapshot = new DeviceSnapshot(
|
||||
DateTime.Now,
|
||||
Math.Round(foot, 2),
|
||||
Math.Round(chamber, 2),
|
||||
Math.Round(humidity, 2),
|
||||
Math.Round(Math.Max(0, airSpeed), 2),
|
||||
Math.Round(Math.Max(0, power), 2),
|
||||
Math.Round(_energyKilojoules, 3),
|
||||
Math.Round(Math.Max(0, waterLoss), 2),
|
||||
_settings.PumpSpeedCubicCentimetersPerHour,
|
||||
_pumpRunning,
|
||||
_fanRunning,
|
||||
_heaterRunning,
|
||||
"无报警");
|
||||
|
||||
SnapshotUpdated?.Invoke(this, LastSnapshot);
|
||||
return Task.FromResult(LastSnapshot);
|
||||
}
|
||||
|
||||
private double Noise(double amplitude) => (_random.NextDouble() * 2.0 - 1.0) * amplitude;
|
||||
}
|
||||
156
FootwearTest/Services/TestFormulaService.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FootwearTest.Models;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public sealed class TestFormulaService
|
||||
{
|
||||
private const double VaporizationHeatWhPerGram = 0.672;
|
||||
|
||||
public double CalculateSkinMoistureResistance(
|
||||
double footAreaSquareMeters,
|
||||
double skinSaturatedVaporPressurePa,
|
||||
double skinRelativeHumidity,
|
||||
double environmentSaturatedVaporPressurePa,
|
||||
double environmentRelativeHumidity,
|
||||
double nakedFootSweatGramsPerHour)
|
||||
{
|
||||
return footAreaSquareMeters *
|
||||
(skinSaturatedVaporPressurePa * skinRelativeHumidity - environmentSaturatedVaporPressurePa * environmentRelativeHumidity) /
|
||||
(VaporizationHeatWhPerGram * Math.Max(nakedFootSweatGramsPerHour, 0.001));
|
||||
}
|
||||
|
||||
public MethodASampleRecord CalculateMethodASample(
|
||||
int index,
|
||||
DeviceSnapshot snapshot,
|
||||
double footAreaSquareMeters,
|
||||
double skinMoistureResistance,
|
||||
double skinSaturatedVaporPressurePa = 5623.0,
|
||||
double environmentSaturatedVaporPressurePa = 2809.0)
|
||||
{
|
||||
var he = VaporizationHeatWhPerGram * Math.Max(snapshot.WaterLossGramsPerHour, 0.001);
|
||||
var re = footAreaSquareMeters *
|
||||
(skinSaturatedVaporPressurePa - environmentSaturatedVaporPressurePa * snapshot.EnvironmentHumidityPercent / 100.0) /
|
||||
he - skinMoistureResistance;
|
||||
var hd = Math.Max(snapshot.PowerWatts - he, 0.001);
|
||||
var rt = footAreaSquareMeters * (snapshot.FootTemperatureC - snapshot.EnvironmentTemperatureC) / hd;
|
||||
|
||||
return new MethodASampleRecord(
|
||||
index,
|
||||
snapshot.Timestamp,
|
||||
snapshot.FootTemperatureC,
|
||||
snapshot.EnvironmentTemperatureC,
|
||||
snapshot.EnvironmentHumidityPercent,
|
||||
snapshot.WaterLossGramsPerHour,
|
||||
snapshot.PowerWatts,
|
||||
Math.Round(he, 3),
|
||||
Math.Round(hd, 3),
|
||||
Math.Round(re, 3),
|
||||
Math.Round(rt, 4));
|
||||
}
|
||||
|
||||
public double Average(IEnumerable<double> values)
|
||||
{
|
||||
var list = values.ToArray();
|
||||
return list.Length == 0 ? 0 : list.Average();
|
||||
}
|
||||
|
||||
public double CoefficientOfVariationPercent(IEnumerable<double> values)
|
||||
{
|
||||
var list = values.ToArray();
|
||||
if (list.Length < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var average = list.Average();
|
||||
if (Math.Abs(average) < 0.000001)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var variance = list.Sum(value => Math.Pow(value - average, 2)) / (list.Length - 1);
|
||||
return Math.Sqrt(variance) / Math.Abs(average) * 100.0;
|
||||
}
|
||||
|
||||
public bool PassesTenPercentDeviation(IEnumerable<double> values)
|
||||
{
|
||||
var list = values.ToArray();
|
||||
if (list.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var average = list.Average();
|
||||
return list.All(value => Math.Abs(value - average) <= Math.Abs(average) * 0.10);
|
||||
}
|
||||
|
||||
public MethodBMoistureResult CalculateMethodBMoisture(
|
||||
double m11,
|
||||
double m12,
|
||||
double m21,
|
||||
double m22,
|
||||
double m31,
|
||||
double m32,
|
||||
double m41,
|
||||
double m42,
|
||||
double m51,
|
||||
double m52,
|
||||
double m61,
|
||||
double m62,
|
||||
double m71,
|
||||
double m72)
|
||||
{
|
||||
var m5 = m51 - m52;
|
||||
var m6 = m61 - m62;
|
||||
var m7 = m71 - m72;
|
||||
var m8 = (m6 + m7) / 2.0;
|
||||
var m180 = m5 - m8;
|
||||
var scale = 15.0 / Math.Max(m180, 0.001);
|
||||
|
||||
var m1 = m12 - m11;
|
||||
var m2 = m22 - m21;
|
||||
var m3 = m32 - m31;
|
||||
var m4 = m42 - m41;
|
||||
var t180 = m180 - m1 - m2 - m3 - m4;
|
||||
|
||||
return new MethodBMoistureResult(
|
||||
Math.Round(m1, 3),
|
||||
Math.Round(m2, 3),
|
||||
Math.Round(m3, 3),
|
||||
Math.Round(m4, 3),
|
||||
Math.Round(m5, 3),
|
||||
Math.Round(m6, 3),
|
||||
Math.Round(m7, 3),
|
||||
Math.Round(m8, 3),
|
||||
Math.Round(m180, 3),
|
||||
Math.Round(t180, 3),
|
||||
Math.Round(m1 * scale, 3),
|
||||
Math.Round(m2 * scale, 3),
|
||||
Math.Round(m3 * scale, 3),
|
||||
Math.Round(m4 * scale, 3),
|
||||
Math.Round(t180 * scale, 3),
|
||||
Math.Round((t180 + m3 + m4) * scale, 3),
|
||||
m180 >= 14.1 && m180 <= 15.9);
|
||||
}
|
||||
|
||||
public MethodBWarmthResult CalculateMethodBWarmth(
|
||||
double energyKilojoules,
|
||||
double seconds,
|
||||
double footAreaSquareMeters,
|
||||
double footTemperatureC,
|
||||
double chamberTemperatureC)
|
||||
{
|
||||
var heatWatts = energyKilojoules / Math.Max(seconds, 1.0) * 1000.0;
|
||||
var resistance = footAreaSquareMeters * (footTemperatureC - chamberTemperatureC) / Math.Max(heatWatts, 0.001);
|
||||
return new MethodBWarmthResult(
|
||||
Math.Round(energyKilojoules, 1),
|
||||
seconds,
|
||||
Math.Round(heatWatts, 3),
|
||||
Math.Round(footTemperatureC, 2),
|
||||
Math.Round(chamberTemperatureC, 2),
|
||||
Math.Round(resistance, 3));
|
||||
}
|
||||
}
|
||||
138
FootwearTest/Services/TestRunRepository.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using FootwearTest.Models;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace FootwearTest.Services;
|
||||
|
||||
public sealed class TestRunRepository
|
||||
{
|
||||
private readonly string _databasePath;
|
||||
|
||||
public TestRunRepository()
|
||||
{
|
||||
var directory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"FootwearTest");
|
||||
Directory.CreateDirectory(directory);
|
||||
_databasePath = Path.Combine(directory, "footwear-test.db");
|
||||
EnsureCreated();
|
||||
}
|
||||
|
||||
public async Task<long> SaveAsync(TestRunRecord record)
|
||||
{
|
||||
await using var connection = CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
INSERT INTO TestRuns(Method, SampleDescription, Conditions, ResultSummary, DataJson, IsValid, CreatedAt)
|
||||
VALUES($method, $sample, $conditions, $summary, $data, $valid, $createdAt);
|
||||
SELECT last_insert_rowid();
|
||||
""";
|
||||
command.Parameters.AddWithValue("$method", record.Method);
|
||||
command.Parameters.AddWithValue("$sample", record.SampleDescription);
|
||||
command.Parameters.AddWithValue("$conditions", record.Conditions);
|
||||
command.Parameters.AddWithValue("$summary", record.ResultSummary);
|
||||
command.Parameters.AddWithValue("$data", record.DataJson);
|
||||
command.Parameters.AddWithValue("$valid", record.IsValid ? 1 : 0);
|
||||
command.Parameters.AddWithValue("$createdAt", record.CreatedAt.ToString("O"));
|
||||
var id = (long)(await command.ExecuteScalarAsync() ?? 0L);
|
||||
return id;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<TestRunSummary>> GetRecentAsync(int take = 50)
|
||||
{
|
||||
var items = new List<TestRunSummary>();
|
||||
await using var connection = CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
SELECT Id, Method, SampleDescription, ResultSummary, IsValid, CreatedAt
|
||||
FROM TestRuns
|
||||
ORDER BY Id DESC
|
||||
LIMIT $take;
|
||||
""";
|
||||
command.Parameters.AddWithValue("$take", take);
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
items.Add(new TestRunSummary(
|
||||
reader.GetInt64(0),
|
||||
reader.GetString(1),
|
||||
reader.GetString(2),
|
||||
reader.GetString(3),
|
||||
reader.GetInt32(4) == 1,
|
||||
DateTime.Parse(reader.GetString(5))));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<TestRunRecord?> GetLatestAsync()
|
||||
{
|
||||
await using var connection = CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
SELECT Id, Method, SampleDescription, Conditions, ResultSummary, DataJson, IsValid, CreatedAt
|
||||
FROM TestRuns
|
||||
ORDER BY Id DESC
|
||||
LIMIT 1;
|
||||
""";
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
return await reader.ReadAsync() ? ReadRecord(reader) : null;
|
||||
}
|
||||
|
||||
public async Task<TestRunRecord?> GetByIdAsync(long id)
|
||||
{
|
||||
await using var connection = CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
SELECT Id, Method, SampleDescription, Conditions, ResultSummary, DataJson, IsValid, CreatedAt
|
||||
FROM TestRuns
|
||||
WHERE Id = $id;
|
||||
""";
|
||||
command.Parameters.AddWithValue("$id", id);
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
return await reader.ReadAsync() ? ReadRecord(reader) : null;
|
||||
}
|
||||
|
||||
private TestRunRecord ReadRecord(SqliteDataReader reader)
|
||||
{
|
||||
return new TestRunRecord(
|
||||
reader.GetInt64(0),
|
||||
reader.GetString(1),
|
||||
reader.GetString(2),
|
||||
reader.GetString(3),
|
||||
reader.GetString(4),
|
||||
reader.GetString(5),
|
||||
reader.GetInt32(6) == 1,
|
||||
DateTime.Parse(reader.GetString(7)));
|
||||
}
|
||||
|
||||
private SqliteConnection CreateConnection() => new($"Data Source={_databasePath}");
|
||||
|
||||
private void EnsureCreated()
|
||||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
CREATE TABLE IF NOT EXISTS TestRuns
|
||||
(
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Method TEXT NOT NULL,
|
||||
SampleDescription TEXT NOT NULL,
|
||||
Conditions TEXT NOT NULL,
|
||||
ResultSummary TEXT NOT NULL,
|
||||
DataJson TEXT NOT NULL,
|
||||
IsValid INTEGER NOT NULL,
|
||||
CreatedAt TEXT NOT NULL
|
||||
);
|
||||
""";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
38
FootwearTest/ViewLocator.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using FootwearTest.ViewModels;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace FootwearTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Given a view model, returns the corresponding view if possible.
|
||||
/// </summary>
|
||||
[RequiresUnreferencedCode(
|
||||
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
|
||||
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
public Control? Build(object? param)
|
||||
{
|
||||
if (param is null)
|
||||
return null;
|
||||
|
||||
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return (Control)Activator.CreateInstance(type)!;
|
||||
}
|
||||
|
||||
return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
|
||||
public bool Match(object? data)
|
||||
{
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
FootwearTest/ViewModels/DashboardViewModel.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FootwearTest.Models;
|
||||
using FootwearTest.Services;
|
||||
|
||||
namespace FootwearTest.ViewModels;
|
||||
|
||||
public partial class DashboardViewModel : ViewModelBase
|
||||
{
|
||||
private readonly TestRunRepository _repository;
|
||||
|
||||
public DashboardViewModel(TestRunRepository repository)
|
||||
{
|
||||
_repository = repository;
|
||||
RefreshCommand = new AsyncRelayCommand(RefreshAsync);
|
||||
_ = RefreshAsync();
|
||||
}
|
||||
|
||||
private DeviceSnapshot _snapshot = DeviceSnapshot.Initial;
|
||||
|
||||
public ObservableCollection<TestRunSummary> RecentRuns { get; } = [];
|
||||
public IAsyncRelayCommand RefreshCommand { get; }
|
||||
|
||||
public DeviceSnapshot Snapshot
|
||||
{
|
||||
get => _snapshot;
|
||||
set => SetProperty(ref _snapshot, value);
|
||||
}
|
||||
|
||||
private async Task RefreshAsync()
|
||||
{
|
||||
RecentRuns.Clear();
|
||||
foreach (var run in await _repository.GetRecentAsync(8))
|
||||
{
|
||||
RecentRuns.Add(run);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
FootwearTest/ViewModels/HistoryViewModel.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FootwearTest.Models;
|
||||
using FootwearTest.Services;
|
||||
|
||||
namespace FootwearTest.ViewModels;
|
||||
|
||||
public partial class HistoryViewModel : ViewModelBase
|
||||
{
|
||||
private readonly TestRunRepository _repository;
|
||||
private readonly ReportService _reportService;
|
||||
private readonly ExcelReportService _excelReportService;
|
||||
|
||||
public HistoryViewModel(TestRunRepository repository, ReportService reportService, ExcelReportService excelReportService)
|
||||
{
|
||||
_repository = repository;
|
||||
_reportService = reportService;
|
||||
_excelReportService = excelReportService;
|
||||
LoadCommand = new AsyncRelayCommand(LoadAsync);
|
||||
ExportSelectedExcelCommand = new AsyncRelayCommand(ExportSelectedExcelAsync);
|
||||
_ = LoadAsync();
|
||||
}
|
||||
|
||||
public ObservableCollection<TestRunSummary> Runs { get; } = [];
|
||||
public IAsyncRelayCommand LoadCommand { get; }
|
||||
public IAsyncRelayCommand ExportSelectedExcelCommand { get; }
|
||||
|
||||
private TestRunSummary? _selectedRun;
|
||||
private string _selectedReportText = "选择一条历史记录查看报告。";
|
||||
private string _exportStatusText = "选择历史记录后可导出完整 Excel 报告。";
|
||||
|
||||
public TestRunSummary? SelectedRun
|
||||
{
|
||||
get => _selectedRun;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedRun, value))
|
||||
{
|
||||
_ = LoadSelectedReportAsync(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedReportText
|
||||
{
|
||||
get => _selectedReportText;
|
||||
set => SetProperty(ref _selectedReportText, value);
|
||||
}
|
||||
|
||||
public string ExportStatusText
|
||||
{
|
||||
get => _exportStatusText;
|
||||
set => SetProperty(ref _exportStatusText, value);
|
||||
}
|
||||
|
||||
private async Task LoadAsync()
|
||||
{
|
||||
Runs.Clear();
|
||||
foreach (var run in await _repository.GetRecentAsync())
|
||||
{
|
||||
Runs.Add(run);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadSelectedReportAsync(TestRunSummary? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
SelectedReportText = "选择一条历史记录查看报告。";
|
||||
return;
|
||||
}
|
||||
|
||||
var record = await _repository.GetByIdAsync(value.Id);
|
||||
SelectedReportText = record is null ? "记录不存在。" : _reportService.CreateTextReport(record);
|
||||
ExportStatusText = record is null ? "记录不存在,无法导出。" : "可导出所选记录的完整 Excel 报告。";
|
||||
}
|
||||
|
||||
private async Task ExportSelectedExcelAsync()
|
||||
{
|
||||
if (SelectedRun is null)
|
||||
{
|
||||
ExportStatusText = "请先选择一条历史记录。";
|
||||
return;
|
||||
}
|
||||
|
||||
var record = await _repository.GetByIdAsync(SelectedRun.Id);
|
||||
if (record is null)
|
||||
{
|
||||
ExportStatusText = "记录不存在,无法导出。";
|
||||
return;
|
||||
}
|
||||
|
||||
var path = await _excelReportService.ExportAsync(record);
|
||||
ExportStatusText = $"已导出 Excel: {path}";
|
||||
}
|
||||
}
|
||||
131
FootwearTest/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FootwearTest.Models;
|
||||
using FootwearTest.Services;
|
||||
|
||||
namespace FootwearTest.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDeviceClient _deviceClient;
|
||||
private readonly DispatcherTimer _pollTimer;
|
||||
|
||||
public MainWindowViewModel(
|
||||
IDeviceClient deviceClient,
|
||||
DashboardViewModel dashboard,
|
||||
MethodAViewModel methodA,
|
||||
MethodBViewModel methodB,
|
||||
HistoryViewModel history,
|
||||
ReportViewModel report,
|
||||
SettingsViewModel settings)
|
||||
{
|
||||
_deviceClient = deviceClient;
|
||||
Dashboard = dashboard;
|
||||
MethodA = methodA;
|
||||
MethodB = methodB;
|
||||
History = history;
|
||||
Report = report;
|
||||
Settings = settings;
|
||||
CurrentPage = Dashboard;
|
||||
CurrentPageTitle = "运行总览";
|
||||
ShowDashboardCommand = new RelayCommand(ShowDashboard);
|
||||
ShowMethodACommand = new RelayCommand(ShowMethodA);
|
||||
ShowMethodBCommand = new RelayCommand(ShowMethodB);
|
||||
ShowHistoryCommand = new RelayCommand(ShowHistory);
|
||||
ShowReportCommand = new RelayCommand(ShowReport);
|
||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||
ConnectDeviceCommand = new AsyncRelayCommand(ConnectDeviceAsync);
|
||||
DisconnectDeviceCommand = new AsyncRelayCommand(DisconnectDeviceAsync);
|
||||
|
||||
_pollTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
_pollTimer.Tick += async (_, _) => await RefreshSnapshotAsync();
|
||||
_pollTimer.Start();
|
||||
}
|
||||
|
||||
public DashboardViewModel Dashboard { get; }
|
||||
public MethodAViewModel MethodA { get; }
|
||||
public MethodBViewModel MethodB { get; }
|
||||
public HistoryViewModel History { get; }
|
||||
public ReportViewModel Report { get; }
|
||||
public SettingsViewModel Settings { get; }
|
||||
public IRelayCommand ShowDashboardCommand { get; }
|
||||
public IRelayCommand ShowMethodACommand { get; }
|
||||
public IRelayCommand ShowMethodBCommand { get; }
|
||||
public IRelayCommand ShowHistoryCommand { get; }
|
||||
public IRelayCommand ShowReportCommand { get; }
|
||||
public IRelayCommand ShowSettingsCommand { get; }
|
||||
public IAsyncRelayCommand ConnectDeviceCommand { get; }
|
||||
public IAsyncRelayCommand DisconnectDeviceCommand { get; }
|
||||
|
||||
private ViewModelBase _currentPage = null!;
|
||||
private string _currentPageTitle = "";
|
||||
private DeviceSnapshot _snapshot = DeviceSnapshot.Initial;
|
||||
private string _connectionText = "未连接";
|
||||
|
||||
public ViewModelBase CurrentPage
|
||||
{
|
||||
get => _currentPage;
|
||||
set => SetProperty(ref _currentPage, value);
|
||||
}
|
||||
|
||||
public string CurrentPageTitle
|
||||
{
|
||||
get => _currentPageTitle;
|
||||
set => SetProperty(ref _currentPageTitle, value);
|
||||
}
|
||||
|
||||
public DeviceSnapshot Snapshot
|
||||
{
|
||||
get => _snapshot;
|
||||
set => SetProperty(ref _snapshot, value);
|
||||
}
|
||||
|
||||
public string ConnectionText
|
||||
{
|
||||
get => _connectionText;
|
||||
set => SetProperty(ref _connectionText, value);
|
||||
}
|
||||
|
||||
private void ShowDashboard() => Navigate(Dashboard, "运行总览");
|
||||
|
||||
private void ShowMethodA() => Navigate(MethodA, "方法 A - 热阻/湿阻");
|
||||
|
||||
private void ShowMethodB() => Navigate(MethodB, "方法 B - 吸湿透水汽/保暖");
|
||||
|
||||
private void ShowHistory() => Navigate(History, "历史记录");
|
||||
|
||||
private void ShowReport() => Navigate(Report, "试验报告");
|
||||
|
||||
private void ShowSettings() => Navigate(Settings, "系统设置");
|
||||
|
||||
private async Task ConnectDeviceAsync()
|
||||
{
|
||||
await _deviceClient.ConnectAsync();
|
||||
await RefreshSnapshotAsync();
|
||||
}
|
||||
|
||||
private async Task DisconnectDeviceAsync()
|
||||
{
|
||||
await _deviceClient.DisconnectAsync();
|
||||
ConnectionText = _deviceClient.ConnectionText;
|
||||
}
|
||||
|
||||
private void Navigate(ViewModelBase page, string title)
|
||||
{
|
||||
CurrentPage = page;
|
||||
CurrentPageTitle = title;
|
||||
}
|
||||
|
||||
private async Task RefreshSnapshotAsync()
|
||||
{
|
||||
if (_deviceClient.IsConnected)
|
||||
{
|
||||
Snapshot = await _deviceClient.ReadSnapshotAsync();
|
||||
Dashboard.Snapshot = Snapshot;
|
||||
}
|
||||
|
||||
ConnectionText = _deviceClient.ConnectionText;
|
||||
}
|
||||
}
|
||||
212
FootwearTest/ViewModels/MethodAViewModel.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FootwearTest.Models;
|
||||
using FootwearTest.Services;
|
||||
|
||||
namespace FootwearTest.ViewModels;
|
||||
|
||||
public partial class MethodAViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDeviceClient _deviceClient;
|
||||
private readonly DeviceSettings _settings;
|
||||
private readonly TestFormulaService _formulaService;
|
||||
private readonly TestRunRepository _repository;
|
||||
|
||||
public MethodAViewModel(
|
||||
IDeviceClient deviceClient,
|
||||
DeviceSettings settings,
|
||||
TestFormulaService formulaService,
|
||||
TestRunRepository repository)
|
||||
{
|
||||
_deviceClient = deviceClient;
|
||||
_settings = settings;
|
||||
_formulaService = formulaService;
|
||||
_repository = repository;
|
||||
MeasureSkinResistanceCommand = new AsyncRelayCommand(MeasureSkinResistanceAsync);
|
||||
RunWholeShoeCommand = new AsyncRelayCommand(RunWholeShoeAsync);
|
||||
StopCommand = new AsyncRelayCommand(StopAsync);
|
||||
}
|
||||
|
||||
public ObservableCollection<MethodASampleRecord> Samples { get; } = [];
|
||||
public IAsyncRelayCommand MeasureSkinResistanceCommand { get; }
|
||||
public IAsyncRelayCommand RunWholeShoeCommand { get; }
|
||||
public IAsyncRelayCommand StopCommand { get; }
|
||||
|
||||
private string _sampleDescription = "整鞋样品 1# / 2#";
|
||||
private string _statusText = "待机";
|
||||
private bool _isRunning;
|
||||
private double _skinMoistureResistance = 6.0;
|
||||
private double _averageMoistureResistance;
|
||||
private double _averageThermalResistance;
|
||||
private double _moistureCvPercent;
|
||||
private double _thermalCvPercent;
|
||||
private string _resultText = "尚未开始试验";
|
||||
|
||||
public string SampleDescription
|
||||
{
|
||||
get => _sampleDescription;
|
||||
set => SetProperty(ref _sampleDescription, value);
|
||||
}
|
||||
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
set => SetProperty(ref _statusText, value);
|
||||
}
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get => _isRunning;
|
||||
set => SetProperty(ref _isRunning, value);
|
||||
}
|
||||
|
||||
public double SkinMoistureResistance
|
||||
{
|
||||
get => _skinMoistureResistance;
|
||||
set => SetProperty(ref _skinMoistureResistance, value);
|
||||
}
|
||||
|
||||
public double AverageMoistureResistance
|
||||
{
|
||||
get => _averageMoistureResistance;
|
||||
set => SetProperty(ref _averageMoistureResistance, value);
|
||||
}
|
||||
|
||||
public double AverageThermalResistance
|
||||
{
|
||||
get => _averageThermalResistance;
|
||||
set => SetProperty(ref _averageThermalResistance, value);
|
||||
}
|
||||
|
||||
public double MoistureCvPercent
|
||||
{
|
||||
get => _moistureCvPercent;
|
||||
set => SetProperty(ref _moistureCvPercent, value);
|
||||
}
|
||||
|
||||
public double ThermalCvPercent
|
||||
{
|
||||
get => _thermalCvPercent;
|
||||
set => SetProperty(ref _thermalCvPercent, value);
|
||||
}
|
||||
|
||||
public string ResultText
|
||||
{
|
||||
get => _resultText;
|
||||
set => SetProperty(ref _resultText, value);
|
||||
}
|
||||
|
||||
private async Task MeasureSkinResistanceAsync()
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await EnsureConnectedAsync();
|
||||
IsRunning = true;
|
||||
StatusText = "皮肤湿阻测定中";
|
||||
Samples.Clear();
|
||||
await _deviceClient.SetOutputsAsync(true, true, true);
|
||||
|
||||
for (var i = 1; i <= 30; i++)
|
||||
{
|
||||
await Task.Delay(60);
|
||||
var snapshot = await _deviceClient.ReadSnapshotAsync();
|
||||
var value = _formulaService.CalculateSkinMoistureResistance(
|
||||
_settings.FootAreaSquareMeters,
|
||||
5620.0,
|
||||
1.0,
|
||||
2809.0,
|
||||
snapshot.EnvironmentHumidityPercent / 100.0,
|
||||
snapshot.WaterLossGramsPerHour);
|
||||
SkinMoistureResistance = Math.Round(value, 3);
|
||||
Samples.Add(_formulaService.CalculateMethodASample(i, snapshot, _settings.FootAreaSquareMeters, SkinMoistureResistance));
|
||||
}
|
||||
|
||||
await _deviceClient.SetOutputsAsync(false, true, false);
|
||||
UpdateMethodAResult("皮肤湿阻");
|
||||
await SaveRunAsync("方法 A - 皮肤湿阻", true);
|
||||
StatusText = "皮肤湿阻测定完成";
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
private async Task RunWholeShoeAsync()
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await EnsureConnectedAsync();
|
||||
IsRunning = true;
|
||||
StatusText = "整鞋热阻/湿阻测定中";
|
||||
Samples.Clear();
|
||||
await _deviceClient.SetOutputsAsync(true, true, true);
|
||||
|
||||
for (var i = 1; i <= 30; i++)
|
||||
{
|
||||
await Task.Delay(80);
|
||||
var snapshot = await _deviceClient.ReadSnapshotAsync();
|
||||
Samples.Add(_formulaService.CalculateMethodASample(i, snapshot, _settings.FootAreaSquareMeters, SkinMoistureResistance));
|
||||
UpdateMethodAResult("整鞋热阻/湿阻");
|
||||
}
|
||||
|
||||
await _deviceClient.SetOutputsAsync(false, true, false);
|
||||
var passed = _formulaService.PassesTenPercentDeviation(Samples.Select(sample => sample.ThermalResistance));
|
||||
UpdateMethodAResult("整鞋热阻/湿阻");
|
||||
await SaveRunAsync("方法 A - 整鞋热阻/湿阻", passed);
|
||||
StatusText = passed ? "试验完成" : "结果偏差超过 ±10%,建议重测";
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
private async Task StopAsync()
|
||||
{
|
||||
await _deviceClient.SetOutputsAsync(false, false, false);
|
||||
IsRunning = false;
|
||||
StatusText = "已停止";
|
||||
}
|
||||
|
||||
private async Task EnsureConnectedAsync()
|
||||
{
|
||||
if (!_deviceClient.IsConnected)
|
||||
{
|
||||
await _deviceClient.ConnectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMethodAResult(string stage)
|
||||
{
|
||||
AverageMoistureResistance = Math.Round(_formulaService.Average(Samples.Select(sample => sample.MoistureResistance)), 3);
|
||||
AverageThermalResistance = Math.Round(_formulaService.Average(Samples.Select(sample => sample.ThermalResistance)), 4);
|
||||
MoistureCvPercent = Math.Round(_formulaService.CoefficientOfVariationPercent(Samples.Select(sample => sample.MoistureResistance)), 2);
|
||||
ThermalCvPercent = Math.Round(_formulaService.CoefficientOfVariationPercent(Samples.Select(sample => sample.ThermalResistance)), 2);
|
||||
ResultText = $"{stage}: 湿阻 {AverageMoistureResistance:F3} Pa·m²/W,热阻 {AverageThermalResistance:F4} m²·℃/W,CV {ThermalCvPercent:F2}%";
|
||||
}
|
||||
|
||||
private async Task SaveRunAsync(string method, bool isValid)
|
||||
{
|
||||
var result = new MethodAResult(
|
||||
method,
|
||||
SkinMoistureResistance,
|
||||
AverageMoistureResistance,
|
||||
AverageThermalResistance,
|
||||
MoistureCvPercent,
|
||||
ThermalCvPercent,
|
||||
isValid);
|
||||
var data = JsonSerializer.Serialize(new { Result = result, Samples }, new JsonSerializerOptions { WriteIndented = true });
|
||||
await _repository.SaveAsync(new TestRunRecord(
|
||||
0,
|
||||
method,
|
||||
SampleDescription,
|
||||
$"假脚 {_settings.MethodATargetTemperatureC:F1} ℃;环境 {_settings.EnvironmentTemperatureC:F1} ℃ / {_settings.EnvironmentHumidityPercent:F0}%;风速 {_settings.MethodAAirSpeedMetersPerSecond:F2} m/s",
|
||||
ResultText,
|
||||
data,
|
||||
isValid,
|
||||
DateTime.Now));
|
||||
}
|
||||
}
|
||||
161
FootwearTest/ViewModels/MethodBViewModel.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FootwearTest.Models;
|
||||
using FootwearTest.Services;
|
||||
|
||||
namespace FootwearTest.ViewModels;
|
||||
|
||||
public partial class MethodBViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDeviceClient _deviceClient;
|
||||
private readonly DeviceSettings _settings;
|
||||
private readonly TestFormulaService _formulaService;
|
||||
private readonly TestRunRepository _repository;
|
||||
|
||||
public MethodBViewModel(
|
||||
IDeviceClient deviceClient,
|
||||
DeviceSettings settings,
|
||||
TestFormulaService formulaService,
|
||||
TestRunRepository repository)
|
||||
{
|
||||
_deviceClient = deviceClient;
|
||||
_settings = settings;
|
||||
_formulaService = formulaService;
|
||||
_repository = repository;
|
||||
RunMoistureCommand = new AsyncRelayCommand(RunMoistureAsync);
|
||||
RunWarmthCommand = new AsyncRelayCommand(RunWarmthAsync);
|
||||
}
|
||||
|
||||
public ObservableCollection<string> ProcedureLog { get; } = [];
|
||||
public IAsyncRelayCommand RunMoistureCommand { get; }
|
||||
public IAsyncRelayCommand RunWarmthCommand { get; }
|
||||
|
||||
private string _sampleDescription = "满帮鞋同号两只";
|
||||
private string _statusText = "待机";
|
||||
private string _moistureResultText = "尚未计算";
|
||||
private string _warmthResultText = "尚未计算";
|
||||
private string _moistureDetailText = "m180、T180*、XT180* 完成后显示。";
|
||||
private string _warmthDetailText = "Q、R 完成后显示。";
|
||||
private double _m11 = 6.00;
|
||||
private double _m12 = 7.10;
|
||||
private double _m21 = 14.00;
|
||||
private double _m22 = 16.40;
|
||||
private double _m31 = 410.00;
|
||||
private double _m32 = 413.20;
|
||||
private double _m41 = 18.00;
|
||||
private double _m42 = 19.00;
|
||||
private double _m51 = 100.00;
|
||||
private double _m52 = 84.70;
|
||||
private double _m61 = 100.00;
|
||||
private double _m62 = 99.80;
|
||||
private double _m71 = 100.00;
|
||||
private double _m72 = 99.78;
|
||||
private double _warmthEnergyKilojoules = 48.2;
|
||||
private double _warmthSeconds = 10800;
|
||||
|
||||
public string SampleDescription { get => _sampleDescription; set => SetProperty(ref _sampleDescription, value); }
|
||||
public string StatusText { get => _statusText; set => SetProperty(ref _statusText, value); }
|
||||
public string MoistureResultText { get => _moistureResultText; set => SetProperty(ref _moistureResultText, value); }
|
||||
public string WarmthResultText { get => _warmthResultText; set => SetProperty(ref _warmthResultText, value); }
|
||||
public string MoistureDetailText { get => _moistureDetailText; set => SetProperty(ref _moistureDetailText, value); }
|
||||
public string WarmthDetailText { get => _warmthDetailText; set => SetProperty(ref _warmthDetailText, value); }
|
||||
public double M11 { get => _m11; set => SetProperty(ref _m11, value); }
|
||||
public double M12 { get => _m12; set => SetProperty(ref _m12, value); }
|
||||
public double M21 { get => _m21; set => SetProperty(ref _m21, value); }
|
||||
public double M22 { get => _m22; set => SetProperty(ref _m22, value); }
|
||||
public double M31 { get => _m31; set => SetProperty(ref _m31, value); }
|
||||
public double M32 { get => _m32; set => SetProperty(ref _m32, value); }
|
||||
public double M41 { get => _m41; set => SetProperty(ref _m41, value); }
|
||||
public double M42 { get => _m42; set => SetProperty(ref _m42, value); }
|
||||
public double M51 { get => _m51; set => SetProperty(ref _m51, value); }
|
||||
public double M52 { get => _m52; set => SetProperty(ref _m52, value); }
|
||||
public double M61 { get => _m61; set => SetProperty(ref _m61, value); }
|
||||
public double M62 { get => _m62; set => SetProperty(ref _m62, value); }
|
||||
public double M71 { get => _m71; set => SetProperty(ref _m71, value); }
|
||||
public double M72 { get => _m72; set => SetProperty(ref _m72, value); }
|
||||
public double WarmthEnergyKilojoules { get => _warmthEnergyKilojoules; set => SetProperty(ref _warmthEnergyKilojoules, value); }
|
||||
public double WarmthSeconds { get => _warmthSeconds; set => SetProperty(ref _warmthSeconds, value); }
|
||||
|
||||
private async Task RunMoistureAsync()
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
ProcedureLog.Clear();
|
||||
StatusText = "方法 B 吸湿透水汽流程运行中";
|
||||
ProcedureLog.Add("固定假脚并定位鞋头至风扇 450±10 mm。");
|
||||
ProcedureLog.Add("预热并排空水管空气,确认 C1/C2/C3 称重。");
|
||||
await _deviceClient.SetOutputsAsync(false, true, true);
|
||||
await Task.Delay(250);
|
||||
ProcedureLog.Add("恢复假脚温度稳定,启动泵并进入 180 min 正式周期。");
|
||||
await _deviceClient.SetOutputsAsync(true, true, true);
|
||||
await Task.Delay(500);
|
||||
ProcedureLog.Add("模拟 180 min 周期完成,关闭泵、风扇及温控。");
|
||||
await _deviceClient.SetOutputsAsync(false, false, false);
|
||||
|
||||
var result = _formulaService.CalculateMethodBMoisture(M11, M12, M21, M22, M31, M32, M41, M42, M51, M52, M61, M62, M71, M72);
|
||||
MoistureResultText = result.IsValid
|
||||
? $"T180*={result.T180Corrected:F2} g,XT180*={result.XT180Corrected:F2} g,m180={result.M180:F2} g"
|
||||
: $"m180={result.M180:F2} g,超出 15±0.9 g,结果作废";
|
||||
MoistureDetailText =
|
||||
$"m1={result.M1:F3} g,m2={result.M2:F3} g,m3={result.M3:F3} g,m4={result.M4:F3} g\n" +
|
||||
$"m5={result.M5:F3} g,m6={result.M6:F3} g,m7={result.M7:F3} g,m8={result.M8:F3} g,T180={result.T180:F3} g";
|
||||
StatusText = result.IsValid ? "吸湿透水汽测试完成" : "吸湿透水汽测试需重测";
|
||||
|
||||
await SaveRunAsync("方法 B - 吸湿透水汽性能", MoistureResultText, result, result.IsValid);
|
||||
}
|
||||
|
||||
private async Task RunWarmthAsync()
|
||||
{
|
||||
await EnsureConnectedAsync();
|
||||
ProcedureLog.Clear();
|
||||
StatusText = "方法 B 保暖性能流程运行中";
|
||||
ProcedureLog.Add("密封假脚微径管道,穿戴模拟皮肤、标准长筒袜和样品鞋。");
|
||||
ProcedureLog.Add("开启风扇和温控,稳定到 38±1 ℃。");
|
||||
await _deviceClient.SetOutputsAsync(false, true, true);
|
||||
await Task.Delay(500);
|
||||
ProcedureLog.Add("记录两个 180 min 周期能量消耗,生成热阻结果。");
|
||||
await _deviceClient.SetOutputsAsync(false, false, false);
|
||||
|
||||
var result = _formulaService.CalculateMethodBWarmth(
|
||||
WarmthEnergyKilojoules,
|
||||
WarmthSeconds,
|
||||
_settings.FootAreaSquareMeters,
|
||||
_settings.MethodBWarmthTargetTemperatureC,
|
||||
_settings.EnvironmentTemperatureC);
|
||||
WarmthResultText = $"Q={result.HeatWatts:F3} W,R={result.ThermalResistance:F3} m²·℃/W";
|
||||
WarmthDetailText = $"P={result.EnergyKilojoules:F1} kJ,t={result.Seconds:F0} s,Tf={result.FootTemperatureC:F2} ℃,Tc={result.ChamberTemperatureC:F2} ℃";
|
||||
StatusText = "保暖性能测试完成";
|
||||
|
||||
await SaveRunAsync("方法 B - 保暖性能", WarmthResultText, result, true);
|
||||
}
|
||||
|
||||
private async Task EnsureConnectedAsync()
|
||||
{
|
||||
if (!_deviceClient.IsConnected)
|
||||
{
|
||||
await _deviceClient.ConnectAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveRunAsync(string method, string summary, object result, bool isValid)
|
||||
{
|
||||
var data = JsonSerializer.Serialize(new
|
||||
{
|
||||
Result = result,
|
||||
Masses = new { M11, M12, M21, M22, M31, M32, M41, M42, M51, M52, M61, M62, M71, M72 },
|
||||
ProcedureLog
|
||||
}, new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
await _repository.SaveAsync(new TestRunRecord(
|
||||
0,
|
||||
method,
|
||||
SampleDescription,
|
||||
$"环境 {_settings.EnvironmentTemperatureC:F1} ℃ / {_settings.EnvironmentHumidityPercent:F0}%;风速 {_settings.MethodBAirSpeedMetersPerSecond:F2} m/s;周期 180 min",
|
||||
summary,
|
||||
data,
|
||||
isValid,
|
||||
DateTime.Now));
|
||||
}
|
||||
}
|
||||
63
FootwearTest/ViewModels/ReportViewModel.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FootwearTest.Models;
|
||||
using FootwearTest.Services;
|
||||
|
||||
namespace FootwearTest.ViewModels;
|
||||
|
||||
public partial class ReportViewModel : ViewModelBase
|
||||
{
|
||||
private readonly TestRunRepository _repository;
|
||||
private readonly ReportService _reportService;
|
||||
private readonly ExcelReportService _excelReportService;
|
||||
|
||||
public ReportViewModel(TestRunRepository repository, ReportService reportService, ExcelReportService excelReportService)
|
||||
{
|
||||
_repository = repository;
|
||||
_reportService = reportService;
|
||||
_excelReportService = excelReportService;
|
||||
LoadLatestCommand = new AsyncRelayCommand(LoadLatestAsync);
|
||||
ExportLatestExcelCommand = new AsyncRelayCommand(ExportLatestExcelAsync);
|
||||
_ = LoadLatestAsync();
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadLatestCommand { get; }
|
||||
public IAsyncRelayCommand ExportLatestExcelCommand { get; }
|
||||
|
||||
private string _reportText = "暂无报告。完成一次试验后会自动生成报告文本。";
|
||||
private string _exportStatusText = "Excel 报告将导出到桌面“整鞋试验报告”文件夹。";
|
||||
private TestRunRecord? _latestRecord;
|
||||
|
||||
public string ReportText
|
||||
{
|
||||
get => _reportText;
|
||||
set => SetProperty(ref _reportText, value);
|
||||
}
|
||||
|
||||
public string ExportStatusText
|
||||
{
|
||||
get => _exportStatusText;
|
||||
set => SetProperty(ref _exportStatusText, value);
|
||||
}
|
||||
|
||||
private async Task LoadLatestAsync()
|
||||
{
|
||||
var record = await _repository.GetLatestAsync();
|
||||
_latestRecord = record;
|
||||
ReportText = record is null ? "暂无报告。完成一次试验后会自动生成报告文本。" : _reportService.CreateTextReport(record);
|
||||
ExportStatusText = record is null ? "暂无可导出的报告。" : "Excel 报告将导出到桌面“整鞋试验报告”文件夹。";
|
||||
}
|
||||
|
||||
private async Task ExportLatestExcelAsync()
|
||||
{
|
||||
var record = _latestRecord ?? await _repository.GetLatestAsync();
|
||||
if (record is null)
|
||||
{
|
||||
ExportStatusText = "暂无可导出的报告。";
|
||||
return;
|
||||
}
|
||||
|
||||
var path = await _excelReportService.ExportAsync(record);
|
||||
ExportStatusText = $"已导出 Excel: {path}";
|
||||
}
|
||||
}
|
||||
50
FootwearTest/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FootwearTest.Services;
|
||||
|
||||
namespace FootwearTest.ViewModels;
|
||||
|
||||
public partial class SettingsViewModel : ViewModelBase
|
||||
{
|
||||
private readonly DeviceSettings _settings;
|
||||
|
||||
public SettingsViewModel(DeviceSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
Host = settings.Host;
|
||||
Port = settings.Port;
|
||||
UseSimulator = settings.UseSimulator;
|
||||
FootAreaSquareMeters = settings.FootAreaSquareMeters;
|
||||
CoefficientOfVariationLimitPercent = settings.CoefficientOfVariationLimitPercent;
|
||||
PumpSpeedCubicCentimetersPerHour = settings.PumpSpeedCubicCentimetersPerHour;
|
||||
SaveCommand = new RelayCommand(Save);
|
||||
}
|
||||
|
||||
public IRelayCommand SaveCommand { get; }
|
||||
|
||||
private string _host = "";
|
||||
private int _port;
|
||||
private bool _useSimulator;
|
||||
private double _footAreaSquareMeters;
|
||||
private double _coefficientOfVariationLimitPercent;
|
||||
private double _pumpSpeedCubicCentimetersPerHour;
|
||||
private string _saveStatus = "参数未保存";
|
||||
|
||||
public string Host { get => _host; set => SetProperty(ref _host, value); }
|
||||
public int Port { get => _port; set => SetProperty(ref _port, value); }
|
||||
public bool UseSimulator { get => _useSimulator; set => SetProperty(ref _useSimulator, value); }
|
||||
public double FootAreaSquareMeters { get => _footAreaSquareMeters; set => SetProperty(ref _footAreaSquareMeters, value); }
|
||||
public double CoefficientOfVariationLimitPercent { get => _coefficientOfVariationLimitPercent; set => SetProperty(ref _coefficientOfVariationLimitPercent, value); }
|
||||
public double PumpSpeedCubicCentimetersPerHour { get => _pumpSpeedCubicCentimetersPerHour; set => SetProperty(ref _pumpSpeedCubicCentimetersPerHour, value); }
|
||||
public string SaveStatus { get => _saveStatus; set => SetProperty(ref _saveStatus, value); }
|
||||
|
||||
private void Save()
|
||||
{
|
||||
_settings.Host = Host;
|
||||
_settings.Port = Port;
|
||||
_settings.UseSimulator = UseSimulator;
|
||||
_settings.FootAreaSquareMeters = FootAreaSquareMeters;
|
||||
_settings.CoefficientOfVariationLimitPercent = CoefficientOfVariationLimitPercent;
|
||||
_settings.PumpSpeedCubicCentimetersPerHour = PumpSpeedCubicCentimetersPerHour;
|
||||
SaveStatus = "参数已保存,新的试验流程将使用当前设置";
|
||||
}
|
||||
}
|
||||
8
FootwearTest/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace FootwearTest.ViewModels
|
||||
{
|
||||
public abstract class ViewModelBase : ObservableObject
|
||||
{
|
||||
}
|
||||
}
|
||||
62
FootwearTest/Views/DashboardView.axaml
Normal file
@@ -0,0 +1,62 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:FootwearTest.ViewModels"
|
||||
xmlns:models="using:FootwearTest.Models"
|
||||
x:Class="FootwearTest.Views.DashboardView"
|
||||
x:DataType="vm:DashboardViewModel">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="24" Spacing="18">
|
||||
<Grid ColumnDefinitions="*,*,*,*">
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18" Margin="0,0,14,0">
|
||||
<StackPanel>
|
||||
<TextBlock Text="假脚温度" Foreground="#5B6775"/>
|
||||
<TextBlock Text="{Binding Snapshot.FootTemperatureC, StringFormat='{}{0:F2} ℃'}" FontSize="28" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Grid.Column="1" Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18" Margin="0,0,14,0">
|
||||
<StackPanel>
|
||||
<TextBlock Text="环境温湿度" Foreground="#5B6775"/>
|
||||
<TextBlock Text="{Binding Snapshot.EnvironmentTemperatureC, StringFormat='{}{0:F2} ℃'}" FontSize="28" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding Snapshot.EnvironmentHumidityPercent, StringFormat='{}{0:F1}% RH'}" Foreground="#5B6775"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Grid.Column="2" Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18" Margin="0,0,14,0">
|
||||
<StackPanel>
|
||||
<TextBlock Text="风速" Foreground="#5B6775"/>
|
||||
<TextBlock Text="{Binding Snapshot.AirSpeedMetersPerSecond, StringFormat='{}{0:F2} m/s'}" FontSize="28" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Grid.Column="3" Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel>
|
||||
<TextBlock Text="功率 / 出汗量" Foreground="#5B6775"/>
|
||||
<TextBlock Text="{Binding Snapshot.PowerWatts, StringFormat='{}{0:F2} W'}" FontSize="28" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding Snapshot.WaterLossGramsPerHour, StringFormat='{}{0:F2} g/h'}" Foreground="#5B6775"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="最近试验" FontSize="18" FontWeight="SemiBold"/>
|
||||
<Button Grid.Column="1" Content="刷新" Command="{Binding RefreshCommand}"/>
|
||||
</Grid>
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding RecentRuns}" Margin="0,12,0,0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:TestRunSummary">
|
||||
<Border BorderBrush="#E4E9EF" BorderThickness="0,0,0,1" Padding="0,10">
|
||||
<Grid ColumnDefinitions="140,190,*,90">
|
||||
<TextBlock Text="{Binding CreatedAt, StringFormat='{}{0:MM-dd HH:mm}'}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Method}"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding ResultSummary}" TextWrapping="Wrap"/>
|
||||
<TextBlock Grid.Column="3" Text="{Binding IsValid, StringFormat='有效: {0}'}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
11
FootwearTest/Views/DashboardView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace FootwearTest.Views;
|
||||
|
||||
public partial class DashboardView : UserControl
|
||||
{
|
||||
public DashboardView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
41
FootwearTest/Views/HistoryView.axaml
Normal file
@@ -0,0 +1,41 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:FootwearTest.ViewModels"
|
||||
xmlns:models="using:FootwearTest.Models"
|
||||
x:Class="FootwearTest.Views.HistoryView"
|
||||
x:DataType="vm:HistoryViewModel">
|
||||
<Grid Margin="24" ColumnDefinitions="430,*">
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18" Margin="0,0,18,0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="历史记录" FontSize="18" FontWeight="SemiBold"/>
|
||||
<Button Grid.Column="1" Content="刷新" Command="{Binding LoadCommand}"/>
|
||||
</Grid>
|
||||
<ListBox Grid.Row="1" ItemsSource="{Binding Runs}" SelectedItem="{Binding SelectedRun}" Margin="0,12,0,0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:TestRunSummary">
|
||||
<StackPanel Margin="0,4">
|
||||
<TextBlock Text="{Binding Method}" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding CreatedAt, StringFormat='{}{0:yyyy-MM-dd HH:mm}'}" Foreground="#5B6775"/>
|
||||
<TextBlock Text="{Binding ResultSummary}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="1" Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="记录报告" FontSize="18" FontWeight="SemiBold"/>
|
||||
<Button Grid.Column="1" Content="导出 Excel 完整报告" Command="{Binding ExportSelectedExcelCommand}"/>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="1" Text="{Binding ExportStatusText}" Foreground="#1D5F96" TextWrapping="Wrap" Margin="0,10,0,0"/>
|
||||
<ScrollViewer Grid.Row="2" Margin="0,14,0,0">
|
||||
<TextBlock Text="{Binding SelectedReportText}" TextWrapping="Wrap" FontFamily="Consolas"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
11
FootwearTest/Views/HistoryView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace FootwearTest.Views;
|
||||
|
||||
public partial class HistoryView : UserControl
|
||||
{
|
||||
public HistoryView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
78
FootwearTest/Views/MainWindow.axaml
Normal file
@@ -0,0 +1,78 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:FootwearTest.ViewModels"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="800"
|
||||
x:Class="FootwearTest.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
Width="1280"
|
||||
Height="800"
|
||||
MinWidth="1100"
|
||||
MinHeight="720"
|
||||
WindowState="Maximized"
|
||||
Title="整鞋热阻和湿阻测定 HMI">
|
||||
|
||||
<Grid Background="#F3F5F7" ColumnDefinitions="220,*">
|
||||
<Border Grid.Column="0" Background="#17212B">
|
||||
<Grid RowDefinitions="Auto,*,Auto" Margin="18">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="FootwearTest" Foreground="White" FontSize="24" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="GB/T 33393-2023" Foreground="#AAB7C4" FontSize="13"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Spacing="8" Margin="0,28,0,0">
|
||||
<Button Command="{Binding ShowDashboardCommand}" HorizontalAlignment="Stretch" Background="#243343" BorderBrush="#34485C" Padding="14,10">
|
||||
<TextBlock Text="运行总览" Foreground="White" FontSize="15"/>
|
||||
</Button>
|
||||
<Button Command="{Binding ShowMethodACommand}" HorizontalAlignment="Stretch" Background="#243343" BorderBrush="#34485C" Padding="14,10">
|
||||
<TextBlock Text="方法 A" Foreground="White" FontSize="15"/>
|
||||
</Button>
|
||||
<Button Command="{Binding ShowMethodBCommand}" HorizontalAlignment="Stretch" Background="#243343" BorderBrush="#34485C" Padding="14,10">
|
||||
<TextBlock Text="方法 B" Foreground="White" FontSize="15"/>
|
||||
</Button>
|
||||
<Button Command="{Binding ShowHistoryCommand}" HorizontalAlignment="Stretch" Background="#243343" BorderBrush="#34485C" Padding="14,10">
|
||||
<TextBlock Text="历史记录" Foreground="White" FontSize="15"/>
|
||||
</Button>
|
||||
<Button Command="{Binding ShowReportCommand}" HorizontalAlignment="Stretch" Background="#243343" BorderBrush="#34485C" Padding="14,10">
|
||||
<TextBlock Text="试验报告" Foreground="White" FontSize="15"/>
|
||||
</Button>
|
||||
<Button Command="{Binding ShowSettingsCommand}" HorizontalAlignment="Stretch" Background="#243343" BorderBrush="#34485C" Padding="14,10">
|
||||
<TextBlock Text="系统设置" Foreground="White" FontSize="15"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2" Spacing="8">
|
||||
<TextBlock Text="{Binding ConnectionText}" Foreground="#D9E2EC" TextWrapping="Wrap"/>
|
||||
<Button Command="{Binding ConnectDeviceCommand}" HorizontalAlignment="Stretch" Background="#1F7A4D" BorderBrush="#2C9962" Padding="14,10">
|
||||
<TextBlock Text="连接设备" Foreground="White"/>
|
||||
</Button>
|
||||
<Button Command="{Binding DisconnectDeviceCommand}" HorizontalAlignment="Stretch" Background="#384858" BorderBrush="#4C6075" Padding="14,10">
|
||||
<TextBlock Text="断开连接" Foreground="White"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Column="1" RowDefinitions="76,*">
|
||||
<Border Background="White" BorderBrush="#D6DDE5" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="24,0">
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding CurrentPageTitle}" FontSize="24" FontWeight="SemiBold" Foreground="#16202A"/>
|
||||
<TextBlock Text="整鞋热阻、湿阻、吸湿透水汽和保暖性能测试" Foreground="#5B6775"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="16" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Snapshot.FootTemperatureC, StringFormat='假脚 {0:F1} ℃'}" Foreground="#16202A"/>
|
||||
<TextBlock Text="{Binding Snapshot.EnvironmentTemperatureC, StringFormat='环境 {0:F1} ℃'}" Foreground="#16202A"/>
|
||||
<TextBlock Text="{Binding Snapshot.EnvironmentHumidityPercent, StringFormat='湿度 {0:F0}%'}" Foreground="#16202A"/>
|
||||
<TextBlock Text="{Binding Snapshot.AlarmText}" Foreground="#256F46"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ContentControl Grid.Row="1" Content="{Binding CurrentPage}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
12
FootwearTest/Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace FootwearTest.Views
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
84
FootwearTest/Views/MethodAView.axaml
Normal file
@@ -0,0 +1,84 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:FootwearTest.ViewModels"
|
||||
xmlns:models="using:FootwearTest.Models"
|
||||
x:Class="FootwearTest.Views.MethodAView"
|
||||
x:DataType="vm:MethodAViewModel">
|
||||
<ScrollViewer>
|
||||
<Grid Margin="24" ColumnDefinitions="360,*">
|
||||
<StackPanel Spacing="14" Margin="0,0,18,0">
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="方法 A 试样与操作" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="试样描述" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding SampleDescription}"/>
|
||||
<Button Content="测定皮肤湿阻" Command="{Binding MeasureSkinResistanceCommand}"/>
|
||||
<Button Content="整鞋热阻/湿阻试验" Command="{Binding RunWholeShoeCommand}"/>
|
||||
<Button Content="停止" Command="{Binding StopCommand}"/>
|
||||
<TextBlock Text="{Binding StatusText}" Foreground="#1D5F96" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="实时结果" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding SkinMoistureResistance, StringFormat='皮肤湿阻 {0:F3} Pa·m²/W'}"/>
|
||||
<TextBlock Text="{Binding AverageMoistureResistance, StringFormat='平均湿阻 {0:F3} Pa·m²/W'}"/>
|
||||
<TextBlock Text="{Binding AverageThermalResistance, StringFormat='平均热阻 {0:F4} m²·℃/W'}"/>
|
||||
<TextBlock Text="{Binding MoistureCvPercent, StringFormat='湿阻 CV {0:F2}%'}"/>
|
||||
<TextBlock Text="{Binding ThermalCvPercent, StringFormat='热阻 CV {0:F2}%'}"/>
|
||||
<TextBlock Text="{Binding ResultText}" TextWrapping="Wrap" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="标准控制点" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="试样 2 只;试验前标准环境调节 24 h" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="环境 (23±2) ℃ / (50±5)% RH;风速 (1.00±0.15) m/s" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="假脚/模拟皮肤温度 (35.0±0.3) ℃" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="每分钟记录一次,连续记录至少 30 次;任一测定值偏差不超过 ±10%" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Column="1" Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<TextBlock Text="连续采样记录" FontSize="18" FontWeight="SemiBold"/>
|
||||
<Grid Grid.Row="1" ColumnDefinitions="42,92,78,78,78,78,78,78,88,88" Margin="0,12,0,6">
|
||||
<TextBlock Text="序号" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="1" Text="假脚℃" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="2" Text="环境℃" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="3" Text="湿度%" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="4" Text="出汗g/h" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="5" Text="功率W" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="6" Text="He W" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="7" Text="Hd W" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="8" Text="湿阻" Foreground="#5B6775"/>
|
||||
<TextBlock Grid.Column="9" Text="热阻" Foreground="#5B6775"/>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="2">
|
||||
<ItemsControl ItemsSource="{Binding Samples}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:MethodASampleRecord">
|
||||
<Grid ColumnDefinitions="42,92,78,78,78,78,78,78,88,88" Margin="0,0,0,6">
|
||||
<TextBlock Text="{Binding Index}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding FootTemperatureC, StringFormat='{}{0:F2}'}"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding EnvironmentTemperatureC, StringFormat='{}{0:F2}'}"/>
|
||||
<TextBlock Grid.Column="3" Text="{Binding RelativeHumidityPercent, StringFormat='{}{0:F1}'}"/>
|
||||
<TextBlock Grid.Column="4" Text="{Binding WaterLossGramsPerHour, StringFormat='{}{0:F2}'}"/>
|
||||
<TextBlock Grid.Column="5" Text="{Binding PowerWatts, StringFormat='{}{0:F2}'}"/>
|
||||
<TextBlock Grid.Column="6" Text="{Binding MoistureHeatWatts, StringFormat='{}{0:F3}'}"/>
|
||||
<TextBlock Grid.Column="7" Text="{Binding DryHeatWatts, StringFormat='{}{0:F3}'}"/>
|
||||
<TextBlock Grid.Column="8" Text="{Binding MoistureResistance, StringFormat='{}{0:F3}'}"/>
|
||||
<TextBlock Grid.Column="9" Text="{Binding ThermalResistance, StringFormat='{}{0:F4}'}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
11
FootwearTest/Views/MethodAView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace FootwearTest.Views;
|
||||
|
||||
public partial class MethodAView : UserControl
|
||||
{
|
||||
public MethodAView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
106
FootwearTest/Views/MethodBView.axaml
Normal file
@@ -0,0 +1,106 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:FootwearTest.ViewModels"
|
||||
x:Class="FootwearTest.Views.MethodBView"
|
||||
x:DataType="vm:MethodBViewModel">
|
||||
<ScrollViewer>
|
||||
<Grid Margin="24" ColumnDefinitions="420,*">
|
||||
<StackPanel Spacing="14" Margin="0,0,18,0">
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="方法 B 操作" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="试样描述" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding SampleDescription}"/>
|
||||
<Button Content="运行吸湿透水汽 180 min 流程" Command="{Binding RunMoistureCommand}"/>
|
||||
<Button Content="运行保暖性能 180 min 周期" Command="{Binding RunWarmthCommand}"/>
|
||||
<TextBlock Text="{Binding StatusText}" Foreground="#1D5F96" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="结果" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding MoistureResultText}" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding MoistureDetailText}" TextWrapping="Wrap" Foreground="#5B6775"/>
|
||||
<TextBlock Text="{Binding WarmthResultText}" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding WarmthDetailText}" TextWrapping="Wrap" Foreground="#5B6775"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="标准控制点" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="吸湿透水汽:泵流量 (5.0±0.3) cm³/h,周期 (180±1) min" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="称重精确到 0.01 g;m180 应在 15±0.9 g 范围内" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="结果:T180* 为透水汽性能,XT180* 为吸湿透水汽性能" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="保暖性能:假脚 (38±1) ℃,连续 2 个 180 min 周期" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="保暖性能输入" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="180 min 能量 P (kJ)" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding WarmthEnergyKilojoules}"/>
|
||||
<TextBlock Text="周期 t (s)" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding WarmthSeconds}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Column="1" RowDefinitions="Auto,Auto,*">
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="吸湿透水汽称重输入" FontSize="18" FontWeight="SemiBold"/>
|
||||
<Grid ColumnDefinitions="*,*,*,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<TextBlock Text="m11 皮肤前"/>
|
||||
<TextBox Grid.Row="1" Text="{Binding M11}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="1" Text="m12 皮肤后"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding M12}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="2" Text="m21 袜前"/>
|
||||
<TextBox Grid.Column="2" Grid.Row="1" Text="{Binding M21}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="3" Text="m22 袜后"/>
|
||||
<TextBox Grid.Column="3" Grid.Row="1" Text="{Binding M22}" Margin="0,0,0,8"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Text="m31 鞋前"/>
|
||||
<TextBox Grid.Row="3" Text="{Binding M31}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="1" Grid.Row="2" Text="m32 鞋后"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding M32}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="2" Text="m41 鞋垫前"/>
|
||||
<TextBox Grid.Column="2" Grid.Row="3" Text="{Binding M41}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="3" Grid.Row="2" Text="m42 鞋垫后"/>
|
||||
<TextBox Grid.Column="3" Grid.Row="3" Text="{Binding M42}" Margin="0,0,0,8"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Text="m51 C1前"/>
|
||||
<TextBox Grid.Row="5" Text="{Binding M51}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="1" Grid.Row="4" Text="m52 C1后"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="5" Text="{Binding M52}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="4" Text="m61 C2前"/>
|
||||
<TextBox Grid.Column="2" Grid.Row="5" Text="{Binding M61}" Margin="0,0,10,8"/>
|
||||
<TextBlock Grid.Column="3" Grid.Row="4" Text="m62 C2后"/>
|
||||
<TextBox Grid.Column="3" Grid.Row="5" Text="{Binding M62}" Margin="0,0,0,8"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Text="m71 C3前"/>
|
||||
<TextBox Grid.Row="7" Text="{Binding M71}" Margin="0,0,10,0"/>
|
||||
<TextBlock Grid.Column="1" Grid.Row="6" Text="m72 C3后"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="7" Text="{Binding M72}" Margin="0,0,10,0"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18" Margin="0,14,0,0">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="流程记录" FontSize="18" FontWeight="SemiBold"/>
|
||||
<ItemsControl ItemsSource="{Binding ProcedureLog}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" TextWrapping="Wrap" Margin="0,0,0,4"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
11
FootwearTest/Views/MethodBView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace FootwearTest.Views;
|
||||
|
||||
public partial class MethodBView : UserControl
|
||||
{
|
||||
public MethodBView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
19
FootwearTest/Views/ReportView.axaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:FootwearTest.ViewModels"
|
||||
x:Class="FootwearTest.Views.ReportView"
|
||||
x:DataType="vm:ReportViewModel">
|
||||
<Border Margin="24" Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||
<TextBlock Text="试验报告" FontSize="18" FontWeight="SemiBold"/>
|
||||
<Button Grid.Column="1" Content="加载最新报告" Command="{Binding LoadLatestCommand}"/>
|
||||
<Button Grid.Column="2" Content="导出 Excel 完整报告" Command="{Binding ExportLatestExcelCommand}" Margin="10,0,0,0"/>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="1" Text="{Binding ExportStatusText}" Foreground="#1D5F96" TextWrapping="Wrap" Margin="0,10,0,0"/>
|
||||
<ScrollViewer Grid.Row="2" Margin="0,14,0,0">
|
||||
<TextBlock Text="{Binding ReportText}" TextWrapping="Wrap" FontFamily="Consolas"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
11
FootwearTest/Views/ReportView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace FootwearTest.Views;
|
||||
|
||||
public partial class ReportView : UserControl
|
||||
{
|
||||
public ReportView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
34
FootwearTest/Views/SettingsView.axaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:FootwearTest.ViewModels"
|
||||
x:Class="FootwearTest.Views.SettingsView"
|
||||
x:DataType="vm:SettingsViewModel">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="24" Spacing="14" MaxWidth="640">
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="通讯设置" FontSize="18" FontWeight="SemiBold"/>
|
||||
<CheckBox Content="使用模拟器" IsChecked="{Binding UseSimulator}"/>
|
||||
<TextBlock Text="Modbus TCP IP" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding Host}"/>
|
||||
<TextBlock Text="端口" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding Port}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="White" BorderBrush="#DDE3EA" BorderThickness="1" CornerRadius="6" Padding="18">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="试验参数" FontSize="18" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="假脚表面积 S (m²)" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding FootAreaSquareMeters}"/>
|
||||
<TextBlock Text="方法 A 变异系数阈值 (%)" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding CoefficientOfVariationLimitPercent}"/>
|
||||
<TextBlock Text="方法 B 泵速 (cm³/h)" Foreground="#5B6775"/>
|
||||
<TextBox Text="{Binding PumpSpeedCubicCentimetersPerHour}"/>
|
||||
<Button Content="保存参数" Command="{Binding SaveCommand}" HorizontalAlignment="Left"/>
|
||||
<TextBlock Text="{Binding SaveStatus}" Foreground="#256F46"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
11
FootwearTest/Views/SettingsView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace FootwearTest.Views;
|
||||
|
||||
public partial class SettingsView : UserControl
|
||||
{
|
||||
public SettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
18
FootwearTest/app.manifest
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="FootwearTest.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
BIN
GBT+33393-2023.pdf
Normal file
BIN
tmp/pdfs/gbt33393_p06.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
tmp/pdfs/gbt33393_p07.png
Normal file
|
After Width: | Height: | Size: 249 KiB |
BIN
tmp/pdfs/gbt33393_p08.png
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
tmp/pdfs/gbt33393_p09.png
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
tmp/pdfs/gbt33393_p13.png
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
tmp/pdfs/gbt33393_p14.png
Normal file
|
After Width: | Height: | Size: 327 KiB |
BIN
tmp/pdfs/gbt33393_p15.png
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
tmp/pdfs/gbt33393_p16.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
tmp/pdfs/gbt33393_p17.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
3
整鞋试验热阻和湿阻测定.slnx
Normal file
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="FootwearTest/FootwearTest.csproj" />
|
||||
</Solution>
|
||||