wxw
2024-09-06 d89bbfdd748109020f3255c80918d0f60b6e842c
Merge branch 'master' into wxw
8个文件已修改
8个文件已添加
874 ■■■■■ 已修改文件
Admin.NET/Admin.NET.Core/Service/Log/Dto/ExportLogDto.cs 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Log/Dto/LogInput.cs 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Log/Dto/LogVisOutput.cs 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Log/SysLogDiffService.cs 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Log/SysLogExService.cs 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Log/SysLogOpService.cs 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Log/SysLogVisService.cs 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Hub/PlcHub.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/PLC/PLCTaskAction.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/PLC/PLCUtil.cs 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/Config/SysConfigService.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoInput.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/WcsAlarmInfo/WcsAlarmInfoService.cs 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/device/alarmManage/index.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/device/deviceMonitor/component/setting.vue 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/device/deviceMonitor/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/Admin.NET.Core/Service/Log/Dto/ExportLogDto.cs
New file
@@ -0,0 +1,68 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 导出日志数据
/// </summary>
[ExcelExporter(Name = "日志数据", TableStyle = OfficeOpenXml.Table.TableStyles.None, AutoFitAllColumn = true)]
public class ExportLogDto
{
    /// <summary>
    /// 记录器类别名称
    /// </summary>
    [ExporterHeader(DisplayName = "记录器类别名称", IsBold = true)]
    public string LogName { get; set; }
    /// <summary>
    /// 日志级别
    /// </summary>
    [ExporterHeader(DisplayName = "日志级别", IsBold = true)]
    public string LogLevel { get; set; }
    /// <summary>
    /// 事件Id
    /// </summary>
    [ExporterHeader(DisplayName = "事件Id", IsBold = true)]
    public string EventId { get; set; }
    /// <summary>
    /// 日志消息
    /// </summary>
    [ExporterHeader(DisplayName = "日志消息", IsBold = true)]
    public string Message { get; set; }
    /// <summary>
    /// 异常对象
    /// </summary>
    [ExporterHeader(DisplayName = "异常对象", IsBold = true)]
    public string Exception { get; set; }
    /// <summary>
    /// 当前状态值
    /// </summary>
    [ExporterHeader(DisplayName = "当前状态值", IsBold = true)]
    public string State { get; set; }
    /// <summary>
    /// 日志记录时间
    /// </summary>
    [ExporterHeader(DisplayName = "日志记录时间", IsBold = true)]
    public DateTime LogDateTime { get; set; }
    /// <summary>
    /// 线程Id
    /// </summary>
    [ExporterHeader(DisplayName = "线程Id", IsBold = true)]
    public int ThreadId { get; set; }
    /// <summary>
    /// 请求跟踪Id
    /// </summary>
    [ExporterHeader(DisplayName = "请求跟踪Id", IsBold = true)]
    public string TraceId { get; set; }
}
Admin.NET/Admin.NET.Core/Service/Log/Dto/LogInput.cs
New file
@@ -0,0 +1,73 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
public class PageOpLogInput : PageVisLogInput
{
    /// <summary>
    /// 模块名称
    /// </summary>
    public string? ControllerName { get; set; }
}
public class PageExLogInput : PageOpLogInput
{
}
public class PageVisLogInput : PageLogInput
{
    /// <summary>
    /// 方法名称
    ///</summary>
    public string? ActionName { get; set; }
}
public class PageLogInput : BasePageInput
{
    /// <summary>
    /// 开始时间
    /// </summary>
    public DateTime? StartTime { get; set; }
    /// <summary>
    /// 结束时间
    /// </summary>
    public DateTime? EndTime { get; set; }
    /// <summary>
    /// 账号
    /// </summary>
    public string? Account { get; set; }
    /// <summary>
    /// 操作用时
    /// </summary>
    public long? Elapsed { get; set; }
    /// <summary>
    /// 状态
    /// </summary>
    public string Status { get; set; }
    /// <summary>
    /// IP地址
    /// </summary>
    public string? RemoteIp { get; set; }
}
public class LogInput
{
    /// <summary>
    /// 开始时间
    /// </summary>
    public DateTime? StartTime { get; set; }
    /// <summary>
    /// 结束时间
    /// </summary>
    public DateTime? EndTime { get; set; }
}
Admin.NET/Admin.NET.Core/Service/Log/Dto/LogVisOutput.cs
New file
@@ -0,0 +1,35 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
public class LogVisOutput
{
    /// <summary>
    /// 登录地点
    /// </summary>
    public string Location { get; set; }
    /// <summary>
    /// 经度
    /// </summary>
    public double? Longitude { get; set; }
    /// <summary>
    /// 维度
    /// </summary>
    public double? Latitude { get; set; }
    /// <summary>
    /// 真实姓名
    /// </summary>
    public string RealName { get; set; }
    /// <summary>
    /// 日志时间
    /// </summary>
    public DateTime? LogDateTime { get; set; }
}
Admin.NET/Admin.NET.Core/Service/Log/SysLogDiffService.cs
New file
@@ -0,0 +1,58 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 系统差异日志服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 330)]
public class SysLogDiffService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<SysLogDiff> _sysLogDiffRep;
    public SysLogDiffService(SqlSugarRepository<SysLogDiff> sysLogDiffRep)
    {
        _sysLogDiffRep = sysLogDiffRep;
    }
    /// <summary>
    /// 获取差异日志分页列表 🔖
    /// </summary>
    /// <returns></returns>
    [SuppressMonitor]
    [DisplayName("获取差异日志分页列表")]
    public async Task<SqlSugarPagedList<SysLogDiff>> Page(PageLogInput input)
    {
        return await _sysLogDiffRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime)
            .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime)
            .OrderBy(u => u.CreateTime, OrderByType.Desc)
            .ToPagedListAsync(input.Page, input.PageSize);
    }
    /// <summary>
    /// 获取差异日志详情 🔖
    /// </summary>
    /// <returns></returns>
    [SuppressMonitor]
    [DisplayName("获取差异日志详情")]
    public async Task<SysLogDiff> GetDetail(long id)
    {
        return await _sysLogDiffRep.GetFirstAsync(u => u.Id == id);
    }
    /// <summary>
    /// 清空差异日志 🔖
    /// </summary>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Clear"), HttpPost]
    [DisplayName("清空差异日志")]
    public void Clear()
    {
        _sysLogDiffRep.AsSugarClient().DbMaintenance.TruncateTable<SysLogDiff>();
    }
}
Admin.NET/Admin.NET.Core/Service/Log/SysLogExService.cs
New file
@@ -0,0 +1,86 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 系统异常日志服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 350)]
public class SysLogExService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<SysLogEx> _sysLogExRep;
    public SysLogExService(SqlSugarRepository<SysLogEx> sysLogExRep)
    {
        _sysLogExRep = sysLogExRep;
    }
    /// <summary>
    /// 获取异常日志分页列表 🔖
    /// </summary>
    /// <returns></returns>
    [SuppressMonitor]
    [DisplayName("获取异常日志分页列表")]
    public async Task<SqlSugarPagedList<SysLogEx>> Page(PageExLogInput input)
    {
        return await _sysLogExRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime)
            .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Account), u => u.Account == input.Account)
            .WhereIF(!string.IsNullOrWhiteSpace(input.ControllerName), u => u.ControllerName == input.ControllerName)
            .WhereIF(!string.IsNullOrWhiteSpace(input.ActionName), u => u.ActionName == input.ActionName)
            .WhereIF(!string.IsNullOrWhiteSpace(input.RemoteIp), u => u.RemoteIp == input.RemoteIp)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Elapsed.ToString()), u => u.Elapsed >= input.Elapsed)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Status) && input.Status == "200", u => u.Status == input.Status)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Status) && input.Status != "200", u => u.Status != input.Status)
            //.OrderBy(u => u.CreateTime, OrderByType.Desc)
            .IgnoreColumns(u => new { u.RequestParam, u.ReturnResult, u.Message })
            .OrderBuilder(input)
            .ToPagedListAsync(input.Page, input.PageSize);
    }
    /// <summary>
    /// 获取异常日志详情 🔖
    /// </summary>
    /// <returns></returns>
    [SuppressMonitor]
    [DisplayName("获取异常日志详情")]
    public async Task<SysLogEx> GetDetail(long id)
    {
        return await _sysLogExRep.GetFirstAsync(u => u.Id == id);
    }
    /// <summary>
    /// 清空异常日志 🔖
    /// </summary>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Clear"), HttpPost]
    [DisplayName("清空异常日志")]
    public void Clear()
    {
        _sysLogExRep.AsSugarClient().DbMaintenance.TruncateTable<SysLogEx>();
    }
    /// <summary>
    /// 导出异常日志 🔖
    /// </summary>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Export"), NonUnify]
    [DisplayName("导出异常日志")]
    public async Task<IActionResult> ExportLogEx(LogInput input)
    {
        var logExList = await _sysLogExRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()) && !string.IsNullOrWhiteSpace(input.EndTime.ToString()),
                    u => u.CreateTime >= input.StartTime && u.CreateTime <= input.EndTime)
            .OrderBy(u => u.CreateTime, OrderByType.Desc)
            .Select<ExportLogDto>().ToListAsync();
        IExcelExporter excelExporter = new ExcelExporter();
        var res = await excelExporter.ExportAsByteArray(logExList);
        return new FileStreamResult(new MemoryStream(res), "application/octet-stream") { FileDownloadName = DateTime.Now.ToString("yyyyMMddHHmm") + "异常日志.xlsx" };
    }
}
Admin.NET/Admin.NET.Core/Service/Log/SysLogOpService.cs
New file
@@ -0,0 +1,84 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 系统操作日志服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 360)]
public class SysLogOpService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<SysLogOp> _sysLogOpRep;
    public SysLogOpService(SqlSugarRepository<SysLogOp> sysLogOpRep)
    {
        _sysLogOpRep = sysLogOpRep;
    }
    /// <summary>
    /// 获取操作日志分页列表 🔖
    /// </summary>
    /// <returns></returns>
    [SuppressMonitor]
    [DisplayName("获取操作日志分页列表")]
    public async Task<SqlSugarPagedList<SysLogOp>> Page(PageOpLogInput input)
    {
        return await _sysLogOpRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime)
            .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Account), u => u.Account == input.Account)
            .WhereIF(!string.IsNullOrWhiteSpace(input.RemoteIp), u => u.RemoteIp == input.RemoteIp)
            .WhereIF(!string.IsNullOrWhiteSpace(input.ControllerName), u => u.ControllerName == input.ControllerName)
            .WhereIF(!string.IsNullOrWhiteSpace(input.ActionName), u => u.ActionName == input.ActionName)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Elapsed.ToString()), u => u.Elapsed >= input.Elapsed)
            //.OrderBy(u => u.CreateTime, OrderByType.Desc)
            .IgnoreColumns(u => new { u.RequestParam, u.ReturnResult, u.Message })
            .OrderBuilder(input)
            .ToPagedListAsync(input.Page, input.PageSize);
    }
    /// <summary>
    /// 获取操作日志详情 🔖
    /// </summary>
    /// <returns></returns>
    [SuppressMonitor]
    [DisplayName("获取操作日志详情")]
    public async Task<SysLogOp> GetDetail(long id)
    {
        return await _sysLogOpRep.GetFirstAsync(u => u.Id == id);
    }
    /// <summary>
    /// 清空操作日志 🔖
    /// </summary>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Clear"), HttpPost]
    [DisplayName("清空操作日志")]
    public void Clear()
    {
        _sysLogOpRep.AsSugarClient().DbMaintenance.TruncateTable<SysLogOp>();
    }
    /// <summary>
    /// 导出操作日志 🔖
    /// </summary>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Export"), NonUnify]
    [DisplayName("导出操作日志")]
    public async Task<IActionResult> ExportLogOp(LogInput input)
    {
        var logOpList = await _sysLogOpRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()) && !string.IsNullOrWhiteSpace(input.EndTime.ToString()),
                    u => u.CreateTime >= input.StartTime && u.CreateTime <= input.EndTime)
            .OrderBy(u => u.CreateTime, OrderByType.Desc)
            .Select<ExportLogDto>().ToListAsync();
        IExcelExporter excelExporter = new ExcelExporter();
        var res = await excelExporter.ExportAsByteArray(logOpList);
        return new FileStreamResult(new MemoryStream(res), "application/octet-stream") { FileDownloadName = DateTime.Now.ToString("yyyyMMddHHmm") + "操作日志.xlsx" };
    }
}
Admin.NET/Admin.NET.Core/Service/Log/SysLogVisService.cs
New file
@@ -0,0 +1,72 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 系统访问日志服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 340)]
public class SysLogVisService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<SysLogVis> _sysLogVisRep;
    public SysLogVisService(SqlSugarRepository<SysLogVis> sysLogVisRep)
    {
        _sysLogVisRep = sysLogVisRep;
    }
    /// <summary>
    /// 获取访问日志分页列表 🔖
    /// </summary>
    /// <returns></returns>
    [SuppressMonitor]
    [DisplayName("获取访问日志分页列表")]
    public async Task<SqlSugarPagedList<SysLogVis>> Page(PageVisLogInput input)
    {
        return await _sysLogVisRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.CreateTime >= input.StartTime)
            .WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.CreateTime <= input.EndTime)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Account), u => u.Account == input.Account)
            .WhereIF(!string.IsNullOrWhiteSpace(input.ActionName), u => u.ActionName == input.ActionName)
            .WhereIF(!string.IsNullOrWhiteSpace(input.RemoteIp), u => u.RemoteIp == input.RemoteIp)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Elapsed.ToString()), u => u.Elapsed >= input.Elapsed)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Status) && input.Status == "200", u => u.Status == input.Status)
            .WhereIF(!string.IsNullOrWhiteSpace(input.Status) && input.Status != "200", u => u.Status != input.Status)
            .OrderBy(u => u.CreateTime, OrderByType.Desc)
            .ToPagedListAsync(input.Page, input.PageSize);
    }
    /// <summary>
    /// 清空访问日志 🔖
    /// </summary>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Clear"), HttpPost]
    [DisplayName("清空访问日志")]
    public void Clear()
    {
        _sysLogVisRep.AsSugarClient().DbMaintenance.TruncateTable<SysLogVis>();
    }
    /// <summary>
    /// 获取访问日志列表 🔖
    /// </summary>
    /// <returns></returns>
    [DisplayName("获取访问日志列表")]
    public async Task<List<LogVisOutput>> GetList()
    {
        return await _sysLogVisRep.AsQueryable()
            .Where(u => u.Longitude > 0 && u.Longitude > 0)
            .Select(u => new LogVisOutput
            {
                Location = u.Location,
                Longitude = u.Longitude,
                Latitude = u.Latitude,
                RealName = u.RealName,
                LogDateTime = u.LogDateTime
            }).ToListAsync();
    }
}
Admin.NET/WCS.Application/Hub/PlcHub.cs
@@ -10,10 +10,11 @@
public class PlcHub : Hub<IPlcHub>
{
    private readonly IHubContext<PlcHub, IPlcHub> _plcHubContext;
    public PlcHub(IHubContext<PlcHub, IPlcHub> plcHubContext)
    private readonly SysConfigService _sysConfigService;
    public PlcHub(IHubContext<PlcHub, IPlcHub> plcHubContext, SysConfigService sysConfigService)
    {
        _plcHubContext = plcHubContext;
        _sysConfigService = sysConfigService;
    }
    /// <summary>
    /// 下发PLC连接状态
@@ -48,13 +49,20 @@
                PLCTaskAction.Init();
            else
                PLCTaskAction.Stop();
            await _sysConfigService.UpdateConfigValue("sys_RunningState", context.BoRunningState.Value);
        }
        //脱机模式
        if (context.BoOffline.HasValue)
        {
            PLCTaskAction.boOffline = context.BoOffline.Value;
            await _sysConfigService.UpdateConfigValue("sys_Offline", context.BoOffline.Value);
        }
        //自刷新
        if (context.BoRefresh.HasValue)
        {
            PLCTaskAction.boRefresh = context.BoRefresh.Value;
            await _sysConfigService.UpdateConfigValue("sys_Refresh", context.BoRefresh.Value);
        }
        await _plcHubContext.Clients.All.UpdateService(new PLCServiceModel() { BoRunningState = PLCTaskAction.boRunningState, BoRefresh = PLCTaskAction.boRefresh, BoOffline = PLCTaskAction.boOffline });
    }
}
Admin.NET/WCS.Application/PLC/PLCTaskAction.cs
@@ -19,6 +19,7 @@
    private static readonly ISqlSugarClient _db = SqlSugarSetup.ITenant.GetConnectionScope(SqlSugarConst.MainConfigId);
    private static readonly SysCacheService sysCacheService = App.GetRequiredService<SysCacheService>();
    private static readonly IHubContext<PlcHub, IPlcHub> _plcHubContext = App.GetService<IHubContext<PlcHub, IPlcHub>>();
    private static readonly SysConfigService _sysConfigService = App.GetService<SysConfigService>();
    private static List<WcsPlc> listPlc = new List<WcsPlc>();
    private static List<WcsDevice> listPlcDevice = new List<WcsDevice>();
