hou před 2 týdny
rodič
revize
3918fba3e5
25 změnil soubory, kde provedl 1394 přidání a 174 odebrání
  1. 39 0
      db/mysql/mes_gp12_issue.sql
  2. 8 0
      db/mysql/mes_line_process_qm_max_times.sql
  3. 8 0
      db/mysql/mes_station_time_rule.sql
  4. 15 0
      src/main/java/com/jeesite/modules/mes/dao/MesGp12IssueDao.java
  5. 81 0
      src/main/java/com/jeesite/modules/mes/entity/MesGp12Issue.java
  6. 20 0
      src/main/java/com/jeesite/modules/mes/entity/MesLineProcess.java
  7. 61 0
      src/main/java/com/jeesite/modules/mes/service/MesGp12IssueService.java
  8. 2 0
      src/main/java/com/jeesite/modules/mes/service/MesLineProcessService.java
  9. 27 0
      src/main/java/com/jeesite/modules/mes/service/MesProductDbjRecordService.java
  10. 10 0
      src/main/java/com/jeesite/modules/mes/service/MesProductRecordService.java
  11. 161 0
      src/main/java/com/jeesite/modules/mes/service/MesStationTimeValidateService.java
  12. 93 0
      src/main/java/com/jeesite/modules/mes/web/MesGp12IssueController.java
  13. 44 0
      src/main/java/com/jeesite/modules/mes/web/MesProductController.java
  14. 61 4
      src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java
  15. 17 0
      src/main/java/com/jeesite/modules/mes/web/MesRepairController.java
  16. 8 0
      src/main/java/com/jeesite/modules/utils/ErrorMsg.java
  17. 3 4
      src/main/java/com/jeesite/modules/utils/MesMsgUtils.java
  18. 6 0
      src/main/resources/config/application.yml
  19. 283 0
      src/main/resources/static/screen/gp12-issue-screen.css
  20. 189 0
      src/main/resources/static/screen/gp12-issue-screen.js
  21. 125 0
      src/main/resources/views/modules/mes/mesGp12IssueForm.html
  22. 84 0
      src/main/resources/views/modules/mes/mesGp12IssueList.html
  23. 18 0
      src/main/resources/views/modules/mes/mesLineProcessForm.html
  24. 2 0
      src/main/resources/views/modules/mes/mesLineProcessList.html
  25. 29 166
      src/main/resources/views/modules/mes/mesScreen8.html

+ 39 - 0
db/mysql/mes_gp12_issue.sql

@@ -0,0 +1,39 @@
+-- GP12不良问题点配置表
+CREATE TABLE IF NOT EXISTS `mes_gp12_issue` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `check_item` varchar(255) NOT NULL COMMENT '检验项目',
+  `standard` varchar(2000) DEFAULT NULL COMMENT '判定标准',
+  `method` varchar(1000) DEFAULT NULL COMMENT '检验方法',
+  `sort` int(11) DEFAULT 0 COMMENT '排序号',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态(0正常 1删除 2停用)',
+  `create_by` varchar(64) NOT NULL COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注信息',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_gp12_issue_sort` (`sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GP12不良问题点';
+
+-- 菜单:系统管理/生产线管理/GP12问题点配置
+INSERT INTO `js_sys_menu` VALUES (
+'2075100000000000001', '1680784143702568960', '0,1582259822626603008,1680784143702568960,',
+160, '0000009000,0000001059,0000000160,', '1', 2,
+'系统管理/生产线管理/GP12问题点配置', 'GP12问题点配置', '1', '/mes/mesGp12Issue/list',
+'', '', '', '', 'mes:mesGp12Issue:view,mes:mesGp12Issue:edit',
+40, '1', 'default', 'core', NULL, NULL, '0',
+'system', NOW(), 'system', NOW(),
+'', '', '', '', '', '', '', '', '',
+NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+);
+
+-- 给已有「大屏计划」权限的角色同步菜单
+INSERT INTO `js_sys_role_menu` (`role_code`, `menu_code`)
+SELECT rm.`role_code`, '2075100000000000001'
+FROM `js_sys_role_menu` rm
+WHERE rm.`menu_code` = '1720060989331107840'
+  AND NOT EXISTS (
+      SELECT 1 FROM `js_sys_role_menu` x
+      WHERE x.`role_code` = rm.`role_code`
+        AND x.`menu_code` = '2075100000000000001'
+  );

+ 8 - 0
db/mysql/mes_line_process_qm_max_times.sql

@@ -0,0 +1,8 @@
+-- 气密最大NG次数(仅计NG,OK不计)。空或<=0 表示不限制。
+-- 若列已存在可只更新注释:
+-- ALTER TABLE mes_line_process MODIFY COLUMN qm_max_times int(11) DEFAULT NULL COMMENT '气密最大NG次数(仅计NG)';
+
+ALTER TABLE mes_line_process ADD COLUMN qm_max_times int(11) DEFAULT NULL COMMENT '气密最大NG次数(仅计NG)' AFTER qm_num;
+
+-- 可选:气密主工位默认最大NG次数
+-- UPDATE mes_line_process SET qm_max_times = 3 WHERE is_qm = '1' AND (pid IS NULL OR pid = '') AND qm_max_times IS NULL;

+ 8 - 0
db/mysql/mes_station_time_rule.sql

@@ -0,0 +1,8 @@
+-- 工位最长加工时长(分钟),用于 OP015/OP200/OP210/OP260 等时效管控
+ALTER TABLE mes_line_process ADD COLUMN max_duration int(11) DEFAULT NULL COMMENT '最长加工时长(分钟)' AFTER duration;
+
+-- 可选:为默认管控工位预置 15 分钟(仅主工位,子工位 A/B 继承主工位配置)
+UPDATE mes_line_process SET max_duration = 15 WHERE oprno IN ('OP015','OP200','OP210','OP260') AND pid IS NULL AND max_duration IS NULL;
+
+-- OP030 建议配置为不可重复加工
+UPDATE mes_line_process SET `repeat` = '0' WHERE oprno IN ('OP030','OP030A');

+ 15 - 0
src/main/java/com/jeesite/modules/mes/dao/MesGp12IssueDao.java

@@ -0,0 +1,15 @@
+package com.jeesite.modules.mes.dao;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesGp12Issue;
+
+/**
+ * GP12不良问题点DAO接口
+ * @author mes
+ * @version 2026-07-15
+ */
+@MyBatisDao
+public interface MesGp12IssueDao extends CrudDao<MesGp12Issue> {
+
+}

+ 81 - 0
src/main/java/com/jeesite/modules/mes/entity/MesGp12Issue.java

