具备界面基础功能

This commit is contained in:
2026-01-01 22:40:32 +08:00
parent 0c86b4dad3
commit d039559402
81 changed files with 8333 additions and 1905 deletions

View File

@@ -0,0 +1,321 @@
<UserControl
x:Class="SHH.CameraDashboard.CameraEdit"
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:local="clr-namespace:SHH.CameraDashboard"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance Type=local:CameraEditViewModel}"
d:DesignHeight="700"
d:DesignWidth="500"
mc:Ignorable="d">
<Grid Margin="15" Background="{DynamicResource Brush.Bg.Window}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Margin="0,0,0,15"
FontSize="18"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="设备参数配置" />
<ScrollViewer
Grid.Row="1"
Padding="0,0,5,0"
VerticalScrollBarVisibility="Auto">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Primary}" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Height" Value="26" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="{DynamicResource Brush.Bg.Input}" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Primary}" />
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border}" />
</Style>
</StackPanel.Resources>
<GroupBox
Margin="0,0,0,15"
Padding="10"
BorderBrush="{DynamicResource Brush.Border}"
Foreground="{DynamicResource Brush.Text.Primary}"
Header="基础身份 (Identity)">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<WrapPanel Grid.ColumnSpan="2">
<TextBlock Margin="0,5" Text="设备 ID *" />
<TextBox
Width="80"
Margin="30,5"
Text="{Binding EditingDto.Id}" />
<TextBlock Margin="0,5" Text="品牌类型" />
<ComboBox
Width="180"
Margin="5,5,0,5"
DisplayMemberPath="Label"
ItemsSource="{Binding BrandOptions}"
SelectedValue="{Binding EditingDto.Brand}"
SelectedValuePath="Value" />
</WrapPanel>
<TextBlock
Grid.Row="1"
Margin="0,5"
Text="设备名称" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="0,5"
Text="{Binding EditingDto.Name}" />
<TextBlock
Grid.Row="3"
Margin="0,5"
Text="安装位置" />
<TextBox
Grid.Row="3"
Grid.Column="1"
Margin="0,5"
Text="{Binding EditingDto.Location}" />
</Grid>
</GroupBox>
<GroupBox
Margin="0,0,0,15"
Padding="10"
BorderBrush="{DynamicResource Brush.Border}"
Foreground="{DynamicResource Brush.Text.Primary}"
Header="核心连接 (Connectivity)">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Margin="0,5" Text="IP 地址 *" />
<TextBox
Grid.Column="1"
Margin="0,5,10,5"
Text="{Binding EditingDto.IpAddress}" />
<TextBlock
Grid.Column="2"
Margin="0,5"
HorizontalAlignment="Right"
Text="端口 " />
<TextBox
Grid.Column="3"
Margin="5,5,0,5"
Text="{Binding EditingDto.Port}" />
<WrapPanel Grid.Row="1" Grid.ColumnSpan="4">
<TextBlock
Width="80"
Margin="0,5,0,5"
Text="用户名" />
<TextBox
Width="120"
Margin="0,5,0,5"
Text="{Binding EditingDto.Username}" />
<TextBlock
Width="40"
Margin="33,5,10,5"
Text="密码" />
<TextBox
Width="140"
Margin="0,5,0,5"
Text="{Binding EditingDto.Password}" />
</WrapPanel>
<WrapPanel Grid.Row="2" Grid.ColumnSpan="4">
<TextBlock
Width="80"
Margin="0,5,0,5"
Text="句柄" />
<TextBox
Width="70"
Margin="0,5,0,5"
Text="{Binding EditingDto.RenderHandle}" />
<TextBlock
Width="40"
Margin="23,5,0,5"
Text="通道号" />
<TextBox
Width="40"
Margin="10,5,0,5"
Text="{Binding EditingDto.ChannelIndex}" />
<TextBlock
Width="50"
Margin="20,0,10,0"
Text="码流类型" />
<ComboBox
Width="80"
Margin="0,5,0,5"
SelectedIndex="{Binding EditingDto.StreamType}">
<ComboBoxItem Content="主码流" />
<ComboBoxItem Content="子码流" />
</ComboBox>
</WrapPanel>
</Grid>
</GroupBox>
<GroupBox
Margin="0,0,0,15"
Padding="10"
BorderBrush="{DynamicResource Brush.Border}"
Foreground="{DynamicResource Brush.Text.Primary}"
Header="关联主板 (Metadata)">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<TextBlock Margin="0,5" Text="主板 IP" />
<TextBox
Grid.Column="1"
Margin="0,5,10,5"
Text="{Binding EditingDto.MainboardIp}" />
<TextBlock
Grid.Column="2"
Margin="0,5"
HorizontalAlignment="Right"
Text="端口 " />
<TextBox
Grid.Column="3"
Margin="5,5,0,5"
Text="{Binding EditingDto.MainboardPort}" />
</Grid>
</GroupBox>
<GroupBox
Margin="0,0,0,15"
Padding="10"
BorderBrush="{DynamicResource Brush.Border}"
Foreground="{DynamicResource Brush.Text.Primary}"
Header="运行参数 (Runtime)">
<WrapPanel>
<TextBlock
Width="30"
Margin="0,5,0,5"
Text="RTSP" />
<TextBox
Width="360"
Height="60"
Margin="20,5,0,5"
VerticalContentAlignment="Top"
Text="{Binding EditingDto.RtspPath}" />
</WrapPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
<StackPanel
Grid.Row="2"
Margin="0,10,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Width="100"
Height="32"
Margin="0,0,10,0"
Command="{Binding SaveCommand}"
Content="保存配置"
Cursor="Hand">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{DynamicResource Brush.State.Success}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.9" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button
Width="100"
Height="32"
Command="{Binding CancelCommand}"
Content="取消"
Cursor="Hand">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Primary}" />
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource Brush.Bg.Hover}" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace SHH.CameraDashboard
{
/// <summary>
/// CameraEdit.xaml 的交互逻辑
/// </summary>
public partial class CameraEdit : UserControl
{
public CameraEdit()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,317 @@
<UserControl
x:Class="SHH.CameraDashboard.CameraImageSubscription"
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:local="clr-namespace:SHH.CameraDashboard"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="600"
d:DesignWidth="900"
mc:Ignorable="d">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<local:SubscriptionTypeConverter x:Key="TypeConverter" />
</UserControl.Resources>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="0,0,0,15"
Orientation="Horizontal">
<TextBlock
Margin="0,0,8,0"
VerticalAlignment="Center"
FontSize="18"
Text="📡" />
<TextBlock
VerticalAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="订阅管理" />
<TextBlock
Margin="5,2,0,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text=" (实时分发控制)" />
</StackPanel>
<Border
Grid.Row="1"
Background="{DynamicResource Brush.Bg.Panel}"
BorderBrush="{DynamicResource Brush.Border}"
BorderThickness="1"
CornerRadius="{StaticResource Radius.Normal}">
<DataGrid
BorderThickness="0"
IsReadOnly="True"
ItemsSource="{Binding Subscriptions}"
SelectedItem="{Binding SelectedSubscription}"
SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn
Width="80"
Binding="{Binding AppId}"
Header="App ID">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Primary}" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn
Width="125"
Binding="{Binding Type, Converter={StaticResource TypeConverter}}"
Header="类型">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="5,0" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Width="70" Header="目标帧率">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border
Padding="5,1"
HorizontalAlignment="Center"
Background="{DynamicResource Brush.Bg.L3}"
CornerRadius="3"
ToolTip="配置的期望帧率">
<TextBlock
FontSize="11"
Foreground="{DynamicResource Brush.Text.Inverse}"
Text="{Binding DisplayFps, StringFormat={}{0} FPS}" />
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="70" Header="实际帧率">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border
Padding="5,1"
HorizontalAlignment="Center"
CornerRadius="3">
<TextBlock
FontSize="11"
FontWeight="Bold"
Foreground="{DynamicResource Brush.State.Success}"
Text="{Binding RealFps, StringFormat={}{0:F1} FPS}" />
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="2*"
Binding="{Binding Memo}"
Header="备注">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="ToolTip" Value="{Binding Memo}" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Secondary}" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Width="50" Header="操作">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Height="30"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding AppId}"
Content="注销"
Style="{StaticResource Btn.Ghost.Danger}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Border>
<Border
Grid.Row="3"
Padding="20"
Background="{DynamicResource Brush.Bg.Panel}"
BorderBrush="{DynamicResource Brush.Border}"
BorderThickness="1"
CornerRadius="{StaticResource Radius.Normal}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Row="0"
Grid.ColumnSpan="5"
Margin="0,0,0,15"
Orientation="Horizontal">
<TextBlock
Margin="0,0,5,0"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="" />
<TextBlock
FontSize="14"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="新增 / 编辑订阅" />
</StackPanel>
<TextBlock
Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="App ID:" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="0,5"
Text="{Binding EditAppId}" />
<TextBlock
Grid.Row="1"
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="类型:" />
<ComboBox
Grid.Row="1"
Grid.Column="4"
Height="{StaticResource Height.Input}"
Margin="0,5"
VerticalContentAlignment="Center"
DisplayMemberPath="Value"
ItemsSource="{Binding SubscriptionTypes}"
SelectedValue="{Binding EditType}"
SelectedValuePath="Key" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="目标帧率:" />
<Grid
Grid.Row="2"
Grid.Column="1"
Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Slider
VerticalAlignment="Center"
IsSnapToTickEnabled="True"
Maximum="25"
Minimum="1"
Value="{Binding EditFps}" />
<TextBlock
Grid.Column="1"
Margin="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Brand}"
Text="{Binding EditFps, StringFormat={}{0} fps}" />
</Grid>
<TextBlock
Grid.Row="2"
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="备注:" />
<TextBox
Grid.Row="2"
Grid.Column="4"
Margin="0,5"
Text="{Binding EditMemo}" />
<Grid
Grid.Row="3"
Grid.ColumnSpan="5"
Visibility="{Binding IsNetworkType, Converter={StaticResource BoolToVis}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="目标 IP:" />
<TextBox
Grid.Column="1"
Margin="0,5"
Text="{Binding EditTargetIp}" />
<TextBlock
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="目标端口:" />
<TextBox
Grid.Column="4"
Margin="0,5"
Text="{Binding EditTargetPort}" />
</Grid>
<StackPanel
Grid.Row="4"
Grid.ColumnSpan="5"
Margin="0,15,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Width="100"
Margin="0,0,10,0"
Command="{Binding ClearFormCommand}"
Content="重置表单"
Style="{StaticResource Btn.Ghost}" />
<Button
Width="120"
Command="{Binding SaveCommand}"
Content="提交保存"
Style="{StaticResource PrimaryBtnStyle}" />
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace SHH.CameraDashboard
{
/// <summary>
/// CameraImageSubscription.xaml 的交互逻辑
/// </summary>
public partial class CameraImageSubscription : UserControl
{
public CameraImageSubscription()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,274 @@
<UserControl
x:Class="SHH.CameraDashboard.CameraImgProc"
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:local="clr-namespace:SHH.CameraDashboard"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:CameraImgProcViewModel}"
d:DesignHeight="650"
d:DesignWidth="500"
mc:Ignorable="d">
<UserControl.Resources>
<local:BoolToMaxScaleConverter x:Key="BoolToMaxScaleConverter" />
</UserControl.Resources>
<Grid Margin="20" Background="{DynamicResource Brush.Bg.Window}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Margin="0,0,0,20" LastChildFill="False">
<TextBlock
VerticalAlignment="Center"
FontSize="18"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding Title}" />
<Button
Width="30"
Height="30"
Command="{Binding CancelCommand}"
Content="✕"
DockPanel.Dock="Right"
Style="{DynamicResource Btn.Ghost}" />
</DockPanel>
<Border
Grid.Row="1"
Margin="0,0,0,15"
Padding="15"
Background="{DynamicResource Brush.Bg.Panel}"
CornerRadius="6">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<TextBlock Foreground="{DynamicResource Brush.Text.Secondary}" Text="源画面分辨率:" />
<TextBlock
Margin="10,0,0,0"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding SourceResolutionText}" />
</StackPanel>
</Border>
<GroupBox
Grid.Row="2"
Margin="0,0,0,15"
Padding="15"
BorderBrush="{DynamicResource Brush.Border}"
Foreground="{DynamicResource Brush.Text.Secondary}"
Header="分辨率调整">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DockPanel LastChildFill="False">
<StackPanel>
<TextBlock Foreground="{DynamicResource Brush.Text.Primary}" Text="允许缩小 (Allow Shrink)" />
<TextBlock
FontSize="11"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="可将画面缩小至任意尺寸" />
</StackPanel>
<CheckBox
DockPanel.Dock="Right"
IsChecked="{Binding AllowShrink}"
Style="{DynamicResource Style.CheckBox.Switch}" />
</DockPanel>
<DockPanel
Grid.Column="1"
Margin="20,0,0,0"
LastChildFill="False">
<StackPanel>
<TextBlock Foreground="{DynamicResource Brush.Text.Primary}" Text="允许放大 (Allow Enlarge)" />
<TextBlock
FontSize="11"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="最大支持 1080P" />
</StackPanel>
<CheckBox
Margin="5,0,0,0"
DockPanel.Dock="Right"
IsChecked="{Binding AllowEnlarge}"
IsEnabled="{Binding CanCheckExpand}"
Style="{DynamicResource Style.CheckBox.Switch}" />
</DockPanel>
</Grid>
<StackPanel Grid.Row="1" Margin="0,20,0,0">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Foreground="{DynamicResource Brush.State.Warning}"
Text="请先开启“允许缩小”或“允许放大”以调整分辨率">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSliderEnabled}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Grid Margin="0,0,0,15" IsEnabled="{Binding IsSliderEnabled}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="0,0,10,0"
VerticalAlignment="Center"
Text="缩放比" />
<Slider
Grid.Column="1"
VerticalAlignment="Center"
IsSnapToTickEnabled="True"
Maximum="{Binding AllowEnlarge, Converter={StaticResource BoolToMaxScaleConverter}}"
Minimum="10"
TickFrequency="5"
Value="{Binding ScalePercent}" />
<TextBlock
Grid.Column="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Brand}"
Text="{Binding ScalePercent, StringFormat={}{0:F0}%}" />
</Grid>
<Grid IsEnabled="{Binding IsSliderEnabled}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock
Margin="0,0,0,5"
FontSize="12"
Text="宽度" />
<TextBox
Height="32"
Style="{DynamicResource Style.TextBox.Search}"
Text="{Binding TargetWidth, UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Center" />
</StackPanel>
<ToggleButton
Grid.Column="1"
Margin="15,18,15,0"
IsChecked="{Binding IsRatioLocked}"
ToolTip="锁定长宽比">
<Path
Width="16"
Height="16"
Data="M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 17,6V8H18M12,4A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,4Z"
Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ToggleButton}}"
Stretch="Uniform" />
</ToggleButton>
<StackPanel Grid.Column="2">
<TextBlock
Margin="0,0,0,5"
FontSize="12"
Text="高度" />
<TextBox
Height="32"
Style="{DynamicResource Style.TextBox.Search}"
Text="{Binding TargetHeight, UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Center" />
</StackPanel>
</Grid>
</StackPanel>
<StackPanel Grid.Row="2" Margin="0,0,0,20">
<TextBlock
Margin="0,20,0,10"
FontSize="16"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="常用尺寸预设" />
<WrapPanel>
<WrapPanel.Resources>
<Style BasedOn="{StaticResource Btn.Ghost.Secondary}" TargetType="Button">
<Setter Property="Margin" Value="0,0,8,8" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Padding" Value="6,0" />
<Setter Property="Height" Value="16" />
<Setter Property="Command" Value="{Binding ApplyPresetCommand}" />
</Style>
</WrapPanel.Resources>
<Button CommandParameter="1920x1080" Content="1080P (1920x1080)" />
<Button CommandParameter="1280x720" Content="720P (1280x720)" />
<Button CommandParameter="704x576" Content="D1 (704x576)" />
<Button CommandParameter="640x480" Content="VGA (640x480)" />
<Button CommandParameter="640x640" Content="Square (640x640)" />
<Button CommandParameter="352x288" Content="CIF (352x288)" />
</WrapPanel>
</StackPanel>
</Grid>
</GroupBox>
<StackPanel Grid.Row="3">
<TextBlock
Margin="0,0,0,10"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="高级处理" />
<Border
Padding="10"
Background="{DynamicResource Brush.Bg.Input}"
CornerRadius="4">
<DockPanel LastChildFill="False">
<StackPanel>
<TextBlock Foreground="{DynamicResource Brush.Text.Primary}" Text="图像画质增强 (Image Enhancement)" />
<TextBlock
FontSize="11"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="自动调节对比度与锐度" />
</StackPanel>
<CheckBox
DockPanel.Dock="Right"
IsChecked="{Binding EnhanceImage}"
Style="{DynamicResource Style.CheckBox.Switch}" />
</DockPanel>
</Border>
</StackPanel>
<StackPanel
Grid.Row="4"
Margin="0,20,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Width="100"
Margin="0,0,10,0"
Command="{Binding CancelCommand}"
Content="取消"
Style="{DynamicResource Btn.Ghost}" />
<Button
Width="120"
Command="{Binding SaveCommand}"
Content="保存应用" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,14 @@
using System.Windows.Controls;
namespace SHH.CameraDashboard;
/// <summary>
/// CameraImgProc.xaml 的交互逻辑
/// </summary>
public partial class CameraImgProc : UserControl
{
public CameraImgProc()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,364 @@
<UserControl
x:Class="SHH.CameraDashboard.CameraItemTop"
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:local="clr-namespace:SHH.CameraDashboard"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="100"
d:DesignWidth="800"
mc:Ignorable="d">
<Border
Padding="20,12,20,6"
Background="{DynamicResource Brush.Bg.Panel}"
BorderBrush="{DynamicResource Brush.Border}"
BorderThickness="0,0,0,1">
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelection}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock
FontSize="16"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.5"
Text="请在左侧列表中选择一个摄像头以查看详情" />
</Grid>
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelection}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="210" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<WrapPanel>
<!-- 在线、离线小圆点 -->
<Border
Width="10"
Height="10"
Margin="0,0,10,0"
VerticalAlignment="Center"
CornerRadius="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#FF5555" />
<Style.Triggers>
<DataTrigger Binding="{Binding Camera.IsPhysicalOnline}" Value="True">
<Setter Property="Background" Value="#55FF55" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<!-- 播放状态、ID号 -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Border
Margin="6,2,0,0"
Padding="6,2"
VerticalAlignment="Center"
CornerRadius="4">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#10808080" />
<Style.Triggers>
<DataTrigger Binding="{Binding Camera.Status}" Value="Playing">
<Setter Property="Background" Value="#00CC6A" />
</DataTrigger>
<DataTrigger Binding="{Binding Camera.Status}" Value="Faulted">
<Setter Property="Background" Value="#FF5555" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock
FontSize="11"
FontWeight="SemiBold"
Foreground="White"
Text="{Binding Camera.Status}" />
</Border>
<TextBlock
Grid.Row="1"
Margin="6,8,0,0"
FontSize="13"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.6"
Text="ID:" />
<TextBlock
Grid.Row="1"
Margin="26,8,0,0"
FontSize="13"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="{Binding Camera.Id}" />
</Grid>
</WrapPanel>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<WrapPanel>
<!-- 设备名称 -->
<TextBlock
VerticalAlignment="Center"
FontSize="14"
FontWeight="Bold">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Camera.Name}" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Primary}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Camera.Name}" Value="">
<Setter Property="Text" Value="未命名" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Secondary}" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding Camera.Name}" Value="{x:Null}">
<Setter Property="Text" Value="未命名" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Secondary}" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock
Margin="4,0,0,0"
FontSize="13"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.5"
Text="{Binding Camera.Brand}" />
</WrapPanel>
<!-- IP 地址 -->
<TextBlock
Grid.Row="1"
HorizontalAlignment="Left"
FontFamily="Consolas"
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding Camera.IpAddress}" />
</Grid>
<WrapPanel Grid.Column="2" VerticalAlignment="Center">
<!-- 分辨率 -->
<StackPanel Margin="20,0,20,0">
<TextBlock
HorizontalAlignment="Right"
FontSize="10"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.5"
Text="RESOLUTION" />
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding Camera.Width}" />
<TextBlock
Margin="2,0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text=" x " />
<TextBlock
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding Camera.Height}" />
</StackPanel>
</StackPanel>
<Border BorderBrush="{DynamicResource Brush.Border}" BorderThickness="0,0,1,0" />
<!-- 实际帧 -->
<StackPanel Margin="20,0,20,0">
<TextBlock
HorizontalAlignment="Right"
FontSize="10"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.5"
Text="REAL FPS" />
<TextBlock
HorizontalAlignment="Right"
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding Camera.RealFps}" />
</StackPanel>
</WrapPanel>
<StackPanel
Grid.Column="4"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button
Width="32"
Height="32"
Margin="8,0,8,0"
Command="{Binding DeleteCommand}"
Style="{StaticResource Btn.Ghost.Danger}"
ToolTip="删除此摄像头">
<Path
Width="24"
Height="24"
Data="{StaticResource Icon.Trash}"
Style="{StaticResource Style.IconPath}" />
</Button>
<Button
Width="32"
Height="32"
Margin="8,0,8,0"
Command="{Binding EditDeviceCommand}"
Style="{StaticResource Btn.Ghost.Info}"
ToolTip="编辑设备配置">
<Path
Width="18"
Height="18"
Data="{StaticResource Icon.Action.Edit}"
Style="{StaticResource Style.IconPath}" />
</Button>
<Button
Width="32"
Height="32"
Margin="5,0,0,0"
Command="{Binding ImageProcessCommand}"
Style="{StaticResource Btn.Ghost.Info}"
ToolTip="图像处理">
<Path
Width="24"
Height="24"
Data="{StaticResource Icon.Action.Process}"
Style="{StaticResource Style.IconPath}" />
</Button>
<Button
Width="32"
Height="32"
Margin="5,0,0,0"
Command="{Binding ImageSubscribeCommand}"
Style="{StaticResource Btn.Ghost.Info}"
ToolTip="图像订阅">
<Path
Width="18"
Height="18"
Data="{StaticResource Icon.Action.Subscribe}"
Style="{StaticResource Style.IconPath}" />
</Button>
<Button
Width="32"
Height="32"
Margin="0,0,5,0"
Command="{Binding PtzCommand}"
Style="{StaticResource Btn.Ghost.Info}"
ToolTip="云台控制 (PTZ)">
<Path
Width="24"
Height="24"
Data="{StaticResource Icon.Ptz}"
Style="{StaticResource Style.IconPath}" />
</Button>
<Button
Width="32"
Height="32"
Margin="8,0,8,0"
Background="Transparent"
BorderThickness="0"
Command="{Binding TogglePlayCommand}"
Cursor="Hand">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border
x:Name="bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8">
<Path
Width="24"
Height="24"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform">
<Path.Style>
<Style TargetType="Path">
<Setter Property="Data" Value="{StaticResource Icon.Action.Play}" />
<Setter Property="Fill" Value="{DynamicResource Brush.Text.Secondary}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Camera.Status}" Value="Playing">
<Setter Property="Data" Value="{StaticResource Icon.Action.Pause}" />
<Setter Property="Fill" Value="#00CC6A" />
</DataTrigger>
<DataTrigger Binding="{Binding Camera.Status}" Value="Connecting">
<Setter Property="Data" Value="{StaticResource Icon.Status.Loading}" />
<Setter Property="Fill" Value="{DynamicResource Brush.State.Warning}" />
</DataTrigger>
<DataTrigger Binding="{Binding Camera.IsPhysicalOnline}" Value="False">
<Setter Property="Data" Value="{StaticResource Icon.Status.WifiOff}" />
<Setter Property="Fill" Value="{DynamicResource Brush.State.Danger}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="#1A808080" />
</Trigger>
<DataTrigger Binding="{Binding Camera.IsPhysicalOnline}" Value="False">
<Setter Property="Cursor" Value="Arrow" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,55 @@
using System.Windows;
using System.Windows.Controls;
namespace SHH.CameraDashboard
{
/// <summary>
/// CameraItemTop.xaml 的交互逻辑
/// </summary>
public partial class CameraItemTop : UserControl
{
// 持有 ViewModel 的实例
private CameraItemTopViewModel _viewModel;
public CameraItemTop()
{
InitializeComponent();
// 1. 初始化 ViewModel
_viewModel = new CameraItemTopViewModel();
// 2. 设置 DataContext这样 XAML 里的 {Binding Name} 就能找到 VM 里的属性
this.DataContext = _viewModel;
}
// ============================================================
// 定义依赖属性 (DependencyProperty) - 这是接收绑定的关键
// ============================================================
// 注册一个名为 "DataSource" 的依赖属性
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register(
nameof(DataSource), // 属性名
typeof(WebApiCameraModel), // 属性类型
typeof(CameraItemTop), // 所属类型
new PropertyMetadata(null, OnDataSourceChanged)); // 回调函数
// 这是一个普通的包装属性,方便代码访问
public WebApiCameraModel DataSource
{
get => (WebApiCameraModel)GetValue(DataSourceProperty);
set => SetValue(DataSourceProperty, value);
}
// 当外部绑定的数据发生变化时(比如用户在左侧列表点选了新项)
private static void OnDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as CameraItemTop;
if (control != null && control._viewModel != null)
{
// 【核心逻辑】将外部传进来的数据,转发给 ViewModel
control._viewModel.Camera = e.NewValue as WebApiCameraModel;
}
}
}
}

