wxw
2024-08-30 d9e59ed2e947aff29b7c21a9b0e8b6e12d741692
解决冲突
17个文件已修改
4个文件已添加
856 ■■■■ 已修改文件
Admin.NET/WCS.Application/Entity/WcsAlarmInfo.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Entity/WcsPlc.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Hub/IPlcHub.cs 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Hub/PlcHub.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Hub/TaskLogHub.cs 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/PLC/PLCTaskAction.cs 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/PLC/PLCUtil.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoDto.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoInput.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoOutput.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/WcsAlarmInfo/WcsAlarmInfoService.cs 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Service/WcsPlc/WcsPlcService.cs 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/api/wcs/wcsAlarmInfo.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/api/wcs/wcsPlc.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/device/alarmManage/index.vue 230 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/device/alarmManage/signalR.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/device/deviceInfo/index.vue 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/device/deviceInfo/signalR.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/wcs/wcsAlarmInfo/component/editDialog.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/wcs/wcsAlarmInfo/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Web/src/views/wcs/wcsTask/signalR.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Admin.NET/WCS.Application/Entity/WcsAlarmInfo.cs
@@ -21,6 +21,13 @@
    [SugarColumn(ColumnName = "PlcPort", ColumnDescription = "PLCIP地址")]
    public int PlcPort { get; set; }
    
    /// <summary>
    /// 工位号
    /// </summary>
    [Required]
    [SugarColumn(ColumnName = "StationNum", ColumnDescription = "工位号", Length = 4)]
    public string StationNum { get; set; }
    /// <summary>
    /// 报警编号
    /// </summary>
Admin.NET/WCS.Application/Entity/WcsPlc.cs
@@ -1,4 +1,6 @@