@@ -0,0 +1,81 @@
+package com.jeesite.modules.mes.entity;
+
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+
+/**
+ * GP12不良问题点Entity
+ * @author mes
+ * @version 2026-07-15
+ */
+@Table(name="mes_gp12_issue", alias="a", label="GP12不良问题点信息", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="check_item", attrName="checkItem", label="检验项目", queryType=QueryType.LIKE),
+		@Column(name="standard", attrName="standard", label="判定标准", queryType=QueryType.LIKE),
+		@Column(name="method", attrName="method", label="检验方法", queryType=QueryType.LIKE),
+		@Column(name="sort", attrName="sort", label="排序号"),
+		@Column(name="status", attrName="status", label="状态", isUpdate=false),
+		@Column(name="create_by", attrName="createBy", label="创建者", isUpdate=false, isQuery=false),
+		@Column(name="create_date", attrName="createDate", label="创建时间", isUpdate=false, isQuery=false, isUpdateForce=true),
+		@Column(name="update_by", attrName="updateBy", label="更新者", isQuery=false),
+		@Column(name="update_date", attrName="updateDate", label="更新时间", isQuery=false, isUpdateForce=true),
+		@Column(name="remarks", attrName="remarks", label="备注信息", queryType=QueryType.LIKE),
+	}, orderBy="a.sort ASC, a.update_date DESC"
+)
+public class MesGp12Issue extends DataEntity<MesGp12Issue> {
+
+	private static final long serialVersionUID = 1L;
+	private String checkItem;
+	private String standard;
+	private String method;
+	private Integer sort;
+
+	public MesGp12Issue() {
+		this(null);
+	}
+
+	public MesGp12Issue(String id) {
+		super(id);
+	}
+
+	@NotBlank(message="检验项目不能为空")
+	@Size(min=0, max=255, message="检验项目长度不能超过 255 个字符")
+	public String getCheckItem() {
+		return checkItem;
+	}
+
+	public void setCheckItem(String checkItem) {
+		this.checkItem = checkItem;
+	}
+
+	@Size(min=0, max=2000, message="判定标准长度不能超过 2000 个字符")
+	public String getStandard() {
+		return standard;
+	}
+
+	public void setStandard(String standard) {
+		this.standard = standard;
+	}
+
+	@Size(min=0, max=1000, message="检验方法长度不能超过 1000 个字符")
+	public String getMethod() {
+		return method;
+	}
+
+	public void setMethod(String method) {
+		this.method = method;
+	}
+
+	public Integer getSort() {
+		return sort;
+	}
+
+	public void setSort(Integer sort) {
+		this.sort = sort;
+	}
+}

+ 20 - 0
src/main/java/com/jeesite/modules/mes/entity/MesLineProcess.java

@@ -39,9 +39,11 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
 		@Column(name="stop", attrName="stop", label="设备启动"),
 		@Column(name="type", attrName="type", label="类型"),
 		@Column(name="duration", attrName="duration", label="时长"),
+		@Column(name="max_duration", attrName="maxDuration", label="最长加工时长", comment="最长加工时长(分钟)", isUpdateForce=true),
 		@Column(name="is_pz", attrName="isPz", label="拍照"),
 		@Column(name="is_qm", attrName="isQm", label="气密"),
 		@Column(name="qm_num", attrName="qmNum", label="次数"),
+		@Column(name="qm_max_times", attrName="qmMaxTimes", label="气密最大NG次数", isUpdateForce=true),
 		@Column(name="pid", attrName="pid", label="父级ID",queryType=QueryType.EQ),
 	},
 	joinTable={
@@ -88,9 +90,11 @@ public class MesLineProcess extends DataEntity<MesLineProcess> {
 	private Integer dayyn; //印尼版
 	private String type;
 	private Integer duration;
+	private Integer maxDuration;
 	private String isPz;
 	private String isQm;
 	private Integer qmNum;
+	private Integer qmMaxTimes;
 
 
 	//用于工位授权
@@ -383,6 +387,14 @@ public class MesLineProcess extends DataEntity<MesLineProcess> {
 		this.duration = duration;
 	}
 
+	public Integer getMaxDuration() {
+		return maxDuration;
+	}
+
+	public void setMaxDuration(Integer maxDuration) {
+		this.maxDuration = maxDuration;
+	}
+
 	public String getType() {
 		return type;
 	}
@@ -414,4 +426,12 @@ public class MesLineProcess extends DataEntity<MesLineProcess> {
 	public void setQmNum(Integer qmNum) {
 		this.qmNum = qmNum;
 	}
+
+	public Integer getQmMaxTimes() {
+		return qmMaxTimes;
+	}
+
+	public void setQmMaxTimes(Integer qmMaxTimes) {
+		this.qmMaxTimes = qmMaxTimes;
+	}
 }

+ 61 - 0
src/main/java/com/jeesite/modules/mes/service/MesGp12IssueService.java

@@ -0,0 +1,61 @@
+package com.jeesite.modules.mes.service;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.file.utils.FileUploadUtils;
+import com.jeesite.modules.mes.dao.MesGp12IssueDao;
+import com.jeesite.modules.mes.entity.MesGp12Issue;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * GP12不良问题点Service
+ * @author mes
+ * @version 2026-07-15
+ */
+@Service
+public class MesGp12IssueService extends CrudService<MesGp12IssueDao, MesGp12Issue> {
+
+	public static final String BIZ_TYPE_NG = "mesGp12Issue_ng_image";
+	public static final String BIZ_TYPE_OK = "mesGp12Issue_ok_image";
+
+	@Override
+	public MesGp12Issue get(MesGp12Issue mesGp12Issue) {
+		return super.get(mesGp12Issue);
+	}
+
+	@Override
+	public Page<MesGp12Issue> findPage(MesGp12Issue mesGp12Issue) {
+		return super.findPage(mesGp12Issue);
+	}
+
+	@Override
+	public List<MesGp12Issue> findList(MesGp12Issue mesGp12Issue) {
+		return super.findList(mesGp12Issue);
+	}
+
+	@Override
+	@Transactional
+	public void save(MesGp12Issue mesGp12Issue) {
+		if (mesGp12Issue.getSort() == null) {
+			mesGp12Issue.setSort(0);
+		}
+		super.save(mesGp12Issue);
+		FileUploadUtils.saveFileUpload(mesGp12Issue, mesGp12Issue.getId(), BIZ_TYPE_NG);
+		FileUploadUtils.saveFileUpload(mesGp12Issue, mesGp12Issue.getId(), BIZ_TYPE_OK);
+	}
+
+	@Override
+	@Transactional
+	public void updateStatus(MesGp12Issue mesGp12Issue) {
+		super.updateStatus(mesGp12Issue);
+	}
+
+	@Override
+	@Transactional
+	public void delete(MesGp12Issue mesGp12Issue) {
+		super.delete(mesGp12Issue);
+	}
+}

+ 2 - 0
src/main/java/com/jeesite/modules/mes/service/MesLineProcessService.java

@@ -75,6 +75,8 @@ public class MesLineProcessService extends CrudService<MesLineProcessDao, MesLin
 				mesLineProcess1.setIsPz(mesLineProcess2.getIsPz());
 				mesLineProcess1.setIsQm(mesLineProcess2.getIsQm());
 				mesLineProcess1.setQmNum(mesLineProcess2.getQmNum()>1?mesLineProcess2.getQmNum():1);
+				mesLineProcess1.setQmMaxTimes(mesLineProcess2.getQmMaxTimes());
+				mesLineProcess1.setMaxDuration(mesLineProcess2.getMaxDuration());
 			}
 		}
 

+ 27 - 0
src/main/java/com/jeesite/modules/mes/service/MesProductDbjRecordService.java

@@ -53,6 +53,33 @@ public class MesProductDbjRecordService extends CrudService<MesProductDbjRecordD
 		return super.findList(mesProductDbjRecord);
 	}
 
