具备界面基础功能
This commit is contained in:
113
SHH.CameraDashboard/App/AppGlobal.cs
Normal file
113
SHH.CameraDashboard/App/AppGlobal.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace SHH.CameraDashboard
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序全局状态和事件总线。
|
||||
/// 此类作为一个静态的中央枢纽,用于在应用程序的不同部分之间共享数据和通信。
|
||||
/// </summary>
|
||||
public static class AppGlobal
|
||||
{
|
||||
#region --- 全局数据存储 ---
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个可观察的集合,用于存储和显示所有已配置的服务节点。
|
||||
/// 由于使用了 <see cref="ObservableCollection{T}"/>,当集合内容发生变化时,UI(如 ListView)会自动更新。
|
||||
/// </summary>
|
||||
public static ObservableCollection<ServiceNodeModel> ServiceNodes { get; }
|
||||
= new ObservableCollection<ServiceNodeModel>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当前正在使用的服务节点。
|
||||
/// 当用户从列表中选择一个节点时,应更新此属性。
|
||||
/// </summary>
|
||||
public static ServiceNodeModel? UseServiceNode { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 全局事件总线 ---
|
||||
|
||||
#region CameraAdd
|
||||
|
||||
/// <summary>
|
||||
/// 当应用程序的任何部分请求添加一个新摄像头时发生。
|
||||
/// </summary>
|
||||
public static event Action? OnRequestAddCamera;
|
||||
|
||||
/// <summary>
|
||||
/// 触发 <see cref="OnRequestAddCamera"/> 事件,以请求打开摄像头添加界面。
|
||||
/// </summary>
|
||||
public static void RequestAdd()
|
||||
=> OnRequestAddCamera?.Invoke();
|
||||
|
||||
#endregion
|
||||
|
||||
#region CameraEdit
|
||||
|
||||
/// <summary>
|
||||
/// 当应用程序的任何部分请求编辑一个摄像头时发生。
|
||||
/// 事件处理程序将接收到要编辑的 <see cref="WebApiCameraModel"/> 实例。
|
||||
/// </summary>
|
||||
public static event Action<WebApiCameraModel>? OnRequestEditCamera;
|
||||
|
||||
/// <summary>
|
||||
/// 触发 <see cref="OnRequestEditCamera"/> 事件,以请求打开摄像头编辑界面。
|
||||
/// </summary>
|
||||
/// <param name="camera">要编辑的摄像头数据模型。</param>
|
||||
public static void RequestEdit(WebApiCameraModel camera)
|
||||
=> OnRequestEditCamera?.Invoke(camera);
|
||||
|
||||
#endregion
|
||||
|
||||
#region CameraDelete
|
||||
|
||||
/// <summary>
|
||||
/// 当应用程序的任何部分请求删除一个摄像头时发生。
|
||||
/// 事件处理程序将接收到要删除的 <see cref="WebApiCameraModel"/> 实例。
|
||||
/// </summary>
|
||||
public static event Action<WebApiCameraModel>? OnRequestDeleteCamera;
|
||||
|
||||
/// <summary>
|
||||
/// 触发 <see cref="OnRequestDeleteCamera"/> 事件,以请求删除指定的摄像头。
|
||||
/// </summary>
|
||||
/// <param name="camera">要删除的摄像头数据模型。</param>
|
||||
public static void RequestDelete(WebApiCameraModel camera)
|
||||
=> OnRequestDeleteCamera?.Invoke(camera);
|
||||
|
||||
#endregion
|
||||
|
||||
#region CameraRefreshList
|
||||
|
||||
/// <summary>
|
||||
/// 当应用程序的任何部分请求刷新摄像头列表时发生。
|
||||
/// </summary>
|
||||
public static event Action? OnRefreshListRequest;
|
||||
|
||||
/// <summary>
|
||||
/// 触发 <see cref="OnRefreshListRequest"/> 事件,以请求刷新摄像头列表数据。
|
||||
/// </summary>
|
||||
public static void RequestRefresh()
|
||||
=> OnRefreshListRequest?.Invoke();
|
||||
|
||||
#endregion
|
||||
|
||||
// [新增] 请求云台控制事件
|
||||
public static event Action<WebApiCameraModel>? OnRequestPtzCamera;
|
||||
|
||||
// [新增] 触发方法
|
||||
public static void RequestPtz(WebApiCameraModel camera) => OnRequestPtzCamera?.Invoke(camera);
|
||||
|
||||
// 图像处理
|
||||
public static event Action<WebApiCameraModel>? OnRequestImgProc;
|
||||
|
||||
public static void RequestImgProc(WebApiCameraModel camera) => OnRequestImgProc?.Invoke(camera);
|
||||
|
||||
// 1. 定义事件委托:当 ViewModel 请求订阅时触发
|
||||
public static event Action<WebApiCameraModel>? OnRequestSubscription;
|
||||
|
||||
// 2. 定义触发方法:供 CameraItemTopViewModel 调用
|
||||
public static void RequestSubscription(WebApiCameraModel camera) => OnRequestSubscription?.Invoke(camera);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace SHH.CameraDashboard
|
||||
{
|
||||
// 2. 全局配置存储
|
||||
public static class AppGlobalData
|
||||
{
|
||||
public static List<ServerNode> ActiveServerList { get; private set; } = new List<ServerNode>();
|
||||
|
||||
public static void SaveConfig(IEnumerable<ServerNode> nodes)
|
||||
{
|
||||
ActiveServerList.Clear();
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
ActiveServerList.Add(new ServerNode { Ip = node.Ip, Port = node.Port });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace SHH.CameraDashboard
|
||||
{
|
||||
public enum ThemeType { Dark, Light }
|
||||
|
||||
public static class ThemeManager
|
||||
{
|
||||
public static void ChangeTheme(ThemeType theme)
|
||||
{
|
||||
var appResources = Application.Current.Resources;
|
||||
|
||||
// 1. 找到旧的颜色字典并移除
|
||||
// 我们通过检查 Source 路径来识别它
|
||||
ResourceDictionary oldDict = null;
|
||||
foreach (var dict in appResources.MergedDictionaries)
|
||||
{
|
||||
// 只要路径里包含 "Colors." 说明它是我们的皮肤文件
|
||||
if (dict.Source != null && dict.Source.OriginalString.Contains("Themes/Colors."))
|
||||
{
|
||||
oldDict = dict;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldDict != null)
|
||||
{
|
||||
appResources.MergedDictionaries.Remove(oldDict);
|
||||
}
|
||||
|
||||
// 2. 加载新字典
|
||||
string dictName = theme switch
|
||||
{
|
||||
ThemeType.Light => "/Style/Themes/Colors.Light.xaml",
|
||||
ThemeType.Dark => "/Style/Themes/Colors.Dark.xaml",
|
||||
_ => "/Style/Themes/Colors.Dark.xaml" // 默认
|
||||
};
|
||||
|
||||
var newDict = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri(dictName, UriKind.Relative)
|
||||
};
|
||||
|
||||
// 3. 添加到集合中 (建议加在最前面,或者根据索引位置)
|
||||
appResources.MergedDictionaries.Add(newDict);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="SHH.CameraDashboard.WizardControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Width="800"
|
||||
Height="550"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Border
|
||||
Background="{DynamicResource Brush.Bg.Window}"
|
||||
BorderBrush="{DynamicResource Brush.Border.Focus}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource Radius.Normal}">
|
||||
<Grid Margin="25">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,20">
|
||||
<DockPanel LastChildFill="False">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="{StaticResource Size.Font.Huge}"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource Brush.Text.Primary}"
|
||||
Text="📡 服务节点配置" />
|
||||
<Button
|
||||
Width="30"
|
||||
Height="30"
|
||||
Click="Cancel_Click"
|
||||
Content="✕"
|
||||
DockPanel.Dock="Right"
|
||||
Style="{StaticResource Btn.Ghost}" />
|
||||
</DockPanel>
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
Foreground="{DynamicResource Brush.Text.Secondary}"
|
||||
Text="请添加或修改后端的 IP 地址与端口,配置将用于聚合监控数据。" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border
|
||||
Padding="10"
|
||||
Background="{DynamicResource Brush.Bg.Panel}"
|
||||
BorderBrush="{DynamicResource Brush.Border}"
|
||||
BorderThickness="1,1,1,0">
|
||||
<DockPanel LastChildFill="False">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Text="节点清单" />
|
||||
<Button
|
||||
Height="26"
|
||||
Padding="15,0"
|
||||
Background="{DynamicResource Brush.Accent}"
|
||||
BorderThickness="0"
|
||||
Click="AddNode_Click"
|
||||
Content="+ 新增一行"
|
||||
DockPanel.Dock="Right"
|
||||
Style="{StaticResource {x:Type Button}}" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<ListView
|
||||
x:Name="NodeList"
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
BorderBrush="{DynamicResource Brush.Border}"
|
||||
BorderThickness="1"
|
||||
ItemContainerStyle="{StaticResource Style.ListViewItem.Table}">
|
||||
<ListView.View>
|
||||
<GridView ColumnHeaderContainerStyle="{StaticResource Style.GridViewHeader.Flat}">
|
||||
<GridViewColumn Width="180" Header="节点名称">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Style="{StaticResource Style.TextBox.InTable}" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
|
||||
<GridViewColumn Width="180" Header="IP 地址">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Style="{StaticResource Style.TextBox.InTable}" Text="{Binding Ip, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
|
||||
<GridViewColumn Width="100" Header="端口">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Style="{StaticResource Style.TextBox.InTable}" Text="{Binding Port, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
|
||||
<GridViewColumn Width="180" Header="连通性状态">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Padding="8,2"
|
||||
HorizontalAlignment="Left"
|
||||
Background="#11000000"
|
||||
CornerRadius="4">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Foreground="{Binding StatusColor}"
|
||||
Text="{Binding Status}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
|
||||
<GridViewColumn Width="80" Header="操作">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
Width="45"
|
||||
Height="24"
|
||||
Padding="0"
|
||||
Click="DeleteNode_Click"
|
||||
Content="删除"
|
||||
FontSize="11"
|
||||
Style="{StaticResource Btn.Danger}" />
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</Grid>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Margin="0,20,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,15,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource Brush.Text.Secondary}"
|
||||
Text="修改后请点击检测 →" />
|
||||
<Button
|
||||
Width="110"
|
||||
Height="32"
|
||||
Margin="0,0,10,0"
|
||||
Click="Check_Click"
|
||||
Content="🔍 立即检测"
|
||||
Style="{StaticResource Btn.Ghost}" />
|
||||
<Button
|
||||
Width="120"
|
||||
Height="32"
|
||||
Background="{DynamicResource Brush.Accent}"
|
||||
BorderThickness="0"
|
||||
Click="Apply_Click"
|
||||
Content="保存并应用"
|
||||
Foreground="White" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,94 +0,0 @@
|
||||
using SHH.CameraDashboard.Services;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace SHH.CameraDashboard
|
||||
{
|
||||
public partial class WizardControl : UserControl
|
||||
{
|
||||
private const string ServerConfigFile = "servers.config.json";
|
||||
|
||||
// 绑定源
|
||||
public ObservableCollection<ServerNode> Nodes { get; set; } = new ObservableCollection<ServerNode>();
|
||||
|
||||
// 定义关闭事件,通知主窗体关闭模态框
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public WizardControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 使用泛型加载,指定返回类型为 ObservableCollection<ServerNode>
|
||||
// 这里的 LoadServers 逻辑变为了通用的 Load<T>
|
||||
Nodes = StorageService.Load<ObservableCollection<ServerNode>>(ServerConfigFile);
|
||||
|
||||
NodeList.ItemsSource = Nodes;
|
||||
}
|
||||
|
||||
private void AddNode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Nodes.Add(new ServerNode());
|
||||
}
|
||||
|
||||
private void DeleteNode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button btn && btn.DataContext is ServerNode node)
|
||||
{
|
||||
Nodes.Remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Check_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 禁用按钮防止重复点击
|
||||
var btn = sender as Button;
|
||||
if (btn != null) btn.IsEnabled = false;
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
node.SetResult(false, "⏳ 检测中...");
|
||||
|
||||
// 构造 URL
|
||||
string url = $"http://{node.Ip}:{node.Port}/api/Cameras";
|
||||
// 或者是 /api/health,看你的后端提供什么接口
|
||||
|
||||
try
|
||||
{
|
||||
// 【修改】这里调用封装好的 HttpService
|
||||
// 我们使用 TestConnectionAsync,它内部会触发 OnApiLog 事件记录日志
|
||||
bool isConnected = await HttpService.TestConnectionAsync(url);
|
||||
|
||||
if (isConnected)
|
||||
node.SetResult(true, "✅ 连接成功");
|
||||
else
|
||||
node.SetResult(false, "❌ 状态码异常");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 异常也被 HttpService 记录了,这里只负责更新 UI 状态
|
||||
node.SetResult(false, "❌ 无法连接");
|
||||
}
|
||||
}
|
||||
|
||||
if (btn != null) btn.IsEnabled = true;
|
||||
}
|
||||
|
||||
private void Apply_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 将当前的 Nodes 集合保存到指定文件
|
||||
StorageService.Save(Nodes, ServerConfigFile);
|
||||
|
||||
// 同步到全局单例内存中
|
||||
AppGlobalData.SaveConfig(Nodes);
|
||||
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user