feat(navigation): 重构导航系统引入导航参数和类型

- 新增NavigationType枚举定义导航类型
 - 新增NavigationParameter类用于传递导航参数
 - 重构INavigationService和INavigatable接口
 - 更新NavigationService实现以支持新的导航方式
 - 更新DeviceDetailViewModel, DevicesViewModel, VariableHistoryViewModel, VariableTableViewModel, MqttsViewModel等
 - 使ViewModelBase实现INavigatable接口
 - 更新MainView中的菜单选择导航逻辑
 - 优化VariableHistoryView界面布局,添加返回变量表按钮
This commit is contained in:
2025-10-03 22:28:58 +08:00
parent 1dbae9c208
commit 609c4741c1
14 changed files with 259 additions and 241 deletions

View File

@@ -0,0 +1,12 @@
using DMS.Core.Models;
namespace DMS.Core.Enums;
public enum NavigationType
{
None,
Device,
VariableTable,
Variable,
Mqtt,
}

View File

@@ -0,0 +1,22 @@
using DMS.Core.Enums;
namespace DMS.Core.Models;
public class NavigationParameter
{
public string TargetViewKey { get; set; }
public NavigationType TargetType { get; set; }
public int TargetId { get; set; }
public NavigationParameter(string targetViewKey, int targetId=0,NavigationType targetType=NavigationType.None )
{
TargetViewKey = targetViewKey;
TargetType = targetType;
TargetId = targetId;
}
}

View File

@@ -1,5 +1,6 @@
// 文件: DMS.WPF/Services/INavigatable.cs
using DMS.Core.Models;
using DMS.WPF.ViewModels.Items;
namespace DMS.WPF.Interfaces;
@@ -13,5 +14,13 @@ public interface INavigatable
/// 当导航到此ViewModel时由导航服务调用此方法以传递参数。
/// </summary>
/// <param name="parameter">从导航源传递过来的参数对象。</param>
Task OnNavigatedToAsync(MenuItemViewModel menu);
Task OnNavigatedToAsync(NavigationParameter navigationParameter);
/// <summary>
/// 当从当前ViewModel导航离开时由导航服务调用此方法。
/// </summary>
/// <param name="nextViewModel">即将导航到的下一个ViewModel。</param>
Task OnNavigatedFromAsync(NavigationParameter navigationParameter);
}

View File

@@ -1,5 +1,6 @@
// 文件: DMS.WPF/Services/INavigationService.cs
using DMS.Core.Models;
using DMS.WPF.ViewModels.Items;
namespace DMS.WPF.Interfaces;
@@ -9,18 +10,12 @@ namespace DMS.WPF.Interfaces;
/// </summary>
public interface INavigationService
{
/// <summary>
/// 导航到由唯一键标识的视图,并传递一个参数。
/// </summary>
/// <param name="viewKey">在DI容器中注册的目标视图的唯一键通常是ViewModel的名称。</param>
/// <param name="parameter">要传递给目标ViewModel的参数。</param>
Task NavigateToAsync(MenuItemViewModel menu);
/// <summary>
/// 导航到由唯一键标识的视图,并传递一个参数。
/// </summary>
/// <param name="viewKey">在DI容器中注册的目标视图的唯一键通常是ViewModel的名称。</param>
/// <param name="parameter">要传递给目标ViewModel的参数。</param>
Task NavigateToAsync(string viewKey, object parameter = null);
Task NavigateToAsync(object sender,NavigationParameter parameter);
}

View File

