using System.Security; using System.Text.Json; using System.Windows.Input; namespace Zcbot.WindowsNode; internal sealed class AnsysAcceptanceViewModel : ObservableObject, IDisposable { private readonly IAnsysAcceptanceService service; private readonly AsyncCommand runCommand; private readonly RelayCommand cancelCommand; private readonly RelayCommand enableGateCommand; private CancellationTokenSource? stop; private string? passedReportPath; private string message; private string tone; internal AnsysAcceptanceViewModel(IAnsysAcceptanceService service) { this.service = service; (message, tone) = GateStatus(); runCommand = new AsyncCommand(RunAsync, () => IsAvailable && !IsRunning); cancelCommand = new RelayCommand(Cancel, () => IsRunning); enableGateCommand = new RelayCommand(EnableGate, () => CanEnableGate); RunCommand = runCommand; CancelCommand = cancelCommand; EnableGateCommand = enableGateCommand; } internal event Func? ChooseReportDirectoryRequested; internal event Func? ConfirmRunRequested; internal event Func? ConfirmEnableGateRequested; public bool IsAvailable => service.IsAvailable; public bool IsRunning => stop is not null; public bool IsGateEnabled => service.IsGateEnabled; public bool CanEnableGate => passedReportPath is not null && !IsGateEnabled && !IsRunning; public string Message { get => message; private set => SetProperty(ref message, value); } public string Tone { get => tone; private set => SetProperty(ref tone, value); } public ICommand RunCommand { get; } public ICommand CancelCommand { get; } public ICommand EnableGateCommand { get; } internal void RefreshStatus() { if (IsRunning) return; var status = GateStatus(); Message = status.Message; Tone = status.Tone; RaiseStateChanged(); } internal void Cancel() => stop?.Cancel(); public void Dispose() { stop?.Cancel(); stop?.Dispose(); stop = null; } private async Task RunAsync() { if (!IsAvailable) { Message = "当前安装包没有 ANSYS 验收工具。请先安装新版完整 Node 包。"; Tone = "error"; return; } var parent = ChooseReportDirectoryRequested?.Invoke(); if (string.IsNullOrWhiteSpace(parent) || ConfirmRunRequested?.Invoke() != true) { return; } var workRoot = Path.Combine( parent, $"zcbot-ansys-acceptance-{DateTime.Now:yyyyMMdd-HHmmss}"); stop?.Dispose(); stop = new CancellationTokenSource(); passedReportPath = null; Message = "正在启动真机验收…"; Tone = "warning"; RaiseStateChanged(); var progress = new Progress(line => Message = line); try { var result = await service.RunAsync(workRoot, progress, stop.Token); passedReportPath = result.Passed ? result.ReportPath : null; Tone = result.Passed ? "success" : "error"; Message = result.Passed ? $"验收通过。报告:{result.ReportPath}\n请确认许可证管理端席位已释放,再开启执行门。" : $"验收未通过:{result.Detail}\n报告:{result.ReportPath}"; } catch (OperationCanceledException) { passedReportPath = null; Tone = "neutral"; Message = "验收已停止,执行门保持关闭。"; } catch (Exception exception) when ( exception is IOException or InvalidDataException or InvalidOperationException or JsonException or UnauthorizedAccessException) { passedReportPath = null; Tone = "error"; Message = $"验收启动或执行失败:{exception.Message}"; } finally { stop.Dispose(); stop = null; RaiseStateChanged(); } } private void EnableGate() { if (passedReportPath is null || ConfirmEnableGateRequested?.Invoke() != true) { return; } try { service.EnableGate(passedReportPath); Tone = "success"; Message = "机器级执行门已写入。请从托盘退出 Node 后重新启动;重启后 ANSYS 应显示可用。"; } catch (Exception exception) when ( exception is InvalidDataException or SecurityException or UnauthorizedAccessException) { Tone = "error"; Message = exception is SecurityException or UnauthorizedAccessException ? $"写入机器级环境变量需要管理员权限:{exception.Message}\n请退出 Node,以管理员身份启动后再次点击。" : exception.Message; } RaiseStateChanged(); } private (string Message, string Tone) GateStatus() => IsGateEnabled ? ("当前执行门:已开启。Node 重启并且 probe 正常后可接收 ANSYS 任务。", "success") : ("当前执行门:关闭。安装成功不代表验收通过,请先运行本页验收。", "neutral"); private void RaiseStateChanged() { OnPropertyChanged(nameof(IsRunning)); OnPropertyChanged(nameof(IsGateEnabled)); OnPropertyChanged(nameof(CanEnableGate)); runCommand.RaiseCanExecuteChanged(); cancelCommand.RaiseCanExecuteChanged(); enableGateCommand.RaiseCanExecuteChanged(); } }