| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- using Prism.Commands;
- using Prism.Mvvm;
- using Prism.Regions;
- using System;
- using System.Collections.ObjectModel;
- using System.Diagnostics;
- using System.Windows;
- using System.Windows.Media;
- using System.Windows.Threading;
- using TeamAAS.Core;
- using TeamAAS.Models;
- using TeamAAS.Localization;
- using TeamAAS.Theme;
- namespace TeamAAS.ViewModels
- {
- public class MainWindowViewModel : TeamAAS.BindableBase
- {
- private readonly IRegionManager _regionManager;
- private DispatcherTimer _timer;
- public ObservableCollection<DeviceStatusItem> DeviceStatusItems { get; } = new ObservableCollection<DeviceStatusItem>();
- /// <summary>左侧导航(首页/产品/设置)</summary>
- public ObservableCollection<NavItem> LeftNavItems { get; } = new ObservableCollection<NavItem>();
- /// <summary>右侧导航(标定/统计/用户)</summary>
- public ObservableCollection<NavItem> RightNavItems { get; } = new ObservableCollection<NavItem>();
- private string _memoryUsage = "";
- public string MemoryUsage
- {
- get => _memoryUsage;
- set => SetProperty(ref _memoryUsage, value);
- }
- private string _currentTime = "";
- public string CurrentTime
- {
- get => _currentTime;
- set => SetProperty(ref _currentTime, value);
- }
- private string _currentView = "HomeView";
- public string CurrentView
- {
- get => _currentView;
- set => SetProperty(ref _currentView, value);
- }
- #region 主题与主色
- public bool IsDarkMode
- {
- get => string.Equals(App.SystemConfig?.Theme, "Dark", StringComparison.OrdinalIgnoreCase);
- set
- {
- if (IsDarkMode == value) return;
- App.SystemConfig.Theme = value ? "Dark" : "Light";
- ThemeManager.ApplyTheme(value, App.SystemConfig.AccentColor);
- App.SaveSystemConfig();
- RaisePropertyChanged(nameof(IsDarkMode));
- RaisePropertyChanged(nameof(ThemeIcon));
- RaisePropertyChanged(nameof(ThemeDisplayName));
- }
- }
- public string ThemeIcon => IsDarkMode ? "☀️" : "🌙";
- public string ThemeDisplayName => IsDarkMode ? "深色(点击切换)" : "浅色(点击切换)";
- public ObservableCollection<AccentColorOption> AccentColors { get; }
- = new ObservableCollection<AccentColorOption>();
- public Brush AccentBrush
- {
- get
- {
- try
- {
- var color = (Color)ColorConverter.ConvertFromString(App.SystemConfig?.AccentColor);
- // 规范化为主色不透明版本(配置里可能残留带 alpha 的历史值)
- var brush = new SolidColorBrush(Color.FromRgb(color.R, color.G, color.B));
- brush.Freeze();
- return brush;
- }
- catch { return Brushes.DodgerBlue; }
- }
- set => ApplyAccent(value);
- }
- public DelegateCommand<string> NavigateCommand { get; }
- public DelegateCommand ToggleThemeCommand { get; }
- public DelegateCommand<string> SetAccentCommand { get; }
- public MainWindowViewModel(IRegionManager regionManager)
- {
- _regionManager = regionManager;
- NavigateCommand = new DelegateCommand<string>(Navigate);
- ToggleThemeCommand = new DelegateCommand(() => IsDarkMode = !IsDarkMode);
- SetAccentCommand = new DelegateCommand<string>(hex =>
- {
- if (string.IsNullOrWhiteSpace(hex)) return;
- App.SystemConfig.AccentColor = hex;
- // 主色系画刷与文字对比色由主题覆盖层派生重建(毫秒级生效)
- ThemeManager.UpdateAccent(hex);
- App.SaveSystemConfig();
- RaisePropertyChanged(nameof(AccentBrush));
- });
- InitAccentColors();
- ThemeManager.ThemeChanged += OnAppThemeChanged;
- InitNavItems();
- }
- private void InitAccentColors()
- {
- var presets = new[]
- {
- ("默认蓝", "#2D6CDF"),
- ("成功绿", "#4CAF50"),
- ("活力橙", "#FF9800"),
- ("警示红", "#F44336"),
- ("优雅紫", "#7B1FA2"),
- ("清新青", "#00BCD4"),
- ("少女粉", "#E91E63"),
- ("中性灰", "#607D8B"),
- };
- foreach (var (name, hex) in presets)
- {
- AccentColors.Add(new AccentColorOption
- {
- Name = name,
- Hex = hex,
- Brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex))
- });
- }
- }
- private void ApplyAccent(Brush brush)
- {
- if (!(brush is SolidColorBrush sb)) return;
- // 规范化:只取 RGB、强制不透明。ColorPicker 样式重算时会回写带任意 alpha 的画刷
- // (实测出现过 A=0x0A 的 96% 透明色被存进配置,表现为主色"特别淡、朦了一层"),
- // 统一丢弃 alpha——色相保真,主色永远纯色。
- var color = Color.FromRgb(sb.Color.R, sb.Color.G, sb.Color.B);
- // 按 RGB 去重:同色不同 alpha 的回写(切主题时样式重算触发)完全静默忽略
- var current = TryParseAccentColor();
- if (current.HasValue &&
- current.Value.R == color.R && current.Value.G == color.G && current.Value.B == color.B)
- {
- return;
- }
- App.SystemConfig.AccentColor = color.ToString();
- // 主色系画刷与文字对比色由主题覆盖层派生重建(毫秒级生效,不碰合并字典)
- ThemeManager.UpdateAccent(color);
- App.SaveSystemConfig();
- RaisePropertyChanged(nameof(AccentBrush));
- }
- private static Color? TryParseAccentColor()
- {
- try
- {
- var hex = App.SystemConfig?.AccentColor;
- if (string.IsNullOrWhiteSpace(hex)) return null;
- return (Color)ColorConverter.ConvertFromString(hex);
- }
- catch { return null; }
- }
- private void OnAppThemeChanged()
- {
- RaisePropertyChanged(nameof(IsDarkMode));
- RaisePropertyChanged(nameof(ThemeIcon));
- RaisePropertyChanged(nameof(ThemeDisplayName));
- RaisePropertyChanged(nameof(AccentBrush));
- }
- #endregion
- public void OnLoaded()
- {
- Navigate("HomeView");
- StartTimer();
- }
- #region 导航
- private void InitNavItems()
- {
- // 图标沿用 Material Design Path(与旧 XAML 资源一致)
- AddNav(LeftNavItems, "首页", "HomeView",
- "M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z");
- AddNav(LeftNavItems, "产品", "ProductView",
- "M12,2L2,7V17L12,22L22,17V7L12,2Z M12,2V12 M2,7L12,12L22,7");
- AddNav(LeftNavItems, "设置", "SettingView",
- "M19.14,12.94C19.18,12.64 19.2,12.33 19.2,12C19.2,11.68 19.18,11.36 19.13,11.06L21.16,9.48C21.34,9.34 21.39,9.07 21.27,8.87L19.35,5.55C19.23,5.33 18.96,5.25 18.73,5.33L16.38,6.28C15.88,5.89 15.35,5.56 14.76,5.32L14.4,2.81C14.36,2.57 14.16,2.4 13.92,2.4H10.08C9.83,2.4 9.63,2.57 9.59,2.81L9.24,5.32C8.65,5.56 8.11,5.89 7.62,6.28L5.27,5.33C5.04,5.25 4.77,5.33 4.65,5.55L2.73,8.87C2.61,9.07 2.66,9.34 2.84,9.48L4.87,11.06C4.82,11.36 4.8,11.68 4.8,12C4.8,12.33 4.82,12.64 4.87,12.94L2.84,14.52C2.66,14.66 2.61,14.93 2.73,15.13L4.65,18.45C4.77,18.67 5.04,18.75 5.27,18.67L7.62,17.72C8.11,18.11 8.65,18.44 9.24,18.68L9.6,21.19C9.64,21.43 9.84,21.6 10.08,21.6H13.92C14.17,21.6 14.37,21.43 14.41,21.19L14.76,18.68C15.35,18.44 15.89,18.11 16.38,17.72L18.73,18.67C18.96,18.75 19.23,18.67 19.35,18.45L21.27,15.13C21.39,14.93 21.34,14.66 21.16,14.52L19.14,12.94ZM12,15.6C10.02,15.6 8.4,13.98 8.4,12C8.4,10.02 10.02,8.4 12,8.4C13.98,8.4 15.6,10.02 15.6,12C15.6,13.98 13.98,15.6 12,15.6Z");
- AddNav(RightNavItems, "标定", "CalibrationView",
- "M14.06,9L15,9.94L5.92,19H5V18.08L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z");
- AddNav(RightNavItems, "统计", "StatisticsView",
- "M3,3H21V21H3V3Z M7,17V11 M12,17V7 M17,17V13");
- AddNav(RightNavItems, "用户", "UserView",
- "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z");
- }
- private void AddNav(ObservableCollection<NavItem> collection, string title, string viewName, string iconData)
- {
- // Path.Data 在运行时不会自动把字符串转成 Geometry,
- // 必须在代码里 Geometry.Parse 成对象(并 Freeze 提升渲染性能/线程安全)
- var geometry = Geometry.Parse(iconData);
- geometry.Freeze();
- var item = new NavItem { Title = title, ViewName = viewName, IconData = geometry };
- item.SelectRequested += nav =>
- {
- if (CurrentView != nav.ViewName)
- Navigate(nav.ViewName);
- };
- collection.Add(item);
- }
- private void Navigate(string viewName)
- {
- if (string.IsNullOrEmpty(viewName)) return;
- _regionManager.RequestNavigate("ContentRegion", viewName, r =>
- {
- if (r.Result != true)
- {
- // 解包内部异常链,找到根因(Prism 只抛 ContainerResolutionException 外壳)
- var root = r.Error;
- var depth = 0;
- while (root?.InnerException != null && depth++ < 10)
- root = root.InnerException;
- AppLogger.Error($"导航到 {viewName} 失败,内容区保持原页面 | 根因: {root?.Message}", r.Error, "Navigation");
- }
- });
- CurrentView = viewName;
- // 每次导航都同步选中状态(含首次启动:
- // 初始 CurrentView 与目标相同,不能依赖"是否变化"判断,否则首页不会被选中)
- SyncNavSelection();
- }
- private void SyncNavSelection()
- {
- foreach (var nav in LeftNavItems)
- nav.IsSelected = nav.ViewName == CurrentView;
- foreach (var nav in RightNavItems)
- nav.IsSelected = nav.ViewName == CurrentView;
- }
- #endregion
- #region 定时刷新
- private void StartTimer()
- {
- // Loaded 事件在 WPF 中可能触发多次,避免重复创建定时器导致刷新回调叠加。
- if (_timer != null)
- return;
- _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
- _timer.Tick += OnTimerTick;
- _timer.Start();
- }
- private void OnTimerTick(object sender, EventArgs e)
- {
- RefreshDeviceStatuses();
- UpdateSystemInfo();
- }
- private void RefreshDeviceStatuses()
- {
- var statuses = CoreManager.GetAllDeviceStatuses();
- if (DeviceStatusItems.Count != statuses.Count)
- {
- DeviceStatusItems.Clear();
- foreach (var s in statuses)
- DeviceStatusItems.Add(new DeviceStatusItem { Name = s.Name, Category = L10n.T(s.Category), IsConnected = s.IsConnected });
- return;
- }
- // 设备数量未变时也要同步名称/类别(用户在设备管理页改名后即时反映到状态栏)
- for (int i = 0; i < statuses.Count; i++)
- {
- DeviceStatusItems[i].Name = statuses[i].Name;
- DeviceStatusItems[i].Category = L10n.T(statuses[i].Category);
- DeviceStatusItems[i].IsConnected = statuses[i].IsConnected;
- }
- }
- private void UpdateSystemInfo()
- {
- var proc = Process.GetCurrentProcess();
- MemoryUsage = L10n.F("内存: {0} MB", proc.PrivateMemorySize64 / 1024 / 1024);
- CurrentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
- }
- #endregion
- }
- /// <summary>
- /// 顶部导航项(数据驱动生成导航按钮,新增页面只需在 MainWindowViewModel.InitNavItems 加一行)
- /// </summary>
- public class NavItem : TeamAAS.BindableBase
- {
- public string Title { get; set; }
- public string ViewName { get; set; }
- /// <summary>图标 Geometry 对象(Path.Data 直接绑定,避免运行时字符串→Geometry 转换失败)</summary>
- public Geometry IconData { get; set; }
- private bool _isSelected;
- public bool IsSelected
- {
- get => _isSelected;
- set
- {
- if (SetProperty(ref _isSelected, value) && value)
- SelectRequested?.Invoke(this);
- }
- }
- public event Action<NavItem> SelectRequested;
- }
- public class DeviceStatusItem : TeamAAS.BindableBase
- {
- private string _name;
- private string _category;
- private bool _isConnected;
- public string Name
- {
- get => _name;
- set => SetProperty(ref _name, value);
- }
- public string Category
- {
- get => _category;
- set => SetProperty(ref _category, value);
- }
- public bool IsConnected
- {
- get => _isConnected;
- set => SetProperty(ref _isConnected, value);
- }
- }
- }
|