View File

@@ -0,0 +1,374 @@
<UserControl
x:Class="SHH.CameraDashboard.CameraList"
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:local="clr-namespace:SHH.CameraDashboard"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="600"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</UserControl.Resources>
<Grid Background="{DynamicResource Brush.Bg.Panel}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
Height="32"
Margin="10,10,10,0"
VerticalContentAlignment="Center"
ItemsSource="{Binding NodeOptions}"
SelectedItem="{Binding SelectedNode}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="SemiBold" Text="{Binding ServiceNodeName}" />
<TextBlock
Margin="5,0,0,0"
Foreground="Gray"
Text="{Binding ServiceNodeIp, StringFormat=' ({0})'}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ServiceNodeIp}" Value="ALL">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid Grid.Row="1" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border
Padding="8,6"
Background="{DynamicResource Brush.Bg.Input}"
CornerRadius="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,0,5,0"
VerticalAlignment="Center"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="🔍" />
<TextBox
Grid.Column="1"
VerticalContentAlignment="Center"
Background="Transparent"
BorderThickness="0"
CaretBrush="{DynamicResource Brush.Text.Primary}"
FontSize="14"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Border>
<WrapPanel Grid.Column="1" Margin="0,5,0,0">
<Button
Width="32"
Height="32"
Margin="5,0,0,0"
Command="{Binding AddCameraCommand}"
Style="{StaticResource Btn.Ghost.Info}"
ToolTip="添加新设备">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Plus}"
Style="{StaticResource Style.IconPath}" />
</Button>
<Button
Width="32"
Height="32"
Margin="5,0,0,0"
Command="{Binding RefreshCommand}"
Style="{StaticResource Btn.Ghost.Info}"
ToolTip="重新加载数据">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Refresh}"
Style="{StaticResource Style.IconPath}" />
</Button>
</WrapPanel>
</Grid>
<ListBox
Grid.Row="2"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
ItemsSource="{Binding FilteredCameras}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedCamera}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="10,8" />
<Setter Property="Margin" Value="0,0,0,1" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Margin="0,0,0,1">
<Border
x:Name="bd"
Margin="5,0"
Background="Transparent"
CornerRadius="4"
Opacity="0" />
<Border Padding="{TemplateBinding Padding}">
<ContentPresenter />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource Brush.Text.Primary}" />
<Setter TargetName="bd" Property="Opacity" Value="0.1" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource Brush.Brand}" />
<Setter TargetName="bd" Property="Opacity" Value="0.2" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Brand}" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="10"
Height="10"
Margin="0,0,10,0"
VerticalAlignment="Center"
CornerRadius="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#FF5555" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsPhysicalOnline}" Value="True">
<Setter Property="Background" Value="#55FF55" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<WrapPanel>
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
FontSize="14"
FontWeight="Bold">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Name}" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Primary}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="">
<Setter Property="Text" Value="未命名" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Secondary}" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding Name}" Value="{x:Null}">
<Setter Property="Text" Value="未命名" />
<Setter Property="Foreground" Value="{DynamicResource Brush.Text.Secondary}" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock
Margin="12,0,10,0"
HorizontalAlignment="Center"
FontSize="13"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.5"
Text="{Binding Id}" />
</WrapPanel>
<Button
Grid.Column="1"
Width="24"
Height="24"
Margin="5,0,0,0"
Background="Transparent"
BorderThickness="0"
ToolTip="{Binding Status}">
<Path
Width="14"
Height="14"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform">
<Path.Style>
<Style TargetType="Path">
<Setter Property="Data" Value="{StaticResource Icon.Action.Play}" />
<Setter Property="Fill" Value="{DynamicResource Brush.Brand}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsPhysicalOnline}" Value="False">
<Setter Property="Data" Value="{StaticResource Icon.Status.WifiOff}" />
<Setter Property="Fill" Value="{DynamicResource Brush.State.Danger}" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Connecting">
<Setter Property="Data" Value="{StaticResource Icon.Status.Loading}" />
<Setter Property="Fill" Value="{DynamicResource Brush.State.Warning}" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Authorizing">
<Setter Property="Data" Value="{StaticResource Icon.Status.Loading}" />
<Setter Property="Fill" Value="{DynamicResource Brush.State.Warning}" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Playing">
<Setter Property="Data" Value="{StaticResource Icon.Action.Pause}" />
<Setter Property="Fill" Value="{DynamicResource Brush.Status.Warning}" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Streaming">
<Setter Property="Data" Value="{StaticResource Icon.Action.Pause}" />
<Setter Property="Fill" Value="{DynamicResource Brush.Status.Warning}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
</Grid>
<StackPanel Margin="0,2,0,0" Orientation="Horizontal">
<TextBlock
FontSize="12"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding IpAddress}" />
<TextBlock
Margin="4,0"
FontSize="12"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.3"
Text="|" />
<TextBlock
FontSize="12"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.7"
Text="{Binding Brand}" />
</StackPanel>
<StackPanel Margin="0,2,0,0" Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Playing">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Streaming">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock
FontSize="11"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.6">
<Run Foreground="{DynamicResource Brush.Text.Primary}" Text="{Binding Width}" />
<Run Text="x" />
<Run Foreground="{DynamicResource Brush.Text.Primary}" Text="{Binding Height}" />
</TextBlock>
<TextBlock Text=" " />
<TextBlock
FontSize="11"
Foreground="{DynamicResource Brush.Text.Secondary}"
Opacity="0.6">
<Run Text="FPS:" />
<Run
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding RealFps}" />
</TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid
Grid.Row="2"
Background="{DynamicResource Brush.Bg.Panel}"
Visibility="{Binding IsLoading, Converter={StaticResource BoolToVis}}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ProgressBar
Width="100"
Height="4"
Margin="0,0,0,10"
IsIndeterminate="True" />
<TextBlock
HorizontalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="正在同步数据..." />
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,17 @@
using System.Windows.Controls;
namespace SHH.CameraDashboard
{
/// <summary>
/// CameraList.xaml 的交互逻辑
/// </summary>
public partial class CameraList : UserControl
{
public CameraList()
{
InitializeComponent();
DataContext = new CameraListViewModel();
}
}
}