+	public MesProductDbjRecord findLatestOk(String partSn, String oldOprno, String lineSn) {
+		if (StringUtils.isEmpty(partSn) || StringUtils.isEmpty(oldOprno) || StringUtils.isEmpty(lineSn)) {
+			return null;
+		}
+		String targetOprno = CommonUitl.formatOprno(oldOprno.trim());
+		MesProductDbjRecord query = new MesProductDbjRecord();
+		query.setSn(partSn.trim());
+		query.setLineSn(lineSn.trim());
+		query.setResult("OK");
+		List<MesProductDbjRecord> list = findList(query);
+		MesProductDbjRecord latest = null;
+		for (MesProductDbjRecord record : list) {
+			if (!targetOprno.equals(CommonUitl.formatOprno(record.getOprno()))) {
+				continue;
+			}
+			if (latest == null) {
+				latest = record;
+				continue;
+			}
+			if (record.getCreateDate() != null
+					&& (latest.getCreateDate() == null || record.getCreateDate().after(latest.getCreateDate()))) {
+				latest = record;
+			}
+		}
+		return latest;
+	}
+
 	@Override
 	@Transactional
 	public void save(MesProductDbjRecord mesProductDbjRecord) {

+ 10 - 0
src/main/java/com/jeesite/modules/mes/service/MesProductRecordService.java

@@ -19,6 +19,7 @@ import com.jeesite.modules.mes.entity.*;
 import com.jeesite.modules.mes.resp.*;
 import com.jeesite.modules.mes.util.CommonUitl;
 import com.jeesite.modules.mes.util.CopyUtil;
+import com.jeesite.modules.utils.ErrorMsg;
 import com.jeesite.modules.sys.dao.UserDao;
 import com.jeesite.modules.utils.ProtocolParam;
 import io.swagger.models.auth.In;
@@ -59,6 +60,9 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 	private MesDeviceTimeService mesDeviceTimeService;
 
 	@Autowired
+	private MesStationTimeValidateService mesStationTimeValidateService;
+
+	@Autowired
 	private MesDeviceService mesDeviceService;
 
 	@Autowired
@@ -567,6 +571,12 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 			throw new ServiceException(text("工位不存在!"));
 		}
 
+		String timeErr = mesStationTimeValidateService.validateSubmit(oldOprno, lineSn, sn, mesLineProcess1);
+		if (timeErr != null) {
+			String lx = oldOprno.length() >= 5 ? oldOprno.substring(2, 5) : oldOprno;
+			throw new ServiceException(text(ErrorMsg.getErrorMsg(timeErr, lx)));
+		}
+
 		String cateSn = "";
 		content = content.equals("OK")?"OK":"NG";
 

+ 161 - 0
src/main/java/com/jeesite/modules/mes/service/MesStationTimeValidateService.java

@@ -0,0 +1,161 @@
+package com.jeesite.modules.mes.service;
+
+import com.jeesite.common.lang.ObjectUtils;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.modules.mes.dao.MesProductRecordDao;
+import com.jeesite.modules.mes.entity.MesDeviceTime;
+import com.jeesite.modules.mes.entity.MesLineProcess;
+import com.jeesite.modules.mes.entity.MesProductDbjRecord;
+import com.jeesite.modules.mes.entity.MesProductRecord;
+import com.jeesite.modules.mes.util.CommonUitl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 工位加工时效校验
+ */
+@Service
+public class MesStationTimeValidateService {
+
+	private static final int DEFAULT_MAX_PROCESS_MINUTES = 15;
+	private static final int COOLING_OP023_TO_OP030_MINUTES = 30;
+	private static final int CROSS_OP150_TO_OP160_MINUTES = 15;
+
+	private static final Set<String> MAX_DURATION_OPRNOS = new HashSet<>(Arrays.asList(
+			"OP015", "OP200", "OP210", "OP260"
+	));
+
+	private static final Set<String> CROSS_TO_OP160_OPRNOS = new HashSet<>(Arrays.asList(
+			"OP160", "OP160A", "OP160B"
+	));
+
+	@Autowired
+	private MesDeviceTimeService mesDeviceTimeService;
+	@Autowired
+	private MesProductDbjRecordService mesProductDbjRecordService;
+	@Autowired
+	private MesProductRecordDao mesProductRecordDao;
+
+	/**
+	 * 单部件工位进站校验(精追码)
+	 * @return 两位错误码,null 表示通过
+	 */
+	public String validateDbjEntry(String oldOprno, String lineSn, String partSn) {
+		if (!matchOprno(oldOprno, "OP030")) {
+			return null;
+		}
+		if (StringUtils.isBlank(partSn)) {
+			return "NE";
+		}
+		if (mesProductDbjRecordService.findLatestOk(partSn, oldOprno, lineSn) != null) {
+			return "RP";
+		}
+		MesProductDbjRecord op023Ok = mesProductDbjRecordService.findLatestOk(partSn, "OP023", lineSn);
+		if (op023Ok == null || op023Ok.getCreateDate() == null) {
+			return "QD";
+		}
+		long elapsedMs = System.currentTimeMillis() - op023Ok.getCreateDate().getTime();
+		if (elapsedMs < COOLING_OP023_TO_OP030_MINUTES * 60L * 1000L) {
+			return "GH";
+		}
+		return null;
+	}
+
+	/**
+	 * 工位提交前校验
+	 * @return 两位错误码,null 表示通过
+	 */
+	public String validateSubmit(String oldOprno, String lineSn, String sn, MesLineProcess mesLineProcess) {
+		if (StringUtils.isBlank(sn) || ObjectUtils.isEmpty(mesLineProcess)) {
+			return null;
+		}
+		String crossErr = validateCrossOp150ToOp160(oldOprno, lineSn, sn);
+		if (crossErr != null) {
+			return crossErr;
+		}
+		Integer maxMinutes = resolveMaxProcessMinutes(oldOprno, mesLineProcess);
+		if (maxMinutes == null || maxMinutes <= 0) {
+			return null;
+		}
+		return validateMaxProcessDuration(oldOprno, lineSn, sn, maxMinutes);
+	}
+
+	private String validateCrossOp150ToOp160(String oldOprno, String lineSn, String sn) {
+		if (!isCrossToOp160(oldOprno)) {
+			return null;
+		}
+		Date op150Complete = findOprnoOkCompleteTime(sn, lineSn, "OP150");
+		if (op150Complete == null) {
+			return "QD";
+		}
+		long elapsedMs = System.currentTimeMillis() - op150Complete.getTime();
+		if (elapsedMs > CROSS_OP150_TO_OP160_MINUTES * 60L * 1000L) {
+			return "TO";
+		}
+		return null;
+	}
+
+	private String validateMaxProcessDuration(String oldOprno, String lineSn, String sn, int maxMinutes) {
+		MesDeviceTime mesDeviceTime = mesDeviceTimeService.getInfoByOprnoAndSnAndCate(oldOprno, sn, lineSn);
+		if (ObjectUtils.isEmpty(mesDeviceTime) || mesDeviceTime.getStartDate() == null) {
+			return "TO";
+		}
+		long elapsedMs = System.currentTimeMillis() - mesDeviceTime.getStartDate().getTime();
+		if (elapsedMs > maxMinutes * 60L * 1000L) {
+			return "TO";
+		}
+		return null;
+	}
+
+	private Integer resolveMaxProcessMinutes(String oldOprno, MesLineProcess mesLineProcess) {
+		if (mesLineProcess.getMaxDuration() != null && mesLineProcess.getMaxDuration() > 0) {
+			return mesLineProcess.getMaxDuration();
+		}
+		if (MAX_DURATION_OPRNOS.contains(normalizeOprno(oldOprno))) {
+			return DEFAULT_MAX_PROCESS_MINUTES;
+		}
+		return null;
+	}
+
+	private Date findOprnoOkCompleteTime(String sn, String lineSn, String targetOprno) {
+		MesProductRecord query = new MesProductRecord();
+		query.setSn(sn);
+		query.setLineSn(lineSn);
+		query.setCraft("100000");
+		query.setContent("OK");
+		List<MesProductRecord> list = mesProductRecordDao.findList(query);
+		String target = normalizeOprno(targetOprno);
+		Date latest = null;
+		for (MesProductRecord record : list) {
+			if (!target.equals(normalizeOprno(record.getOprno()))) {
+				continue;
+			}
+			Date completeDate = record.getUpdateDate() != null ? record.getUpdateDate() : record.getCreateDate();
+			if (completeDate != null && (latest == null || completeDate.after(latest))) {
+				latest = completeDate;
+			}
+		}
+		return latest;
+	}
+
+	private boolean isCrossToOp160(String oldOprno) {
+		return CROSS_TO_OP160_OPRNOS.contains(normalizeOprno(oldOprno));
+	}
+
+	private boolean matchOprno(String oldOprno, String target) {
+		return normalizeOprno(oldOprno).equals(normalizeOprno(target));
+	}
+
+	private String normalizeOprno(String oprno) {
+		if (StringUtils.isBlank(oprno)) {
+			return "";
+		}
+		return CommonUitl.formatOprno(oprno.trim().toUpperCase());
+	}
+}

