更新最新版20260604

This commit is contained in:
GukSang.Jin
2026-06-04 09:22:18 +08:00
parent fff560db5d
commit 38eb461c36
3 changed files with 151 additions and 68 deletions

View File

@@ -21,9 +21,11 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
private const string DataSheetName = "实时数据";
private const int StandardTrialCount = 3;
public string Export(SlipReportExport report)
public string Export(SlipReportExport report, string? exportDirectory = null)
{
var directory = GetExportDirectory();
var directory = string.IsNullOrWhiteSpace(exportDirectory)
? GetDefaultExportDirectory()
: exportDirectory;
Directory.CreateDirectory(directory);
var safeNumber = MakeSafeFileName(string.IsNullOrWhiteSpace(report.TestNumber)
@@ -52,7 +54,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
return path;
}
private static string GetExportDirectory()
public static string GetDefaultExportDirectory()
{
var legacyDirectory = Path.Combine(@"D:\文件保存", DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture));
if (Directory.Exists(@"D:\"))

View File

@@ -1,3 +1,6 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -32,6 +35,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private const string DefaultAdcPortName = "COM4";
private const string LegacyPlcPortName = "COM7";
private const string LegacyAdcPortName = "COM8";
private static readonly TimeSpan ResetButtonPendingTimeout = TimeSpan.FromMilliseconds(800);
private readonly SlipResistanceDeviceService deviceService = new();
private readonly SlipExcelExportService excelExportService = new();
@@ -45,6 +49,9 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private bool isLoadingDeviceSettings;
private bool wasRunning;
private bool isResetButtonPending;
private bool hasObservedResetDeviceBusy;
private DateTime resetButtonPendingStartedAt = DateTime.MinValue;
private string activePlcPortName = string.Empty;
private string activeAdcPortName = string.Empty;
private int activeBaudRate;
@@ -258,7 +265,20 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
private async Task Clear()
{
ApplyConnectionSettings();
await RunDeviceCommand(deviceService.PulseResetAsync(), "已按老代码逻辑发送复位指令 M90");
BeginResetButtonFeedback();
try
{
await deviceService.PulseResetAsync();
CurrentStatus = "已按老代码逻辑发送复位指令 M90";
}
catch (Exception ex)
{
Log.Error(ex, "设备指令失败:已按老代码逻辑发送复位指令 M90");
CurrentStatus = $"设备指令失败:{ex.Message}";
ClearResetButtonFeedback();
UpdateResetButtonText(deviceService.CurrentSnapshot);
}
}
[RelayCommand]
@@ -293,7 +313,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
}
[RelayCommand]
private void ExportReport()
private async Task ExportReport()
{
var points = currentRun.Count > 0 ? currentRun.ToList() : lastCompletedRun;
if (points.Count == 0)
@@ -305,6 +325,15 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
try
{
CurrentStatus = "请选择 Excel 报告保存目录";
var exportDirectory = await SelectExportDirectoryAsync();
if (exportDirectory is null)
{
Log.Information("用户取消 Excel 导出目录选择TestNumber={TestNumber}, PointCount={PointCount}", TestNumber, points.Count);
CurrentStatus = "已取消 Excel 导出";
return;
}
var report = new SlipReportExport(
TestNumber,
OperatorName,
@@ -322,7 +351,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
Samples.ToList(),
points);
var path = excelExportService.Export(report);
var path = excelExportService.Export(report, exportDirectory);
UploadProgress = 100;
CurrentStatus = $"Excel 已导出:{path}";
}
@@ -333,6 +362,79 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
}
}
private async Task<string?> SelectExportDirectoryAsync()
{
var storageProvider = GetStorageProvider();
if (storageProvider is null || !storageProvider.CanPickFolder)
{
var defaultDirectory = SlipExcelExportService.GetDefaultExportDirectory();
Log.Warning("当前平台无法打开 Excel 导出目录选择器将使用默认目录Path={Path}", defaultDirectory);
return defaultDirectory;
}
var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "选择报告保存目录",
AllowMultiple = false,
SuggestedStartLocation = await TryGetSuggestedExportFolderAsync(storageProvider)
});
var selectedFolder = folders.FirstOrDefault();
if (selectedFolder is null)
{
return null;
}
var localPath = selectedFolder.TryGetLocalPath();
if (string.IsNullOrWhiteSpace(localPath) && selectedFolder.Path.IsFile)
{
localPath = selectedFolder.Path.LocalPath;
}
if (string.IsNullOrWhiteSpace(localPath))
{
throw new InvalidOperationException("选择的目录不是本地文件夹,无法保存 Excel");
}
return localPath;
}
private static async Task<IStorageFolder?> TryGetSuggestedExportFolderAsync(IStorageProvider storageProvider)
{
try
{
var defaultDirectory = SlipExcelExportService.GetDefaultExportDirectory();
if (Directory.Exists(defaultDirectory))
{
return await storageProvider.TryGetFolderFromPathAsync(defaultDirectory);
}
var parentDirectory = Path.GetDirectoryName(defaultDirectory);
if (!string.IsNullOrWhiteSpace(parentDirectory) && Directory.Exists(parentDirectory))
{
return await storageProvider.TryGetFolderFromPathAsync(parentDirectory);
}
return null;
}
catch (Exception ex)
{
Log.Warning(ex, "准备 Excel 默认导出目录失败,将不设置目录选择初始位置");
return null;
}
}
private static IStorageProvider? GetStorageProvider()
{
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop
&& desktop.MainWindow is not null)
{
return desktop.MainWindow.StorageProvider;
}
return null;
}
[RelayCommand]
private Task LiftMotion() => RunDeviceCommand(deviceService.ApplyOldLiftAsync(), "提升按老代码逻辑执行M4=0, M5=1");
@@ -478,7 +580,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
{
DeviceStatus = device.IsTestRunning ? "联机 / 测试中" : device.IsResetting ? "联机 / 复位中" : "联机 / 待机";
TestButtonText = device.IsTestRunning ? "停止" : "测试";
ResetButtonText = device.IsResetting ? "复位中" : "复位";
UpdateResetButtonText(device);
VerticalPressure = device.VerticalLoadN.ToString("F1", CultureInfo.InvariantCulture);
HorizontalForce = device.HorizontalFrictionN.ToString("F1", CultureInfo.InvariantCulture);
FrictionCoefficient = device.FrictionCoefficient.ToString("F3", CultureInfo.InvariantCulture);
@@ -489,7 +591,7 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
{
DeviceStatus = IsAdcCalibrationError(device.LastError) ? "数据无效" : "离线";
TestButtonText = device.IsTestRunning ? "停止" : "测试";
ResetButtonText = device.IsResetting ? "复位中" : "复位";
UpdateResetButtonText(device);
VerticalPressure = "--";
HorizontalForce = "--";
FrictionCoefficient = "--";
@@ -530,6 +632,37 @@ namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.ViewModel
wasRunning = isRecording;
}
private void BeginResetButtonFeedback()
{
isResetButtonPending = true;
hasObservedResetDeviceBusy = false;
resetButtonPendingStartedAt = DateTime.UtcNow;
ResetButtonText = "复位中";
}
private void ClearResetButtonFeedback()
{
isResetButtonPending = false;
hasObservedResetDeviceBusy = false;
resetButtonPendingStartedAt = DateTime.MinValue;
}
private void UpdateResetButtonText(SlipDeviceSnapshot device)
{
if (device.IsResetting)
{
hasObservedResetDeviceBusy = true;
}
else if (!device.IsConnected
|| hasObservedResetDeviceBusy
|| (isResetButtonPending && DateTime.UtcNow - resetButtonPendingStartedAt >= ResetButtonPendingTimeout))
{
ClearResetButtonFeedback();
}
ResetButtonText = device.IsResetting || isResetButtonPending ? "复位中" : "复位";
}
private void BeginRun()
{
currentRun.Clear();

View File

@@ -7,16 +7,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="1440"
d:DesignHeight="900"
d:DesignWidth="1024"
d:DesignHeight="768"
x:Class="Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
WindowState="Maximized"
Width="1280"
Height="800"
MinWidth="1024"
MinHeight="720"
Width="1024"
Height="768"
Title="鞋类 整鞋试验方法 防滑性能测定-GB/T 3903.6-2024"
Background="#F4F7FB"
BackgroundAnimationEnabled="False"
@@ -195,11 +193,11 @@
</suki:SukiWindow.Styles>
<Grid RowDefinitions="*"
Width="1264"
HorizontalAlignment="Left"
MaxWidth="1264"
HorizontalAlignment="Stretch"
Margin="8"
ClipToBounds="True">
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" RowSpacing="8">
<Grid Grid.Row="0" RowDefinitions="Auto,*" RowSpacing="8">
<Border Classes="panel">
<Grid ColumnDefinitions="1.2*,1.15*" ColumnSpacing="14">
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="Auto,*,Auto,*" RowSpacing="6" ColumnSpacing="8">
@@ -249,57 +247,7 @@
</Grid>
</Border>
<Border Grid.Row="1"
Background="{StaticResource PanelBrush}"
BorderBrush="{StaticResource LineBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="8,4"
ClipToBounds="True">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled"
ClipToBounds="True">
<StackPanel Orientation="Horizontal" Spacing="16">
<TextBlock Text="标准数据" FontWeight="SemiBold" VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="设备状态" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding DeviceStatus}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="正压力(N)" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding VerticalPressure}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="摩擦力(N)" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding HorizontalForce}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="位移(mm)" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Distance}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="实时系数" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding FrictionCoefficient}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="静摩擦系数" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding StaticCoefficient}" FontWeight="SemiBold" Foreground="#5B21B6" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="动摩擦系数" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding DynamicCoefficient}" FontWeight="SemiBold" Foreground="#047857" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="结果" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding ResultSummary}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Text="{Binding StandardReference}" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding CurrentStatus}" Foreground="{StaticResource TextSecondaryBrush}" VerticalAlignment="Center"/>
</StackPanel>
</ScrollViewer>
</Border>
<DockPanel Grid.Row="2" LastChildFill="True" MinHeight="0" ClipToBounds="True">
<DockPanel Grid.Row="1" LastChildFill="True" MinHeight="0" ClipToBounds="True">
<Border DockPanel.Dock="Left" Width="280" Classes="panel" Margin="0,0,10,0">
<Grid RowDefinitions="Auto,Auto,*" RowSpacing="8">
<TextBlock Text="实时数据"