@@ -1,7 +1,10 @@
using DMS.Core.Models;
using DMS.WPF.Interfaces;
using DMS.WPF.ViewModels;
using DMS.WPF.ViewModels.Items;
using DMS.WPF.Views;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace DMS.WPF.Services;
@@ -22,49 +25,34 @@ public class NavigationService : INavigationService
_notificationService = notificationService;
}
/// <summary>
/// 导航到指定键的视图,并传递参数。
/// </summary>
public async Task NavigateToAsync(MenuItemViewModel menu)
{
if (string.IsNullOrEmpty(menu.TargetViewKey))
{
return;
}
var mainViewModel = App.Current.Services.GetRequiredService<MainViewModel>();
var viewModel = GetViewModelByKey(menu.TargetViewKey);
if (viewModel == null)
{
_notificationService.ShowError($"切换界面失败,没有找到界面:{menu.TargetViewKey}");
return;
}
if (viewModel is INavigatable navigatableViewModel)
{
await navigatableViewModel.OnNavigatedToAsync(menu);
}
mainViewModel.CurrentViewModel = viewModel;
}
/// <summary>
/// 导航到指定键的视图,并传递参数。
/// </summary>
public async Task NavigateToAsync(string viewKey, object parameter = null)
public async Task NavigateToAsync(object sender,NavigationParameter parameter)
{
var mainViewModel = App.Current.Services.GetRequiredService<MainViewModel>();
var viewModel = GetViewModelByKey(viewKey);
if (parameter == null || string.IsNullOrWhiteSpace(parameter.TargetViewKey) )return;
var viewModel = GetViewModelByKey(parameter.TargetViewKey);
if (viewModel == null)
{
_notificationService.ShowError($"切换界面失败,没有找到界面:{viewKey}");
_notificationService.ShowError($"切换界面失败,没有找到界面:{parameter.TargetViewKey}");
return;
}
if (sender is INavigatable fromViewModel)
{
await fromViewModel.OnNavigatedFromAsync(parameter);
}
var mainViewModel = App.Current.Services.GetRequiredService<MainViewModel>();
mainViewModel.CurrentViewModel = viewModel;
if (viewModel is INavigatable toViewModel)
{
await toViewModel.OnNavigatedToAsync(parameter);
}
}
@@ -74,29 +62,29 @@ public class NavigationService : INavigationService
{
switch (key)
{
case "HomeView":
case nameof(HomeViewModel):
return App.Current.Services.GetRequiredService<HomeViewModel>();
case "DevicesView":
case nameof(DevicesViewModel):
return App.Current.Services.GetRequiredService<DevicesViewModel>();
case "DeviceDetailView":
case nameof(DeviceDetailViewModel):
return App.Current.Services.GetRequiredService<DeviceDetailViewModel>();
case "DataTransformView":
case nameof(DataTransformViewModel):
return App.Current.Services.GetRequiredService<DataTransformViewModel>();
case "VariableTableView":
case nameof(VariableTableViewModel):
return App.Current.Services.GetRequiredService<VariableTableViewModel>();
case "VariableHistoryView":
case nameof(VariableHistoryViewModel):
return App.Current.Services.GetRequiredService<VariableHistoryViewModel>();
case "LogHistoryView":
case nameof(LogHistoryViewModel):
return App.Current.Services.GetRequiredService<LogHistoryViewModel>();
case "MqttsView":
case nameof(MqttsViewModel):
return App.Current.Services.GetRequiredService<MqttsViewModel>();
case "MqttServerDetailView":
case nameof(MqttServerDetailViewModel):
return App.Current.Services.GetRequiredService<MqttServerDetailViewModel>();
case "SettingView":
case nameof(SettingViewModel):
return App.Current.Services.GetRequiredService<SettingViewModel>();
case "EmailManagementView":
case nameof(EmailManagementViewModel):
return App.Current.Services.GetRequiredService<EmailManagementViewModel>();
case "TriggersView":
case nameof(TriggersViewModel):
return App.Current.Services.GetRequiredService<TriggersViewModel>();
default:
return null;

View File

@@ -5,6 +5,7 @@ using Dm;
using DMS.Application.DTOs;
using DMS.Application.Interfaces;
using DMS.Core.Enums;
using DMS.Core.Models;
using DMS.WPF.Services;
using DMS.Services;
using DMS.WPF.Interfaces;
@@ -14,7 +15,7 @@ using iNKORE.UI.WPF.Modern.Common.IconKeys;
namespace DMS.WPF.ViewModels;
public partial class DeviceDetailViewModel : ViewModelBase, INavigatable
public partial class DeviceDetailViewModel : ViewModelBase
{
private readonly IMapper _mapper;
private readonly IDialogService _dialogService;
@@ -70,7 +71,7 @@ public partial class DeviceDetailViewModel : ViewModelBase, INavigatable
{
Header = variableTableItemViewModel.Name,
Icon = SegoeFluentIcons.DataSense.Glyph,
TargetViewKey = "VariableTableView"
TargetViewKey = nameof(VariableTableViewModel)
};
int addVarTableId = await _wpfDataService.VariableTableDataService.AddVariableTable(
_mapper.Map<VariableTableDto>(variableTableItemViewModel),
@@ -187,9 +188,10 @@ public partial class DeviceDetailViewModel : ViewModelBase, INavigatable
}
public async Task OnNavigatedToAsync(MenuItemViewModel menu)
public override async Task OnNavigatedToAsync(NavigationParameter parameter)
{
if (_dataStorageService.Devices.TryGetValue(menu.TargetId, out var device))
if (_dataStorageService.Devices.TryGetValue(parameter.TargetId, out var device))
{
CurrentDevice = device;
}
@@ -199,9 +201,12 @@ public partial class DeviceDetailViewModel : ViewModelBase, INavigatable
public void NavigateToVariableTable()
{
if (SelectedVariableTable == null) return;
var menu = _dataStorageService.Menus.FirstOrDefault(m => m.MenuType == MenuType.VariableTableMenu &&
m.TargetId == SelectedVariableTable.Id);
if (menu == null) return;
_navigationService.NavigateToAsync(menu);
// var menu = _dataStorageService.Menus.FirstOrDefault(m => m.MenuType == MenuType.VariableTableMenu &&
// m.TargetId == SelectedVariableTable.Id);
// if (menu == null) return;
_navigationService.NavigateToAsync(
this,
new NavigationParameter(nameof(VariableTableViewModel), SelectedVariableTable.Id,
NavigationType.VariableTable));
}
}

View File

@@ -6,6 +6,7 @@ using DMS.Application.DTOs;
using DMS.Application.Interfaces;
using DMS.Application.Interfaces.Database;
using DMS.Core.Enums;
using DMS.Core.Models;
using DMS.WPF.Interfaces;
using DMS.WPF.Services;
using DMS.WPF.ViewModels.Dialogs;
@@ -122,7 +123,7 @@ public partial class DevicesViewModel : ViewModelBase, INavigatable
{
Header = dto.VariableTable.Name,
Icon = SegoeFluentIcons.DataSense.Glyph,
TargetViewKey = "VariableTableView"
TargetViewKey = nameof(VariableTableViewModel)
};
}
@@ -241,16 +242,9 @@ public partial class DevicesViewModel : ViewModelBase, INavigatable
{
if (SelectedDevice == null) return;
var menu = _dataStorageService.Menus.FirstOrDefault(m => m.MenuType == MenuType.DeviceMenu &&
m.TargetId == SelectedDevice.Id);
if (menu == null) return;
_navigationService.NavigateToAsync(menu);
_navigationService.NavigateToAsync(this,new NavigationParameter(nameof(DeviceDetailViewModel),SelectedDevice.Id,NavigationType.Device));
}
public async Task OnNavigatedToAsync(MenuItemViewModel menu)
{
}
[RelayCommand]
private async Task AddVariableTable(DeviceItemViewModel device)
@@ -276,7 +270,7 @@ public partial class DevicesViewModel : ViewModelBase, INavigatable
{
Header = variableTableItemViewModel.Name,
Icon = SegoeFluentIcons.DataSense.Glyph,
TargetViewKey = "VariableTableView"
TargetViewKey = nameof(VariableTableViewModel)
};
int addVarTableId = await _wpfDataService.VariableTableDataService.AddVariableTable(
_mapper.Map<VariableTableDto>(variableTableItemViewModel),

View File

@@ -57,10 +57,6 @@ public partial class MainViewModel : ViewModelBase
CurrentViewModel = new HomeViewModel();
CurrentViewModel.OnLoaded();
// 发送消息加载数据
MessageHelper.SendLoadMessage(LoadTypes.All);
// 当菜单加载成功后,在前台显示菜单
// _dataServices.OnMenuTreeListChanged += (menus) => { Menus = new ObservableCollection<MenuBean>(menus); };
}
/// <summary>
@@ -84,86 +80,6 @@ public partial class MainViewModel : ViewModelBase
// Application.Current.Shutdown();
}
/// <summary>
/// 添加变量表。
/// </summary>
/// <param name="menu">当前菜单项,用于获取父级设备信息。</param>
private async Task AddVariableTable(MenuBean menu)
{
// var db = DbContext.GetInstance();
// try
// {
// // 1. 检查父级设备信息
// if (menu.Parent?.Data is not Device device)
// {
// _logger.LogWarning("尝试添加变量表时Parent 或 Parent.Data 为空,或 Parent.Data 不是 Device 类型。");
// NotificationHelper.ShowError("操作失败:无法获取有效的设备信息。");
// return;
// }
//
//
// // 2. 显示添加变量表对话框
// var varTable = await _dialogService.ShowAddVarTableDialog();
// if (varTable == null)
// {
// // 用户取消或未选择
// return;
// }
//
// // 3. 设置变量表属性
// varTable.IsActive = true;
// varTable.DeviceId = device.Id;
// varTable.ProtocolType = device.ProtocolType;
//
// // 4. 添加变量表到数据库
// // 假设 _varTableRepository.AddAsync 返回一个布尔值表示成功,或者一个表示 ID 的整数
// // 这里为了演示我们假设它返回新添加的ID如果失败则返回0
// await db.BeginTranAsync();
// var addVarTable = await _varTableRepository.AddAsync(varTable, db);
//
// // 5. 添加变量表菜单
// MenuBean newMenu = new MenuBean
// {
// Icon = SegoeFluentIcons.Tablet.Glyph,
// Name = varTable.Name,
// DataId = addVarTable.Id, // 使用实际添加的ID
// Type = MenuType.VariableTableMenu,
// ParentId = menu.Parent.Id
// };
//
// var addMenuRes = await _menuRepository.AddAsync(newMenu, db);
// if (addMenuRes > 0)
// {
// await db.CommitTranAsync();
// // 变量表和菜单都添加成功
// MessageHelper.SendLoadMessage(LoadTypes.Menu);
// MessageHelper.SendLoadMessage(LoadTypes.Devices);
// NotificationHelper.ShowSuccess($"变量表:{varTable.Name},添加成功");
// _logger.LogInformation($"变量表:{varTable.Name},添加成功");
// }
// else
// {
// await db.RollbackTranAsync();
// // 变量表菜单添加失败 (此时变量表可能已添加成功,需要根据业务决定是否回滚)
// NotificationHelper.ShowError($"变量表:{varTable.Name},添加菜单失败");
// _logger.LogError($"变量表:{varTable.Name},添加菜单失败");
// // 考虑:如果菜单添加失败,是否需要删除之前添加的变量表?
// // 例如await _varTableRepository.DeleteAsync(addVarTableId);
// }
// }
// catch (Exception e)
// {
// await db.RollbackTranAsync();
// NotificationHelper.ShowError($"添加变量表时出现了错误:{e.Message}", e);
// }
}
/// <summary>
/// 处理菜单选择变化的逻辑。
/// </summary>
/// <param name="menu">被选中的菜单项。</param>
public async Task MenuSelectionChanged(MenuItemViewModel menu)
{
_navigationService.NavigateToAsync(menu);
}
}

