hou 3 дней назад
Родитель
Сommit
47dcb0c35b

+ 108 - 2
src/main/java/com/jeesite/modules/mes/web/MesProductController.java

@@ -27,6 +27,7 @@ import com.jeesite.modules.mes.service.*;
 import com.jeesite.modules.mes.util.CommonUitl;
 import com.jeesite.modules.sys.entity.EmpUser;
 import com.jeesite.modules.sys.service.EmpUserService;
+import com.jeesite.modules.sys.utils.DictUtils;
 import com.jeesite.modules.utils.NettyServerHandler;
 import org.apache.poi.ss.formula.functions.T;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
@@ -88,6 +89,8 @@ public class MesProductController extends BaseController {
 	@Autowired
 	private MesGp12IssueService mesGp12IssueService;
 	@Autowired
+	private MesRepairService mesRepairService;
+	@Autowired
 	private FileUploadService fileUploadService;
 
 
@@ -328,6 +331,96 @@ public class MesProductController extends BaseController {
 		return "modules/mes/mesScreen9";
 	}
 
+	/**
+	 * 返修区大屏数据:汇总统计 + 待处理返修清单(数据来源 mes_repair)
+	 * state: 1=待返修 2=待检查 3=已完成 4=返修NG待处理
+	 */
+	@RequestMapping(value = "screenRepairData")
+	@ResponseBody
+	public CommonResp screenRepairData() {
+		CommonResp<Map<String, Object>> resp = new CommonResp<>();
+		Map<String, Object> data = MapUtils.newHashMap();
+
+		LocalDate today = LocalDate.now();
+		LocalDate tomorrow = today.plusDays(1);
+		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+		String todayStart = today.format(formatter) + " 00:00:00";
+		String todayEnd = tomorrow.format(formatter) + " 00:00:00";
+
+		MesRepair pendingQuery = new MesRepair();
+		pendingQuery.setState("1");
+		Long pendingCount = mesRepairService.findCount(pendingQuery);
+
+		MesRepair checkingQuery = new MesRepair();
+		checkingQuery.setState("2");
+		Long checkingCount = mesRepairService.findCount(checkingQuery);
+
+		MesRepair todayDoneQuery = new MesRepair();
+		todayDoneQuery.setState("3");
+		todayDoneQuery.setResult("1");
+		todayDoneQuery.getSqlMap().getWhere().and("a.update_date", QueryType.GTE, todayStart);
+		todayDoneQuery.getSqlMap().getWhere().and("a.update_date", QueryType.LT, todayEnd);
+		Long todayDoneCount = mesRepairService.findCount(todayDoneQuery);
+
+		MesRepair todayFailQuery = new MesRepair();
+		todayFailQuery.setState("3");
+		todayFailQuery.setResult("2");
+		todayFailQuery.getSqlMap().getWhere().and("a.update_date", QueryType.GTE, todayStart);
+		todayFailQuery.getSqlMap().getWhere().and("a.update_date", QueryType.LT, todayEnd);
+		Long todayFailCount = mesRepairService.findCount(todayFailQuery);
+
+		Map<String, Object> summary = MapUtils.newHashMap();
+		summary.put("pending", pendingCount == null ? 0 : pendingCount);
+		summary.put("repairing", checkingCount == null ? 0 : checkingCount);
+		summary.put("todayDone", todayDoneCount == null ? 0 : todayDoneCount);
+		summary.put("todayFail", todayFailCount == null ? 0 : todayFailCount);
+		data.put("summary", summary);
+
+		// 未完成返修:排除已完成(state=3)
+		MesRepair listQuery = new MesRepair();
+		listQuery.getSqlMap().getWhere().and("a.state", QueryType.NE, "3");
+		listQuery.getSqlMap().getOrder().setOrderBy("a.update_date DESC");
+		List<MesRepair> repairList = mesRepairService.findList(listQuery);
+		if (repairList == null) {
+			repairList = ListUtils.newArrayList();
+		}
+		if (repairList.size() > 100) {
+			repairList = repairList.subList(0, 100);
+		}
+
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		List<Map<String, Object>> rows = ListUtils.newArrayList();
+		for (MesRepair item : repairList) {
+			Map<String, Object> row = MapUtils.newHashMap();
+			row.put("sn", item.getSn());
+			row.put("oprno", item.getOprno());
+			row.put("fxOprno", item.getFxOprno());
+			row.put("itemTitle", item.getItemTitle());
+			row.put("type", item.getType());
+			row.put("typeText", DictUtils.getDictLabel("mes_repair_type", item.getType(), ""));
+			row.put("remark", item.getRemark());
+			row.put("state", item.getState());
+			row.put("stateText", DictUtils.getDictLabel("mes_repair_state", item.getState(), "未知"));
+			row.put("result", item.getResult());
+			row.put("resultText", StringUtils.isEmpty(item.getResult())
+					? ""
+					: DictUtils.getDictLabel("mes_repair_result", item.getResult(), ""));
+			row.put("fxBy", item.getFxBy());
+			row.put("fxDate", item.getFxDate() == null ? "" : sdf.format(item.getFxDate()));
+			row.put("checkBy", item.getCheckBy());
+			row.put("checkDate", item.getCheckDate() == null ? "" : sdf.format(item.getCheckDate()));
+			row.put("lineSn", item.getLineSn());
+			row.put("createBy", item.getCreateBy());
+			row.put("createDate", item.getCreateDate() == null ? "" : sdf.format(item.getCreateDate()));
+			rows.add(row);
+		}
+		data.put("list", rows);
+
+		resp.setData(data);
+		resp.setResult(Global.TRUE);
+		return resp;
+	}
+
 	@RequestMapping(value = "screenOprnoRecordCount")
 	@ResponseBody
 	public CommonResp screenOprnoRecordCount(HttpServletRequest req) {
@@ -344,11 +437,11 @@ public class MesProductController extends BaseController {
 				if (item == null) {
 					continue;
 				}
-				String oprno = item.trim();
+				String oprno = toBaseOprno(item.trim());
 				if (StringUtils.isEmpty(oprno)) {
 					continue;
 				}
-				oprnoSet.add(oprno.toUpperCase(Locale.ROOT));
+				oprnoSet.add(oprno);
 			}
 		}
 
@@ -442,6 +535,19 @@ public class MesProductController extends BaseController {
 		return resp;
 	}
 
+	/**
+	 * 去掉工位号末尾字母后缀,例如 OP010A/OP010B -> OP010。
+	 * 查询时按主工位汇总所有子工位。
+	 */
+	private String toBaseOprno(String oprno) {
+		if (StringUtils.isEmpty(oprno)) {
+			return "";
+		}
+		String value = oprno.toUpperCase(Locale.ROOT).trim();
+		String base = value.replaceAll("[A-Z]+$", "");
+		return StringUtils.isEmpty(base) ? value : base;
+	}
+
 	private List<String> loadGp12IssueImages(String host, String bizKey, String bizType) {
 		List<String> urls = ListUtils.newArrayList();
 		FileUpload fu = new FileUpload();

+ 69 - 60
src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java

@@ -1135,6 +1135,11 @@ public class MesProductRecordController extends BaseController {
 			return ret;
 		}
 
+		// OK/NG样件:无需录入正式产品,直接放行
+		if(mesProductSampleService.checkSampleSn(CommonUitl.formatOprno(oldOprno), lineSn, sn)){
+			return bs+"UD";
+		}
+
 		// ========== 新增:当前产线生产类型限制 ==========
 		// 单部件工位扫精追码,不做整件产品类型限制
 		if (!mesLineProcess1.getType().equals("3") && !mesProductCateService.isCurrentProductAllowed(lineSn, sn)) {
@@ -1294,12 +1299,7 @@ public class MesProductRecordController extends BaseController {
 				return ret;
 			}
 
-			// 0.检查工件是否是样件,是样件可直接做
-			if(mesProductSampleService.checkSampleSn(oprno,lineSn,sn)){
-				return bs+"UD";  // 检查工件是否是样件
-			}
-
-			// 1.检查开班样件点检
+			// 1.检查开班样件点检(正式件才校验;样件已在上方提前放行)
 			if(!mesProductSampleService.checkRecord(oprno,lineSn,sn)){
 				return bs+"SJ";  // 未进行开班点检
 			}
@@ -1563,7 +1563,7 @@ public class MesProductRecordController extends BaseController {
 
 			//320添加子工位
 
-			// 判断是否是OK/NG样件
+			// 判断是否是OK/NG样件:样件只记点检结果,不走正式件过站
 			MesProductSample mesProductSample = new MesProductSample();
 			mesProductSample.setSn(sn);
 			mesProductSample.setOprno(CommonUitl.formatOprno(oprno));
@@ -1585,6 +1585,7 @@ public class MesProductRecordController extends BaseController {
 				}
 				mesProductSampleRecord.setResult(testResult);
 				mesProductSampleRecordService.save(mesProductSampleRecord);
+				return "RSOK";
 			}
 
 			if(isFlag){
@@ -2611,37 +2612,40 @@ public class MesProductRecordController extends BaseController {
 		String ret = checkQualityCommon(oldOprno,sn,lineSn,userCode);
 		String checkret = ret.substring(3,5);
 		if(checkret.equals("UD")){
+			boolean isSample = mesProductSampleService.checkSampleSn(oprno, lineSn, sn);
+			// 样件跳过最大NG次数、15分钟间隔限制
+			if(!isSample){
+				String qmMaxErr = validateQmMaxTimes(oldOprno, lineSn, sn, oprno);
+				if(qmMaxErr != null){
+					resp.setResult(Global.FALSE);
+					resp.setMessage(qmMaxErr);
+					return resp;
+				}
 
-			String qmMaxErr = validateQmMaxTimes(oldOprno, lineSn, sn, oprno);
-			if(qmMaxErr != null){
-				resp.setResult(Global.FALSE);
-				resp.setMessage(qmMaxErr);
-				return resp;
-			}
-
-			String check = Global.getConfig("mes.qm.ng.time");
-			if(check.equals("1")){
-				// 检查是否超过15分钟
-				MesProductQm mpr2 = new MesProductQm();
-				mpr2.setComponentNum(sn);
-				mpr2.getSqlMap().getWhere().and("a.oprno",QueryType.RIGHT_LIKE,oprno);
-				mpr2.getSqlMap().getOrder().setOrderBy("a.test_time desc");
-				MesProductQm mprinfo = mesProductQmService.findInfo(mpr2);
-				if(!ObjectUtils.isEmpty(mprinfo)){
-					// 计算两个日期之间相差的分钟数
-					Date curDate = new Date();
-					Date date2 = mprinfo.getTestTime();
-					long minutesBetween = Math.abs(ChronoUnit.MINUTES.between(curDate.toInstant(), date2.toInstant()));
-					logger.info("minutesBetween:"+ minutesBetween);
-					if(minutesBetween < 15){ // 未超过15分钟
-						resp.setResult(Global.FALSE);
-						resp.setMessage("两次气密必须间隔15分钟");
-						return resp;
+				String check = Global.getConfig("mes.qm.ng.time");
+				if(check.equals("1")){
+					// 检查是否超过15分钟
+					MesProductQm mpr2 = new MesProductQm();
+					mpr2.setComponentNum(sn);
+					mpr2.getSqlMap().getWhere().and("a.oprno",QueryType.RIGHT_LIKE,oprno);
+					mpr2.getSqlMap().getOrder().setOrderBy("a.test_time desc");
+					MesProductQm mprinfo = mesProductQmService.findInfo(mpr2);
+					if(!ObjectUtils.isEmpty(mprinfo)){
+						// 计算两个日期之间相差的分钟数
+						Date curDate = new Date();
+						Date date2 = mprinfo.getTestTime();
+						long minutesBetween = Math.abs(ChronoUnit.MINUTES.between(curDate.toInstant(), date2.toInstant()));
+						logger.info("minutesBetween:"+ minutesBetween);
+						if(minutesBetween < 15){ // 未超过15分钟
+							resp.setResult(Global.FALSE);
+							resp.setMessage("两次气密必须间隔15分钟");
+							return resp;
+						}
 					}
 				}
 			}
 
-			resp.setMessage("工件可以加工");
+			resp.setMessage(isSample ? "样件可以检测" : "工件可以加工");
 			resp.setResult(Global.TRUE);
 		}else{
 			String lx = ret.substring(0,3);
@@ -2712,9 +2716,12 @@ public class MesProductRecordController extends BaseController {
 
 		try{
 			String formatOprno = CommonUitl.formatOprno(oprno);
-			String qmMaxErr = validateQmMaxTimes(oprno, lineSn, sn, formatOprno);
-			if(qmMaxErr != null){
-				return renderResult(Global.FALSE, text(qmMaxErr));
+			boolean isSample = mesProductSampleService.checkSampleSn(oprno, lineSn, sn);
+			if(!isSample){
+				String qmMaxErr = validateQmMaxTimes(oprno, lineSn, sn, formatOprno);
+				if(qmMaxErr != null){
+					return renderResult(Global.FALSE, text(qmMaxErr));
+				}
 			}
 
 			MesProductQm mesProductQm = new MesProductQm();
@@ -2734,30 +2741,43 @@ public class MesProductRecordController extends BaseController {
 			mesProductQm.setRemark(remark);
 			mesProductQmService.add(mesProductQm);
 
-			// 检查是否是样件
-			if(mesProductSampleService.checkSampleSn(oprno,lineSn,sn)){ // 是样件
+			// 样件:只记点检结果,不走正式件过站
+			if(isSample){
 				MesProductSample mesProductSample = new MesProductSample();
 				mesProductSample.setSn(sn);
-				mesProductSample.setOprno(CommonUitl.formatOprno(oprno));
+				mesProductSample.setOprno(formatOprno);
+				mesProductSample.setLineSn(lineSn);
+				mesProductSample.setStatus("0");
 				MesProductSample mesProductSample1 = mesProductSampleService.findInfo(mesProductSample);
 				if(!ObjectUtils.isEmpty(mesProductSample1)){
-					double maxVal = StringUtils.isEmpty(mesProductSample1.getUpLimit())?Double.valueOf("0"):Double.valueOf(mesProductSample1.getUpLimit());
-					double minVal = StringUtils.isEmpty(mesProductSample1.getLowLimit())?Double.valueOf("0"):Double.valueOf(mesProductSample1.getLowLimit());
-					double val = StringUtils.isEmpty(leakVal)?Double.valueOf("0"):Double.valueOf(leakVal);
-					if(val > maxVal || val < minVal){
-						if(mesProductSample1.getType().equals("1")){ // OK件
-							result = "NG";
-						}else{
-							result = "OK";
+					String deviceResult = result;
+					// 仅配置了上下限时才按泄漏值校正设备结果
+					if(!StringUtils.isEmpty(mesProductSample1.getUpLimit()) || !StringUtils.isEmpty(mesProductSample1.getLowLimit())){
+						double maxVal = StringUtils.isEmpty(mesProductSample1.getUpLimit()) ? Double.MAX_VALUE : Double.valueOf(mesProductSample1.getUpLimit());
+						double minVal = StringUtils.isEmpty(mesProductSample1.getLowLimit()) ? -Double.MAX_VALUE : Double.valueOf(mesProductSample1.getLowLimit());
+						double val = StringUtils.isEmpty(leakVal) ? 0D : Double.valueOf(leakVal);
+						if(val > maxVal || val < minVal){
+							if("1".equals(mesProductSample1.getType())){ // OK件超限视为设备判坏
+								deviceResult = "NG";
+							}else{ // NG件超限视为设备判好(与配置区间不符)
+								deviceResult = "OK";
+							}
 						}
 					}
+					// 点检结果:OK件需设备OK,NG件需设备NG
+					String testResult = "OK";
+					if("1".equals(mesProductSample1.getType()) && !"OK".equalsIgnoreCase(deviceResult)){
+						testResult = "NG";
+					}else if("2".equals(mesProductSample1.getType()) && !"NG".equalsIgnoreCase(deviceResult)){
+						testResult = "NG";
+					}
 
 					MesProductSampleRecord mesProductSampleRecord = new MesProductSampleRecord();
 					mesProductSampleRecord.setSn(sn);
 					mesProductSampleRecord.setOprno(oprno);
 					mesProductSampleRecord.setLineSn(lineSn);
 					mesProductSampleRecord.setType(mesProductSample1.getType());
-					mesProductSampleRecord.setResult(result);
+					mesProductSampleRecord.setResult(testResult);
 					mesProductSampleRecord.setTestVal(leakVal);
 					mesProductSampleRecord.setUpLimit(mesProductSample1.getUpLimit());
 					mesProductSampleRecord.setLowLimit(mesProductSample1.getLowLimit());
@@ -2766,9 +2786,9 @@ public class MesProductRecordController extends BaseController {
 					mesProductSampleRecord.setStatus("0");
 					mesProductSampleRecordService.save(mesProductSampleRecord);
 				}
+				return renderResult(Global.TRUE, text("样件点检成功!"));
 			}
 
-
 			List<ParamsResp> params = new ArrayList<>();
 
 			String content = "NG";
@@ -2833,21 +2853,10 @@ public class MesProductRecordController extends BaseController {
 	@ResponseBody
 	public Page<MesProductRecord> workData(MesProductRecordUser mesProductRecordUser,MesProductRecord mesProductRecord, HttpServletRequest request, HttpServletResponse response) {
 
-		Date edate = new Date();
-
-		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-		Calendar calendar = Calendar.getInstance();
-		calendar.add(Calendar.HOUR, -48); // 减去48小时
-		Date sdate = calendar.getTime();
-
-		mesProductRecord.setUpdateDate_gte(sdate);
-		mesProductRecord.setUpdateDate_lte(edate);
-
 		if(mesProductRecord.getOprno().length() == 5){
 			mesProductRecord.setOprno(mesProductRecord.getOprno()+"A");
 		}
 
-
 		Page page1 = new Page<>(request, response);
 		mesProductRecord.setPage(page1);
 		mesProductRecord.setCraft("100000");

+ 6 - 1
src/main/resources/config/application.yml

@@ -566,8 +566,9 @@ shiro:
 
   # 记住我密钥设置,你可以通过 com.jeesite.test.RememberMeKeyGen 类快速生成一个秘钥。
   # 若不设置,则每次启动系统后自动生成一个新秘钥,这样会导致每次重启后,客户端记录的用户信息将失效。
+  # 大屏等长期打开的页面依赖会话时,固定密钥可避免每次发版重启后必须重新登录。
   rememberMe:
-    secretKey: ~
+    secretKey: mescloud-screen-remember-me-key-20250731
 
 #  # 指定获取客户端IP的Header名称,防止IP伪造。指定为空,则使用原生方法获取IP。
 #  remoteAddrHeaderName: X-Forwarded-For
@@ -626,7 +627,11 @@ shiro:
     ${adminPath}/mes/mesProduct/screen7 = anon
     ${adminPath}/mes/mesProduct/screen8 = anon
     ${adminPath}/mes/mesProduct/screen9 = anon
+    ${adminPath}/mes/mesProduct/screenRepairData = anon
     ${adminPath}/mes/mesProduct/screenData = anon
+    ${adminPath}/mes/mesProduct/screenData4 = anon
+    ${adminPath}/mes/mesProduct/screenOprnoRecordCount = anon
+    ${adminPath}/mes/mesProduct/screenGp12IssueList = anon
     ${adminPath}/mes/mesProductCcd/testDate = anon
     ${adminPath}/mes/mesProductCcd/add = anon
     ${adminPath}/mes/mesProductRecord/result = anon

+ 17 - 5
src/main/resources/mappings/modules/mes/MesProductRecordDao.xml

@@ -41,17 +41,29 @@
             LIMIT 20000
     </select>
     <select id="findRecordCountByOprnos" resultType="java.util.HashMap">
-        SELECT oprno, COUNT(1) AS recordCount
+        SELECT
+            CASE
+            <foreach collection="oprnos" item="oprno">
+                WHEN oprno = #{oprno} OR oprno REGEXP CONCAT('^', #{oprno}, '[A-Z]+$') THEN #{oprno}
+            </foreach>
+            END AS oprno,
+            COUNT(1) AS recordCount
         FROM mes_product_record
-        WHERE oprno IN
-        <foreach collection="oprnos" item="oprno" open="(" separator="," close=")">
-            #{oprno}
+        WHERE content = 'OK'
+        AND
+        <foreach collection="oprnos" item="oprno" open="(" separator=" OR " close=")">
+            oprno = #{oprno} OR oprno REGEXP CONCAT('^', #{oprno}, '[A-Z]+$')
         </foreach>
         <if test="dateScope == 'today'">
             AND create_date &gt;= CURDATE()
             AND create_date &lt; DATE_ADD(CURDATE(), INTERVAL 1 DAY)
         </if>
-        GROUP BY oprno
+        GROUP BY
+            CASE
+            <foreach collection="oprnos" item="oprno">
+                WHEN oprno = #{oprno} OR oprno REGEXP CONCAT('^', #{oprno}, '[A-Z]+$') THEN #{oprno}
+            </foreach>
+            END
     </select>
     <update id="updateAll">
        update mes_product_record

+ 16 - 5
src/main/resources/static/screen/gp12-issue-screen.js

@@ -124,18 +124,31 @@
 		$.ajax({
 			url: apiUrl,
 			type: "GET",
-			dataType: "json"
+			dataType: "json",
+			timeout: 20000
 		}).done(function (res) {
 			if (res && res.result === "true" && $.isArray(res.data)) {
 				applyData(res.data);
-			} else {
+				scheduleRefresh(config.refreshInterval || 60000);
+			} else if (!state.items.length) {
 				applyData([]);
+				scheduleRefresh(5000);
+			} else {
+				scheduleRefresh(5000);
 			}
 		}).fail(function () {
-			applyData([]);
+			// 保留上次成功数据,服务恢复后自动重试
+			scheduleRefresh(5000);
 		});
 	}
 
+	function scheduleRefresh(delay) {
+		if (state.refreshTimer) {
+			clearTimeout(state.refreshTimer);
+		}
+		state.refreshTimer = setTimeout(loadData, delay || 60000);
+	}
+
 	function fitScreen() {
 		var width = $(window).width();
 		var height = $(window).height();
@@ -177,8 +190,6 @@
 		setInterval(updateClock, 1000);
 		bindPreview();
 		loadData();
-
-		state.refreshTimer = setInterval(loadData, config.refreshInterval || 60000);
 		fitScreen();
 		$(window).on("resize", fitScreen);
 	}

+ 16 - 0
src/main/resources/static/screen/oprno-record-screen.css

@@ -34,6 +34,7 @@ body {
 }
 
 .screen-header {
+	position: relative;
 	display: grid;
 	grid-template-columns: 1fr auto 1fr;
 	align-items: center;
@@ -56,6 +57,21 @@ body {
 	color: #9ad8ff;
 }
 
+.screen-conn-tip {
+	position: absolute;
+	right: 0;
+	top: 58px;
+	padding: 4px 12px;
+	border: 1px solid rgba(255, 176, 32, 0.45);
+	border-radius: 4px;
+	background: rgba(48, 28, 0, 0.82);
+	color: #ffd28a;
+	font-size: 14px;
+	line-height: 1.4;
+	white-space: nowrap;
+	z-index: 8;
+}
+
 .summary-row {
 	display: grid;
 	grid-template-columns: repeat(3, 1fr);

+ 128 - 13
src/main/resources/static/screen/oprno-record-screen.js

@@ -1,5 +1,7 @@
 (function () {
 	var config = window.screenOprnoConfig || {};
+	var NORMAL_POLL_MS = 1000 * 60;
+	var RETRY_POLL_MS = 1000 * 5;
 	var tableAutoScroll = {
 		timer: null,
 		pauseTimer: null,
@@ -13,16 +15,20 @@
 		chart: null,
 		scope: config.defaultScope === "today" ? "today" : "all",
 		panelCollapsed: false,
-		seriesName: "全部生产数量"
+		seriesName: "全部生产数量",
+		refreshing: false,
+		failCount: 0,
+		pollTimer: null,
+		hasLoaded: false
 	};
 
 	$(function () {
 		changeZoom();
 		initPage();
 		bindDelegatedEvents();
+		ensureConnTip();
 		refreshData();
 		setInterval(updateTime, 1000);
-		setInterval(refreshData, 1000 * 60);
 		$(window).resize(function () {
 			changeZoom();
 			if (state.chart) {
@@ -52,7 +58,7 @@
 			state.oprnos = normalizeOprnos($("#oprnoInput").val());
 			renderInput();
 			renderTags();
-			refreshData();
+			refreshData(true);
 		});
 		$("#clearBtn").on("click", function () {
 			state.oprnos = [];
@@ -87,7 +93,7 @@
 		state.scope = scope;
 		syncScopeTabs();
 		updateScopeLabels();
-		refreshData();
+		refreshData(true);
 	}
 
 	function syncScopeTabs() {
@@ -170,7 +176,7 @@
 		$("#addOprno").val("");
 		renderInput();
 		renderTags();
-		refreshData();
+		refreshData(true);
 	}
 
 	function removeOprno(oprno) {
@@ -179,7 +185,7 @@
 		});
 		renderInput();
 		renderTags();
-		refreshData();
+		refreshData(true);
 	}
 
 	function renderInput() {
@@ -199,12 +205,88 @@
 		$("#oprnoTotal").text(state.oprnos.length);
 	}
 
-	function refreshData() {
+	function ensureConnTip() {
+		if ($("#screenConnTip").length) {
+			return;
+		}
+		var $header = $(".screen-header");
+		if (!$header.length) {
+			return;
+		}
+		$header.append('<div class="screen-conn-tip" id="screenConnTip" style="display:none;"></div>');
+	}
+
+	function showConnTip(text) {
+		ensureConnTip();
+		var $tip = $("#screenConnTip");
+		if (!$tip.length) {
+			return;
+		}
+		$tip.text(text || "").show();
+	}
+
+	function hideConnTip() {
+		$("#screenConnTip").hide().text("");
+	}
+
+	function scheduleNextRefresh(delay) {
+		if (state.pollTimer) {
+			clearTimeout(state.pollTimer);
+		}
+		state.pollTimer = setTimeout(function () {
+			refreshData(false);
+		}, delay || NORMAL_POLL_MS);
+	}
+
+	function isAuthFailure(xhr) {
+		if (!xhr) {
+			return false;
+		}
+		if (xhr.status === 401 || xhr.status === 403) {
+			return true;
+		}
+		var text = xhr.responseText || "";
+		if (!text) {
+			return false;
+		}
+		// 会话失效时 Shiro 常返回登录页 HTML,而不是 JSON
+		return /<html[\s>]/i.test(text) && /login/i.test(text);
+	}
+
+	function tryRecoverSession() {
+		var key = "oprnoScreenReloadedAt";
+		var now = Date.now();
+		var last = Number(sessionStorage.getItem(key) || 0);
+		// 30 秒内只自动刷新一次,避免死循环
+		if (now - last < 30000) {
+			showConnTip("登录已失效,正在重试连接…");
+			scheduleNextRefresh(RETRY_POLL_MS);
+			return;
+		}
+		sessionStorage.setItem(key, String(now));
+		showConnTip("登录已失效,正在自动刷新页面…");
+		setTimeout(function () {
+			window.location.reload();
+		}, 800);
+	}
+
+	function refreshData(immediate) {
+		if (state.pollTimer) {
+			clearTimeout(state.pollTimer);
+			state.pollTimer = null;
+		}
+		if (state.refreshing) {
+			scheduleNextRefresh(RETRY_POLL_MS);
+			return;
+		}
 		if (!state.oprnos.length) {
 			state.data = [];
 			renderData();
+			hideConnTip();
+			scheduleNextRefresh(NORMAL_POLL_MS);
 			return;
 		}
+		state.refreshing = true;
 		$.ajax({
 			type: "POST",
 			url: "/js/a/mes/mesProduct/screenOprnoRecordCount",
@@ -213,17 +295,40 @@
 				dateScope: state.scope
 			},
 			dataType: "json",
+			timeout: 20000,
 			success: function (ret) {
+				state.refreshing = false;
 				if (ret && ret.result === "true") {
+					state.failCount = 0;
+					state.hasLoaded = true;
 					state.data = ret.data || [];
-				} else {
+					hideConnTip();
+					renderData();
+					scheduleNextRefresh(immediate ? NORMAL_POLL_MS : NORMAL_POLL_MS);
+					return;
+				}
+				state.failCount += 1;
+				// 失败时保留上次成功数据,避免大屏被清空看起来像卡住
+				if (!state.hasLoaded) {
 					state.data = [];
+					renderData();
 				}
-				renderData();
+				showConnTip("数据刷新失败," + Math.ceil(RETRY_POLL_MS / 1000) + " 秒后重试…");
+				scheduleNextRefresh(RETRY_POLL_MS);
 			},
-			error: function () {
-				state.data = [];
-				renderData();
+			error: function (xhr) {
+				state.refreshing = false;
+				state.failCount += 1;
+				if (isAuthFailure(xhr)) {
+					tryRecoverSession();
+					return;
+				}
+				if (!state.hasLoaded) {
+					state.data = [];
+					renderData();
+				}
+				showConnTip("服务连接中," + Math.ceil(RETRY_POLL_MS / 1000) + " 秒后重试…");
+				scheduleNextRefresh(RETRY_POLL_MS);
 			}
 		});
 	}
@@ -434,13 +539,23 @@
 		return max.oprno + " / " + Number(max.recordCount || 0);
 	}
 
+	function toBaseOprno(oprno) {
+		var value = $.trim(oprno || "").toUpperCase();
+		if (!value) {
+			return "";
+		}
+		// OP010A/OP010B -> OP010,主工位号汇总所有子工位
+		var base = value.replace(/[A-Z]+$/, "");
+		return base || value;
+	}
+
 	function normalizeOprnos(input) {
 		var source = Array.isArray(input) ? input.join(",") : String(input || "");
 		var items = source.split(/[,,;;\s]+/);
 		var map = {};
 		var ret = [];
 		items.forEach(function (item) {
-			var oprno = $.trim(item || "").toUpperCase();
+			var oprno = toBaseOprno(item);
 			if (!oprno || map[oprno]) {
 				return;
 			}

+ 326 - 0
src/main/resources/static/screen/repair-screen.css

@@ -0,0 +1,326 @@
+html,
+body {
+	width: 100%;
+	height: 100%;
+	margin: 0;
+	overflow: hidden;
+	background: #061326;
+	font-family: "Microsoft YaHei", Arial, sans-serif;
+}
+
+.screen-page {
+	position: relative;
+	width: 1920px;
+	height: 1080px;
+	color: #fff;
+	overflow: hidden;
+}
+
+.screen-page .bg {
+	position: absolute;
+	inset: 0;
+	width: 100%;
+	height: 100%;
+	object-fit: cover;
+	z-index: 0;
+}
+
+.screen-content {
+	position: relative;
+	z-index: 1;
+	height: 100%;
+	padding: 32px 48px 42px;
+	box-sizing: border-box;
+}
+
+.screen-header {
+	display: grid;
+	grid-template-columns: 1fr auto 1fr;
+	align-items: center;
+	height: 86px;
+}
+
+.screen-title-new {
+	grid-column: 2;
+	color: #fff;
+	font-size: 40px;
+	font-weight: 700;
+	text-align: center;
+	letter-spacing: 4px;
+	text-shadow: 0 0 18px rgba(0, 246, 255, 0.45);
+}
+
+.screen-time {
+	justify-self: end;
+	font-size: 20px;
+	color: #9ad8ff;
+}
+
+.summary-row {
+	display: grid;
+	grid-template-columns: repeat(4, 1fr);
+	gap: 20px;
+	margin-top: 12px;
+}
+
+.summary-item {
+	height: 116px;
+	padding: 18px 24px;
+	border: 1px solid rgba(0, 143, 253, 0.34);
+	border-radius: 6px;
+	background: linear-gradient(180deg, rgba(5, 37, 78, 0.88), rgba(5, 25, 54, 0.76));
+	box-shadow: inset 0 0 30px rgba(0, 143, 253, 0.12);
+	box-sizing: border-box;
+}
+
+.summary-item.is-pending {
+	border-color: rgba(255, 184, 0, 0.42);
+	box-shadow: inset 0 0 30px rgba(255, 184, 0, 0.1);
+}
+
+.summary-item.is-repairing {
+	border-color: rgba(0, 246, 255, 0.42);
+	box-shadow: inset 0 0 30px rgba(0, 246, 255, 0.1);
+}
+
+.summary-item.is-done {
+	border-color: rgba(64, 222, 120, 0.42);
+	box-shadow: inset 0 0 30px rgba(64, 222, 120, 0.1);
+}
+
+.summary-item.is-fail {
+	border-color: rgba(255, 80, 80, 0.42);
+	box-shadow: inset 0 0 30px rgba(255, 80, 80, 0.1);
+}
+
+.summary-label {
+	font-size: 20px;
+	color: #9ad8ff;
+}
+
+.summary-item.is-pending .summary-label {
+	color: #ffd27a;
+}
+
+.summary-item.is-done .summary-label {
+	color: #8dffb5;
+}
+
+.summary-item.is-fail .summary-label {
+	color: #ffb0b0;
+}
+
+.summary-value {
+	margin-top: 12px;
+	color: #40de78;
+	font-size: 42px;
+	font-weight: 700;
+	line-height: 1;
+}
+
+.summary-item.is-pending .summary-value {
+	color: #ffb800;
+}
+
+.summary-item.is-repairing .summary-value {
+	color: #00f6ff;
+}
+
+.summary-item.is-done .summary-value {
+	color: #40de78;
+}
+
+.summary-item.is-fail .summary-value {
+	color: #ff6b6b;
+}
+
+.main-panel {
+	height: 770px;
+	margin-top: 22px;
+	border: 1px solid rgba(0, 143, 253, 0.34);
+	border-radius: 6px;
+	background: rgba(4, 20, 45, 0.78);
+	box-shadow: inset 0 0 40px rgba(0, 143, 253, 0.14);
+	box-sizing: border-box;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+}
+
+.panel-title {
+	height: 56px;
+	line-height: 56px;
+	padding: 0 24px;
+	color: #00f6ff;
+	font-size: 22px;
+	font-weight: 700;
+	border-bottom: 1px solid rgba(0, 143, 253, 0.24);
+	flex-shrink: 0;
+}
+
+.panel-title .page-indicator {
+	float: right;
+	color: #9ad8ff;
+	font-size: 18px;
+	font-weight: 400;
+}
+
+.table-wrap {
+	flex: 1;
+	min-height: 0;
+	overflow: hidden;
+	padding: 0 8px 10px;
+}
+
+.repair-table {
+	width: 100%;
+	height: 100%;
+	border-collapse: collapse;
+	table-layout: fixed;
+	text-align: center;
+}
+
+.repair-table thead {
+	display: table;
+	width: 100%;
+	table-layout: fixed;
+}
+
+.repair-table tbody {
+	display: block;
+	height: calc(100% - 54px);
+	overflow: hidden;
+}
+
+.repair-table tbody tr {
+	display: table;
+	width: 100%;
+	table-layout: fixed;
+	height: 88px;
+}
+
+.repair-table th,
+.repair-table td {
+	padding: 0 10px;
+	border-bottom: 1px solid rgba(0, 143, 253, 0.18);
+	box-sizing: border-box;
+	word-break: break-all;
+}
+
+.repair-table th {
+	height: 54px;
+	color: #9ad8ff;
+	font-size: 18px;
+	font-weight: 700;
+	background: rgba(20, 55, 120, 0.55);
+}
+
+.repair-table td {
+	font-size: 18px;
+	color: #e8f6ff;
+	vertical-align: middle;
+}
+
+.repair-table tbody tr:nth-child(odd) {
+	background: rgba(0, 143, 253, 0.06);
+}
+
+.repair-table tbody tr:nth-child(even) {
+	background: rgba(0, 40, 80, 0.22);
+}
+
+.repair-table .col-seq {
+	width: 70px;
+}
+
+.repair-table .col-sn {
+	width: 280px;
+}
+
+.repair-table .col-oprno {
+	width: 120px;
+}
+
+.repair-table .col-item {
+	width: 180px;
+}
+
+.repair-table .col-type {
+	width: 110px;
+}
+
+.repair-table .col-remark {
+	width: 240px;
+	text-align: left;
+}
+
+.repair-table .col-state {
+	width: 120px;
+}
+
+.repair-table .col-user {
+	width: 110px;
+}
+
+.repair-table .col-time {
+	width: 180px;
+}
+
+.state-tag {
+	display: inline-block;
+	min-width: 84px;
+	padding: 4px 10px;
+	border-radius: 4px;
+	font-size: 16px;
+	font-weight: 700;
+	line-height: 1.4;
+}
+
+.state-tag.is-pending {
+	color: #ffb800;
+	background: rgba(255, 184, 0, 0.16);
+	border: 1px solid rgba(255, 184, 0, 0.4);
+}
+
+.state-tag.is-repairing {
+	color: #00f6ff;
+	background: rgba(0, 246, 255, 0.14);
+	border: 1px solid rgba(0, 246, 255, 0.4);
+}
+
+.state-tag.is-done {
+	color: #40de78;
+	background: rgba(64, 222, 120, 0.14);
+	border: 1px solid rgba(64, 222, 120, 0.4);
+}
+
+.state-tag.is-fail {
+	color: #ff6b6b;
+	background: rgba(255, 80, 80, 0.14);
+	border: 1px solid rgba(255, 80, 80, 0.4);
+}
+
+.state-tag.is-other {
+	color: #9ad8ff;
+	background: rgba(0, 143, 253, 0.14);
+	border: 1px solid rgba(0, 143, 253, 0.35);
+}
+
+.empty-row td {
+	height: 240px;
+	color: #7aa7c7;
+	font-size: 22px;
+}
+
+#repairBody {
+	opacity: 1;
+	transition: opacity 0.45s ease;
+}
+
+#repairBody.is-fading {
+	opacity: 0;
+}
+
+#repairBody.is-visible {
+	opacity: 1;
+}

+ 221 - 0
src/main/resources/static/screen/repair-screen.js

@@ -0,0 +1,221 @@
+(function ($, window) {
+	"use strict";
+
+	var config = window.repairScreenConfig || {};
+	var state = {
+		items: [],
+		currentPage: 0,
+		totalPages: 0,
+		carouselTimer: null,
+		refreshTimer: null
+	};
+
+	function pad(num) {
+		return num < 10 ? "0" + num : String(num);
+	}
+
+	function updateClock() {
+		var now = new Date();
+		var text = now.getFullYear() + "-" + pad(now.getMonth() + 1) + "-" + pad(now.getDate()) +
+			" " + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());
+		$("#screenTime").text(text);
+	}
+
+	function escapeHtml(text) {
+		return String(text == null ? "" : text)
+			.replace(/&/g, "&amp;")
+			.replace(/</g, "&lt;")
+			.replace(/>/g, "&gt;")
+			.replace(/"/g, "&quot;");
+	}
+
+	function stateClass(stateCode) {
+		if (stateCode === "1") {
+			return "is-pending";
+		}
+		if (stateCode === "2") {
+			return "is-repairing";
+		}
+		if (stateCode === "3") {
+			return "is-done";
+		}
+		if (stateCode === "4") {
+			return "is-fail";
+		}
+		return "is-other";
+	}
+
+	function renderSummary(summary) {
+		summary = summary || {};
+		$("#pendingCount").text(summary.pending || 0);
+		$("#repairingCount").text(summary.repairing || 0);
+		$("#todayDoneCount").text(summary.todayDone || 0);
+		$("#todayFailCount").text(summary.todayFail || 0);
+	}
+
+	function renderPage(pageIndex, animate) {
+		var pageSize = config.pageSize || 7;
+		var start = pageIndex * pageSize;
+		var pageData = state.items.slice(start, start + pageSize);
+		var html = "";
+
+		if (!pageData.length) {
+			html = '<tr class="empty-row"><td colspan="9">暂无待处理返修数据</td></tr>';
+		} else {
+			pageData.forEach(function (item, index) {
+				var seq = start + index + 1;
+				html += "<tr>" +
+					'<td class="col-seq">' + seq + "</td>" +
+					'<td class="col-sn">' + escapeHtml(item.sn) + "</td>" +
+					'<td class="col-oprno">' + escapeHtml(item.oprno || "--") + "</td>" +
+					'<td class="col-item">' + escapeHtml(item.itemTitle || "--") + "</td>" +
+					'<td class="col-type">' + escapeHtml(item.typeText || "--") + "</td>" +
+					'<td class="col-remark">' + escapeHtml(item.remark || "--") + "</td>" +
+					'<td class="col-state"><span class="state-tag ' + stateClass(item.state) + '">' +
+					escapeHtml(item.stateText || "--") + "</span></td>" +
+					'<td class="col-user">' + escapeHtml(item.createBy || "--") + "</td>" +
+					'<td class="col-time">' + escapeHtml(item.createDate || "--") + "</td>" +
+					"</tr>";
+			});
+		}
+
+		var $body = $("#repairBody");
+		if (!animate) {
+			$body.removeClass("is-fading").addClass("is-visible").html(html);
+			return;
+		}
+
+		$body.removeClass("is-visible").addClass("is-fading");
+		setTimeout(function () {
+			$body.html(html);
+			$body.removeClass("is-fading").addClass("is-visible");
+		}, 450);
+	}
+
+	function updatePageIndicator() {
+		state.totalPages = Math.max(1, Math.ceil(state.items.length / (config.pageSize || 7)));
+		$("#pageIndicator").text("第 " + (state.currentPage + 1) + " / " + state.totalPages + " 页");
+	}
+
+	function startCarousel() {
+		stopCarousel();
+		if (state.totalPages <= 1) {
+			return;
+		}
+		state.carouselTimer = setInterval(function () {
+			state.currentPage++;
+			if (state.currentPage >= state.totalPages) {
+				state.currentPage = 0;
+			}
+			updatePageIndicator();
+			renderPage(state.currentPage, true);
+		}, config.carouselInterval || 8000);
+	}
+
+	function stopCarousel() {
+		if (state.carouselTimer) {
+			clearInterval(state.carouselTimer);
+			state.carouselTimer = null;
+		}
+	}
+
+	function applyData(payload) {
+		payload = payload || {};
+		renderSummary(payload.summary);
+		state.items = $.isArray(payload.list) ? payload.list : [];
+		state.currentPage = 0;
+		updatePageIndicator();
+		renderPage(0, false);
+		startCarousel();
+	}
+
+	function showLoadError(message) {
+		renderSummary({});
+		state.items = [];
+		state.currentPage = 0;
+		updatePageIndicator();
+		$("#repairBody").removeClass("is-fading").addClass("is-visible").html(
+			'<tr class="empty-row"><td colspan="9">' + escapeHtml(message || "数据加载失败") + "</td></tr>"
+		);
+		stopCarousel();
+	}
+
+	function loadData() {
+		var apiUrl = config.apiUrl;
+		if (!apiUrl) {
+			showLoadError("未配置数据接口");
+			return;
+		}
+
+		$.ajax({
+			url: apiUrl,
+			type: "GET",
+			dataType: "json",
+			timeout: 20000
+		}).done(function (res) {
+			if (res && (res.result === "true" || res.result === true) && res.data) {
+				applyData(res.data);
+				scheduleRefresh(config.refreshInterval || 60000);
+			} else {
+				showLoadError((res && res.message) ? res.message : "接口返回异常");
+				scheduleRefresh(5000);
+			}
+		}).fail(function (xhr) {
+			var msg = "数据加载失败";
+			if (xhr && xhr.status === 401) {
+				msg = "接口未授权,请检查匿名访问配置";
+			} else if (xhr && xhr.status === 404) {
+				msg = "接口不存在,请确认服务已重启";
+			} else if (xhr && (xhr.status === 0 || xhr.status >= 500)) {
+				msg = "服务连接中,稍后自动重试…";
+			} else if (xhr && xhr.status) {
+				msg = "数据加载失败(HTTP " + xhr.status + ")";
+			}
+			// 保留上次成功数据,避免发版重启时大屏被清空
+			if (!state.items.length) {
+				showLoadError(msg);
+			}
+			scheduleRefresh(5000);
+		});
+	}
+
+	function scheduleRefresh(delay) {
+		if (state.refreshTimer) {
+			clearTimeout(state.refreshTimer);
+		}
+		state.refreshTimer = setTimeout(loadData, delay || 60000);
+	}
+
+	function fitScreen() {
+		var width = $(window).width();
+		var height = $(window).height();
+		var designWidth = 1920;
+		var designHeight = 1080;
+		var scale = Math.min(width / designWidth, height / designHeight);
+		var marginLeft = (width - designWidth * scale) / 2;
+		var marginTop = (height - designHeight * scale) / 2;
+
+		$("body").css({
+			transform: "scale(" + scale + ")",
+			transformOrigin: "0 0",
+			width: designWidth + "px",
+			height: designHeight + "px",
+			overflow: "hidden",
+			marginLeft: marginLeft + "px",
+			marginTop: marginTop + "px"
+		});
+	}
+
+	function init() {
+		$("#screenTitle").text(config.title || "返修区看板");
+		updateClock();
+		setInterval(updateClock, 1000);
+		loadData();
+		fitScreen();
+		$(window).on("resize", fitScreen);
+	}
+
+	$(function () {
+		init();
+	});
+})(jQuery, window);

+ 0 - 8
src/main/resources/views/modules/mes/mesProductCmtInfo.html

@@ -30,13 +30,9 @@
                             <th>电压1</th>
                             <th>电流1</th>
                             <th>送丝速度1</th>
-                            <th>错误码1</th>
-                            <th>JOB号1</th>
                             <th>电压2</th>
                             <th>电流2</th>
                             <th>送丝速度2</th>
-                            <th>错误码2</th>
-                            <th>JOB号2</th>
                             <th>记录时间</th>
                         </tr>
                     </thead>
@@ -45,13 +41,9 @@
                             <td>{{ item.V1 }}</td>
                             <td>{{ item.I1 }}</td>
                             <td>{{ item.speed1 }}</td>
-                            <td>{{ item.code1 }}</td>
-                            <td>{{ item.JOB1 }}</td>
                             <td>{{ item.V2 }}</td>
                             <td>{{ item.I2 }}</td>
                             <td>{{ item.speed2 }}</td>
-                            <td>{{ item.code2 }}</td>
-                            <td>{{ item.JOB2 }}</td>
                             <td> {{ item.createDate }}</td>
                         </tr>
                     </tbody>

+ 3 - 3
src/main/resources/views/modules/mes/mesScreen5.html

@@ -5,7 +5,7 @@
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
 	<meta http-equiv="X-UA-Compatible" content="ie=edge">
 	<title>MES工位加工记录统计大屏</title>
-	<link rel="stylesheet" href="${ctxStatic}/screen/oprno-record-screen.css?v=202506244">
+	<link rel="stylesheet" href="${ctxStatic}/screen/oprno-record-screen.css?v=20250731">
 	<style>
 		.screen-header .header-left-tools {
 			justify-self: start;
@@ -183,9 +183,9 @@
 <script>
 	window.screenOprnoConfig = {
 		title: "华为项目加工记录统计大屏",
-		defaultOprnos: ["OP010A", "OP050A", "OP060A", "OP070A", "OP070B", "OP080A", "OP090A", "OP090B", "OP090C", "OP100A", "OP100B", "OP100C"]
+		defaultOprnos: ["OP010", "OP050", "OP060", "OP070", "OP080", "OP090", "OP100"]
 	};
 </script>
-<script src="${ctxStatic}/screen/oprno-record-screen.js?v=202506244"></script>
+<script src="${ctxStatic}/screen/oprno-record-screen.js?v=20250731"></script>
 </body>
 </html>

+ 3 - 3
src/main/resources/views/modules/mes/mesScreen6.html

@@ -5,7 +5,7 @@
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
 	<meta http-equiv="X-UA-Compatible" content="ie=edge">
 	<title>MES工位加工记录统计大屏</title>
-	<link rel="stylesheet" href="${ctxStatic}/screen/oprno-record-screen.css?v=202506244">
+	<link rel="stylesheet" href="${ctxStatic}/screen/oprno-record-screen.css?v=20250731">
 	<style>
 		.screen-header .header-left-tools {
 			justify-self: start;
@@ -183,9 +183,9 @@
 <script>
 	window.screenOprnoConfig = {
 		title: "华为项目加工记录统计大屏",
-		defaultOprnos: ["OP110A", "OP110B", "OP120A", "OP120B", "OP130A", "OP140A", "OP150A", "OP160A", "OP160B", "OP170A", "OP180A", "OP180B", "OP190A", "OP190B"]
+		defaultOprnos: ["OP110", "OP120", "OP130", "OP140", "OP150", "OP160", "OP170", "OP180", "OP190"]
 	};
 </script>
-<script src="${ctxStatic}/screen/oprno-record-screen.js?v=202506244"></script>
+<script src="${ctxStatic}/screen/oprno-record-screen.js?v=20250731"></script>
 </body>
 </html>

+ 3 - 3
src/main/resources/views/modules/mes/mesScreen7.html

@@ -5,7 +5,7 @@
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
 	<meta http-equiv="X-UA-Compatible" content="ie=edge">
 	<title>MES工位加工记录统计大屏</title>
-	<link rel="stylesheet" href="${ctxStatic}/screen/oprno-record-screen.css?v=202506244">
+	<link rel="stylesheet" href="${ctxStatic}/screen/oprno-record-screen.css?v=20250731">
 	<style>
 		.screen-header .header-left-tools {
 			justify-self: start;
@@ -183,9 +183,9 @@
 <script>
 	window.screenOprnoConfig = {
 		title: "华为项目加工记录统计大屏",
-		defaultOprnos: ["OP200A", "OP210A", "OP220A", "OP230A", "OP240A", "OP240B", "OP250A", "OP260A", "OP270A", "OP270B", "OP280A", "OP290A", "OP300A", "OP310A", "OP320A", "OP330A", "OP340A", "OP350A", "OP360A"]
+		defaultOprnos: ["OP200", "OP210", "OP220", "OP230", "OP240", "OP250", "OP260", "OP270", "OP280", "OP290", "OP300", "OP310", "OP320", "OP330", "OP340", "OP350", "OP360"]
 	};
 </script>
-<script src="${ctxStatic}/screen/oprno-record-screen.js?v=202506244"></script>
+<script src="${ctxStatic}/screen/oprno-record-screen.js?v=20250731"></script>
 </body>
 </html>

+ 48 - 164
src/main/resources/views/modules/mes/mesScreen9.html

@@ -4,188 +4,72 @@
 	<meta charset="UTF-8">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
 	<meta http-equiv="X-UA-Compatible" content="ie=edge">
-	<title>MES工位加工记录统计大屏</title>
-	<link rel="stylesheet" href="${ctxStatic}/screen/oprno-record-screen.css?v=202506244">
-	<style>
-		.screen-header .header-left-tools {
-			justify-self: start;
-			display: flex;
-			align-items: center;
-		}
-
-		.scope-tab-group {
-			display: inline-flex;
-			padding: 4px;
-			border: 1px solid rgba(0, 143, 253, 0.34);
-			border-radius: 6px;
-			background: rgba(4, 20, 45, 0.78);
-			box-shadow: inset 0 0 24px rgba(0, 143, 253, 0.1);
-		}
-
-		.scope-tab {
-			min-width: 112px;
-			height: 40px;
-			padding: 0 16px;
-			border: none;
-			border-radius: 4px;
-			background: transparent;
-			color: #9ad8ff;
-			font-size: 18px;
-			font-family: inherit;
-			cursor: pointer;
-			transition: color 0.2s, background 0.2s, box-shadow 0.2s;
-		}
-
-		.scope-tab.is-active {
-			color: #fff;
-			background: linear-gradient(180deg, rgba(64, 222, 120, 0.32), rgba(0, 143, 253, 0.24));
-			box-shadow: 0 0 14px rgba(64, 222, 120, 0.22);
-		}
-
-		.scope-tab:not(.is-active):hover {
-			color: #d8f4ff;
-			background: rgba(0, 143, 253, 0.16);
-		}
-
-		.main-grid-wrap {
-			position: relative;
-			display: flex;
-			align-items: stretch;
-			height: 770px;
-			margin-top: 22px;
-		}
-
-		.main-grid-wrap .side-config-panel {
-			width: 470px;
-			flex-shrink: 0;
-			overflow: hidden;
-			transition: width 0.28s ease, opacity 0.28s ease, margin 0.28s ease;
-		}
-
-		.main-grid-wrap.is-folded .side-config-panel {
-			width: 0;
-			opacity: 0;
-			margin-right: 0;
-			pointer-events: none;
-		}
-
-		.config-fold-trigger {
-			position: relative;
-			z-index: 6;
-			flex-shrink: 0;
-			display: flex;
-			flex-direction: column;
-			align-items: center;
-			justify-content: center;
-			width: 24px;
-			margin: 0 10px 0 0;
-			align-self: center;
-			height: 136px;
-			padding: 0;
-			border: 1px solid rgba(0, 246, 255, 0.38);
-			border-radius: 0 6px 6px 0;
-			background: linear-gradient(180deg, rgba(5, 37, 78, 0.96), rgba(5, 25, 54, 0.92));
-			box-shadow: inset 0 0 20px rgba(0, 143, 253, 0.14), 0 0 16px rgba(0, 143, 253, 0.18);
-			cursor: pointer;
-			color: #00f6ff;
-			font-size: 22px;
-			font-family: inherit;
-			line-height: 1;
-		}
-
-		.config-fold-trigger:hover {
-			border-color: rgba(64, 222, 120, 0.55);
-			color: #40de78;
-			box-shadow: inset 0 0 24px rgba(64, 222, 120, 0.12), 0 0 18px rgba(64, 222, 120, 0.2);
-		}
-
-		.config-fold-trigger .fold-icon {
-			display: block;
-			font-size: 28px;
-			font-weight: 700;
-			line-height: 1;
-			transition: transform 0.28s ease;
-		}
-
-		.config-fold-trigger .fold-label {
-			margin-top: 10px;
-			font-size: 14px;
-			writing-mode: vertical-rl;
-			text-orientation: mixed;
-			letter-spacing: 3px;
-		}
-
-		.main-grid-wrap.is-folded .config-fold-trigger .fold-icon {
-			transform: rotate(180deg);
-		}
-
-		.main-grid-wrap .data-panel-main {
-			flex: 1;
-			min-width: 0;
-		}
-	</style>
+	<title>返修区看板</title>
+	<link rel="stylesheet" href="${ctxStatic}/screen/repair-screen.css?v=202607264">
 </head>
 <body>
 <div class="screen-page">
 	<img class="bg" src="${ctxStatic}/screen/imgs/bg3.png" alt="">
 	<div class="screen-content">
 		<div class="screen-header">
-			<div class="header-left-tools">
-				<div class="scope-tab-group" id="scopeSwitch">
-					<button type="button" class="scope-tab is-active" data-scope="all">全部产量</button>
-					<button type="button" class="scope-tab" data-scope="today">今日产量</button>
-				</div>
-			</div>
-			<div class="screen-title-new" id="screenTitle"></div>
+			<div></div>
+			<div class="screen-title-new" id="screenTitle">返修区看板</div>
 			<div class="screen-time" id="screenTime"></div>
 		</div>
 		<div class="summary-row">
-			<div class="summary-item"><div class="summary-label">工位数量</div><div class="summary-value" id="oprnoTotal">0</div></div>
-			<div class="summary-item"><div class="summary-label" id="recordTotalLabel">全部生产数量</div><div class="summary-value" id="recordTotal">0</div></div>
-			<div class="summary-item"><div class="summary-label">最高工位</div><div class="summary-value" id="maxOprno">--</div></div>
+			<div class="summary-item is-pending">
+				<div class="summary-label">待返修</div>
+				<div class="summary-value" id="pendingCount">0</div>
+			</div>
+			<div class="summary-item is-repairing">
+				<div class="summary-label">待检查</div>
+				<div class="summary-value" id="repairingCount">0</div>
+			</div>
+			<div class="summary-item is-done">
+				<div class="summary-label">今日完成</div>
+				<div class="summary-value" id="todayDoneCount">0</div>
+			</div>
+			<div class="summary-item is-fail">
+				<div class="summary-label">今日失败</div>
+				<div class="summary-value" id="todayFailCount">0</div>
+			</div>
 		</div>
-		<div class="main-grid-wrap" id="mainGrid">
-			<div class="side-config-panel" id="sideConfigPanel">
-				<div class="panel control-panel">
-					<div class="panel-title">工位号</div>
-					<textarea class="oprno-input" id="oprnoInput"></textarea>
-					<div class="input-row">
-						<input class="add-input" id="addOprno" type="text" placeholder="新增工位号">
-						<button type="button" class="screen-btn primary" id="addBtn">添加</button>
-					</div>
-					<div class="action-row">
-						<button type="button" class="screen-btn primary" id="queryBtn">查询</button>
-						<button type="button" class="screen-btn" id="clearBtn">清空</button>
-					</div>
-					<div class="tag-list" id="tagList"></div>
-					<div class="hint">支持逗号、空格、换行分隔工位号。</div>
-				</div>
+		<div class="main-panel">
+			<div class="panel-title">
+				返修待处理清单
+				<span class="page-indicator" id="pageIndicator">第 1 / 1 页</span>
 			</div>
-			<button type="button" class="config-fold-trigger" id="panelCollapseBtn" aria-expanded="true" title="收起工位配置">
-				<span class="fold-icon">‹</span>
-				<span class="fold-label">收起</span>
-			</button>
-			<div class="panel data-panel data-panel-main">
-				<div class="panel-title" id="dataPanelTitle">各工位全部生产数量</div>
-				<div class="chart-box" id="recordChart"></div>
-				<div class="table-wrap">
-					<table class="record-table">
-						<thead><tr><th>序号</th><th>工位</th><th id="recordCountHeader">全部生产数量</th></tr></thead>
-						<tbody id="recordBody"></tbody>
-					</table>
-				</div>
+			<div class="table-wrap">
+				<table class="repair-table">
+					<thead>
+					<tr>
+						<th class="col-seq">序号</th>
+						<th class="col-sn">工件码</th>
+						<th class="col-oprno">责任工位</th>
+						<th class="col-item">返工白名单</th>
+						<th class="col-type">返工类型</th>
+						<th class="col-remark">不良信息</th>
+						<th class="col-state">状态</th>
+						<th class="col-user">上报人</th>
+						<th class="col-time">创建时间</th>
+					</tr>
+					</thead>
+					<tbody id="repairBody" class="is-visible"></tbody>
+				</table>
 			</div>
 		</div>
 	</div>
 </div>
 <script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
-<script src="${ctxStatic}/echarts/4.2/echarts.min.js"></script>
 <script>
-	window.screenOprnoConfig = {
-		title: "华为项目加工记录统计大屏",
-		defaultOprnos: ["OP420", "OP430", "OP440", "OP450", "OP460", "OP470", "OP480", "OP490", "OP320A"]
+	window.repairScreenConfig = {
+		title: "返修区看板",
+		pageSize: 7,
+		carouselInterval: 8000,
+		refreshInterval: 60000,
+		apiUrl: "${ctx}/mes/mesProduct/screenRepairData"
 	};
 </script>
-<script src="${ctxStatic}/screen/oprno-record-screen.js?v=202506244"></script>
+<script src="${ctxStatic}/screen/repair-screen.js?v=202607264"></script>
 </body>
 </html>