You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
716 lines
29 KiB
716 lines
29 KiB
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<CarSelectMessage>, ILoadAware
|
|
{
|
|
private ObservableCollection<VehModelClass> _vehModelClasses;
|
|
public ObservableCollection<VehModelClass> VehModelClasses
|
|
{
|
|
get => _vehModelClasses;
|
|
set => SetProperty(ref _vehModelClasses, value);
|
|
}
|
|
private readonly IVehDataService _vehDataService;
|
|
private ObservableCollection<VehKeyClass> _allKeys;
|
|
public ObservableCollection<VehKeyClass> AllKeys
|
|
{
|
|
get => _allKeys;
|
|
set => SetProperty(ref _allKeys, value);
|
|
}
|
|
[ObservableProperty]
|
|
private string searchText = string.Empty;
|
|
[ObservableProperty]
|
|
private ObservableCollection<VehKeyClass> _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<KeyModelViewModel> _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<string, byte[]> binKeyByteDic;
|
|
[ObservableProperty]
|
|
private string _localFilePathBin;
|
|
//选中的钥匙型号
|
|
[ObservableProperty]
|
|
private VehModelClass _selectedVehModelClass;
|
|
public KeyModelViewModel(IVehDataService vehDataService, IMessenger messenger, IImageCacheService imageCacheService, ILogger<KeyModelViewModel> logger, IWindowService windowService, IHidCommunicationService hidCommunicationService, IModalDialogService dialogService, IFileServiceFactory fileServiceFactory, IServiceProvider serviceProvider)
|
|
{
|
|
_vehDataService = vehDataService;
|
|
_imageCacheService = imageCacheService;
|
|
_messenger = messenger;
|
|
_logger = logger;
|
|
WeakReferenceMessenger.Default.Register<CarSelectMessage>(this);
|
|
// 防抖计时器(250毫秒)
|
|
_debounceTimer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(250)
|
|
};
|
|
_debounceTimer.Tick += OnDebounceTimerTick;
|
|
VehModelClasses = new ObservableCollection<VehModelClass>();
|
|
FilterKeys = new ObservableCollection<VehKeyClass>();
|
|
_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 页面方法
|
|
// <summary>
|
|
/// 选中事件
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
[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<VehKeyClass>(SelectedVehModelClass.modelList ?? new List<VehKeyClass>());
|
|
}
|
|
foreach (var keyItem in FilterKeys)
|
|
{
|
|
keyItem.IsSelected = false;
|
|
}
|
|
SelectkeyItem = null;
|
|
item.IsSelected = true;
|
|
|
|
var loadingToken = _currentLoadingCts?.Token ?? CancellationToken.None;
|
|
_ = LoadImagesPathAsync(FilterKeys.ToList(), loadingToken);
|
|
}
|
|
/// <summary>
|
|
/// 选中事件
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
[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<Dictionary<string, byte[]>> GetBinFileByteDic(string sourceUrl)
|
|
{
|
|
if (string.IsNullOrEmpty(sourceUrl))
|
|
{
|
|
return null;
|
|
}
|
|
Dictionary<string, byte[]> binfileDic = new Dictionary<string, byte[]>();
|
|
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<VehKeyClass>();
|
|
if (string.IsNullOrWhiteSpace(SearchText))
|
|
{
|
|
FilterKeys = new ObservableCollection<VehKeyClass>(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<VehKeyClass>(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<VehModelClass>();
|
|
SelectedVehModelClass = null;
|
|
AllKeys = new ObservableCollection<VehKeyClass>();
|
|
FilterKeys = new ObservableCollection<VehKeyClass>();
|
|
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<VehKeyClass>();
|
|
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<VehModelClass>(defaultState.VehModelClasses);
|
|
AllKeys = new ObservableCollection<VehKeyClass>(defaultState.AllKeys);
|
|
FilterKeys = new ObservableCollection<VehKeyClass>(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;
|
|
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 异步加载车钥匙图片(BitmapImage内存占用较高)
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
/// <returns></returns>
|
|
private async Task LoadImagesAsync(List<VehKeyClass> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 异步加载车钥匙图片(图片路径)
|
|
/// </summary>
|
|
/// <param name="items"></param>
|
|
/// <returns></returns>
|
|
private async Task LoadImagesPathAsync(List<VehKeyClass> 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<VehModelClass> keylist, List<VehKeyClass> allKeys)
|
|
{
|
|
var vehModelClasses = keylist ?? new List<VehModelClass>();
|
|
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<VehKeyClass>();
|
|
|
|
return new DefaultVisibleKeysState
|
|
{
|
|
VehModelClasses = vehModelClasses,
|
|
SelectedVehModelClass = selectedVehModelClass,
|
|
AllKeys = allKeys ?? new List<VehKeyClass>(),
|
|
VisibleKeys = visibleKeys
|
|
};
|
|
}
|
|
|
|
private sealed class DefaultVisibleKeysState
|
|
{
|
|
public List<VehModelClass> VehModelClasses { get; init; } = new();
|
|
public VehModelClass? SelectedVehModelClass { get; init; }
|
|
public List<VehKeyClass> AllKeys { get; init; } = new();
|
|
public List<VehKeyClass> 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
|
|
}
|
|
}
|
|
|