using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows.Data; using System.Windows.Input; using System.Windows.Threading; // 引入 DispatcherTimer 命名空间 namespace SHH.CameraDashboard { public class CameraListViewModel : INotifyPropertyChanged { // 存储从 API 获取的所有原始数据 private readonly ObservableCollection _allCameras; // 提供给界面绑定的视图 (支持搜索过滤) public ICollectionView FilteredCameras { get; private set; } #region --- 状态属性 --- private bool _isLoading; public bool IsLoading { get => _isLoading; set { _isLoading = value; OnPropertyChanged(nameof(IsLoading)); } } private WebApiCameraModel _selectedCamera; public WebApiCameraModel SelectedCamera { get => _selectedCamera; set { _selectedCamera = value; OnPropertyChanged(nameof(SelectedCamera)); } } private string _searchText; public string SearchText { get => _searchText; set { _searchText = value; OnPropertyChanged(nameof(SearchText)); // 输入文字时立即触发过滤 FilteredCameras.Refresh(); } } #endregion #region --- 下拉框节点选择 --- // 下拉框的数据源 public ObservableCollection NodeOptions => AppGlobal.ServiceNodes; private ServiceNodeModel _selectedNode; public ServiceNodeModel SelectedNode { get => _selectedNode; set { if (_selectedNode != value) { _selectedNode = value; OnPropertyChanged(nameof(SelectedNode)); AppGlobal.UseServiceNode = value; // 切换节点时,自动重新加载数据 _ = LoadDataAsync(); } } } #endregion public ICommand RefreshCommand { get; } public CameraListViewModel() { _allCameras = new ObservableCollection(); // 初始化 CollectionView FilteredCameras = CollectionViewSource.GetDefaultView(_allCameras); FilteredCameras.Filter = FilterCameras; AddCameraCommand = new RelayCommand(ExecuteAddCamera); DeleteCommand = new RelayCommand(ExecuteDelete); RefreshCommand = new RelayCommand(async _ => await LoadDataAsync()); // --- 新增:定时器 --- // 使用 DispatcherTimer 确保在 UI 线程执行,避免跨线程操作集合报错 var timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromSeconds(2); // 这里设置间隔,例如 2 秒 timer.Tick += async (s, e) => await LoadDataAsync(); timer.Start(); } /// /// 核心:加载数据逻辑 (无刷版本) /// public async Task LoadDataAsync() { // 如果正在加载中,跳过本次(防止并发) if (IsLoading) return; try { // 【改动1】只有第一次加载(列表为空)时才显示 Loading 转圈 // 这样定时刷新时,界面就不会弹出遮罩层 if (_allCameras.Count == 0) IsLoading = true; // 确定要查询的目标节点列表 var targetNodes = new List(); if (SelectedNode == null || SelectedNode.ServiceNodeIp == "ALL") { if (AppGlobal.ServiceNodes != null) targetNodes.AddRange(AppGlobal.ServiceNodes); } else { targetNodes.Add(SelectedNode); } if (targetNodes.Count == 0) { // 如果没有节点,清空列表 if (_allCameras.Count > 0) _allCameras.Clear(); return; } // 创建并发任务列表 var tasks = new List?>>(); foreach (var node in targetNodes) { if (string.IsNullOrWhiteSpace(node.ServiceNodeIp) || string.IsNullOrWhiteSpace(node.ServiceNodePort)) continue; tasks.Add(ApiClient.Instance.Cameras.GetListByAddressAsync(node.ServiceNodeIp, node.ServiceNodePort, "左侧列表刷新")); } var results = await Task.WhenAll(tasks); // 【改动2】先将所有新数据收集到一个临时列表中,不要直接操作界面集合 var newLatestData = new List(); foreach (var list in results) { if (list != null) { newLatestData.AddRange(list); } } // 【改动3】执行无刷更新 (智能合并) if (newLatestData.Count > 0) UpdateCollectionNoFlash(newLatestData); } finally { IsLoading = false; } } /// /// 辅助方法:比对新旧数据,只更新变化的,实现“无刷” /// private void UpdateCollectionNoFlash(List newData) { // 1. 删除:界面上有,但新数据里没有的 (说明设备断开或移除了) // 使用 ToList() 避免在遍历时修改集合报错 var itemsToRemove = _allCameras.Where(old => !newData.Any(n => n.Id == old.Id)).ToList(); foreach (var item in itemsToRemove) { _allCameras.Remove(item); } // 2. 更新或新增 foreach (var newCam in newData) { // 尝试在界面列表中找这个 ID var oldCam = _allCameras.FirstOrDefault(x => x.Id == newCam.Id); if (oldCam != null) { // --- [更新] --- // 找到老对象,手动更新它的属性。 // 注意:你的 WebApiCameraModel 必须实现了 INotifyPropertyChanged // 这里的赋值才会让界面文字发生变化,否则界面不动。 oldCam.Name = newCam.Name; oldCam.IpAddress = newCam.IpAddress; oldCam.Brand = newCam.Brand; oldCam.Status = newCam.Status; oldCam.IsOnline = newCam.IsOnline; oldCam.IsPhysicalOnline = newCam.IsPhysicalOnline; oldCam.IsRunning = newCam.IsRunning; oldCam.Width = newCam.Width; oldCam.Height = newCam.Height; oldCam.RealFps = newCam.RealFps; oldCam.TotalFrames = newCam.TotalFrames; oldCam.StreamType = newCam.StreamType; // 如果有其他变化的字段(如帧率),继续在这里赋值... } else { // --- [新增] --- // 没找到,说明是新上线的设备,添加到列表 _allCameras.Add(newCam); } } } /// /// 本地搜索过滤逻辑 /// private bool FilterCameras(object obj) { if (obj is WebApiCameraModel camera) { // 搜索框为空显示所有 if (string.IsNullOrWhiteSpace(SearchText)) return true; string lowerSearch = SearchText.ToLower(); // 匹配 名称、IP 或 品牌 return (camera.Name?.ToLower().Contains(lowerSearch) == true) || (camera.IpAddress?.Contains(lowerSearch) == true) || (camera.Brand?.ToLower().Contains(lowerSearch) == true); } return false; } public ICommand AddCameraCommand { get; } private void ExecuteAddCamera(object obj) { // 简单直接:通知全世界“我要添加摄像头!” AppGlobal.RequestAdd(); } // [新增] 删除命令 public ICommand DeleteCommand { get; } private void ExecuteDelete(object obj) { // 这里的 obj 就是从界面传过来的 WebApiCameraModel if (obj is WebApiCameraModel camera) { AppGlobal.RequestDelete(camera); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } }