add:增加文档和上位机
This commit is contained in:
96
上位机/YKC/Pages/ChargerPage.xaml
Normal file
96
上位机/YKC/Pages/ChargerPage.xaml
Normal file
@@ -0,0 +1,96 @@
|
||||
<Page x:Class="YKC.ChargerPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="充电桩管理" Background="#F0F2F5">
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="24">
|
||||
<TextBlock Text="充电桩管理" FontSize="16" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,20"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 写入桩ID -->
|
||||
<Border Grid.Column="0" Background="White" CornerRadius="6"
|
||||
Margin="0,0,8,0" Padding="24">
|
||||
<StackPanel>
|
||||
<TextBlock Text="写入桩 ID" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,16"/>
|
||||
|
||||
<TextBlock Text="桩编号" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtSetPileIdx" Text="1" Width="80"
|
||||
Style="{StaticResource FormInput}" Margin="0,0,0,16"/>
|
||||
|
||||
<TextBlock Text="序列号 (HEX, 14位)" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtSetPileId" Width="240" MaxLength="14"
|
||||
Style="{StaticResource FormInput}" FontFamily="Consolas"
|
||||
Margin="0,0,0,16"/>
|
||||
|
||||
<WrapPanel>
|
||||
<Button Content="写 入" Style="{StaticResource BtnPrimary}" Width="90"
|
||||
Click="SetPileId_Click"/>
|
||||
<TextBlock x:Name="txtSetResult" Foreground="#10B981"
|
||||
FontSize="13" VerticalAlignment="Center" Margin="12,0,0,0"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 查询充电桩 -->
|
||||
<Border Grid.Column="1" Background="White" CornerRadius="6"
|
||||
Margin="8,0,0,0" Padding="24">
|
||||
<StackPanel>
|
||||
<TextBlock Text="查询充电桩信息" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,16"/>
|
||||
|
||||
<TextBlock Text="桩编号" Style="{StaticResource FieldLabel}"/>
|
||||
<WrapPanel Margin="0,0,0,14">
|
||||
<TextBox x:Name="txtInfoPileIdx" Text="1" Width="80"
|
||||
Style="{StaticResource FormInput}"/>
|
||||
<Button Content="查 询" Style="{StaticResource BtnOutline}" Width="80"
|
||||
Margin="10,0,0,0" Click="GetPileInfo_Click"/>
|
||||
<TextBlock x:Name="txtInfoResult" VerticalAlignment="Center"
|
||||
FontSize="13" Margin="10,0,0,0"/>
|
||||
</WrapPanel>
|
||||
|
||||
<!-- 查询结果 -->
|
||||
<Border x:Name="panelInfo" Background="#F8FAFC"
|
||||
CornerRadius="4" Padding="14" Visibility="Collapsed">
|
||||
<StackPanel>
|
||||
<TextBlock Text="序列号" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtInfoSerial" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
<TextBlock Text="类型" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtInfoType" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
<TextBlock Text="枪数量" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtInfoGuns" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
<TextBlock Text="协议版本" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtInfoProto" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
<TextBlock Text="软件版本" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtInfoSw" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
<TextBlock Text="SIM 卡" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtInfoSim" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- 设备操作 -->
|
||||
<Border Background="White" CornerRadius="6" Padding="24" Margin="0,14,0,0">
|
||||
<StackPanel>
|
||||
<TextBlock Text="设备操作" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,14"/>
|
||||
<WrapPanel>
|
||||
<Button Content="重启设备" Style="{StaticResource BtnDanger}" Width="110"
|
||||
Click="Reboot_Click"/>
|
||||
<TextBlock x:Name="txtRebootResult" Foreground="#10B981"
|
||||
FontSize="13" VerticalAlignment="Center" Margin="12,0,0,0"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
118
上位机/YKC/Pages/ChargerPage.xaml.cs
Normal file
118
上位机/YKC/Pages/ChargerPage.xaml.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace YKC
|
||||
{
|
||||
public partial class ChargerPage : Page
|
||||
{
|
||||
public ChargerPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SetPileId_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string idx = txtSetPileIdx.Text.Trim();
|
||||
string id = txtSetPileId.Text.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(id) || id.Length != 14)
|
||||
{
|
||||
ShowResult(txtSetResult, "序列号需14位HEX", false);
|
||||
return;
|
||||
}
|
||||
|
||||
ShowResult(txtSetResult, "发送中...", true);
|
||||
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject
|
||||
{
|
||||
["cmd"] = Config.Cmd["SET_PILE_ID"],
|
||||
["pile_index"] = int.Parse(idx),
|
||||
["pile_id"] = id
|
||||
});
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (result.Value<bool>("success"))
|
||||
ShowResult(txtSetResult, "写入成功", true);
|
||||
else
|
||||
ShowResult(txtSetResult, result.Value<string>("error") ?? "失败", false);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private async void GetPileInfo_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string idx = txtInfoPileIdx.Text.Trim();
|
||||
panelInfo.Visibility = Visibility.Visible;
|
||||
txtInfoResult.Text = "查询中...";
|
||||
txtInfoResult.Foreground = new System.Windows.Media.SolidColorBrush(
|
||||
(System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#64748B"));
|
||||
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject
|
||||
{
|
||||
["cmd"] = Config.Cmd["GET_PILE_INFO"],
|
||||
["pile_index"] = int.Parse(idx)
|
||||
});
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (result.Value<bool>("success") && result["serial"] != null)
|
||||
{
|
||||
txtInfoSerial.Text = result.Value<string>("serial") ?? "--";
|
||||
txtInfoType.Text = result.Value<string>("type") ?? "--";
|
||||
txtInfoGuns.Text = result.Value<string>("gun_num") ?? "--";
|
||||
txtInfoProto.Text = result.Value<string>("protocol_ver") ?? "--";
|
||||
txtInfoSw.Text = result.Value<string>("software_ver") ?? "--";
|
||||
txtInfoSim.Text = result.Value<string>("sim") ?? "--";
|
||||
txtInfoResult.Text = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
txtInfoSerial.Text = "--"; txtInfoType.Text = "--";
|
||||
txtInfoGuns.Text = "--"; txtInfoProto.Text = "--";
|
||||
txtInfoSw.Text = "--"; txtInfoSim.Text = "--";
|
||||
ShowResult(txtInfoResult, result.Value<string>("error") ?? "查询失败", false);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private async void Reboot_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialogResult = MessageBox.Show("确认重启设备?", "警告", MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
if (dialogResult != MessageBoxResult.Yes) return;
|
||||
|
||||
ShowResult(txtRebootResult, "发送中...", true);
|
||||
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject
|
||||
{
|
||||
["cmd"] = Config.Cmd["REBOOT"]
|
||||
});
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (result.Value<bool>("success"))
|
||||
ShowResult(txtRebootResult, "重启指令已发送", true);
|
||||
else
|
||||
ShowResult(txtRebootResult, result.Value<string>("error") ?? "失败", false);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowResult(TextBlock el, string msg, bool ok)
|
||||
{
|
||||
el.Text = msg;
|
||||
el.Foreground = new System.Windows.Media.SolidColorBrush(
|
||||
(System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(
|
||||
ok ? "#10B981" : "#EF4444"));
|
||||
var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(4) };
|
||||
timer.Tick += (s, _) => { el.Text = ""; timer.Stop(); };
|
||||
timer.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
92
上位机/YKC/Pages/DashboardPage.xaml
Normal file
92
上位机/YKC/Pages/DashboardPage.xaml
Normal file
@@ -0,0 +1,92 @@
|
||||
<Page x:Class="YKC.DashboardPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="总览" Background="#F0F2F5">
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="24">
|
||||
<!-- 统计卡片行 -->
|
||||
<WrapPanel Margin="0,0,0,20">
|
||||
<Border Background="White" CornerRadius="6" Width="240"
|
||||
Padding="20,18" Margin="0,0,16,16">
|
||||
<StackPanel>
|
||||
<TextBlock Text="设备连接" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtConnStatus" Text="--"
|
||||
FontSize="20" FontWeight="SemiBold" Foreground="#1E293B"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="240"
|
||||
Padding="20,18" Margin="0,0,16,16">
|
||||
<StackPanel>
|
||||
<TextBlock Text="在线充电桩" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtOnlineCount" Text="--"
|
||||
FontSize="28" FontWeight="SemiBold" Foreground="#10B981"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="240"
|
||||
Padding="20,18" Margin="0,0,16,16">
|
||||
<StackPanel>
|
||||
<TextBlock Text="充电中" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtChargingCount" Text="--"
|
||||
FontSize="28" FontWeight="SemiBold" Foreground="#3B82F6"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
|
||||
<!-- 标题 + 按钮 -->
|
||||
<Grid Margin="0,0,0,12">
|
||||
<TextBlock Text="充电桩列表" FontSize="15" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" VerticalAlignment="Center"/>
|
||||
<Button Content="刷新" Style="{StaticResource BtnPrimary}"
|
||||
Width="90" HorizontalAlignment="Right"
|
||||
Click="Refresh_Click"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 表格 -->
|
||||
<Border Background="White" CornerRadius="6" Padding="0">
|
||||
<DataGrid x:Name="dgPiles" AutoGenerateColumns="False" IsReadOnly="True"
|
||||
HeadersVisibility="Column" CanUserAddRows="False"
|
||||
CanUserDeleteRows="False" RowHeight="40"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HorizontalGridLinesBrush="#F1F5F9"
|
||||
BorderThickness="0" Background="White">
|
||||
<DataGrid.ColumnHeaderStyle>
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#F8FAFC"/>
|
||||
<Setter Property="Foreground" Value="#64748B"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Padding" Value="16,10"/>
|
||||
<Setter Property="BorderBrush" Value="#E2E8F0"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||
</Style>
|
||||
</DataGrid.ColumnHeaderStyle>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Padding" Value="16,8"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGridCell">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<ContentPresenter Margin="{TemplateBinding Padding}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="#" Width="50" Binding="{Binding Index}"/>
|
||||
<DataGridTextColumn Header="序列号" Width="*" Binding="{Binding Serial}"/>
|
||||
<DataGridTextColumn Header="桩状态" Width="80" Binding="{Binding StatusText}"/>
|
||||
<DataGridTextColumn Header="枪1" Width="80" Binding="{Binding Gun1Text}"/>
|
||||
<DataGridTextColumn Header="枪2" Width="80" Binding="{Binding Gun2Text}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
105
上位机/YKC/Pages/DashboardPage.xaml.cs
Normal file
105
上位机/YKC/Pages/DashboardPage.xaml.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OxyPlot;
|
||||
|
||||
namespace YKC
|
||||
{
|
||||
public partial class DashboardPage : Page
|
||||
{
|
||||
private Timer _timer;
|
||||
|
||||
public class PileRow
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public string Serial { get; set; } = "--";
|
||||
public string StatusText { get; set; } = "--";
|
||||
public string Gun1Text { get; set; } = "--";
|
||||
public string Gun2Text { get; set; } = "--";
|
||||
}
|
||||
|
||||
public ObservableCollection<PileRow> Rows { get; } = new ObservableCollection<PileRow>();
|
||||
|
||||
public DashboardPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
dgPiles.ItemsSource = Rows;
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => RefreshData()),
|
||||
System.Windows.Threading.DispatcherPriority.Background);
|
||||
_timer = new Timer((_) => Dispatcher.BeginInvoke(new Action(RefreshData)), null, 5000, 5000);
|
||||
};
|
||||
Unloaded += (s, e) => _timer?.Dispose();
|
||||
}
|
||||
|
||||
private void Refresh_Click(object sender, RoutedEventArgs e) => RefreshData();
|
||||
|
||||
private void RefreshData()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject
|
||||
{
|
||||
["cmd"] = Config.Cmd["GET_STATUS"]
|
||||
});
|
||||
if (result.Value<bool>("success") == false && result["piles"] == null)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => txtConnStatus.Text = "无响应"));
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() => txtConnStatus.Text = "在线"));
|
||||
|
||||
var piles = result["piles"] as JArray ?? new JArray();
|
||||
int online = 0, charging = 0;
|
||||
var rows = new ObservableCollection<PileRow>();
|
||||
|
||||
foreach (var p in piles)
|
||||
{
|
||||
bool isOnline = p.Value<bool>("is_online");
|
||||
if (isOnline) online++;
|
||||
|
||||
var guns = p["guns"] as JArray ?? new JArray();
|
||||
foreach (var g in guns)
|
||||
if (g.Value<int?>("status") == 3) charging++;
|
||||
|
||||
rows.Add(new PileRow
|
||||
{
|
||||
Index = rows.Count + 1,
|
||||
Serial = p.Value<string>("serial") ?? "--",
|
||||
StatusText = isOnline ? "在线" : "离线",
|
||||
Gun1Text = guns.Count > 0 ? GunStatusText(guns[0].Value<int>("status")) : "--",
|
||||
Gun2Text = guns.Count > 1 ? GunStatusText(guns[1].Value<int>("status")) : "--",
|
||||
});
|
||||
}
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
txtOnlineCount.Text = online.ToString();
|
||||
txtChargingCount.Text = charging.ToString();
|
||||
Rows.Clear();
|
||||
foreach (var r in rows) Rows.Add(r);
|
||||
}));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private string GunStatusText(int status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case 0: return "离线";
|
||||
case 1: return "故障";
|
||||
case 2: return "空闲";
|
||||
case 3: return "充电中";
|
||||
default: return status.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
上位机/YKC/Pages/GatewayPage.xaml
Normal file
145
上位机/YKC/Pages/GatewayPage.xaml
Normal file
@@ -0,0 +1,145 @@
|
||||
<Page x:Class="YKC.GatewayPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="网关管理" Background="#F0F2F5">
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="24">
|
||||
<TextBlock Text="网关管理" FontSize="16" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,20"/>
|
||||
|
||||
<Grid Margin="0,0,0,14">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 网关基本信息 -->
|
||||
<Border Grid.Column="0" Background="White" CornerRadius="6"
|
||||
Margin="0,0,8,0" Padding="24">
|
||||
<StackPanel>
|
||||
<TextBlock Text="网关基本信息" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,14"/>
|
||||
|
||||
<TextBlock Text="网关 ID" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtGwId" Text="--" Style="{StaticResource InfoValue}"
|
||||
FontFamily="Consolas"/>
|
||||
|
||||
<TextBlock Text="软件版本" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtGwSw" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
|
||||
<TextBlock Text="硬件版本" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtGwHw" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
|
||||
<TextBlock Text="运行时间" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtGwUptime" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
|
||||
<Grid>
|
||||
<Button Content="刷新" Style="{StaticResource BtnPrimary}" Width="80"
|
||||
Click="RefreshGwInfo_Click" Margin="0,4,0,0"/>
|
||||
<TextBlock x:Name="txtGwStatus" FontSize="12"
|
||||
VerticalAlignment="Center" Margin="92,4,0,0"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 4G 状态 -->
|
||||
<Border Grid.Column="1" Background="White" CornerRadius="6"
|
||||
Margin="8,0,0,0" Padding="24">
|
||||
<StackPanel>
|
||||
<TextBlock Text="4G 状态" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,14"/>
|
||||
|
||||
<TextBlock Text="SIM 卡号" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtSim" Text="--" Style="{StaticResource InfoValue}"
|
||||
FontFamily="Consolas"/>
|
||||
|
||||
<TextBlock Text="网络状态" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtNetStatus" Text="--" Style="{StaticResource InfoValue}"
|
||||
Foreground="#10B981"/>
|
||||
|
||||
<TextBlock Text="信号强度" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtSignal" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
|
||||
<TextBlock Text="运营商" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtIsp" Text="--" Style="{StaticResource InfoValue}"/>
|
||||
|
||||
<Grid>
|
||||
<Button Content="刷新" Style="{StaticResource BtnPrimary}" Width="80"
|
||||
Click="Refresh4G_Click" Margin="0,4,0,0"/>
|
||||
<TextBlock x:Name="txt4GStatus" FontSize="12"
|
||||
VerticalAlignment="Center" Margin="92,4,0,0"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 云服务器配置 -->
|
||||
<Border Grid.Column="0" Background="White" CornerRadius="6"
|
||||
Margin="0,0,8,0" Padding="24">
|
||||
<StackPanel>
|
||||
<TextBlock Text="云服务器配置" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,14"/>
|
||||
|
||||
<TextBlock Text="服务器地址 (IP 或域名)" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtCloudHost" Width="220"
|
||||
Style="{StaticResource FormInput}" Margin="0,0,0,12"/>
|
||||
|
||||
<TextBlock Text="端口号" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtCloudPort" Width="120"
|
||||
Style="{StaticResource FormInput}" Margin="0,0,0,16"/>
|
||||
|
||||
<WrapPanel>
|
||||
<Button Content="查 询" Style="{StaticResource BtnOutline}" Width="80"
|
||||
Click="LoadCloudConfig_Click"/>
|
||||
<Button Content="保 存" Style="{StaticResource BtnPrimary}" Width="80"
|
||||
Click="SaveCloudConfig_Click" Margin="8,0,0,0"/>
|
||||
<TextBlock x:Name="txtCloudResult" Foreground="#10B981"
|
||||
FontSize="13" VerticalAlignment="Center" Margin="12,0,0,0"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 网络配置 -->
|
||||
<Border Grid.Column="1" Background="White" CornerRadius="6"
|
||||
Margin="8,0,0,0" Padding="24">
|
||||
<StackPanel>
|
||||
<TextBlock Text="有线网络配置" FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="#1E293B" Margin="0,0,0,14"/>
|
||||
|
||||
<TextBlock Text="IP 地址" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtNetIp" Width="200"
|
||||
Style="{StaticResource FormInput}" Margin="0,0,0,10"/>
|
||||
|
||||
<TextBlock Text="子网掩码" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtNetMask" Width="200"
|
||||
Style="{StaticResource FormInput}" Margin="0,0,0,10"/>
|
||||
|
||||
<TextBlock Text="默认网关" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtNetGw" Width="200"
|
||||
Style="{StaticResource FormInput}" Margin="0,0,0,10"/>
|
||||
|
||||
<TextBlock Text="DNS 服务器" Style="{StaticResource FieldLabel}"/>
|
||||
<TextBox x:Name="txtNetDns" Width="200"
|
||||
Style="{StaticResource FormInput}" Margin="0,0,0,16"/>
|
||||
|
||||
<WrapPanel>
|
||||
<Button Content="查 询" Style="{StaticResource BtnOutline}" Width="80"
|
||||
Click="LoadNetConfig_Click"/>
|
||||
<Button Content="保 存" Style="{StaticResource BtnPrimary}" Width="80"
|
||||
Click="SaveNetConfig_Click" Margin="8,0,0,0"/>
|
||||
<TextBlock x:Name="txtNetResult" Foreground="#10B981"
|
||||
FontSize="13" VerticalAlignment="Center" Margin="12,0,0,0"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
227
上位机/YKC/Pages/GatewayPage.xaml.cs
Normal file
227
上位机/YKC/Pages/GatewayPage.xaml.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace YKC
|
||||
{
|
||||
public partial class GatewayPage : Page
|
||||
{
|
||||
private static readonly Regex IpRegex = new Regex(
|
||||
@"^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$");
|
||||
private static readonly Regex DomainRegex = new Regex(
|
||||
@"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z]{2,})+$");
|
||||
|
||||
public GatewayPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (txtGwId != null) RefreshAll();
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsValidIp(string s) => IpRegex.IsMatch(s);
|
||||
private bool IsValidDomain(string s) => DomainRegex.IsMatch(s);
|
||||
private bool IsValidHost(string s) => IsValidIp(s) || IsValidDomain(s);
|
||||
|
||||
private void ShowResult(TextBlock el, string msg, bool ok)
|
||||
{
|
||||
el.Text = msg;
|
||||
el.Foreground = new System.Windows.Media.SolidColorBrush(
|
||||
(System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(
|
||||
ok ? "#10B981" : "#EF4444"));
|
||||
var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(3) };
|
||||
timer.Tick += (s, _) => { el.Text = ""; timer.Stop(); };
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private string FormatUptime(int sec)
|
||||
{
|
||||
if (sec <= 0) return "--";
|
||||
int d = sec / 86400;
|
||||
int h = (sec % 86400) / 3600;
|
||||
int m = (sec % 3600) / 60;
|
||||
string result = "";
|
||||
if (d > 0) result += d + "天";
|
||||
if (h > 0) result += h + "时";
|
||||
if (m > 0) result += m + "分";
|
||||
return result.Length > 0 ? result : sec + "秒";
|
||||
}
|
||||
|
||||
private void RefreshAll()
|
||||
{
|
||||
RefreshGwInfo();
|
||||
Refresh4G();
|
||||
}
|
||||
|
||||
private async void RefreshGwInfo()
|
||||
{
|
||||
txtGwStatus.Text = "加载中...";
|
||||
txtGwStatus.Foreground = new System.Windows.Media.SolidColorBrush(
|
||||
(System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#64748B"));
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject { ["cmd"] = Config.Cmd["GET_GW_INFO"] });
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (result.Value<bool>("success") && result["device_id"] != null)
|
||||
{
|
||||
txtGwId.Text = result.Value<string>("device_id") ?? "--";
|
||||
txtGwSw.Text = result.Value<string>("software_ver") ?? result.Value<string>("sw_ver") ?? "--";
|
||||
txtGwHw.Text = result.Value<string>("hardware_ver") ?? result.Value<string>("hw_ver") ?? "--";
|
||||
txtGwUptime.Text = FormatUptime(result.Value<int?>("uptime") ?? 0);
|
||||
txtGwStatus.Text = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
txtGwId.Text = "--"; txtGwSw.Text = "--"; txtGwHw.Text = "--"; txtGwUptime.Text = "--";
|
||||
ShowResult(txtGwStatus, result.Value<string>("error") ?? "查询超时", false);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private async void Refresh4G()
|
||||
{
|
||||
txt4GStatus.Text = "加载中...";
|
||||
txt4GStatus.Foreground = new System.Windows.Media.SolidColorBrush(
|
||||
(System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#64748B"));
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject { ["cmd"] = Config.Cmd["GET_4G_STATUS"] });
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (result.Value<bool>("success") && result["sim"] != null)
|
||||
{
|
||||
txtSim.Text = result.Value<string>("sim") ?? result.Value<string>("iccid") ?? "--";
|
||||
txtNetStatus.Text = result.Value<string>("net_status") ?? result.Value<string>("status_text") ?? "--";
|
||||
txtSignal.Text = result.Value<string>("signal") != null ? result.Value<int>("signal") + " dBm" : "--";
|
||||
txtIsp.Text = result.Value<string>("isp") ?? result.Value<string>("operator") ?? "--";
|
||||
txt4GStatus.Text = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
txtSim.Text = "--"; txtNetStatus.Text = "--"; txtSignal.Text = "--"; txtIsp.Text = "--";
|
||||
ShowResult(txt4GStatus, result.Value<string>("error") ?? "查询超时", false);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void RefreshGwInfo_Click(object sender, RoutedEventArgs e) => RefreshGwInfo();
|
||||
private void Refresh4G_Click(object sender, RoutedEventArgs e) => Refresh4G();
|
||||
|
||||
private async void LoadCloudConfig_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
txtCloudResult.Text = "加载中...";
|
||||
txtCloudResult.Foreground = new System.Windows.Media.SolidColorBrush(
|
||||
(System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#64748B"));
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject { ["cmd"] = Config.Cmd["GET_CLOUD_CONFIG"] });
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
txtCloudResult.Text = "";
|
||||
if (result.Value<bool>("success") && result["host"] != null)
|
||||
{
|
||||
txtCloudHost.Text = result.Value<string>("host") ?? "";
|
||||
txtCloudPort.Text = result.Value<string>("port") ?? result.Value<int?>("port")?.ToString() ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowResult(txtCloudResult, result.Value<string>("error") ?? "查询超时", false);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private async void SaveCloudConfig_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string host = txtCloudHost.Text.Trim();
|
||||
string port = txtCloudPort.Text.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(host)) { ShowResult(txtCloudResult, "请输入服务器地址", false); return; }
|
||||
if (!IsValidHost(host)) { ShowResult(txtCloudResult, "地址格式错误", false); return; }
|
||||
if (string.IsNullOrEmpty(port) || !int.TryParse(port, out int p) || p < 1 || p > 65535)
|
||||
{ ShowResult(txtCloudResult, "端口范围 1-65535", false); return; }
|
||||
|
||||
ShowResult(txtCloudResult, "保存中...", true);
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject
|
||||
{
|
||||
["cmd"] = Config.Cmd["SET_CLOUD_CONFIG"],
|
||||
["host"] = host,
|
||||
["port"] = p
|
||||
});
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ShowResult(txtCloudResult, result.Value<bool>("success") ? "保存成功" : (result.Value<string>("error") ?? "失败"), result.Value<bool>("success"));
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private async void LoadNetConfig_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
txtNetResult.Text = "加载中...";
|
||||
txtNetResult.Foreground = new System.Windows.Media.SolidColorBrush(
|
||||
(System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#64748B"));
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject { ["cmd"] = Config.Cmd["GET_NET_CONFIG"] });
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
txtNetResult.Text = "";
|
||||
if (result.Value<bool>("success") && result["ip"] != null)
|
||||
{
|
||||
txtNetIp.Text = result.Value<string>("ip") ?? "";
|
||||
txtNetMask.Text = result.Value<string>("mask") ?? result.Value<string>("netmask") ?? "";
|
||||
txtNetGw.Text = result.Value<string>("gateway") ?? result.Value<string>("gw") ?? "";
|
||||
txtNetDns.Text = result.Value<string>("dns") ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowResult(txtNetResult, result.Value<string>("error") ?? "查询超时", false);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private async void SaveNetConfig_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string ip = txtNetIp.Text.Trim();
|
||||
string mask = txtNetMask.Text.Trim();
|
||||
string gw = txtNetGw.Text.Trim();
|
||||
string dns = txtNetDns.Text.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(ip)) { ShowResult(txtNetResult, "请输入IP地址", false); return; }
|
||||
if (!IsValidIp(ip)) { ShowResult(txtNetResult, "IP格式错误", false); return; }
|
||||
if (string.IsNullOrEmpty(mask)) { ShowResult(txtNetResult, "请输入子网掩码", false); return; }
|
||||
if (!IsValidIp(mask)) { ShowResult(txtNetResult, "掩码格式错误", false); return; }
|
||||
if (!string.IsNullOrEmpty(gw) && !IsValidIp(gw)) { ShowResult(txtNetResult, "网关格式错误", false); return; }
|
||||
if (!string.IsNullOrEmpty(dns) && !IsValidIp(dns)) { ShowResult(txtNetResult, "DNS格式错误", false); return; }
|
||||
|
||||
ShowResult(txtNetResult, "保存中...", true);
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var result = UdpClientHolder.Instance.SendSync(new JObject
|
||||
{
|
||||
["cmd"] = Config.Cmd["SET_NET_CONFIG"],
|
||||
["ip"] = ip,
|
||||
["mask"] = mask,
|
||||
["gateway"] = gw,
|
||||
["dns"] = dns
|
||||
});
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ShowResult(txtNetResult, result.Value<bool>("success") ? "保存成功,重启生效" : (result.Value<string>("error") ?? "失败"), result.Value<bool>("success"));
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
170
上位机/YKC/Pages/RealtimePage.xaml
Normal file
170
上位机/YKC/Pages/RealtimePage.xaml
Normal file
@@ -0,0 +1,170 @@
|
||||
<Page x:Class="YKC.RealtimePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:oxy="http://oxyplot.org/wpf"
|
||||
Title="实时监控" Background="#F0F2F5">
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="20">
|
||||
<!-- 标题 + 选择器 -->
|
||||
<Grid Margin="0,0,0,12">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="实时监控" FontSize="16" FontWeight="SemiBold"
|
||||
Foreground="#1E293B"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock Text="桩" Foreground="#64748B" FontSize="13"
|
||||
VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<ComboBox x:Name="cmbPile" Style="{StaticResource StyledCombo}" Width="80"
|
||||
SelectedIndex="0" SelectionChanged="PileGun_Changed">
|
||||
<ComboBoxItem>桩 01</ComboBoxItem><ComboBoxItem>桩 02</ComboBoxItem>
|
||||
<ComboBoxItem>桩 03</ComboBoxItem><ComboBoxItem>桩 04</ComboBoxItem>
|
||||
<ComboBoxItem>桩 05</ComboBoxItem><ComboBoxItem>桩 06</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<TextBlock Text="枪" Foreground="#64748B" FontSize="13"
|
||||
VerticalAlignment="Center" Margin="12,0,6,0"/>
|
||||
<ComboBox x:Name="cmbGun" Style="{StaticResource StyledCombo}" Width="70"
|
||||
SelectedIndex="0" SelectionChanged="PileGun_Changed">
|
||||
<ComboBoxItem>枪 1</ComboBoxItem><ComboBoxItem>枪 2</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 数值卡片行1 -->
|
||||
<WrapPanel Margin="0,0,0,6">
|
||||
<Border Background="White" CornerRadius="6" Width="170"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="电流 (A)" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtCurrent" Text="--" FontSize="22"
|
||||
FontWeight="SemiBold" Foreground="#10B981"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="170"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="电压 (V)" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtVoltage" Text="--" FontSize="22"
|
||||
FontWeight="SemiBold" Foreground="#1E293B"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="170"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="功率 (kW)" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtPower" Text="--" FontSize="22"
|
||||
FontWeight="SemiBold" Foreground="#1E293B"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="170"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="度数 (kWh)" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtEnergy" Text="--" FontSize="22"
|
||||
FontWeight="SemiBold" Foreground="#1E293B"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="170"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="总金额 (元)" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtTotalAmount" Text="--" FontSize="22"
|
||||
FontWeight="SemiBold" Foreground="#FF5722"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="150"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="状态" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtStatus" Text="--" FontSize="22"
|
||||
FontWeight="SemiBold" Foreground="#3B82F6"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
|
||||
<!-- 订单信息行 -->
|
||||
<WrapPanel Margin="0,0,0,10">
|
||||
<Border Background="White" CornerRadius="6" Width="280"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="订单编号" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtOrderNo" Text="--" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="#1E293B"
|
||||
FontFamily="Consolas" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="140"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="目标 SOC" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtSoc" Text="--" FontSize="22"
|
||||
FontWeight="SemiBold" Foreground="#1E293B"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="180"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="累计充电时间" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtCumTime" Text="--" FontSize="18"
|
||||
FontWeight="SemiBold" Foreground="#1E293B"
|
||||
FontFamily="Consolas"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Background="White" CornerRadius="6" Width="180"
|
||||
Padding="14,12" Margin="0,0,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="剩余时间" Style="{StaticResource InfoLabel}"/>
|
||||
<TextBlock x:Name="txtRemTime" Text="--" FontSize="18"
|
||||
FontWeight="SemiBold" Foreground="#1E293B"
|
||||
FontFamily="Consolas"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
|
||||
<!-- 图表 2x2 -->
|
||||
<Grid Height="580">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="290"/>
|
||||
<RowDefinition Height="290"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Grid.Column="0" Background="White" CornerRadius="6"
|
||||
Margin="0,0,7,7">
|
||||
<Grid>
|
||||
<TextBlock Text="电流曲线 (A)" Foreground="#94A3B8" FontSize="12"
|
||||
Margin="14,10,0,0" VerticalAlignment="Top"/>
|
||||
<oxy:PlotView x:Name="plotCurrent" Margin="8,32,8,8"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Row="0" Grid.Column="1" Background="White" CornerRadius="6"
|
||||
Margin="7,0,0,7">
|
||||
<Grid>
|
||||
<TextBlock Text="电压曲线 (V)" Foreground="#94A3B8" FontSize="12"
|
||||
Margin="14,10,0,0" VerticalAlignment="Top"/>
|
||||
<oxy:PlotView x:Name="plotVoltage" Margin="8,32,8,8"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Row="1" Grid.Column="0" Background="White" CornerRadius="6"
|
||||
Margin="0,7,7,0">
|
||||
<Grid>
|
||||
<TextBlock Text="功率曲线 (kW)" Foreground="#94A3B8" FontSize="12"
|
||||
Margin="14,10,0,0" VerticalAlignment="Top"/>
|
||||
<oxy:PlotView x:Name="plotPower" Margin="8,32,8,8"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Row="1" Grid.Column="1" Background="White" CornerRadius="6"
|
||||
Margin="7,7,0,0">
|
||||
<Grid>
|
||||
<TextBlock Text="费用明细 (元)" Foreground="#94A3B8" FontSize="12"
|
||||
Margin="14,10,0,0" VerticalAlignment="Top"/>
|
||||
<oxy:PlotView x:Name="plotAmount" Margin="8,32,8,8"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
214
上位机/YKC/Pages/RealtimePage.xaml.cs
Normal file
214
上位机/YKC/Pages/RealtimePage.xaml.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OxyPlot;
|
||||
using OxyPlot.Axes;
|
||||
using OxyPlot.Series;
|
||||
|
||||
namespace YKC
|
||||
{
|
||||
public partial class RealtimePage : Page
|
||||
{
|
||||
private Timer _pollTimer;
|
||||
private int _currentPile = 1;
|
||||
private int _currentGun = 1;
|
||||
|
||||
public RealtimePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitLinePlot(plotCurrent, OxyColor.Parse("#3B82F6"));
|
||||
InitLinePlot(plotVoltage, OxyColor.Parse("#8B5CF6"));
|
||||
InitLinePlot(plotPower, OxyColor.Parse("#F59E0B"));
|
||||
InitBarPlot(plotAmount);
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(RefreshData),
|
||||
System.Windows.Threading.DispatcherPriority.Background);
|
||||
_pollTimer = new Timer((_) =>
|
||||
Dispatcher.BeginInvoke(new Action(RefreshData)), null, 2000, 2000);
|
||||
};
|
||||
Unloaded += (s, e) => _pollTimer?.Dispose();
|
||||
}
|
||||
|
||||
private void InitLinePlot(OxyPlot.Wpf.PlotView pv, OxyColor color)
|
||||
{
|
||||
var model = new PlotModel { PlotAreaBorderColor = OxyColors.Transparent };
|
||||
model.Axes.Add(new DateTimeAxis
|
||||
{
|
||||
Position = AxisPosition.Bottom,
|
||||
TextColor = OxyColor.Parse("#94A3B8"),
|
||||
AxislineColor = OxyColor.Parse("#E2E8F0"),
|
||||
TicklineColor = OxyColor.Parse("#E2E8F0"),
|
||||
MajorGridlineColor = OxyColor.Parse("#F1F5F9"),
|
||||
StringFormat = "HH:mm:ss",
|
||||
FontSize = 10
|
||||
});
|
||||
model.Axes.Add(new LinearAxis
|
||||
{
|
||||
Position = AxisPosition.Left,
|
||||
TextColor = OxyColor.Parse("#94A3B8"),
|
||||
AxislineColor = OxyColors.Transparent,
|
||||
TicklineColor = OxyColors.Transparent,
|
||||
MajorGridlineStyle = LineStyle.Dash,
|
||||
MajorGridlineColor = OxyColor.Parse("#F1F5F9"),
|
||||
FontSize = 10,
|
||||
MinimumPadding = 0.1,
|
||||
MaximumPadding = 0.1
|
||||
});
|
||||
model.Series.Add(new LineSeries
|
||||
{
|
||||
Color = color,
|
||||
StrokeThickness = 2,
|
||||
MarkerType = MarkerType.None,
|
||||
CanTrackerInterpolatePoints = false
|
||||
});
|
||||
pv.Model = model;
|
||||
}
|
||||
|
||||
private void InitBarPlot(OxyPlot.Wpf.PlotView pv)
|
||||
{
|
||||
var model = new PlotModel { PlotAreaBorderColor = OxyColors.Transparent };
|
||||
var catAxis = new CategoryAxis
|
||||
{
|
||||
Position = AxisPosition.Bottom,
|
||||
TextColor = OxyColor.Parse("#64748B"),
|
||||
FontSize = 12,
|
||||
AxislineColor = OxyColor.Parse("#E2E8F0"),
|
||||
TicklineColor = OxyColor.Parse("#E2E8F0")
|
||||
};
|
||||
catAxis.Labels.Add("服务费");
|
||||
catAxis.Labels.Add("电费");
|
||||
catAxis.Labels.Add("总金额");
|
||||
model.Axes.Add(catAxis);
|
||||
model.Axes.Add(new LinearAxis
|
||||
{
|
||||
Position = AxisPosition.Left,
|
||||
Title = "元",
|
||||
TextColor = OxyColor.Parse("#94A3B8"),
|
||||
FontSize = 10,
|
||||
AxislineColor = OxyColors.Transparent,
|
||||
TicklineColor = OxyColors.Transparent,
|
||||
MajorGridlineStyle = LineStyle.Dash,
|
||||
MajorGridlineColor = OxyColor.Parse("#F1F5F9"),
|
||||
MinimumPadding = 0,
|
||||
MaximumPadding = 0.15
|
||||
});
|
||||
model.Series.Add(new RectangleBarSeries { FillColor = OxyColor.Parse("#1E9FFF") });
|
||||
model.Series.Add(new RectangleBarSeries { FillColor = OxyColor.Parse("#10B981") });
|
||||
model.Series.Add(new RectangleBarSeries { FillColor = OxyColor.Parse("#6366F1") });
|
||||
pv.Model = model;
|
||||
}
|
||||
|
||||
private void PileGun_Changed(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cmbPile == null || cmbGun == null) return;
|
||||
_currentPile = cmbPile.SelectedIndex + 1;
|
||||
_currentGun = cmbGun.SelectedIndex + 1;
|
||||
RefreshData();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void RefreshData()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (txtCurrent == null || plotCurrent == null || plotCurrent.Model == null) return;
|
||||
var latest = PilesStoreHolder.Instance.GetLatest(_currentPile, _currentGun);
|
||||
if (latest == null) return;
|
||||
|
||||
txtCurrent.Text = FormatValue(latest.Value<double?>("current")) + " A";
|
||||
txtVoltage.Text = FormatValue(latest.Value<double?>("voltage")) + " V";
|
||||
txtPower.Text = FormatValue(latest.Value<double?>("power")) + " kW";
|
||||
txtEnergy.Text = FormatValue(latest.Value<double?>("energy")) + " kWh";
|
||||
txtTotalAmount.Text = "¥ " + FormatValue(latest.Value<double?>("total_amount"));
|
||||
|
||||
int? st = latest.Value<int?>("status");
|
||||
if (st == 0) { txtStatus.Text = "离线"; txtStatus.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#909399")); }
|
||||
else if (st == 1) { txtStatus.Text = "故障"; txtStatus.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EF4444")); }
|
||||
else if (st == 2) { txtStatus.Text = "空闲"; txtStatus.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#10B981")); }
|
||||
else if (st == 3) { txtStatus.Text = "充电中"; txtStatus.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#3B82F6")); }
|
||||
else { txtStatus.Text = "--"; }
|
||||
|
||||
txtOrderNo.Text = latest.Value<string>("order_no") ?? "--";
|
||||
txtSoc.Text = latest.Value<double?>("soc") != null ? latest.Value<double>("soc").ToString("F1") + "%" : "--";
|
||||
txtCumTime.Text = FormatMin(latest.Value<double?>("cumulative_time"));
|
||||
txtRemTime.Text = FormatMin(latest.Value<double?>("remaining_time"));
|
||||
|
||||
UpdateCharts();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void UpdateCharts()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (plotCurrent == null || plotCurrent.Model == null || plotCurrent.Model.Series.Count == 0) return;
|
||||
if (plotAmount == null || plotAmount.Model == null || plotAmount.Model.Series.Count == 0) return;
|
||||
|
||||
var chartData = PilesStoreHolder.Instance.GetChartData(_currentPile, _currentGun);
|
||||
if (chartData == null || chartData.Count == 0) return;
|
||||
|
||||
var curPoints = new List<DataPoint>();
|
||||
var volPoints = new List<DataPoint>();
|
||||
var powPoints = new List<DataPoint>();
|
||||
|
||||
foreach (var item in chartData)
|
||||
{
|
||||
long unixTs = item.Value<long>("t");
|
||||
var dt = DateTimeOffset.FromUnixTimeSeconds(unixTs).DateTime;
|
||||
double oxyTime = DateTimeAxis.ToDouble(dt);
|
||||
curPoints.Add(new DataPoint(oxyTime, item.Value<double?>("current") ?? 0));
|
||||
volPoints.Add(new DataPoint(oxyTime, item.Value<double?>("voltage") ?? 0));
|
||||
powPoints.Add(new DataPoint(oxyTime, item.Value<double?>("power") ?? 0));
|
||||
}
|
||||
|
||||
((LineSeries)plotCurrent.Model.Series[0]).Points.Clear();
|
||||
((LineSeries)plotCurrent.Model.Series[0]).Points.AddRange(curPoints);
|
||||
((LineSeries)plotVoltage.Model.Series[0]).Points.Clear();
|
||||
((LineSeries)plotVoltage.Model.Series[0]).Points.AddRange(volPoints);
|
||||
((LineSeries)plotPower.Model.Series[0]).Points.Clear();
|
||||
((LineSeries)plotPower.Model.Series[0]).Points.AddRange(powPoints);
|
||||
plotCurrent.InvalidatePlot(true);
|
||||
plotVoltage.InvalidatePlot(true);
|
||||
plotPower.InvalidatePlot(true);
|
||||
|
||||
var latest = PilesStoreHolder.Instance.GetLatest(_currentPile, _currentGun);
|
||||
if (latest != null)
|
||||
{
|
||||
double sf = latest.Value<double?>("service_fee") ?? 0;
|
||||
double ef = latest.Value<double?>("electricity_fee") ?? 0;
|
||||
double ta = latest.Value<double?>("total_amount") ?? 0;
|
||||
|
||||
((RectangleBarSeries)plotAmount.Model.Series[0]).Items.Clear();
|
||||
((RectangleBarSeries)plotAmount.Model.Series[0]).Items.Add(new RectangleBarItem(-0.25, 0, 0.25, sf));
|
||||
((RectangleBarSeries)plotAmount.Model.Series[1]).Items.Clear();
|
||||
((RectangleBarSeries)plotAmount.Model.Series[1]).Items.Add(new RectangleBarItem(0.75, 0, 1.25, ef));
|
||||
((RectangleBarSeries)plotAmount.Model.Series[2]).Items.Clear();
|
||||
((RectangleBarSeries)plotAmount.Model.Series[2]).Items.Add(new RectangleBarItem(1.75, 0, 2.25, ta));
|
||||
plotAmount.InvalidatePlot(true);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private string FormatValue(double? v)
|
||||
{
|
||||
return v.HasValue ? v.Value.ToString("F2") : "--";
|
||||
}
|
||||
|
||||
private string FormatMin(double? v)
|
||||
{
|
||||
return v.HasValue ? Math.Round(v.Value).ToString() + " min" : "--";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user