ソースを参照

浇筑历史数据和生产领料已领数量优化

liuwei 3 ヶ月 前
親
コミット
4df6bec066

+ 41 - 0
src/main/java/com/jeesite/modules/mes/service/MesMaterialLlService.java

@@ -1,5 +1,6 @@
 package com.jeesite.modules.mes.service;
 
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -190,6 +191,46 @@ public class MesMaterialLlService extends CrudService<MesMaterialLlDao, MesMater
 		mesMaterialLl.setIsNewRecord(false);
 		mesMaterialLl.setState("1");
 		super.update(mesMaterialLl);
+		// ERP成功后按「订单号+料号」重算备料已领数量
+		refreshBlNumsYl(mesMaterialLl.getMoDocNo(), mesMaterialLl.getItemCode());
+	}
+
+	/**
+	 * 按生产订单号+料号,将备料已领数量更新为已成功同步ERP的领料数量之和
+	 * (issue_doc_no 非空视为ERP成功)
+	 */
+	@Transactional
+	public void refreshBlNumsYl(String moDocNo, String itemCode) {
+		if (StringUtils.isBlank(moDocNo) || StringUtils.isBlank(itemCode)) {
+			return;
+		}
+		MesMaterialLl llQuery = new MesMaterialLl();
+		llQuery.setMoDocNo(moDocNo);
+		llQuery.setItemCode(itemCode);
+		List<MesMaterialLl> llList = findList(llQuery);
+		BigDecimal sum = BigDecimal.ZERO;
+		if (ListUtils.isNotEmpty(llList)) {
+			for (MesMaterialLl ll : llList) {
+				if (StringUtils.isBlank(ll.getIssueDocNo()) || ll.getIssuedQty() == null) {
+					continue;
+				}
+				sum = sum.add(BigDecimal.valueOf(ll.getIssuedQty()));
+			}
+		}
+		String numsYl = sum.stripTrailingZeros().toPlainString();
+
+		MesMaterialBl blQuery = new MesMaterialBl();
+		blQuery.setOrderId(moDocNo);
+		blQuery.setErpSn(itemCode);
+		List<MesMaterialBl> blList = mesMaterialBlService.findList(blQuery);
+		if (ListUtils.isEmpty(blList)) {
+			return;
+		}
+		for (MesMaterialBl bl : blList) {
+			bl.setNumsYl(numsYl);
+			bl.setIsNewRecord(false);
+			mesMaterialBlService.update(bl);
+		}
 	}
 
 }

+ 47 - 0
src/main/java/com/jeesite/modules/tianHong/dao/MachineAttrHistoryDao.java

@@ -0,0 +1,47 @@
+package com.jeesite.modules.tianHong.dao;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.ibatis.annotations.Param;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.tianHong.entity.MachineAttrHistory;
+
+/**
+ * 设备历史属性DAO接口
+ * 所属包路径 com.jeesite.modules.tianHong.dao 自动映射到 tianHong 数据源(bzh 库)
+ * @author ljr
+ * @version 2026-07-07
+ */
+@MyBatisDao
+public interface MachineAttrHistoryDao extends CrudDao<MachineAttrHistory> {
+
+	/**
+	 * 按设备+时间点分页列表(仅主表属性;关联数据由 Service 按秒补齐)
+	 */
+	@Override
+	List<MachineAttrHistory> findList(MachineAttrHistory machineAttrHistory);
+
+	/**
+	 * 该设备时间点总数(distinct event_time)
+	 */
+	Long findHistoryCount(MachineAttrHistory machineAttrHistory);
+
+	List<MachineAttrHistory> findCruBySecList(@Param("eqpId") String eqpId, @Param("secList") List<Date> secList);
+
+	List<MachineAttrHistory> findAluBySecList(@Param("eqpId") String eqpId, @Param("secList") List<Date> secList);
+
+	List<MachineAttrHistory> findMouldBySecList(@Param("eqpId") String eqpId, @Param("secList") List<Date> secList);
+
+	List<MachineAttrHistory> findPressureBySecList(@Param("eqpId") String eqpId, @Param("secList") List<Date> secList);
+
+	List<MachineAttrHistory> findEleBySecList(@Param("eqpId") String eqpId, @Param("secList") List<Date> secList);
+
+	/**
+	 * 查询指定设备的所有属性明细(用于详情展示)
+	 */
+	List<MachineAttrHistory> findAttrList(MachineAttrHistory machineAttrHistory);
+
+}

+ 298 - 0
src/main/java/com/jeesite/modules/tianHong/entity/MachineAttrHistory.java