+ 93 - 0
src/main/java/com/jeesite/modules/mes/web/MesGp12IssueController.java

@@ -0,0 +1,93 @@
+package com.jeesite.modules.mes.web;
+
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesGp12Issue;
+import com.jeesite.modules.mes.service.MesGp12IssueService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * GP12不良问题点Controller
+ * @author mes
+ * @version 2026-07-15
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesGp12Issue")
+public class MesGp12IssueController extends BaseController {
+
+	@Autowired
+	private MesGp12IssueService mesGp12IssueService;
+
+	@ModelAttribute
+	public MesGp12Issue get(String id, boolean isNewRecord) {
+		return mesGp12IssueService.get(id, isNewRecord);
+	}
+
+	@RequiresPermissions("mes:mesGp12Issue:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesGp12Issue mesGp12Issue, Model model) {
+		model.addAttribute("mesGp12Issue", mesGp12Issue);
+		return "modules/mes/mesGp12IssueList";
+	}
+
+	@RequiresPermissions("mes:mesGp12Issue:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesGp12Issue> listData(MesGp12Issue mesGp12Issue, HttpServletRequest request, HttpServletResponse response) {
+		mesGp12Issue.setPage(new Page<>(request, response));
+		return mesGp12IssueService.findPage(mesGp12Issue);
+	}
+
+	@RequiresPermissions("mes:mesGp12Issue:view")
+	@RequestMapping(value = "form")
+	public String form(MesGp12Issue mesGp12Issue, Model model) {
+		model.addAttribute("mesGp12Issue", mesGp12Issue);
+		return "modules/mes/mesGp12IssueForm";
+	}
+
+	@RequiresPermissions("mes:mesGp12Issue:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesGp12Issue mesGp12Issue) {
+		mesGp12IssueService.save(mesGp12Issue);
+		return renderResult(Global.TRUE, text("保存GP12问题点成功!"));
+	}
+
+	@RequiresPermissions("mes:mesGp12Issue:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesGp12Issue mesGp12Issue) {
+		mesGp12IssueService.delete(mesGp12Issue);
+		return renderResult(Global.TRUE, text("删除GP12问题点成功!"));
+	}
+
+	@RequiresPermissions("mes:mesGp12Issue:edit")
+	@RequestMapping(value = "disable")
+	@ResponseBody
+	public String disable(MesGp12Issue mesGp12Issue) {
+		mesGp12Issue.setStatus(MesGp12Issue.STATUS_DISABLE);
+		mesGp12IssueService.updateStatus(mesGp12Issue);
+		return renderResult(Global.TRUE, text("停用GP12问题点成功"));
+	}
+
+	@RequiresPermissions("mes:mesGp12Issue:edit")
+	@RequestMapping(value = "enable")
+	@ResponseBody
+	public String enable(MesGp12Issue mesGp12Issue) {
+		mesGp12Issue.setStatus(MesGp12Issue.STATUS_NORMAL);
+		mesGp12IssueService.updateStatus(mesGp12Issue);
+		return renderResult(Global.TRUE, text("启用GP12问题点成功"));
+	}
+}

+ 44 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductController.java

@@ -19,6 +19,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
 import com.jeesite.common.service.ServiceException;
 import com.jeesite.common.utils.excel.ExcelExport;
 import com.jeesite.modules.file.entity.FileUpload;
+import com.jeesite.modules.file.service.FileUploadService;
 import com.jeesite.modules.mes.entity.*;
 import com.jeesite.modules.mes.resp.CommonResp;
 import com.jeesite.modules.mes.resp.MesShiftDataResp;