using Org.BouncyCastle.Crypto;
namespace WCS.Application;
/// <summary>
@@ -46,4 +48,9 @@
    [SugarColumn(ColumnName = "Text", ColumnDescription = "描述", Length = 100)]
    public string? Text { get; set; }
    
    /// <summary>
    /// 是否连接
    /// </summary>
    [SugarColumn(IsIgnore = true)]
    public bool IsConn { get; set; } = false;
}
Admin.NET/WCS.Application/Hub/IPlcHub.cs
New file
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WCS.Application
{
    public interface IPlcHub
    {
        /// <summary>
        /// 下发PLC连接状态
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        Task PublicPlcConn(WcsPlc context);
        /// <summary>
        /// 下发工位状态
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        //Task PublicStationStatus(List<WcsDevice> context);
        /// <summary>
        /// 下发报警信息
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        Task PublicAlarm(WcsAlarmInfoOutput context);
    }
}
Admin.NET/WCS.Application/Hub/PlcHub.cs
New file
@@ -0,0 +1,47 @@
using Furion.InstantMessaging;
using Microsoft.AspNetCore.SignalR;
namespace WCS.Application;
/// <summary>
/// PLC集线器
/// </summary>
[MapHub("/hubs/Plc")]
public class PlcHub : Hub<IPlcHub>
{
    private readonly IHubContext<PlcHub, IPlcHub> _plcHubContext;
    public PlcHub(IHubContext<PlcHub, IPlcHub> plcHubContext)
    {
        _plcHubContext = plcHubContext;
    }
    /// <summary>
    /// 下发PLC连接状态
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    //public async Task PublicPlcConn(WcsPlc context)
    //{
    //    await _plcHubContext.Clients.All.PublicPlcConn(context);
    //}
    /// <summary>
    /// 下发工位状态
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    //public async Task PublicStationStatus(List<WcsDevice> context)
    //{
    //    await _plcHubContext.Clients.All.PublicStationStatus(context);
    //}
    /// <summary>
    /// 下发报警信息
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    //public async Task PublicAlarm(WcsAlarmInfoOutput context)
    //{
    //    await _plcHubContext.Clients.All.PublicAlarm(context);
    //}
}
Admin.NET/WCS.Application/Hub/TaskLogHub.cs
@@ -16,41 +16,41 @@
        _taskLogHubContext = taskLogHubContext;
    }
    /// <summary>
    /// 连接
    /// </summary>
    /// <returns></returns>
    public override async Task OnConnectedAsync()
    {
        await base.OnConnectedAsync();
    }
    ///// <summary>
    ///// 连接
    ///// </summary>
    ///// <returns></returns>
    //public override async Task OnConnectedAsync()
    //{
    //    await base.OnConnectedAsync();
    //}
    /// <summary>
    /// 断开
    /// </summary>
    /// <param name="exception"></param>
    /// <returns></returns>
    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await base.OnDisconnectedAsync(exception);
    }
    ///// <summary>
    ///// 断开
    ///// </summary>
    ///// <param name="exception"></param>
    ///// <returns></returns>
    //public override async Task OnDisconnectedAsync(Exception exception)
    //{
    //    await base.OnDisconnectedAsync(exception);
    //}
    /// <summary>
    /// 下发任务
    /// </summary>
    /// <returns></returns>
    public async Task PublicTask(WcsTaskOutput context)
    {
        await _taskLogHubContext.Clients.All.PublicTask(context);
    }
    ///// <summary>
    ///// 下发任务
    ///// </summary>
    ///// <returns></returns>
    //public async Task PublicTask(WcsTaskOutput context)
    //{
    //    await _taskLogHubContext.Clients.All.PublicTask(context);
    //}
    /// <summary>
    /// 下发任务明细
    /// </summary>
    /// <returns></returns>
    public async Task PublicTaskMonitor(WcsTaskMonitorOutput context)
    {
        await _taskLogHubContext.Clients.All.PublicTaskMonitor(context);
    }
    ///// <summary>
    ///// 下发任务明细
    ///// </summary>
    ///// <returns></returns>
    //public async Task PublicTaskMonitor(WcsTaskMonitorOutput context)
    //{
    //    await _taskLogHubContext.Clients.All.PublicTaskMonitor(context);
    //}
}
Admin.NET/WCS.Application/PLC/PLCTaskAction.cs
@@ -1,4 +1,5 @@
using Furion.Logging;
using Admin.NET.Core.Service;
using Furion.Logging;
using Microsoft.AspNetCore.SignalR;
namespace WCS.Application;
@@ -6,8 +7,14 @@
{
    //服务运行状态
    public static bool boRunningState = false;
    //脱机模式
    public static bool boOffline = false;
    //自刷新
    public static bool boRefresh = false;
    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 List<WcsPlc> listPlc;
    private static List<WcsDevice> listPlcDevice;
@@ -34,7 +41,7 @@
        if (listPlcUtil.Count != 0)
        {
            cts.Cancel();
            listPlc = _db.Queryable<WcsPlc>().ToList();
            listPlc = _db.Queryable<WcsPlc>().Where(s => s.Type == PLCTypeEnum.StackingMachine || s.Type == PLCTypeEnum.ConveyorLine || s.Type == PLCTypeEnum.BoxConveyorLine).ToList();
            listPlcDevice = _db.Queryable<WcsDevice>().ToList();
            listPlcStation = _db.Queryable<WcsPosition>().ToList();
            //等待几秒钟,把已有线程取消掉再连接
@@ -125,6 +132,59 @@
            }, cts.Token);
        }
    }
    /// <summary>
    /// 连接状态线程
    /// </summary>
    public static void ConnectionStatus()
    {
        Task.Run(() =>
        {
            try
            {
                //取消线程 关闭PLC连接
                if (cts.Token.IsCancellationRequested)
                {
                    foreach (var modPlcUtil in listPlcUtil)
                    {
                        modPlcUtil.Close();
                    }
                    throw new OperationCanceledException();
                }
                //获取每个PLC连接状态
                foreach (var modPlc in listPlc)
                {
                    var modPlcUtil = listPlcUtil.FirstOrDefault(s => s.PlcId == modPlc.Id);
                    if (modPlcUtil == null)
                        modPlc.IsConn = false;
                    else
                        modPlc.IsConn = modPlcUtil.Connected;
                    if (sysCacheService.ExistKey("PLCCONN" + modPlc.Id))
                    {
                        var cachePlc = sysCacheService.Get<WcsPlc>("PLCCONN" + modPlc.Id);
                        if (cachePlc.IsConn != modPlc.IsConn)
                        {
                            //连接状态变更 通知前端
                            _plcHubContext.Clients.All.PublicPlcConn(modPlc);
                        }
                    }
                    sysCacheService.Set("PLCCONN" + modPlc.Id, modPlc);
                }
                Thread.Sleep(3000);
            }
            catch (OperationCanceledException)
            {
                sysCacheService.RemoveByPrefixKey("PLCCONN");
                Console.WriteLine("中止线程");
            }
            catch (Exception ex)
            {
                Log.Error(ex.Message, ex);
            }
        });
    }
    /// <summary>
    /// 停止服务
    /// </summary>