@@ -0,0 +1,298 @@
+package com.jeesite.modules.tianHong.entity;
+
+import java.util.Date;
+import java.util.List;
+
+import com.jeesite.common.entity.DataEntity;
+
+/**
+ * 设备历史属性Entity
+ * 对应 bzh 库 machine_attr_history 表,关联 crutemphistory、alutemphistory、mouldtemphistory、pressurehistory、EleConsumeHistory
+ * @author ljr
+ * @version 2026-07-07
+ */
+public class MachineAttrHistory extends DataEntity<MachineAttrHistory> {
+
+	private static final long serialVersionUID = 1L;
+
+	// ===== machine_attr_history 主表字段(按 eqp_id 聚合,行转列取关键属性)=====
+	private String eqpId;				// 设备号
+	private String aluVal;				// 剩余模次(attr='alu_val')
+	private String pressurizeTime;		// 保压时间(attr='pressurizeTime')
+	private String coolingTime;			// 冷却时间(attr='coolingTime')
+	private String prePres;				// 预压压力设定(attr='PrePres')
+	private String holdPressure;		// 保压开始信号(attr='HoldPressure')
+	private String moldFillingPre;		// 充型压力(attr='moldFillingPre')
+	private Date attrEventTime;			// machine_attr_history 最后更新时间
+
+	// ===== 关联表:crutemphistory 炉膛温度 =====
+	private String cruTemp;				// 炉膛温度
+	private Date cruTempTime;			// 炉膛温度时间
+
+	// ===== 关联表:alutemphistory 铝水温度 =====
+	private String aluTemp;				// 铝水温度
+	private Date aluTempTime;			// 铝水温度时间
+
+	// ===== 关联表:mouldtemphistory 模具温度(一个设备可能多个测温点,拼接展示)=====
+	private String mouldInfo;			// 模具温度信息(如 "M1:725.5, M2:730.2")
+	private Date mouldTempTime;			// 模具温度时间
+
+	// ===== 关联表:pressurehistory 压力(理论压力与实际压力)=====
+	private String theoryPressure;		// 理论压力
+	private String realPressure;		// 实际压力
+	private String pressureLevel;		// 压力段位
+	private Date pressureTime;			// 压力时间
+
+	// ===== 关联表:EleConsumeHistory 电量(总计)=====
+	private String eleConsume;			// 电量
+	private Date eleConsumeTime;		// 电量时间
+
+	// ===== 查询参数:时间范围 =====
+	private Date beginTime;
+	private Date endTime;
+
+	// ===== 手工分页(避免拦截器对复杂 SQL 二次 LIMIT)=====
+	private Integer limit;
+	private Integer offset;
+
+	// ===== 详情:该设备的所有属性记录列表 =====
+	private List<MachineAttrHistory> attrList;
+
+	// ===== 行转列详情字段(findAttrList 查询时使用)=====
+	private String attr;
+	private String val;
+	private Date eventTime;
+
+	public MachineAttrHistory() {
+		this(null);
+	}
+
+	public MachineAttrHistory(String id) {
+		super(id);
+	}
+
+	public String getEqpId() {
+		return eqpId;
+	}
+
+	public void setEqpId(String eqpId) {
+		this.eqpId = eqpId;
+	}
+
+	public String getAluVal() {
+		return aluVal;
+	}
+
+	public void setAluVal(String aluVal) {
+		this.aluVal = aluVal;
+	}
+
+	public String getPressurizeTime() {
+		return pressurizeTime;
+	}
+
+	public void setPressurizeTime(String pressurizeTime) {
+		this.pressurizeTime = pressurizeTime;
+	}
+
+	public String getCoolingTime() {
+		return coolingTime;
+	}
+
+	public void setCoolingTime(String coolingTime) {
+		this.coolingTime = coolingTime;
+	}
+
+	public String getPrePres() {
+		return prePres;
+	}
+
+	public void setPrePres(String prePres) {
+		this.prePres = prePres;
+	}
+
+	public String getHoldPressure() {
+		return holdPressure;
+	}
+
+	public void setHoldPressure(String holdPressure) {
+		this.holdPressure = holdPressure;
+	}
+
+	public String getMoldFillingPre() {
+		return moldFillingPre;
+	}
+
+	public void setMoldFillingPre(String moldFillingPre) {
+		this.moldFillingPre = moldFillingPre;
+	}
+
+	public Date getAttrEventTime() {
+		return attrEventTime;
+	}
+
+	public void setAttrEventTime(Date attrEventTime) {
+		this.attrEventTime = attrEventTime;
+	}
+
+	public String getCruTemp() {
+		return cruTemp;
+	}
+
+	public void setCruTemp(String cruTemp) {
+		this.cruTemp = cruTemp;
+	}
+
+	public Date getCruTempTime() {
+		return cruTempTime;
+	}
+
+	public void setCruTempTime(Date cruTempTime) {
+		this.cruTempTime = cruTempTime;
+	}
+
+	public String getAluTemp() {
+		return aluTemp;
+	}
+
+	public void setAluTemp(String aluTemp) {
+		this.aluTemp = aluTemp;
+	}
+
+	public Date getAluTempTime() {
+		return aluTempTime;
+	}
+
+	public void setAluTempTime(Date aluTempTime) {
+		this.aluTempTime = aluTempTime;
+	}
+
+	public String getMouldInfo() {
+		return mouldInfo;
+	}
+
+	public void setMouldInfo(String mouldInfo) {
+		this.mouldInfo = mouldInfo;
+	}
+
+	public Date getMouldTempTime() {
+		return mouldTempTime;
+	}
+
+	public void setMouldTempTime(Date mouldTempTime) {
+		this.mouldTempTime = mouldTempTime;
+	}
+
+	public String getTheoryPressure() {
+		return theoryPressure;
+	}
+
+	public void setTheoryPressure(String theoryPressure) {
+		this.theoryPressure = theoryPressure;
+	}
+
+	public String getRealPressure() {
+		return realPressure;
+	}
+
+	public void setRealPressure(String realPressure) {
+		this.realPressure = realPressure;
+	}
+
+	public String getPressureLevel() {
+		return pressureLevel;
+	}
+
+	public void setPressureLevel(String pressureLevel) {
+		this.pressureLevel = pressureLevel;
+	}
+
+	public Date getPressureTime() {
+		return pressureTime;
+	}
+
+	public void setPressureTime(Date pressureTime) {
+		this.pressureTime = pressureTime;
+	}
+
+	public String getEleConsume() {
+		return eleConsume;
+	}
+
+	public void setEleConsume(String eleConsume) {
+		this.eleConsume = eleConsume;
+	}
+
+	public Date getEleConsumeTime() {
+		return eleConsumeTime;
+	}
+
+	public void setEleConsumeTime(Date eleConsumeTime) {
+		this.eleConsumeTime = eleConsumeTime;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public Integer getLimit() {
+		return limit;
+	}
+
+	public void setLimit(Integer limit) {
+		this.limit = limit;
+	}
+
+	public Integer getOffset() {
+		return offset;
+	}
+
+	public void setOffset(Integer offset) {
+		this.offset = offset;
+	}
+
+	public List<MachineAttrHistory> getAttrList() {
+		return attrList;
+	}
+
+	public void setAttrList(List<MachineAttrHistory> attrList) {
+		this.attrList = attrList;
+	}
+
+	public String getAttr() {
+		return attr;
+	}
+
+	public void setAttr(String attr) {
+		this.attr = attr;
+	}
+
+	public String getVal() {
+		return val;
+	}
+
+	public void setVal(String val) {
+		this.val = val;
+	}
+
+	public Date getEventTime() {
+		return eventTime;
+	}
+
+	public void setEventTime(Date eventTime) {
+		this.eventTime = eventTime;
+	}
+
+}

+ 254 - 0
src/main/java/com/jeesite/modules/tianHong/service/MachineAttrHistoryService.java

@@ -0,0 +1,254 @@
+package com.jeesite.modules.tianHong.service;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.tianHong.dao.MachineAttrHistoryDao;
+import com.jeesite.modules.tianHong.entity.MachineAttrHistory;
+
+/**
+ * 设备历史属性Service
+ * @author ljr
+ * @version 2026-07-07
+ */
+@Service
+@Transactional(readOnly = true)
+public class MachineAttrHistoryService extends CrudService<MachineAttrHistoryDao, MachineAttrHistory> {
+
+	/**
+	 * 分页:必须先选设备;按 event_time 倒序,每页默认 20;关联表按「秒」补齐
+	 */
+	@Override
+	public Page<MachineAttrHistory> findPage(MachineAttrHistory machineAttrHistory) {
+		Page<MachineAttrHistory> page = machineAttrHistory.getPage();
+		if (page == null) {
+			page = new Page<>();
+			machineAttrHistory.setPage(page);
+		}
+		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
+			page.setList(Collections.emptyList());
+			page.setCount(0);
+			return page;
+		}
+		int pageSize = page.getPageSize() > 0 ? page.getPageSize() : 20;
+		int pageNo = page.getPageNo() > 0 ? page.getPageNo() : 1;
+		if (pageSize > 100) {
+			pageSize = 100;
+		}
+		page.setPageSize(pageSize);
+		page.setPageNo(pageNo);
+
+		machineAttrHistory.setPage(null);
+		Long count = dao.findHistoryCount(machineAttrHistory);
+		page.setCount(count == null ? 0L : count);
+
+		machineAttrHistory.setLimit(pageSize);
+		machineAttrHistory.setOffset((pageNo - 1) * pageSize);
+		List<MachineAttrHistory> list = dao.findList(machineAttrHistory);
+		fillRelatedBySecond(machineAttrHistory.getEqpId(), list);
+		page.setList(list);
+		return page;
+	}
+
+	/**
+	 * 查询列表(必须带设备号)
+	 */
+	@Override
+	public List<MachineAttrHistory> findList(MachineAttrHistory machineAttrHistory) {
+		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
+			return Collections.emptyList();
+		}
+		if (machineAttrHistory.getLimit() == null) {
+			machineAttrHistory.setLimit(20);
+		}
+		if (machineAttrHistory.getOffset() == null) {
+			machineAttrHistory.setOffset(0);
+		}
+		List<MachineAttrHistory> list = dao.findList(machineAttrHistory);
+		fillRelatedBySecond(machineAttrHistory.getEqpId(), list);
+		return list;
+	}
+
+	/**
+	 * 用本页时间点截断到秒,按索引范围补齐温压电量(避免大表按秒 JOIN)
+	 */
+	private void fillRelatedBySecond(String eqpId, List<MachineAttrHistory> list) {
+		if (StringUtils.isBlank(eqpId) || list == null || list.isEmpty()) {
+			return;
+		}
+		Set<Date> secSet = new LinkedHashSet<>();
+		for (MachineAttrHistory row : list) {
+			if (row.getAttrEventTime() != null) {
+				secSet.add(truncateToSecond(row.getAttrEventTime()));
+			}
+		}
+		if (secSet.isEmpty()) {
+			return;
+		}
+		List<Date> secList = new ArrayList<>(secSet);
+
+		Map<Long, MachineAttrHistory> cruMap = latestBySec(dao.findCruBySecList(eqpId, secList));
+		Map<Long, MachineAttrHistory> aluMap = latestBySec(dao.findAluBySecList(eqpId, secList));
+		Map<Long, MachineAttrHistory> mouldMap = latestBySec(dao.findMouldBySecList(eqpId, secList));
+		Map<Long, MachineAttrHistory> pressMap = latestBySec(dao.findPressureBySecList(eqpId, secList));
+		Map<Long, MachineAttrHistory> eleMap = latestBySec(dao.findEleBySecList(eqpId, secList));
+
+		for (MachineAttrHistory row : list) {
+			if (row.getAttrEventTime() == null) {
+				continue;
+			}
+			Long key = secKey(row.getAttrEventTime());
+			MachineAttrHistory cru = cruMap.get(key);
+			if (cru != null) {
+				row.setCruTemp(cru.getCruTemp());
+				row.setCruTempTime(cru.getCruTempTime());
+			}
+			MachineAttrHistory alu = aluMap.get(key);
+			if (alu != null) {
+				row.setAluTemp(alu.getAluTemp());
+				row.setAluTempTime(alu.getAluTempTime());
+			}
+			MachineAttrHistory mould = mouldMap.get(key);
+			if (mould != null) {
+				row.setMouldInfo(mould.getMouldInfo());
+				row.setMouldTempTime(mould.getMouldTempTime());
+			}
+			MachineAttrHistory press = pressMap.get(key);
+			if (press != null) {
+				row.setTheoryPressure(press.getTheoryPressure());
+				row.setRealPressure(press.getRealPressure());
+				row.setPressureLevel(press.getPressureLevel());
+				row.setPressureTime(press.getPressureTime());
+			}
+			MachineAttrHistory ele = eleMap.get(key);
+			if (ele != null) {
+				row.setEleConsume(ele.getEleConsume());
+				row.setEleConsumeTime(ele.getEleConsumeTime());
+			}
+		}
+	}
+
+	/** 同一秒多条时保留 EventTime 最新的一条 */
+	private Map<Long, MachineAttrHistory> latestBySec(List<MachineAttrHistory> rows) {
+		Map<Long, MachineAttrHistory> map = new HashMap<>();
+		if (rows == null) {
+			return map;
+		}
+		for (MachineAttrHistory row : rows) {
+			Date t = row.getEventTime();
+			if (t == null) {
+				t = row.getCruTempTime();
+			}
+			if (t == null) {
+				t = row.getAluTempTime();
+			}
+			if (t == null) {
+				t = row.getMouldTempTime();
+			}
+			if (t == null) {
+				t = row.getPressureTime();
+			}
+			if (t == null) {
+				t = row.getEleConsumeTime();
+			}
+			if (t == null) {
+				continue;
+			}
+			Long key = secKey(t);
+			MachineAttrHistory old = map.get(key);
+			if (old == null || compareTime(t, resolveTime(old)) > 0) {
+				if (row.getEventTime() == null) {
+					row.setEventTime(t);
+				}
+				map.put(key, row);
+			}
+		}
+		return map;
+	}
+
+	private Date resolveTime(MachineAttrHistory row) {
+		if (row.getEventTime() != null) {
+			return row.getEventTime();
+		}
+		if (row.getCruTempTime() != null) {
+			return row.getCruTempTime();
+		}
+		if (row.getAluTempTime() != null) {
+			return row.getAluTempTime();
+		}
+		if (row.getMouldTempTime() != null) {
+			return row.getMouldTempTime();
+		}
+		if (row.getPressureTime() != null) {
+			return row.getPressureTime();
+		}
+		return row.getEleConsumeTime();
+	}
+
+	private int compareTime(Date a, Date b) {
+		if (a == null && b == null) {
+			return 0;
+		}
+		if (a == null) {
+			return -1;
+		}
+		if (b == null) {
+			return 1;
+		}
+		return a.compareTo(b);
+	}
+
+	private Date truncateToSecond(Date date) {
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(date);
+		cal.set(Calendar.MILLISECOND, 0);
+		return cal.getTime();
+	}
+
+	/** 用 epoch 秒做 map key,避免 Date 毫秒差异 */
+	private Long secKey(Date date) {
+		return date.getTime() / 1000L;
+	}
+
+	/**
+	 * 查询指定设备的所有属性明细(用于详情展示)
+	 */
+	public List<MachineAttrHistory> findAttrList(MachineAttrHistory machineAttrHistory) {
+		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
+			return Collections.emptyList();
+		}
+		return dao.findAttrList(machineAttrHistory);
+	}
+
+	/**
+	 * 获取单条数据(按 eqp_id 查询属性明细)
+	 */
+	@Override
+	public MachineAttrHistory get(MachineAttrHistory machineAttrHistory) {
+		if (StringUtils.isBlank(machineAttrHistory.getEqpId())) {
+			return null;
+		}
+		MachineAttrHistory entity = new MachineAttrHistory();
+		entity.setEqpId(machineAttrHistory.getEqpId());
+		MachineAttrHistory queryParam = new MachineAttrHistory();
+		queryParam.setEqpId(machineAttrHistory.getEqpId());
+		queryParam.setBeginTime(machineAttrHistory.getBeginTime());
+		queryParam.setEndTime(machineAttrHistory.getEndTime());
+		entity.setAttrList(dao.findAttrList(queryParam));
+		return entity;
+	}
+
+}