@@ -84,6 +85,10 @@ public class MesProductController extends BaseController {
 	private MesToolReplaceRecordService mesToolReplaceRecordService;
 	@Autowired
 	private MesProcessProductService mesProcessProductService;
+	@Autowired
+	private MesGp12IssueService mesGp12IssueService;
+	@Autowired
+	private FileUploadService fileUploadService;
 
 
 	/**
@@ -412,6 +417,45 @@ public class MesProductController extends BaseController {
 		return resp;
 	}
 
+	@RequestMapping(value = "screenGp12IssueList")
+	@ResponseBody
+	public CommonResp screenGp12IssueList(HttpServletRequest req) {
+		CommonResp<List<Map<String, Object>>> resp = new CommonResp<>();
+		MesGp12Issue query = new MesGp12Issue();
+		query.setStatus(MesGp12Issue.STATUS_NORMAL);
+		List<MesGp12Issue> items = mesGp12IssueService.findList(query);
+
+		String host = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/js";
+		List<Map<String, Object>> data = ListUtils.newArrayList();
+		for (MesGp12Issue item : items) {
+			Map<String, Object> row = MapUtils.newHashMap();
+			row.put("checkItem", item.getCheckItem());
+			row.put("standard", item.getStandard());
+			row.put("method", item.getMethod());
+			row.put("ngImages", loadGp12IssueImages(host, item.getId(), MesGp12IssueService.BIZ_TYPE_NG));
+			row.put("okImages", loadGp12IssueImages(host, item.getId(), MesGp12IssueService.BIZ_TYPE_OK));
+			data.add(row);
+		}
+
+		resp.setData(data);
+		resp.setResult(Global.TRUE);
+		return resp;
+	}
+
+	private List<String> loadGp12IssueImages(String host, String bizKey, String bizType) {
+		List<String> urls = ListUtils.newArrayList();
+		FileUpload fu = new FileUpload();
+		fu.setBizKey(bizKey);
+		fu.setBizType(bizType);
+		List<FileUpload> imgs = fileUploadService.findList(fu);
+		for (FileUpload img : imgs) {
+			if (img != null && StringUtils.isNotEmpty(img.getFileUrl())) {
+				urls.add(host + img.getFileUrl());
+			}
+		}
+		return urls;
+	}
+
 	@RequestMapping(value = "alarmData")
 	@ResponseBody
 	public CommonResp alarmData() {

+ 61 - 4
src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java

@@ -214,6 +214,8 @@ public class MesProductRecordController extends BaseController {
 	private MesProductCcService mesProductCcService;
 	@Autowired
 	private MesHwdgTimeService mesHwdgTimeService;
+	@Autowired
+	private MesStationTimeValidateService mesStationTimeValidateService;
 
 	@Autowired
 	private MesGlueRecordService mesGlueRecordService;
@@ -987,8 +989,16 @@ public class MesProductRecordController extends BaseController {
 		String userCode = ProtocolParam.getUserCode(msg).trim();
 
 		ret = checkQualityCommon(oldOprno,sn,lineSn,userCode);
-
-		return "RS"+ret.substring(3,5);
+		String checkret = ret.substring(3,5);
+		if(checkret.equals("UD")){
+			// OP022 等 Netty 客户端扫码进站时拦截气密最大NG次数
+			String qmMaxErr = validateQmMaxTimes(oldOprno, lineSn, sn, oprno);
+			if(qmMaxErr != null){
+				return "RSMT";
+			}
+		}
+		// 返回完整结果(如 130QN),保留前工位号;勿改成 RS+状态码,否则客户端只能解析出 OPRS0
+		return ret;
 	}
 
 	// 查询质量公共部分
@@ -1148,6 +1158,11 @@ public class MesProductRecordController extends BaseController {
 				return ret;
 			}
 
+			String timeErr = mesStationTimeValidateService.validateDbjEntry(oldOprno, lineSn, sn);
+			if (timeErr != null) {
+				return bs + timeErr;
+			}
+
 			if(mesLineProcess1.getQuality().equals("0") && mesLineProcess1.getRepeat().equals("1")){ // 不防呆且可以重复工作
 				ret = bs+"UD";
 				return ret;
@@ -1494,6 +1509,10 @@ public class MesProductRecordController extends BaseController {
 	}
 
 	private String updateQualityCommon(MesLineProcess mesLineProcess1,String oprno,String lineSn,String sn,String craft,String content,String userCode,List<ParamsResp> params){
+		String timeErr = mesStationTimeValidateService.validateSubmit(oprno, lineSn, sn, mesLineProcess1);
+		if (timeErr != null) {
+			return "RS" + timeErr;
+		}
 		if(mesLineProcess1.getType().equals("3")){ // 单部件
 			List<BindMaterialResp> bmlists = mesLineProcessMaterialService.getBindMaterial(oprno, lineSn);
 			mesProductRecordService.updateRecordResult(sn,oprno,craft,"",content,userCode,params,lineSn,true,bmlists);
@@ -2593,6 +2612,13 @@ public class MesProductRecordController extends BaseController {
 		String checkret = ret.substring(3,5);
 		if(checkret.equals("UD")){
 
+			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分钟
@@ -2628,6 +2654,31 @@ public class MesProductRecordController extends BaseController {
 		return resp;
 	}
 
+	/**
+	 * 气密最大NG次数校验(qmMaxTimes 仅计NG,OK不计;空或不大于0 不限制;万能码豁免)
+	 * @return 超限时返回错误文案,否则 null
+	 */
+	private String validateQmMaxTimes(String oldOprno, String lineSn, String sn, String formatOprno) {
+		String universalSn = Global.getConfig("mes.universal.sn");
+		if (!StringUtils.isEmpty(universalSn) && universalSn.contains(sn)) {
+			return null;
+		}
+		MesLineProcess process = mesLineProcessService.findInfoByOprnoLineSn(oldOprno, lineSn);
+		if (ObjectUtils.isEmpty(process) || process.getQmMaxTimes() == null || process.getQmMaxTimes() <= 0) {
+			return null;
+		}
+		MesProductQm query = new MesProductQm();
+		query.setComponentNum(sn);
+		query.setTestResult("NG");
+		query.getSqlMap().getWhere().and("a.oprno", QueryType.RIGHT_LIKE, formatOprno);
+		List<MesProductQm> list = mesProductQmService.findList(query);
+		int ngCount = list == null ? 0 : list.size();
+		if (ngCount >= process.getQmMaxTimes()) {
+			return "气密已达最大NG次数(" + process.getQmMaxTimes() + "),当前NG " + ngCount + "次";
+		}
+		return null;
+	}
+
 
 	@PostMapping(value = "qmresult")
 	@ResponseBody
@@ -2660,6 +2711,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));
+			}
+
 			MesProductQm mesProductQm = new MesProductQm();
 			mesProductQm.setOprno(oprno);
 			mesProductQm.setComponentNum(sn);
@@ -3824,8 +3881,8 @@ private MesProductProdVisual2Service mesProductProdVisual2Service;
 				String res = updateQualityCommon(mesLineProcess1,oprno,lineSn,sn,craft,result,userCode,params);
 				logger.info("res:"+res);
 				if(!res.equals("RSOK")){
-					String lx = ret.substring(0,2);
-					String checkret = ret.substring(2,4);
+					String lx = oldOprno.length() >= 5 ? oldOprno.substring(2, 5) : oldOprno;
+					String checkret = res.length() >= 4 ? res.substring(2, 4) : res;
 					String lmsg = ErrorMsg.getErrorMsg(checkret,lx);
 					return renderResult(Global.FALSE, text(lmsg));
 				}else{

+ 17 - 0
src/main/java/com/jeesite/modules/mes/web/MesRepairController.java

@@ -312,6 +312,23 @@ public class MesRepairController extends BaseController {
 				mesRepair1.setSopList(sopLists);
 			}
 			mesRepair1.setTitle(lpinfo.getTitle());
+
+			String host = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/js";
+			FileUpload fu = new FileUpload();
+			fu.setBizKey(mesRepair1.getId());
+			fu.setBizType("mesRepair_image");
+			List<FileUpload> finfo = fileUploadService.findList(fu);
+			List imgList = ListUtils.newArrayList();
+			for (FileUpload fi:finfo){
+				Map<String, Object> map1 = MapUtils.newHashMap();
+				map1.put("id",fi.getFileEntity().getFileId());
+				map1.put("url",host + fi.getFileUrl());
+				map1.put("name",fi.getFileName());
+				map1.put("size",fi.getFileEntity().getFileSize());
+				imgList.add(map1);
+			}
+			mesRepair1.setImgList(imgList);
+
 			resp.setData(mesRepair1);
 
 		}

+ 8 - 0
src/main/java/com/jeesite/modules/utils/ErrorMsg.java

@@ -38,6 +38,8 @@ public class ErrorMsg {
                 lmsg = "配件寿命不足";
             }else if(processMsgRet.equalsIgnoreCase("QM")) {
                 lmsg = "两次气密必须间隔15分钟";
+            }else if(processMsgRet.equalsIgnoreCase("MT")) {
+                lmsg = "气密已达最大NG次数";
             }else if(processMsgRet.equalsIgnoreCase("GS")) {
                 lmsg = "工件码格式不正确";
             }else if(processMsgRet.equalsIgnoreCase("CF")) {
@@ -64,6 +66,12 @@ public class ErrorMsg {
                 lmsg = "工件类型错误,当前工件:智界";
             } else if (processMsgRet.equalsIgnoreCase("CT")) {
                 lmsg = "工件类型错误";  // 兜底
+            } else if (processMsgRet.equalsIgnoreCase("TO")) {
+                lmsg = "工位加工超时";
+            } else if (processMsgRet.equalsIgnoreCase("GH")) {
+                lmsg = "上一工位完成后冷却时间未到";
+            } else if (processMsgRet.equalsIgnoreCase("RP")) {
+                lmsg = "该精追码已在本工位加工完成,不可重复";
             }
         }catch (Exception e){ }
         return lmsg;