Admin.NET/WCS.Application/PLC/PLCUtil.cs
@@ -21,9 +21,9 @@
        _client = new SiemensClient((SiemensVersion)modPlc.PLCType, modPlc.IP, modPlc.Port);
        _client.Open();
    }
    public bool Connected()
    public bool Connected
    {
        return _client.Connected;
        get { return _client.Connected; }
    }
    public IoTClient.Result Open()
    {
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoDto.cs
@@ -22,6 +22,11 @@
    public int PlcPort { get; set; }
    /// <summary>
    /// 工位号
    /// </summary>
    public string StationNum { get; set; }
    /// <summary>
    /// 报警编号
    /// </summary>
    public string AlarmCode { get; set; }
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoInput.cs
@@ -19,6 +19,11 @@
    /// <summary>
    /// 报警编号
    /// </summary>
    public virtual string StationNum { get; set; }
    /// <summary>
    /// 报警编号
    /// </summary>
    public virtual string AlarmCode { get; set; }
    /// <summary>
@@ -143,6 +148,12 @@
    public override int PlcPort { get; set; }
    /// <summary>
    /// 工位号
    /// </summary>
    [Required(ErrorMessage = "工位号不能为空")]
    public override string StationNum { get; set; }
    /// <summary>
    /// 报警编号
    /// </summary>
    [Required(ErrorMessage = "报警编号不能为空")]
Admin.NET/WCS.Application/Service/WcsAlarmInfo/Dto/WcsAlarmInfoOutput.cs
@@ -21,6 +21,11 @@
    public int PlcPort { get; set; }
    /// <summary>
    /// 工位号
    /// </summary>
    public string StationNum { get; set; }
    /// <summary>
    /// 报警编号
    /// </summary>
    public string AlarmCode { get; set; }
Admin.NET/WCS.Application/Service/WcsAlarmInfo/WcsAlarmInfoService.cs
@@ -1,4 +1,6 @@
namespace WCS.Application;
using Microsoft.AspNetCore.SignalR;
namespace WCS.Application;
/// <summary>
/// 报警信息表服务
@@ -7,9 +9,12 @@
public class WcsAlarmInfoService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<WcsAlarmInfo> _wcsAlarmInfoRep;
    public WcsAlarmInfoService(SqlSugarRepository<WcsAlarmInfo> wcsAlarmInfoRep)
    private readonly IHubContext<PlcHub, IPlcHub> _plcHubContext;
    public WcsAlarmInfoService(SqlSugarRepository<WcsAlarmInfo> wcsAlarmInfoRep, IHubContext<PlcHub, IPlcHub> plcHubContext)
    {
        _wcsAlarmInfoRep = wcsAlarmInfoRep;
        _plcHubContext = plcHubContext;
    }
    /// <summary>
@@ -102,11 +107,27 @@
    [DisplayName("获取报警信息表列表")]
    public async Task<List<WcsAlarmInfoOutput>> List([FromQuery] PageWcsAlarmInfoInput input)
    {
        return await _wcsAlarmInfoRep.AsQueryable().Select<WcsAlarmInfoOutput>().ToListAsync();
        return await _wcsAlarmInfoRep.AsQueryable()
            //.WhereIF(!input.Status.IsNullOrEmpty(), s => s.Status == input.Status)
            .Select<WcsAlarmInfoOutput>().ToListAsync();
    }
    /// <summary>
    /// 复位报警
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [ApiDescriptionSettings(Name = "Reset")]
    [DisplayName("复位报警")]
    public async Task Reset()
    {
        //测试推数据用的
        await _plcHubContext.Clients.All.PublicAlarm(new WcsAlarmInfoOutput() { Id = new Random().Next(), StationNum = "205", AlarmCode = "MB102", AlarmName = "有物品遮挡", AlarmTime = DateTime.Now });
        //throw Oops.Bah("开发中");
    }
}
Admin.NET/WCS.Application/Service/WcsPlc/WcsPlcService.cs
@@ -1,4 +1,6 @@

using Admin.NET.Core.Service;
namespace WCS.Application;
/// <summary>
@@ -8,9 +10,11 @@
public class WcsPlcService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<WcsPlc> _wcsPlcRep;
    public WcsPlcService(SqlSugarRepository<WcsPlc> wcsPlcRep)
    private readonly SysCacheService _sysCacheService;
    public WcsPlcService(SqlSugarRepository<WcsPlc> wcsPlcRep, SysCacheService sysCacheService)
    {
        _wcsPlcRep = wcsPlcRep;
        _sysCacheService = sysCacheService;
    }
    /// <summary>
@@ -105,6 +109,34 @@
        return await _wcsPlcRep.AsQueryable().Select<WcsPlcOutput>().ToListAsync();
    }
    /// <summary>
    /// 获取PLC连接状态和服务状态
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpGet]
    [ApiDescriptionSettings(Name = "ListStatus")]
    [DisplayName("获取PLC连接状态")]
    public async Task<dynamic> ListStatus([FromQuery] PageWcsPlcInput input)
    {
        var listPlc = await _wcsPlcRep.AsQueryable()
            .Where(s => s.Type == PLCTypeEnum.StackingMachine || s.Type == PLCTypeEnum.ConveyorLine || s.Type == PLCTypeEnum.BoxConveyorLine)
            .ToListAsync();
        foreach (var modPlc in listPlc)
        {
            if (_sysCacheService.ExistKey("PlcConn" + modPlc.Id))
            {
                var cachePlc = _sysCacheService.Get<WcsPlc>("PlcConn" + modPlc.Id);
                modPlc.IsConn = cachePlc.IsConn;
            }
            else
            {
                modPlc.IsConn = false;
            }
        }
        //服务状态
        var modService = new { PLCTaskAction.boRunningState, PLCTaskAction.boOffline, PLCTaskAction.boRefresh };
        return new { listPlc, modService };
    }
}
Web/src/api/wcs/wcsAlarmInfo.ts
@@ -5,6 +5,8 @@
  UpdateWcsAlarmInfo = '/api/wcsAlarmInfo/update',
  PageWcsAlarmInfo = '/api/wcsAlarmInfo/page',
  DetailWcsAlarmInfo = '/api/wcsAlarmInfo/detail',
  ListWcsAlarmInfo = '/api/wcsAlarmInfo/list',
  ResetWcsAlarmInfo = '/api/wcsAlarmInfo/reset',
}
// 增加报警信息表
@@ -47,4 +49,18 @@
            data: { id },
        });
// 报警信息列表
export const listWcsAlarmInfo = (params?: any) =>
    request({
            url: Api.ListWcsAlarmInfo,
            method: 'get',
            data: params,
        });
// 复位
export const resetWcsAlarmInfo = (params?: any) =>
    request({
            url: Api.ResetWcsAlarmInfo,
            method: 'post',
            data: params,
        });