+ 106 - 0
src/main/java/com/jeesite/modules/tianHong/web/MachineAttrHistoryController.java

@@ -0,0 +1,106 @@
+package com.jeesite.modules.tianHong.web;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.tianHong.entity.MachineAttrHistory;
+import com.jeesite.modules.tianHong.service.MachineAttrHistoryService;
+
+/**
+ * 设备历史属性Controller
+ * 对应 bzh 库 machine_attr_history 表,关联温度/压力/电量等历史表
+ * @author ljr
+ * @version 2026-07-07
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/tianHong/machineAttrHistory")
+public class MachineAttrHistoryController extends BaseController {
+
+	@Autowired
+	private MachineAttrHistoryService machineAttrHistoryService;
+
+	/**
+	 * 获取数据
+	 * @param id 设备号(eqp_id),仅 form 表单请求时传入
+	 */
+	@ModelAttribute
+	public MachineAttrHistory get(String id, boolean isNewRecord) {
+		// listData 等列表请求不传 id,直接返回空对象,避免在 @ModelAttribute 阶段执行不必要的查询
+		if (isNewRecord || id == null || id.isEmpty()) {
+			return new MachineAttrHistory();
+		}
+		MachineAttrHistory query = new MachineAttrHistory();
+		query.setEqpId(id);
+		MachineAttrHistory entity = machineAttrHistoryService.get(query);
+		if (entity == null) {
+			entity = new MachineAttrHistory();
+			entity.setEqpId(id);
+		}
+		return entity;
+	}
+
+	/**
+	 * 查询列表页面
+	 */
+	@RequiresPermissions("tianHong:machineAttrHistory:view")
+	@RequestMapping(value = { "list", "" })
+	public String list(MachineAttrHistory machineAttrHistory, Model model) {
+		model.addAttribute("machineAttrHistory", machineAttrHistory);
+		return "modules/tianHong/machineAttrHistoryList";
+	}
+
+	/**
+	 * 查询列表数据:须传设备号;按 event_time 倒序分页(默认每页 20)
+	 */
+	@RequiresPermissions("tianHong:machineAttrHistory:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MachineAttrHistory> listData(MachineAttrHistory machineAttrHistory, HttpServletRequest request,
+			HttpServletResponse response) {
+		machineAttrHistory.setPage(new Page<>(request, response));
+		return machineAttrHistoryService.findPage(machineAttrHistory);
+	}
+
+	/**
+	 * 查询指定设备的所有属性明细(用于详情模态框)
+	 */
+	@RequiresPermissions("tianHong:machineAttrHistory:view")
+	@RequestMapping(value = "attrListData")
+	@ResponseBody
+	public List<MachineAttrHistory> attrListData(String eqpId, Date beginTime, Date endTime) {
+		if (StringUtils.isBlank(eqpId)) {
+			return Collections.emptyList();
+		}
+		MachineAttrHistory query = new MachineAttrHistory();
+		query.setEqpId(eqpId);
+		query.setBeginTime(beginTime);
+		query.setEndTime(endTime);
+		return machineAttrHistoryService.findAttrList(query);
+	}
+
+	/**
+	 * 查看详情页面
+	 */
+	@RequiresPermissions("tianHong:machineAttrHistory:view")
+	@RequestMapping(value = "form")
+	public String form(MachineAttrHistory machineAttrHistory, Model model) {
+		model.addAttribute("machineAttrHistory", machineAttrHistory);
+		return "modules/tianHong/machineAttrHistoryForm";
+	}
+
+}

