Explorar o código

feat(ui): 设备详情增加加载遮罩并禁用交互

在读取、校验及应用配置时显示 BusyOverlay,防止重复操作。
yangkaixiang hai 1 mes
pai
achega
19343e589f

+ 26 - 1
windows/QuickIP.Client/DeviceDetailsWindow.xaml

@@ -10,7 +10,8 @@
         ShowInTaskbar="False"
         WindowStartupLocation="CenterOwner">
     <Grid Background="#F5F7FB">
-        <ScrollViewer Margin="20"
+        <ScrollViewer x:Name="ContentScrollViewer"
+                      Margin="20"
                       VerticalScrollBarVisibility="Auto"
                       HorizontalScrollBarVisibility="Disabled"
                       CanContentScroll="True">
@@ -220,6 +221,30 @@
         </Grid>
         </ScrollViewer>
 
+        <Border x:Name="BusyOverlay"
+                Visibility="Collapsed"
+                Panel.ZIndex="90"
+                Background="#80F5F7FB">
+            <Border HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    Padding="18,16"
+                    Background="White"
+                    CornerRadius="12">
+                <StackPanel>
+                    <ProgressBar Width="220"
+                                 Height="6"
+                                 IsIndeterminate="True" />
+                    <TextBlock x:Name="BusyMessageTextBlock"
+                               Margin="0,12,0,0"
+                               HorizontalAlignment="Center"
+                               FontSize="13"
+                               FontWeight="SemiBold"
+                               Foreground="#111827"
+                               Text="正在处理,请稍候..." />
+                </StackPanel>
+            </Border>
+        </Border>
+
         <Border x:Name="StatusMessageBorder"
                 Visibility="Collapsed"
                 Panel.ZIndex="100"

+ 95 - 45
windows/QuickIP.Client/DeviceDetailsWindow.xaml.cs

@@ -15,6 +15,7 @@ public partial class DeviceDetailsWindow : Window
     private readonly string _localIPv4;
     private readonly string _password;
     private bool _configValidated;
+    private bool _isBusy;
     private bool _suppressConfigChangeHandling;
     private CancellationTokenSource? _statusMessageCts;
 
@@ -86,39 +87,55 @@ public partial class DeviceDetailsWindow : Window
     {
         if (RemoteTargetInterfaceComboBox.SelectedItem is not RemoteInterfaceInfo selected)
         {
+            UpdateButtonStates();
             return;
         }
 
-        await LoadRemoteInterfaceConfigAsync(selected.SystemName);
+        await LoadRemoteInterfaceConfigAsync(selected.SystemName, useBusyState: true);
     }
 
-    private async Task LoadRemoteInterfaceConfigAsync(string interfaceName)
+    private async Task LoadRemoteInterfaceConfigAsync(string interfaceName, bool useBusyState = false)
     {
-        var result = await _agentApiService.GetInterfaceConfigAsync(_baseAddress, _password, _localIPv4, interfaceName);
-        if (!result.Success || result.Data is null)
+        if (useBusyState)
         {
-            RemoteConfigInterfaceTextBlock.Text = interfaceName;
-            RemoteConfigIpTextBlock.Text = "读取失败";
-            RemoteConfigGatewayTextBlock.Text = "读取失败";
-            RemoteConfigDnsTextBlock.Text = "读取失败";
-            ShowStatusMessage($"读取目标接口 {interfaceName} 配置失败:{result.Message}");
-            return;
+            SetBusyState(true, "正在读取 Linux 端 IP 配置...");
         }
 
-        var config = result.Data;
-        RemoteConfigInterfaceTextBlock.Text = config.Interface;
-        RemoteConfigIpTextBlock.Text = string.IsNullOrWhiteSpace(config.IP) ? "无" : $"{config.IP}/{config.Prefix}";
-        RemoteConfigGatewayTextBlock.Text = string.IsNullOrWhiteSpace(config.Gateway) ? "无" : config.Gateway;
-        RemoteConfigDnsTextBlock.Text = config.DnsSummary;
-        _suppressConfigChangeHandling = true;
-        NewIpTextBox.Text = config.IP;
-        NewMaskTextBox.Text = PrefixToMask(config.Prefix);
-        NewGatewayTextBox.Text = config.Gateway;
-        NewDnsTextBox.Text = config.Dns.FirstOrDefault() ?? string.Empty;
-        _suppressConfigChangeHandling = false;
-        _configValidated = false;
-        ShowStatusMessage("已回填目标接口当前配置,可直接修改后校验。");
-        UpdateButtonStates();
+        try
+        {
+            var result = await _agentApiService.GetInterfaceConfigAsync(_baseAddress, _password, _localIPv4, interfaceName);
+            if (!result.Success || result.Data is null)
+            {
+                RemoteConfigInterfaceTextBlock.Text = interfaceName;
+                RemoteConfigIpTextBlock.Text = "读取失败";
+                RemoteConfigGatewayTextBlock.Text = "读取失败";
+                RemoteConfigDnsTextBlock.Text = "读取失败";
+                ShowStatusMessage($"读取目标接口 {interfaceName} 配置失败:{result.Message}");
+                return;
+            }
+
+            var config = result.Data;
+            RemoteConfigInterfaceTextBlock.Text = config.Interface;
+            RemoteConfigIpTextBlock.Text = string.IsNullOrWhiteSpace(config.IP) ? "无" : $"{config.IP}/{config.Prefix}";
+            RemoteConfigGatewayTextBlock.Text = string.IsNullOrWhiteSpace(config.Gateway) ? "无" : config.Gateway;
+            RemoteConfigDnsTextBlock.Text = config.DnsSummary;
+            _suppressConfigChangeHandling = true;
+            NewIpTextBox.Text = config.IP;
+            NewMaskTextBox.Text = PrefixToMask(config.Prefix);
+            NewGatewayTextBox.Text = config.Gateway;
+            NewDnsTextBox.Text = config.Dns.FirstOrDefault() ?? string.Empty;
+            _suppressConfigChangeHandling = false;
+            _configValidated = false;
+            ShowStatusMessage("已读取Linux端IP配置。");
+            UpdateButtonStates();
+        }
+        finally
+        {
+            if (useBusyState)
+            {
+                SetBusyState(false);
+            }
+        }
     }
 
     private async void ReloadInterfaceConfigButton_OnClick(object sender, RoutedEventArgs e)