Web/src/api/wcs/wcsPlc.ts
@@ -5,6 +5,7 @@
  UpdateWcsPlc = '/api/wcsPlc/update',
  PageWcsPlc = '/api/wcsPlc/page',
  DetailWcsPlc = '/api/wcsPlc/detail',
  ListStatus='/api/wcsPlc/ListStatus'
}
// 增加PLC
@@ -48,3 +49,10 @@
        });
// 获取PLC连接状态和服务状态
export const listStatus = () =>
    request({
            url: Api.ListStatus,
            method: 'get'
        });
Web/src/views/device/alarmManage/index.vue
@@ -1,32 +1,31 @@
<template>
    <div class="sys-user-container">
      <splitpanes>
        <pane size="22">
        <el-row>
            <el-col :span="5">
            <el-row style="display: flex;text-align: center;justify-content: center;height: 40px;">
                <el-button type="warning" style="width: 95%;height: 30px;">报警复位</el-button>
                    <el-button type="warning" style="width: 95%;height: 30px;" @click="reset">报警复位</el-button>
            </el-row>
            <el-table :data="tableData" border style="width: 100%" :default-sort = "{prop: 'date', order: 'descending'}">
                <el-table-column prop="variable" label="变量" sortable width="80"></el-table-column>
                <el-table-column prop="desc" label="描述" width="120"></el-table-column>
                <el-table-column prop="address" label="位置" width="80"></el-table-column>
                <el-table-column prop="date" label="时间" sortable width="80"></el-table-column>
                <el-table :data="paginatedData" border style="width: 100%" v-loading="loading"
                    :default-sort="{ prop: 'date', order: 'descending' }">
                    <el-table-column prop="alarmCode" label="变量" align="center"></el-table-column>
                    <el-table-column prop="alarmName" label="描述" align="center"></el-table-column>
                    <el-table-column prop="stationNum" label="位置" align="center"></el-table-column>
                    <el-table-column prop="alarmTime" label="时间" align="center"></el-table-column>
            </el-table>
        </pane>
        <pane size="78" style="position: relative;background-color: #fff;">
            <el-card shadow="hover" :body-style="{ paddingBottom: '0', padding: '10px' }">
                <el-pagination v-model:currentPage="tableParams.page" v-model:page-size="tableParams.pageSize"
                    :total="tableParams.total" :page-sizes="[10, 20, 50, 100, 200, 500]" size="small" background=""
                    @size-change="handleSizeChange" @current-change="handleCurrentChange" layout="prev, pager, next" />
            </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>
                </div>
            </el-card>
            <el-card shadow="hover" :body-style="{ paddingBottom: '0', padding: '10px' }" class="card-line">
                <div style="margin: 40px; height: 50%;">
                <div class="grid-container-line">
                    <div
                        v-for="cell in cellsDataLine"
                        :key="cell.Id">
                        <div v-if="cell.IsShow === 0"
                            class="grid-item-line">
                        <div v-for="cell in cellsDataLine" :key="cell.Id">
                            <div v-if="cell.IsShow === 0" class="grid-item-line">
                            <div class="grid-item-line-end">
                                {{cell.EndLocat}}
                            </div>
@@ -37,44 +36,80 @@
                        </div>
                    </div>
                </div>
            </el-card>
            <el-card shadow="hover" :body-style="{ paddingBottom: '0', padding: '10px' }" class="card">
                <div class="grid-container">
                    <div
                        v-for="cell in cellsData"
                        :key="cell.Id">
                        <div
                            v-if="cell.IsShow === 0"
                            :class="['grid-item', { 'active': cell.IsUse === 1 }, { 'active2': cell.IsUse === 2 }]"
                            >
                            <!-- 临时用的code需增加点位字段替换 -->
                        <div v-for="cell in cellsData" :key="cell.Id">
                            <div v-if="cell.IsShow === 0"
                                :class="['grid-item', { 'active': cell.IsUse === 1 }, { 'active2': cell.IsUse === 2 }]">
                            <div>{{ cell.Code }}</div>
                        </div>
                    </div>
                </div>
            </el-card>
        </pane>
      </splitpanes>
                </div>
            </el-col>
        </el-row>
    </div>
  </template>
  
  <script lang="ts" setup>
  import { ref, reactive } from 'vue';
  import { Splitpanes, Pane } from 'splitpanes';
import { ref, reactive, onMounted, computed } from 'vue';
  import 'splitpanes/dist/splitpanes.css';
  import { Vue2 } from 'vue-demi';
import { listWcsAlarmInfo, resetWcsAlarmInfo } from '/@/api/wcs/wcsAlarmInfo';
import { signalR } from './signalR';
const tableParams = ref({
    page: 1,
    pageSize: 10,
    total: 0,
});
const loading = ref(false);
const tableData = ref<any>([]);
const handleQuery = async () => {
    loading.value = true;
    var res = await listWcsAlarmInfo({ status: 1 });
    tableData.value = res.data.result;
    tableParams.value.total = tableData.value.length;
    console.log(tableParams.value.total);
    loading.value = false;
};
handleQuery();
  const  tableData= [{
          variable:'MB100.0',
          desc:'报警描述',
          address: '205工位',
          date: '2024-08-29'
        }, {
          variable:'MB102.0',
          desc:'报警描述',
          address: '202工位',
          date: '2024-08-28'
        }];