+ 117 - 0
src/main/resources/mappings/modules/tianHong/MachineAttrHistoryDao.xml

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeesite.modules.tianHong.dao.MachineAttrHistoryDao">
+
+	<!-- machine_attr_history 时间范围 -->
+	<sql id="attrEventTimeRange">
+		<if test="beginTime != null">
+			AND event_time &gt;= #{beginTime}
+		</if>
+		<if test="endTime != null">
+			AND event_time &lt;= #{endTime}
+		</if>
+	</sql>
+
+	<!-- 按秒列表:每个秒做索引范围,避免大表 DATE_FORMAT / 开放式 JOIN -->
+	<sql id="eventTimeInSecList">
+		<foreach collection="secList" item="sec" separator=" OR ">
+			(EventTime &gt;= #{sec} AND EventTime &lt; DATE_ADD(#{sec}, INTERVAL 1 SECOND))
+		</foreach>
+	</sql>
+
+	<!--
+		主列表:只查属性主表(快)。温压电量在 Service 里按「秒」二次补齐。
+	-->
+	<select id="findList" resultType="com.jeesite.modules.tianHong.entity.MachineAttrHistory">
+		SELECT
+			m.eqp_id AS "eqpId",
+			CONCAT(m.eqp_id, '_', DATE_FORMAT(page.event_time, '%Y%m%d%H%i%s%f')) AS "id",
+			MAX(CASE WHEN m.attr = 'alu_val' THEN m.val END) AS "aluVal",
+			MAX(CASE WHEN m.attr = 'pressurizeTime' THEN m.val END) AS "pressurizeTime",
+			MAX(CASE WHEN m.attr = 'coolingTime' THEN m.val END) AS "coolingTime",
+			MAX(CASE WHEN m.attr = 'PrePres' THEN m.val END) AS "prePres",
+			MAX(CASE WHEN m.attr = 'HoldPressure' THEN m.val END) AS "holdPressure",
+			MAX(CASE WHEN m.attr = 'moldFillingPre' THEN m.val END) AS "moldFillingPre",
+			page.event_time AS "attrEventTime"
+		FROM (
+			SELECT event_time
+			FROM (
+				SELECT event_time
+				FROM machine_attr_history
+				WHERE eqp_id = #{eqpId}
+				<include refid="attrEventTimeRange"/>
+				GROUP BY event_time
+				ORDER BY event_time DESC
+				LIMIT #{limit} OFFSET #{offset}
+			) x
+		) page
+		LEFT JOIN machine_attr_history m
+			ON m.eqp_id = #{eqpId} AND m.event_time = page.event_time
+		GROUP BY page.event_time, m.eqp_id
+		ORDER BY page.event_time DESC
+	</select>
+
+	<select id="findHistoryCount" resultType="java.lang.Long">
+		SELECT COUNT(*)
+		FROM (
+			SELECT event_time
+			FROM machine_attr_history
+			WHERE eqp_id = #{eqpId}
+			<include refid="attrEventTimeRange"/>
+			GROUP BY event_time
+		) t
+	</select>
+
+	<select id="findCruBySecList" resultType="com.jeesite.modules.tianHong.entity.MachineAttrHistory">
+		SELECT Value AS "cruTemp", EventTime AS "cruTempTime", EventTime AS "eventTime"
+		FROM crutemphistory
+		WHERE EQP_ID = #{eqpId}
+		AND (<include refid="eventTimeInSecList"/>)
+	</select>
+
+	<select id="findAluBySecList" resultType="com.jeesite.modules.tianHong.entity.MachineAttrHistory">
+		SELECT Value AS "aluTemp", EventTime AS "aluTempTime", EventTime AS "eventTime"
+		FROM alutemphistory
+		WHERE EQP_ID = #{eqpId}
+		AND (<include refid="eventTimeInSecList"/>)
+	</select>
+
+	<select id="findMouldBySecList" resultType="com.jeesite.modules.tianHong.entity.MachineAttrHistory">
+		SELECT
+			MAX(EventTime) AS "eventTime",
+			GROUP_CONCAT(CONCAT(MouldID, ':', Value) ORDER BY MouldID SEPARATOR ', ') AS "mouldInfo",
+			MAX(EventTime) AS "mouldTempTime"
+		FROM mouldtemphistory
+		WHERE EQP_ID = #{eqpId}
+		AND (<include refid="eventTimeInSecList"/>)
+		GROUP BY DATE_FORMAT(EventTime, '%Y-%m-%d %H:%i:%s')
+	</select>
+
+	<select id="findPressureBySecList" resultType="com.jeesite.modules.tianHong.entity.MachineAttrHistory">
+		SELECT TheoryValue AS "theoryPressure", RealValue AS "realPressure",
+			LevelID AS "pressureLevel", EventTime AS "pressureTime", EventTime AS "eventTime"
+		FROM pressurehistory
+		WHERE EQP_ID = #{eqpId}
+		AND (<include refid="eventTimeInSecList"/>)
+	</select>
+
+	<select id="findEleBySecList" resultType="com.jeesite.modules.tianHong.entity.MachineAttrHistory">
+		SELECT Value AS "eleConsume", EventTime AS "eleConsumeTime", EventTime AS "eventTime"
+		FROM EleConsumeHistory
+		WHERE EQP_ID = #{eqpId}
+		AND (<include refid="eventTimeInSecList"/>)
+	</select>
+
+	<select id="findAttrList" resultType="com.jeesite.modules.tianHong.entity.MachineAttrHistory">
+		SELECT
+			eqp_id AS "eqpId",
+			attr AS "attr",
+			val AS "val",
+			event_time AS "eventTime"
+		FROM machine_attr_history
+		WHERE eqp_id = #{eqpId}
+		<include refid="attrEventTimeRange"/>
+		ORDER BY event_time DESC, attr
+	</select>
+
+</mapper>

+ 66 - 0
src/main/resources/views/modules/tianHong/machineAttrHistoryForm.html

@@ -0,0 +1,66 @@
+<% layout('/layouts/default.html', {title: '设备历史属性详情', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('设备历史属性详情')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply"></i> ${text('返回')}</a>
+			</div>
+		</div>
+		<div class="box-body">
+			<form id="inputForm" class="form-horizontal">
+
+				<h4 class="form-header">${text('属性明细列表')}</h4>
+				<table id="attrDataGrid"></table>
+			</form>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+// 属性字段 -> 中文属性名 映射(含通配规则)
+var attrNameMap = {
+	'alu_val': '剩余模次',
+	'pressurizeTime': '保压时间',
+	'coolingTime': '冷却时间',
+	'PrePres': '预压压力设定',
+	'HoldPressure': '保压开始信号',
+	'moldFillingPre': '各段压力(冲型压力)'
+};
+var attrNameRules = [
+	{re:/^COOL_FLOW_(\d+)$/, name:function(m){ return '冷却流量['+m[1]+']'; }},
+	{re:/^f_stTime_(\d+)$/,  name:function(m){ return '风冷等待时间['+m[1]+']'; }},
+	{re:/^f_coolingTime_(\d+)$/, name:function(m){ return '风冷开启时间['+m[1]+']'; }},
+	{re:/^s_stTime_(\d+)$/,  name:function(m){ return '水冷等待时间['+m[1]+']'; }},
+	{re:/^s_coolingTime_(\d+)$/, name:function(m){ return '水冷开启时间['+m[1]+']'; }},
+	{re:/^F_SWITCH_(\d+)$/,  name:function(m){ return '风冷控制开关['+m[1]+']'; }},
+	{re:/^W_SWITCH_(\d+)$/,  name:function(m){ return '水冷控制开关['+m[1]+']'; }}
+];
+function getAttrCnName(attr){
+	if(attr == null) return '';
+	if(attrNameMap[attr] != null) return attrNameMap[attr];
+	for(var i=0; i<attrNameRules.length; i++){
+		var m = attr.match(attrNameRules[i].re);
+		if(m){ return attrNameRules[i].name(m); }
+	}
+	return attr;
+}
+
+$('#attrDataGrid').dataGrid({
+	data: ${toJson(machineAttrHistory.attrList)!'[]'},
+	datatype: "local", // 设置本地数据,避免 dataGrid 走 ajax 加载校验 searchForm/url
+	columnModel: [
+		{header:'${text("属性名")}', name:'attrCnName', width:220, align:"center", formatter:function(val, obj, row, act){
+			return getAttrCnName(js.val(row,'attr'));
+		}},
+		{header:'${text("属性字段")}', name:'attr', width:220, align:"center"},
+		{header:'${text("属性值")}', name:'val', width:200, align:"center"},
+		{header:'${text("更新时间")}', name:'eventTime', width:180, align:"center"}
+	],
+	showRownum: true,
+	// 固定显示10条数据的高度,扩大属性明细列表的显示面积
+	autoGridHeight: function(){ return 540; }
+});
+</script>

+ 81 - 0
src/main/resources/views/modules/tianHong/machineAttrHistoryList.html

@@ -0,0 +1,81 @@
+<% layout('/layouts/default.html', {title: '设备历史属性', libs: ['dataGrid','laydate']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('设备历史属性')}
+			</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="#" class="btn btn-default" id="btnRefresh" title="${text('刷新')}"><i class="fa fa-refresh"></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="${machineAttrHistory}" action="${ctx}/tianHong/machineAttrHistory/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="20" data-order-by="${parameter.orderBy}">
+				<div class="form-group">
+				<label class="control-label">${text('设备号')}:</label>
+				<div class="control-inline">
+					<#form:input path="eqpId" maxlength="40" class="form-control width-120" required="true"/>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="control-label">${text('开始时间')}:</label>
+				<div class="control-inline">
+					<#form:input path="beginTime" maxlength="20" class="form-control laydate width-160"
+						dateFmt="yyyy-MM-dd HH:mm:ss" />
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="control-label">${text('结束时间')}:</label>
+				<div class="control-inline">
+					<#form:input path="endTime" maxlength="20" class="form-control laydate width-160"
+						dateFmt="yyyy-MM-dd HH:mm:ss" />
+				</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对象(须先填设备号;每页 20,时间倒序由后端保证)
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	pageSize: 20,
+	columnModel: [
+		{header:'${text("设备号")}', name:'eqpId', index:'t.eqp_id', width:120, align:"center", frozen:true},
+		{header:'${text("炉膛温度(℃)")}', name:'cruTemp', index:'c.Value', width:110, align:"center"},
+		{header:'${text("铝水温度(℃)")}', name:'aluTemp', index:'a.Value', width:110, align:"center"},
+		{header:'${text("模具温度(℃)")}', name:'mouldInfo', index:'mt.mould_info', width:200, align:"center"},
+		{header:'${text("理论压力")}', name:'theoryPressure', index:'p.TheoryValue', width:100, align:"center"},
+		{header:'${text("实际压力")}', name:'realPressure', index:'p.RealValue', width:100, align:"center"},
+		{header:'${text("压力段位")}', name:'pressureLevel', index:'p.LevelID', width:90, align:"center"},
+		{header:'${text("电量总计")}', name:'eleConsume', index:'e.Value', width:110, align:"center"},
+		{header:'${text("剩余模次")}', name:'aluVal', width:90, align:"center"},
+		{header:'${text("保压时间")}', name:'pressurizeTime', width:90, align:"center"},
+		{header:'${text("冷却时间")}', name:'coolingTime', width:90, align:"center"},
+		{header:'${text("预压压力设定")}', name:'prePres', width:110, align:"center"},
+		{header:'${text("保压开始信号")}', name:'holdPressure', width:110, align:"center"},
+		{header:'${text("充型压力")}', name:'moldFillingPre', width:100, align:"center"},
+		{header:'${text("属性更新时间")}', name:'attrEventTime', width:150, align:"center"},
+		{header:'${text("操作")}', name:'actions', width:120, align:"center", formatter: function(val, obj, row, act){
+			var actions = [];
+			actions.push('<a href="${ctx}/tianHong/machineAttrHistory/form?id='+js.val(row,'eqpId')+'" class="addTabPage" data-title="${text("设备属性明细")}"><i class="fa fa-list"></i> ${text("属性明细")}</a>&nbsp;');
+			return actions.join('');
+		}}
+	]
+});
+
+// 刷新按钮
+$('#btnRefresh').click(function(){
+	$('#dataGrid').dataGrid('refresh');
+});
+</script>