using AIK.Common; using AIK.Common.AIKProtocol; using AIK.Common.Enum; using AIK.Common.Event; using AIK.Common.Interface; using AIK.Common.Language; using AIK.Models; using AIK.Models.HidModels; using AIK.Models.VideoModels; using AIK.Service.Extensions; using AIK.Service.IService; using AIK.Service.Service; using AIK.ViewModels.Messages; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.Logging; using Microsoft.VisualBasic; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks; using System.Windows; using System.Windows.Automation; using System.Windows.Input; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Threading; namespace AIK.ViewModels.CommonViewModel { public partial class KeyModelViewModel : ObservableObject, IRecipient, ILoadAware { private ObservableCollection _vehModelClasses; public ObservableCollection VehModelClasses { get => _vehModelClasses; set => SetProperty(ref _vehModelClasses, value); } private readonly IVehDataService _vehDataService; private ObservableCollection _allKeys; public ObservableCollection AllKeys { get => _allKeys; set => SetProperty(ref _allKeys, value); } [ObservableProperty] private string searchText = string.Empty; [ObservableProperty] private ObservableCollection _filterKeys; [ObservableProperty] private VehKeyClass _selectkeyItem; [ObservableProperty] private bool _isLoading = true; [ObservableProperty] private CacheInfo _cacheInfo = new CacheInfo(); [ObservableProperty] private bool _isCacheValid; private readonly IImageCacheService _imageCacheService; [ObservableProperty] private bool _isErrorState; [ObservableProperty] private string _errorMessage = "加载数据失败"; [ObservableProperty] private bool _isEmptyState; [ObservableProperty] private string _emptyStateMessage = "暂无数据"; [ObservableProperty] private string _messageTitle = "暂无数据"; private readonly IMessenger _messenger; private CarSelectMessage _parentmessage { get; set; } private DispatcherTimer _debounceTimer; private int _pendingCarId = -1; private int _currentCarId = -1; private CancellationTokenSource _currentLoadingCts; private ILogger _logger; private readonly IWindowService _windowService; private readonly IHidCommunicationService _hidCommunicationService; [ObservableProperty] private HidDeviceStatus _hidDeviceStatus = new HidDeviceStatus(); private readonly IModalDialogService _dialogService; [ObservableProperty] private bool _isOperationInProgress = false; private readonly IServiceProvider _serviceProvider; private readonly IFileServiceFactory _fileServiceFactory; private readonly IFileCacheService _fileCacheService; public Dictionary binKeyByteDic; [ObservableProperty] private string _localFilePathBin; //选中的钥匙型号 [ObservableProperty] private VehModelClass _selectedVehModelClass; public KeyModelViewModel(IVehDataService vehDataService, IMessenger messenger, IImageCacheService imageCacheService, ILogger logger, IWindowService windowService, IHidCommunicationService hidCommunicationService, IModalDialogService dialogService, IFileServiceFactory fileServiceFactory, IServiceProvider serviceProvider) { _vehDataService = vehDataService; _imageCacheService = imageCacheService; _messenger = messenger; _logger = logger; WeakReferenceMessenger.Default.Register(this); // 防抖计时器(250毫秒) _debounceTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; _debounceTimer.Tick += OnDebounceTimerTick; VehModelClasses = new ObservableCollection(); FilterKeys = new ObservableCollection(); _windowService = windowService; _hidCommunicationService = hidCommunicationService; _hidCommunicationService.ConnectionStateChanged += OnConnectionStateChanged; _dialogService = dialogService; _fileServiceFactory = fileServiceFactory; _fileCacheService = fileServiceFactory.GetService(Common.Enum.FileServiceType.BIN); } public async Task OnLoadedAsync() { //await _hidCommunicationService.ConnectAsync(); } #region 页面方法 // /// 选中事件 /// /// [RelayCommand] private async Task SelectVehModelClassItem(VehModelClass item) { SelectedVehModelClass = item; foreach (var key in VehModelClasses) { key.IsSelected = false; } if (item.ID == -1) { //全部遥控器 FilterKeys = AllKeys; } else { FilterKeys = new ObservableCollection(SelectedVehModelClass.modelList ?? new List()); } foreach (var keyItem in FilterKeys) { keyItem.IsSelected = false; } SelectkeyItem = null; item.IsSelected = true; var loadingToken = _currentLoadingCts?.Token ?? CancellationToken.None; _ = LoadImagesPathAsync(FilterKeys.ToList(), loadingToken); } /// /// 选中事件 /// /// [RelayCommand] private async Task SelectItem(VehKeyClass item) { SelectkeyItem = item; foreach (var key in FilterKeys) { key.IsSelected = false; } item.IsSelected = true; InitState(); binKeyByteDic = await GetBinFileByteDic(item.sourceUrl); } private async Task> GetBinFileByteDic(string sourceUrl) { if (string.IsNullOrEmpty(sourceUrl)) { return null; } Dictionary binfileDic = new Dictionary(); foreach (var key in sourceUrl.Split(',')) { string filename = Path.GetFileNameWithoutExtension(key); var fbyte = await _fileCacheService.GetFileByteAsync(key, filename); if (!binfileDic.ContainsKey(key)) { binfileDic[key] = fbyte; } } return binfileDic; } [RelayCommand] private void Search() { UpdateFilteredGroups(); } private void UpdateFilteredGroups() { FilterKeys.Clear(); var TempList = new List(); if (string.IsNullOrWhiteSpace(SearchText)) { FilterKeys = new ObservableCollection(AllKeys); } else { var searchLower = SearchText.ToLower(); TempList = AllKeys .Where(item => item.Name?.ToLower().Contains(searchLower) == true || item.modelEngName?.ToLower().Contains(searchLower) == true || item.modelInName?.ToLower().Contains(searchLower) == true || item.modelPuName?.ToLower().Contains(searchLower) == true || item.modelXiName?.ToLower().Contains(searchLower) == true) .ToList(); FilterKeys = new ObservableCollection(TempList); } } // 当搜索文本改变时自动过滤 partial void OnSearchTextChanged(string value) { UpdateFilteredGroups(); } private void InitState() { Application.Current.Dispatcher.Invoke(() => { IsLoading = false; IsErrorState = false; IsEmptyState = false; }); } [RelayCommand] private void ClickCloseMessage() { InitState(); } [RelayCommand] private void ConnectK3Tool() { StartBurning(); } [RelayCommand] private void ManualConnectK3Tool() { _hidCommunicationService.ReconnectNowAsync(); } private void StartBurning() { //_dialogService.ShowProgressDialogAsync(new ProgressDialogViewModel()); //string filePath = @"C:\AIK资料\测试相关数据\测试相关数据\1.bin"; //_dialogService.ShowProgressAsync( // async (progress, ct) => //{ // return await _hidCommunicationService.SendFileAsync( // filePath, // progress, // ct); //}); //_hidCommunicationService.SendFileAsync(filePath); if (SelectkeyItem.support.Contains("4")) { string tips = AiKLanguage.LanguageKey.GetValueOrDefault("BurnCSubKeyMobileAppTips"); _dialogService.ShowQRCodeDialogAsync(_serviceProvider,tips, string.Empty, MessageTypeEnum.Warning, true, false); return; } if (SelectkeyItem.support.Contains("11")) { string tips = AiKLanguage.LanguageKey.GetValueOrDefault("BurnCFSubKeyMobileAppTips"); _dialogService.ShowQRCodeDialogAsync(_serviceProvider, tips, string.Empty, MessageTypeEnum.Warning, true, false); return; } if (!string.IsNullOrEmpty(LocalFilePathBin)) { var filebin = File.ReadAllBytes(LocalFilePathBin); if (filebin == null || filebin.Length == 0) { _dialogService.ShowDialogAsync(AiKLanguage.LanguageKey.GetValueOrDefault("FileNotFound"), AiKLanguage.LanguageKey.GetValueOrDefault("BurnTips"), MessageTypeEnum.Warning, true, false); } else { string tempfilename = Path.GetFileName(LocalFilePathBin); _dialogService.ShowProgressAsync( async (progress, ct) => { return await _hidCommunicationService.SendFileProcessAsync( tempfilename, filebin, progress, ct); }); } } else { if (SelectkeyItem != null && binKeyByteDic != null && binKeyByteDic.Count > 0) { string keyname = SelectkeyItem.sourceUrl.Split(',').FirstOrDefault(); if (binKeyByteDic.TryGetValue(keyname, out byte[] filebyte)) { _dialogService.ShowProgressAsync( async (progress, ct) => { return await _hidCommunicationService.SendFileProcessAsync( keyname, filebyte, progress, ct); }); } } else { _dialogService.ShowDialogAsync(AiKLanguage.LanguageKey.GetValueOrDefault("FileNotFound"), AiKLanguage.LanguageKey.GetValueOrDefault("BurnTips"), MessageTypeEnum.Warning, true, false); } } } [RelayCommand] private void ShowVideo() { _windowService.Show(WindowKey.VideoPopup, typeof(VideoPlayerViewModel)); WeakReferenceMessenger.Default.Send(new ShowVideoMessage { VideoPath = "C:\\Users\\23231\\Downloads\\SP2MP4.mp4", Title = "钥匙演示视频" }); WeakReferenceMessenger.Default.Send(new VideoControlMessage { Action = VideoControlMessage.ControlAction.Play }); } #endregion #region 防抖操作 private async Task ProcessCarSelectionAsync(int carId) { // 取消之前的加载任务 _currentLoadingCts?.Cancel(); _currentLoadingCts?.Dispose(); _currentLoadingCts = new CancellationTokenSource(); var cancellationToken = _currentLoadingCts.Token; try { await Application.Current.Dispatcher.InvokeAsync(() => { IsLoading = true; if (FilterKeys != null) { FilterKeys.Clear(); } }, System.Windows.Threading.DispatcherPriority.Send); await LoadImagesForCurrentCollectionAsync(carId, cancellationToken); _currentCarId = carId; // 更新当前显示的车型ID } catch (OperationCanceledException) { // 任务被取消是正常情况 System.Diagnostics.Debug.WriteLine($"加载任务被取消: {carId}"); } catch (Exception ex) { // 错误处理 await Application.Current.Dispatcher.InvokeAsync(() => { IsErrorState = true; ErrorMessage = $"加载失败: {ex.Message}"; IsLoading = false; }); } } private void OnDebounceTimerTick(object sender, EventArgs e) { _debounceTimer.Stop(); if (_pendingCarId != -1) { _ = ProcessCarSelectionAsync(_pendingCarId); _pendingCarId = -1; } } #endregion #region 消息传递 public void Receive(CarSelectMessage message) { if (message != null) { if (message.Value == null) { VehModelClasses = new ObservableCollection(); SelectedVehModelClass = null; AllKeys = new ObservableCollection(); FilterKeys = new ObservableCollection(); IsEmptyState = true; EmptyStateMessage = AiKLanguage.LanguageKey.GetValueOrDefault("NoKeyData"); IsLoading = false; return; } InitState(); SelectkeyItem = null; var carId = int.Parse(message.Value.id); // 检查是否是当前正在显示的数据 //if (_currentCarId == carId) //{ // return; // 已经是当前数据,忽略请求 //} // 防抖处理 _debounceTimer.Stop(); _pendingCarId = carId; _debounceTimer.Start(); //#region 测试 //IsErrorState = true; //MessageTitle = "网络异常"; //ErrorMessage = "网络连接失败,图片下载缓存成功https://keytest.aik518.com/prod-api/work_file/file/file_download?objectName=/keys/images/DF8.03.03.jpg,网络连接失败,图片下载缓存成功,网络连接失败,图片下载缓存成功"; //#endregion } } private async Task LoadImagesForCurrentCollectionAsync(int id, CancellationToken cancellationToken = default) { try { //// 强制UI更新 await Task.Delay(2); // 让UI线程有机会处理渲染 cancellationToken.ThrowIfCancellationRequested(); // 第一步:在后台线程加载数据 var (keylist, filteredItems) = await Task.Run(async () => { cancellationToken.ThrowIfCancellationRequested(); //var keylist = await _vehDataService.GetVehKeyDataAsync(id); var keylist = await _vehDataService.GetVehKeyDataByIdAsync(id); var filteredItems = new List(); foreach (var key in keylist) { if (cancellationToken.IsCancellationRequested) break; if (key.modelList == null) continue; foreach (var item in key.modelList) { //放出所有型号 //if (item.support == null || (!item.support.Contains("1") && !item.support.Contains("2"))) // continue; // 快速处理 item.IsSelected = false; //C子机 4 if (item.support.Contains("4")) { item.IsShowLabel = true; } if (item.kzInfoList.Count > 0 && item.kzInfoList[0].cloudRemote) { item.IsShowCloudLabel = true; //item.CloudLabel = "CloudData"; item.CloudLabel = AiKLanguage.LanguageKey.GetValueOrDefault("CloudData"); } if (item.support.Contains("11")) { item.IsShowCFLabel = true; } // 优化字符串处理 if (!string.IsNullOrEmpty(item.information) && item.information.Contains(':')) { var colonIndex = item.information.IndexOf(':'); if (colonIndex > 0 && colonIndex < item.information.Length - 1) { item.information = item.information.Substring(colonIndex + 1).Replace(" ", ""); } } filteredItems.Add(item); } } return (keylist, filteredItems); }, cancellationToken); // 第二步:在UI线程更新集合 // 阶段2:立即显示基础数据(无图片) await Application.Current.Dispatcher.InvokeAsync(() => { cancellationToken.ThrowIfCancellationRequested(); var defaultState = BuildDefaultVisibleKeysState(keylist, filteredItems); VehModelClasses = new ObservableCollection(defaultState.VehModelClasses); AllKeys = new ObservableCollection(defaultState.AllKeys); FilterKeys = new ObservableCollection(defaultState.VisibleKeys); SelectedVehModelClass = defaultState.SelectedVehModelClass; SelectkeyItem = null; if (filteredItems.Count == 0) { IsEmptyState = true; EmptyStateMessage = AiKLanguage.LanguageKey.GetValueOrDefault("NoKeyData"); } //IsLoading = false; // 立即隐藏加载动画,让用户看到内容 }, System.Windows.Threading.DispatcherPriority.Normal, cancellationToken); if (FilterKeys.Count > 0) { _ = LoadImagesPathAsync(FilterKeys.ToList(), cancellationToken); } } catch (Exception ex) { await Application.Current.Dispatcher.InvokeAsync(() => { IsErrorState = true; ErrorMessage = $"加载数据失败:{ex.Message}"; }); } finally { IsLoading = false; } } /// /// 异步加载车钥匙图片(BitmapImage内存占用较高) /// /// /// private async Task LoadImagesAsync(List items, CancellationToken cancellationToken = default) { try { var itemsWithImages = items.Where(keym => !string.IsNullOrEmpty(keym.picUrl)).ToList(); // 使用信号量限制并发数量 var semaphore = new SemaphoreSlim(initialCount: 5, maxCount: 5); var imageTasks = itemsWithImages.Select(async keym => { await semaphore.WaitAsync(cancellationToken); try { cancellationToken.ThrowIfCancellationRequested(); var imageUrl = keym.picUrl.Split(',')[0]; var bitmapImage = await _imageCacheService.GetImageAsync(imageUrl, null, cancellationToken); if (bitmapImage != null) { bitmapImage.Freeze(); await Application.Current.Dispatcher.InvokeAsync(() => { keym.BitmapImage = bitmapImage; }, System.Windows.Threading.DispatcherPriority.Background); } } catch (OperationCanceledException) { // 单个图片加载被取消,静默处理 _logger.LogDebug("图片加载被取消: {ImageUrl}", keym.picUrl); } catch (Exception ex) { _logger.LogWarning(ex, "加载图片失败: {ImageUrl}", keym.picUrl); } finally { semaphore.Release(); } }); await Task.WhenAll(imageTasks); } catch (OperationCanceledException) { _logger.LogInformation("图片加载操作被取消"); // 不需要重新抛出,这是正常的取消操作 } catch (Exception ex) { _logger.LogError(ex, "加载图片时发生错误"); throw; } } /// /// 异步加载车钥匙图片(图片路径) /// /// /// private async Task LoadImagesPathAsync(List items, CancellationToken cancellationToken = default) { try { var itemsWithImages = items .Where(keym => !string.IsNullOrEmpty(keym.picUrl) && string.IsNullOrEmpty(keym.LocalImagePath)) .ToList(); // 使用信号量限制并发数量 var semaphore = new SemaphoreSlim(initialCount: 5, maxCount: 5); var imageTasks = itemsWithImages.Select(async keym => { await semaphore.WaitAsync(cancellationToken); try { cancellationToken.ThrowIfCancellationRequested(); var imageUrl = keym.picUrl.Split(',')[0]; var localiamgepath = await _imageCacheService.GetImagePathAsync(imageUrl, null, cancellationToken); if (!string.IsNullOrEmpty(localiamgepath)) { await Application.Current.Dispatcher.InvokeAsync(() => { keym.LocalImagePath = localiamgepath; }, System.Windows.Threading.DispatcherPriority.Background); } } catch (OperationCanceledException) { // 单个图片加载被取消,静默处理 _logger.LogDebug("图片加载被取消: {ImageUrl}", keym.picUrl); } catch (Exception ex) { _logger.LogWarning(ex, "加载图片失败: {ImageUrl}", keym.picUrl); } finally { semaphore.Release(); } }); await Task.WhenAll(imageTasks); } catch (OperationCanceledException) { _logger.LogInformation("图片加载操作被取消"); // 不需要重新抛出,这是正常的取消操作 } catch (Exception ex) { _logger.LogError(ex, "加载图片时发生错误"); throw; } } private static DefaultVisibleKeysState BuildDefaultVisibleKeysState(List keylist, List allKeys) { var vehModelClasses = keylist ?? new List(); foreach (var vehModel in vehModelClasses) { vehModel.IsSelected = false; } var allKeysEntry = new VehModelClass { ID = -1, typeName = "全部遥控器", IsSelected = false }; vehModelClasses.Insert(0, allKeysEntry); var selectedVehModelClass = vehModelClasses .Skip(1) .FirstOrDefault(model => model.modelList?.Any() == true) ?? vehModelClasses.Skip(1).FirstOrDefault(); if (selectedVehModelClass != null) { selectedVehModelClass.IsSelected = true; } var visibleKeys = selectedVehModelClass?.modelList?.ToList() ?? new List(); return new DefaultVisibleKeysState { VehModelClasses = vehModelClasses, SelectedVehModelClass = selectedVehModelClass, AllKeys = allKeys ?? new List(), VisibleKeys = visibleKeys }; } private sealed class DefaultVisibleKeysState { public List VehModelClasses { get; init; } = new(); public VehModelClass? SelectedVehModelClass { get; init; } public List AllKeys { get; init; } = new(); public List VisibleKeys { get; init; } = new(); } #endregion #region 连接状态事件处理 private void OnConnectionStateChanged(object? sender, ConnectionStateChangedEventArgs e) { HidDeviceStatus.DeviceStatus = e.IsConnected; if (e.IsConnected) { HidDeviceStatus.DeviceStatusText = AiKLanguage.LanguageKey.GetValueOrDefault("Connected"); HidDeviceStatus.DeviceStatusColor = "#28a745"; HidDeviceStatus.DeviceStatusIcon = "\uE62a"; } else { HidDeviceStatus.DeviceStatusText = AiKLanguage.LanguageKey.GetValueOrDefault("Disconnected"); ; HidDeviceStatus.DeviceStatusColor = "#FFF44336"; HidDeviceStatus.DeviceStatusIcon = "\uE6f9"; } } #endregion } }