Jelajahi Sumber

大屏基础修改

sunxianglong 1 Minggu lalu
induk
melakukan
6ab5fae15f

+ 32 - 9
src/main/java/com/jeesite/modules/mes/entity/MesProductScreen.java

@@ -9,18 +9,19 @@ import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.Size;
 
 /**
- * 大屏展示配置Entity(生产人数 / 设备数量 / 计划产量)
+ * 大屏展示配置Entity(生产人数 / 设备数量 / 总计划产量)
  * 对应表 mes_product_screen
  * @author mes
  * @version 2026-08-12
  */
 @Table(name="mes_product_screen", alias="a", label="大屏展示配置信息", columns={
 		@Column(name="id", attrName="id", label="id", isPK=true),
-		@Column(name="screen_type", attrName="screenType", label="大屏类型", comment="ZS总屏 MP毛坯 JJ机加 ZP装配", queryType=QueryType.EQ),
+		@Column(name="screen_type", attrName="screenType", label="大屏类型", comment="ZS总屏 MP铸造 JJ机加 ZP装配", queryType=QueryType.EQ),
 		@Column(name="people_num", attrName="peopleNum", label="生产人数"),
 		@Column(name="machine_num", attrName="machineNum", label="设备数量"),
-		@Column(name="plan_output", attrName="planOutput", label="每日计划"),
-		@Column(name="op_plan", attrName="opPlan", label="工位当天计划", isQuery=false),
+		@Column(name="plan_output", attrName="planOutput", label="总计划产量"),
+		@Column(name="week_plan", attrName="weekPlan", label="本周计划"),
+		@Column(name="op_plan", attrName="opPlan", label="工位白夜班计划", isQuery=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="update_by", isQuery=false),
@@ -30,12 +31,16 @@ import javax.validation.constraints.Size;
 public class MesProductScreen extends DataEntity<MesProductScreen> {
 
 	private static final long serialVersionUID = 1L;
-	/** ZS总屏 / MP毛坯 / JJ机加 / ZP装配 */
+	/** ZS总屏 / MP铸造 / JJ机加 / ZP装配 */
 	private String screenType;
 	private String peopleNum;
 	private String machineNum;
 	private String planOutput;
-	/** 工位当天计划 JSON,如 {"OP060":30,"OP070":30} */
+	/** 本周计划产量(可编辑) */
+	private String weekPlan;
+	/** 本周产量(只读,按大屏类型汇总,不落库) */
+	private String weekOutput;
+	/** 工位白/夜班计划 JSON:{"day":{"OP060":30},"night":{"OP060":20}};兼容旧版 {"OP060":30} */
 	private String opPlan;
 
 	public MesProductScreen() {
@@ -76,8 +81,8 @@ public class MesProductScreen extends DataEntity<MesProductScreen> {
 		this.machineNum = machineNum;
 	}
 
-	@NotBlank(message="每日计划不能为空")
-	@Size(min=0, max=255, message="每日计划长度不能超过 255 个字符")
+	@NotBlank(message="总计划产量不能为空")
+	@Size(min=0, max=255, message="总计划产量长度不能超过 255 个字符")
 	public String getPlanOutput() {
 		return planOutput;
 	}
@@ -86,7 +91,25 @@ public class MesProductScreen extends DataEntity<MesProductScreen> {
 		this.planOutput = planOutput;
 	}
 
-	@Size(min=0, max=2000, message="工位当天计划长度不能超过 2000 个字符")
+	@Size(min=0, max=255, message="本周计划长度不能超过 255 个字符")
+	public String getWeekPlan() {
+		return weekPlan;
+	}
+
+	public void setWeekPlan(String weekPlan) {
+		this.weekPlan = weekPlan;
+	}
+
+	/** 非表字段:本周实际产量(展示用) */
+	public String getWeekOutput() {
+		return weekOutput;
+	}
+
+	public void setWeekOutput(String weekOutput) {
+		this.weekOutput = weekOutput;
+	}
+
+	@Size(min=0, max=2000, message="工位白夜班计划长度不能超过 2000 个字符")
 	public String getOpPlan() {
 		return opPlan;
 	}

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

@@ -1452,6 +1452,46 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 	}
 
 	/**
+	 * 日历当天固定两班产能:
+	 * day = 当天 08:00–20:00;night = 当天 20:00–次日 08:00
+	 */
+	public Map<String, Integer> getCalendarDayShiftCapacityStats(String lineSn, List<String> oprnos, String shiftType) {
+		Map<String, Integer> ret = MapUtils.newHashMap();
+		if (StringUtils.isBlank(lineSn) || oprnos == null || oprnos.isEmpty()) {
+			return ret;
+		}
+		boolean night = "night".equalsIgnoreCase(StringUtils.trimToEmpty(shiftType));
+		Calendar startCal = Calendar.getInstance();
+		Calendar endCal = Calendar.getInstance();
+		startCal.set(Calendar.MINUTE, 0);
+		startCal.set(Calendar.SECOND, 0);
+		startCal.set(Calendar.MILLISECOND, 0);
+		endCal.set(Calendar.MINUTE, 0);
+		endCal.set(Calendar.SECOND, 0);
+		endCal.set(Calendar.MILLISECOND, 0);
+		if (night) {
+			startCal.set(Calendar.HOUR_OF_DAY, 20);
+			endCal.add(Calendar.DAY_OF_MONTH, 1);
+			endCal.set(Calendar.HOUR_OF_DAY, 8);
+		} else {
+			startCal.set(Calendar.HOUR_OF_DAY, 8);
+			endCal.set(Calendar.HOUR_OF_DAY, 20);
+		}
+		Date startTime = startCal.getTime();
+		Date endTime = endCal.getTime();
+		for (String oprno : oprnos) {
+			if (StringUtils.isBlank(oprno)) {
+				continue;
+			}
+			String formatOprno = CommonUitl.formatOprno(oprno.trim());
+			Long c = mesProductRecordDao.countHourCapacityByLineAndOprnoPrefix(
+					lineSn.trim(), formatOprno, startTime, endTime);
+			ret.put(formatOprno, c == null ? 0 : c.intValue());
+		}
+		return ret;
+	}
+
+	/**
 	 * 固定两班制当班产能:08:00–20:00 或 20:00–次日 08:00
 	 */
 	public Map<String, Integer> getFixedTwoShiftCapacityStats(String lineSn, List<String> oprnos) {
@@ -1594,6 +1634,46 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 	}
 
 	/**
+	 * 指定产线、工位前缀、时间段内产能(与 dayCapacity 口径一致:craft=100000 且非 UDF)
+	 */
+	public int countCapacityByLineAndOprnoPrefix(String lineSn, String oprnoPrefix, Date startTime, Date endTime) {
+		if (StringUtils.isBlank(lineSn) || StringUtils.isBlank(oprnoPrefix) || startTime == null || endTime == null) {
+			return 0;
+		}
+		Long c = mesProductRecordDao.countHourCapacityByLineAndOprnoPrefix(
+				lineSn.trim(), CommonUitl.formatOprno(oprnoPrefix.trim()), startTime, endTime);
+		return c == null ? 0 : c.intValue();
+	}
+
+	/**
+	 * 本周产量:周一 00:00 至下周一 00:00,按大屏类型取对应下线工位产能合计。
+	 * MP铸造 OP100 / JJ机加 OP160 / ZP装配·ZS总屏 OP400;产线默认 XT。
+	 */
+	public int getWeekOutputByScreenType(String screenType) {
+		String code = MesProductScreenService.normalizeScreenType(screenType);
+		String oprno;
+		if ("MP".equals(code)) {
+			oprno = "OP100";
+		} else if ("JJ".equals(code)) {
+			oprno = "OP160";
+		} else if ("ZP".equals(code) || "ZS".equals(code)) {
+			oprno = "OP400";
+		} else {
+			return 0;
+		}
+		Calendar startCal = Calendar.getInstance();
+		startCal.setFirstDayOfWeek(Calendar.MONDAY);
+		startCal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
+		startCal.set(Calendar.HOUR_OF_DAY, 0);
+		startCal.set(Calendar.MINUTE, 0);
+		startCal.set(Calendar.SECOND, 0);
+		startCal.set(Calendar.MILLISECOND, 0);
+		Calendar endCal = (Calendar) startCal.clone();
+		endCal.add(Calendar.DAY_OF_MONTH, 7);
+		return countCapacityByLineAndOprnoPrefix("XT", oprno, startCal.getTime(), endCal.getTime());
+	}
+
+	/**
 	 * 固定两班制当前班次:指定工位 craft=100000 且 content=NG 的数量。
 	 * 白班 08:00–20:00;夜班 20:00–次日 08:00。
 	 */

+ 48 - 17
src/main/java/com/jeesite/modules/mes/service/MesProductScreenService.java

@@ -59,7 +59,7 @@ public class MesProductScreenService extends CrudService<MesProductScreenDao, Me
 
 	/**
 	 * 按大屏类型取最新一条配置。screenType 为空时取全局最新(兼容旧调用)。
-	 * ZS总屏 / MP毛坯 / JJ机加 / ZP装配
+	 * ZS总屏 / MP铸造 / JJ机加 / ZP装配
 	 */
 	public MesProductScreen getLatestByScreenType(String screenType) {
 		String code = normalizeScreenType(screenType);
@@ -97,32 +97,63 @@ public class MesProductScreenService extends CrudService<MesProductScreenDao, Me
 		return s.toUpperCase();
 	}
 
+	/**
+	 * 解析工位计划。兼容两种格式:
+	 * 1) 旧版扁平:{"OP060":30}
+	 * 2) 白/夜班:{"day":{"OP060":30},"night":{"OP060":20}}
+	 * 旧版数据计入白班。
+	 */
 	public static Map<String, Integer> parseOpPlanMap(String json) {
-		Map<String, Integer> map = new LinkedHashMap<>();
+		return parseOpPlanShiftMaps(json).get("day");
+	}
+
+	public static Map<String, Map<String, Integer>> parseOpPlanShiftMaps(String json) {
+		Map<String, Map<String, Integer>> ret = new LinkedHashMap<>();
+		Map<String, Integer> day = new LinkedHashMap<>();
+		Map<String, Integer> night = new LinkedHashMap<>();
+		ret.put("day", day);
+		ret.put("night", night);
 		if (StringUtils.isBlank(json)) {
-			return map;
+			return ret;
 		}
 		try {
 			JSONObject obj = JSON.parseObject(json);
 			if (obj == null) {
-				return map;
+				return ret;
+			}
+			if (obj.containsKey("day") || obj.containsKey("night")) {
+				fillOpPlanFlat(day, obj.getJSONObject("day"));
+				fillOpPlanFlat(night, obj.getJSONObject("night"));
+				return ret;
+			}
+			// 旧版扁平结构 -> 白班
+			fillOpPlanFlat(day, obj);
+		} catch (Exception ignore) {
+			return ret;
+		}
+		return ret;
+	}
+
+	private static void fillOpPlanFlat(Map<String, Integer> map, JSONObject obj) {
+		if (map == null || obj == null) {
+			return;
+		}
+		for (String key : obj.keySet()) {
+			if (StringUtils.isBlank(key)) {
+				continue;
 			}
-			for (String key : obj.keySet()) {
-				if (StringUtils.isBlank(key)) {
+			String code = key.trim().toUpperCase();
+			int val = 0;
+			try {
+				Object raw = obj.get(key);
+				if (raw instanceof JSONObject) {
 					continue;
 				}
-				String code = key.trim().toUpperCase();
-				int val = 0;
-				try {
-					val = obj.getIntValue(key);
-				} catch (Exception ignore) {
-					val = 0;
-				}
-				map.put(code, val);
+				val = obj.getIntValue(key);
+			} catch (Exception ignore) {
+				val = 0;
 			}
-		} catch (Exception ignore) {
-			return map;
+			map.put(code, val);
 		}
-		return map;
 	}
 }

+ 62 - 0
src/main/java/com/jeesite/modules/mes/web/MesFlawRecordController.java

@@ -9,8 +9,10 @@ import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Pattern;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -25,11 +27,13 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.util.ObjectUtils;
 
 import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
 import com.jeesite.common.mybatis.mapper.query.QueryType;
 import com.jeesite.common.web.BaseController;
 import com.jeesite.modules.mes.entity.MesProductRecord;
 import com.jeesite.modules.mes.entity.MesTemplateItems;
 import com.jeesite.modules.mes.req.MesSearchDateReq;
+import com.jeesite.modules.mes.resp.CommonResp;
 import com.jeesite.modules.mes.service.MesProductRecordService;
 import com.jeesite.modules.mes.service.MesTemplateItemsService;
 import com.jeesite.modules.mes.util.CommonUitl;
@@ -85,6 +89,64 @@ public class MesFlawRecordController extends BaseController {
 	}
 
 	/**
+	 * 大屏:按白班/夜班统计探伤去重产量(与探伤记录管理口径一致:OP070A + 有图片,按工件码去重)
+	 * shiftType=day  -> 当天 08:00–20:00
+	 * shiftType=night -> 当天 20:00–次日 08:00
+	 */
+	@RequestMapping(value = "calendarShiftDistinctCount")
+	@ResponseBody
+	public CommonResp<Integer> calendarShiftDistinctCount(HttpServletRequest request) {
+		CommonResp<Integer> resp = new CommonResp<>();
+		String shiftType = StringUtils.trimToEmpty(request.getParameter("shiftType"));
+		if (!"day".equalsIgnoreCase(shiftType) && !"night".equalsIgnoreCase(shiftType)) {
+			resp.setResult("false");
+			resp.setMessage("shiftType 只能为 day 或 night");
+			return resp;
+		}
+		try {
+			boolean night = "night".equalsIgnoreCase(shiftType);
+			Calendar startCal = Calendar.getInstance();
+			Calendar endCal = Calendar.getInstance();
+			startCal.set(Calendar.MINUTE, 0);
+			startCal.set(Calendar.SECOND, 0);
+			startCal.set(Calendar.MILLISECOND, 0);
+			endCal.set(Calendar.MINUTE, 0);
+			endCal.set(Calendar.SECOND, 0);
+			endCal.set(Calendar.MILLISECOND, 0);
+			if (night) {
+				startCal.set(Calendar.HOUR_OF_DAY, 20);
+				endCal.add(Calendar.DAY_OF_MONTH, 1);
+				endCal.set(Calendar.HOUR_OF_DAY, 8);
+			} else {
+				startCal.set(Calendar.HOUR_OF_DAY, 8);
+				endCal.set(Calendar.HOUR_OF_DAY, 20);
+			}
+			// 右开区间:结束时刻减 1 秒,避免与下一班边界重复
+			endCal.add(Calendar.SECOND, -1);
+			MesProductRecord query = new MesProductRecord();
+			query.setUpdateDate_gte(startCal.getTime());
+			query.setUpdateDate_lte(endCal.getTime());
+			applyListQuery(query);
+			query.setPage(null);
+			List<MesProductRecord> withImages = filterRecordsWithImages(mesProductRecordService.findList(query));
+			Set<String> distinctSn = new HashSet<>();
+			for (MesProductRecord record : withImages) {
+				if (record != null && StringUtils.isNotBlank(record.getSn())) {
+					distinctSn.add(record.getSn().trim());
+				}
+			}
+			resp.setData(distinctSn.size());
+			resp.setResult("true");
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			resp.setData(0);
+			resp.setResult("false");
+			resp.setMessage("查询失败:" + e.getMessage());
+		}
+		return resp;
+	}
+
+	/**
 	 * 查询列表数据(仅返回磁盘上存在探伤图片的记录)
 	 */
 	@RequiresPermissions("mes:mesProductRecord:view")

+ 4 - 3
src/main/java/com/jeesite/modules/mes/web/MesProductController.java

@@ -360,7 +360,8 @@ public class MesProductController extends BaseController {
 		return resp;
 	}
 
-	@RequestMapping(value = {"screen", ""})
+	/** 大屏轮换入口(勿映射空路径 "",否则会与 list 冲突,误进后台列表页) */
+	@RequestMapping(value = "screen")
 	public String screen(Model model) {
 		return "modules/mes/mesScreenRotate";
 	}
@@ -651,8 +652,8 @@ public class MesProductController extends BaseController {
 	}
 
 	/**
-	 * 综合大屏 - 当天返修数量 / 报废数量
-	 * 返修数据来自 mes_repair(返工记录表),报废数据来自 mes_scrap(报废记录表)
+	 * 综合大屏 - 当天返工数量 / 报废数量
+	 * 返工数据来自 mes_repair(返工记录表),报废数据来自 mes_scrap(报废记录表)
 	 */
 	@RequestMapping(value = "dashboardCount")
 	@ResponseBody

+ 41 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java

@@ -461,6 +461,47 @@ public class MesProductRecordController extends BaseController {
 	}
 
 	/**
+	 * 日历当天白班/夜班产量:
+	 * shiftType=day  -> 当天 08:00–20:00
+	 * shiftType=night -> 当天 20:00–次日 08:00
+	 */
+	@RequestMapping(value = "calendarShiftCapacityData")
+	@ResponseBody
+	public CommonResp<Map<String, Integer>> calendarShiftCapacityData(HttpServletRequest request) {
+		CommonResp<Map<String, Integer>> resp = new CommonResp<>();
+		String lineSn = StringUtils.trimToEmpty(request.getParameter("lineSn"));
+		String oprnos = StringUtils.trimToEmpty(request.getParameter("oprnos"));
+		String shiftType = StringUtils.trimToEmpty(request.getParameter("shiftType"));
+		if (StringUtils.isBlank(lineSn) || StringUtils.isBlank(oprnos)) {
+			resp.setResult("false");
+			resp.setMessage("lineSn、oprnos 不能为空");
+			return resp;
+		}
+		if (!"day".equalsIgnoreCase(shiftType) && !"night".equalsIgnoreCase(shiftType)) {
+			resp.setResult("false");
+			resp.setMessage("shiftType 只能为 day 或 night");
+			return resp;
+		}
+		try {
+			List<String> oprnoList = new ArrayList<>();
+			String[] arr = oprnos.split(",");
+			for (String item : arr) {
+				String oprno = StringUtils.trimToEmpty(item);
+				if (StringUtils.isNotBlank(oprno)) {
+					oprnoList.add(oprno);
+				}
+			}
+			resp.setData(mesProductRecordService.getCalendarDayShiftCapacityStats(lineSn, oprnoList, shiftType));
+			resp.setResult("true");
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			resp.setResult("false");
+			resp.setMessage("查询失败:" + e.getMessage());
+		}
+		return resp;
+	}
+
+	/**
 	 * 当班产能:根据班次生产计划管理的当前班次时间段统计;
 	 * 传 fixedTwoShift=true 时改为固定两班 08:00–20:00 / 20:00–次日 08:00
 	 */

+ 60 - 4
src/main/java/com/jeesite/modules/mes/web/MesProductScreenController.java

@@ -83,21 +83,62 @@ public class MesProductScreenController extends BaseController {
 	public Page<MesProductScreen> listData(MesProductScreen mesProductScreen,
 			HttpServletRequest request, HttpServletResponse response) {
 		mesProductScreen.setPage(new Page<>(request, response));
-		return mesProductScreenService.findPage(mesProductScreen);
+		Page<MesProductScreen> page = mesProductScreenService.findPage(mesProductScreen);
+		if (page != null && page.getList() != null) {
+			for (MesProductScreen item : page.getList()) {
+				fillWeekOutput(item);
+			}
+		}
+		return page;
 	}
 
 	@RequiresPermissions("mes:mesProductScreen:view")
 	@RequestMapping(value = "form")
 	public String form(MesProductScreen mesProductScreen, Model model) {
+		fillWeekOutput(mesProductScreen);
 		model.addAttribute("mesProductScreen", mesProductScreen);
 		model.addAttribute("screenTypeMap", buildScreenTypeMap());
 		return "modules/mes/mesProductScreenForm";
 	}
 
+	/** 按大屏类型查询本周产量(只读展示,不落库) */
+	@RequiresPermissions("mes:mesProductScreen:view")
+	@RequestMapping(value = "weekOutputData")
+	@ResponseBody
+	public CommonResp<Map<String, Object>> weekOutputData(HttpServletRequest request) {
+		CommonResp<Map<String, Object>> resp = new CommonResp<>();
+		Map<String, Object> data = new HashMap<>();
+		try {
+			String screenType = StringUtils.trimToEmpty(request.getParameter("screenType"));
+			int weekOutput = mesProductRecordService.getWeekOutputByScreenType(screenType);
+			data.put("weekOutput", weekOutput);
+			resp.setData(data);
+			resp.setResult(Global.TRUE);
+		} catch (Exception e) {
+			data.put("weekOutput", 0);
+			resp.setData(data);
+			resp.setResult(Global.FALSE);
+			resp.setMessage(e.getMessage());
+		}
+		return resp;
+	}
+
+	private void fillWeekOutput(MesProductScreen mesProductScreen) {
+		if (mesProductScreen == null) {
+			return;
+		}
+		try {
+			int n = mesProductRecordService.getWeekOutputByScreenType(mesProductScreen.getScreenType());
+			mesProductScreen.setWeekOutput(String.valueOf(n));
+		} catch (Exception e) {
+			mesProductScreen.setWeekOutput("0");
+		}
+	}
+
 	private static Map<String, String> buildScreenTypeMap() {
 		Map<String, String> map = new java.util.LinkedHashMap<>();
 		map.put("ZS", "总屏");
-		map.put("MP", "毛坯大屏");
+		map.put("MP", "铸造大屏");
 		map.put("JJ", "机加大屏");
 		map.put("ZP", "装配大屏");
 		return map;
@@ -132,7 +173,22 @@ public class MesProductScreenController extends BaseController {
 		data.put("peopleNums", parseIntSafe(latest == null ? null : latest.getPeopleNum()));
 		data.put("deviceNums", parseIntSafe(latest == null ? null : latest.getMachineNum()));
 		data.put("planNums", parseIntSafe(latest == null ? null : latest.getPlanOutput()));
-		data.put("opPlans", MesProductScreenService.parseOpPlanMap(latest == null ? null : latest.getOpPlan()));
+		data.put("weekPlan", parseIntSafe(latest == null ? null : latest.getWeekPlan()));
+		int weekOutput = 0;
+		try {
+			weekOutput = mesProductRecordService.getWeekOutputByScreenType(
+					latest == null ? screenType : latest.getScreenType());
+		} catch (Exception ignore) {
+			weekOutput = 0;
+		}
+		data.put("weekOutput", weekOutput);
+		Map<String, Map<String, Integer>> shiftPlans = MesProductScreenService.parseOpPlanShiftMaps(
+				latest == null ? null : latest.getOpPlan());
+		Map<String, Integer> dayPlans = shiftPlans.get("day");
+		Map<String, Integer> nightPlans = shiftPlans.get("night");
+		data.put("opPlans", dayPlans); // 兼容旧大屏:默认取白班计划
+		data.put("opPlansDay", dayPlans);
+		data.put("opPlansNight", nightPlans);
 		resp.setData(data);
 		resp.setResult(Global.TRUE);
 		return resp;
@@ -153,7 +209,7 @@ public class MesProductScreenController extends BaseController {
 		return page;
 	}
 
-	/** 大屏:当天返修/报废数量(替代 mesRepair/listData + mesScrap/listData 计数) */
+	/** 大屏:当天返工/报废数量(替代 mesRepair/listData + mesScrap/listData 计数) */
 	@RequestMapping(value = "repairScrapCount")
 	@ResponseBody
 	public CommonResp<Map<String, Object>> repairScrapCount() {

+ 1 - 1
src/main/java/com/jeesite/modules/mes/web/MesSaleSnController.java

@@ -157,7 +157,7 @@ public class MesSaleSnController extends BaseController {
 	public String findSaleList() {
 		MesProductSale query = new MesProductSale();
 		query.setShState("1");
-		query.setMesState("1");
+//		query.setMesState("1");
 		List<MesProductSale> list = mesProductSaleService.findList(query);
 		if (list != null) {
 			for (MesProductSale sale : list) {

+ 120 - 106
src/main/resources/views/modules/mes/mesProductScreenForm.html

@@ -22,7 +22,7 @@
 								<select name="screenType" id="screenType" class="form-control required">
 									<option value="">${text('-- 请选择 --')}</option>
 									<option value="ZS">总屏</option>
-									<option value="MP">毛坯大屏</option>
+									<option value="MP">铸造大屏</option>
 									<option value="JJ">机加大屏</option>
 									<option value="ZP">装配大屏</option>
 								</select>
@@ -32,6 +32,25 @@
 					<div class="col-xs-6">
 						<div class="form-group">
 							<label class="control-label col-sm-4" title="">
+								${text('本周计划')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="weekPlan" maxlength="255" class="form-control digits"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								${text('本周产量')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<input type="text" id="weekOutput" name="weekOutput" class="form-control"
+									value="${mesProductScreen.weekOutput!}" readonly="readonly"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
 								<span class="required ">*</span> ${text('生产人数')}:<i class="fa icon-question hide"></i></label>
 							<div class="col-sm-8">
 								<#form:input path="peopleNum" maxlength="255" class="form-control required digits"/>
@@ -50,7 +69,7 @@
 					<div class="col-xs-6">
 						<div class="form-group">
 							<label class="control-label col-sm-4" title="">
-								<span class="required ">*</span> ${text('每日计划')}:<i class="fa icon-question hide"></i></label>
+								<span class="required ">*</span> ${text('总计划产量')}:<i class="fa icon-question hide"></i></label>
 							<div class="col-sm-8">
 								<#form:input path="planOutput" maxlength="255" class="form-control required digits"/>
 							</div>
@@ -58,77 +77,13 @@
 					</div>
 				</div>
 				<#form:hidden path="opPlan"/>
-				<div class="form-unit hide" id="opPlanTitle">${text('工位当天计划')}</div>
-				<div id="opPlanMp" class="hide">
-					<div class="row">
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP060')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP060" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP080')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP080" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP090')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP090" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP100')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP100" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-					</div>
+				<div class="form-unit hide" id="opPlanDayTitle">白班计划</div>
+				<div id="opPlanDayBody" class="hide">
+					<div class="row" id="opPlanDayRow"></div>
 				</div>
-				<div id="opPlanJj" class="hide">
-					<div class="row">
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP110')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP110" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP120')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP120" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP130')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP130" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP140')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP140" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP150')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP150" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-						<div class="col-xs-4">
-							<div class="form-group">
-								<label class="control-label col-sm-5">${text('OP160')}:</label>
-								<div class="col-sm-7"><input type="text" id="plan_OP160" class="form-control digits" maxlength="8"/></div>
-							</div>
-						</div>
-					</div>
-				</div>
-				<div id="opPlanZp" class="hide">
-					<div class="row" id="opPlanZpRow"></div>
+				<div class="form-unit hide" id="opPlanNightTitle">夜班计划</div>
+				<div id="opPlanNightBody" class="hide">
+					<div class="row" id="opPlanNightRow"></div>
 				</div>
 			</div>
 			<div class="box-footer">
@@ -146,7 +101,7 @@
 </div>
 <% } %>
 <script>
-var OPS_MP = ['OP060','OP080','OP090','OP100'];
+var OPS_MP = ['OP060','OP070','OP080','OP090','OP100'];
 var OPS_JJ = ['OP110','OP120','OP130','OP140','OP150','OP160'];
 var OPS_ZP = (function(){
 	var arr = [];
@@ -155,61 +110,95 @@ var OPS_ZP = (function(){
 	}
 	return arr;
 })();
-function buildOpPlanZpInputs() {
-	var $row = $("#opPlanZpRow");
-	if (!$row.length || $row.children().length) return;
+function todayMd() {
+	var d = new Date();
+	return (d.getMonth() + 1) + '.' + d.getDate();
+}
+function updateShiftTitles() {
+	var md = todayMd();
+	$("#opPlanDayTitle").text(md + '白班计划');
+	$("#opPlanNightTitle").text(md + '夜班计划');
+}
+function opKeysByScreenType(st) {
+	if (st === 'MP') return OPS_MP;
+	if (st === 'JJ') return OPS_JJ;
+	if (st === 'ZP') return OPS_ZP;
+	return [];
+}
+function buildShiftInputs(rowId, shift) {
+	var st = $("#screenType").val();
+	var keys = opKeysByScreenType(st);
+	var $row = $("#" + rowId);
+	if (!$row.length) return;
 	var html = [];
-	for (var i = 0; i < OPS_ZP.length; i++) {
-		var op = OPS_ZP[i];
+	for (var i = 0; i < keys.length; i++) {
+		var op = keys[i];
 		html.push(
 			'<div class="col-xs-4"><div class="form-group">' +
 			'<label class="control-label col-sm-5">' + op + ':</label>' +
-			'<div class="col-sm-7"><input type="text" id="plan_' + op + '" class="form-control digits" maxlength="8"/></div>' +
+			'<div class="col-sm-7"><input type="text" id="plan_' + shift + '_' + op + '" class="form-control digits" maxlength="8"/></div>' +
 			'</div></div>'
 		);
 	}
 	$row.html(html.join(''));
 }
-function opKeysByScreenType(st) {
-	if (st === 'MP') return OPS_MP;
-	if (st === 'JJ') return OPS_JJ;
-	if (st === 'ZP') return OPS_ZP;
-	return [];
-}
 function collectOpPlan() {
 	var st = $("#screenType").val();
 	var keys = opKeysByScreenType(st);
-	var obj = {};
+	if (!keys.length) {
+		$("#opPlan").val('');
+		return;
+	}
+	var day = {}, night = {};
 	for (var i = 0; i < keys.length; i++) {
-		obj[keys[i]] = parseInt($("#plan_" + keys[i]).val(), 10) || 0;
+		var op = keys[i];
+		day[op] = parseInt($("#plan_day_" + op).val(), 10) || 0;
+		night[op] = parseInt($("#plan_night_" + op).val(), 10) || 0;
 	}
-	$("#opPlan").val(keys.length ? JSON.stringify(obj) : '');
+	$("#opPlan").val(JSON.stringify({ day: day, night: night }));
 }
 function toggleOpPlanFields() {
 	var st = $("#screenType").val();
-	$("#opPlanMp, #opPlanJj, #opPlanZp").addClass('hide');
-	if (st === 'MP') {
-		$("#opPlanTitle, #opPlanMp").removeClass('hide');
-	} else if (st === 'JJ') {
-		$("#opPlanTitle, #opPlanJj").removeClass('hide');
-	} else if (st === 'ZP') {
-		buildOpPlanZpInputs();
-		$("#opPlanTitle, #opPlanZp").removeClass('hide');
+	var show = (st === 'MP' || st === 'JJ' || st === 'ZP');
+	if (show) {
+		updateShiftTitles();
+		buildShiftInputs('opPlanDayRow', 'day');
+		buildShiftInputs('opPlanNightRow', 'night');
+		$("#opPlanDayTitle, #opPlanDayBody, #opPlanNightTitle, #opPlanNightBody").removeClass('hide');
+		fillOpPlanInputs();
 	} else {
-		$("#opPlanTitle").addClass('hide');
+		$("#opPlanDayTitle, #opPlanDayBody, #opPlanNightTitle, #opPlanNightBody").addClass('hide');
 	}
 }
 function fillOpPlanInputs() {
 	var raw = $("#opPlan").val();
-	var obj = {};
-	try { obj = raw ? JSON.parse(raw) : {}; } catch (e) { obj = {}; }
-	buildOpPlanZpInputs();
+	var day = {}, night = {};
+	try {
+		var obj = raw ? JSON.parse(raw) : {};
+		if (obj && (obj.day || obj.night)) {
+			day = obj.day || {};
+			night = obj.night || {};
+		} else {
+			// 兼容旧版扁平计划,计入白班
+			day = obj || {};
+		}
+	} catch (e) {
+		day = {};
+		night = {};
+	}
 	var keys = OPS_MP.concat(OPS_JJ).concat(OPS_ZP);
 	for (var i = 0; i < keys.length; i++) {
 		var k = keys[i];
-		var v = obj[k];
-		if (v == null) v = obj[k.replace('OP0','OP')];
-		$("#plan_" + k).val(v == null || v === '' ? '' : v);
+		var dv = day[k];
+		var nv = night[k];
+		if (dv == null) dv = day[k.replace('OP0','OP')];
+		if (nv == null) nv = night[k.replace('OP0','OP')];
+		if ($("#plan_day_" + k).length) {
+			$("#plan_day_" + k).val(dv == null || dv === '' ? '' : dv);
+		}
+		if ($("#plan_night_" + k).length) {
+			$("#plan_night_" + k).val(nv == null || nv === '' ? '' : nv);
+		}
 	}
 }
 $("#inputForm").validate({
@@ -225,13 +214,38 @@ $("#inputForm").validate({
 		}, "json");
     }
 });
+function loadWeekOutput() {
+	var st = $("#screenType").val() || '';
+	if (!st) {
+		$("#weekOutput").val('0');
+		return;
+	}
+	$.ajax({
+		url: '${ctx}/mes/mesProductScreen/weekOutputData',
+		type: 'POST',
+		dataType: 'json',
+		data: { screenType: st },
+		success: function(resp) {
+			var n = 0;
+			if (resp && (resp.result === 'true' || resp.result === true) && resp.data) {
+				n = Number(resp.data.weekOutput) || 0;
+			}
+			$("#weekOutput").val(n);
+		},
+		error: function() {
+			$("#weekOutput").val('0');
+		}
+	});
+}
 (function(){
 	var st = "${mesProductScreen.screenType!}";
 	var eq = st.indexOf('=');
 	if (eq > 0) st = st.substring(0, eq);
 	$("#screenType").val(st);
-	fillOpPlanInputs();
 	toggleOpPlanFields();
-	$("#screenType").on('change', toggleOpPlanFields);
+	$("#screenType").on('change', function(){
+		toggleOpPlanFields();
+		loadWeekOutput();
+	});
 })();
-</script>
+</script>

+ 9 - 7
src/main/resources/views/modules/mes/mesProductScreenList.html

@@ -23,7 +23,7 @@
 						<select name="screenType" class="form-control width-120">
 							<option value="">${text('全部')}</option>
 							<option value="ZS">总屏</option>
-							<option value="MP">毛坯大屏</option>
+							<option value="MP">铸造大屏</option>
 							<option value="JJ">机加大屏</option>
 							<option value="ZP">装配大屏</option>
 						</select>
@@ -42,7 +42,7 @@
 					</div>
 				</div>
 				<div class="form-group">
-					<label class="control-label">${text('每日计划')}:</label>
+					<label class="control-label">${text('总计划产量')}:</label>
 					<div class="control-inline">
 						<#form:input path="planOutput" maxlength="255" class="form-control width-120"/>
 					</div>
@@ -67,15 +67,17 @@ $('#dataGrid').dataGrid({
 			var eq = code.indexOf('=');
 			if (eq > 0) code = code.substring(0, eq);
 			if (code === 'ZS') return '总屏';
-			if (code === 'MP') return '毛坯大屏';
+			if (code === 'MP') return '铸造大屏';
 			if (code === 'JJ') return '机加大屏';
 			if (code === 'ZP') return '装配大屏';
 			return val || '';
 		}},
-		{header:'${text("生产人数")}', name:'peopleNum', index:'a.people_num', width:150, align:"center"},
-		{header:'${text("设备数量")}', name:'machineNum', index:'a.machine_num', width:150, align:"center"},
-		{header:'${text("每日计划")}', name:'planOutput', index:'a.plan_output', width:150, align:"center"},
-		{header:'${text("操作人")}', name:'createBy', index:'a.create_by', width:150, align:"center"},
+		{header:'${text("本周计划")}', name:'weekPlan', index:'a.week_plan', width:120, align:"center"},
+		{header:'${text("本周产量")}', name:'weekOutput', width:120, align:"center", sortable:false},
+		{header:'${text("生产人数")}', name:'peopleNum', index:'a.people_num', width:120, align:"center"},
+		{header:'${text("设备数量")}', name:'machineNum', index:'a.machine_num', width:120, align:"center"},
+		{header:'${text("总计划产量")}', name:'planOutput', index:'a.plan_output', width:120, align:"center"},
+		{header:'${text("操作人")}', name:'createBy', index:'a.create_by', width:120, align:"center"},
 		{header:'${text("操作")}', name:'actions', width:120, formatter: function(val, obj, row, act){
 			var actions = [];
 			//<% if(hasPermi('mes:mesProductScreen:edit')){ %>

+ 28 - 17
src/main/resources/views/modules/mes/mesScreen3.html

@@ -275,8 +275,8 @@
 		.mes3-data-card:nth-child(2) { left: 180px; top: 80px; }  /* 设备数量 */
 
 		/* 数据卡片位置 - 顶部 */
-		.mes3-data-card:nth-child(3) { left: 320px; top: 20px; }  /* 计划产量 */
-		.mes3-data-card:nth-child(4) { left: 500px; top: 20px; }  /* 实际产量 */
+		.mes3-data-card:nth-child(3) { left: 320px; top: 20px; }  /* 总计划产量 */
+		.mes3-data-card:nth-child(4) { left: 500px; top: 20px; }  /* 总产量 */
 
 		/* 数据卡片位置 - 右侧 */
 		.mes3-data-card:nth-child(5) { left: 640px; top: 80px; }  /* 近修数量 */
@@ -573,7 +573,7 @@
 			font-weight: normal;
 		}
 
-		/* ===== 报警信息 ===== */
+		/* ===== 停机信息 ===== */
 		.mes3-alert-list {
 			padding-top: 5px;
 			padding-right: 4px;
@@ -675,9 +675,9 @@
 					<canvas id="chartLeft1" class="mes3-chart"></canvas>
 				</div>
 			</div>
-			<!-- 面板2: 近七日产品质检(对齐前屏同名逻辑) -->
+			<!-- 面板2: 近七日成品质检 -->
 			<div class="mes3-panel mes3-panel-left2">
-				<div class="panel-header">近七日产品质检数据分析</div>
+				<div class="panel-header">近七日成品质检数据</div>
 				<div class="panel-body">
 					<canvas id="chartLeft2" class="mes3-chart"></canvas>
 				</div>
@@ -704,15 +704,15 @@
 					<div class="card-value" id="deviceCount">56</div>
 				</div>
 				<div class="mes3-data-card">
-					<div class="card-label">每日计划</div>
+					<div class="card-label" id="labelPlanProduction">总计划产量</div>
 					<div class="card-value" id="planProduction">221</div>
 				</div>
 				<div class="mes3-data-card">
-					<div class="card-label">实际产量</div>
+					<div class="card-label" id="labelActualProduction">总产量</div>
 					<div class="card-value" id="actualProduction">225</div>
 				</div>
 				<div class="mes3-data-card">
-					<div class="card-label">返修数量</div>
+					<div class="card-label">返工数量</div>
 					<div class="card-value" id="repairCount">11</div>
 				</div>
 				<div class="mes3-data-card">
@@ -793,9 +793,9 @@
 			</div>
 		</div>
 
-		<!-- 右侧面板3: 报警信息 -->
+		<!-- 右侧面板3: 停机信息 -->
 		<div class="mes3-panel-right3">
-			<div class="panel-header">产线阶段报警信息</div>
+			<div class="panel-header">产线阶段停机信息</div>
 			<div class="panel-body" style="padding: 0 15px;">
 				<div id="alertList" class="mes3-alert-list"></div>
 			</div>
@@ -826,6 +826,16 @@
 
 	function pad(n) { return n < 10 ? '0' + n : '' + n; }
 
+	function formatMd(d) {
+		return (d.getMonth() + 1) + '.' + d.getDate();
+	}
+	function updateOverviewLabels() {
+		var today = formatMd(new Date());
+		var planLabel = document.getElementById('labelPlanProduction');
+		var actualLabel = document.getElementById('labelActualProduction');
+		if (planLabel) planLabel.textContent = today + '总计划产量';
+		if (actualLabel) actualLabel.textContent = today + '总产量';
+	}
 	function updateClock() {
 		var now = new Date();
 		var y = now.getFullYear();
@@ -835,6 +845,7 @@
 		document.getElementById('mes3Date').textContent = y + '年' + pad(m) + '月' + pad(d) + '日 ' + w;
 		var h = now.getHours(), mi = now.getMinutes(), s = now.getSeconds();
 		document.getElementById('mes3Time').textContent = pad(h) + ':' + pad(mi) + ':' + pad(s);
+		updateOverviewLabels();
 	}
 
 	// ====== 各窗口测试数据 ======
@@ -1342,15 +1353,15 @@
 
 		// 图例
 		ctx.fillStyle = '#00FF88';
-		ctx.fillRect(padL + 60, 8, 12, 12);
+		ctx.fillRect(padL + 40, 8, 12, 12);
 		ctx.fillStyle = '#ffffff';
 		ctx.font = '12px Microsoft YaHei';
 		ctx.textAlign = 'left';
-		ctx.fillText('上线', padL + 78, 18);
+		ctx.fillText('OP060上线', padL + 58, 18);
 		ctx.fillStyle = '#00C8FF';
-		ctx.fillRect(padL + 130, 8, 12, 12);
+		ctx.fillRect(padL + 150, 8, 12, 12);
 		ctx.fillStyle = '#ffffff';
-		ctx.fillText('下线', padL + 148, 18);
+		ctx.fillText('OP400下线', padL + 168, 18);
 
 		if (!upData || upData.length < 2) return;
 		var stepX = cw / (upData.length - 1);
@@ -1509,7 +1520,7 @@
 		renderAlerts();
 	}
 
-	/** 总览三卡:生产人数 / 设备数量 / 每日计划 <- mesProductScreen 总屏配置 */
+	/** 总览三卡:生产人数 / 设备数量 / 总计划产量 <- mesProductScreen 总屏配置 */
 	var SCREEN_TYPE = 'ZS';
 	function updateScreenConfigByApi() {
 		$.ajax({
@@ -1612,7 +1623,7 @@
 		return list.length;
 	}
 
-	// 中间卡:返修/报废数量 <- mesProductScreen/repairScrapCount
+	// 中间卡:返工/报废数量 <- mesProductScreen/repairScrapCount
 	function updateRepairScrapCountByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProductScreen/repairScrapCount',
@@ -1717,7 +1728,7 @@
 		}
 	}
 
-	/** 实际产量:OP400 当天完成(OK)数 */
+	/** 总产量:OP400 当天完成(OK)数 */
 	function updateActualProductionByApi() {
 		var dayRange = getCalendarDayUpdateDateRange();
 		var pageSize = 3000;

+ 153 - 66
src/main/resources/views/modules/mes/mesScreen4.html

@@ -292,7 +292,7 @@
 
 	<div class="mes4-header">
 		<img class="mes4-logo" src="${ctxStatic}/screen/imgs/ljrLogo.png" alt="logo">
-		<div class="mes4-title">毛坯阶段统计大屏</div>
+		<div class="mes4-title">铸造车间统计大屏</div>
 		<div class="mes4-datetime">
 			<span class="mes4-date" id="mes4Date"></span>
 			<span class="mes4-time" id="mes4Time"></span>
@@ -301,17 +301,17 @@
 	</div>
 
 	<div class="mes4-panel p-alert">
-		<div class="mes4-ptitle">  产线阶段报警信息  </div>
+		<div class="mes4-ptitle">  产线阶段停机信息  </div>
 		<div class="mes4-pbody">
-			<div class="alert-head"><div>序号</div><div>工位 / 报警类型</div></div>
+			<div class="alert-head"><div>序号</div><div>工位 / 停机类型</div></div>
 			<div id="alertList"></div>
 		</div>
 	</div>
 
 	<div class="mes4-panel p-quality">
-		<div class="mes4-ptitle">  近七日产品质检数据分析  </div>
+		<div class="mes4-ptitle">  近七日压铸车间质检数据分析  </div>
 		<div class="legend-inline">
-			<span><i class="dot" style="background:#00FF88"></i>产出</span>
+			<span><i class="dot" style="background:#00FF88"></i>产量</span>
 			<span><i class="dot" style="background:#00C8FF"></i>不良</span>
 		</div>
 		<div class="mes4-pbody"><canvas id="chartQuality" class="chart"></canvas></div>
@@ -329,9 +329,10 @@
 				<tr>
 					<th style="width:160px">工位</th>
 					<th style="width:300px">工位名称</th>
-					<th id="thDayPlan">当天计划</th>
-					<th id="thShiftCapacity">当班产量</th>
-					<th id="thPrevShiftCapacity">上班产量</th>
+					<th id="thDayPlan">白班计划</th>
+					<th id="thDayOutput">白班产量</th>
+					<th id="thNightPlan">夜班计划</th>
+					<th id="thNightOutput">夜班产量</th>
 				</tr>
 				</thead>
 				<tbody id="opTableBody"></tbody>
@@ -358,11 +359,11 @@
 						<div class="oc-value"><span id="deviceCount">56</span></div>
 					</div>
 					<div class="overview-card">
-						<div class="oc-label">每日计划</div>
+						<div class="oc-label" id="labelPlanProduction">每日计划</div>
 						<div class="oc-value"><span id="planProduction">221</span></div>
 					</div>
 					<div class="overview-card">
-						<div class="oc-label">实际产量</div>
+						<div class="oc-label" id="labelActualProduction">实际产量</div>
 						<div class="oc-value"><span id="actualProduction">201</span></div>
 					</div>
 				</div>
@@ -382,8 +383,8 @@
 	<div class="mes4-panel p-hourly">
 		<div class="mes4-ptitle">  近12小时上下线数据分析  </div>
 		<div class="legend-inline">
-			<span><i class="dot" style="background:#00FF88"></i>上线</span>
-			<span><i class="dot" style="background:#00C8FF"></i>下线</span>
+			<span><i class="dot" style="background:#00FF88"></i>OP060上线</span>
+			<span><i class="dot" style="background:#00C8FF"></i>OP100下线</span>
 		</div>
 		<div class="mes4-pbody"><canvas id="chartHourly" class="chart"></canvas></div>
 	</div>
@@ -414,28 +415,19 @@
 	}
 
 	function updateCapacityHeaders() {
-		var now = new Date();
-		var hour = now.getHours();
-		var today = formatMd(now);
-		var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
-		var ymd = formatMd(yesterday);
-		var dayEl = document.getElementById('thDayPlan');
-		var shiftEl = document.getElementById('thShiftCapacity');
-		var prevEl = document.getElementById('thPrevShiftCapacity');
-		var curShift, prevShift;
-		if (hour >= 8 && hour < 20) {
-			curShift = today + '白班';
-			prevShift = ymd + '夜班';
-		} else if (hour >= 20) {
-			curShift = today + '夜班';
-			prevShift = today + '白班';
-		} else {
-			curShift = ymd + '夜班';
-			prevShift = ymd + '白班';
-		}
-		if (dayEl) dayEl.textContent = curShift + '计划';
-		if (shiftEl) shiftEl.textContent = curShift + '实际';
-		if (prevEl) prevEl.textContent = prevShift + '产量';
+		var today = formatMd(new Date());
+		var dayPlanEl = document.getElementById('thDayPlan');
+		var dayOutEl = document.getElementById('thDayOutput');
+		var nightPlanEl = document.getElementById('thNightPlan');
+		var nightOutEl = document.getElementById('thNightOutput');
+		if (dayPlanEl) dayPlanEl.textContent = today + '白班计划';
+		if (dayOutEl) dayOutEl.textContent = today + '白班产量';
+		if (nightPlanEl) nightPlanEl.textContent = today + '夜班计划';
+		if (nightOutEl) nightOutEl.textContent = today + '夜班产量';
+		var planLabel = document.getElementById('labelPlanProduction');
+		var actualLabel = document.getElementById('labelActualProduction');
+		if (planLabel) planLabel.textContent = today + '每日计划';
+		if (actualLabel) actualLabel.textContent = today + '实际产量';
 	}
 
 	function updateClock() {
@@ -454,18 +446,34 @@
 	var mockData = {};
 	/** 本屏产线:XT(工位 OP060-OP100,与 mesLineProcess 一致) */
 	var LINE_SN = 'XT';
-	/** 大屏配置类型:毛坯 */
+	/** 大屏配置类型:铸造车间 */
 	var SCREEN_TYPE = 'MP';
 	/** 每小时上下线:上线 OP060 / 下线 OP100 */
 	var HOURLY_API_CFG = { lineSn: LINE_SN, onlineOprno: 'OP060', offlineOprno: 'OP100' };
+	function formatHourLabel(h) {
+		var n = Number(h);
+		if (isNaN(n)) {
+			var s = String(h == null ? '' : h);
+			return s.indexOf('点') >= 0 ? s : (s + '点');
+		}
+		return (n % 24) + '点';
+	}
 	function buildRecentHourLabels12() {
 		var labels = [];
 		var nowH = new Date().getHours();
 		for (var i = 11; i >= 0; i--) {
-			labels.push(String((nowH - i + 24) % 24));
+			labels.push(formatHourLabel((nowH - i + 24) % 24));
 		}
 		return labels;
 	}
+	function normalizeHourLabels(labels) {
+		var out = [];
+		if (!labels || !labels.length) return buildRecentHourLabels12();
+		for (var i = 0; i < labels.length; i++) {
+			out.push(formatHourLabel(labels[i]));
+		}
+		return out;
+	}
 	function updateHourlyUpDownByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProduct/hourlyUpDownOkData',
@@ -484,7 +492,7 @@
 						return;
 					}
 					mockData.hourly = {
-						labels: d.labels && d.labels.length ? d.labels : buildRecentHourLabels12(),
+						labels: normalizeHourLabels(d.labels),
 						up: d.up,
 						down: d.down,
 						max: 4
@@ -943,9 +951,10 @@
 			html += '<tr>';
 			html += '<td class="op-col">' + r.code + '</td>';
 			html += '<td class="op-name">' + r.name + '</td>';
-			html += '<td class="op-val">' + (r.plan || 0) + '</td>';
-			html += '<td class="op-val">' + r.shift + '</td>';
-			html += '<td class="op-val">' + (r.prevShift || 0) + '</td>';
+			html += '<td class="op-val">' + (r.dayPlan || 0) + '</td>';
+			html += '<td class="op-val">' + (r.dayOutput || 0) + '</td>';
+			html += '<td class="op-val">' + (r.nightPlan || 0) + '</td>';
+			html += '<td class="op-val">' + (r.nightOutput || 0) + '</td>';
 			html += '</tr>';
 		}
 		var el = document.getElementById('opTableBody');
@@ -962,8 +971,8 @@
 		return n >= fromNum && n <= toNum;
 	}
 
-	function lookupOpPlan(code) {
-		var plans = mockData.opPlans || {};
+	function lookupOpPlan(code, plans) {
+		plans = plans || {};
 		var c = String(code || '').toUpperCase();
 		if (plans[c] != null && plans[c] !== '') return Number(plans[c]) || 0;
 		var n = parseOprnoNum(c);
@@ -977,7 +986,8 @@
 	function applyOpPlansToTable() {
 		var list = mockData.opTable || [];
 		for (var i = 0; i < list.length; i++) {
-			list[i].plan = lookupOpPlan(list[i].code);
+			list[i].dayPlan = lookupOpPlan(list[i].code, mockData.opPlansDay);
+			list[i].nightPlan = lookupOpPlan(list[i].code, mockData.opPlansNight);
 		}
 		renderTable();
 	}
@@ -991,6 +1001,39 @@
 	}
 	function syncAchieveRateWithPlan() {}
 
+	/** 工位表固定插入 OP070(探伤处理);产量走探伤记录去重口径,不走普通工位 OK 统计 */
+	function ensureOp070Row(list) {
+		var out = [];
+		var has070 = false;
+		for (var i = 0; i < list.length; i++) {
+			var row = list[i];
+			if (parseOprnoNum(row.code) === 70) {
+				if (has070) continue;
+				row.code = 'OP070';
+				row.name = '探伤处理';
+				row._opNum = 70;
+				row._flaw = true;
+				row.dayPlan = lookupOpPlan('OP070', mockData.opPlansDay);
+				row.nightPlan = lookupOpPlan('OP070', mockData.opPlansNight);
+				has070 = true;
+			}
+			out.push(row);
+		}
+		if (!has070) {
+			out.push({
+				code: 'OP070',
+				name: '探伤处理',
+				dayPlan: lookupOpPlan('OP070', mockData.opPlansDay),
+				nightPlan: lookupOpPlan('OP070', mockData.opPlansNight),
+				dayOutput: 0,
+				nightOutput: 0,
+				_opNum: 70,
+				_flaw: true
+			});
+		}
+		return out;
+	}
+
 	function updateLineProcessTableByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProductScreen/lineProcessList',
@@ -1010,16 +1053,20 @@
 					if (ls !== LINE_SN) continue;
 					var code = p.oprno || '';
 					if (!isOprnoInRange(code, 60, 100)) continue;
+					// OP070/OP070A 统一由 ensureOp070Row 插入,避免重复
 					if (parseOprnoNum(code) === 70) continue;
 					out.push({
 						code: code,
 						name: p.title || '',
-						plan: lookupOpPlan(code),
-						shift: 0,
-						prevShift: 0,
-						_opNum: parseOprnoNum(code)
+						dayPlan: lookupOpPlan(code, mockData.opPlansDay),
+						nightPlan: lookupOpPlan(code, mockData.opPlansNight),
+						dayOutput: 0,
+						nightOutput: 0,
+						_opNum: parseOprnoNum(code),
+						_flaw: false
 					});
 				}
+				out = ensureOp070Row(out);
 				out.sort(function(a, b) { return a._opNum - b._opNum; });
 				mockData.opTable = out;
 				renderTable();
@@ -1036,30 +1083,32 @@
 		if (!list.length) return;
 		var oprnos = [];
 		for (var i = 0; i < list.length; i++) {
-			if (list[i] && list[i].code) oprnos.push(list[i].code);
+			if (list[i] && list[i].code && !list[i]._flaw) oprnos.push(list[i].code);
 		}
-		if (!oprnos.length) return;
-		fetchCapacityInto('shiftCapacityData', LINE_SN, oprnos, 'shift', { fixedTwoShift: 'true' });
-		fetchCapacityInto('prevShiftCapacityData', LINE_SN, oprnos, 'prevShift', { fixedTwoShift: 'true' });
+		if (oprnos.length) {
+			fetchCalendarShiftCapacity(LINE_SN, oprnos, 'day', 'dayOutput');
+			fetchCalendarShiftCapacity(LINE_SN, oprnos, 'night', 'nightOutput');
+		}
+		fetchFlawShiftCapacity('day', 'dayOutput');
+		fetchFlawShiftCapacity('night', 'nightOutput');
 	}
 
-	function fetchCapacityInto(action, lineSn, oprnos, field, extraData) {
-		var data = { lineSn: lineSn, oprnos: oprnos.join(',') };
-		if (extraData) {
-			for (var k in extraData) {
-				if (extraData.hasOwnProperty(k)) data[k] = extraData[k];
-			}
-		}
+	function fetchCalendarShiftCapacity(lineSn, oprnos, shiftType, field) {
 		$.ajax({
-			url: '${ctx}/mes/mesProductRecord/' + action,
+			url: '${ctx}/mes/mesProductRecord/calendarShiftCapacityData',
 			type: 'POST',
 			dataType: 'json',
-			data: data,
+			data: {
+				lineSn: lineSn,
+				oprnos: oprnos.join(','),
+				shiftType: shiftType
+			},
 			success: function(resp) {
 				if (!resp || resp.result !== 'true' || !resp.data) return;
 				var map = resp.data || {};
 				var list = mockData.opTable || [];
 				for (var i = 0; i < list.length; i++) {
+					if (list[i]._flaw) continue;
 					var code = list[i].code || '';
 					var formatted = code.length === 6 ? code.substring(0, 5) : code;
 					list[i][field] = Number(map[code] != null ? map[code] : map[formatted]) || 0;
@@ -1069,6 +1118,27 @@
 		});
 	}
 
+	/** OP070 探伤产量:探伤记录管理口径,时间段内有图片的记录按工件码去重 */
+	function fetchFlawShiftCapacity(shiftType, field) {
+		$.ajax({
+			url: '${ctx}/mes/mesFlawRecord/calendarShiftDistinctCount',
+			type: 'POST',
+			dataType: 'json',
+			data: { shiftType: shiftType },
+			success: function(resp) {
+				if (!resp || resp.result !== 'true') return;
+				var count = Number(resp.data) || 0;
+				var list = mockData.opTable || [];
+				for (var i = 0; i < list.length; i++) {
+					if (list[i]._flaw || parseOprnoNum(list[i].code) === 70) {
+						list[i][field] = count;
+					}
+				}
+				renderTable();
+			}
+		});
+	}
+
 	// 近七日产品质检:产出=OP100 OK,不良=返工 OP070-OP100
 	function updateWeekQualityByApi() {
 		$.ajax({
@@ -1155,7 +1225,25 @@
 		renderAlerts();
 	}
 
-	/** 总览三卡:生产人数 / 设备数量 / 计划产量 <- mesProductScreen 最新配置 */
+	function sumPlanMap(map) {
+		var total = 0;
+		if (!map) return 0;
+		for (var k in map) {
+			if (!map.hasOwnProperty(k)) continue;
+			total += Number(map[k]) || 0;
+		}
+		return total;
+	}
+	function refreshOverviewPlanProduction() {
+		var dayTotal = sumPlanMap(mockData.opPlansDay);
+		var nightTotal = sumPlanMap(mockData.opPlansNight);
+		mockData.cards = mockData.cards || {};
+		mockData.cards.planProduction = dayTotal + nightTotal;
+		var elPlan = document.getElementById('planProduction');
+		if (elPlan) elPlan.textContent = mockData.cards.planProduction;
+	}
+
+	/** 总览:生产人数 / 设备数量;每日计划=当日白班计划合计+夜班计划合计 */
 	function updateScreenConfigByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProductScreen/latestConfig',
@@ -1168,14 +1256,13 @@
 				mockData.cards = mockData.cards || {};
 				mockData.cards.productionPeople = Number(d.peopleNums) || 0;
 				mockData.cards.deviceCount = Number(d.deviceNums) || 0;
-				mockData.cards.planProduction = Number(d.planNums) || 0;
 				var elPeople = document.getElementById('productionPeople');
 				var elDevice = document.getElementById('deviceCount');
-				var elPlan = document.getElementById('planProduction');
 				if (elPeople) elPeople.textContent = mockData.cards.productionPeople;
 				if (elDevice) elDevice.textContent = mockData.cards.deviceCount;
-				if (elPlan) elPlan.textContent = mockData.cards.planProduction;
-				mockData.opPlans = d.opPlans || {};
+				mockData.opPlansDay = d.opPlansDay || d.opPlans || {};
+				mockData.opPlansNight = d.opPlansNight || {};
+				refreshOverviewPlanProduction();
 				applyOpPlansToTable();
 			}
 		});
@@ -1204,7 +1291,7 @@
 		if (el) el.textContent = mockData.cards.actualProduction;
 	}
 
-	/** 实际产量:取本阶段末工位当天 OK 数(半成品 OP100) */
+	/** 实际产量:当日(0点–24点)末工位 OP100 OK 数 */
 	function updateActualProductionByApi() {
 		var oprno = HOURLY_API_CFG.offlineOprno || 'OP100';
 		$.ajax({

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

@@ -318,17 +318,17 @@
 	</div>
 
 	<div class="mes4-panel p-alert">
-		<div class="mes4-ptitle">  产线阶段报警信息  </div>
+		<div class="mes4-ptitle">  产线阶段停机信息  </div>
 		<div class="mes4-pbody">
-			<div class="alert-head"><div>序号</div><div>工位 / 报警类型</div></div>
+			<div class="alert-head"><div>序号</div><div>工位 / 停机类型</div></div>
 			<div id="alertList"></div>
 		</div>
 	</div>
 
 	<div class="mes4-panel p-quality">
-		<div class="mes4-ptitle">  近七日产品质检数据分析  </div>
+		<div class="mes4-ptitle">  近七日机加车间质检数据分析  </div>
 		<div class="legend-inline">
-			<span><i class="dot" style="background:#00FF88"></i>产出</span>
+			<span><i class="dot" style="background:#00FF88"></i>产量</span>
 			<span><i class="dot" style="background:#00C8FF"></i>不良</span>
 		</div>
 		<div class="mes4-pbody"><canvas id="chartQuality" class="chart"></canvas></div>
@@ -347,9 +347,10 @@
 				<tr>
 					<th style="width:160px">工位</th>
 					<th style="width:300px">工位名称</th>
-					<th id="thDayPlan">当天计划</th>
-					<th id="thShiftCapacity">当班产量</th>
-					<th id="thPrevShiftCapacity">上班产量</th>
+					<th id="thDayPlan">白班计划</th>
+					<th id="thDayOutput">白班产量</th>
+					<th id="thNightPlan">夜班计划</th>
+					<th id="thNightOutput">夜班产量</th>
 				</tr>
 				</thead>
 				<tbody id="opTableBody"></tbody>
@@ -376,11 +377,11 @@
 						<div class="oc-value"><span id="deviceCount">56</span></div>
 					</div>
 					<div class="overview-card">
-						<div class="oc-label">每日计划</div>
+						<div class="oc-label" id="labelPlanProduction">每日计划</div>
 						<div class="oc-value"><span id="planProduction">221</span></div>
 					</div>
 					<div class="overview-card">
-						<div class="oc-label">实际产量</div>
+						<div class="oc-label" id="labelActualProduction">实际产量</div>
 						<div class="oc-value"><span id="actualProduction">201</span></div>
 					</div>
 				</div>
@@ -400,8 +401,8 @@
 	<div class="mes4-panel p-hourly">
 		<div class="mes4-ptitle">  近12小时上下线数据分析  </div>
 		<div class="legend-inline">
-			<span><i class="dot" style="background:#00FF88"></i>上线</span>
-			<span><i class="dot" style="background:#00C8FF"></i>下线</span>
+			<span><i class="dot" style="background:#00FF88"></i>OP110上线</span>
+			<span><i class="dot" style="background:#00C8FF"></i>OP160下线</span>
 		</div>
 		<div class="mes4-pbody"><canvas id="chartHourly" class="chart"></canvas></div>
 	</div>
@@ -432,28 +433,19 @@
 	}
 
 	function updateCapacityHeaders() {
-		var now = new Date();
-		var hour = now.getHours();
-		var today = formatMd(now);
-		var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
-		var ymd = formatMd(yesterday);
-		var dayEl = document.getElementById('thDayPlan');
-		var shiftEl = document.getElementById('thShiftCapacity');
-		var prevEl = document.getElementById('thPrevShiftCapacity');
-		var curShift, prevShift;
-		if (hour >= 8 && hour < 20) {
-			curShift = today + '白班';
-			prevShift = ymd + '夜班';
-		} else if (hour >= 20) {
-			curShift = today + '夜班';
-			prevShift = today + '白班';
-		} else {
-			curShift = ymd + '夜班';
-			prevShift = ymd + '白班';
-		}
-		if (dayEl) dayEl.textContent = curShift + '计划';
-		if (shiftEl) shiftEl.textContent = curShift + '实际';
-		if (prevEl) prevEl.textContent = prevShift + '产量';
+		var today = formatMd(new Date());
+		var dayPlanEl = document.getElementById('thDayPlan');
+		var dayOutEl = document.getElementById('thDayOutput');
+		var nightPlanEl = document.getElementById('thNightPlan');
+		var nightOutEl = document.getElementById('thNightOutput');
+		if (dayPlanEl) dayPlanEl.textContent = today + '白班计划';
+		if (dayOutEl) dayOutEl.textContent = today + '白班产量';
+		if (nightPlanEl) nightPlanEl.textContent = today + '夜班计划';
+		if (nightOutEl) nightOutEl.textContent = today + '夜班产量';
+		var planLabel = document.getElementById('labelPlanProduction');
+		var actualLabel = document.getElementById('labelActualProduction');
+		if (planLabel) planLabel.textContent = today + '每日计划';
+		if (actualLabel) actualLabel.textContent = today + '实际产量';
 	}
 
 	/** 工位表:每页条数与自动翻页间隔(毫秒) */
@@ -465,7 +457,7 @@
 	function padOpTableRowsToPageSize(html, rowCount) {
 		for (var j = rowCount; j < OP_TABLE_PAGE_SIZE; j++) {
 			var alt = (j % 2 === 1) ? ' op-row-alt' : '';
-			html += '<tr class="op-row-empty' + alt + '"><td colspan="5">&nbsp;</td></tr>';
+			html += '<tr class="op-row-empty' + alt + '"><td colspan="6">&nbsp;</td></tr>';
 		}
 		return html;
 	}
@@ -524,14 +516,30 @@
 	var SCREEN_TYPE = 'JJ';
 	/** 每小时上下线:上线 OP110 / 下线 OP160 */
 	var HOURLY_API_CFG = { lineSn: LINE_SN, onlineOprno: 'OP110', offlineOprno: 'OP160' };
+	function formatHourLabel(h) {
+		var n = Number(h);
+		if (isNaN(n)) {
+			var s = String(h == null ? '' : h);
+			return s.indexOf('点') >= 0 ? s : (s + '点');
+		}
+		return (n % 24) + '点';
+	}
 	function buildRecentHourLabels12() {
 		var labels = [];
 		var nowH = new Date().getHours();
 		for (var i = 11; i >= 0; i--) {
-			labels.push(String((nowH - i + 24) % 24));
+			labels.push(formatHourLabel((nowH - i + 24) % 24));
 		}
 		return labels;
 	}
+	function normalizeHourLabels(labels) {
+		var out = [];
+		if (!labels || !labels.length) return buildRecentHourLabels12();
+		for (var i = 0; i < labels.length; i++) {
+			out.push(formatHourLabel(labels[i]));
+		}
+		return out;
+	}
 	function updateHourlyUpDownByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProduct/hourlyUpDownOkData',
@@ -550,7 +558,7 @@
 						return;
 					}
 					mockData.hourly = {
-						labels: d.labels && d.labels.length ? d.labels : buildRecentHourLabels12(),
+						labels: normalizeHourLabels(d.labels),
 						up: d.up,
 						down: d.down,
 						max: 4
@@ -1015,9 +1023,10 @@
 			html += '<tr' + ((i % 2 === 1) ? ' class="op-row-alt"' : '') + '>';
 			html += '<td class="op-col">' + r.code + '</td>';
 			html += '<td class="op-name">' + r.name + '</td>';
-			html += '<td class="op-val">' + (r.plan || 0) + '</td>';
-			html += '<td class="op-val">' + r.shift + '</td>';
-			html += '<td class="op-val">' + (r.prevShift || 0) + '</td>';
+			html += '<td class="op-val">' + (r.dayPlan || 0) + '</td>';
+			html += '<td class="op-val">' + (r.dayOutput || 0) + '</td>';
+			html += '<td class="op-val">' + (r.nightPlan || 0) + '</td>';
+			html += '<td class="op-val">' + (r.nightOutput || 0) + '</td>';
 			html += '</tr>';
 		}
 		html = padOpTableRowsToPageSize(html, slice.length);
@@ -1035,8 +1044,8 @@
 		}
 	}
 
-	function lookupOpPlan(code) {
-		var plans = mockData.opPlans || {};
+	function lookupOpPlan(code, plans) {
+		plans = plans || {};
 		var c = String(code || '').toUpperCase();
 		if (plans[c] != null && plans[c] !== '') return Number(plans[c]) || 0;
 		var n = parseOprnoNum(c);
@@ -1050,7 +1059,8 @@
 	function applyOpPlansToTable() {
 		var list = mockData.opTable || [];
 		for (var i = 0; i < list.length; i++) {
-			list[i].plan = lookupOpPlan(list[i].code);
+			list[i].dayPlan = lookupOpPlan(list[i].code, mockData.opPlansDay);
+			list[i].nightPlan = lookupOpPlan(list[i].code, mockData.opPlansNight);
 		}
 		renderTable();
 	}
@@ -1090,9 +1100,10 @@
 					out.push({
 						code: code,
 						name: p.title || '',
-						plan: lookupOpPlan(code),
-						shift: 0,
-						prevShift: 0,
+						dayPlan: lookupOpPlan(code, mockData.opPlansDay),
+						nightPlan: lookupOpPlan(code, mockData.opPlansNight),
+						dayOutput: 0,
+						nightOutput: 0,
 						_opNum: parseOprnoNum(code)
 					});
 				}
@@ -1117,22 +1128,20 @@
 			if (list[i] && list[i].code) oprnos.push(list[i].code);
 		}
 		if (!oprnos.length) return;
-		fetchCapacityInto('shiftCapacityData', LINE_SN, oprnos, 'shift', { fixedTwoShift: 'true' });
-		fetchCapacityInto('prevShiftCapacityData', LINE_SN, oprnos, 'prevShift', { fixedTwoShift: 'true' });
+		fetchCalendarShiftCapacity(LINE_SN, oprnos, 'day', 'dayOutput');
+		fetchCalendarShiftCapacity(LINE_SN, oprnos, 'night', 'nightOutput');
 	}
 
-	function fetchCapacityInto(action, lineSn, oprnos, field, extraData) {
-		var data = { lineSn: lineSn, oprnos: oprnos.join(',') };
-		if (extraData) {
-			for (var k in extraData) {
-				if (extraData.hasOwnProperty(k)) data[k] = extraData[k];
-			}
-		}
+	function fetchCalendarShiftCapacity(lineSn, oprnos, shiftType, field) {
 		$.ajax({
-			url: '${ctx}/mes/mesProductRecord/' + action,
+			url: '${ctx}/mes/mesProductRecord/calendarShiftCapacityData',
 			type: 'POST',
 			dataType: 'json',
-			data: data,
+			data: {
+				lineSn: lineSn,
+				oprnos: oprnos.join(','),
+				shiftType: shiftType
+			},
 			success: function(resp) {
 				if (!resp || resp.result !== 'true' || !resp.data) return;
 				var map = resp.data || {};
@@ -1235,7 +1244,25 @@
 		renderAlerts();
 	}
 
-	/** 总览三卡:生产人数 / 设备数量 / 计划产量 <- mesProductScreen 最新配置 */
+	function sumPlanMap(map) {
+		var total = 0;
+		if (!map) return 0;
+		for (var k in map) {
+			if (!map.hasOwnProperty(k)) continue;
+			total += Number(map[k]) || 0;
+		}
+		return total;
+	}
+	function refreshOverviewPlanProduction() {
+		var dayTotal = sumPlanMap(mockData.opPlansDay);
+		var nightTotal = sumPlanMap(mockData.opPlansNight);
+		mockData.cards = mockData.cards || {};
+		mockData.cards.planProduction = dayTotal + nightTotal;
+		var elPlan = document.getElementById('planProduction');
+		if (elPlan) elPlan.textContent = mockData.cards.planProduction;
+	}
+
+	/** 总览:生产人数 / 设备数量;每日计划=当日白班计划合计+夜班计划合计 */
 	function updateScreenConfigByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProductScreen/latestConfig',
@@ -1248,14 +1275,13 @@
 				mockData.cards = mockData.cards || {};
 				mockData.cards.productionPeople = Number(d.peopleNums) || 0;
 				mockData.cards.deviceCount = Number(d.deviceNums) || 0;
-				mockData.cards.planProduction = Number(d.planNums) || 0;
 				var elPeople = document.getElementById('productionPeople');
 				var elDevice = document.getElementById('deviceCount');
-				var elPlan = document.getElementById('planProduction');
 				if (elPeople) elPeople.textContent = mockData.cards.productionPeople;
 				if (elDevice) elDevice.textContent = mockData.cards.deviceCount;
-				if (elPlan) elPlan.textContent = mockData.cards.planProduction;
-				mockData.opPlans = d.opPlans || {};
+				mockData.opPlansDay = d.opPlansDay || d.opPlans || {};
+				mockData.opPlansNight = d.opPlansNight || {};
+				refreshOverviewPlanProduction();
 				applyOpPlansToTable();
 			}
 		});

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

@@ -318,17 +318,17 @@
 	</div>
 
 	<div class="mes4-panel p-alert">
-		<div class="mes4-ptitle">  产线阶段报警信息  </div>
+		<div class="mes4-ptitle">  产线阶段停机信息  </div>
 		<div class="mes4-pbody">
-			<div class="alert-head"><div>序号</div><div>工位 / 报警类型</div></div>
+			<div class="alert-head"><div>序号</div><div>工位 / 停机类型</div></div>
 			<div id="alertList"></div>
 		</div>
 	</div>
 
 	<div class="mes4-panel p-quality">
-		<div class="mes4-ptitle">  近七日产品质检数据分析  </div>
+		<div class="mes4-ptitle">  近七日装配车间质检数据分析  </div>
 		<div class="legend-inline">
-			<span><i class="dot" style="background:#00FF88"></i>产出</span>
+			<span><i class="dot" style="background:#00FF88"></i>产量</span>
 			<span><i class="dot" style="background:#00C8FF"></i>不良</span>
 		</div>
 		<div class="mes4-pbody"><canvas id="chartQuality" class="chart"></canvas></div>
@@ -347,9 +347,10 @@
 				<tr>
 					<th style="width:160px">工位</th>
 					<th style="width:300px">工位名称</th>
-					<th id="thDayPlan">当天计划</th>
-					<th id="thShiftCapacity">当班产量</th>
-					<th id="thPrevShiftCapacity">上班产量</th>
+					<th id="thDayPlan">白班计划</th>
+					<th id="thDayOutput">白班产量</th>
+					<th id="thNightPlan">夜班计划</th>
+					<th id="thNightOutput">夜班产量</th>
 				</tr>
 				</thead>
 				<tbody id="opTableBody"></tbody>
@@ -376,11 +377,11 @@
 						<div class="oc-value"><span id="deviceCount">56</span></div>
 					</div>
 					<div class="overview-card">
-						<div class="oc-label">每日计划</div>
+						<div class="oc-label" id="labelPlanProduction">每日计划</div>
 						<div class="oc-value"><span id="planProduction">221</span></div>
 					</div>
 					<div class="overview-card">
-						<div class="oc-label">实际产量</div>
+						<div class="oc-label" id="labelActualProduction">实际产量</div>
 						<div class="oc-value"><span id="actualProduction">201</span></div>
 					</div>
 				</div>
@@ -400,8 +401,8 @@
 	<div class="mes4-panel p-hourly">
 		<div class="mes4-ptitle">  近12小时上下线数据分析  </div>
 		<div class="legend-inline">
-			<span><i class="dot" style="background:#00FF88"></i>上线</span>
-			<span><i class="dot" style="background:#00C8FF"></i>下线</span>
+			<span><i class="dot" style="background:#00FF88"></i>OP170上线</span>
+			<span><i class="dot" style="background:#00C8FF"></i>OP400下线</span>
 		</div>
 		<div class="mes4-pbody"><canvas id="chartHourly" class="chart"></canvas></div>
 	</div>
@@ -432,28 +433,19 @@
 	}
 
 	function updateCapacityHeaders() {
-		var now = new Date();
-		var hour = now.getHours();
-		var today = formatMd(now);
-		var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
-		var ymd = formatMd(yesterday);
-		var dayEl = document.getElementById('thDayPlan');
-		var shiftEl = document.getElementById('thShiftCapacity');
-		var prevEl = document.getElementById('thPrevShiftCapacity');
-		var curShift, prevShift;
-		if (hour >= 8 && hour < 20) {
-			curShift = today + '白班';
-			prevShift = ymd + '夜班';
-		} else if (hour >= 20) {
-			curShift = today + '夜班';
-			prevShift = today + '白班';
-		} else {
-			curShift = ymd + '夜班';
-			prevShift = ymd + '白班';
-		}
-		if (dayEl) dayEl.textContent = curShift + '计划';
-		if (shiftEl) shiftEl.textContent = curShift + '实际';
-		if (prevEl) prevEl.textContent = prevShift + '产量';
+		var today = formatMd(new Date());
+		var dayPlanEl = document.getElementById('thDayPlan');
+		var dayOutEl = document.getElementById('thDayOutput');
+		var nightPlanEl = document.getElementById('thNightPlan');
+		var nightOutEl = document.getElementById('thNightOutput');
+		if (dayPlanEl) dayPlanEl.textContent = today + '白班计划';
+		if (dayOutEl) dayOutEl.textContent = today + '白班产量';
+		if (nightPlanEl) nightPlanEl.textContent = today + '夜班计划';
+		if (nightOutEl) nightOutEl.textContent = today + '夜班产量';
+		var planLabel = document.getElementById('labelPlanProduction');
+		var actualLabel = document.getElementById('labelActualProduction');
+		if (planLabel) planLabel.textContent = today + '每日计划';
+		if (actualLabel) actualLabel.textContent = today + '实际产量';
 	}
 
 	/** 工位表:每页条数与自动翻页间隔(毫秒) */
@@ -465,7 +457,7 @@
 	function padOpTableRowsToPageSize(html, rowCount) {
 		for (var j = rowCount; j < OP_TABLE_PAGE_SIZE; j++) {
 			var alt = (j % 2 === 1) ? ' op-row-alt' : '';
-			html += '<tr class="op-row-empty' + alt + '"><td colspan="5">&nbsp;</td></tr>';
+			html += '<tr class="op-row-empty' + alt + '"><td colspan="6">&nbsp;</td></tr>';
 		}
 		return html;
 	}
@@ -524,14 +516,30 @@
 	var SCREEN_TYPE = 'ZP';
 	/** 每小时上下线:上线 OP170 / 下线 OP400 */
 	var HOURLY_API_CFG = { lineSn: LINE_SN, onlineOprno: 'OP170', offlineOprno: 'OP400' };
+	function formatHourLabel(h) {
+		var n = Number(h);
+		if (isNaN(n)) {
+			var s = String(h == null ? '' : h);
+			return s.indexOf('点') >= 0 ? s : (s + '点');
+		}
+		return (n % 24) + '点';
+	}
 	function buildRecentHourLabels12() {
 		var labels = [];
 		var nowH = new Date().getHours();
 		for (var i = 11; i >= 0; i--) {
-			labels.push(String((nowH - i + 24) % 24));
+			labels.push(formatHourLabel((nowH - i + 24) % 24));
 		}
 		return labels;
 	}
+	function normalizeHourLabels(labels) {
+		var out = [];
+		if (!labels || !labels.length) return buildRecentHourLabels12();
+		for (var i = 0; i < labels.length; i++) {
+			out.push(formatHourLabel(labels[i]));
+		}
+		return out;
+	}
 	function updateHourlyUpDownByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProduct/hourlyUpDownOkData',
@@ -550,7 +558,7 @@
 						return;
 					}
 					mockData.hourly = {
-						labels: d.labels && d.labels.length ? d.labels : buildRecentHourLabels12(),
+						labels: normalizeHourLabels(d.labels),
 						up: d.up,
 						down: d.down,
 						max: 4
@@ -1015,9 +1023,10 @@
 			html += '<tr' + ((i % 2 === 1) ? ' class="op-row-alt"' : '') + '>';
 			html += '<td class="op-col">' + r.code + '</td>';
 			html += '<td class="op-name">' + r.name + '</td>';
-			html += '<td class="op-val">' + (r.plan || 0) + '</td>';
-			html += '<td class="op-val">' + r.shift + '</td>';
-			html += '<td class="op-val">' + (r.prevShift || 0) + '</td>';
+			html += '<td class="op-val">' + (r.dayPlan || 0) + '</td>';
+			html += '<td class="op-val">' + (r.dayOutput || 0) + '</td>';
+			html += '<td class="op-val">' + (r.nightPlan || 0) + '</td>';
+			html += '<td class="op-val">' + (r.nightOutput || 0) + '</td>';
 			html += '</tr>';
 		}
 		html = padOpTableRowsToPageSize(html, slice.length);
@@ -1035,8 +1044,8 @@
 		}
 	}
 
-	function lookupOpPlan(code) {
-		var plans = mockData.opPlans || {};
+	function lookupOpPlan(code, plans) {
+		plans = plans || {};
 		var c = String(code || '').toUpperCase();
 		if (plans[c] != null && plans[c] !== '') return Number(plans[c]) || 0;
 		var n = parseOprnoNum(c);
@@ -1050,7 +1059,8 @@
 	function applyOpPlansToTable() {
 		var list = mockData.opTable || [];
 		for (var i = 0; i < list.length; i++) {
-			list[i].plan = lookupOpPlan(list[i].code);
+			list[i].dayPlan = lookupOpPlan(list[i].code, mockData.opPlansDay);
+			list[i].nightPlan = lookupOpPlan(list[i].code, mockData.opPlansNight);
 		}
 		renderTable();
 	}
@@ -1090,9 +1100,10 @@
 					out.push({
 						code: code,
 						name: p.title || '',
-						plan: lookupOpPlan(code),
-						shift: 0,
-						prevShift: 0,
+						dayPlan: lookupOpPlan(code, mockData.opPlansDay),
+						nightPlan: lookupOpPlan(code, mockData.opPlansNight),
+						dayOutput: 0,
+						nightOutput: 0,
 						_opNum: parseOprnoNum(code)
 					});
 				}
@@ -1117,22 +1128,20 @@
 			if (list[i] && list[i].code) oprnos.push(list[i].code);
 		}
 		if (!oprnos.length) return;
-		fetchCapacityInto('shiftCapacityData', LINE_SN, oprnos, 'shift', { fixedTwoShift: 'true' });
-		fetchCapacityInto('prevShiftCapacityData', LINE_SN, oprnos, 'prevShift', { fixedTwoShift: 'true' });
+		fetchCalendarShiftCapacity(LINE_SN, oprnos, 'day', 'dayOutput');
+		fetchCalendarShiftCapacity(LINE_SN, oprnos, 'night', 'nightOutput');
 	}
 
-	function fetchCapacityInto(action, lineSn, oprnos, field, extraData) {
-		var data = { lineSn: lineSn, oprnos: oprnos.join(',') };
-		if (extraData) {
-			for (var k in extraData) {
-				if (extraData.hasOwnProperty(k)) data[k] = extraData[k];
-			}
-		}
+	function fetchCalendarShiftCapacity(lineSn, oprnos, shiftType, field) {
 		$.ajax({
-			url: '${ctx}/mes/mesProductRecord/' + action,
+			url: '${ctx}/mes/mesProductRecord/calendarShiftCapacityData',
 			type: 'POST',
 			dataType: 'json',
-			data: data,
+			data: {
+				lineSn: lineSn,
+				oprnos: oprnos.join(','),
+				shiftType: shiftType
+			},
 			success: function(resp) {
 				if (!resp || resp.result !== 'true' || !resp.data) return;
 				var map = resp.data || {};
@@ -1235,7 +1244,25 @@
 		renderAlerts();
 	}
 
-	/** 总览三卡:生产人数 / 设备数量 / 计划产量 <- mesProductScreen 最新配置 */
+	function sumPlanMap(map) {
+		var total = 0;
+		if (!map) return 0;
+		for (var k in map) {
+			if (!map.hasOwnProperty(k)) continue;
+			total += Number(map[k]) || 0;
+		}
+		return total;
+	}
+	function refreshOverviewPlanProduction() {
+		var dayTotal = sumPlanMap(mockData.opPlansDay);
+		var nightTotal = sumPlanMap(mockData.opPlansNight);
+		mockData.cards = mockData.cards || {};
+		mockData.cards.planProduction = dayTotal + nightTotal;
+		var elPlan = document.getElementById('planProduction');
+		if (elPlan) elPlan.textContent = mockData.cards.planProduction;
+	}
+
+	/** 总览:生产人数 / 设备数量;每日计划=当日白班计划合计+夜班计划合计 */
 	function updateScreenConfigByApi() {
 		$.ajax({
 			url: '${ctx}/mes/mesProductScreen/latestConfig',
@@ -1248,14 +1275,13 @@
 				mockData.cards = mockData.cards || {};
 				mockData.cards.productionPeople = Number(d.peopleNums) || 0;
 				mockData.cards.deviceCount = Number(d.deviceNums) || 0;
-				mockData.cards.planProduction = Number(d.planNums) || 0;
 				var elPeople = document.getElementById('productionPeople');
 				var elDevice = document.getElementById('deviceCount');
-				var elPlan = document.getElementById('planProduction');
 				if (elPeople) elPeople.textContent = mockData.cards.productionPeople;
 				if (elDevice) elDevice.textContent = mockData.cards.deviceCount;
-				if (elPlan) elPlan.textContent = mockData.cards.planProduction;
-				mockData.opPlans = d.opPlans || {};
+				mockData.opPlansDay = d.opPlansDay || d.opPlans || {};
+				mockData.opPlansNight = d.opPlansNight || {};
+				refreshOverviewPlanProduction();
 				applyOpPlansToTable();
 			}
 		});