Browse Source

热处理参数获取

zhanghe 4 tháng trước cách đây
mục cha
commit
5cdb3e1a55

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

@@ -0,0 +1,15 @@
+package com.jeesite.modules.mes.dao;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesHeatProcess;
+
+/**
+ * 热加工数据DAO接口
+ * @author system
+ * @version 2026-06-09
+ */
+@MyBatisDao
+public interface MesHeatProcessDao extends CrudDao<MesHeatProcess> {
+
+}

+ 114 - 0
src/main/java/com/jeesite/modules/mes/entity/MesHeatProcess.java

@@ -0,0 +1,114 @@
+package com.jeesite.modules.mes.entity;
+
+import javax.validation.constraints.Size;
+
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.utils.excel.annotation.ExcelField;
+import com.jeesite.common.utils.excel.annotation.ExcelField.Align;
+import com.jeesite.common.utils.excel.annotation.ExcelFields;
+
+/**
+ * 热处理数据管理Entity
+ * @author system
+ * @version 2026-06-09
+ */
+@Table(name="mes_heat_process", alias="a", label="热加工数据信息", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="zone1_tc_pv", attrName="zone1TcPv", label="一区温控PV"),
+		@Column(name="zone1_tc_sp", attrName="zone1TcSp", label="一区温控SP"),
+		@Column(name="zone2_tc_pv", attrName="zone2TcPv", label="二区温控PV"),
+		@Column(name="zone2_tc_sp", attrName="zone2TcSp", label="二区温控SP"),
+		@Column(name="zone1_mon_pv", attrName="zone1MonPv", label="一区监控PV"),
+		@Column(name="zone1_mon_sp", attrName="zone1MonSp", label="一区监控SP"),
+		@Column(name="zone2_mon_pv", attrName="zone2MonPv", label="二区监控PV"),
+		@Column(name="zone2_mon_sp", attrName="zone2MonSp", label="二区监控SP"),
+		@Column(name="main_drive_run_freq", attrName="mainDriveRunFreq", label="主驱动运行频率"),
+		@Column(name="main_drive_set_freq", attrName="mainDriveSetFreq", label="主驱动设定频率"),
+		@Column(name="furnace_count", attrName="furnaceCount", label="入炉数"),
+		@Column(name="fan_speed_zone1", attrName="fanSpeedZone1", label="风机转速-一区"),
+		@Column(name="fan_speed_zone2", attrName="fanSpeedZone2", label="风机转速-二区"),
+		@Column(name="create_date", attrName="createDate", label="采集时间", isUpdate=false, isQuery=false, isUpdateForce=true),
+	}, orderBy="a.id DESC"
+)
+public class MesHeatProcess extends DataEntity<MesHeatProcess> {
+
+	private static final long serialVersionUID = 1L;
+	private String zone1TcPv;
+	private String zone1TcSp;
+	private String zone2TcPv;
+	private String zone2TcSp;
+	private String zone1MonPv;
+	private String zone1MonSp;
+	private String zone2MonPv;
+	private String zone2MonSp;
+	private String mainDriveRunFreq;
+	private String mainDriveSetFreq;
+	private String furnaceCount;
+	private String fanSpeedZone1;
+	private String fanSpeedZone2;
+
+	@ExcelFields({
+		@ExcelField(title="一区温控PV", attrName="zone1TcPv", align=Align.CENTER, sort=20),
+		@ExcelField(title="一区温控SP", attrName="zone1TcSp", align=Align.CENTER, sort=30),
+		@ExcelField(title="二区温控PV", attrName="zone2TcPv", align=Align.CENTER, sort=40),
+		@ExcelField(title="二区温控SP", attrName="zone2TcSp", align=Align.CENTER, sort=50),
+		@ExcelField(title="一区监控PV", attrName="zone1MonPv", align=Align.CENTER, sort=60),
+		@ExcelField(title="一区监控SP", attrName="zone1MonSp", align=Align.CENTER, sort=70),
+		@ExcelField(title="二区监控PV", attrName="zone2MonPv", align=Align.CENTER, sort=80),
+		@ExcelField(title="二区监控SP", attrName="zone2MonSp", align=Align.CENTER, sort=90),
+		@ExcelField(title="主驱动运行频率(Hz)", attrName="mainDriveRunFreq", align=Align.CENTER, sort=100),
+		@ExcelField(title="主驱动设定频率(Hz)", attrName="mainDriveSetFreq", align=Align.CENTER, sort=110),
+		@ExcelField(title="入炉数", attrName="furnaceCount", align=Align.CENTER, sort=120),
+		@ExcelField(title="风机转速-一区", attrName="fanSpeedZone1", align=Align.CENTER, sort=130),
+		@ExcelField(title="风机转速-二区", attrName="fanSpeedZone2", align=Align.CENTER, sort=140),
+		@ExcelField(title="采集时间", attrName="createDate", align=Align.CENTER, sort=150),
+	})
+	public MesHeatProcess() {
+		this(null);
+	}
+
+	public MesHeatProcess(String id){
+		super(id);
+	}
+
+	@Size(max=64) public String getZone1TcPv() { return zone1TcPv; }
+	public void setZone1TcPv(String v) { this.zone1TcPv = v; }
+
+	@Size(max=64) public String getZone1TcSp() { return zone1TcSp; }
+	public void setZone1TcSp(String v) { this.zone1TcSp = v; }
+
+	@Size(max=64) public String getZone2TcPv() { return zone2TcPv; }
+	public void setZone2TcPv(String v) { this.zone2TcPv = v; }
+
+	@Size(max=64) public String getZone2TcSp() { return zone2TcSp; }
+	public void setZone2TcSp(String v) { this.zone2TcSp = v; }
+
+	@Size(max=64) public String getZone1MonPv() { return zone1MonPv; }
+	public void setZone1MonPv(String v) { this.zone1MonPv = v; }
+
+	@Size(max=64) public String getZone1MonSp() { return zone1MonSp; }
+	public void setZone1MonSp(String v) { this.zone1MonSp = v; }
+
+	@Size(max=64) public String getZone2MonPv() { return zone2MonPv; }
+	public void setZone2MonPv(String v) { this.zone2MonPv = v; }
+
+	@Size(max=64) public String getZone2MonSp() { return zone2MonSp; }
+	public void setZone2MonSp(String v) { this.zone2MonSp = v; }
+
+	@Size(max=64) public String getMainDriveRunFreq() { return mainDriveRunFreq; }
+	public void setMainDriveRunFreq(String v) { this.mainDriveRunFreq = v; }
+
+	@Size(max=64) public String getMainDriveSetFreq() { return mainDriveSetFreq; }
+	public void setMainDriveSetFreq(String v) { this.mainDriveSetFreq = v; }
+
+	@Size(max=64) public String getFurnaceCount() { return furnaceCount; }
+	public void setFurnaceCount(String v) { this.furnaceCount = v; }
+
+	@Size(max=64) public String getFanSpeedZone1() { return fanSpeedZone1; }
+	public void setFanSpeedZone1(String v) { this.fanSpeedZone1 = v; }
+
+	@Size(max=64) public String getFanSpeedZone2() { return fanSpeedZone2; }
+	public void setFanSpeedZone2(String v) { this.fanSpeedZone2 = v; }
+}