//复位报警
const reset = async () => {
    await resetWcsAlarmInfo();
}
//连接signalR 监听变更
onMounted(async () => {
    signalR.off('PublicAlarm');
    signalR.on('PublicAlarm', (data: any) => {
        console.log(data)
        var listAlarm = tableData.value.filter(t => t.id == data.id);
        if (listAlarm.length == 0) {
            tableData.value.unshift(data)
            tableParams.value.total = tableData.value.length;
        }
        else {
            //如果已经存在就更新数据
            const index = tableData.value.findIndex(t => t.id == data.id);
            tableData.value.splice(index, 1, data);
        }
    });
});
// 改变页面容量
const handleSizeChange = (val: number) => {
    tableParams.value.pageSize = val;
};
// 改变页码序号
const handleCurrentChange = (val: number) => {
    tableParams.value.page = val;
};
//表格显示数据
const paginatedData = computed(() => {
    const start = (tableParams.value.page - 1) * tableParams.value.pageSize
    const end = start + tableParams.value.pageSize
    return tableData.value.slice(start, end)
})
  //堆垛机数据
  const cellsDataLine=[
@@ -100,7 +135,7 @@
    { Id: 20, Code: '020', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 21, Code: '021', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 22, Code: '022', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 23, Code: '023', LineCode:'05',EndLocat:'',IsShow: 0, IsUse: 0,BoxHeight:300 },
    { Id: 23, Code: '023', LineCode: '05', EndLocat: '', IsShow: 0, IsUse: 0, BoxHeight: 100 },
    { Id: 24, Code: '024', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10},
    { Id: 25, Code: '025', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 26, Code: '026', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
@@ -1913,6 +1948,7 @@
        display: flex;
        align-items: center;
    }
    .card-page{
        width: 100%;
        height: 30px;
@@ -1922,28 +1958,40 @@
        align-items: center;
        text-align: center;
    }
    .card-page>button{
        width: 150px;
        height: 30px;
    }
    .card-line{
        border: none;
        background-color: transparent;
        position:absolute;
        z-index: 99;
    }
    .grid-container-line{
        display: grid;
        grid-template-columns: repeat(51, 25px); /* 每列宽度 */
        grid-template-rows: repeat(1, 310px);  /* 每行高度 */
        gap: 0px; /* Gap between cells */
    grid-template-columns: repeat(51, 1fr);
    /* 自适应列宽 */
    grid-template-rows: 1fr;
    /* 自适应行高 */
    gap: 0;
        margin-top: 25px;
    width: 100%;
    /* 宽度自适应 */
    height: 1fr;
    /* 高度自适应 */
    }
    .grid-item-line{
        text-align: center;
        line-height: 50px; /* Vertical center the content */
        font-size: 12px; /* Adjust font size */
        width: 25px;
    line-height: 50px;
    /* Vertical center the content */
    font-size: 12px;
    /* Adjust font size */
    width: 30px;
        height: 100%;
        color: #fff;
        font-size: 14px;
@@ -1951,6 +1999,7 @@
        display: flex;
        justify-content: center;
    }
    .grid-item-line-end{
        width:80px;
        height: 25px;
@@ -1965,22 +2014,25 @@
        text-align: center;
        color: #fff;
    }
    .grid-item-line-box{
        width: 15px;
        height: 15px;
    width: 20px;
    height: 20px;
        background-color:rgb(97, 250, 145);
        position: absolute;
        display: flex;
        text-align: center;
        justify-content: center;
        align-items: center; /* Added to vertically center the text */
    align-items: center;
    /* Added to vertically center the text */
        text-align: center;
        color: black;
    }
    .grid-item-line-child{
        height: 100%;
    height: 220px;
        width: 3px;
        background-color: #9c9c9c;
    background-color: #000000;
    }
    .card{
@@ -1990,32 +2042,74 @@
        z-index: 90;
        margin-top: 310px;
    }
      .grid-container {
    margin-top: 10px;
        width: 100%;
        display: grid;
        grid-template-columns: repeat(51, 25px); /* 每列宽度 */
        grid-template-rows: repeat(12, 30px);  /* 每行高度 */
        gap: 0px; /* Gap between cells */
    grid-template-columns: repeat(51, 1fr);
    /* 每列宽度 */
    grid-template-rows: repeat(12, 1fr);
    /* 每行高度 */
    gap: 0px;
    /* Gap between cells */
    }
    .grid-item {
        background-color: #9c9c9c;
        border: 1px solid #797777;
        text-align: center;
        line-height: 35px;
        font-size: 12px;
        width: 25px;
        height: 30px;
    line-height: 1.4vw;
    /* 行高 */
    width: 100%;
    /* 自适应宽度 */
    height: 100%;
    /* 自适应高度 */
        color: #fff;
        font-size: 14px;
    font-size: 0.7vw;
    }
.card {
    width: 100%;
    position: absolute;
    border-top: none;
    z-index: 90;
    margin-top: 310px;
}
.grid-container {
    margin-top: 10px;
    width: 100%;
    display: grid;
    grid-template-columns: repeat(51, 1fr);
    /* 每列宽度 */
    grid-template-rows: repeat(12, 1fr);
    /* 每行高度 */
    gap: 0px;
    /* Gap between cells */
}
.grid-item {
    background-color: #9c9c9c;
    border: 1px solid #797777;
    text-align: center;
    line-height: 1.4vw;
    /* 行高 */
    width: 100%;
    /* 自适应宽度 */
    height: 100%;
    /* 自适应高度 */
    color: #fff;
    font-size: 0.6vw;
}
    .active {
        background-color: rgb(57, 141, 251); /* IsUse 为1时背景颜色为蓝色 */
    background-color: rgb(57, 141, 251);
    /* IsUse 为1时背景颜色为蓝色 */
        color: #f5f5f5;
    }
    .active2>div {
        display: none;
    }
  </style>
Web/src/views/device/alarmManage/signalR.ts
New file
@@ -0,0 +1,37 @@
import * as SignalR from '@microsoft/signalr';
import { getToken } from '/@/utils/axios-utils';
// 初始化SignalR对象
const connection = new SignalR.HubConnectionBuilder()
    .configureLogging(SignalR.LogLevel.Information)
    .withUrl(`${window.__env__.VITE_API_URL}/hubs/Plc?token=${getToken()}`, { transport: SignalR.HttpTransportType.WebSockets, skipNegotiation: true })
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: () => {
            return 5000; // 每5秒重连一次
        },
    })
    .build();
