From 78c8668ac3553c8a2418235fba2a2d8020263270 Mon Sep 17 00:00:00 2001 From: xingheng Date: Fri, 10 Oct 2025 09:44:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B8=83=E7=BD=AE=E5=9B=BE?= =?UTF-8?q?=E7=BB=86=E5=88=86=E5=9B=BE=E4=BE=8B=EF=BC=8C=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E4=BC=9A=E8=A2=AB=E9=81=AE=E4=BD=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppApi/IOModuleApiController.cs | 264 +++++++++++++++--- .../Views/ec_objecttype/FormLayoutFilter.js | 3 +- .../ec_panel_channel/ec_PanelChannelEntity.cs | 14 +- 3 files changed, 228 insertions(+), 53 deletions(-) diff --git a/Learun.Application.Web/AppApi/IOModuleApiController.cs b/Learun.Application.Web/AppApi/IOModuleApiController.cs index a7caf92b..7e370742 100644 --- a/Learun.Application.Web/AppApi/IOModuleApiController.cs +++ b/Learun.Application.Web/AppApi/IOModuleApiController.cs @@ -454,7 +454,9 @@ namespace Learun.Application.Web.AppApi [HttpGet] public void AutoAssignCable2Channel_ResExport(string projectId, bool flg) { - var BLL = new ec_Wire_GroupBLL(); + ICache redisObj = CacheFactory.CaChe(); + var cablesNeedAssigned = redisObj.Read>("IOModule_AutoAssign2Ch_" + projectId, CacheId.IOModule_AutoAssign2Ch); + var PanelAssigned = redisObj.Read>("IOModule_AutoAssign2ChByPanel_" + projectId, CacheId.IOModule_AutoAssign2Ch); var bytes = new byte[] { }; try { @@ -462,8 +464,67 @@ namespace Learun.Application.Web.AppApi using (var package = new ExcelPackage())//新建 { - var sheet = package.Workbook.Worksheets.Add("test"); - sheet.Cells[1, 1].Value = "test"; + var sheet = package.Workbook.Worksheets.Add("已经分配的"); + sheet.Cells[1, 1].Value = "电缆位号"; + sheet.Cells[1, 2].Value = "电缆对"; + sheet.Cells[1, 3].Value = "信号类型"; + sheet.Cells[1, 4].Value = "预分配箱子"; + sheet.Cells[1, 5].Value = "实际分配箱子"; + sheet.Cells[1, 6].Value = "实际分配端子排"; + sheet.Cells[1, 7].Value = "备注"; + int rowIdx = 2; + #region ByCable + foreach (var cable in cablesNeedAssigned) + { + sheet.Cells[rowIdx, 1].Value = cable.TagNumber; + rowIdx++; + foreach (var set in cable.Sets) + { + + sheet.Cells[rowIdx, 2].Value = set.CableSetName; + sheet.Cells[rowIdx, 7].Value = set.ConnectionInfo; + rowIdx++; + } + + rowIdx = rowIdx + 2; + } + #endregion + #region ByPanel + sheet = package.Workbook.Worksheets.Add("模块视角"); + sheet.Cells[1, 1].Value = "采集箱位号"; + sheet.Cells[1, 2].Value = "模块名字"; + sheet.Cells[1, 3].Value = "IO类型"; + sheet.Cells[1, 4].Value = "通道名字"; + sheet.Cells[1, 5].Value = "通道顺序"; + sheet.Cells[1, 6].Value = "关联电缆"; + sheet.Cells[1, 7].Value = "关联Set"; + sheet.Cells[1, 8].Value = "关联信号"; + sheet.Cells[1, 9].Value = "备注"; + rowIdx = 2; + + foreach (var p in PanelAssigned) + { + var newTS = p.strips.FindAll(x => x.Channels.Any(y => !string.IsNullOrEmpty(y.ConnectionInfo))); + + foreach (var ts in newTS) + { + foreach (var ch in ts.Channels) + { + sheet.Cells[rowIdx, 1].Value = p.TagNumber; + sheet.Cells[rowIdx, 2].Value = ts.StripName; + sheet.Cells[rowIdx, 3].Value = ts.IO_TYPE; + sheet.Cells[rowIdx, 4].Value = ch.ChannelName; + sheet.Cells[rowIdx, 5].Value = ch.Channel_Seq; + sheet.Cells[rowIdx, 6].Value = ch.assignCable; + sheet.Cells[rowIdx, 7].Value = ch.assignCableSet; + sheet.Cells[rowIdx, 8].Value = ch.ConnectionInfo; + rowIdx++; + } + } + rowIdx = rowIdx + 2; + } + #endregion + package.Save(); bytes = package.GetAsByteArray(); } } @@ -638,7 +699,7 @@ namespace Learun.Application.Web.AppApi .GroupBy(x => x.PanelID) .ToDictionary(x => x.Key, x => x.ToList());//带出strip下的channel - + var PanelResultPreview = new List();//为了看by panel的预览情况,有时候光by cable看不出问题 foreach (var c in cNotPanel) { foreach (var set in c.Sets) @@ -654,6 +715,9 @@ namespace Learun.Application.Web.AppApi var cablesOnThisPanel = cablesGrouped.Where(x => x.AssignedPanel.PanelID == curPanelId).ToList(); if (cablesOnThisPanel == null || cablesOnThisPanel.Count == 0) continue;//next panel var lastUsedStrip = (ec_PanelStripEntity)null; + var lastUsedPanel = new ec_PanelEntity(); + lastUsedPanel.strips = allStrips[curPanelId];//已经存在的所有模块 + foreach (var cable in cablesOnThisPanel) { //1.2.2 分组原则为同一信号类型、同一系统的为一组,系统属性从电缆的from端上的设备中取设备的系统,同组的优先放到同一个箱子的同一个模块里面, @@ -778,7 +842,7 @@ namespace Learun.Application.Web.AppApi #region NewTS //1.2 流程图 根据信号数里自动新建端子排,端子排通道数里根据箱子模板中的默认值获得 - var newTS = CreatePanelStripByProfile2(projId, cable.AssignedPanel, newTSSeq++, ioTypeOnC); + var newTS = CreatePanelStripByProfile2(projId, cable.AssignedPanel, newTSSeq++, ioTypeOnC);//当前采集箱无模块,需要新建 matchedStrips.Add(newTS); @@ -795,6 +859,7 @@ namespace Learun.Application.Web.AppApi List notUsedChs = new List(); #region 1.2 流程图 按规则先放几个信号进去(放不下就延到下一个模块) + #region old //if (cable.Sets.Count > 10) //{ // //另外如果电缆对数大于10对,即像12 * 2 * 0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道,如果不够4个就留4个以下即可。 @@ -1001,6 +1066,8 @@ namespace Learun.Application.Web.AppApi //else //{ //5%的空白通道 + #endregion + #region FindNextAvailableTS(可能多个) //还分2种情况判断模块是否符合: //case1,. 占满可用通道后,还剩至少一个预分配的set和spared的set。 //这种时候要spared不用考虑 @@ -1009,20 +1076,21 @@ namespace Learun.Application.Web.AppApi bool NeedNextTS = false; #region FindNextAvailableTS + var trueAvailableChs = 0; + var sparedButCanNotUsedChs = 0;//需要特意预留的通道, int idx = 0; while (idx < matchedStrips.Count) { NeedNextTS = false; var TS = matchedStrips[idx]; var allCh = TS.Channels; - var allChCount = allCh.Count; - if (allChCount == 0) + if (allCh.Count == 0) { NeedNextTS = true; //同时又是最后一个了 if (idx == matchedStrips.Count - 1) { - var newTS = CreatePanelStripByProfile2(projId, cable.AssignedPanel, newTSSeq++, ioTypeOnC); + var newTS = CreatePanelStripByProfile2(projId, cable.AssignedPanel, newTSSeq++, ioTypeOnC);//当前ts无ch,需要新建 if (newTS.Channels.Count() < setsSpared.Count + 1)//1代表至少要放一个set { //有问题了 panel的io模板,估计会导致无限循环了 @@ -1030,62 +1098,105 @@ namespace Learun.Application.Web.AppApi } matchedStrips.Add(newTS); } - idx++; continue;//本模块没有一个ch + idx++; continue;//本模块没有一个ch,否则后面 5% 会除以0 } //被占用的(wt里有或者信号里或有),或者被锁定的,都算占用 usedChs = allCh.Where(x => allUsedCHBySignalOrSet.Contains(x.ChannelID) || x.lock_flg == 1).ToList(); notUsedChs = allCh.Where(x => !usedChs.Select(c => c.ChannelID).Contains(x.ChannelID)).ToList(); + trueAvailableChs = 0; var ChNeedConn = 0; var SparedNeedConn = 0; + //分3种情况,确认具体要接的ch个数和是否需要下一个ts if (cable.CableClass.ToLower() == SWS.Share.Enum.cableClass.homerun.ToString()) { - //1.2.3(2)如果模块是通讯信号(像RS485这种就是通讯信号),则不用管预留空白通道这个事情,看到有空的通道就放。 - - if (allChCount < sets.Count) + //1.2.3(2) 如果模块是通讯信号(像RS485这种就是通讯信号),则不用管预留空白通道这个事情,看到有空的通道就放。 + #region 可用通道(通讯) + trueAvailableChs = notUsedChs.Count; + #endregion + if (trueAvailableChs < sets.Count) { //case1,能顺利接完,set还有多,还要再do循环一次,多余的set和spared都要放到下一个模块 NeedNextTS = true; - ChNeedConn = allChCount; SparedNeedConn = 0; + ChNeedConn = trueAvailableChs; SparedNeedConn = 0; } - else if (allChCount == sets.Count) + else if (trueAvailableChs == sets.Count) { //case3,刚好,只有spared可能多 - ChNeedConn = allChCount; + ChNeedConn = trueAvailableChs; SparedNeedConn = 0; //homerun不用考虑预留一个set去配合后续的spared } else - { + { ChNeedConn = sets.Count; SparedNeedConn = 0; } } else { + #region 可用通道(非通讯) //1.2 流程图 空的通道数够不够 - double sparedRate = notUsedChs.Count * 1.0 / allChCount * 1.0; - if (sparedRate < 0.05) + if (cable.Sets.Count > 10) { - NeedNextTS = true; - //同时又是最后一个了 - if (idx == matchedStrips.Count - 1) + //1.2.3 空白通道原则,另外如果电缆对数大于10对,即像12*2*0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。 + if (notUsedChs.Count < 4) { - var newTS = CreatePanelStripByProfile2(projId, cable.AssignedPanel, newTSSeq++, ioTypeOnC); - if (newTS.Channels.Count() < setsSpared.Count + 1)//1代表至少要放一个set - { - //有问题了 panel的io模板,估计会导致无限循环了 - break; - } - matchedStrips.Add(newTS); + //可以认为这个ts不可用了 + trueAvailableChs = 0; + sparedButCanNotUsedChs = notUsedChs.Count; + notUsedChs.ForEach(x => x.lock_flg = 1);//全部标记为不可用 } - idx++; continue;//本模块可用的模块小于5% - } - //没有空闲通道 next - //或者空闲通道不够放下这个电缆的所有set - //算一下5%是多少个通道 - var trueAvailableChs = notUsedChs.Count - (allChCount - FindMaxNumberChannel(allChCount));//真正能用的(没接过的,然后去掉5%) + else + { + //all 16,used 7,notused9, 砍去4, + // 1. 计算起始索引 + int startIndex = Math.Max(0, notUsedChs.Count - 4); + // 2. 计算要截取的数量(从起始索引到末尾的元素数) + int takeCount = notUsedChs.Count - startIndex; + + // 3. 截取最后4个元素 + var lastFour = notUsedChs.GetRange(startIndex, takeCount);//最后4个给不可用,并且这4个也要被标记为不能使用 + lastFour.ForEach(x => x.lock_flg = 1);//标记为不可用 + usedChs.AddRange(lastFour); + notUsedChs = allCh.Where(x => !usedChs.Select(c => c.ChannelID).Contains(x.ChannelID)).ToList(); + trueAvailableChs = notUsedChs.Count; + sparedButCanNotUsedChs = 4; + + + } + } + else + { + //5% + double sparedRate = notUsedChs.Count * 1.0 / allCh.Count * 1.0; + if (sparedRate < 0.05) + { + NeedNextTS = true; + trueAvailableChs = 0; + sparedButCanNotUsedChs = notUsedChs.Count; + notUsedChs.ForEach(x => x.lock_flg = 1);//全部标记为不可用 + } + else + { + sparedButCanNotUsedChs = allCh.Count - FindMaxNumberChannel(allCh.Count); + // 1. 计算起始索引 + int startIndex = Math.Max(0, notUsedChs.Count - sparedButCanNotUsedChs); + + // 2. 计算要截取的数量(从起始索引到末尾的元素数) + int takeCount = notUsedChs.Count - startIndex; + + // 3. 截取最后4个元素 + var lastFour = notUsedChs.GetRange(startIndex, takeCount);//最后4个给不可用,并且这4个也要被标记为不能使用 + lastFour.ForEach(x => x.lock_flg = 1);//标记为不可用 + + trueAvailableChs = notUsedChs.Count - sparedButCanNotUsedChs;//真正能用的(没接过的,然后去掉5%) + } + } + + + #endregion if (trueAvailableChs < sets.Count) { //case1,能顺利接完,set还有多,还要再do循环一次,多余的set和spared都要放到下一个模块 @@ -1097,10 +1208,21 @@ namespace Learun.Application.Web.AppApi //case3,刚好,只有spared可能多 if (setsSpared.Count > 0) { - //自己的感觉(非客户word需求里的原话):spared不能自己单独占用一个模块 - NeedNextTS = true; - ChNeedConn = trueAvailableChs - 1;//留一个给spared - SparedNeedConn = 0; + if (sets.Count == 1) + { + //避免死循环 + ChNeedConn = 1; + NeedNextTS = false; + SparedNeedConn = trueAvailableChs - 1; + } + else + { + //自己的感觉(非客户word需求里的原话):spared不能自己单独占用一个模块 + NeedNextTS = true; + ChNeedConn = trueAvailableChs - 1;//留一个给spared + SparedNeedConn = 0; + } + } else { @@ -1120,10 +1242,21 @@ namespace Learun.Application.Web.AppApi //这种时候要确保spared也能放下 else if (trueAvailableChs < sets.Count + setsSpared.Count) { - //自己的感觉(非客户word需求里的原话):spared不能自己单独占用一个模块 - ChNeedConn = sets.Count - 1;//留一个给spared - NeedNextTS = true; - SparedNeedConn = 0; + if (sets.Count == 1) + { + //避免死循环 + ChNeedConn = 1; + NeedNextTS = false; + SparedNeedConn = trueAvailableChs - 1; + } + else + { + //自己的感觉(非客户word需求里的原话):spared不能自己单独占用一个模块 + ChNeedConn = sets.Count - 1;//留一个给spared。如果sets.COUNT只有1个,这种情况就会死循环了 + NeedNextTS = true; + SparedNeedConn = 0; + } + } else { @@ -1137,17 +1270,20 @@ namespace Learun.Application.Web.AppApi //到这里,说明本模块至少都是可用的 - TS.Channels = allCh.OrderBy(X => X.Channel_Seq).ToList();//不能像之前一样过滤掉,否则下一个电缆进来时,总数就不对了。 - + TS.Channels = allCh.OrderBy(X => X.Channel_Seq).ToList();//不能像之前一样过滤掉,否则下一个电缆进来时,总数就不对了。 lastUsedStrip = TS; + #region conn for (int i = 0; i < ChNeedConn; i++) { var set = sets[i]; var ch = notUsedChs[i]; + ch.assignCable = cable.TagNumber; + ch.assignCableSet = set.CableSetName + " / " + set.CableSetSeq; + ch.ConnectionInfo = $"信号:{set.PreAssignGroup_Desc}/信号类型:{set.PreAssignIOTypeDetail}/传感器:{set.PreAssignSensorCode}"; if (lastUsedStrip.TagNumber == "CreatePanelStripByProfile2") { set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):(自动创建){lastUsedStrip.StripName}/通道:{ch.ChannelName}"; @@ -1170,6 +1306,10 @@ namespace Learun.Application.Web.AppApi { var set = setsSpared[i]; var ch = notUsedChs[ChNeedConn + i]; + ch.assignCable = cable.TagNumber; + ch.assignCableSet = set.CableSetName + " / " + set.CableSetSeq + "/冗余"; + ch.ConnectionInfo = $"信号:{set.PreAssignGroup_Desc}/信号类型:{set.PreAssignIOTypeDetail}/传感器:{set.PreAssignSensorCode}"; + if (lastUsedStrip.TagNumber == "CreatePanelStripByProfile2") { set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):(自动创建){lastUsedStrip.StripName}/通道:{ch.ChannelName}/冗余"; @@ -1183,17 +1323,40 @@ namespace Learun.Application.Web.AppApi allUsedCHBySignalOrSet.Add(ch.ChannelID); } + #endregion + #region 预留空白通道 + if (sparedButCanNotUsedChs > 0) + { + Console.WriteLine(""); + } #endregion //shi否要继续 TS.ChannelsSpared = notUsedChs.Where(x => x.lock_flg != 1).Count(); - TS.ChannelsUsed = allChCount - TS.ChannelsSpared;// usedChs.Count + (notUsedChs.Where(x => x.lock_flg == 1).Count()); + TS.ChannelsUsed = allCh.Count - TS.ChannelsSpared;// usedChs.Count + (notUsedChs.Where(x => x.lock_flg == 1).Count()); + //var xx = lastUsedPanel.strips; + #region panel record + if (ChNeedConn > 0 || SparedNeedConn > 0) + { + //记录本模块被使用过 + var idx2 = lastUsedPanel.strips.FindIndex(ts => ts.StripID == lastUsedStrip.StripID); + if (idx2 != -1) + { + lastUsedPanel.strips[idx2] = lastUsedStrip; + + } + else + { + lastUsedPanel.strips.Add(lastUsedStrip); + } + } + #endregion if (NeedNextTS) { sets = sets.Where(x => !x.IsConned ?? false).ToList();//移除已经分配过的set //同时又是最后一个了 if (idx == matchedStrips.Count - 1) { - var newTS = CreatePanelStripByProfile2(projId, cable.AssignedPanel, newTSSeq++, ioTypeOnC); + var newTS = CreatePanelStripByProfile2(projId, cable.AssignedPanel, newTSSeq++, ioTypeOnC);//还有set需要接,需要新建 if (newTS.Channels.Count() < setsSpared.Count + 1)//1代表至少要放一个set { //有问题了 panel的io模板,估计会导致无限循环了 @@ -1208,6 +1371,8 @@ namespace Learun.Application.Web.AppApi { break; } + + } #endregion @@ -1229,7 +1394,14 @@ namespace Learun.Application.Web.AppApi } + PanelResultPreview.Add(lastUsedPanel); } + //必要的数据存入redis,以便后续步骤使用 + redisObj.Remove("IOModule_AutoAssign2Ch_" + projId, CacheId.IOModule_AutoAssign2Ch); + redisObj.Write>("IOModule_AutoAssign2Ch_" + projId, cablesNeedAssigned, CacheId.IOModule_AutoAssign2Ch); + + redisObj.Remove("IOModule_AutoAssign2ChByPanel_" + projId, CacheId.IOModule_AutoAssign2Ch); + redisObj.Write>("IOModule_AutoAssign2ChByPanel_" + projId, PanelResultPreview, CacheId.IOModule_AutoAssign2Ch); return Success(cablesNeedAssigned); diff --git a/Learun.Application.Web/Areas/ZZDT_EC/Views/ec_objecttype/FormLayoutFilter.js b/Learun.Application.Web/Areas/ZZDT_EC/Views/ec_objecttype/FormLayoutFilter.js index 093a680c..a5b7c26a 100644 --- a/Learun.Application.Web/Areas/ZZDT_EC/Views/ec_objecttype/FormLayoutFilter.js +++ b/Learun.Application.Web/Areas/ZZDT_EC/Views/ec_objecttype/FormLayoutFilter.js @@ -22,8 +22,7 @@ var bootstrap = function ($, learun) { url: top.$.rootUrl + '/ZZDT_EC/ec_library_file/GetTreeDataByObjectType',//获取数据地址 param: { ProjectId: ProjectId, ObjectTypeID: objectTypeId }, //请求后台参数 type: 'tree',//数据展示类型: 1.default:普通;2.tree:树形数据;3. treemultiple:树形多选;multiple:普通多选 - allowSearch: true, - maxHeight: 225 + allowSearch: true }).lrselectSet(LayoutLibFileID);//lrselectSet获取下拉框选择的值,传入objectTypeId }, initData: function () { diff --git a/Learun.Framework.Module/Learun.Application.Module/Learun.Application.TwoDevelopment/ZZDT_EC/ec_panel_channel/ec_PanelChannelEntity.cs b/Learun.Framework.Module/Learun.Application.Module/Learun.Application.TwoDevelopment/ZZDT_EC/ec_panel_channel/ec_PanelChannelEntity.cs index febe37d5..f28b9414 100644 --- a/Learun.Framework.Module/Learun.Application.Module/Learun.Application.TwoDevelopment/ZZDT_EC/ec_panel_channel/ec_PanelChannelEntity.cs +++ b/Learun.Framework.Module/Learun.Application.Module/Learun.Application.TwoDevelopment/ZZDT_EC/ec_panel_channel/ec_PanelChannelEntity.cs @@ -150,12 +150,16 @@ namespace Learun.Application.TwoDevelopment.ZZDT_EC /// /// [SugarColumn(IsIgnore = true)] - public string HomerunCableName { get; set; } = ""; - /// - /// true时,表示假的。真正的结构还是panel strip term - /// + public string HomerunCableName { get; set; } = ""; + [SugarColumn(IsIgnore = true)] - bool fakeFlg { get; set; } = false; + public string assignCable { get; set; } + + [SugarColumn(IsIgnore = true)] + public string assignCableSet { get; set; } + + [SugarColumn(IsIgnore = true)] + public string ConnectionInfo { get; set; } #endregion } }