@@ -142,21 +159,28 @@ public partial class DeviceDetailsWindow : Window
             return;
         }
 
-        var result = await _agentApiService.ValidateInterfaceConfigAsync(_baseAddress, _password, _localIPv4, request);
-        _configValidated = result.Success && result.Data?.Valid == true;
-        if (result.Data is not null)
+        SetBusyState(true, "正在校验配置,请稍候...");
+        try
         {
-            var warnings = result.Data.Warnings.Count > 0 ? $" 警告:{string.Join(";", result.Data.Warnings)}" : string.Empty;
-            var errors = result.Data.Errors.Count > 0 ? $" 错误:{string.Join(";", result.Data.Errors)}" : string.Empty;
-            ShowStatusMessage(result.Success ? $"校验通过。{warnings}" : $"校验失败。{errors}{warnings}");
+            var result = await _agentApiService.ValidateInterfaceConfigAsync(_baseAddress, _password, _localIPv4, request);
+            _configValidated = result.Success && result.Data?.Valid == true;
+            if (result.Data is not null)
+            {
+                var warnings = result.Data.Warnings.Count > 0 ? $" 警告:{string.Join(";", result.Data.Warnings)}" : string.Empty;
+                var errors = result.Data.Errors.Count > 0 ? $" 错误:{string.Join(";", result.Data.Errors)}" : string.Empty;
+                ShowStatusMessage(_configValidated ? $"校验通过,可应用配置。{warnings}" : $"校验失败。{errors}{warnings}");
+            }
+            else
+            {
+                ShowStatusMessage($"校验失败:{result.Message}");
+            }
+
+            UpdateButtonStates();
         }
-        else
+        finally
         {
-            ShowStatusMessage($"校验失败:{result.Message}");
+            SetBusyState(false);
         }
-
-        ShowStatusMessage(_configValidated ? "配置已通过校验,可提交应用。" : "当前配置尚未通过校验。");
-        UpdateButtonStates();
     }
 
     private async void ApplyConfigButton_OnClick(object sender, RoutedEventArgs e)
@@ -182,15 +206,23 @@ public partial class DeviceDetailsWindow : Window
             return;
         }
 
-        var applyResult = await _agentApiService.ApplyInterfaceConfigAsync(_baseAddress, _password, _localIPv4, request);
-        if (!applyResult.Success || applyResult.Data is null)
+        SetBusyState(true, "正在提交并应用配置,请稍候...");
+        try
         {
-            ShowStatusMessage($"提交配置任务失败:{applyResult.Message}");
-            return;
-        }
+            var applyResult = await _agentApiService.ApplyInterfaceConfigAsync(_baseAddress, _password, _localIPv4, request);
+            if (!applyResult.Success || applyResult.Data is null)
+            {
+                ShowStatusMessage($"提交配置任务失败:{applyResult.Message}");
+                return;
+            }
 
-        ShowStatusMessage($"配置任务已提交:{applyResult.Data.TaskId},正在轮询状态。");
-        await PollTaskAsync(applyResult.Data.TaskId);
+            ShowStatusMessage($"配置任务已提交:{applyResult.Data.TaskId},正在轮询状态。");
+            await PollTaskAsync(applyResult.Data.TaskId);
+        }
+        finally
+        {
+            SetBusyState(false);
+        }
     }
 
     private async Task PollTaskAsync(string taskId)
@@ -427,8 +459,26 @@ public partial class DeviceDetailsWindow : Window
 
     private void UpdateButtonStates()
     {
-        ReloadInterfaceConfigButton.IsEnabled = RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
-        ValidateConfigButton.IsEnabled = RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
-        ApplyConfigButton.IsEnabled = _configValidated && RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
+        var hasSelectedInterface = RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
+        var canEdit = !_isBusy && hasSelectedInterface;
+
+        RemoteTargetInterfaceComboBox.IsEnabled = !_isBusy && RemoteTargetInterfaceComboBox.Items.Count > 0;
+        ReloadInterfaceConfigButton.IsEnabled = canEdit;
+        ValidateConfigButton.IsEnabled = canEdit;
+        ApplyConfigButton.IsEnabled = !_isBusy && _configValidated && hasSelectedInterface;
+        NewIpTextBox.IsEnabled = canEdit;
+        NewMaskTextBox.IsEnabled = canEdit;
+        NewGatewayTextBox.IsEnabled = canEdit;
+        NewDnsTextBox.IsEnabled = canEdit;
+        RebootButton.IsEnabled = !_isBusy;
+        ShutdownButton.IsEnabled = !_isBusy;
+    }
+
+    private void SetBusyState(bool isBusy, string? message = null)
+    {
+        _isBusy = isBusy;
+        BusyOverlay.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
+        BusyMessageTextBlock.Text = string.IsNullOrWhiteSpace(message) ? "正在处理,请稍候..." : message;
+        UpdateButtonStates();
     }
 }