connection.keepAliveIntervalInMilliseconds = 15 * 1000; // 心跳检测15s
connection.serverTimeoutInMilliseconds = 30 * 60 * 1000; // 超时时间30m
// 启动连接
connection.start().then(() => {
    console.log('启动连接plc');
});
// 断开连接
connection.onclose(async () => {
    console.log('断开连接plc');
});
// 重连中
connection.onreconnecting(() => {
    console.log('服务器已断线plc');
});
// 重连成功
connection.onreconnected(() => {
    console.log('重连成功plc');
});
// connection.on('PublicAlarm', () => {});
export { connection as signalR };
Web/src/views/device/deviceInfo/index.vue
@@ -1,60 +1,73 @@
<template>
    <div class="sys-user-container">
      <splitpanes>
        <pane size="10">
        <el-row>
            <el-col :span="3">
          <el-collapse v-model="activeName">
            <el-collapse-item title="设备控制" name="1">
              <el-card
                class="box-card"
                shadow="hover">
                <el-switch v-model="value1" active-text="" inactive-text="程序服务"></el-switch>
                        <el-card class="box-card" shadow="hover">
                            <el-switch v-model="state.boRunningState" active-text="" inactive-text="程序服务"></el-switch>
              </el-card>
              <el-card
                class="box-card"
                shadow="hover"
                style="margin-top: 3px;">
                <el-switch v-model="value2" active-text="" inactive-text="脱机模式"></el-switch>
                        <el-card class="box-card" shadow="hover" style="margin-top: 3px;">
                            <el-switch v-model="state.boOffline" active-text="" inactive-text="脱机模式"></el-switch>
              </el-card>
              <el-card
                class="box-card"
                shadow="hover"
                style="margin-top: 3px;">
                <el-switch v-model="value3" active-text="" inactive-text="自刷新"></el-switch>
                        <el-card class="box-card" shadow="hover" style="margin-top: 3px;">
                            <el-switch v-model="state.boRefresh" active-text="" inactive-text="自刷新"></el-switch>
              </el-card>
            </el-collapse-item>
  
            <el-collapse-item title="堆垛机" name="2">
              <div style="overflow-x: auto;white-space: nowrap;height: 380px;">
                <el-card
                  v-for="(stacker, index) in stackers"
                  :key="index"
                  class="box-card"
                  shadow="hover"
                  style="margin-top: 3px;">
                  <el-switch v-model="stacker.value" :inactive-text="`${index + 1}号堆垛机`"></el-switch>
                        <div style="overflow-x: auto;white-space: nowrap;">
                            <el-card v-for="(stacker, index) in stackers" :key="index" class="box-card" shadow="hover">
                                <el-switch disabled  v-model="stacker.isConn" :inactive-text="`${stacker.text}`"></el-switch>
                </el-card>
              </div>
            </el-collapse-item>
  
            <el-collapse-item title="输送线" name="3">
              <el-card
                v-for="(conveyor, index) in conveyors"
                :key="index"
                class="box-card"
                shadow="hover"
                style="margin-top: 3px;">
                <el-switch v-model="conveyor.value" :inactive-text="`${index + 1}楼输送线`"></el-switch>
                        <el-card v-for="(conveyor, index) in conveyors" :key="index" class="box-card" shadow="hover">
                            <el-switch disabled  v-model="conveyor.isConn" :inactive-text="`${conveyor.text}`"></el-switch>
              </el-card>
            </el-collapse-item>
          </el-collapse>
        </pane>
        <pane size="90" style="position: relative;background-color: #fff;">
            <el-card shadow="hover" :body-style="{ paddingBottom: '0', padding: '10px' }">
            </el-col>
            <el-col :span="21">
                <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>
                </div>
                <div style="margin: 40px; height: 50%;">
                    <div class="grid-container-line">
                        <div v-for="cell in cellsDataLine" :key="cell.Id">
                            <div v-if="cell.IsShow === 0" class="grid-item-line">
                                <div class="grid-item-line-end">
                                    {{ cell.EndLocat }}
                                </div>
                                <div class="grid-item-line-box" :style="{ marginTop: cell.BoxHeight + 'px' }">
                                    {{ cell.LineCode }}
                                </div>
                                <div class="grid-item-line-child"></div>
                            </div>
                        </div>
                    </div>
                    <div class="grid-container">
                        <div v-for="cell in cellsData" :key="cell.Id">
                            <div v-if="cell.IsShow === 0"
                                :class="['grid-item', { 'active': cell.IsUse === 1 }, { 'active2': cell.IsUse === 2 }]">
                                <div>{{ cell.Code }}</div>
                            </div>
                        </div>
                    </div>
                </div>
            </el-col>
        </el-row>
    </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { listStatus } from '/@/api/wcs/wcsPlc';