+ 3 - 4
src/main/java/com/jeesite/modules/utils/MesMsgUtils.java

@@ -422,8 +422,8 @@ public class MesMsgUtils {
 		String mes_lx = ProtocolParam.getReturnLx(msg);
 		String mes_rs = "    ";
 		if(!ret.equalsIgnoreCase("")) {
-			//保存成功
-			mes_rs = ret;//"RSOK";
+			// TCP 结果区固定 4 位;去掉内部「RS UD」中的空格,避免客户端错位解析成「 U」
+			mes_rs = ret.replace(" ", "");
 		}else {
 			//未保存成功
 			mes_rs = mes_lx+"NS";
@@ -446,8 +446,7 @@ public class MesMsgUtils {
 		String mes_lx = ProtocolParam.getReturnLx(msg);
 		String mes_rs = "    ";
 		if(!ret.equalsIgnoreCase("")) {
-			//保存成功
-			mes_rs = ret;//"RSOK";
+			mes_rs = ret.replace(" ", "");
 		}else {
 			//未保存成功
 			mes_rs = mes_lx+"NS";

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

@@ -620,6 +620,12 @@ shiro:
     ${adminPath}/mes/mesProductRecord/qmresult = anon
     ${adminPath}/mes/mesProductRecord/device = anon
     ${adminPath}/mes/mesProduct/screen = anon
+    ${adminPath}/mes/mesProduct/screen4 = anon
+    ${adminPath}/mes/mesProduct/screen5 = anon
+    ${adminPath}/mes/mesProduct/screen6 = anon
+    ${adminPath}/mes/mesProduct/screen7 = anon
+    ${adminPath}/mes/mesProduct/screen8 = anon
+    ${adminPath}/mes/mesProduct/screen9 = anon
     ${adminPath}/mes/mesProduct/screenData = anon
     ${adminPath}/mes/mesProductCcd/testDate = anon
     ${adminPath}/mes/mesProductCcd/add = anon

+ 283 - 0
src/main/resources/static/screen/gp12-issue-screen.css

@@ -0,0 +1,283 @@
+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(2, 1fr);
+	gap: 20px;
+	margin-top: 12px;
+}
+
+.summary-item {
+	height: 96px;
+	padding: 16px 24px;
+	border: 1px solid rgba(255, 80, 80, 0.28);
+	border-radius: 6px;
+	background: linear-gradient(180deg, rgba(78, 5, 20, 0.72), rgba(54, 5, 15, 0.68));
+	box-shadow: inset 0 0 30px rgba(255, 80, 80, 0.1);
+	box-sizing: border-box;
+}
+
+.summary-label {
+	font-size: 20px;
+	color: #ffb0b0;
+}
+
+.summary-value {
+	margin-top: 10px;
+	color: #ff6b6b;
+	font-size: 38px;
+	font-weight: 700;
+	line-height: 1;
+}
+
+.main-panel {
+	height: 940px;
+	margin-top: 22px;
+	border: 1px solid rgba(255, 80, 80, 0.34);
+	border-radius: 6px;
+	background: rgba(4, 20, 45, 0.78);
+	box-shadow: inset 0 0 40px rgba(255, 80, 80, 0.08);
+	box-sizing: border-box;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+}
+
+.panel-title {
+	height: 56px;
+	line-height: 56px;
+	padding: 0 24px;
+	color: #ff8a8a;
+	font-size: 22px;
+	font-weight: 700;
+	border-bottom: 1px solid rgba(255, 80, 80, 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;
+}
+
+.issue-table {
+	width: 100%;
+	height: 100%;
+	border-collapse: collapse;
+	table-layout: fixed;
+	text-align: center;
+}
+
+.issue-table thead {
+	display: table;
+	width: 100%;
+	table-layout: fixed;
+}
+
+.issue-table tbody {
+	display: block;
+	height: calc(100% - 54px);
+	overflow: hidden;
+}
+
+.issue-table tbody tr {
+	display: table;
+	width: 100%;
+	table-layout: fixed;
+}
+
+.issue-table th {
+	height: 54px;
+	color: #ff8a8a;
+	font-size: 20px;
+	font-weight: 700;
+	border-bottom: 1px solid rgba(255, 80, 80, 0.24);
+	border-right: 1px solid rgba(255, 80, 80, 0.22);
+}
+
+.issue-table th:last-child {
+	border-right: none;
+}
+
+.issue-table td {
+	height: 175px;
+	color: #fff;
+	font-size: 18px;
+	border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+	border-right: 1px solid rgba(0, 246, 255, 0.16);
+	vertical-align: middle;
+	padding: 8px 12px;
+	box-sizing: border-box;
+}
+
+.issue-table td:last-child {
+	border-right: none;
+}
+
+.issue-table tr:nth-child(even) td {
+	background: rgba(255, 80, 80, 0.06);
+}
+
+.issue-table .col-seq {
+	width: 70px;
+	color: #40de78;
+	font-size: 26px;
+	font-weight: 700;
+}
+
+.issue-table .col-item {
+	width: 180px;
+	text-align: center;
+	font-weight: 700;
+	color: #9ad8ff;
+	line-height: 1.4;
+	word-break: break-word;
+	white-space: pre-wrap;
+}
+
+.issue-table .col-standard {
+	width: 340px;
+	text-align: center;
+	line-height: 1.45;
+	word-break: break-word;
+	white-space: pre-wrap;
+}
+
+.issue-table .col-method {
+	width: 220px;
+	text-align: center;
+	line-height: 1.45;
+	word-break: break-word;
+	white-space: pre-wrap;
+}
+
+.issue-table .col-ng,
+.issue-table .col-ok {
+	width: 300px;
+}
+
+.image-list {
+	display: flex;
+	flex-wrap: wrap;
+	align-items: center;
+	justify-content: center;
+	gap: 8px;
+}
+
+.issue-image {
+	width: 160px;
+	height: 130px;
+	object-fit: contain;
+	border-radius: 4px;
+	border: 1px solid rgba(255, 255, 255, 0.14);
+	background: rgba(0, 0, 0, 0.22);
+	cursor: pointer;
+}
+
+.no-image {
+	color: #75a7c8;
+	font-size: 16px;
+}
+
+.empty-row td {
+	height: 420px;
+	color: #75a7c8;
+	font-size: 24px;
+	text-align: center;
+}
+
+.issue-table tbody.is-fading {
+	opacity: 0;
+	transition: opacity 0.45s ease;
+}
+
+.issue-table tbody.is-visible {
+	opacity: 1;
+	transition: opacity 0.45s ease;
+}
+
+.image-preview-mask {
+	display: none;
+	position: fixed;
+	inset: 0;
+	z-index: 9999;
+	background: rgba(0, 0, 0, 0.78);
+	align-items: center;
+	justify-content: center;
+}
+
+.image-preview-mask.is-show {
+	display: flex;
+}
+
+.image-preview-mask img {
+	max-width: 88%;
+	max-height: 88%;
+	object-fit: contain;
+	border: 1px solid rgba(255, 255, 255, 0.2);
+	border-radius: 6px;
+}

+ 189 - 0
src/main/resources/static/screen/gp12-issue-screen.js

@@ -0,0 +1,189 @@
+(function ($, window) {
+	"use strict";
+
+	var config = window.gp12IssueConfig || {};
+	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 renderImages(images) {
+		if (!$.isArray(images) || !images.length) {
+			return '<span class="no-image">暂无</span>';
+		}
+		var html = '<div class="image-list">';
+		images.forEach(function (url) {
+			if (!url) {
+				return;
+			}
+			html += '<img class="issue-image" src="' + escapeHtml(url) + '" alt="示例图片">';
+		});
+		html += "</div>";
+		return html;
+	}
+
+	function renderPage(pageIndex, animate) {
+		var pageSize = config.pageSize || 5;
+		var start = pageIndex * pageSize;
+		var pageData = state.items.slice(start, start + pageSize);
+		var html = "";
+
+		if (!pageData.length) {
+			html = '<tr class="empty-row"><td colspan="6">暂无不良问题点数据,请先在「GP12问题点配置」中维护</td></tr>';
+		} else {
+			pageData.forEach(function (item, index) {
+				var seq = start + index + 1;
+				html += "<tr>" +
+					'<td class="col-seq">' + seq + "</td>" +
+					'<td class="col-item">' + escapeHtml(item.checkItem) + "</td>" +
+					'<td class="col-standard">' + escapeHtml(item.standard) + "</td>" +
+					'<td class="col-method">' + escapeHtml(item.method) + "</td>" +
+					'<td class="col-ng">' + renderImages(item.ngImages) + "</td>" +
+					'<td class="col-ok">' + renderImages(item.okImages) + "</td>" +
+					"</tr>";
+			});
+		}
+
+		var $body = $("#issueBody");
+		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 updateSummary() {
+		state.totalPages = Math.max(1, Math.ceil(state.items.length / (config.pageSize || 5)));
+		$("#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;
+			}
+			updateSummary();
+			renderPage(state.currentPage, true);
+		}, config.carouselInterval || 8000);
+	}
+
+	function stopCarousel() {
+		if (state.carouselTimer) {
+			clearInterval(state.carouselTimer);
+			state.carouselTimer = null;
+		}
+	}
+
+	function applyData(items) {
+		state.items = items || [];
+		state.currentPage = 0;
+		updateSummary();
+		renderPage(0, false);
+		startCarousel();
+	}
+
+	function loadData() {
+		var apiUrl = config.apiUrl;
+		if (!apiUrl) {
+			applyData([]);
+			return;
+		}
+
+		$.ajax({
+			url: apiUrl,
+			type: "GET",
+			dataType: "json"
+		}).done(function (res) {
+			if (res && res.result === "true" && $.isArray(res.data)) {
+				applyData(res.data);
+			} else {
+				applyData([]);
+			}
+		}).fail(function () {
+			applyData([]);
+		});
+	}
+
+	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 bindPreview() {
+		if (!$("#imagePreviewMask").length) {
+			$("body").append(
+				'<div class="image-preview-mask" id="imagePreviewMask"><img id="imagePreviewImg" alt="预览"></div>'
+			);
+		}
+		$(document).on("click", ".issue-image", function () {
+			$("#imagePreviewImg").attr("src", $(this).attr("src"));
+			$("#imagePreviewMask").addClass("is-show");
+		});
+		$(document).on("click", "#imagePreviewMask", function () {
+			$(this).removeClass("is-show");
+		});
+	}
+
+	function init() {
+		$("#screenTitle").text(config.title || "GP12不良问题点看板");
+		updateClock();
+		setInterval(updateClock, 1000);
+		bindPreview();
+		loadData();
+
+		state.refreshTimer = setInterval(loadData, config.refreshInterval || 60000);
+		fitScreen();
+		$(window).on("resize", fitScreen);
+	}
+
+	$(function () {
+		init();
+	});
+})(jQuery, window);

+ 125 - 0
src/main/resources/views/modules/mes/mesGp12IssueForm.html

@@ -0,0 +1,125 @@
+<% layout('/layouts/default.html', {title: 'GP12问题点配置', libs: ['validate','fileupload']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-note"></i> ${text(mesGp12Issue.isNewRecord ? '新增GP12问题点' : '编辑GP12问题点')}
+			</div>
+			<div class="box-tools pull-right">
+				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
+			</div>
+		</div>
+		<#form:form id="inputForm" model="${mesGp12Issue}" action="${ctx}/mes/mesGp12Issue/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="">
+								<span class="required">*</span> ${text('检验项目')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="checkItem" maxlength="255" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="">
+								${text('判定标准')}:</label>
+							<div class="col-sm-8">
+								<#form:textarea path="standard" rows="4" maxlength="2000" class="form-control"/>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="">
+								${text('检验方法')}:</label>
+							<div class="col-sm-8">
+								<#form:textarea path="method" rows="3" maxlength="1000" class="form-control"/>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								${text('排序号')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="sort" maxlength="8" class="form-control digits" placeholder="数字越小越靠前"/>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2">
+								${text('NG图示')}:</label>
+							<div class="col-sm-8">
+								<#form:fileupload id="uploadNgImage" bizKey="${mesGp12Issue.id}" bizType="mesGp12Issue_ng_image"
+									uploadType="image" class="" readonly="false" preview="true" maxUploadNum="9"/>
+								<div class="help-block">可上传多张,也可不传</div>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2">
+								${text('OK图示')}:</label>
+							<div class="col-sm-8">
+								<#form:fileupload id="uploadOkImage" bizKey="${mesGp12Issue.id}" bizType="mesGp12Issue_ok_image"
+									uploadType="image" class="" readonly="false" preview="true" maxUploadNum="9"/>
+								<div class="help-block">可上传多张,也可不传</div>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="">
+								${text('备注')}:</label>
+							<div class="col-sm-8">
+								<#form:textarea path="remarks" rows="2" maxlength="500" class="form-control"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% if (hasPermi('mes:mesGp12Issue:edit')){ %>
+							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
+						<% } %>
+						<button type="button" class="btn btn-sm btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply-all"></i> ${text('关 闭')}</button>
+					</div>
+				</div>
+			</div>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+$("#inputForm").validate({
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){
+					contentWindow.page();
+				});
+			}
+		}, "json");
+    }
+});
+</script>