View File

@@ -0,0 +1,413 @@
<UserControl
x:Class="SHH.CameraDashboard.CameraPtz"
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:local="clr-namespace:SHH.CameraDashboard"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:CameraPtzViewModel}"
d:DesignHeight="600"
d:DesignWidth="500"
mc:Ignorable="d">
<Grid Margin="20" Background="{DynamicResource Brush.Bg.Window}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Margin="0,0,0,20" LastChildFill="False">
<TextBlock
VerticalAlignment="Center"
FontSize="18"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="{Binding Title}" />
</DockPanel>
<Grid Grid.Row="1" Margin="0,0,0,30">
<Grid
Width="220"
Height="220"
Margin="50,0,0,0"
HorizontalAlignment="Left">
<Ellipse
Fill="{DynamicResource Brush.Bg.Input}"
Stroke="{DynamicResource Brush.Border}"
StrokeThickness="1" />
<Button
Width="60"
Height="60"
Margin="0,10,0,0"
VerticalAlignment="Top"
local:TouchBehavior.Action="{x:Static local:PtzAction.Up}"
Style="{StaticResource Btn.Ghost.Secondary}">
<Path
Width="24"
Height="24"
Data="{StaticResource Icon.ChevronUp}"
Style="{StaticResource Style.IconPath}" />
</Button>
<Button
Width="60"
Height="60"
Margin="0,0,0,10"
VerticalAlignment="Bottom"
local:TouchBehavior.Action="{x:Static local:PtzAction.Down}"
Style="{StaticResource Btn.Ghost.Secondary}">
<Path
Width="24"
Height="24"
Data="{StaticResource Icon.ChevronUp}"
RenderTransformOrigin="0.5,0.5"
Style="{StaticResource Style.IconPath}">
<Path.RenderTransform>
<RotateTransform Angle="180" />
</Path.RenderTransform>
</Path>
</Button>
<Button
Width="60"
Height="60"
Margin="10,0,0,0"
HorizontalAlignment="Left"
local:TouchBehavior.Action="{x:Static local:PtzAction.Left}"
Style="{StaticResource Btn.Ghost.Secondary}">
<Path
Width="24"
Height="24"
Data="{StaticResource Icon.ChevronUp}"
RenderTransformOrigin="0.5,0.5"
Style="{StaticResource Style.IconPath}">
<Path.RenderTransform>
<RotateTransform Angle="-90" />
</Path.RenderTransform>
</Path>
</Button>
<Button
Width="60"
Height="60"
Margin="0,0,10,0"
HorizontalAlignment="Right"
local:TouchBehavior.Action="{x:Static local:PtzAction.Right}"
Style="{StaticResource Btn.Ghost.Secondary}">
<Path
Width="24"
Height="24"
Data="{StaticResource Icon.ChevronUp}"
RenderTransformOrigin="0.5,0.5"
Style="{StaticResource Style.IconPath}">
<Path.RenderTransform>
<RotateTransform Angle="90" />
</Path.RenderTransform>
</Path>
</Button>
<Button
Width="60"
Height="60"
IsHitTestVisible="False"
Opacity="0.8"
Style="{StaticResource Btn.Icon.Info}">
<Path
Width="28"
Height="28"
Data="{StaticResource Icon.Ptz}"
Style="{StaticResource Style.IconPath}" />
</Button>
</Grid>
<WrapPanel
Margin="300,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<Button
Width="150"
Height="48"
Margin="0,0,5,0"
Command="{Binding SyncTimeCommand}"
Style="{StaticResource Btn.Ghost.Info}">
<StackPanel Orientation="Horizontal">
<Path
Width="32"
Height="32"
Data="{StaticResource Icon.Action.Time}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Text="一键校时" />
</StackPanel>
</Button>
<Button
Width="150"
Height="48"
Margin="5,20,0,0"
Command="{Binding RebootCommand}"
Style="{StaticResource Btn.Ghost.Danger}">
<StackPanel Orientation="Horizontal">
<Path
Width="32"
Height="32"
Data="{StaticResource Icon.Action.Reboot}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Text="远程重启" />
</StackPanel>
</Button>
</WrapPanel>
</Grid>
<Border
Grid.Row="2"
Padding="15"
Background="{DynamicResource Brush.Bg.Input}"
CornerRadius="8">
<StackPanel>
<TextBlock
Margin="0,0,0,15"
FontSize="18"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Secondary}"
Text="镜头与辅助" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="15" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="变倍" />
</StackPanel>
<Button
Grid.Column="1"
Width="120"
local:TouchBehavior.Action="{x:Static local:PtzAction.ZoomOut}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Minus}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="缩小" />
</StackPanel>
</Button>
<Button
Grid.Column="2"
Width="120"
local:TouchBehavior.Action="{x:Static local:PtzAction.ZoomIn}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Plus}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="放大" />
</StackPanel>
</Button>
<StackPanel
Grid.Row="1"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="聚焦" />
</StackPanel>
<Button
Grid.Row="1"
Grid.Column="1"
Width="120"
local:TouchBehavior.Action="{x:Static local:PtzAction.FocusNear}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Focus.Near}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="拉近" />
</StackPanel>
</Button>
<Button
Grid.Row="1"
Grid.Column="2"
Width="120"
local:TouchBehavior.Action="{x:Static local:PtzAction.FocusFar}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Focus.Far}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="拉远" />
</StackPanel>
</Button>
<StackPanel
Grid.Row="2"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Margin="6,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="光圈" />
</StackPanel>
<Button
Grid.Row="2"
Grid.Column="1"
Width="120"
local:TouchBehavior.Action="{x:Static local:PtzAction.IrisClose}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Iris.Small}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="变小" />
</StackPanel>
</Button>
<Button
Grid.Row="2"
Grid.Column="2"
Width="120"
local:TouchBehavior.Action="{x:Static local:PtzAction.IrisOpen}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Iris.Large}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="变大" />
</StackPanel>
</Button>
<Border
Grid.Row="3"
Grid.ColumnSpan="3"
Margin="0,5"
VerticalAlignment="Center"
BorderBrush="{DynamicResource Brush.Border}"
BorderThickness="0,0,0,1" />
<StackPanel
Grid.Row="4"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Margin="6,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="雨刷" />
</StackPanel>
<Button
Grid.Row="4"
Grid.Column="1"
Width="120"
Command="{Binding StopCommand}"
CommandParameter="{x:Static local:PtzAction.Wiper}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Action.StopSmall}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="停止" />
</StackPanel>
</Button>
<Button
Grid.Row="4"
Grid.Column="2"
Width="120"
Command="{Binding StartCommand}"
CommandParameter="{x:Static local:PtzAction.Wiper}"
Style="{StaticResource Btn.Ghost.Secondary}">
<StackPanel Orientation="Horizontal">
<Path
Width="16"
Height="16"
Data="{StaticResource Icon.Action.PlaySmall}"
Style="{StaticResource Style.IconPath}" />
<TextBlock
Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="16"
Text="启动" />
</StackPanel>
</Button>
</Grid>
</StackPanel>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace SHH.CameraDashboard
{
/// <summary>
/// CameraPtz.xaml 的交互逻辑
/// </summary>
public partial class CameraPtz : UserControl
{
public CameraPtz()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,126 @@
<UserControl
x:Class="SHH.CameraDashboard.ServiceNodesDiagnostic"
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:local="clr-namespace:SHH.CameraDashboard"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources>
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<local:InverseNullToVisibilityConverter x:Key="InverseNullToVisibilityConverter" />
</UserControl.Resources>
<Grid Background="{DynamicResource Brush.Bg.Window}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="250" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" MinWidth="300" />
</Grid.ColumnDefinitions>
<ListView
x:Name="LogList"
Grid.Column="0"
Background="Transparent"
BorderThickness="0"
ItemContainerStyle="{StaticResource Style.ListViewItem.Table}"
ItemsSource="{Binding Logs}"
SelectedItem="{Binding SelectedLog}">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource Style.GridViewHeader.Flat}">
<GridViewColumn
Width="150"
DisplayMemberBinding="{Binding Time, StringFormat='MM-dd HH:mm:ss fff'}"
Header="请求时间" />
<GridViewColumn
Width="60"
DisplayMemberBinding="{Binding StatusCode}"
Header="状态" />
<GridViewColumn
Width="70"
DisplayMemberBinding="{Binding ElapsedMilliseconds}"
Header="耗时 ms" />
<GridViewColumn
Width="60"
DisplayMemberBinding="{Binding Method}"
Header="动作" />
<GridViewColumn
Width="120"
DisplayMemberBinding="{Binding AppModule}"
Header="模块" />
<GridViewColumn
Width="300"
DisplayMemberBinding="{Binding Url}"
Header="URL" />
</GridView>
</ListView.View>
</ListView>
<GridSplitter
Grid.Column="1"
Width="5"
HorizontalAlignment="Stretch"
Background="{DynamicResource Brush.Bg.Panel}" />
<Grid Grid.Column="2" Background="{DynamicResource Brush.Bg.Input}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
Background="{DynamicResource Brush.Bg.Panel}"
BorderBrush="{DynamicResource Brush.Border}"
BorderThickness="0,0,0,1">
<DockPanel LastChildFill="False">
<StackPanel Orientation="Horizontal">
<RadioButton
Command="{Binding SwitchTabCommand}"
CommandParameter="0"
Content="Request"
GroupName="Logs"
IsChecked="True"
Style="{StaticResource TabRadioStyle}" />
<RadioButton
Command="{Binding SwitchTabCommand}"
CommandParameter="1"
Content="Response"
GroupName="Logs"
Style="{StaticResource TabRadioStyle}" />
</StackPanel>
</DockPanel>
</Border>
<TextBox
Grid.Row="1"
IsReadOnly="True"
Style="{StaticResource Style.TextBox.CodeEditor}"
Text="{Binding DisplayContent}"
Visibility="{Binding SelectedLog, Converter={StaticResource NullToVisibilityConverter}}" />
<StackPanel
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding SelectedLog, Converter={StaticResource InverseNullToVisibilityConverter}}">
<TextBlock Foreground="{DynamicResource Brush.Text.Secondary}" Text="请选择一条日志查看详情" />
</StackPanel>
</Grid>
</Grid>
<CheckBox
Margin="15,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Content="过滤自动发送"
Foreground="{DynamicResource Brush.Text.Secondary}"
IsChecked="{Binding IsFilterAutoLogs}"
Style="{DynamicResource Style.CheckBox.Switch}" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace SHH.CameraDashboard
{
/// <summary>
/// ServiceNodesDiagnostic.xaml 的交互逻辑
/// </summary>
public partial class ServiceNodesDiagnostic : UserControl
{
public ServiceNodesDiagnostic()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,126 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace SHH.CameraDashboard;
public class ServiceNodesViewModel : INotifyPropertyChanged, IOverlayClosable, IDisposable
{
// 接口要求的关闭事件
public event Action? RequestClose;
// 日志数据集合
public ObservableCollection<LogWebApiModel> Logs { get; } = new ObservableCollection<LogWebApiModel>();
private LogWebApiModel _selectedLog;
public LogWebApiModel SelectedLog
{
get => _selectedLog;
set
{
_selectedLog = value;
OnPropertyChanged();
UpdateDisplayContent(); // 选中项改变时更新右侧文本
}
}
private string _displayContent;
public string DisplayContent
{
get => _displayContent;
set { _displayContent = value; OnPropertyChanged(); }
}
private int _currentTabIndex = 0; // 0: Request, 1: Response
// =========================================================
// [新增] 1. 过滤开关属性 (绑定到界面 CheckBox)
// =========================================================
private bool _isFilterAutoLogs;
public bool IsFilterAutoLogs
{
get => _isFilterAutoLogs;
set
{
_isFilterAutoLogs = value;
OnPropertyChanged();
// 可选:切换时是否要清空现有日志?根据需求决定
// if (value) Logs.Clear();
}
}
public ICommand ClearCommand { get; }
public ICommand CloseCommand { get; }
public ICommand SwitchTabCommand { get; }
public ServiceNodesViewModel()
{
// 【关键步骤】订阅全局 API 服务的日志事件
WebApiService.Instance.OnRequestCompleted += OnNewLogReceived;
// 初始化指令
ClearCommand = new RelayCommand<object>(_ => Logs.Clear());
CloseCommand = new RelayCommand<object>(_ => RequestClose?.Invoke());
SwitchTabCommand = new RelayCommand<string>(index =>
{
_currentTabIndex = int.Parse(index);
UpdateDisplayContent();
});
}
/// <summary>
/// 当 WebApiService 产生新日志时的回调
/// </summary>
private void OnNewLogReceived(LogWebApiModel log)
{
// 检查程序是否正在退出
if (Application.Current == null) return;
if (IsFilterAutoLogs && CheckIsAutoLog(log))
{
return; // 如果是自动日志且开启了过滤,直接丢弃,不进入 UI 线程
}
// 【线程安全】必须将操作封送回 UI 线程
Application.Current.Dispatcher.InvokeAsync(() =>
{
// 1. 将最新日志插入顶部
Logs.Insert(0, log);
// 2. 限制日志数量 (例如只保留最近 500 条),防止内存溢出
if (Logs.Count > 500)
{
Logs.RemoveAt(Logs.Count - 1);
}
// 3. (可选) 如果你想让列表自动滚动,可以在 View 的 CodeBehind 里做,或者在这里处理选中项逻辑
}, DispatcherPriority.Background);
}
private bool CheckIsAutoLog(LogWebApiModel log)
{
if (log.IsAutoPost) return true;
return false;
}
private void UpdateDisplayContent()
{
if (SelectedLog == null)
{
DisplayContent = string.Empty;
return;
}
// 根据当前选中的 Tab 显示请求或响应内容
DisplayContent = _currentTabIndex == 0 ? SelectedLog.RequestData : SelectedLog.ResponseData;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public void Dispose()
{
WebApiService.Instance.OnRequestCompleted -= OnNewLogReceived;
}
}

View File

@@ -0,0 +1,194 @@
<UserControl
x:Class="SHH.CameraDashboard.WizardClientsControl"
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="850"
Height="550"
mc:Ignorable="d">
<Border
Background="{DynamicResource Brush.Bg.Window}"
BorderBrush="{DynamicResource Brush.Border.Focus}"
BorderThickness="1"
CornerRadius="{DynamicResource 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="{DynamicResource Size.Font.Huge}"
FontWeight="Bold"
Foreground="{DynamicResource Brush.Text.Primary}"
Text="📡 服务节点配置" />
<Button
Width="30"
Height="30"
Command="{Binding CancelCommand}"
Content="✕"
DockPanel.Dock="Right"
Style="{DynamicResource 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
Width="90"
Height="26"
Padding="15,0"
Background="{DynamicResource Brush.Accent}"
BorderThickness="0"
Command="{Binding AddNodeCommand}"
Content="+ 新增一行"
DockPanel.Dock="Right"
Foreground="White" />
</DockPanel>
</Border>
<ListView
x:Name="NodeList"
Grid.Row="1"
Background="Transparent"
BorderBrush="{DynamicResource Brush.Border}"
BorderThickness="1"
ItemContainerStyle="{DynamicResource Style.ListViewItem.Table}"
ItemsSource="{Binding ServiceNodes}">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{DynamicResource Style.GridViewHeader.Flat}">
<GridViewColumn Width="180" Header="节点名称">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Style="{DynamicResource Style.TextBox.InTable}" Text="{Binding ServiceNodeName, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="180" Header="IP 地址">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Style="{DynamicResource Style.TextBox.InTable}" Text="{Binding ServiceNodeIp, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="100" Header="端口">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Style="{DynamicResource Style.TextBox.InTable}" Text="{Binding ServiceNodePort, 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"
Text="{Binding Status}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="在线">
<Setter Property="Foreground" Value="{DynamicResource Brush.Status.Success}" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="离线">
<Setter Property="Foreground" Value="{DynamicResource Brush.Status.Danger}" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="正在检测...">
<Setter Property="Foreground" Value="{DynamicResource Brush.Accent}" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="警告">
<Setter Property="Foreground" Value="{DynamicResource Brush.Status.Warning}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="80" Header="操作">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Width="45"
Height="24"
Padding="0"
Command="{Binding DataContext.DeleteNodeCommand, RelativeSource={RelativeSource AncestorType=ListView}}"
CommandParameter="{Binding}"
Content="删除"
FontSize="11"
Style="{DynamicResource 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"
Command="{Binding CheckCommand}"
Content="🔍 立即检测"
Style="{DynamicResource Btn.Ghost}" />
<Button
Width="120"
Height="32"
Background="{DynamicResource Brush.Accent}"
BorderThickness="0"
Command="{Binding ConfirmCommand}"
Content="保存并应用"
Foreground="White" />
</StackPanel>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,19 @@
using System.Windows.Controls;
namespace SHH.CameraDashboard
{
/// <summary>
/// WizardClientsControl.xaml 的交互逻辑
/// <para>客户端配置向导的视图,通过数据绑定与 <see cref="ViewModels.WizardClientsViewModel"/> 交互</para>
/// </summary>
public partial class WizardClientsControl : UserControl
{
/// <summary>
/// 初始化 <see cref="WizardClientsControl"/> 类的新实例
/// </summary>
public WizardClientsControl()
{
InitializeComponent();
}
}
}