View File

@@ -6,6 +6,7 @@ using DMS.Application.DTOs;
using DMS.Application.Interfaces;
using DMS.Application.Interfaces.Database;
using DMS.Core.Enums;
using DMS.Core.Models;
using DMS.WPF.Interfaces;
using DMS.WPF.Services;
using DMS.WPF.ViewModels.Dialogs;
@@ -177,12 +178,13 @@ public partial class MqttsViewModel : ViewModelBase
}
// 导航到MQTT服务器详情页
var menu = new MenuItemViewModel
{
TargetViewKey = "MqttServerDetailView",
TargetId = SelectedMqtt.Id
};
// var menu = new MenuItemViewModel
// {
// TargetViewKey = "MqttServerDetailView",
// TargetId = SelectedMqtt.Id
// };
await _navigationService.NavigateToAsync(menu);
await _navigationService.NavigateToAsync(
this, new NavigationParameter(nameof(MqttServerDetailViewModel), SelectedMqtt.Id, NavigationType.Mqtt));
}
}

View File

@@ -6,7 +6,9 @@ using DMS.Application.DTOs;
using DMS.Application.Events;
using DMS.Application.Interfaces;
using DMS.Application.Interfaces.Database;
using DMS.Core.Enums;
using DMS.Core.Events;
using DMS.Core.Models;
using DMS.WPF.Interfaces;
using DMS.WPF.ViewModels.Items;
using LiveChartsCore;
@@ -28,6 +30,7 @@ partial class VariableHistoryViewModel : ViewModelBase, INavigatable
private readonly IDataStorageService _dataStorageService;
private readonly IEventService _eventService;
private readonly INotificationService _notificationService;
private readonly INavigationService _navigationService;
/// <summary>
/// 加载历史记录条数限制
@@ -83,8 +86,8 @@ partial class VariableHistoryViewModel : ViewModelBase, INavigatable
public VariableHistoryViewModel(IMapper mapper, IDialogService dialogService, IHistoryAppService historyAppService,
IWPFDataService wpfDataService, IDataStorageService dataStorageService,
IEventService eventService,
INotificationService notificationService)
IEventService eventService, INotificationService notificationService,
INavigationService navigationService)
{
_mapper = mapper;
_dialogService = dialogService;
@@ -93,6 +96,7 @@ partial class VariableHistoryViewModel : ViewModelBase, INavigatable
_dataStorageService = dataStorageService;
_eventService = eventService;
_notificationService = notificationService;
_navigationService = navigationService;
_variableHistoryList = new ObservableList<VariableHistoryDto>();
_variableHistorySynchronizedView = _variableHistoryList.CreateView(v => v);
@@ -168,9 +172,9 @@ partial class VariableHistoryViewModel : ViewModelBase, INavigatable
}
public async Task OnNavigatedToAsync(MenuItemViewModel menu)
public override async Task OnNavigatedToAsync(NavigationParameter parameter)
{
if (_dataStorageService.Variables.TryGetValue(menu.TargetId, out VariableItemViewModel variableItem))
if (_dataStorageService.Variables.TryGetValue(parameter.TargetId, out VariableItemViewModel variableItem))
{
CurrentVariable = variableItem;
// 加载所有变量的历史记录
@@ -190,6 +194,23 @@ partial class VariableHistoryViewModel : ViewModelBase, INavigatable
}
}
/// <summary>
/// 返回变量表命令
/// </summary>
[RelayCommand]
private async Task NavigateToVariableTable()
{
try
{
// 导航到变量表页面
await _navigationService.NavigateToAsync(this,new NavigationParameter(nameof(VariableTableViewModel),CurrentVariable.VariableTableId,NavigationType.VariableTable));
}
catch (Exception ex)
{
_notificationService.ShowError($"导航到变量表失败: {ex.Message}", ex);
}
}
/// <summary>
/// 根据搜索文本过滤历史记录