+ 91 - 0
src/main/java/com/jeesite/modules/mes/service/MesHeatProcessService.java

@@ -0,0 +1,91 @@
+package com.jeesite.modules.mes.service;
+
+import java.util.List;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.common.service.ServiceException;
+import com.jeesite.common.utils.excel.ExcelImport;
+import com.jeesite.common.validator.ValidatorUtils;
+import com.jeesite.modules.mes.dao.MesHeatProcessDao;
+import com.jeesite.modules.mes.entity.MesHeatProcess;
+
+/**
+ * 热加工数据Service
+ * @author system
+ * @version 2026-06-09
+ */
+@Service
+public class MesHeatProcessService extends CrudService<MesHeatProcessDao, MesHeatProcess> {
+
+	@Override
+	public MesHeatProcess get(MesHeatProcess e) { return super.get(e); }
+
+	@Override
+	public Page<MesHeatProcess> findPage(MesHeatProcess e) { return super.findPage(e); }
+
+	@Override
+	public List<MesHeatProcess> findList(MesHeatProcess e) { return super.findList(e); }
+
+	@Override
+	@Transactional
+	public void save(MesHeatProcess e) { super.save(e); }
+
+	@Override
+	@Transactional
+	public void updateStatus(MesHeatProcess e) { super.updateStatus(e); }
+
+	@Override
+	@Transactional
+	public void delete(MesHeatProcess e) { super.delete(e); }
+
+	@Transactional
+	public String importData(MultipartFile file) {
+		if (file == null) throw new ServiceException(text("请选择导入的数据文件!"));
+		int successNum = 0; int failureNum = 0;
+		StringBuilder successMsg = new StringBuilder();
+		StringBuilder failureMsg = new StringBuilder();
+		try (ExcelImport ei = new ExcelImport(file, 2, 0)) {
+			List<MesHeatProcess> list = ei.getDataList(MesHeatProcess.class);
+			for (MesHeatProcess item : list) {
+				try {
+					ValidatorUtils.validateWithException(item);
+					this.save(item);
+					successNum++;
+					successMsg.append("<br/>").append(successNum).append("、编号 ").append(item.getId()).append(" 导入成功");
+				} catch (Exception ex) {
+					failureNum++;
+					String msg = "<br/>" + failureNum + "、编号 " + item.getId() + " 导入失败:";
+					if (ex instanceof ConstraintViolationException) {
+						for (ConstraintViolation<?> v : ((ConstraintViolationException) ex).getConstraintViolations()) {
+							msg += Global.getText(v.getMessage()) + " (" + v.getPropertyPath() + ")";
+						}
+					} else {
+						msg += ex.getMessage();
+					}
+					failureMsg.append(msg);
+					logger.error(msg, ex);
+				}
+			}
+		} catch (Exception ex) {
+			logger.error(ex.getMessage(), ex);
+			failureMsg.append(ex.getMessage());
+			return failureMsg.toString();
+		}
+		if (failureNum > 0) {
+			failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+			throw new ServiceException(failureMsg.toString());
+		} else {
+			successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+		}
+		return successMsg.toString();
+	}
+}

+ 138 - 0
src/main/java/com/jeesite/modules/mes/web/MesHeatProcessController.java