+ 84 - 0
src/main/resources/views/modules/mes/mesGp12IssueList.html

@@ -0,0 +1,84 @@
+<% layout('/layouts/default.html', {title: 'GP12问题点配置', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('GP12问题点配置')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<a href="${ctx}/mes/mesProduct/screen8" target="_blank" class="btn btn-default"><i class="fa fa-desktop"></i> ${text('看板')}</a>
+				<% if(hasPermi('mes:mesGp12Issue:edit')){ %>
+					<a href="${ctx}/mes/mesGp12Issue/form" class="btn btn-default btnTool" title="${text('新增GP12问题点')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
+				<% } %>
+				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
+			</div>
+		</div>
+		<div class="box-body">
+			<#form:form id="searchForm" model="${mesGp12Issue}" action="${ctx}/mes/mesGp12Issue/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
+				<div class="form-group">
+					<label class="control-label">${text('检验项目')}:</label>
+					<div class="control-inline">
+						<#form:input path="checkItem" maxlength="255" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('判定标准')}:</label>
+					<div class="control-inline">
+						<#form:input path="standard" maxlength="2000" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('检验方法')}:</label>
+					<div class="control-inline">
+						<#form:input path="method" maxlength="1000" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('状态')}:</label>
+					<div class="control-inline width-120">
+						<#form:select path="status" dictType="sys_search_status" blankOption="true" class="form-control isQuick"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<button type="submit" class="btn btn-primary btn-sm">${text('查询')}</button>
+					<button type="reset" class="btn btn-default btn-sm">${text('重置')}</button>
+				</div>
+			</#form:form>
+			<table id="dataGrid"></table>
+			<div id="dataGridPage"></div>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("排序号")}', name:'sort', index:'a.sort', width:80, align:"center"},
+		{header:'${text("检验项目")}', name:'checkItem', index:'a.check_item', width:180, align:"left", formatter: function(val, obj, row, act){
+			return '<a href="${ctx}/mes/mesGp12Issue/form?id='+row.id+'" class="btnList" data-title="${text("编辑GP12问题点")}">'+(val||row.id)+'</a>';
+		}},
+		{header:'${text("判定标准")}', name:'standard', index:'a.standard', width:260, align:"left"},
+		{header:'${text("检验方法")}', name:'method', index:'a.method', width:180, align:"left"},
+		{header:'${text("状态")}', name:'status', index:'a.status', width:80, align:"center", formatter: function(val, obj, row, act){
+			return js.getDictLabel(${@DictUtils.getDictListJson('sys_search_status')}, val, '${text("未知")}', true);
+		}},
+		{header:'${text("更新时间")}', name:'updateDate', index:'a.update_date', width:150, align:"center"},
+		{header:'${text("操作")}', name:'actions', width:120, formatter: function(val, obj, row, act){
+			var actions = [];
+			//<% if(hasPermi('mes:mesGp12Issue:edit')){ %>
+				actions.push('<a href="${ctx}/mes/mesGp12Issue/form?id='+row.id+'" class="btnList" title="${text("编辑GP12问题点")}"><i class="fa fa-pencil"></i></a>&nbsp;');
+				if (row.status == Global.STATUS_NORMAL){
+					actions.push('<a href="${ctx}/mes/mesGp12Issue/disable?id='+row.id+'" class="btnList" title="${text("停用GP12问题点")}" data-confirm="${text("确认要停用该GP12问题点吗?")}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');
+				}else if (row.status == Global.STATUS_DISABLE){
+					actions.push('<a href="${ctx}/mes/mesGp12Issue/enable?id='+row.id+'" class="btnList" title="${text("启用GP12问题点")}" data-confirm="${text("确认要启用该GP12问题点吗?")}"><i class="glyphicon glyphicon-ok-circle"></i></a>&nbsp;');
+				}
+				actions.push('<a href="${ctx}/mes/mesGp12Issue/delete?id='+row.id+'" class="btnList" title="${text("删除GP12问题点")}" data-confirm="${text("确认要删除该GP12问题点吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
+			//<% } %>
+			return actions.join('');
+		}}
+	]
+});
+</script>