View File

@@ -739,11 +739,9 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable
{
// 导航到历史记录视图
var navigationService = App.Current.Services.GetRequiredService<INavigationService>();
MenuItemViewModel viewModel = new MenuItemViewModel();
viewModel.TargetViewKey = "VariableHistoryView";
viewModel.MenuType = MenuType.VariableMenu;
viewModel.TargetId = SelectedVariable.Id;
navigationService.NavigateToAsync(viewModel);
navigationService.NavigateToAsync(
this,
new NavigationParameter(nameof(VariableHistoryViewModel), SelectedVariable.Id, NavigationType.Variable));
}
/// <summary>
@@ -875,9 +873,9 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable
// }
}
public async Task OnNavigatedToAsync(MenuItemViewModel menu)
public override async Task OnNavigatedToAsync(NavigationParameter parameter)
{
if (_dataStorageService.VariableTables.TryGetValue(menu.TargetId, out var varTable))
if (_dataStorageService.VariableTables.TryGetValue(parameter.TargetId, out var varTable))
{
CurrentVariableTable = varTable;
// 根据变量表的协议类型设置对应的布尔属性

View File

@@ -1,8 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using DMS.Core.Models;
using DMS.WPF.Interfaces;
using DMS.WPF.ViewModels.Items;
namespace DMS.WPF.ViewModels;
public abstract class ViewModelBase : ObservableObject
public abstract class ViewModelBase : ObservableObject,INavigatable
{
public virtual void OnLoaded()
{
@@ -20,5 +23,13 @@ public abstract class ViewModelBase : ObservableObject
}
public virtual async Task OnNavigatedToAsync(NavigationParameter parameter)
{
}
public virtual async Task OnNavigatedFromAsync(NavigationParameter parameter)
{
}
}

View File

@@ -4,6 +4,8 @@ using DMS.WPF.ViewModels;
using iNKORE.UI.WPF.Modern.Controls;
using Microsoft.Extensions.DependencyInjection;
using DMS.Core.Enums;
using DMS.Core.Models;
using DMS.WPF.Interfaces;
using DMS.WPF.ViewModels.Items;
namespace DMS.WPF.Views;
@@ -60,7 +62,24 @@ public partial class MainView : Window
var menu = args.SelectedItem as MenuItemViewModel;
if (menu != null)
{
await _viewModel.MenuSelectionChanged(menu);
NavigationType navigationType = NavigationType.None;
switch (menu.MenuType)
{
case MenuType.DeviceMenu:
navigationType=NavigationType.Device;
break;
case MenuType.VariableTableMenu:
navigationType=NavigationType.VariableTable;
break;
case MenuType.MqttMenu:
navigationType=NavigationType.Mqtt;
break;
}
var navigationService= App.Current.Services.GetRequiredService<INavigationService>();
navigationService.NavigateToAsync(this,new NavigationParameter(menu.TargetViewKey,menu.TargetId,navigationType));
}
}

View File

@@ -34,76 +34,102 @@
</UserControl.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<!-- 操作菜单栏 -->
<ui:CommandBar DefaultLabelPosition="Right" IsOpen="False">
<!-- 重新加载 -->
<ui:AppBarButton Command="{Binding ReloadCommand}" Label="重新加载">
<ui:AppBarButton.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Refresh}" />
</ui:AppBarButton.Icon>
</ui:AppBarButton>
<Border DockPanel.Dock="Top" Background="White" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="8"
Margin="20,10" Padding="15">
<Border.Effect>
<DropShadowEffect ShadowDepth="2"
BlurRadius="5"
Opacity="0.1"
Color="#888888"/>
</Border.Effect>
<ui:AppBarButton x:Name="ExportButton" Label="导出">
<ui:AppBarButton.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Save}" />
</ui:AppBarButton.Icon>
</ui:AppBarButton>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ui:AppBarButton x:Name="ShareButton" Label="分享">
<ui:AppBarButton.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Share}" />
</ui:AppBarButton.Icon>
</ui:AppBarButton>
<!-- 返回变量表按钮 -->
<Button Grid.Column="0"
Content="返回变量表"
Command="{Binding NavigateToVariableTableCommand}"
Style="{StaticResource ButtonPrimary}"
Margin="0,0,10,0"/>
<ui:CommandBar.SecondaryCommands>
<ui:AppBarButton
x:Name="SettingsButton"
Icon="Setting"
Label="设置" />
</ui:CommandBar.SecondaryCommands>
</ui:CommandBar>
<!-- 操作菜单栏 -->
<ui:CommandBar Grid.Column="1"
DefaultLabelPosition="Right"
IsOpen="False"
HorizontalAlignment="Right">
<!-- 重新加载 -->
<ui:AppBarButton Command="{Binding ReloadCommand}" Label="重新加载">
<ui:AppBarButton.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Refresh}" />
</ui:AppBarButton.Icon>
</ui:AppBarButton>
<!-- 时间范围选择器和详细信息区域 -->
<Border Background="White"
BorderBrush="#E0E0E0"
BorderThickness="1"
CornerRadius="8"
Margin="20,10"
Padding="15">
<Border.Effect>
<DropShadowEffect ShadowDepth="2"
BlurRadius="5"
Opacity="0.1"
Color="#888888"/>
</Border.Effect>
<ui:AppBarButton x:Name="ExportButton" Label="导出">
<ui:AppBarButton.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Save}" />
</ui:AppBarButton.Icon>
</ui:AppBarButton>
<ikw:SimpleStackPanel Spacing="15">
<!-- 时间范围选择 -->
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="10">
<TextBlock Text="开始时间:"
VerticalAlignment="Center"
Foreground="#666666"
FontSize="14"/>
<hc:DateTimePicker Width="200" SelectedDateTime="{Binding StartTime}" />
<ui:AppBarButton x:Name="ShareButton" Label="分享">
<ui:AppBarButton.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Share}" />
</ui:AppBarButton.Icon>
</ui:AppBarButton>
<TextBlock Text="结束时间:"
VerticalAlignment="Center"
Foreground="#666666"
FontSize="14"
Margin="10,0,0,0"/>
<hc:DateTimePicker Width="200" SelectedDateTime="{Binding EndTime}" />
<ui:CommandBar.SecondaryCommands>
<ui:AppBarButton
x:Name="SettingsButton"
Icon="Setting"
Label="设置" />
</ui:CommandBar.SecondaryCommands>
</ui:CommandBar>
</Grid>
</Border>
<Button Content="重新加载"
Command="{Binding ReloadCommand}"
Style="{StaticResource ButtonPrimary}"
Margin="10,0,0,0"/>
</ikw:SimpleStackPanel>
<!-- 时间范围选择器和详细信息区域 -->
<Border DockPanel.Dock="Top"
Background="White"
BorderBrush="#E0E0E0"
BorderThickness="1"
CornerRadius="8"
Margin="20,10"
Padding="15">
<Border.Effect>
<DropShadowEffect ShadowDepth="2"
BlurRadius="5"
Opacity="0.1"
Color="#888888"/>
</Border.Effect>
<ikw:SimpleStackPanel Spacing="15">
<!-- 时间范围选择 -->
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="10">
<TextBlock Text="开始时间:"
VerticalAlignment="Center"
Foreground="#666666"
FontSize="14"/>
<hc:DateTimePicker Width="200" SelectedDateTime="{Binding StartTime}" />
<TextBlock Text="结束时间:"
VerticalAlignment="Center"
Foreground="#666666"
FontSize="14"
Margin="10,0,0,0"/>
<hc:DateTimePicker Width="200" SelectedDateTime="{Binding EndTime}" />
<Button Content="重新加载"
Command="{Binding ReloadCommand}"
Style="{StaticResource ButtonPrimary}"
Margin="10,0,0,0"/>
</ikw:SimpleStackPanel>
</Border>
</StackPanel>
</ikw:SimpleStackPanel>
</Border>
<!-- 变量历史记录列表和图表 -->
<TabControl Margin="20">