@@ -0,0 +1,138 @@
+package com.jeesite.modules.mes.web;
+
+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.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.jeesite.common.config.Global;
+import com.jeesite.common.collect.ListUtils;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.DateUtils;
+import com.jeesite.common.utils.excel.ExcelExport;
+import com.jeesite.common.utils.excel.annotation.ExcelField.Type;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesHeatProcess;
+import com.jeesite.modules.mes.service.MesHeatProcessService;
+
+/**
+ * 热处理数据管理Controller
+ * @author system
+ * @version 2026-06-09
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesHeatProcess")
+public class MesHeatProcessController extends BaseController {
+
+	@Autowired
+	private MesHeatProcessService mesHeatProcessService;
+
+	@ModelAttribute
+	public MesHeatProcess get(String id, boolean isNewRecord) {
+		return mesHeatProcessService.get(id, isNewRecord);
+	}
+
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesHeatProcess e, Model model) {
+		model.addAttribute("mesHeatProcess", e);
+		return "modules/mes/mesHeatProcessList";
+	}
+
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesHeatProcess> listData(MesHeatProcess e, HttpServletRequest request, HttpServletResponse response) {
+		e.setPage(new Page<>(request, response));
+		return mesHeatProcessService.findPage(e);
+	}
+
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = "form")
+	public String form(MesHeatProcess e, Model model) {
+		model.addAttribute("mesHeatProcess", e);
+		return "modules/mes/mesHeatProcessForm";
+	}
+
+	/**
+	 * 实时数据看板页面
+	 */
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = "board")
+	public String board() {
+		return "modules/mes/mesHeatProcessBoard";
+	}
+
+	/**
+	 * 实时数据看板 - 获取最新一条数据
+	 */
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = "latestData")
+	@ResponseBody
+	public MesHeatProcess latestData() {
+		MesHeatProcess query = new MesHeatProcess();
+		query.setPage(new Page<>(1, 1));
+		Page<MesHeatProcess> page = mesHeatProcessService.findPage(query);
+		List<MesHeatProcess> list = page.getList();
+		return ListUtils.isEmpty(list) ? new MesHeatProcess() : list.get(0);
+	}
+
+	@RequiresPermissions("mes:mesHeatProcess:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesHeatProcess e) {
+		mesHeatProcessService.save(e);
+		return renderResult(Global.TRUE, text("保存热加工数据成功!"));
+	}
+
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = "exportData")
+	public void exportData(MesHeatProcess e, HttpServletResponse response) {
+		List<MesHeatProcess> list = mesHeatProcessService.findList(e);
+		String fileName = "热加工数据" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
+		try (ExcelExport ee = new ExcelExport("热加工数据", MesHeatProcess.class)) {
+			ee.setDataList(list).write(response, fileName);
+		}
+	}
+
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = "importTemplate")
+	public void importTemplate(HttpServletResponse response) {
+		MesHeatProcess e = new MesHeatProcess();
+		List<MesHeatProcess> list = ListUtils.newArrayList(e);
+		String fileName = "热加工数据模板.xlsx";
+		try (ExcelExport ee = new ExcelExport("热加工数据", MesHeatProcess.class, Type.IMPORT)) {
+			ee.setDataList(list).write(response, fileName);
+		}
+	}
+
+	@ResponseBody
+	@RequiresPermissions("mes:mesHeatProcess:edit")
+	@PostMapping(value = "importData")
+	public String importData(MultipartFile file) {
+		try {
+			String message = mesHeatProcessService.importData(file);
+			return renderResult(Global.TRUE, "posfull:" + message);
+		} catch (Exception ex) {
+			return renderResult(Global.FALSE, "posfull:" + ex.getMessage());
+		}
+	}
+
+	@RequiresPermissions("mes:mesHeatProcess:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesHeatProcess e) {
+		mesHeatProcessService.delete(e);
+		return renderResult(Global.TRUE, text("删除热加工数据成功!"));
+	}
+}

+ 96 - 0
src/main/java/com/jeesite/modules/scheduling/HeatProcessScheduling.java