+ 18 - 0
src/main/resources/views/modules/mes/mesLineProcessForm.html

@@ -108,6 +108,15 @@
 					<div class="col-xs-12">
 						<div class="form-group">
 							<label class="control-label col-sm-2" title="">
+								<span class="required hide">*</span> ${text('最长加工时长(分钟)')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-4">
+								<#form:input path="maxDuration" maxlength="8" class="form-control digits"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="">
 								<span class="required hide">*</span> ${text('是否拍照后提交')}:<i class="fa icon-question hide"></i></label>
 							<div class="col-sm-4">
 								<#form:select path="isPz" dictType="mes_line_process_quality" blankOption="true" class="form-control" />
@@ -132,6 +141,15 @@
 							</div>
 						</div>
 					</div>
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="同一工件在本工位最多允许NG次数(仅计NG,OK不计);空或不填表示不限制">
+								<span class="required hide">*</span> ${text('气密最大NG次数')}:<i class="fa icon-question"></i></label>
+							<div class="col-sm-4">
+								<#form:input path="qmMaxTimes" maxlength="8" class="form-control digits"/>
+							</div>
+						</div>
+					</div>
 				</div>
 			</div>
 			<div class="box-footer">

+ 2 - 0
src/main/resources/views/modules/mes/mesLineProcessList.html

@@ -71,6 +71,7 @@ $('#dataGrid').dataGrid({
 				return js.getDictLabel(${@DictUtils.getDictListJson('mes_line_process_type')}, val, '${text("未知")}', true);
 			}},
 		{header:'${text("固化时长(分钟)")}', name:'duration', index:'a.duration', width:100, align:"center"},
+		{header:'${text("最长加工(分钟)")}', name:'maxDuration', index:'a.max_duration', width:110, align:"center"},
 		{header:'${text("是否拍照后提交")}', name:'isPz', index:'a.is_pz', width:100, align:"center", formatter: function(val, obj, row, act){
 				return js.getDictLabel(${@DictUtils.getDictListJson('mes_line_process_quality')}, val, '${text("未知")}', true);
 			}},
@@ -78,6 +79,7 @@ $('#dataGrid').dataGrid({
 				return js.getDictLabel(${@DictUtils.getDictListJson('mes_line_process_quality')}, val, '${text("未知")}', true);
 			}},
 		{header:'${text("NG后需要连续测试OK的次数")}', name:'qmNum', index:'a.qm_num', width:100, align:"center"},
+		{header:'${text("气密最大NG次数")}', name:'qmMaxTimes', index:'a.qm_max_times', width:120, align:"center"},
 		{header:'${text("状态")}', name:'status', index:'a.status', width:100, align:"center", formatter: function(val, obj, row, act){
 				return js.getDictLabel(${@DictUtils.getDictListJson('sys_search_status')}, val, '${text("未知")}', true);
 			}},

+ 29 - 166
src/main/resources/views/modules/mes/mesScreen8.html

@@ -4,188 +4,51 @@
 	<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>GP12不良问题点看板</title>
+	<link rel="stylesheet" href="${ctxStatic}/screen/gp12-issue-screen.css?v=202607154">
 </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">GP12不良问题点看板</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>
-		<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="issue-table">
+					<thead>
+					<tr>
+						<th class="col-seq">序号</th>
+						<th class="col-item">检验项目</th>
+						<th class="col-standard">判定标准</th>
+						<th class="col-method">检验方法</th>
+						<th class="col-ng">NG图示</th>
+						<th class="col-ok">OK图示</th>
+					</tr>
+					</thead>
+					<tbody id="issueBody" 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: ["OP320", "OP330", "OP340", "OP350", "OP360", "OP370", "OP380", "OP390", "OP400", "OP410"]
+	window.gp12IssueConfig = {
+		title: "GP12不良问题点看板",
+		pageSize: 4,
+		carouselInterval: 8000,
+		refreshInterval: 60000,
+		apiUrl: "${ctx}/mes/mesProduct/screenGp12IssueList"
 	};
 </script>
-<script src="${ctxStatic}/screen/oprno-record-screen.js?v=202506244"></script>
+<script src="${ctxStatic}/screen/gp12-issue-screen.js?v=202607153"></script>
 </body>
 </html>