@@ -37,6 +38,9 @@
    {
        //订阅事件
        DeviceValueChangeEvent += PLCService.OnChangeEvent;
        boRunningState = _sysConfigService.GetConfigValue<bool>("sys_RunningState").Result;
        boOffline = _sysConfigService.GetConfigValue<bool>("sys_Offline").Result;
        boRefresh = _sysConfigService.GetConfigValue<bool>("sys_Refresh").Result;
    }
    /// <summary>
    /// 初始化PLC连接
@@ -60,18 +64,20 @@
            var plc = new PLCUtil(modPlc);
            listPlcUtil.Add(plc);
        }
        cts = new CancellationTokenSource();
        boRunningState = true;
        _plcHubContext.Clients.All.UpdateService(new PLCServiceModel()
        {
            BoRunningState = boRunningState,
            BoOffline = boOffline,
            BoRefresh = boRefresh
        });
        StartRead();
        ConnectionStatus();
        StartWatchAlarm();
        StartWatchPosition();
        if (boRunningState)
        {
            cts = new CancellationTokenSource();
            StartRead();
            ConnectionStatus();
            StartWatchAlarm();
            StartWatchPosition();
        }
    }
    /// <summary>
    /// 开启读取plc线程
Admin.NET/WCS.Application/PLC/PLCUtil.cs
@@ -53,41 +53,55 @@
                address = DbNumber + "." + Pos;
            else
                address = DbNumber + Pos;
            return this.GetPlcDBValue(PosType, address, Length);
        }
    }
    /// <summary>
    /// 读取PLC值
    /// </summary>
    /// <param name="PosType">字符类型</param>
    /// <param name="Pos">偏移量/地址</param>
    /// <param name="Length">长度(字符串)</param>
    /// <returns></returns>
    public (IoTClient.Result, dynamic value) GetPlcDBValue(PLCDataTypeEnum PosType, string Pos, int? Length = 0)
    {
        lock (OLock)
        {
            dynamic result = null;
            switch (PosType)
            {
                case PLCDataTypeEnum.Bit:
                    result = _client.ReadBoolean(address);
                    result = _client.ReadBoolean(Pos);
                    break;
                case PLCDataTypeEnum.Byte:
                    result = _client.ReadByte(address);
                    result = _client.ReadByte(Pos);
                    break;
                case PLCDataTypeEnum.Short:
                    result = _client.ReadInt16(address);
                    result = _client.ReadInt16(Pos);
                    break;
                case PLCDataTypeEnum.UShort:
                    result = _client.ReadUInt16(address);
                    result = _client.ReadUInt16(Pos);
                    break;
                case PLCDataTypeEnum.Int:
                    result = _client.ReadInt32(address);
                    result = _client.ReadInt32(Pos);
                    break;
                case PLCDataTypeEnum.UInt:
                    result = _client.ReadUInt32(address);
                    result = _client.ReadUInt32(Pos);
                    break;
                case PLCDataTypeEnum.Long:
                    result = _client.ReadInt64(address);
                    result = _client.ReadInt64(Pos);
                    break;
                case PLCDataTypeEnum.ULong:
                    result = _client.ReadUInt64(address);
                    result = _client.ReadUInt64(Pos);
                    break;
                case PLCDataTypeEnum.Float:
                    result = _client.ReadFloat(address);
                    result = _client.ReadFloat(Pos);
                    break;
                case PLCDataTypeEnum.Double:
                    result = _client.ReadDouble(address);
                    result = _client.ReadDouble(Pos);
                    break;
                case PLCDataTypeEnum.String:
                    result = _client.ReadString(address, Convert.ToUInt16(Length));
                    result = _client.ReadString(Pos, Convert.ToUInt16(Length));
                    break;
                default:
                    result = new IoTClient.Result<object>();
@@ -159,6 +173,13 @@
            address = DbNumber + "." + Pos;
        else
            address = DbNumber + Pos;
        return this.SetPlcDBValue(PosType, address, Pos, Value);
    }
    /// <summary>
    /// 写入PLC值
    /// </summary>
    public IoTClient.Result SetPlcDBValue(PLCDataTypeEnum PosType, string Pos, string Value)
    {
        switch (PosType)
        {
            case PLCDataTypeEnum.Bit:
@@ -173,25 +194,25 @@
                        throw new Exception("写入值错误");
                    }
                }
                return _client.Write(address, bit);
                return _client.Write(Pos, bit);
            case PLCDataTypeEnum.Byte:
                return _client.Write(address, byte.Parse(Value));
                return _client.Write(Pos, byte.Parse(Value));
            case PLCDataTypeEnum.Short:
                return _client.Write(address, short.Parse(Value));
                return _client.Write(Pos, short.Parse(Value));
            case PLCDataTypeEnum.UShort:
                return _client.Write(address, ushort.Parse(Value));
                return _client.Write(Pos, ushort.Parse(Value));
            case PLCDataTypeEnum.Int:
                return _client.Write(address, int.Parse(Value));
                return _client.Write(Pos, int.Parse(Value));
            case PLCDataTypeEnum.UInt:
                return _client.Write(address, uint.Parse(Value));
                return _client.Write(Pos, uint.Parse(Value));
            case PLCDataTypeEnum.Long:
                return _client.Write(address, long.Parse(Value));
                return _client.Write(Pos, long.Parse(Value));
            case PLCDataTypeEnum.ULong:
                return _client.Write(address, ulong.Parse(Value));
                return _client.Write(Pos, ulong.Parse(Value));
            case PLCDataTypeEnum.Float:
                return _client.Write(address, float.Parse(Value));
                return _client.Write(Pos, float.Parse(Value));
            case PLCDataTypeEnum.Double:
                return _client.Write(address, float.Parse(Value));
                return _client.Write(Pos, float.Parse(Value));
            default:
                return new IoTClient.Result();
        }
Admin.NET/WCS.Application/Service/Config/SysConfigService.cs
@@ -107,4 +107,21 @@
        _sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}");
    }
    /// <summary>
    /// 更新参数配置值
    /// </summary>
    /// <param name="code"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    [NonAction]
    public async Task UpdateConfigValue(string code, bool value)
    {
        var config = await _sysConfigRep.GetFirstAsync(u => u.Code == code);
        if (config == null) return;
        config.Value = value?"True":"False";
        await _sysConfigRep.AsUpdateable(config).ExecuteCommandAsync();
        _sysCacheService.Remove($"{CacheConst.KeyConfig}{config.Code}");
    }
}
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoInput.cs
@@ -200,3 +200,9 @@
{
}
public class ResetInput
{
    [Required(ErrorMessage = "楼层不能为空")]
    public int layer { get; set; }
}
Admin.NET/WCS.Application/Service/WcsAlarmInfo/WcsAlarmInfoService.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.SignalR;
using Admin.NET.Core.Service;
using Microsoft.AspNetCore.SignalR;
namespace WCS.Application;
@@ -10,10 +11,12 @@
{
    private readonly SqlSugarRepository<WcsAlarmInfo> _wcsAlarmInfoRep;
    private readonly IHubContext<PlcHub, IPlcHub> _plcHubContext;
    public WcsAlarmInfoService(SqlSugarRepository<WcsAlarmInfo> wcsAlarmInfoRep, IHubContext<PlcHub, IPlcHub> plcHubContext)
    private readonly SysDictDataService _dictDataService;
    public WcsAlarmInfoService(SqlSugarRepository<WcsAlarmInfo> wcsAlarmInfoRep, IHubContext<PlcHub, IPlcHub> plcHubContext, SysDictDataService dictDataService)
    {
        _wcsAlarmInfoRep = wcsAlarmInfoRep;
        _plcHubContext = plcHubContext;
        _dictDataService = dictDataService;
    }
    /// <summary>
@@ -121,13 +124,35 @@
    [HttpPost]
    [ApiDescriptionSettings(Name = "Reset")]
    [DisplayName("复位报警")]
    public async Task Reset()
    public async Task Reset(ResetInput input)
    {
        //测试推数据用的
        await _plcHubContext.Clients.All.PublicAlarm(new List<WcsAlarmInfo>() { new WcsAlarmInfo() { Id = 100, StationNum = "260", AlarmCode = "MB102", AlarmName = "有物品遮挡", AlarmTime = DateTime.Now, Status = YesNoEnum.N } });
        //await _plcHubContext.Clients.All.PublicAlarm(new List<WcsAlarmInfo>() { new WcsAlarmInfo() { Id = 100, StationNum = "260", AlarmCode = "MB102", AlarmName = "有物品遮挡", AlarmTime = DateTime.Now, Status = YesNoEnum.Y } });
        //await _plcHubContext.Clients.All.PublicAlarm(new WcsAlarmInfo() { Id = 100, StationNum = "260", AlarmCode = "MB102", AlarmName = "有物品遮挡", AlarmTime = DateTime.Now, Status = YesNoEnum.Y });
        var modPlc = await _wcsAlarmInfoRep.Context.Queryable<WcsPlc>().Where(s => s.Type == PLCTypeEnum.ConveyorLine && s.Text == (input.layer.ToString() + "层托盘输送线")).FirstAsync();
        if (modPlc == null)
            throw Oops.Bah("未找到输送线PLC");
        var listDict = await _dictDataService.GetDataList("reset_alarm");
        var value = listDict.FirstOrDefault(s => s.Code == input.layer.ToString());
        if (value == null)
            throw Oops.Bah("未找到复位地址,请在字典管理中设置");
        PLCUtil modUtil = new PLCUtil(modPlc);
        switch (input.layer)
        {
            case 1:
                break;
            case 2:
                {
                    modUtil.SetPlcDBValue(PLCDataTypeEnum.Bit, value.Value, "1");
                }
                break;
            case 3:
                break;
            default:
                break;
        }
        modUtil.Close();
        //throw Oops.Bah("开发中");
    }
Web/src/views/device/alarmManage/index.vue
@@ -18,9 +18,9 @@
            </el-col>
            <el-col :span="19">
                <div class="card-page">
                    <el-button type="primary" :plain="isPlain1" @click="floorTogglePlain(1)">一层平面</el-button>
                    <el-button type="primary" :plain="isPlain2" @click="floorTogglePlain(2)">二层平面</el-button>
                    <el-button type="primary" :plain="isPlain3" @click="floorTogglePlain(3)">三层平面</el-button>
                    <el-button type="primary" :plain="layer != 1" @click="floorTogglePlain(1)">一层平面</el-button>
                    <el-button type="primary" :plain="layer != 2" @click="floorTogglePlain(2)">二层平面</el-button>
                    <el-button type="primary" :plain="layer != 3" @click="floorTogglePlain(3)">三层平面</el-button>
                </div>
                <div style="margin: 40px; height: 50%;">
                    <div class="grid-container-line">
@@ -56,6 +56,7 @@
import 'splitpanes/dist/splitpanes.css';
import { listWcsAlarmInfo, resetWcsAlarmInfo } from '/@/api/wcs/wcsAlarmInfo';
import { signalR } from './signalR';
import { ElMessageBox, ElMessage } from "element-plus";
//连接signalR 监听变更
onMounted(async () => {
    signalR.off('PublicAlarm');
@@ -1950,32 +1951,22 @@
//复位报警
const reset = async () => {
    await resetWcsAlarmInfo();
    await resetWcsAlarmInfo({ layer: layer.value });
    ElMessage.success("复位成功");
    // await handleQuery();
}
const isPlain1 = ref(false);//一层
const isPlain2 = ref(true);//二层
const isPlain3 = ref(true);//三层
const layer = ref(1);
//切换层平面
function floorTogglePlain(buttonNumber) {
    layer.value = buttonNumber;
    if (buttonNumber === 1) {
        isPlain1.value = false;
        isPlain2.value = true;
        isPlain3.value = true;
        //切换输送线数据
        cellsData.value = cellsDataOne.value;
    } else if (buttonNumber === 2) {
        isPlain2.value = false;
        isPlain1.value = true;
        isPlain3.value = true;
        //切换输送线数据
        cellsData.value = cellsDataTwo.value;
    } else if (buttonNumber === 3) {
        isPlain3.value = false;
        isPlain1.value = true;
        isPlain2.value = true;
        //切换输送线数据
        cellsData.value = cellsDataThree.value;
    }
Web/src/views/device/deviceMonitor/component/setting.vue
New file
@@ -0,0 +1,198 @@
<template>
    <div class="wcsAlarmInfo-container">
        <el-dialog v-model="isShowDialog" :width="800" draggable="" :close-on-click-modal="false">
            <template #header>
                <div class="dialog-header">
                    <span>{{ props.title }}</span>
                </div>
            </template>
            <el-card class="box-card" shadow="hover">
                <template #header>
                    <div class="card-header">
                        <div>
                            <span>工位号:</span>
                            <el-select v-model="stationValue" placeholder="请选择" filterable value-key="stationNum"
                                style="width: 200px;">
                                <el-option v-for="item in listStationsData" :key="item.id" :label="item.stationNum"
                                    :value="item">
                                </el-option>
                            </el-select>
                            <el-button style="margin-left: 10px;">自动</el-button>
                        </div>
                        <div>
                            <div :class="['lineStatus', stationValue.status ? 'device-status-0' : 'device-status-1']"></div>
                        </div>
                    </div>
                </template>
                <div>
                    <el-form label-position="left" label-width="80px">
                        <el-row :gutter="25">
                            <el-col :span="12">
                                <el-form-item label="起始工位">
                                    <el-input v-model="stationValue.startLocatNo"></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="目的工位">
                                    <el-input v-model="stationValue.endLocatNo"></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="PLC">
                                    <el-input v-model="stationValue.plc"></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="WCS">
                                    <el-input v-model="stationValue.wcs"></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="取货排">
                                    <el-input ></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="放货排">
                                    <el-input ></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="取货列">
                                    <el-input ></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="放货列">
                                    <el-input ></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="取货层">
                                    <el-input ></el-input>
                                </el-form-item>
                            </el-col>
                            <el-col :span="12">
                                <el-form-item label="放货层">
                                    <el-input ></el-input>
                                </el-form-item>
                            </el-col>
                        </el-row>
                    </el-form>
                    <div style="text-align: center;margin: 10px;">
                        <el-button class="button" size="large">写入数据</el-button>
                    </div>
                </div>
                <template #footer>
                    <div class="card-footer">
                        <el-button class="button" size="large">正&nbsp;&nbsp;&nbsp;&nbsp;转</el-button>
                        <el-button class="button" size="large">反&nbsp;&nbsp;&nbsp;&nbsp;转</el-button>
                        <el-button class="button" size="large">移栽上升</el-button>
                        <el-button class="button" size="large">移栽下降</el-button>
                    </div>
                </template>
            </el-card>
            <!-- <template #footer>
                <span class="dialog-footer">
                    <el-button @click="closeDialog" type="primary">关 闭</el-button>
                </span>
            </template> -->
        </el-dialog>
    </div>
</template>
<script lang="ts" setup>
import { ref, defineModel } from 'vue';
const listStationsData = defineModel<any>("listStationsData")
const stationValue = defineModel<any>("stationValue")
const props = defineProps({
    title: {
        type: String,
        default: '',
    }
});
// const emit = defineEmits<{ (e: "update:listStationsData", value: Array<any>): void, (e: "update:stationValue", value): void }>()
// const handlestationChange = () => {
//     emit('update:listStationsData', listStationsData.value);
//     emit('update:stationValue', stationValue);
// };
const isShowDialog = ref(false);
const openDialog = async (row: any) => {
    console.log(listStationsData.value);
    debugger;
    isShowDialog.value = true;
};
const closeDialog = () => {
    isShowDialog.value = false;
};
defineExpose({ openDialog });
</script>
<style>
.el-col {
    margin-bottom: 15px;
}
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: #fff;
    padding: 10px;
    border-radius: 10px 10px 0 0;
}
.card-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.text {
    font-size: 14px;
    color: #fff;
}
.item {
    margin-bottom: 18px;
}
.box-card {
    width: 100%;
    background: linear-gradient(135deg, #66ccff, #3399ff);
    border-radius: 10px;
    color: #fff;
}
.dialog-footer {
    display: flex;
    justify-content: flex-end;
    padding: 10px;
}
.lineStatus {
    right: 0;
    height: 20px;
    width: 20px;
    border-radius: 50%;
    background-color: #67C23A;
}
.device-status-0 {
    background-color: #67C23A;
}
.device-status-1 {
    background-color: red;
}
</style>
Web/src/views/device/deviceMonitor/index.vue
@@ -11,7 +11,7 @@
                        <el-option v-for="item in lineOptions" :key="item.id" :label="item.text"
                            :value="item.id"></el-option>
                    </el-select>
                    <el-select v-model="stationValue" placeholder="请选择" style="margin-top: 10px;"
                    <el-select v-model="stationValue" placeholder="请选择" style="margin-top: 10px;" filterable
                        value-key="stationNum">
                        <el-option v-for="item in listStationsData" :key="item.id" :label="item.stationNum"
                            :value="item">
@@ -53,7 +53,7 @@
                    <el-form label-position="left" label-width="80px">
                        <el-form-item>
                            <el-button>写入</el-button>
                            <el-button>设置</el-button>
                            <el-button @click="openDialog">设置</el-button>
                        </el-form-item>
                    </el-form>
                </div>
@@ -100,13 +100,13 @@
                        <el-form label-position="left">
                            <el-form-item>
                                <el-button>写入</el-button>
                                <el-button>设置</el-button>
                            </el-form-item>
                        </el-form>
                    </div>
                </el-card>
            </div>
        </el-main>
        <setting ref="settingDialogRef" :title="title" v-model:listStationsData="listStationsData" v-model:stationValue="stationValue" />
    </el-container>
</template>
@@ -116,11 +116,14 @@
import { getDictDataItem as di, getDictDataList as dl } from '/@/utils/dict-utils';
import { listWcsPlc } from '/@/api/wcs/wcsPlc';
import setting from '/@/views/device/deviceMonitor/component/setting.vue'
const stations = ref<any>([]);
const listStationsData = ref<any>([]);
const listStackingMachineData = ref<any>([]);
const lineOptions = ref<any>([]);
const lineValue = ref(1);
const title = ref<string>('');
const stationValue = ref<any>({
    taskNo: '',
    taskType: '',
@@ -130,7 +133,11 @@
    wcs: '',
    status: false
});
const settingDialogRef = ref();
// 打开打印页面
const openDialog = async () => {
    settingDialogRef.value.openDialog(stationValue);
}
// 查询操作
const handleQuery = async () => {
    var listplc = await listWcsPlc({ type: 1 });
@@ -146,11 +153,14 @@
        listStationsData.value = stations.value[lineValue.value];
        stationValue.value = listStationsData.value[0];
    }
    title.value = listStationsData.value[0].text;
};
handleQuery();
const handleLineChange = (field: string, value: number) => {
    listStationsData.value = stations.value[value];
    title.value = lineOptions.value.filter(s => s.id == value)[0].text;
    if (listStationsData.value.length > 0)
        stationValue.value = listStationsData.value[0];
};
@@ -166,6 +176,12 @@
    margin-bottom: 10px;
}
.el-button {
    background-color: #fff;
    border-color: #fff;
    color: #000000;
}
.linefix {
    border-bottom: 1px solid rgb(197, 195, 195);
    display: flex;