This commit is contained in:
3
App.xaml
3
App.xaml
@@ -1,7 +1,6 @@
|
||||
<Application x:Class="MembranePoreTester.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="Views/MainWindow.xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Application.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||
|
||||
|
||||
27
App.xaml.cs
27
App.xaml.cs
@@ -1,4 +1,6 @@
|
||||
using MembranePoreTester.Communication;
|
||||
using MembranePoreTester.Models;
|
||||
using MembranePoreTester.Views;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using OfficeOpenXml;
|
||||
using System;
|
||||
@@ -9,18 +11,31 @@ namespace MembranePoreTester
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
public static UserRole CurrentUserRole { get; set; } = UserRole.Operator;
|
||||
|
||||
public static IPlcService PlcService { get; private set; }
|
||||
public static PlcConfiguration PlcConfig { get; private set; }
|
||||
|
||||
protected async override void OnStartup(StartupEventArgs e)
|
||||
protected override async void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
|
||||
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
||||
|
||||
base.OnStartup(e);
|
||||
|
||||
// 防止在登录窗口关闭时应用程序因没有窗口而自动退出
|
||||
ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||
|
||||
// 显示登录窗口,验证用户身份
|
||||
var loginWindow = new LoginWindow();
|
||||
if (loginWindow.ShowDialog() != true)
|
||||
{
|
||||
Shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// 登录成功后初始化数据库和PLC连接
|
||||
using var db = new AppDbContext();
|
||||
db.Database.EnsureCreated(); // 自动建表
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
@@ -43,6 +58,12 @@ namespace MembranePoreTester
|
||||
{
|
||||
MessageBox.Show($"PLC 连接失败:{ex.Message}");
|
||||
}
|
||||
|
||||
// 启动主窗口,设置为应用程序的主窗口并恢复默认的退出模式
|
||||
var mainWindow = new MainWindow();
|
||||
MainWindow = mainWindow;
|
||||
ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||
mainWindow.Show();
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
|
||||
8
Models/UserRole.cs
Normal file
8
Models/UserRole.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace MembranePoreTester.Models
|
||||
{
|
||||
public enum UserRole
|
||||
{
|
||||
Operator, // 操作员
|
||||
Admin // 管理员
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,8 @@ namespace MembranePoreTester.ViewModels
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 2. 读取当前工位的加压上限(实时)
|
||||
ushort upperLimitAddress = StationId == 1 ? _plcConfig.PressureUpperLimit
|
||||
: StationId == 2 ? _plcConfig.PressureUpperLimit2
|
||||
@@ -121,12 +123,16 @@ namespace MembranePoreTester.ViewModels
|
||||
double pressureUpperLimit = await _plcService.ReadFloatAsync(upperLimitAddress);
|
||||
|
||||
// 3. 如果压力已达到或超过上限,停止采集
|
||||
if (pressure >= pressureUpperLimit - 3)
|
||||
if (pressure >= pressureUpperLimit * 1000 - 10 * 1000 && pressure > 0)
|
||||
{
|
||||
StopCollecting();
|
||||
return; // 不再添加当前数据点
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 2. 读取当前模式对应的流量
|
||||
double flow = 0;
|
||||
if (TestMode.Contains("湿膜"))
|
||||
@@ -578,19 +584,19 @@ namespace MembranePoreTester.ViewModels
|
||||
System.Diagnostics.Debug.WriteLine($"P={p.Pressure}, Wet={p.WetFlow}, Dry={p.DryFlow}");
|
||||
}
|
||||
|
||||
var cleanedPoints = CleanDataPoints(originalPoints);
|
||||
//var cleanedPoints = CleanDataPoints(originalPoints);
|
||||
|
||||
if (cleanedPoints.Count < 2)
|
||||
{
|
||||
MessageBox.Show("有效数据点不足,至少需要 2 个数据点进行计算。");
|
||||
return;
|
||||
}
|
||||
//if (cleanedPoints.Count < 2)
|
||||
//{
|
||||
// MessageBox.Show("有效数据点不足,至少需要 2 个数据点进行计算。");
|
||||
// return;
|
||||
//}
|
||||
|
||||
int invalidCount = originalPoints.Count - cleanedPoints.Count;
|
||||
//int invalidCount = originalPoints.Count - cleanedPoints.Count;
|
||||
|
||||
// 用清洗后的点替换 ObservableCollection 的内容(触发UI更新)
|
||||
Record.DataPoints.Clear();
|
||||
foreach (var p in cleanedPoints)
|
||||
foreach (var p in originalPoints)
|
||||
Record.DataPoints.Add(p);
|
||||
|
||||
// 刷新曲线以显示清洗后的数据
|
||||
@@ -603,11 +609,11 @@ namespace MembranePoreTester.ViewModels
|
||||
RangePercentage = PoreDistributionAnalysis.CalculatePoreRangePercentage(
|
||||
Record.DataPoints, Record.PressureUnit, Record.Liquid, LowerPore, UpperPore);
|
||||
|
||||
if (invalidCount > 0)
|
||||
{
|
||||
MessageBox.Show($"已自动过滤 {invalidCount} 个无效数据点");
|
||||
System.Diagnostics.Debug.WriteLine($"已自动过滤 {invalidCount} 个无效数据点");
|
||||
}
|
||||
//if (invalidCount > 0)
|
||||
//{
|
||||
// MessageBox.Show($"已自动过滤 {invalidCount} 个无效数据点");
|
||||
// System.Diagnostics.Debug.WriteLine($"已自动过滤 {invalidCount} 个无效数据点");
|
||||
//}
|
||||
}
|
||||
|
||||
//private void Calculate()
|
||||
@@ -1146,6 +1152,11 @@ namespace MembranePoreTester.ViewModels
|
||||
//}
|
||||
|
||||
|
||||
public void RefreshPlot()
|
||||
{
|
||||
UpdatePlot();
|
||||
}
|
||||
|
||||
private void UpdatePlot()
|
||||
{
|
||||
// 确保在 UI 线程执行
|
||||
@@ -1156,11 +1167,11 @@ namespace MembranePoreTester.ViewModels
|
||||
}
|
||||
|
||||
var sorted = Record.DataPoints.OrderBy(p => p.Pressure).ToList();
|
||||
if (sorted.Count == 0)
|
||||
{
|
||||
PlotModel = null;
|
||||
return;
|
||||
}
|
||||
//if (sorted.Count == 0)
|
||||
//{
|
||||
// PlotModel = null;
|
||||
// return;
|
||||
//}
|
||||
|
||||
var model = new PlotModel
|
||||
{
|
||||
|
||||
33
Views/LoginWindow.xaml
Normal file
33
Views/LoginWindow.xaml
Normal file
@@ -0,0 +1,33 @@
|
||||
<Window x:Class="MembranePoreTester.Views.LoginWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="登录" Height="250" Width="350"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="NoResize">
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Text="膜孔径测试系统" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,20"/>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,5">
|
||||
<Label Width="80" Content="用户名:"/>
|
||||
<!--<TextBox x:Name="txtUsername" Width="180" Text="admin"/>-->
|
||||
<ComboBox x:Name="cmbRole" Width="180" SelectedIndex="0">
|
||||
<ComboBoxItem Content="管理员" Tag="Admin"/>
|
||||
<ComboBoxItem Content="操作员" Tag="Operator"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,5">
|
||||
<Label Width="80" Content="密码:"/>
|
||||
<PasswordBox x:Name="txtPassword" Width="180" PasswordChar="*"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20,0,0">
|
||||
<Button x:Name="btnLogin" Content="登录" Width="80" Margin="10" Click="BtnLogin_Click"/>
|
||||
<Button x:Name="btnChangePwd" Content="修改密码" Width="80" Margin="10" Click="BtnChangePwd_Click"/>
|
||||
<Button x:Name="btnExit" Content="退出" Width="80" Margin="10" Click="BtnExit_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
168
Views/LoginWindow.xaml.cs
Normal file
168
Views/LoginWindow.xaml.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using MembranePoreTester.Models;
|
||||
|
||||
namespace MembranePoreTester.Views
|
||||
{
|
||||
public partial class LoginWindow : Window
|
||||
{
|
||||
private const string PasswordFile = "passwords.json";
|
||||
|
||||
public LoginWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
EnsurePasswordFile();
|
||||
}
|
||||
|
||||
private void EnsurePasswordFile()
|
||||
{
|
||||
if (!File.Exists(PasswordFile))
|
||||
{
|
||||
var defaultPasswords = new
|
||||
{
|
||||
AdminPassword = "admin123",
|
||||
OperatorPassword = "oper123"
|
||||
};
|
||||
string json = JsonSerializer.Serialize(defaultPasswords, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(PasswordFile, json);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnLogin_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string role = ((ComboBoxItem)cmbRole.SelectedItem).Tag.ToString();
|
||||
string password = txtPassword.Password;
|
||||
|
||||
string expectedPassword = GetPassword(role);
|
||||
if (password == expectedPassword)
|
||||
{
|
||||
App.CurrentUserRole = role == "Admin" ? UserRole.Admin : UserRole.Operator;
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("密码错误", "登录失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnChangePwd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string role = ((ComboBoxItem)cmbRole.SelectedItem).Tag.ToString();
|
||||
string title = role == "Admin" ? "修改管理员密码" : "修改操作员密码";
|
||||
|
||||
var dialog = new Window
|
||||
{
|
||||
Title = title,
|
||||
Width = 350,
|
||||
Height = 250,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
Owner = this,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
Content = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(20),
|
||||
Children =
|
||||
{
|
||||
new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0,10,0,10),
|
||||
Children = { new Label { Width = 80, Content = "新密码:" }, new PasswordBox { Name = "newPwd", Width = 180 } } },
|
||||
new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0,10,0,10),
|
||||
Children = { new Label { Width = 80, Content = "确认密码:" }, new PasswordBox { Name = "confirmPwd", Width = 180 } } },
|
||||
new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center, Margin = new Thickness(0,20,0,0),
|
||||
Children = { new Button { Content = "确定", Width = 80, Margin = new Thickness {Left= 5} }, new Button { Content = "取消", Width = 80, Margin = new Thickness {Left= 5} } } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var confirmBtn = ((StackPanel)((StackPanel)dialog.Content).Children[2]).Children[0] as Button;
|
||||
var cancelBtn = ((StackPanel)((StackPanel)dialog.Content).Children[2]).Children[1] as Button;
|
||||
var newPwdBox = ((StackPanel)((StackPanel)dialog.Content).Children[0]).Children[1] as PasswordBox;
|
||||
var confirmPwdBox = ((StackPanel)((StackPanel)dialog.Content).Children[1]).Children[1] as PasswordBox;
|
||||
|
||||
confirmBtn.Click += (s, ev) =>
|
||||
{
|
||||
string newPwd = newPwdBox.Password;
|
||||
string confirmPwd = confirmPwdBox.Password;
|
||||
if (string.IsNullOrEmpty(newPwd))
|
||||
{
|
||||
MessageBox.Show("密码不能为空", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
if (newPwd != confirmPwd)
|
||||
{
|
||||
MessageBox.Show("两次输入的密码不一致", "错误", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (UpdatePassword(role, newPwd))
|
||||
{
|
||||
MessageBox.Show("密码修改成功,下次登录生效", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
dialog.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("密码修改失败,请检查文件权限", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
};
|
||||
|
||||
cancelBtn.Click += (s, ev) => dialog.Close();
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
|
||||
private void BtnExit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private string GetPassword(string role)
|
||||
{
|
||||
if (!File.Exists(PasswordFile)) return role == "Admin" ? "admin123" : "oper123";
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(PasswordFile);
|
||||
using JsonDocument doc = JsonDocument.Parse(json);
|
||||
string key = role == "Admin" ? "AdminPassword" : "OperatorPassword";
|
||||
if (doc.RootElement.TryGetProperty(key, out JsonElement value))
|
||||
return value.GetString();
|
||||
return role == "Admin" ? "admin123" : "oper123";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return role == "Admin" ? "admin123" : "oper123";
|
||||
}
|
||||
}
|
||||
|
||||
private bool UpdatePassword(string role, string newPassword)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, string> passwords;
|
||||
if (File.Exists(PasswordFile))
|
||||
{
|
||||
string json = File.ReadAllText(PasswordFile);
|
||||
passwords = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
passwords = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
string key = role == "Admin" ? "AdminPassword" : "OperatorPassword";
|
||||
passwords[key] = newPassword;
|
||||
|
||||
string newJson = JsonSerializer.Serialize(passwords, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(PasswordFile, newJson);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"修改密码失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@
|
||||
<Border DockPanel.Dock="Top" Background="White" BorderBrush="#E9ECF0" BorderThickness="0,0,0,1" Padding="10,5">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="📋 历史记录" Click="OpenHistory_Click" Width="100" HorizontalAlignment="Left" Background="#607D8B"/>
|
||||
<Button Content="阀门控制" Click="OpenValveControl_Click" Padding="10,5" Margin="5"/>
|
||||
<Button x:Name="btnValveControl" Content="阀门控制" Click="OpenValveControl_Click" Padding="10,5" Margin="5"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -12,6 +12,19 @@ namespace MembranePoreTester.Views
|
||||
{
|
||||
InitializeComponent();
|
||||
HistoryWindow.LoadRecordEvent += OnLoadRecord;
|
||||
Loaded += MainWindow_Loaded;
|
||||
}
|
||||
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 根据角色设置权限
|
||||
bool isAdmin = App.CurrentUserRole == Models.UserRole.Admin;
|
||||
|
||||
// 禁用/启用阀门控制按钮(假设 x:Name="btnValveControl")
|
||||
if (btnValveControl != null) btnValveControl.IsEnabled = isAdmin;
|
||||
//// 如果还有参数设置按钮(例如工具栏上的按钮)
|
||||
//if (btnParameterSetting != null) btnParameterSetting.IsEnabled = isAdmin;
|
||||
//// 其他需要限制的全局控件可在此添加
|
||||
}
|
||||
|
||||
private void OnLoadRecord(object sender, LoadRecordEventArgs e)
|
||||
@@ -32,32 +45,34 @@ namespace MembranePoreTester.Views
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OpenHistory_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainVM = DataContext as MainViewModel;
|
||||
if (mainVM == null) return;
|
||||
|
||||
// 获取当前选中的工位索引(假设选项卡控件是 TabControl,名称为 stationTabControl)
|
||||
// 需要在 XAML 中为 TabControl 设置 x:Name="stationTabControl"
|
||||
int currentStation = stationTabControl.SelectedIndex + 1; // 假设索引从0开始
|
||||
int currentStation = stationTabControl.SelectedIndex + 1;
|
||||
var historyWin = new HistoryWindow { SelectedStation = currentStation };
|
||||
historyWin.ShowDialog();
|
||||
}
|
||||
|
||||
|
||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
// 仅管理员可使用 Ctrl+P 打开参数设置窗口
|
||||
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.P)
|
||||
{
|
||||
var win = new ParameterWindow();
|
||||
win.Owner = this;
|
||||
win.ShowDialog();
|
||||
if (App.CurrentUserRole == Models.UserRole.Admin)
|
||||
{
|
||||
var win = new ParameterWindow();
|
||||
win.Owner = this;
|
||||
win.ShowDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("权限不足,无法打开参数设置", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var selectedStation = stationTabControl.SelectedItem as MainViewModel.StationItem;
|
||||
@@ -72,26 +87,15 @@ namespace MembranePoreTester.Views
|
||||
var selectedTabItem = innerTabControl.SelectedItem as TabItem;
|
||||
bool isPoreDistributionActive = selectedTabItem?.Header?.ToString() == "孔分布测试";
|
||||
|
||||
// 关键:设置 StationItem 的状态
|
||||
selectedStation.IsPoreDistributionActive = isPoreDistributionActive;
|
||||
selectedStation.PoreDistributionVM.IsActive = isPoreDistributionActive;
|
||||
|
||||
if (isPoreDistributionActive)
|
||||
{
|
||||
// 切换到孔分布时,清空之前的数据
|
||||
//selectedStation.PoreDistributionVM.ClearData();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 离开孔分布界面时,停止自动采集
|
||||
//selectedStation.PoreDistributionVM.StopCollecting();
|
||||
}
|
||||
// 清空数据和停止采集的代码已注释,保持原样
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:查找 Visual Tree 中的指定类型元素
|
||||
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
|
||||
{
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
@@ -106,6 +110,11 @@ namespace MembranePoreTester.Views
|
||||
|
||||
private void OpenValveControl_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (App.CurrentUserRole != Models.UserRole.Admin)
|
||||
{
|
||||
MessageBox.Show("权限不足,无法打开阀门控制", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
var win = new ValveControlWindow();
|
||||
win.Owner = this;
|
||||
win.Show();
|
||||
|
||||
@@ -19,6 +19,10 @@ namespace MembranePoreTester.Views
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"工位{vm.StationId} IsVisible={this.IsVisible}");
|
||||
vm.IsActive = this.IsVisible;
|
||||
if (this.IsVisible)
|
||||
{
|
||||
vm.RefreshPlot(); // 新增这一行
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"AdminPassword": "admin123",
|
||||
"OperatorPassword": "oper123",
|
||||
"PlcSettings": {
|
||||
// Modbus TCP 连接参数
|
||||
"IpAddress": "192.168.1.10", // PLC 的 IP 地址
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="NModbus4.NetCore" Version="4.0.0" />
|
||||
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
|
||||
<PackageReference Include="SunnyUI" Version="3.9.3" />
|
||||
|
||||
Reference in New Issue
Block a user