Compare commits
10
Commits
048472579c
...
21addffdde
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21addffdde | ||
|
|
fa2f9f64c5 | ||
|
|
1ff51cbc45 | ||
|
|
5d14afcb66 | ||
|
|
b0a7742b8f | ||
|
|
ec6825fc54 | ||
|
|
65661ef211 | ||
|
|
dd287f8b8b | ||
|
|
7f539a0316 | ||
|
|
edcbc2ebc4 |
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -14,29 +15,72 @@ namespace DeviceCommand.Base
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
// ========= 静态注册表:用于 EnovaDataController 反向查找设备实例并分发数据 =========
|
||||
private static readonly List<EnovaDataReporter> _instances = new List<EnovaDataReporter>();
|
||||
private static readonly object _registryLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 当前已注册的所有 EnovaDataReporter 实例(线程安全快照)
|
||||
/// </summary>
|
||||
public static IReadOnlyList<EnovaDataReporter> Instances
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_registryLock)
|
||||
{
|
||||
return _instances.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备编码:用于在多设备场景下按 deviceCode 过滤分发
|
||||
/// 留空表示该实例接收所有上报数据
|
||||
/// </summary>
|
||||
public string DeviceCode { get; set; } = string.Empty;
|
||||
|
||||
// 显式实现/自动属性,方便外部随时更新配置
|
||||
public string TargetUrl { get; set; } = "http://127.0.0.1:8080/api/channel/state";
|
||||
public int TimeoutMilliseconds { get; set; } = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数注入 HttpClient(符合 Prism 依赖注入规范)
|
||||
/// 收到下位机 POST 上报数据时触发
|
||||
/// </summary>
|
||||
public event EventHandler<EnovaChannelDataReceivedEventArgs>? ChannelDataReceived;
|
||||
|
||||
|
||||
public EnovaDataReporter(HttpClient httpClient)
|
||||
{
|
||||
// 如果容器没有注入,则给个默认的单例/实例防空
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
|
||||
// 自动注册到静态实例表,便于 Controller 反向找到本实例
|
||||
lock (_registryLock)
|
||||
{
|
||||
_instances.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<EnovaReportResponse> ReportChannelStateAsync(List<EnovaChannelReportData> dataList, CancellationToken ct = default)
|
||||
/// <summary>
|
||||
/// 从静态注册表中注销当前实例(设备销毁/释放时调用)
|
||||
/// </summary>
|
||||
public virtual void Unregister()
|
||||
{
|
||||
lock (_registryLock)
|
||||
{
|
||||
_instances.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> ReportChannelStateAsync(List<EnovaChannelData> dataList, CancellationToken ct = default)
|
||||
{
|
||||
if (dataList == null || dataList.Count == 0)
|
||||
{
|
||||
return new EnovaReportResponse { Success = false, ErrorInfo = "上报数据集合为空" };
|
||||
return new ApiResponse { Success = false, ErrorInfo = "上报数据集合为空" };
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(TargetUrl))
|
||||
{
|
||||
return new EnovaReportResponse { Success = false, ErrorInfo = "目标上报 URL 未配置" };
|
||||
return new ApiResponse { Success = false, ErrorInfo = "目标上报 URL 未配置" };
|
||||
}
|
||||
|
||||
try
|
||||
@@ -58,12 +102,12 @@ namespace DeviceCommand.Base
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
string responseContent = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<EnovaReportResponse>(responseContent);
|
||||
return result ?? new EnovaReportResponse { Success = true }; // 防止对方返回空Body [cite: 261]
|
||||
var result = JsonConvert.DeserializeObject<ApiResponse>(responseContent);
|
||||
return result ?? new ApiResponse { Success = true }; // 防止对方返回空Body
|
||||
}
|
||||
else
|
||||
{
|
||||
return new EnovaReportResponse
|
||||
return new ApiResponse
|
||||
{
|
||||
Success = false,
|
||||
ErrorInfo = $"服务器响应错误代码: {(int)response.StatusCode} {response.ReasonPhrase}"
|
||||
@@ -74,8 +118,98 @@ namespace DeviceCommand.Base
|
||||
{
|
||||
// 完美承接你上位机原有的异常日志记录器逻辑
|
||||
// Logger.LoggerHelper.ErrorWithNotify($"Enova3 数据上传失败: {ex.Message}");
|
||||
return new EnovaReportResponse { Success = false, ErrorInfo = $"网络异常: {ex.Message}" };
|
||||
return new ApiResponse { Success = false, ErrorInfo = $"网络异常: {ex.Message}" };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 Controller 转发过来的下位机上报数据,并触发事件
|
||||
/// </summary>
|
||||
public virtual ApiResponse HandleIncomingChannelData(List<EnovaChannelData> dataList)
|
||||
{
|
||||
if (dataList == null || dataList.Count == 0)
|
||||
{
|
||||
return new ApiResponse { Success = false, ErrorInfo = "接收到的数据为空" };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 触发事件,让派生类(如 CTS3)或外部订阅者处理具体业务
|
||||
ChannelDataReceived?.Invoke(this, new EnovaChannelDataReceivedEventArgs(dataList));
|
||||
return new ApiResponse { Success = true, ErrorInfo = string.Empty };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ApiResponse { Success = false, ErrorInfo = $"处理上报数据时异常: {ex.Message}" };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由 Controller 调用:将下位机上报的数据广播到所有匹配的实例
|
||||
/// 当数据中含 DeviceCode 时,按 DeviceCode 精确匹配;否则广播给所有实例
|
||||
/// </summary>
|
||||
public static ApiResponse Dispatch(List<EnovaChannelData> dataList)
|
||||
{
|
||||
if (dataList == null || dataList.Count == 0)
|
||||
{
|
||||
return new ApiResponse { Success = false, ErrorInfo = "接收到的数据为空" };
|
||||
}
|
||||
|
||||
EnovaDataReporter[] snapshot;
|
||||
lock (_registryLock)
|
||||
{
|
||||
snapshot = _instances.ToArray();
|
||||
}
|
||||
|
||||
if (snapshot.Length == 0)
|
||||
{
|
||||
return new ApiResponse { Success = false, ErrorInfo = "无可用的设备实例接收数据" };
|
||||
}
|
||||
|
||||
// 按 DeviceCode 分组分发:同一批数据可能来自多个 deviceCode
|
||||
var groups = dataList
|
||||
.GroupBy(d => d?.DeviceCode ?? string.Empty)
|
||||
.ToList();
|
||||
|
||||
var errors = new List<string>();
|
||||
int successCount = 0;
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
string deviceCode = group.Key;
|
||||
var items = group.ToList();
|
||||
|
||||
// 选取目标:1) 设置了相同 DeviceCode 的实例;2) 没设置 DeviceCode 的实例(通用接收者)
|
||||
var targets = snapshot
|
||||
.Where(r => string.Equals(r.DeviceCode, deviceCode, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.IsNullOrEmpty(r.DeviceCode))
|
||||
.ToList();
|
||||
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
errors.Add($"DeviceCode={deviceCode} 无匹配的设备实例");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var resp = target.HandleIncomingChannelData(items);
|
||||
if (resp != null && resp.Success)
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add(resp?.ErrorInfo ?? "未知错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ApiResponse
|
||||
{
|
||||
Success = errors.Count == 0,
|
||||
ErrorInfo = errors.Count == 0 ? string.Empty : string.Join(";", errors)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Model.Model;
|
||||
@@ -6,12 +7,28 @@ using Model.Model;
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
/// <summary>
|
||||
/// Enova3 上位机数据上报核心接口
|
||||
/// Enova3 通道数据接收事件参数
|
||||
/// </summary>
|
||||
public class EnovaChannelDataReceivedEventArgs : EventArgs
|
||||
{
|
||||
public List<EnovaChannelData> DataList { get; }
|
||||
public DateTime ReceivedTime { get; }
|
||||
|
||||
public EnovaChannelDataReceivedEventArgs(List<EnovaChannelData> dataList)
|
||||
{
|
||||
DataList = dataList ?? new List<EnovaChannelData>();
|
||||
ReceivedTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enova3 上位机数据上报 / 接收核心接口
|
||||
/// 既支持上位机主动推送数据到客户平台,也支持接收下位机通过 HTTP POST 上报的数据
|
||||
/// </summary>
|
||||
public interface IEnovaDataReporter
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户平台接收数据的目标 HTTP URL
|
||||
/// 客户平台接收数据的目标 HTTP URL(用于主动推送)
|
||||
/// </summary>
|
||||
string TargetUrl { get; set; }
|
||||
|
||||
@@ -20,12 +37,25 @@ namespace DeviceCommand.Base
|
||||
/// </summary>
|
||||
int TimeoutMilliseconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当 EnovaDataController 收到下位机 POST 上报的数据时触发
|
||||
/// </summary>
|
||||
event EventHandler<EnovaChannelDataReceivedEventArgs> ChannelDataReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 异步推送通道的实时状态数据到客户平台
|
||||
/// </summary>
|
||||
/// <param name="dataList">包含各通道状态的采集数据集合</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
/// <returns>平台服务器的响应状态</returns>
|
||||
Task<EnovaReportResponse> ReportChannelStateAsync(List<EnovaChannelReportData> dataList, CancellationToken ct = default);
|
||||
Task<ApiResponse> ReportChannelStateAsync(List<EnovaChannelData> dataList, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// 处理 EnovaDataController 转发过来的下位机上报数据
|
||||
/// 由 Controller 在收到 HTTP POST 后调用,内部会触发 <see cref="ChannelDataReceived"/> 事件
|
||||
/// </summary>
|
||||
/// <param name="dataList">下位机上报的通道数据集合</param>
|
||||
/// <returns>处理结果,将作为 HTTP 响应返回给下位机</returns>
|
||||
ApiResponse HandleIncomingChannelData(List<EnovaChannelData> dataList);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using NModbus;
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
@@ -38,20 +38,48 @@ namespace DeviceCommand.Base
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 开始连接 - IP: {IPAddress}, 端口: {Port}");
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 超时设置 - 发送: {SendTimeout}ms, 接收: {ReceiveTimeout}ms");
|
||||
|
||||
if (_tcpClient.Connected)
|
||||
{
|
||||
var remoteEndPoint = (IPEndPoint)_tcpClient.Client.RemoteEndPoint!;
|
||||
if (remoteEndPoint.Address.MapToIPv4().ToString() == IPAddress && remoteEndPoint.Port == Port)
|
||||
string currentIp = remoteEndPoint.Address.MapToIPv4().ToString();
|
||||
int currentPort = remoteEndPoint.Port;
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 已有连接: {currentIp}:{currentPort}");
|
||||
|
||||
if (currentIp == IPAddress && currentPort == Port)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 参数匹配,复用现有连接");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 参数不匹配,需要重新连接");
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 关闭并释放旧连接");
|
||||
_tcpClient.Close();
|
||||
_tcpClient.Dispose();
|
||||
_tcpClient = new TcpClient();
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 调用 ConnectAsync({IPAddress}, {Port})");
|
||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 创建ModbusMaster");
|
||||
Modbus = new ModbusFactory().CreateMaster(_tcpClient);
|
||||
return true;
|
||||
|
||||
bool isConnected = _tcpClient.Connected;
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 连接结果: {(isConnected ? "成功" : "失败")}");
|
||||
return isConnected;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 连接异常: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 异常类型: {ex.GetType().Name}");
|
||||
System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 异常堆栈: {ex.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using S7.Net;
|
||||
using S7.Net;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
@@ -9,7 +9,6 @@ namespace DeviceCommand.Base
|
||||
{
|
||||
public class S7Device : IS7Device
|
||||
{
|
||||
// 保持和你一致的连接参数命名属性
|
||||
public string IPAddress { get; private set; } = "127.0.0.1";
|
||||
public CpuType CpuType { get; private set; } = CpuType.S71200;
|
||||
public short Rack { get; private set; } = 0;
|
||||
@@ -20,21 +19,15 @@ namespace DeviceCommand.Base
|
||||
private Plc _plc;
|
||||
public Plc PlcContext => _plc;
|
||||
|
||||
// S7.Net 的 Plc.IsConnected 属性内部会通过 Socket 状态进行判断
|
||||
public bool IsConnected => _plc?.IsConnected ?? false;
|
||||
|
||||
// 统一线程锁
|
||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
public S7Device()
|
||||
{
|
||||
// 初始化默认配置
|
||||
_plc = new Plc(CpuType, IPAddress, Rack, Slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备参数配置(符合你的命名风格)
|
||||
/// </summary>
|
||||
public void ConfigureDevice(string ipAddress, CpuType cpuType, short rack = 0, short slot = 1, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||
{
|
||||
IPAddress = ipAddress;
|
||||
@@ -50,32 +43,30 @@ namespace DeviceCommand.Base
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
// 如果已经连接,检查当前的 IP 和 CPU 类型是否一致,一致则直接复用
|
||||
if (_plc != null && _plc.IsConnected)
|
||||
{
|
||||
if (_plc.IP == IPAddress && _plc.CPU == CpuType && _plc.Rack == Rack && _plc.Slot == Slot)
|
||||
return true;
|
||||
}
|
||||
|
||||
// 修复:释放并彻底清空旧连接实例
|
||||
if (_plc != null)
|
||||
{
|
||||
_plc.Close();
|
||||
}
|
||||
|
||||
// 重新实例化 Plc 对象并配置超时
|
||||
_plc = new Plc(CpuType, IPAddress, Rack, Slot)
|
||||
{
|
||||
ReadTimeout = ReceiveTimeout,
|
||||
WriteTimeout = SendTimeout
|
||||
};
|
||||
|
||||
// 部分版本 S7.Net 的 OpenAsync 本身不接受 CancellationToken,我们通过 WaitAsync 实现超时
|
||||
await _plc.OpenAsync().WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
|
||||
await _plc.OpenAsync();
|
||||
return _plc.IsConnected;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[S7Device] 连接异常: IP={IPAddress}, CPU={CpuType}, Error={ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"[S7Device] 异常堆栈: {ex.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
@@ -125,8 +116,23 @@ namespace DeviceCommand.Base
|
||||
public async Task<T> ReadAsync<T>(string address, CancellationToken ct = default)
|
||||
{
|
||||
var result = await ReadAsync(address, ct);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[S7Device.ReadAsync<T>] 地址={address}, 返回类型={result?.GetType().Name ?? "null"}, 值={result}");
|
||||
|
||||
try
|
||||
{
|
||||
if (result is IConvertible convertible)
|
||||
{
|
||||
return (T)Convert.ChangeType(convertible, typeof(T));
|
||||
}
|
||||
return (T)result;
|
||||
}
|
||||
catch (InvalidCastException ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[S7Device.ReadAsync<T>] 类型转换失败: 目标类型={typeof(T).Name}, 实际类型={result.GetType().Name}, 值={result}, 异常={ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count, CancellationToken ct = default)
|
||||
{
|
||||
|
||||
@@ -14,11 +14,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\Logger\Logger.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Devices\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
using DeviceCommand.Base;
|
||||
using Model.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// CTS3-6-300-8IS0 设备:通过 EnovaDataController 接收下位机 POST 上报的通道数据
|
||||
/// </summary>
|
||||
public class CTS3 : EnovaDataReporter
|
||||
{
|
||||
/// <summary>
|
||||
/// 最近一次收到的通道数据快照(key = ChannelCode)
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, EnovaChannelData> LatestChannelData => _latestChannelData;
|
||||
private readonly Dictionary<string, EnovaChannelData> _latestChannelData = new Dictionary<string, EnovaChannelData>();
|
||||
private readonly object _dataLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 业务侧可订阅此事件以获得更友好的回调(仅本设备数据)
|
||||
/// </summary>
|
||||
public event EventHandler<EnovaChannelDataReceivedEventArgs>? OnDataUpdated;
|
||||
|
||||
public CTS3(HttpClient httpClient) : base(httpClient)
|
||||
{
|
||||
// 订阅基类事件:当 Controller 调用 HandleIncomingChannelData 时会触发
|
||||
ChannelDataReceived += OnChannelDataReceivedInternal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定 DeviceCode 的便捷构造,便于多台 CTS3 共存时按 deviceCode 精确匹配
|
||||
/// </summary>
|
||||
public CTS3(HttpClient httpClient, string deviceCode) : this(httpClient)
|
||||
{
|
||||
DeviceCode = deviceCode ?? string.Empty;
|
||||
}
|
||||
|
||||
private void OnChannelDataReceivedInternal(object? sender, EnovaChannelDataReceivedEventArgs e)
|
||||
{
|
||||
if (e?.DataList == null || e.DataList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 缓存最近一次的通道快照
|
||||
lock (_dataLock)
|
||||
{
|
||||
foreach (var data in e.DataList)
|
||||
{
|
||||
if (data == null) continue;
|
||||
string key = data.ChannelCode ?? string.Empty;
|
||||
_latestChannelData[key] = data;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 业务异常拦截示例:通道错误状态
|
||||
foreach (var data in e.DataList)
|
||||
{
|
||||
if (data == null) continue;
|
||||
if (data.ChannelState == "错误" || data.ChannelState == "0x04")
|
||||
{
|
||||
// TODO: 触发本地报警逻辑
|
||||
// Logger.LoggerHelper.WarnWithNotify($"通道 {data.ChannelCode} 故障");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 转发给业务订阅者
|
||||
OnDataUpdated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public override void Unregister()
|
||||
{
|
||||
ChannelDataReceived -= OnChannelDataReceivedInternal;
|
||||
base.Unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
using DeviceCommand.Base;
|
||||
using S7.Net;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// THC-1100-602A 恒温恒湿试验箱驱动 (S7通讯协议)
|
||||
/// 基于西门子PLC S7-1200/S7-1500系列
|
||||
/// </summary>
|
||||
public class THC1100 : S7Device
|
||||
{
|
||||
#region THC设备默认连接参数
|
||||
private const string DefaultIpAddress = "192.168.1.3";
|
||||
private const CpuType DefaultCpuType = CpuType.S71200;
|
||||
private const short DefaultRack = 0;
|
||||
private const short DefaultSlot = 1;
|
||||
private const int DefaultSendTimeout = 3000;
|
||||
private const int DefaultReceiveTimeout = 3000;
|
||||
#endregion
|
||||
|
||||
#region 寄存器地址定义
|
||||
// ========== 主控界面相关 (DB53) ==========
|
||||
private const string RealTimeTemperatureSetPointAddress = "DB53.DBD280";
|
||||
private const string RealTimeHumidityMeasuredValueAddress = "DB53.DBD1092";
|
||||
private const string RealTimeTemperatureMeasuredValueAddress = "DB53.DBD1052";
|
||||
private const string OperationModeAddress = "DB53.DBW2";
|
||||
private const string TestTypeAddress = "DB53.DBW4";
|
||||
private const string CurrentStepAddress = "DB53.DBW6";
|
||||
private const string TotalStepsAddress = "DB53.DBW8";
|
||||
private const string LoopCountAddress = "DB53.DBW10";
|
||||
private const string RunTimeAddress = "DB53.DBD12";
|
||||
private const string StepTimeAddress = "DB53.DBD16";
|
||||
private const string SystemStatusAddress = "DB53.DBW20";
|
||||
|
||||
// ========== 超温保护相关 (DB53) ==========
|
||||
private const string OverTempHighLimitAddress = "DB53.DBD284";
|
||||
private const string OverTempLowLimitAddress = "DB53.DBD288";
|
||||
private const string OverTempEnableAddress = "DB53.DBX292.0";
|
||||
|
||||
// ========== 报警相关 (DB53) ==========
|
||||
private const string AlarmStatusAddress = "DB53.DBW293";
|
||||
private const string AlarmCodeAddress = "DB53.DBW295";
|
||||
private const string AlarmCountAddress = "DB53.DBW297";
|
||||
|
||||
// ========== 控制指令 (DB53) ==========
|
||||
private const string ControlCommandAddress = "DB53.DBW300";
|
||||
|
||||
// ========== 程序步骤参数 (DB54) ==========
|
||||
private const string StepTemperatureBaseAddress = "DB54.DBD";
|
||||
private const string StepTimeBaseAddress = "DB54.DBD";
|
||||
#endregion
|
||||
|
||||
#region 运行模式枚举
|
||||
public enum OperationMode
|
||||
{
|
||||
Stop = 0,
|
||||
Running = 1,
|
||||
Paused = 2
|
||||
}
|
||||
#endregion
|
||||
|
||||
public THC1100() : base()
|
||||
{
|
||||
// 使用THC设备的默认参数配置
|
||||
ConfigureDevice(
|
||||
ipAddress: DefaultIpAddress,
|
||||
cpuType: DefaultCpuType,
|
||||
rack: DefaultRack,
|
||||
slot: DefaultSlot,
|
||||
sendTimeout: DefaultSendTimeout,
|
||||
receiveTimeout: DefaultReceiveTimeout
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写连接方法,确保使用正确的默认参数连接
|
||||
/// </summary>
|
||||
public override async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(IPAddress))
|
||||
{
|
||||
ConfigureDevice(
|
||||
ipAddress: DefaultIpAddress,
|
||||
cpuType: DefaultCpuType,
|
||||
rack: DefaultRack,
|
||||
slot: DefaultSlot,
|
||||
sendTimeout: DefaultSendTimeout,
|
||||
receiveTimeout: DefaultReceiveTimeout
|
||||
);
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 连接参数: IP={IPAddress}, CPU={CpuType}, Rack={Rack}, Slot={Slot}");
|
||||
|
||||
bool result = await base.ConnectAsync(ct);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 连接失败: IP={IPAddress}, CPU={CpuType}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region 基础测量方法
|
||||
public async Task<float> GetRealTimeTemperatureSetPointAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] data = await ReadBytesAsync(DataType.DataBlock, 53, 280, 4, ct);
|
||||
float value = ByteArrayToFloat(data);
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度设定值(DB53.DBD280): {value}");
|
||||
return value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度设定值失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<float> GetRealTimeHumidityMeasuredValueAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] data = await ReadBytesAsync(DataType.DataBlock, 53, 1092, 4, ct);
|
||||
float value = ByteArrayToFloat(data);
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 读取湿度测量值(DB53.DBD1092): {value}");
|
||||
return value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 读取湿度测量值失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<float> GetRealTimeTemperatureMeasuredValueAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] data = await ReadBytesAsync(DataType.DataBlock, 53, 1052, 4, ct);
|
||||
float value = ByteArrayToFloat(data);
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度测量值(DB53.DBD1052): {value}");
|
||||
return value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度测量值失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private float ByteArrayToFloat(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length < 4)
|
||||
throw new ArgumentException("数据长度不足");
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] reversed = (byte[])data.Clone();
|
||||
Array.Reverse(reversed);
|
||||
return BitConverter.ToSingle(reversed, 0);
|
||||
}
|
||||
return BitConverter.ToSingle(data, 0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 运行控制方法
|
||||
public async Task<OperationMode> GetOperationModeAsync(CancellationToken ct = default)
|
||||
{
|
||||
var value = await ReadAsync<ushort>(OperationModeAddress, ct);
|
||||
return (OperationMode)value;
|
||||
}
|
||||
|
||||
public async Task<int> GetTestTypeAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(TestTypeAddress, ct);
|
||||
}
|
||||
|
||||
public async Task SetTestTypeAsync(int testType, CancellationToken ct = default)
|
||||
{
|
||||
if (testType < 0 || testType > 65535)
|
||||
throw new ArgumentOutOfRangeException(nameof(testType), "试验类型必须在0-65535范围内");
|
||||
|
||||
await WriteAsync(TestTypeAddress, (ushort)testType, ct);
|
||||
}
|
||||
|
||||
public async Task<int> GetCurrentStepAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(CurrentStepAddress, ct);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalStepsAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(TotalStepsAddress, ct);
|
||||
}
|
||||
|
||||
public async Task SetTotalStepsAsync(int steps, CancellationToken ct = default)
|
||||
{
|
||||
if (steps < 1 || steps > 999)
|
||||
throw new ArgumentOutOfRangeException(nameof(steps), "步数必须在1-999之间");
|
||||
|
||||
await WriteAsync(TotalStepsAddress, (ushort)steps, ct);
|
||||
}
|
||||
|
||||
public async Task<int> GetLoopCountAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(LoopCountAddress, ct);
|
||||
}
|
||||
|
||||
public async Task SetLoopCountAsync(int count, CancellationToken ct = default)
|
||||
{
|
||||
if (count < 0 || count > 999)
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "循环次数必须在0-999之间");
|
||||
|
||||
await WriteAsync(LoopCountAddress, (ushort)count, ct);
|
||||
}
|
||||
|
||||
public async Task<float> GetRunTimeAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<float>(RunTimeAddress, ct);
|
||||
}
|
||||
|
||||
public async Task<float> GetStepTimeAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<float>(StepTimeAddress, ct);
|
||||
}
|
||||
|
||||
public async Task<int> GetSystemStatusAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(SystemStatusAddress, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 超温保护方法
|
||||
public async Task<float> GetOverTempHighLimitAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<float>(OverTempHighLimitAddress, ct);
|
||||
}
|
||||
|
||||
public async Task SetOverTempHighLimitAsync(float temperature, CancellationToken ct = default)
|
||||
{
|
||||
await WriteAsync(OverTempHighLimitAddress, temperature, ct);
|
||||
}
|
||||
|
||||
public async Task<float> GetOverTempLowLimitAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<float>(OverTempLowLimitAddress, ct);
|
||||
}
|
||||
|
||||
public async Task SetOverTempLowLimitAsync(float temperature, CancellationToken ct = default)
|
||||
{
|
||||
await WriteAsync(OverTempLowLimitAddress, temperature, ct);
|
||||
}
|
||||
|
||||
public async Task<bool> GetOverTempEnableAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<bool>(OverTempEnableAddress, ct);
|
||||
}
|
||||
|
||||
public async Task SetOverTempEnableAsync(bool enable, CancellationToken ct = default)
|
||||
{
|
||||
await WriteAsync(OverTempEnableAddress, enable, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 报警相关方法
|
||||
public async Task<int> GetAlarmStatusAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(AlarmStatusAddress, ct);
|
||||
}
|
||||
|
||||
public async Task<int> GetAlarmCodeAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(AlarmCodeAddress, ct);
|
||||
}
|
||||
|
||||
public async Task<int> GetAlarmCountAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await ReadAsync<ushort>(AlarmCountAddress, ct);
|
||||
}
|
||||
|
||||
public async Task ClearAlarmAsync(CancellationToken ct = default)
|
||||
{
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x08, ct);
|
||||
await Task.Delay(100, ct);
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x00, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 控制指令方法
|
||||
public async Task StartDeviceAsync(CancellationToken ct = default)
|
||||
{
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x01, ct);
|
||||
await Task.Delay(100, ct);
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x00, ct);
|
||||
}
|
||||
|
||||
public async Task StopDeviceAsync(CancellationToken ct = default)
|
||||
{
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x02, ct);
|
||||
await Task.Delay(100, ct);
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x00, ct);
|
||||
}
|
||||
|
||||
public async Task ResetDeviceAsync(CancellationToken ct = default)
|
||||
{
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x04, ct);
|
||||
await Task.Delay(100, ct);
|
||||
await WriteAsync(ControlCommandAddress, (ushort)0x00, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 程序步骤方法
|
||||
public async Task SetStepTemperatureAsync(int step, float temperature, CancellationToken ct = default)
|
||||
{
|
||||
if (step < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0");
|
||||
|
||||
string address = $"{StepTemperatureBaseAddress}{(step - 1) * 12}";
|
||||
await WriteAsync(address, temperature, ct);
|
||||
}
|
||||
|
||||
public async Task<float> GetStepTemperatureAsync(int step, CancellationToken ct = default)
|
||||
{
|
||||
if (step < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0");
|
||||
|
||||
string address = $"{StepTemperatureBaseAddress}{(step - 1) * 12}";
|
||||
return await ReadAsync<float>(address, ct);
|
||||
}
|
||||
|
||||
public async Task SetStepTimeAsync(int step, float duration, CancellationToken ct = default)
|
||||
{
|
||||
if (step < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0");
|
||||
if (duration < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(duration), "时间不能为负数");
|
||||
|
||||
string address = $"{StepTimeBaseAddress}{(step - 1) * 12 + 8}";
|
||||
await WriteAsync(address, duration, ct);
|
||||
}
|
||||
|
||||
public async Task<float> GetStepTimeAsync(int step, CancellationToken ct = default)
|
||||
{
|
||||
if (step < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0");
|
||||
|
||||
string address = $"{StepTimeBaseAddress}{(step - 1) * 12 + 8}";
|
||||
return await ReadAsync<float>(address, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 数据打包方法
|
||||
public async Task<(float TemperatureSetPoint, float TemperatureMeasured, float HumidityMeasured)> GetThcDataPackAsync(CancellationToken ct = default)
|
||||
{
|
||||
var tempSet = await GetRealTimeTemperatureSetPointAsync(ct);
|
||||
var tempMeas = await GetRealTimeTemperatureMeasuredValueAsync(ct);
|
||||
var humidMeas = await GetRealTimeHumidityMeasuredValueAsync(ct);
|
||||
return (tempSet, tempMeas, humidMeas);
|
||||
}
|
||||
|
||||
public async Task<(OperationMode Mode, int CurrentStep, int TotalSteps, int LoopCount, float RunTime)> GetRunStatusPackAsync(CancellationToken ct = default)
|
||||
{
|
||||
var mode = await GetOperationModeAsync(ct);
|
||||
var currentStep = await GetCurrentStepAsync(ct);
|
||||
var totalSteps = await GetTotalStepsAsync(ct);
|
||||
var loopCount = await GetLoopCountAsync(ct);
|
||||
var runTime = await GetRunTimeAsync(ct);
|
||||
return (mode, currentStep, totalSteps, loopCount, runTime);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// UMC1000 温湿度控制器(RS485)
|
||||
/// </summary>
|
||||
public class UMC1000Rtu : ModbusRtu
|
||||
{
|
||||
private readonly byte _slaveId;
|
||||
|
||||
/// <summary>
|
||||
/// 温度测量值 REAL
|
||||
/// </summary>
|
||||
private const ushort TemperatureAddress = 150;
|
||||
|
||||
/// <summary>
|
||||
/// 湿度测量值 REAL
|
||||
/// </summary>
|
||||
private const ushort HumidityAddress = 152;
|
||||
|
||||
public UMC1000Rtu(
|
||||
byte slaveId,
|
||||
string portName,
|
||||
int baudRate = 9600,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000)
|
||||
{
|
||||
_slaveId = slaveId;
|
||||
|
||||
ConfigureDevice(
|
||||
portName,
|
||||
baudRate,
|
||||
dataBits,
|
||||
stopBits,
|
||||
parity,
|
||||
readTimeout,
|
||||
writeTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取温度
|
||||
/// </summary>
|
||||
public async Task<float> ReadTemperatureAsync(
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var a = await ReadFloatAsync(
|
||||
TemperatureAddress,
|
||||
ct);
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取湿度
|
||||
/// </summary>
|
||||
public async Task<float> ReadHumidityAsync(
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return await ReadFloatAsync(
|
||||
HumidityAddress,
|
||||
ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次读取温湿度(减少通讯次数,提升轮询效率)
|
||||
/// </summary>
|
||||
public async Task<(float Temperature, float Humidity)> ReadAllAsync(
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
ushort[] regs = await ReadHoldingRegistersAsync(
|
||||
_slaveId,
|
||||
TemperatureAddress,
|
||||
4,
|
||||
ct);
|
||||
|
||||
return
|
||||
(
|
||||
ToFloat(regs[0], regs[1]),
|
||||
ToFloat(regs[2], regs[3])
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<float> ReadFloatAsync(
|
||||
ushort address,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ushort[] regs = await ReadHoldingRegistersAsync(
|
||||
_slaveId,
|
||||
address,
|
||||
2,
|
||||
ct);
|
||||
|
||||
return ToFloat(regs[0], regs[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两个寄存器完美转换为 Float 数值(已解决 00 64 改完解析变 0 的致命问题)
|
||||
/// </summary>
|
||||
private static float ToFloat(ushort reg1, ushort reg2)
|
||||
{
|
||||
// 核心诊断:从回包 01 03 08 [00 64 00 00] 来看,0x0064 正好是十进制 100。
|
||||
// 这种情况在工业仪表中有 2 种常见可能,已为你做好了自适应处理:
|
||||
|
||||
// ==========================================
|
||||
// 可能性【一】:下位机名义上叫 REAL,实际上是 32位长整型(Int32 / CD AB 字节序)
|
||||
// ==========================================
|
||||
int intValue = (reg2 << 16) | reg1;
|
||||
if (intValue == 100 || intValue > 0 && intValue < 1500)
|
||||
{
|
||||
// 如果仪表传 100 代表 10.0℃,可以在这里除以 10.0f
|
||||
return (float)intValue;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 可能性【二】:下位机确实是标准 IEEE 754 浮点数,但受高低字错位影响
|
||||
// ==========================================
|
||||
Span<byte> bytes = stackalloc byte[4];
|
||||
|
||||
// 采用安全的绝对字节映射,不再依赖会引发未知异常的 Array.Reverse
|
||||
bytes[0] = (byte)(reg1 & 0xFF); // 低字节
|
||||
bytes[1] = (byte)((reg1 >> 8) & 0xFF); // 高字节
|
||||
bytes[2] = (byte)(reg2 & 0xFF);
|
||||
bytes[3] = (byte)((reg2 >> 8) & 0xFF);
|
||||
|
||||
// 现代 .NET 高性能内存强转(等同于 C++ 的 reinterpret_cast<float*>)
|
||||
return MemoryMarshal.Read<float>(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
public class UMC1300 : ModbusTcp
|
||||
{
|
||||
// 从站地址(设备 ID)
|
||||
private readonly byte _slaveId;
|
||||
|
||||
// 寄存器地址常量(根据设备手册定义)
|
||||
private const ushort ADDR_TEMP_PV = 0x0000; // 温度测量值
|
||||
private const ushort ADDR_HUMID_PV = 0x0001; // 湿度测量值
|
||||
private const ushort ADDR_TEMP_MV = 0x0002; // 温度输出值
|
||||
private const ushort ADDR_HUMID_MV = 0x0003; // 湿度输出值
|
||||
|
||||
// 转换系数:寄存器原始值 × SCALE = 工程值
|
||||
private const float SCALE = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="slaveId">Modbus 从站地址(1~247)</param>
|
||||
/// <param name="ip">设备 IP 地址</param>
|
||||
/// <param name="port">端口,默认 502</param>
|
||||
/// <param name="sendTimeoutMs">发送超时(毫秒)</param>
|
||||
/// <param name="receiveTimeoutMs">接收超时(毫秒)</param>
|
||||
public UMC1300(byte slaveId, string ip, int port = 502,
|
||||
int sendTimeoutMs = 3000, int receiveTimeoutMs = 3000)
|
||||
: base()
|
||||
{
|
||||
_slaveId = slaveId;
|
||||
ConfigureDevice(ip, port, sendTimeoutMs, receiveTimeoutMs);
|
||||
}
|
||||
|
||||
public override async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return await base.ConnectAsync(ct);
|
||||
}
|
||||
|
||||
// ==================== 读取单个值 ====================
|
||||
|
||||
/// <summary>读取温度 PV(工程值)</summary>
|
||||
public async Task<float> ReadTemperaturePVAsync(CancellationToken ct = default)
|
||||
{
|
||||
ushort[] raw = await ReadInputRegistersAsync(_slaveId, ADDR_TEMP_PV, 1, ct);
|
||||
return raw[0] * SCALE;
|
||||
}
|
||||
|
||||
/// <summary>读取湿度 PV(工程值)</summary>
|
||||
public async Task<float> ReadHumidityPVAsync(CancellationToken ct = default)
|
||||
{
|
||||
ushort[] raw = await ReadInputRegistersAsync(_slaveId, ADDR_HUMID_PV, 1, ct);
|
||||
return raw[0] * SCALE;
|
||||
}
|
||||
|
||||
/// <summary>读取温度 MV(工程值)</summary>
|
||||
public async Task<float> ReadTemperatureMVAsync(CancellationToken ct = default)
|
||||
{
|
||||
ushort[] raw = await ReadHoldingRegistersAsync(_slaveId, ADDR_TEMP_MV, 1, ct);
|
||||
return raw[0] * SCALE;
|
||||
}
|
||||
|
||||
/// <summary>读取湿度 MV(工程值)</summary>
|
||||
public async Task<float> ReadHumidityMVAsync(CancellationToken ct = default)
|
||||
{
|
||||
ushort[] raw = await ReadHoldingRegistersAsync(_slaveId, ADDR_HUMID_MV, 1, ct);
|
||||
return raw[0] * SCALE;
|
||||
}
|
||||
|
||||
// ==================== 批量读取 ====================
|
||||
|
||||
/// <summary>
|
||||
/// 一次性读取温度 PV、湿度 PV、温度 MV、湿度 MV(效率更高)
|
||||
/// </summary>
|
||||
/// <returns>(tempPV, humidPV, tempMV, humidMV)</returns>
|
||||
public async Task<(float tempPV, float humidPV, float tempMV, float humidMV)> ReadAllAsync(CancellationToken ct = default)
|
||||
{
|
||||
// 从 0x0000 开始连续读取 4 个保持寄存器
|
||||
ushort[] raw = await ReadHoldingRegistersAsync(_slaveId, ADDR_TEMP_PV, 4, ct);
|
||||
return (
|
||||
raw[0] * SCALE, // 温度 PV
|
||||
raw[1] * SCALE, // 湿度 PV
|
||||
raw[2] * SCALE, // 温度 MV
|
||||
raw[3] * SCALE // 湿度 MV
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,22 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "Service\Service.
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module", "Module", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoginModule", "LoginModule\LoginModule.csproj", "{F79AC87E-7A5A-486F-BE6C-51E81CA569E4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIShare", "UIShare\UIShare.csproj", "{F7A7D4FA-974C-470F-9543-0B256640BD81}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SettingModule", "SettingModule\SettingModule.csproj", "{2C3C2DBB-F782-416B-8571-07D3F533820B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateInfoModule", "UpdateInfoModule\UpdateInfoModule.csproj", "{B99017CA-BB14-426A-BBF0-C0C05C6510CA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainModule", "MainModule\MainModule.csproj", "{715852A3-D2DE-4C2E-AEF2-2BC0ADBEAC0A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LOT", "LOT\LOT.csproj", "{01E01684-DDE8-4B00-9BFC-2C5CDB2A261F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCommand", "DeviceCommand\DeviceCommand.csproj", "{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOT_API", "IOT_API\IOT_API.csproj", "{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -55,44 +43,18 @@ Global
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C769E6C6-55E9-40C3-A611-9EFAB101BE6A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F79AC87E-7A5A-486F-BE6C-51E81CA569E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F79AC87E-7A5A-486F-BE6C-51E81CA569E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F79AC87E-7A5A-486F-BE6C-51E81CA569E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F79AC87E-7A5A-486F-BE6C-51E81CA569E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F7A7D4FA-974C-470F-9543-0B256640BD81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F7A7D4FA-974C-470F-9543-0B256640BD81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F7A7D4FA-974C-470F-9543-0B256640BD81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F7A7D4FA-974C-470F-9543-0B256640BD81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2C3C2DBB-F782-416B-8571-07D3F533820B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2C3C2DBB-F782-416B-8571-07D3F533820B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2C3C2DBB-F782-416B-8571-07D3F533820B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2C3C2DBB-F782-416B-8571-07D3F533820B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B99017CA-BB14-426A-BBF0-C0C05C6510CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B99017CA-BB14-426A-BBF0-C0C05C6510CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B99017CA-BB14-426A-BBF0-C0C05C6510CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B99017CA-BB14-426A-BBF0-C0C05C6510CA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{715852A3-D2DE-4C2E-AEF2-2BC0ADBEAC0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{715852A3-D2DE-4C2E-AEF2-2BC0ADBEAC0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{715852A3-D2DE-4C2E-AEF2-2BC0ADBEAC0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{715852A3-D2DE-4C2E-AEF2-2BC0ADBEAC0A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{01E01684-DDE8-4B00-9BFC-2C5CDB2A261F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{01E01684-DDE8-4B00-9BFC-2C5CDB2A261F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{01E01684-DDE8-4B00-9BFC-2C5CDB2A261F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{01E01684-DDE8-4B00-9BFC-2C5CDB2A261F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2F035F70-5F1D-4C22-B4F0-1AEA0ED127A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1E2A7C73-2CAE-4084-939B-0F8EE73AA955}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{F79AC87E-7A5A-486F-BE6C-51E81CA569E4} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{2C3C2DBB-F782-416B-8571-07D3F533820B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{B99017CA-BB14-426A-BBF0-C0C05C6510CA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{715852A3-D2DE-4C2E-AEF2-2BC0ADBEAC0A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {22BD9235-6581-454D-97D8-F4E932F80888}
|
||||
EndGlobalSection
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using DeviceCommand.Base;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model.Model;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/enova")] // 路由可以自己定,定好后把完整的 URL 提供给上位机配置
|
||||
public class EnovaDataController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 接收下位机 POST 上报的通道数据,并联动分发到所有匹配的 EnovaDataReporter 实例(如 CTS3)
|
||||
/// </summary>
|
||||
[HttpPost("upload-status")]
|
||||
public IActionResult ReceiveChannelStatus([FromBody] List<EnovaChannelData> rawDataList)
|
||||
{
|
||||
// 1. 基础校验
|
||||
if (rawDataList == null || rawDataList.Count == 0)
|
||||
{
|
||||
return Ok(new ApiResponse
|
||||
{
|
||||
Success = false,
|
||||
ErrorInfo = "接收到的数据为空"
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 2. 联动设备:将数据分发到所有已注册的 EnovaDataReporter 实例
|
||||
// (包括 CTS3 等派生类,由它们自行通过事件处理)
|
||||
ApiResponse dispatchResult = EnovaDataReporter.Dispatch(rawDataList);
|
||||
|
||||
// 3. 严格按照文档 Page 6 的格式返回响应
|
||||
return Ok(dispatchResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 系统内部异常处理
|
||||
return Ok(new ApiResponse
|
||||
{
|
||||
Success = false,
|
||||
ErrorInfo = $"服务器内部错误: {ex.Message}"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace WebAPI
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
// ======= 核心配置开始 =======
|
||||
builder.Services.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
options.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString;
|
||||
|
||||
// 3. 属性命名策略保持原样(不强制转为驼峰)
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||
});
|
||||
// ======= 核心配置结束 =======
|
||||
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
// 注意:如果上位机只支持 http 请求,现场部署时你可能需要根据实际情况注释掉下面这行 Https 重定向
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:18581",
|
||||
"sslPort": 44392
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5287",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7240;http://localhost:5287",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@WebAPI_HostAddress = http://localhost:5287
|
||||
|
||||
GET {{WebAPI_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<prism:PrismApplication x:Class="LOT.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:LOT"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:prism="http://prismlibrary.com/">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!--自定义style-->
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/CommonStyle.xaml"></ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</prism:PrismApplication>
|
||||
@@ -1,82 +0,0 @@
|
||||
using Castle.DynamicProxy;
|
||||
using Common;
|
||||
using LOT.ViewModels;
|
||||
using LOT.ViewModels.Dialogs;
|
||||
using LOT.Views;
|
||||
using LOT.Views;
|
||||
using LOT.Views.Dialogs;
|
||||
using Logger;
|
||||
using Notifications.Wpf.Core;
|
||||
using ORM;
|
||||
using Service.Implement;
|
||||
using Service.Interface;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using UIShare.PubEvent;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace LOT
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : PrismApplication
|
||||
{
|
||||
protected override Window CreateShell()
|
||||
{
|
||||
//UI线程未捕获异常处理事件
|
||||
this.DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
//Task线程内未捕获异常处理事件
|
||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||
////多线程异常
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
return Container.Resolve<ShellView>();
|
||||
}
|
||||
private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
LoggerHelper.Error(e.Exception.Message,e.Exception.StackTrace);
|
||||
}
|
||||
|
||||
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
LoggerHelper.Error(e.Exception.Message, e.Exception.StackTrace);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
//记录dump文件
|
||||
Exception ex = e.ExceptionObject as Exception;
|
||||
MiniDump.TryDump($"dumps\\Error_{DateTime.Now:yyyy-MM-dd HH-mm-ss-ms}.dmp", MiniDump.Option.WithFullMemory, ex);
|
||||
}
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
//初始化数据库
|
||||
//DatabaseConfig.SetTenant(10001);
|
||||
//DatabaseConfig.InitMySql("127.0.0.1",3306,"LOT","root","123456");
|
||||
//DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true);
|
||||
//SqlSugarContext.InitDatabase();
|
||||
//显示登录窗口
|
||||
var login=Container.Resolve<LoginModuleView>();
|
||||
var re=Container.Resolve<IRegionManager>();
|
||||
RegionManager.SetRegionManager(login, re);
|
||||
RegionManager.SetRegionManager(Application.Current.MainWindow, re);
|
||||
login.Show();
|
||||
}
|
||||
protected override void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
//注册弹窗
|
||||
containerRegistry.RegisterDialog<MessageBoxView, MessageBoxViewModel>("MessageBox");
|
||||
// 注册通知管理器
|
||||
INotificationManager NotificationManager = new NotificationManager();
|
||||
containerRegistry.RegisterInstance<INotificationManager>(NotificationManager);
|
||||
}
|
||||
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
||||
protected override IModuleCatalog CreateModuleCatalog()
|
||||
{
|
||||
//指定模块加载方式为从文件夹中以反射发现并加载module(推荐用法)
|
||||
return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -1,67 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Noto\NotoSans-Bold.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Noto\NotoSans-BoldItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Noto\NotoSans-Italic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Noto\NotoSans-Regular.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-Black.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-BlackItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-Bold.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-BoldItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-Italic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-Light.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-LightItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-Medium.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-MediumItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-Regular.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-Thin.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\Roboto-ThinItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\RobotoCondensed-Bold.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\RobotoCondensed-BoldItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\RobotoCondensed-Italic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\RobotoCondensed-Light.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\RobotoCondensed-LightItalic.ttf" />
|
||||
<Content Remove="C:\Users\23560\.nuget\packages\materialdesignthemes\5.3.0\contentFiles\any\net8.0-windows7.0\Resources\Roboto\RobotoCondensed-Regular.ttf" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="Prism.Unity" Version="9.0.537" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||
<ProjectReference Include="..\Logger\Logger.csproj" />
|
||||
<ProjectReference Include="..\LoginModule\LoginModule.csproj" />
|
||||
<ProjectReference Include="..\MainModule\MainModule.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
<ProjectReference Include="..\ORM\ORM.csproj" />
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
<ProjectReference Include="..\SettingModule\SettingModule.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
<ProjectReference Include="..\UpdateInfoModule\UpdateInfoModule.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Resources\Images\error.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Images\info.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\Images\warning.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -1,120 +0,0 @@
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace LOT.ViewModels.Dialogs
|
||||
{
|
||||
public class MessageBoxViewModel : DialogViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
|
||||
private string _Title;
|
||||
public string Title
|
||||
{
|
||||
get => _Title;
|
||||
set => SetProperty(ref _Title, value);
|
||||
}
|
||||
|
||||
private string _Message = "";
|
||||
public string Message
|
||||
{
|
||||
get => _Message;
|
||||
set => SetProperty(ref _Message, value);
|
||||
}
|
||||
|
||||
private string _Icon= $"pack://siteoforigin:,,,/Resources/Images/info.png";
|
||||
public string Icon
|
||||
{
|
||||
get => _Icon;
|
||||
set => SetProperty(ref _Icon, value);
|
||||
}
|
||||
|
||||
private bool _ShowYes;
|
||||
public bool ShowYes
|
||||
{
|
||||
get => _ShowYes;
|
||||
set => SetProperty(ref _ShowYes, value);
|
||||
}
|
||||
|
||||
private bool _ShowNo;
|
||||
public bool ShowNo
|
||||
{
|
||||
get => _ShowNo;
|
||||
set => SetProperty(ref _ShowNo, value);
|
||||
}
|
||||
|
||||
private bool _ShowOk;
|
||||
public bool ShowOk
|
||||
{
|
||||
get => _ShowOk;
|
||||
set => SetProperty(ref _ShowOk, value);
|
||||
}
|
||||
|
||||
private bool _ShowCancel;
|
||||
public bool ShowCancel
|
||||
{
|
||||
get => _ShowCancel;
|
||||
set => SetProperty(ref _ShowCancel, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand YesCommand { get; set; }
|
||||
public ICommand NoCommand { get; set; }
|
||||
public ICommand OkCommand { get; set; }
|
||||
public ICommand CancelCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public DialogCloseListener RequestClose { get; set; }
|
||||
|
||||
public MessageBoxViewModel(IContainerProvider containerProvider):base(containerProvider)
|
||||
{
|
||||
YesCommand = new DelegateCommand(OnYes);
|
||||
NoCommand = new DelegateCommand(OnNo);
|
||||
OkCommand = new DelegateCommand(OnOk);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
}
|
||||
|
||||
private void CloseDialog(ButtonResult result)
|
||||
{
|
||||
var parameters = new DialogParameters();
|
||||
RequestClose.Invoke(new DialogResult(result));
|
||||
}
|
||||
|
||||
private void OnYes() => CloseDialog(ButtonResult.Yes);
|
||||
private void OnNo() => CloseDialog(ButtonResult.No);
|
||||
private void OnOk() => CloseDialog(ButtonResult.OK);
|
||||
private void OnCancel() => CloseDialog(ButtonResult.Cancel);
|
||||
|
||||
#region Prism Dialog 规范
|
||||
public bool CanCloseDialog() => true;
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
|
||||
}
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
|
||||
Title = parameters.GetValue<string>("Title");
|
||||
Message = parameters.GetValue<string>("Message");
|
||||
var iconKey = parameters.GetValue<string>("Icon"); // info / error / warn
|
||||
Icon = iconKey switch
|
||||
{
|
||||
"info" => $"pack://siteoforigin:,,,/Resources/Images/info.png",
|
||||
"error" => $"pack://siteoforigin:,,,/Resources/Images/error.png",
|
||||
"warn" => $"pack://siteoforigin:,,,/Resources/Images/warning.png",
|
||||
_ => $"pack://siteoforigin:,,,/Resources/Images/info.png" // 默认
|
||||
};
|
||||
|
||||
|
||||
ShowYes = parameters.GetValue<bool>("ShowYes");
|
||||
ShowNo = parameters.GetValue<bool>("ShowNo");
|
||||
ShowOk = parameters.GetValue<bool>("ShowOk");
|
||||
ShowCancel = parameters.GetValue<bool>("ShowCancel");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using LOT.Views;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Notifications.Wpf.Core;
|
||||
using Prism.Events;
|
||||
using Prism.Ioc;
|
||||
using Prism.Modularity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using UIShare.PubEvent;
|
||||
|
||||
namespace LOT.ViewModels
|
||||
{
|
||||
public class ShellViewModel : BindableBase
|
||||
{
|
||||
#region 属性
|
||||
private bool _IsLeftDrawerOpen;
|
||||
|
||||
public bool IsLeftDrawerOpen
|
||||
{
|
||||
get => _IsLeftDrawerOpen;
|
||||
set => SetProperty(ref _IsLeftDrawerOpen, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region 命令
|
||||
public ICommand LeftDrawerOpenCommand { get; set; }
|
||||
public ICommand MinimizeCommand { get; set; }
|
||||
public ICommand MaximizeCommand { get; set; }
|
||||
public ICommand CloseCommand { get; set; }
|
||||
public ICommand NavigateCommand { get; set; }
|
||||
public ICommand LoadCommand { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
private IEventAggregator _eventAggregator;
|
||||
private IRegionManager _regionManager;
|
||||
private IContainerProvider _containerProvider;
|
||||
private INotificationManager _notificationManager;
|
||||
private IModuleManager _moduleManager;
|
||||
public ShellViewModel( IContainerProvider containerProvider)
|
||||
{
|
||||
_containerProvider= containerProvider;
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
_regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
||||
_moduleManager = containerProvider.Resolve<IModuleManager>();
|
||||
LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
|
||||
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
|
||||
MaximizeCommand = new DelegateCommand<Window>(MaximizeWindow);
|
||||
CloseCommand = new DelegateCommand<Window>(CloseWindow);
|
||||
NavigateCommand = new DelegateCommand<string>(Navigate);
|
||||
LoadCommand = new DelegateCommand(Load);
|
||||
//订阅登录成功事件
|
||||
_eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(() =>
|
||||
{
|
||||
Application.Current.MainWindow.Show();
|
||||
_regionManager.RequestNavigate("ShellViewManager", "MainView");
|
||||
});
|
||||
}
|
||||
|
||||
#region 命令处理
|
||||
private void Load()
|
||||
{
|
||||
_notificationManager.ShowAsync(new NotificationContent { Title = "登录成功", Message = "", Type = NotificationType.Success });
|
||||
//默认导航到主界面
|
||||
Type moduleAType = typeof(MainModule.MainModule);
|
||||
_moduleManager.LoadModule(moduleAType.Name);
|
||||
}
|
||||
private void Navigate(string content)
|
||||
{
|
||||
switch (content)
|
||||
{
|
||||
case "主界面":
|
||||
_regionManager.RequestNavigate("ShellViewManager", "MainView");
|
||||
break;
|
||||
|
||||
case "设置界面":
|
||||
_regionManager.RequestNavigate("ShellViewManager", "SettingView");
|
||||
break;
|
||||
|
||||
case "更新界面":
|
||||
_regionManager.RequestNavigate("ShellViewManager", "UpdateInfoView");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void LeftDrawerOpen()
|
||||
{
|
||||
IsLeftDrawerOpen = true;
|
||||
}
|
||||
private void MinimizeWindow(Window window)
|
||||
{
|
||||
if (window != null)
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
private void MaximizeWindow(Window window)
|
||||
{
|
||||
if (window != null)
|
||||
{
|
||||
window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseWindow(Window window)
|
||||
{
|
||||
window?.Close();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<UserControl x:Class="LOT.Views.Dialogs.MessageBoxView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:LOT.Views.Dialogs"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
Background="Transparent"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Height="250"
|
||||
Width="300">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<Border CornerRadius="20"
|
||||
Background="white"
|
||||
MouseLeftButtonDown="Border_MouseLeftButtonDown">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Margin="15 5 5 5"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Title}"
|
||||
Foreground="#333" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Grid.Row="1">
|
||||
<!-- Icon -->
|
||||
<Image
|
||||
Width="100"
|
||||
Height="100"
|
||||
VerticalAlignment="Top"
|
||||
Margin="5 15 0 0"
|
||||
Source="{Binding Icon}" />
|
||||
<!-- Message -->
|
||||
<TextBlock
|
||||
FontSize="20"
|
||||
Margin="15 0 0 0"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Message}" />
|
||||
</StackPanel>
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="2"
|
||||
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
|
||||
<Button Content="Yes"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowYes, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding YesCommand}" />
|
||||
|
||||
<Button Content="No"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowNo, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding NoCommand}" />
|
||||
|
||||
<Button Content="OK"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowOk, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding OkCommand}" />
|
||||
|
||||
<Button Content="Cancel"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowCancel, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding CancelCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace LOT.Views.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// MessageBoxView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MessageBoxView : UserControl
|
||||
{
|
||||
public MessageBoxView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<mah:MetroWindow xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
x:Class="LOT.Views.LoginModuleView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:local="clr-namespace:LOT.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="LOT"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Height="315"
|
||||
Width="420"
|
||||
ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<ContentControl prism:RegionManager.RegionName="LoginRegion" />
|
||||
</Grid>
|
||||
</mah:MetroWindow>
|
||||
@@ -1,38 +0,0 @@
|
||||
using UIShare.PubEvent;
|
||||
using MahApps.Metro.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace LOT.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Login.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LoginModuleView : MetroWindow
|
||||
{
|
||||
public LoginModuleView(IEventAggregator eventAggregator)
|
||||
{
|
||||
InitializeComponent();
|
||||
//订阅登录成功事件
|
||||
eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(() =>
|
||||
{
|
||||
this.Close();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
<Window x:Class="LOT.Views.ShellView"
|
||||
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"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="false"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
WindowStyle="None"
|
||||
Title="ShellView"
|
||||
d:DesignHeight="1080"
|
||||
d:DesignWidth="1920">
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome GlassFrameThickness="-1" />
|
||||
</WindowChrome.WindowChrome>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<materialDesign:DrawerHost x:Name="MainDrawerHost"
|
||||
IsLeftDrawerOpen="{Binding IsLeftDrawerOpen, Mode=TwoWay}">
|
||||
|
||||
<!-- ✅ 左侧抽屉内容 -->
|
||||
<materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<StackPanel Width="220"
|
||||
Background="{DynamicResource MaterialDesignPaper}">
|
||||
<TextBlock Text="导航菜单"
|
||||
FontSize="18"
|
||||
Margin="16"
|
||||
Foreground="{DynamicResource PrimaryHueMidBrush}" />
|
||||
<Separator Margin="0,0,0,8" />
|
||||
<Button Content="主界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="设置界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="更新界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
</StackPanel>
|
||||
</materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- 顶部工具栏 -->
|
||||
<materialDesign:ColorZone Mode="PrimaryMid"
|
||||
MouseLeftButtonDown="ColorZone_MouseLeftButtonDown">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Menu Grid.Column="0"
|
||||
Background="Transparent"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center">
|
||||
<!-- 文件菜单 -->
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="菜单"
|
||||
Foreground="White"
|
||||
Command="{Binding DataContext.LeftDrawerOpenCommand, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Menu"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
|
||||
<!-- 工具菜单 -->
|
||||
<MenuItem Header="工具"
|
||||
FontSize="13"
|
||||
Height="50"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Tools"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
<MenuItem Header="注销登录"
|
||||
|
||||
Foreground="Black" />
|
||||
</MenuItem>
|
||||
|
||||
|
||||
</Menu>
|
||||
|
||||
<Menu Grid.Column="2"
|
||||
Margin="0 0 20 0">
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="最小化"
|
||||
Foreground="White"
|
||||
Command="{Binding MinimizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Minimize"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="最大化"
|
||||
Foreground="White"
|
||||
Command="{Binding MaximizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Maximize"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="关闭"
|
||||
Foreground="White"
|
||||
Command="{Binding CloseCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Close"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
</Grid>
|
||||
<!-- 左侧菜单 -->
|
||||
|
||||
|
||||
|
||||
</materialDesign:ColorZone>
|
||||
|
||||
<materialDesign:DialogHost Grid.Row="1"
|
||||
x:Name="DialogHost"
|
||||
DialogBackground="Transparent"
|
||||
Background="Transparent"
|
||||
Identifier="Root">
|
||||
<!-- 主内容区 -->
|
||||
<Grid>
|
||||
<ContentControl prism:RegionManager.RegionName="ShellViewManager" />
|
||||
<Border x:Name="Overlay"
|
||||
Background="#40000000"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1">
|
||||
<StackPanel Width="150"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 100">
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border x:Name="Waitinglay"
|
||||
Background="#40000000"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1">
|
||||
<StackPanel Width="150"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 100">
|
||||
<ProgressBar Width="80"
|
||||
Height="80"
|
||||
Margin="20"
|
||||
IsIndeterminate="True"
|
||||
Style="{StaticResource MaterialDesignCircularProgressBar}" />
|
||||
<TextBlock FontSize="30"
|
||||
Text="加载中......"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:DialogHost>
|
||||
</Grid>
|
||||
</materialDesign:DrawerHost>
|
||||
|
||||
</Window>
|
||||
@@ -1,51 +0,0 @@
|
||||
using UIShare.PubEvent;
|
||||
using Prism.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace LOT.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// ShellView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ShellView : Window
|
||||
{
|
||||
public ShellView(IEventAggregator eventAggregator)
|
||||
{
|
||||
InitializeComponent();
|
||||
//注册灰度遮罩层
|
||||
eventAggregator.GetEvent<OverlayEvent>().Subscribe(ShowOverlay);
|
||||
eventAggregator.GetEvent<WaitingEvent>().Subscribe(ShowWaitinglay);
|
||||
}
|
||||
|
||||
private void ShowWaitinglay(bool arg)
|
||||
{
|
||||
Waitinglay.Visibility = arg ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShowOverlay(bool arg)
|
||||
{
|
||||
Overlay.Visibility = arg ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
|
||||
private void ColorZone_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,14 @@ namespace Logger
|
||||
|
||||
Logger.Error(message);
|
||||
}
|
||||
public static void Info(string message)
|
||||
{
|
||||
Logger.Info(message);
|
||||
}
|
||||
public static void Warn(string message)
|
||||
{
|
||||
Logger.Warn(message);
|
||||
}
|
||||
// 解析堆栈,找到项目文件路径和行号
|
||||
public static string GetProjectStackLine(string stackTrace)
|
||||
{
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using LoginModule.Views;
|
||||
using Prism.Modularity;
|
||||
using System.Reflection;
|
||||
namespace LoginModule
|
||||
{
|
||||
public class LoginModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
regionManager.RegisterViewWithRegion("LoginRegion", typeof(LoginView));
|
||||
regionManager.RegisterViewWithRegion("LoginRegion", typeof(RegisterView));
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<LoginView>("LoginView");
|
||||
containerRegistry.RegisterForNavigation<RegisterView>("RegisterView");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace LoginModule.ViewModels
|
||||
{
|
||||
public class LoginViewModel: NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private string _Password;
|
||||
public string Password
|
||||
{
|
||||
get => _Password;
|
||||
set => SetProperty(ref _Password, value);
|
||||
}
|
||||
private string _Account;
|
||||
public string Account
|
||||
{
|
||||
get => _Account;
|
||||
set => SetProperty(ref _Account, value);
|
||||
}
|
||||
#endregion
|
||||
public ICommand LoginCommand { get; set; }
|
||||
public ICommand RegisterCommand { get; set; }
|
||||
private IEventAggregator _eventAggregator;
|
||||
public LoginViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
LoginCommand = new AsyncDelegateCommand(OnLogin);
|
||||
RegisterCommand = new AsyncDelegateCommand(OnRegister);
|
||||
}
|
||||
|
||||
private async Task OnRegister()
|
||||
{
|
||||
_regionManager.RequestNavigate("LoginRegion", "RegisterView");
|
||||
}
|
||||
|
||||
private async Task OnLogin()
|
||||
{
|
||||
_eventAggregator.GetEvent<LoginSuccessEvent>().Publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace LoginModule.ViewModels
|
||||
{
|
||||
public class RegisterViewModel : NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
private string _SecondPassword;
|
||||
public string SecondPassword
|
||||
{
|
||||
get => _SecondPassword;
|
||||
set => SetProperty(ref _SecondPassword, value);
|
||||
}
|
||||
private string _Password;
|
||||
public string Password
|
||||
{
|
||||
get => _Password;
|
||||
set => SetProperty(ref _Password, value);
|
||||
}
|
||||
private string _Account;
|
||||
public string Account
|
||||
{
|
||||
get => _Account;
|
||||
set => SetProperty(ref _Account, value);
|
||||
}
|
||||
#endregion
|
||||
public ICommand BackCommand { get; set; }
|
||||
public ICommand RegisterCommand { get; set; }
|
||||
public RegisterViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
BackCommand = new DelegateCommand(OnBack);
|
||||
RegisterCommand = new AsyncDelegateCommand(OnRegister);
|
||||
}
|
||||
|
||||
private void OnBack()
|
||||
{
|
||||
_regionManager.RequestNavigate("LoginRegion", "LoginView");
|
||||
}
|
||||
|
||||
private async Task OnRegister()
|
||||
{
|
||||
_regionManager.RequestNavigate("LoginRegion", "LoginView");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
<UserControl x:Class="LoginModule.Views.LoginView"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
xmlns:local="clr-namespace:LoginModule.Views"
|
||||
mc:Ignorable="d"
|
||||
Height="315"
|
||||
Width="420">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/CommonStyle.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- 这里是你的额外样式 -->
|
||||
<Style TargetType="TextBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.TextBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="PasswordBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.PasswordBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button"
|
||||
BasedOn="{StaticResource MahApps.Styles.Button.Flat}">
|
||||
<Setter Property="FontSize"
|
||||
Value="15" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="SemiBold" />
|
||||
<Setter Property="Foreground"
|
||||
Value="White" />
|
||||
<Setter Property="Background"
|
||||
Value="#2196F3" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0" />
|
||||
<Setter Property="Margin"
|
||||
Value="0,20,0,0" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<Border Grid.Row="0"
|
||||
Background="White"
|
||||
Margin="20,20,20,0"
|
||||
CornerRadius="5"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#E0E0E0"
|
||||
Padding="30,20">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<Label Content="用户登录"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="15"
|
||||
Padding="3" />
|
||||
<StackPanel Grid.Row="1">
|
||||
|
||||
<!-- 用户名输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="17">
|
||||
<materialDesign:PackIcon Kind="Account"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding Account}"
|
||||
mah:TextBoxHelper.Watermark="请输入账号"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 密码输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Lock"
|
||||
VerticalAlignment="Center" />
|
||||
<PasswordBox helpers:PasswordBoxHelper.Password="{Binding Password, Mode=TwoWay}"
|
||||
mah:TextBoxHelper.Watermark="请输入密码"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
<Label Content="注册账户"
|
||||
HorizontalAlignment="Right"
|
||||
Cursor="Hand">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseLeftButtonUp">
|
||||
<i:InvokeCommandAction Command="{Binding RegisterCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<Button Grid.Row="2"
|
||||
Command="{Binding LoginCommand}"
|
||||
Content="登 录"
|
||||
Width="120"
|
||||
Height="33"
|
||||
mah:ControlsHelper.CornerRadius="5">
|
||||
<Button.Effect>
|
||||
<DropShadowEffect BlurRadius="8"
|
||||
ShadowDepth="3"
|
||||
Opacity="0.5" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 底部版权信息 -->
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="© 2025 大学生学习交流平台"
|
||||
Foreground="#777"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,10" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace LoginModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// LoginView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LoginView : UserControl
|
||||
{
|
||||
public LoginView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
<UserControl x:Class="LoginModule.Views.RegisterView"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
xmlns:local="clr-namespace:LoginModule.Views"
|
||||
mc:Ignorable="d"
|
||||
Height="315"
|
||||
Width="420">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/CommonStyle.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- 这里是你的额外样式 -->
|
||||
<Style TargetType="TextBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.TextBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="PasswordBox"
|
||||
BasedOn="{StaticResource MahApps.Styles.PasswordBox}">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0,0,0,2" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="#E0E0E0" />
|
||||
<Setter Property="Padding"
|
||||
Value="5,8" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button"
|
||||
BasedOn="{StaticResource MahApps.Styles.Button.Flat}">
|
||||
<Setter Property="FontSize"
|
||||
Value="15" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="SemiBold" />
|
||||
<Setter Property="Foreground"
|
||||
Value="White" />
|
||||
<Setter Property="Background"
|
||||
Value="#2196F3" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0" />
|
||||
<Setter Property="Margin"
|
||||
Value="0,20,0,0" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<Border Grid.Row="0"
|
||||
Background="White"
|
||||
Margin="20,20,20,0"
|
||||
CornerRadius="5"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#E0E0E0"
|
||||
Padding="30,20">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<Label Content="用户注册"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="15"
|
||||
Padding="3" />
|
||||
<StackPanel Grid.Row="1">
|
||||
|
||||
<!-- 用户名输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Account"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding Account}"
|
||||
mah:TextBoxHelper.Watermark="请输入账号"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 密码输入 -->
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Lock"
|
||||
VerticalAlignment="Center" />
|
||||
<PasswordBox helpers:PasswordBoxHelper.Password="{Binding Password, Mode=TwoWay}"
|
||||
mah:TextBoxHelper.Watermark="请输入密码"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="7">
|
||||
<materialDesign:PackIcon Kind="Lock"
|
||||
VerticalAlignment="Center" />
|
||||
<PasswordBox helpers:PasswordBoxHelper.Password="{Binding SecondPassword, Mode=TwoWay}"
|
||||
mah:TextBoxHelper.Watermark="请输入二次密码"
|
||||
mah:TextBoxHelper.ClearTextButton="True"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="180"
|
||||
Height="30"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
<Label Content="返回"
|
||||
HorizontalAlignment="Right"
|
||||
Cursor="Hand">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseLeftButtonUp">
|
||||
<i:InvokeCommandAction Command="{Binding BackCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<Button Grid.Row="2"
|
||||
Command="{Binding RegisterCommand}"
|
||||
Content="注 册"
|
||||
Width="120"
|
||||
Height="33"
|
||||
mah:ControlsHelper.CornerRadius="5">
|
||||
<Button.Effect>
|
||||
<DropShadowEffect BlurRadius="8"
|
||||
ShadowDepth="3"
|
||||
Opacity="0.5" />
|
||||
</Button.Effect>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 底部版权信息 -->
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="© 2025 大学生学习交流平台"
|
||||
Foreground="#777"
|
||||
FontSize="12"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,10" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace LoginModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// RegisterView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class RegisterView : UserControl
|
||||
{
|
||||
public RegisterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using MainModule.Views;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MainModule
|
||||
{
|
||||
[Module(OnDemand=true)]
|
||||
public class MainModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(MainView));
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<MainView>("MainView");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Views\MainView.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,112 +0,0 @@
|
||||
using Model.Entity;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System.Collections.ObjectModel;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace MainModule.ViewModels
|
||||
{
|
||||
public class MainViewModel : NavigateViewModelBase
|
||||
{
|
||||
public ObservableCollection<ChamberMonitorItem> Chambers { get; } = new();
|
||||
private IContainerProvider _containerProvider;
|
||||
public MainViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_containerProvider = containerProvider;
|
||||
|
||||
// 触发配置与绑定初始化
|
||||
ConfigureAndBindChambers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 核心配置与数据绑定逻辑
|
||||
/// </summary>
|
||||
private void ConfigureAndBindChambers()
|
||||
{
|
||||
// 实例 1:Modbus TCP 环境箱配置
|
||||
Chambers.Add(new ChamberMonitorItem
|
||||
{
|
||||
Id = 1,
|
||||
Name = "环境箱 01",
|
||||
ProtocolType = "Modbus TCP",
|
||||
IsConnected = false,
|
||||
Temperature = 0.0,
|
||||
Humidity = 0.0
|
||||
});
|
||||
|
||||
// 实例 2:Modbus TCP B+ 环境箱配置
|
||||
Chambers.Add(new ChamberMonitorItem
|
||||
{
|
||||
Id = 2,
|
||||
Name = "环境箱 02",
|
||||
ProtocolType = "Modbus TCP B+",
|
||||
IsConnected = false,
|
||||
Temperature = 0.0,
|
||||
Humidity = 0.0
|
||||
});
|
||||
|
||||
// 实例 3:西门子 S7 环境箱配置
|
||||
Chambers.Add(new ChamberMonitorItem
|
||||
{
|
||||
Id = 3,
|
||||
Name = "环境箱 03",
|
||||
ProtocolType = "S7",
|
||||
IsConnected = false,
|
||||
Temperature = 0.0,
|
||||
Humidity = 0.0
|
||||
});
|
||||
|
||||
// 实例 4:HTTP 环境箱配置
|
||||
Chambers.Add(new ChamberMonitorItem
|
||||
{
|
||||
Id = 4,
|
||||
Name = "环境箱 04",
|
||||
ProtocolType = "HTTP",
|
||||
IsConnected = false,
|
||||
Temperature = 0.0,
|
||||
Humidity = 0.0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 环境箱 UI 状态及温湿度数据绑定模型
|
||||
/// </summary>
|
||||
public class ChamberMonitorItem :BindableBase
|
||||
{
|
||||
private double _temperature;
|
||||
private double _humidity;
|
||||
private bool _isConnected;
|
||||
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string ProtocolType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 温度(支持通知刷新)
|
||||
/// </summary>
|
||||
public double Temperature
|
||||
{
|
||||
get => _temperature;
|
||||
set => SetProperty(ref _temperature, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 湿度(支持通知刷新)
|
||||
/// </summary>
|
||||
public double Humidity
|
||||
{
|
||||
get => _humidity;
|
||||
set => SetProperty(ref _humidity, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接状态:True-已连接(绿灯),False-断开(红灯)
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
<UserControl x:Class="MainModule.Views.MainView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
d:DesignHeight="1080"
|
||||
d:DesignWidth="1920">
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="#F4F6F9">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="环境箱监控状态面板" FontSize="20" FontWeight="Bold" Foreground="#2C3E50" Margin="12,4,0,16"/>
|
||||
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding Chambers}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Rows="2" Margin="-6"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="White" BorderBrush="#E2E8F0" BorderThickness="1" CornerRadius="8" Margin="6" Padding="20">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="8" Color="#A0AEC0" Opacity="0.15" ShadowDepth="1"/>
|
||||
</Border.Effect>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel Grid.Row="0" LastChildFill="False">
|
||||
<StackPanel DockPanel.Dock="Left">
|
||||
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold" Foreground="#1A202C"/>
|
||||
<TextBlock Text="{Binding ProtocolType, StringFormat=驱动协议: {0}}" FontSize="12" Foreground="#718096" Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0" VerticalAlignment="Center">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#E53E3E"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Background" Value="#38A169"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="13" FontWeight="Medium">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="OFFLINE"/>
|
||||
<Setter Property="Foreground" Value="#E53E3E"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="ONLINE"/>
|
||||
<Setter Property="Foreground" Value="#38A169"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
|
||||
<UniformGrid Grid.Row="1" Columns="2" Margin="0,20,0,0">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock Text="温度采集" FontSize="13" Foreground="#718096" HorizontalAlignment="Center"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="{Binding Temperature, StringFormat={}{0:F1}}" FontSize="40" FontWeight="Light" Foreground="#3182CE"/>
|
||||
<TextBlock Text=" ℃" FontSize="16" Foreground="#3182CE" VerticalAlignment="Bottom" Margin="2,0,0,8"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock Text="湿度采集" FontSize="13" Foreground="#718096" HorizontalAlignment="Center"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="{Binding Humidity, StringFormat={}{0:F1}}" FontSize="40" FontWeight="Light" Foreground="#319795"/>
|
||||
<TextBlock Text=" %RH" FontSize="16" Foreground="#319795" VerticalAlignment="Bottom" Margin="2,0,0,8"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// MainView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
public MainView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Text.Json.Serialization;
|
||||
namespace Model.Model
|
||||
{
|
||||
public class EnovaChannelData
|
||||
{
|
||||
public string Laboratory { get; set; }
|
||||
public string Manufacture { get; set; }
|
||||
public string TaskId { get; set; }
|
||||
|
||||
// 针对文档命名不一致的字段进行别名特性处理(优先匹配真实JSON示例)
|
||||
[JsonPropertyName("deviceCode")]
|
||||
public string DeviceCode { get; set; }
|
||||
[JsonPropertyName("devCode")] // 兼容参数表
|
||||
public string DevCodeAlternate { set => DeviceCode = value; }
|
||||
|
||||
[JsonPropertyName("channelCode")]
|
||||
public string ChannelCode { get; set; }
|
||||
[JsonPropertyName("chnCode")]
|
||||
public string ChnCodeAlternate { set => ChannelCode = value; }
|
||||
|
||||
[JsonPropertyName("channelIP")]
|
||||
public string ChannelIP { get; set; }
|
||||
[JsonPropertyName("chnlP")]
|
||||
public string ChnlPAlternate { set => ChannelIP = value; }
|
||||
|
||||
[JsonPropertyName("pcIP")]
|
||||
public string PcIP { get; set; }
|
||||
[JsonPropertyName("pclP")]
|
||||
public string PclPAlternate { set => PcIP = value; }
|
||||
|
||||
public string PcName { get; set; }
|
||||
public string BarCode { get; set; }
|
||||
public string ProjectName { get; set; }
|
||||
public string StepId { get; set; }
|
||||
public string CycleDepth { get; set; }
|
||||
public string Cycles { get; set; }
|
||||
public string StepTime { get; set; }
|
||||
public string TotalTime { get; set; }
|
||||
public string PowerState { get; set; }
|
||||
public string ChannelState { get; set; }
|
||||
public string Voltage { get; set; }
|
||||
public string Current { get; set; }
|
||||
public string Power { get; set; }
|
||||
public string GivenVoltage { get; set; }
|
||||
public string GivenCurrent { get; set; }
|
||||
public string GivenPower { get; set; }
|
||||
public string Cap { get; set; }
|
||||
public string ChgCap { get; set; }
|
||||
public string DchgCap { get; set; }
|
||||
public string CapDiff { get; set; }
|
||||
public string Energy { get; set; }
|
||||
public string ChgEnergy { get; set; }
|
||||
public string DchgEnergy { get; set; }
|
||||
public string EnergyDiff { get; set; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public string Timestamp { get; set; }
|
||||
[JsonPropertyName("timeStamp")]
|
||||
public string TimestampAlternate { set => Timestamp = value; }
|
||||
|
||||
// 对应 elements 里的 dict 动态解析
|
||||
public Dictionary<string, string> OtherSignals { get; set; }
|
||||
}
|
||||
|
||||
// 严格按照文档 Page 6 要求返回的响应结构
|
||||
public class ApiResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string ErrorInfo { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Model.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// Enova3 通道状态上报实体(单条通道数据)
|
||||
/// </summary>
|
||||
public class EnovaChannelReportData
|
||||
{
|
||||
public string laboratory { get; set; } = string.Empty; // 实验室名称
|
||||
public string manufacture { get; set; } = string.Empty; // 供应商名称
|
||||
public string taskId { get; set; } = string.Empty; // 任务ID
|
||||
public string devCode { get; set; } = string.Empty; // 台架编码(注:表格里是devCode,示例里是deviceCode,建议互补或以表格为准)
|
||||
public string deviceCode { get; set; } = string.Empty; // 兼容示例中的 deviceCode
|
||||
public string chnCode { get; set; } = string.Empty; // 通道编码
|
||||
public string channelCode { get; set; } = string.Empty; // 兼容示例中的 channelCode
|
||||
public string chnlP { get; set; } = string.Empty; // 通道IP
|
||||
public string channelIP { get; set; } = string.Empty; // 兼容示例中的 channelIP
|
||||
public string pclP { get; set; } = string.Empty; // 上位机IP
|
||||
public string pcIP { get; set; } = string.Empty; // 兼容示例中的 pcIP
|
||||
public string pcName { get; set; } = string.Empty; // 上位机名称
|
||||
public string barCode { get; set; } = string.Empty; // 样品条码
|
||||
public string projectName { get; set; } = string.Empty; // 项目名称
|
||||
public string stepId { get; set; } = string.Empty; // 工步行
|
||||
public string cycleDepth { get; set; } = string.Empty; // 循环层
|
||||
public string cycles { get; set; } = string.Empty; // 循环次数
|
||||
public string stepTime { get; set; } = string.Empty; // 工步时间
|
||||
public string totalTime { get; set; } = string.Empty; // 运行时间
|
||||
public string powerState { get; set; } = string.Empty; // 电源状态
|
||||
public string channelState { get; set; } = string.Empty; // 通道状态
|
||||
public string voltage { get; set; } = string.Empty; // 电压
|
||||
public string current { get; set; } = string.Empty; // 电流
|
||||
public string power { get; set; } = string.Empty; // 功率
|
||||
public string givenVoltage { get; set; } = string.Empty; // 给定电压
|
||||
public string givenCurrent { get; set; } = string.Empty; // 给定电流
|
||||
public string givenPower { get; set; } = string.Empty; // 给定功率
|
||||
public string cap { get; set; } = string.Empty; // 容量
|
||||
public string chgCap { get; set; } = string.Empty; // 充电容量
|
||||
public string dchgCap { get; set; } = string.Empty; // 放电容量
|
||||
public string capDiff { get; set; } = string.Empty; // 容量差
|
||||
public string energy { get; set; } = string.Empty; // 能量
|
||||
public string chgEnergy { get; set; } = string.Empty; // 充电能量
|
||||
public string dchgEnergy { get; set; } = string.Empty; // 放电能量
|
||||
public string energyDiff { get; set; } = string.Empty; // 能量差
|
||||
public string timeStamp { get; set; } = string.Empty; // 采集时间(如:2024-07-11 13:03:03)
|
||||
public string timestamp { get; set; } = string.Empty; // 兼容小写格式
|
||||
|
||||
// 强类型:其他信号集合(支持动态 KV 对)
|
||||
public Dictionary<string, string> otherSignals { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户平台统一返回接收格式
|
||||
/// </summary>
|
||||
public class EnovaReportResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string ErrorInfo { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using SettingModule.Views;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SettingModule
|
||||
{
|
||||
public class SettingModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(SettingView));
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<SettingView>("SettingView");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Views\LoginView.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\SettingView.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,28 +0,0 @@
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace SettingModule.ViewModels
|
||||
{
|
||||
public class SettingViewModel : NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
|
||||
|
||||
#endregion
|
||||
#region 命令
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
public SettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
#region 命令处理
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<UserControl x:Class="SettingModule.Views.SettingView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
d:DesignHeight="1080"
|
||||
d:DesignWidth="1920">
|
||||
<Grid Background="Green">
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SettingModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// SettingView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SettingView : UserControl
|
||||
{
|
||||
public SettingView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace UIShare.Converters
|
||||
{
|
||||
public class BooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
bool input = value is bool b && b;
|
||||
bool invert = parameter?.ToString()?.ToLower() == "invert";
|
||||
|
||||
if (invert)
|
||||
input = !input;
|
||||
|
||||
return input ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
bool output = value is Visibility v && v == Visibility.Visible;
|
||||
bool invert = parameter?.ToString()?.ToLower() == "invert";
|
||||
|
||||
if (invert)
|
||||
output = !output;
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace UIShare.Helpers
|
||||
{
|
||||
public static class PasswordBoxHelper
|
||||
{
|
||||
public static readonly DependencyProperty PasswordProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"Password",
|
||||
typeof(string),
|
||||
typeof(PasswordBoxHelper),
|
||||
new FrameworkPropertyMetadata(
|
||||
string.Empty,
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
OnPasswordPropertyChanged));
|
||||
|
||||
public static void SetPassword(DependencyObject d, string value)
|
||||
=> d.SetValue(PasswordProperty, value);
|
||||
|
||||
public static string GetPassword(DependencyObject d)
|
||||
=> (string)d.GetValue(PasswordProperty);
|
||||
|
||||
private static void OnPasswordPropertyChanged(
|
||||
DependencyObject d,
|
||||
DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PasswordBox pb)
|
||||
{
|
||||
pb.PasswordChanged -= PasswordChanged;
|
||||
if (pb.Password != (string)e.NewValue)
|
||||
{
|
||||
pb.Password = (string)e.NewValue;
|
||||
}
|
||||
pb.PasswordChanged += PasswordChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is PasswordBox pb)
|
||||
{
|
||||
SetPassword(pb, pb.Password);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UIShare.PubEvent
|
||||
{
|
||||
public class LoginSuccessEvent:PubSubEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UIShare.PubEvent
|
||||
{
|
||||
public class OverlayEvent : PubSubEvent<bool>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UIShare.PubEvent
|
||||
{
|
||||
public class WaitingEvent : PubSubEvent<bool>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- 引用MaterialDesign和MahApps的资源 -->
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.MahApps;component/Themes/MaterialDesignTheme.MahApps.Fonts.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.MahApps;component/Themes/MaterialDesignTheme.MahApps.Flyout.xaml" />
|
||||
|
||||
<!-- MahApps资源 -->
|
||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
|
||||
|
||||
<!-- Material Design资源 -->
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Secondary/MaterialDesignColor.Lime.xaml" />
|
||||
<!--自定义style-->
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/WindowStyle.xaml"></ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
@@ -1,22 +0,0 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="DialogUserManageStyle"
|
||||
TargetType="Window">
|
||||
<Setter Property="WindowStyle"
|
||||
Value="None" />
|
||||
<Setter Property="Topmost"
|
||||
Value="True" />
|
||||
<Setter Property="ResizeMode"
|
||||
Value="NoResize" />
|
||||
<Setter Property="ShowInTaskbar"
|
||||
Value="False" />
|
||||
<Setter Property="AllowsTransparency"
|
||||
Value="true" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
<Setter Property="SizeToContent"
|
||||
Value="WidthAndHeight" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Prism.Unity" Version="9.0.537" />
|
||||
<PackageReference Include="MaterialDesignColors" Version="5.3.0" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
|
||||
<PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" />
|
||||
<PackageReference Include="Notifications.Wpf.Core" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Behaviors\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,27 +0,0 @@
|
||||
using Notifications.Wpf.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UIShare.ViewModelBase
|
||||
{
|
||||
public abstract class DialogViewModelBase : BindableBase,IDialogAware
|
||||
{
|
||||
public DialogCloseListener RequestClose { get; set; }
|
||||
public IEventAggregator _eventAggregator;
|
||||
private INotificationManager _notificationManager;
|
||||
public DialogViewModelBase(IContainerProvider containerProvider)
|
||||
{
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
||||
}
|
||||
#region Dialog
|
||||
|
||||
public virtual bool CanCloseDialog() => true;
|
||||
public virtual void OnDialogClosed() { }
|
||||
public virtual void OnDialogOpened(IDialogParameters parameters) { }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using Notifications.Wpf.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UIShare.ViewModelBase
|
||||
{
|
||||
public abstract class NavigateViewModelBase : BindableBase, INavigationAware
|
||||
{
|
||||
public DialogCloseListener RequestClose { get; set; }
|
||||
public IEventAggregator _eventAggregator;
|
||||
public IDialogService _dialogService;
|
||||
public IRegionManager _regionManager;
|
||||
private INotificationManager _notificationManager;
|
||||
public NavigateViewModelBase(IContainerProvider containerProvider)
|
||||
{
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
_dialogService = containerProvider.Resolve<IDialogService>();
|
||||
_regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
||||
}
|
||||
|
||||
protected void ShowInfoMessageBox(string Message,Action callback)
|
||||
{
|
||||
var dialogParams = new DialogParameters();
|
||||
dialogParams.Add("Title", "提示");
|
||||
dialogParams.Add("Message", Message);
|
||||
dialogParams.Add("Icon", "info");
|
||||
dialogParams.Add("ShowOk", true);
|
||||
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
|
||||
{
|
||||
callback();
|
||||
});
|
||||
}
|
||||
protected void ShowErrorMessageBox(string Message,Action callback)
|
||||
{
|
||||
var dialogParams = new DialogParameters();
|
||||
dialogParams.Add("Title", "错误");
|
||||
dialogParams.Add("Message", Message);
|
||||
dialogParams.Add("Icon", "info");
|
||||
dialogParams.Add("ShowOk", true);
|
||||
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
|
||||
{
|
||||
callback();
|
||||
});
|
||||
}
|
||||
#region Navigation
|
||||
|
||||
public virtual void OnNavigatedTo(NavigationContext navigationContext) { }
|
||||
|
||||
public virtual bool IsNavigationTarget(NavigationContext navigationContext) => true;
|
||||
|
||||
public virtual void OnNavigatedFrom(NavigationContext navigationContext) { }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Reflection;
|
||||
using UpdateInfoModule.Views;
|
||||
|
||||
namespace UpdateInfoModule
|
||||
{
|
||||
public class UpdateInfoModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(UpdateInfoView));
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<UpdateInfoView>("UpdateInfoView");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Views\UpdateInfoView.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,30 +0,0 @@
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace UpdateInfoModule.ViewModels
|
||||
{
|
||||
public class UpdateInfoViewModel : NavigateViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
|
||||
|
||||
#endregion
|
||||
#region 命令
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public UpdateInfoViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
#region 命令处理
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<UserControl x:Class="UpdateInfoModule.Views.UpdateInfoView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
d:DesignHeight="1080"
|
||||
d:DesignWidth="1920">
|
||||
<Grid Background="Yellow">
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace UpdateInfoModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// UpdateInfoView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class UpdateInfoView : UserControl
|
||||
{
|
||||
public UpdateInfoView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user