import { signalR } from './signalR';
            </el-card>
            <el-card shadow="hover" :body-style="{ paddingBottom: '0', padding: '10px' }" class="card-line">
                <div class="grid-container-line">
@@ -101,33 +114,49 @@
  import { Vue2 } from 'vue-demi';
const state = ref<any>({});
const stackers = ref<any>({});
const conveyors = ref<any>({});
// 查询状态
const handleQuery = async () => {
    var res = await listStatus();
    state.value = res.data.result.modService;
    stackers.value = res.data.result.listPlc.filter(s => s.type == 0);
    conveyors.value = res.data.result.listPlc.filter(s => s.type == 1 || s.type == 4);
};
handleQuery();
//连接signalR 监听变更
onMounted(async () => {
    signalR.off('PublicPlcConn');
    signalR.on('PublicPlcConn', (data: any) => {
        console.log(data)
        if (data.type === 0) {
            // 替换 stackers 中的相应项
            const index = stackers.value.findIndex(item => item.id === data.id);
            if (index !== -1) {
                stackers.value[index] = data;
            }
        } else if (data.type === 1 || data.type === 4) {
            // 替换 conveyors 中的相应项
            const index = conveyors.value.findIndex(item => item.id === data.id);
            if (index !== -1) {
                conveyors.value[index] = data;
            }
        }
        console.log(stackers.value[0].isConn)
    });
});
  const activeName = ['1', '2', '3'];
  const value1 = ref(false);
  const value2 = ref(false);
  const value3 = ref(false);
  const stackers = reactive([
    { value: false },
    { value: false },
    { value: false },
    { value: false },
    { value: false },
    { value: false },
    { value: false },
    { value: false },
    { value: false },
    { value: false },
  ]);
  const conveyors = reactive([
    { value: false },
    { value: false },
    { value: false },
  ]);
  //堆垛机数据
  const cellsDataLine=[
      { Id: 1, Code: '001', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 2, Code: '002', LineCode:'01',EndLocat:'01010101',IsShow: 0, IsUse: 0,BoxHeight:190 },
    { Id: 2, Code: '002', LineCode: '01', EndLocat: '01010101', IsShow: 0, IsUse: 0, BoxHeight: 10 },
    { Id: 3, Code: '003', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 4, Code: '004', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 5, Code: '005', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
@@ -148,7 +177,7 @@
    { Id: 20, Code: '020', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 21, Code: '021', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 22, Code: '022', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 23, Code: '023', LineCode:'05',EndLocat:'',IsShow: 0, IsUse: 0,BoxHeight:300 },
    { Id: 23, Code: '023', LineCode: '05', EndLocat: '', IsShow: 0, IsUse: 0, BoxHeight: 200 },
    { Id: 24, Code: '024', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10},
    { Id: 25, Code: '025', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
    { Id: 26, Code: '026', LineCode:'',EndLocat:'',IsShow: 1, IsUse: 0,BoxHeight:10 },
@@ -1960,7 +1989,9 @@
        padding: 0;
        display: flex;
        align-items: center;
    margin-top: 3px;
    }
    .card-page{
        width: 100%;
        height: 30px;
@@ -1970,27 +2001,39 @@
        align-items: center;
        text-align: center;
    }
    .card-page>button{
        width: 150px;
        height: 30px;
    }
    .card-line{
        border: none;
        background-color: transparent;
        position:absolute;
        z-index: 99;
    }
    .grid-container-line{
        display: grid;
        grid-template-columns: repeat(51, 30px); /* 每列宽度 */
        grid-template-rows: repeat(1, 310px);  /* 每行高度 */
        gap: 0px; /* Gap between cells */
    grid-template-columns: repeat(51, 1fr);
    /* 自适应列宽 */
    grid-template-rows: 1fr;
    /* 自适应行高 */
    gap: 0;
        margin-top: 25px;
    width: 100%;
    /* 宽度自适应 */
    height: 1fr;
    /* 高度自适应 */
    }
    .grid-item-line{
        text-align: center;
        line-height: 50px; /* Vertical center the content */
        font-size: 12px; /* Adjust font size */
    line-height: 50px;
    /* Vertical center the content */
    font-size: 12px;
    /* Adjust font size */
        width: 30px;
        height: 100%;
        color: #fff;
@@ -1999,6 +2042,7 @@
        display: flex;
        justify-content: center;
    }
    .grid-item-line-end{
        width:80px;
        height: 25px;
@@ -2013,6 +2057,7 @@
        text-align: center;
        color: #fff;
    }
    .grid-item-line-box{
        width: 20px;
        height: 20px;
@@ -2021,14 +2066,16 @@
        display: flex;
        text-align: center;
        justify-content: center;
        align-items: center; /* Added to vertically center the text */
    align-items: center;
    /* Added to vertically center the text */
        text-align: center;
        color: black;
    }
    .grid-item-line-child{
        height: 100%;
    height: 220px;
        width: 3px;
        background-color: #9c9c9c;
    background-color: #000000;
    }
    .card{
@@ -2038,12 +2085,17 @@
        z-index: 90;
        margin-top: 310px;
    }
      .grid-container {
    margin-top: 10px;
        width: 100%;
        display: grid;
        grid-template-columns: repeat(51, 30px); /* 每列宽度 */
        grid-template-rows: repeat(12, 35px);  /* 每行高度 */
        gap: 0px; /* Gap between cells */
    grid-template-columns: repeat(51, 1fr);
    /* 每列宽度 */
    grid-template-rows: repeat(12, 1fr);
    /* 每行高度 */
    gap: 0px;
    /* Gap between cells */
    }
    .grid-item {
@@ -2074,3 +2126,27 @@
    
  </style>
  
.grid-item {
    background-color: #9c9c9c;
    border: 1px solid #797777;
    text-align: center;
    line-height: 1.4vw;
    /* 行高 */
    width: 100%;
    /* 自适应宽度 */
    height: 100%;
    /* 自适应高度 */
    color: #fff;
    font-size: 0.7vw;
}
.active {
    background-color: rgb(57, 141, 251);
    /* IsUse 为1时背景颜色为蓝色 */
    color: #f5f5f5;
}
.active2>div {
    display: none;
}
</style>
Web/src/views/device/deviceInfo/signalR.ts
New file
@@ -0,0 +1,37 @@
import * as SignalR from '@microsoft/signalr';
import { getToken } from '/@/utils/axios-utils';
// 初始化SignalR对象
const connection = new SignalR.HubConnectionBuilder()
    .configureLogging(SignalR.LogLevel.Information)
    .withUrl(`${window.__env__.VITE_API_URL}/hubs/Plc?token=${getToken()}`, { transport: SignalR.HttpTransportType.WebSockets, skipNegotiation: true })
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: () => {
            return 5000; // 每5秒重连一次
        },
    })
    .build();
connection.keepAliveIntervalInMilliseconds = 15 * 1000; // 心跳检测15s
connection.serverTimeoutInMilliseconds = 30 * 60 * 1000; // 超时时间30m
// 启动连接
connection.start().then(() => {
    console.log('启动连接plc');
});
// 断开连接
connection.onclose(async () => {
    console.log('断开连接plc');
});
// 重连中
connection.onreconnecting(() => {
    console.log('服务器已断线plc');
});
// 重连成功
connection.onreconnected(() => {
    console.log('重连成功plc');
});
// connection.on('PublicPlcConn', () => {});
export { connection as signalR };
Web/src/views/wcs/wcsAlarmInfo/component/editDialog.vue
@@ -27,6 +27,13 @@
                        
                    </el-col>
                    <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
                        <el-form-item label="工位号" prop="stationNum">
                            <el-input v-model="ruleForm.stationNum" placeholder="请输入工位号" maxlength="20" show-word-limit clearable />
                        </el-form-item>
                    </el-col>
                    <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
                        <el-form-item label="报警编号" prop="alarmCode">
                            <el-input v-model="ruleForm.alarmCode" placeholder="请输入报警编号" maxlength="20" show-word-limit clearable />
                            
@@ -102,6 +109,7 @@
    const rules = ref<FormRules>({
        plcIP: [{required: true, message: '请输入PLCIP地址!', trigger: 'blur',},],
        plcPort: [{required: true, message: '请输入PLCIP地址!', trigger: 'blur',},],
        stationNum: [{required: true, message: '请输入工位号!', trigger: 'blur',},],
        alarmCode: [{required: true, message: '请输入报警编号!', trigger: 'blur',},],
    });
Web/src/views/wcs/wcsAlarmInfo/index.vue
@@ -55,6 +55,7 @@
        <el-table-column type="index" label="序号" width="55" align="center" />
        <el-table-column prop="plcIP" label="PLCIP地址" show-overflow-tooltip="" />
        <el-table-column prop="plcPort" label="PLCIP端口" show-overflow-tooltip="" />
        <el-table-column prop="stationNum" label="工位号" show-overflow-tooltip="" />
        <el-table-column prop="alarmCode" label="报警编号" show-overflow-tooltip="" />
        <el-table-column prop="alarmName" label="报警描述" show-overflow-tooltip="" />
        <el-table-column prop="ledIP" label="显示屏ip地址" show-overflow-tooltip="" />
Web/src/views/wcs/wcsTask/signalR.ts
@@ -32,6 +32,6 @@
    console.log('重连成功task');
});
connection.on('PublicTask', () => {});
// connection.on('PublicTask', () => {});
export { connection as signalR };