@@ -0,0 +1,96 @@
+package com.jeesite.modules.scheduling;
+
+import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
+import com.jeesite.common.config.Global;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.modules.mes.entity.MesHeatProcess;
+import com.jeesite.modules.mes.service.MesHeatProcessService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * 热加工数据采集定时任务
+ */
+@Configuration
+@EnableScheduling
+public class HeatProcessScheduling {
+
+    private static final Logger logger = LoggerFactory.getLogger(HeatProcessScheduling.class);
+
+    private static final String DEFAULT_PLC_IP = "10.0.20.111";
+
+    @Autowired
+    private MesHeatProcessService mesHeatProcessService;
+
+    /**
+     * 每 30 秒采集一次
+     */
+    @Scheduled(fixedRate = 30000)
+    public void collect() {
+        String enabled = Global.getConfig("mes.heatprocess.enabled");
+        if (StringUtils.isNotBlank(enabled) && !"true".equalsIgnoreCase(enabled.trim())) {
+            return;
+        }
+        String ip = Global.getConfig("mes.heatprocess.plc.ip");
+        if (StringUtils.isBlank(ip)) {
+            ip = DEFAULT_PLC_IP;
+        }
+
+        S7PLC s7PLC = null;
+        try {
+            s7PLC = new S7PLC(EPlcType.S1200, ip);
+
+            MesHeatProcess e = new MesHeatProcess();
+
+            // 温度  SP表示设定值,pv表示实际值
+            e.setZone1TcPv(fmtFloat(s7PLC.readFloat32("DB11.0")));
+            e.setZone1TcSp(fmtFloat(s7PLC.readFloat32("DB11.4")));
+            e.setZone2TcPv(fmtFloat(s7PLC.readFloat32("DB11.16")));
+            e.setZone2TcSp(fmtFloat(s7PLC.readFloat32("DB11.20")));
+            e.setZone1MonPv(fmtFloat(s7PLC.readFloat32("DB11.32")));
+            e.setZone1MonSp(fmtFloat(s7PLC.readFloat32("DB11.36")));
+            e.setZone2MonPv(fmtFloat(s7PLC.readFloat32("DB11.40")));
+            e.setZone2MonSp(fmtFloat(s7PLC.readFloat32("DB11.44")));
+
+            // 数值除 10 转 Hz =====
+            short runFreq = s7PLC.readInt16("DB4.316");
+            short setFreq = s7PLC.readInt16("DB4.318");
+            e.setMainDriveRunFreq(String.valueOf(runFreq / 10.0));
+            e.setMainDriveSetFreq(String.valueOf(setFreq / 10.0));
+
+            // ===== 入炉数(Dint, DB6) =====
+            e.setFurnaceCount(String.valueOf(s7PLC.readInt32("DB6.12")));
+
+            // ===== 风机转速(Dint, DB4) =====
+            e.setFanSpeedZone1(String.valueOf(s7PLC.readInt32("DB4.272")));
+            e.setFanSpeedZone2(String.valueOf(s7PLC.readInt32("DB4.276")));
+
+            mesHeatProcessService.save(e);
+
+            logger.info("热加工数据采集成功[{}] 一区温控PV:{} 二区温控PV:{} 主驱动:{}Hz 入炉数:{}",
+                    ip, e.getZone1TcPv(), e.getZone2TcPv(), e.getMainDriveRunFreq(), e.getFurnaceCount());
+        } catch (Exception ex) {
+            logger.error("热加工数据采集失败[{}]:{}", ip, ex.getMessage());
+        } finally {
+            if (s7PLC != null) {
+                try {
+                    s7PLC.close();
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    /**
+     * 浮点保留 1 位小数
+     */
+    private String fmtFloat(Float val) {
+        if (val == null) return null;
+        return String.format("%.1f", val);
+    }
+}

+ 7 - 0
src/main/resources/mappings/modules/mes/MesHeatProcessDao.xml

@@ -0,0 +1,7 @@
+<?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.mes.dao.MesHeatProcessDao">
+
+	<!-- 使用 JeeSite 内置 SQL 构建,无需手写 CRUD -->
+
+</mapper>

+ 135 - 0
src/main/resources/views/modules/mes/mesHeatProcessBoard.html

@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<title>热加工数据实时看板</title>
+	<script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
+	<style>
+		* { margin: 0; padding: 0; box-sizing: border-box; }
+		html, body { width: 100%; height: 100%; }
+		body {
+			font-family: "Microsoft YaHei", sans-serif;
+			background: #04122b;
+			background-image: radial-gradient(circle at 50% 0%, #0a2350 0%, #04122b 60%);
+			color: #d6e6ff;
+			overflow: auto;
+			padding-bottom: 24px;
+		}
+		.bd-header {
+			height: 64px; display: flex; align-items: center; justify-content: center;
+			position: relative; border-bottom: 1px solid rgba(0,160,255,0.25);
+		}
+		.bd-title { font-size: 24px; letter-spacing: 4px; color: #5ad8ff; font-weight: bold; text-shadow: 0 0 12px rgba(0,180,255,0.6); }
+		.bd-time { position: absolute; right: 24px; font-size: 14px; color: #8fb6e6; }
+		.bd-status { position: absolute; left: 24px; font-size: 14px; }
+		.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
+		.dot-on { background: #25e29a; box-shadow: 0 0 8px #25e29a; }
+		.dot-off { background: #ff5a6a; box-shadow: 0 0 8px #ff5a6a; }
+		.section { margin: 18px 24px 0; }
+		.section-title { font-size: 16px; color: #7fd6ff; letter-spacing: 2px; margin-bottom: 12px; border-left: 4px solid #2fa8ff; padding-left: 10px; }
+		.cards { display: flex; flex-wrap: wrap; gap: 14px; }
+		.card {
+			flex: 1 1 200px; min-width: 180px; height: 130px; border-radius: 12px;
+			background: linear-gradient(160deg, rgba(13,45,95,0.9), rgba(8,26,56,0.9));
+			border: 1px solid rgba(0,160,255,0.35);
+			display: flex; flex-direction: column; align-items: center; justify-content: center;
+			position: relative; overflow: hidden;
+		}
+		.card::before { content:""; position:absolute; top:0; left:0; right:0; height:3px; background: linear-gradient(90deg, transparent, #2fa8ff, transparent); }
+		.card-label { font-size: 14px; color: #9cc4f0; margin-bottom: 12px; letter-spacing: 1px; }
+		.card-value { font-size: 36px; font-weight: bold; color: #41e6c3; line-height: 1; text-shadow: 0 0 14px rgba(65,230,195,0.5); }
+		.card-unit { font-size: 16px; color: #7fd6ff; margin-left: 4px; }
+		.tc-pv .card-value { color: #ffb454; text-shadow: 0 0 14px rgba(255,180,84,0.5); }
+		.tc-sp .card-value { color: #41e6c3; }
+		.mon-pv .card-value { color: #5ab8ff; }
+		.mon-sp .card-value { color: #ff7a9c; text-shadow: 0 0 14px rgba(255,122,156,0.5); }
+		.bd-foot { text-align: center; color: #6f93c4; font-size: 13px; margin-top: 18px; }
+	</style>
+</head>
+<body>
+	<div class="bd-header">
+		<span class="bd-status"><i id="connDot" class="dot dot-off"></i><span id="connText">未连接</span></span>
+		<span class="bd-title">热加工数据实时监控看板</span>
+		<span class="bd-time" id="nowTime"></span>
+	</div>
+
+	<div class="section">
+		<div class="section-title">一区温度</div>
+		<div class="cards">
+			<div class="card tc-pv"><div class="card-label">一区温控 PV</div><div><span class="card-value" id="z1TcPv">--</span><span class="card-unit">℃</span></div></div>
+			<div class="card tc-sp"><div class="card-label">一区温控 SP</div><div><span class="card-value" id="z1TcSp">--</span><span class="card-unit">℃</span></div></div>
+			<div class="card mon-pv"><div class="card-label">一区监控 PV</div><div><span class="card-value" id="z1MonPv">--</span><span class="card-unit">℃</span></div></div>
+			<div class="card mon-sp"><div class="card-label">一区监控 SP</div><div><span class="card-value" id="z1MonSp">--</span><span class="card-unit">℃</span></div></div>
+		</div>
+	</div>
+
+	<div class="section">
+		<div class="section-title">二区温度</div>
+		<div class="cards">
+			<div class="card tc-pv"><div class="card-label">二区温控 PV</div><div><span class="card-value" id="z2TcPv">--</span><span class="card-unit">℃</span></div></div>
+			<div class="card tc-sp"><div class="card-label">二区温控 SP</div><div><span class="card-value" id="z2TcSp">--</span><span class="card-unit">℃</span></div></div>
+			<div class="card mon-pv"><div class="card-label">二区监控 PV</div><div><span class="card-value" id="z2MonPv">--</span><span class="card-unit">℃</span></div></div>
+			<div class="card mon-sp"><div class="card-label">二区监控 SP</div><div><span class="card-value" id="z2MonSp">--</span><span class="card-unit">℃</span></div></div>
+		</div>
+	</div>
+
+	<div class="section">
+		<div class="section-title">主驱动 / 产量 / 风机</div>
+		<div class="cards">
+			<div class="card tc-pv"><div class="card-label">主驱动运行频率</div><div><span class="card-value" id="runFreq">--</span><span class="card-unit">Hz</span></div></div>
+			<div class="card tc-sp"><div class="card-label">主驱动设定频率</div><div><span class="card-value" id="setFreq">--</span><span class="card-unit">Hz</span></div></div>
+			<div class="card mon-pv"><div class="card-label">入炉数</div><div><span class="card-value" id="furnaceCount">--</span></div></div>
+			<div class="card mon-sp"><div class="card-label">风机转速-一区</div><div><span class="card-value" id="fan1">--</span></div></div>
+			<div class="card mon-sp"><div class="card-label">风机转速-二区</div><div><span class="card-value" id="fan2">--</span></div></div>
+		</div>
+	</div>
+
+	<div class="bd-foot">最近更新时间:<span id="dataTime">--</span> | 每 5 秒自动刷新</div>
+
+<script>
+	var CTX = "${ctx}";
+	function pad(n){ return n < 10 ? '0'+n : ''+n; }
+	function updateClock(){
+		var d = new Date();
+		document.getElementById('nowTime').textContent =
+			d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate())+' '+pad(d.getHours())+':'+pad(d.getMinutes())+':'+pad(d.getSeconds());
+	}
+	function setConn(ok){
+		var dot = document.getElementById('connDot'), txt = document.getElementById('connText');
+		if(ok){ dot.className='dot dot-on'; txt.textContent='采集正常'; }
+		else { dot.className='dot dot-off'; txt.textContent='采集异常 / 无数据'; }
+	}
+	function setVal(id, val){
+		var el = document.getElementById(id);
+		if(el) el.textContent = (val===null||val===undefined||val==='') ? '--' : val;
+	}
+	var MAP = {
+		z1TcPv: 'zone1TcPv', z1TcSp: 'zone1TcSp', z1MonPv: 'zone1MonPv', z1MonSp: 'zone1MonSp',
+		z2TcPv: 'zone2TcPv', z2TcSp: 'zone2TcSp', z2MonPv: 'zone2MonPv', z2MonSp: 'zone2MonSp',
+		runFreq: 'mainDriveRunFreq', setFreq: 'mainDriveSetFreq',
+		furnaceCount: 'furnaceCount', fan1: 'fanSpeedZone1', fan2: 'fanSpeedZone2'
+	};
+	function loadData(){
+		$.ajax({
+			url: CTX + '/mes/mesHeatProcess/latestData',
+			type: 'POST',
+			dataType: 'json',
+			success: function(data){
+				if(!data || !data.id){ setConn(false); return; }
+				for(var k in MAP){ setVal(k, data[MAP[k]]); }
+				setVal('dataTime', data.createDate || '--');
+				setConn(true);
+			},
+			error: function(){ setConn(false); }
+		});
+	}
+	$(function(){
+		updateClock();
+		setInterval(updateClock, 1000);
+		loadData();
+		setInterval(loadData, 5000);
+	});
+</script>
+</body>
+</html>

+ 96 - 0
src/main/resources/views/modules/mes/mesHeatProcessForm.html

@@ -0,0 +1,96 @@
+<% layout('/layouts/default.html', {title: '热处理数据管理', libs: ['validate']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-note"></i> ${text(mesHeatProcess.isNewRecord ? '新增热处理数据' : '查看热处理数据')}
+			</div>
+		</div>
+		<#form:form id="inputForm" model="${mesHeatProcess}" action="${ctx}/mes/mesHeatProcess/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('温度(℃)')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('一区温控PV')}:</label>
+						<div class="col-sm-8"><#form:input path="zone1TcPv" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('一区温控SP')}:</label>
+						<div class="col-sm-8"><#form:input path="zone1TcSp" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('二区温控PV')}:</label>
+						<div class="col-sm-8"><#form:input path="zone2TcPv" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('二区温控SP')}:</label>
+						<div class="col-sm-8"><#form:input path="zone2TcSp" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('一区监控PV')}:</label>
+						<div class="col-sm-8"><#form:input path="zone1MonPv" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('一区监控SP')}:</label>
+						<div class="col-sm-8"><#form:input path="zone1MonSp" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('二区监控PV')}:</label>
+						<div class="col-sm-8"><#form:input path="zone2MonPv" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('二区监控SP')}:</label>
+						<div class="col-sm-8"><#form:input path="zone2MonSp" maxlength="64" class="form-control"/></div>
+					</div></div>
+				</div>
+				<div class="form-unit">${text('主驱动 / 产量 / 风机')}</div>
+				<div class="row">
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('主驱动运行频率(Hz)')}:</label>
+						<div class="col-sm-8"><#form:input path="mainDriveRunFreq" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('主驱动设定频率(Hz)')}:</label>
+						<div class="col-sm-8"><#form:input path="mainDriveSetFreq" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('入炉数')}:</label>
+						<div class="col-sm-8"><#form:input path="furnaceCount" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('风机转速-一区')}:</label>
+						<div class="col-sm-8"><#form:input path="fanSpeedZone1" maxlength="64" class="form-control"/></div>
+					</div></div>
+					<div class="col-xs-6"><div class="form-group">
+						<label class="control-label col-sm-4">${text('风机转速-二区')}:</label>
+						<div class="col-sm-8"><#form:input path="fanSpeedZone2" maxlength="64" class="form-control"/></div>
+					</div></div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% if (hasPermi('mes:mesHeatProcess:edit')){ %>
+							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
+						<% } %>
+						<button type="button" class="btn btn-sm btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply-all"></i> ${text('关 闭')}</button>
+					</div>
+				</div>
+			</div>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+$("#inputForm").validate({
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){ contentWindow.page(); });
+			}
+		}, "json");
+	}
+});
+</script>

+ 121 - 0
src/main/resources/views/modules/mes/mesHeatProcessList.html

@@ -0,0 +1,121 @@
+<% layout('/layouts/default.html', {title: '热处理数据管理', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('热处理数据管理')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<a href="${ctx}/mes/mesHeatProcess/board" class="btn btn-info btnTool" data-title="${text('实时数据看板')}" title="${text('实时数据看板')}"><i class="fa fa-dashboard"></i> ${text('实时看板')}</a>
+				<a href="#" class="btn btn-default" id="btnExport"><i class="glyphicon glyphicon-export"></i> 导出</a>
+				<% if(hasPermi('mes:mesHeatProcess:edit')){ %>
+					<a href="#" class="btn btn-default" id="btnImport"><i class="glyphicon glyphicon-import"></i> 导入</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="${mesHeatProcess}" action="${ctx}/mes/mesHeatProcess/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
+				<div class="form-group">
+					<label class="control-label">${text('一区温控PV')}:</label>
+					<div class="control-inline">
+						<#form:input path="zone1TcPv" maxlength="64" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<button type="submit" class="btn btn-primary btn-sm">${text('查询')}</button>
+					<button type="reset" class="btn btn-default btn-sm">${text('重置')}</button>
+				</div>
+			</#form:form>
+			<table id="dataGrid"></table>
+			<div id="dataGridPage"></div>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("采集时间")}', name:'createDate', index:'a.create_date', width:150, align:"center", frozen:true, formatter: function(val, obj, row, act){
+			return '<a href="${ctx}/mes/mesHeatProcess/form?id='+row.id+'" class="btnList" data-title="${text("查看")}">'+(val||row.id)+'</a>';
+		}},
+		{header:'${text("一区温控PV")}', name:'zone1TcPv', index:'a.zone1_tc_pv', width:100, align:"center"},
+		{header:'${text("一区温控SP")}', name:'zone1TcSp', index:'a.zone1_tc_sp', width:100, align:"center"},
+		{header:'${text("二区温控PV")}', name:'zone2TcPv', index:'a.zone2_tc_pv', width:100, align:"center"},
+		{header:'${text("二区温控SP")}', name:'zone2TcSp', index:'a.zone2_tc_sp', width:100, align:"center"},
+		{header:'${text("一区监控PV")}', name:'zone1MonPv', index:'a.zone1_mon_pv', width:100, align:"center"},
+		{header:'${text("一区监控SP")}', name:'zone1MonSp', index:'a.zone1_mon_sp', width:100, align:"center"},
+		{header:'${text("二区监控PV")}', name:'zone2MonPv', index:'a.zone2_mon_pv', width:100, align:"center"},
+		{header:'${text("二区监控SP")}', name:'zone2MonSp', index:'a.zone2_mon_sp', width:100, align:"center"},
+		{header:'${text("主驱动运行频率(Hz)")}', name:'mainDriveRunFreq', index:'a.main_drive_run_freq', width:130, align:"center"},
+		{header:'${text("主驱动设定频率(Hz)")}', name:'mainDriveSetFreq', index:'a.main_drive_set_freq', width:130, align:"center"},
+		{header:'${text("入炉数")}', name:'furnaceCount', index:'a.furnace_count', width:90, align:"center"},
+		{header:'${text("风机转速-一区")}', name:'fanSpeedZone1', index:'a.fan_speed_zone1', width:110, align:"center"},
+		{header:'${text("风机转速-二区")}', name:'fanSpeedZone2', index:'a.fan_speed_zone2', width:110, align:"center"},
+		{header:'${text("操作")}', name:'actions', width:80, formatter: function(val, obj, row, act){
+			var actions = [];
+			//<% if(hasPermi('mes:mesHeatProcess:edit')){ %>
+			actions.push('<a href="${ctx}/mes/mesHeatProcess/delete?id='+row.id+'" class="btnList" title="${text("删除")}" data-confirm="${text("确认要删除该记录吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
+			//<% } %>
+			return actions.join('');
+		}}
+	]
+});
+</script>
+<script>
+$('#btnExport').click(function(){
+	js.ajaxSubmitForm($('#searchForm'), {
+		url:'${ctx}/mes/mesHeatProcess/exportData',
+		downloadFile:true
+	});
+});
+$('#btnImport').click(function(){
+	js.layer.open({
+		type: 1,
+		area: ['400px'],
+		title: '${text("导入热加工数据")}',
+		resize: false,
+		scrollbar: true,
+		content: js.template('importTpl'),
+		btn: ['<i class="fa fa-check"></i> ${text("导入")}',
+			'<i class="fa fa-remove"></i> ${text("关闭")}'],
+		btn1: function(index, layero){
+			var form = {
+				inputForm: layero.find('#inputForm'),
+				file: layero.find('#file').val()
+			};
+			if (form.file == '' || (!js.endWith(form.file, '.xls') && !js.endWith(form.file, '.xlsx'))){
+				js.showMessage("${text('文件不正确,请选择后缀为“xls”或“xlsx”的文件。')}", null, 'warning');
+				return false;
+			}
+			js.ajaxSubmitForm(form.inputForm, function(data){
+				js.showMessage(data.message);
+				if(data.result == Global.TRUE){
+					js.layer.closeAll();
+				}
+				page();
+			}, "json");
+			return true;
+		}
+	});
+});
+</script>
+<script id="importTpl" type="text/template">//<!--
+<form id="inputForm" action="${ctx}/mes/mesHeatProcess/importData" method="post" enctype="multipart/form-data"
+	class="form-horizontal mt20 mb10" style="overflow:auto;max-height:200px;">
+	<div class="row">
+		<div class="col-xs-12 col-xs-offset-1">
+			<input type="file" id="file" name="file" class="form-file"/>
+			<div class="mt10 pt5" style="color:red">
+				${text('提示:仅允许导入“xls”或“xlsx”格式文件!')}
+			</div>
+			<div class="mt10 pt5">
+				<a href="${ctx}/mes/mesHeatProcess/importTemplate" class="btn btn-default btn-xs"><i class="fa fa-file-excel-o"></i> ${text('下载模板')}</a>
+			</div>
+		</div>
+	</div>
+</form>
+//--></script>

+ 169 - 0
src/main/resources/views/modules/mes/mesSmeltingBoard.html

@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<title>熔炼数据实时看板</title>
+	<script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
+	<style>
+		* { margin: 0; padding: 0; box-sizing: border-box; }
+		html, body { width: 100%; height: 100%; }
+		body {
+			font-family: "Microsoft YaHei", sans-serif;
+			background: #04122b;
+			background-image: radial-gradient(circle at 50% 0%, #0a2350 0%, #04122b 60%);
+			color: #d6e6ff;
+			overflow: auto;
+			padding-bottom: 24px;
+		}
+		.board-header {
+			height: 64px; display: flex; align-items: center; justify-content: center;
+			position: relative; border-bottom: 1px solid rgba(0,160,255,0.25);
+		}
+		.board-title { font-size: 24px; letter-spacing: 4px; color: #5ad8ff; font-weight: bold; text-shadow: 0 0 12px rgba(0,180,255,0.6); }
+		.board-time { position: absolute; right: 24px; font-size: 14px; color: #8fb6e6; }
+		.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
+		.dot-on  { background: #25e29a; box-shadow: 0 0 8px #25e29a; }
+		.dot-off { background: #ff5a6a; box-shadow: 0 0 8px #ff5a6a; }
+
+		/* 两炉布局:左右各占一半 */
+		.boards {
+			display: flex; gap: 20px; padding: 24px;
+		}
+		.furnace {
+			flex: 1; min-width: 0;
+			border: 1px solid rgba(0,160,255,0.35);
+			border-radius: 14px;
+			background: linear-gradient(160deg, rgba(10,35,80,0.55), rgba(4,18,43,0.55));
+			padding: 18px 16px 16px;
+		}
+		.furnace-head {
+			display: flex; align-items: center; justify-content: space-between;
+			margin-bottom: 16px; padding: 0 8px;
+		}
+		.furnace-name {
+			font-size: 22px; color: #ffd866; letter-spacing: 4px; font-weight: bold;
+			text-shadow: 0 0 12px rgba(255, 216, 102, 0.5);
+		}
+		.furnace-status { font-size: 13px; color: #9cc4f0; }
+		.cards { display: flex; flex-wrap: wrap; gap: 14px; }
+		.card {
+			flex: 1 1 calc(50% - 14px); min-width: 200px; height: 150px;
+			border-radius: 12px;
+			background: linear-gradient(160deg, rgba(13,45,95,0.9), rgba(8,26,56,0.9));
+			border: 1px solid rgba(0,160,255,0.35);
+			box-shadow: 0 0 24px rgba(0,100,220,0.18) inset;
+			display: flex; flex-direction: column; align-items: center; justify-content: center;
+			position: relative; overflow: hidden;
+		}
+		.card::before {
+			content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
+			background: linear-gradient(90deg, transparent, #2fa8ff, transparent);
+		}
+		.card-label { font-size: 16px; color: #9cc4f0; letter-spacing: 2px; margin-bottom: 14px; }
+		.card-value { font-size: 46px; font-weight: bold; color: #41e6c3; line-height: 1; text-shadow: 0 0 14px rgba(65,230,195,0.5); }
+		.card-unit { font-size: 18px; color: #7fd6ff; margin-left: 6px; }
+		.card-1 .card-value { color: #ffb454; text-shadow: 0 0 14px rgba(255,180,84,0.5); }
+		.card-2 .card-value { color: #41e6c3; }
+		.card-3 .card-value { color: #5ab8ff; }
+		.card-4 .card-value { color: #ff7a9c; text-shadow: 0 0 14px rgba(255,122,156,0.5); }
+		.furnace-foot {
+			text-align: center; color: #6f93c4; font-size: 12px; margin-top: 14px;
+		}
+	</style>
+</head>
+<body>
+	<div class="board-header">
+		<span class="board-title">熔炼数据实时监控看板</span>
+		<span class="board-time" id="nowTime"></span>
+	</div>
+
+	<div class="boards">
+		<!-- 一号炉 -->
+		<div class="furnace" id="furnace-1">
+			<div class="furnace-head">
+				<span class="furnace-name">一号炉</span>
+				<span class="furnace-status"><i id="dot-1" class="dot dot-off"></i><span id="conn-1">未连接</span></span>
+			</div>
+			<div class="cards">
+				<div class="card card-1"><div class="card-label">铝液温度</div><div><span class="card-value" id="ma-1">--</span><span class="card-unit">℃</span></div></div>
+				<div class="card card-2"><div class="card-label">气氛温度</div><div><span class="card-value" id="at-1">--</span><span class="card-unit">℃</span></div></div>
+				<div class="card card-3"><div class="card-label">排气温度</div><div><span class="card-value" id="eg-1">--</span><span class="card-unit">℃</span></div></div>
+				<div class="card card-4"><div class="card-label">熔化温度</div><div><span class="card-value" id="me-1">--</span><span class="card-unit">℃</span></div></div>
+			</div>
+			<div class="furnace-foot">最近更新:<span id="t-1">--</span></div>
+		</div>
+
+		<!-- 二号炉 -->
+		<div class="furnace" id="furnace-2">
+			<div class="furnace-head">
+				<span class="furnace-name">二号炉</span>
+				<span class="furnace-status"><i id="dot-2" class="dot dot-off"></i><span id="conn-2">未连接</span></span>
+			</div>
+			<div class="cards">
+				<div class="card card-1"><div class="card-label">铝液温度</div><div><span class="card-value" id="ma-2">--</span><span class="card-unit">℃</span></div></div>
+				<div class="card card-2"><div class="card-label">气氛温度</div><div><span class="card-value" id="at-2">--</span><span class="card-unit">℃</span></div></div>
+				<div class="card card-3"><div class="card-label">排气温度</div><div><span class="card-value" id="eg-2">--</span><span class="card-unit">℃</span></div></div>
+				<div class="card card-4"><div class="card-label">熔化温度</div><div><span class="card-value" id="me-2">--</span><span class="card-unit">℃</span></div></div>
+			</div>
+			<div class="furnace-foot">最近更新:<span id="t-2">--</span></div>
+		</div>
+	</div>
+
+<script>
+	var CTX = "${ctx}";
+
+	function pad(n){ return n < 10 ? '0'+n : ''+n; }
+	function updateClock(){
+		var d = new Date();
+		document.getElementById('nowTime').textContent =
+			d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate())+' '+pad(d.getHours())+':'+pad(d.getMinutes())+':'+pad(d.getSeconds());
+	}
+	function setConn(suffix, ok){
+		var dot = document.getElementById('dot-' + suffix);
+		var txt = document.getElementById('conn-' + suffix);
+		if (ok) { dot.className='dot dot-on'; txt.textContent='采集正常'; }
+		else    { dot.className='dot dot-off'; txt.textContent='采集异常 / 无数据'; }
+	}
+	function setVal(id, val){
+		var el = document.getElementById(id);
+		if (el) el.textContent = (val===null||val===undefined||val==='') ? '--' : val;
+	}
+
+	// 拉取指定炉号的最新数据
+	function loadFurnace(heatNumber, suffix){
+		$.ajax({
+			url: CTX + '/mes/mesSmelting/latestData',
+			type: 'POST',
+			dataType: 'json',
+			data: { heatNumber: heatNumber },
+			success: function(data){
+				if (!data || !data.id) {
+					setConn(suffix, false);
+					return;
+				}
+				setVal('ma-' + suffix, data.moltenAluminumTemperature);
+				setVal('at-' + suffix, data.atmosphereTemperature);
+				setVal('eg-' + suffix, data.exhaustGasTemperature);
+				setVal('me-' + suffix, data.meltingTemperature);
+				setVal('t-'  + suffix, data.createDate || '--');
+				setConn(suffix, true);
+			},
+			error: function(){ setConn(suffix, false); }
+		});
+	}
+
+	function loadAll(){
+		loadFurnace('一号炉', '1');
+		loadFurnace('二号炉', '2');
+	}
+
+	$(function(){
+		updateClock();
+		setInterval(updateClock, 1000);
+		loadAll();
+		setInterval(loadAll, 5000);
+	});
+</script>
+</body>
+</html>