兼容win7版应用
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

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
}
}