zhanghe před 4 měsíci
rodič
revize
456f261c16

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

@@ -1,6 +1,9 @@
 package com.jeesite.modules.mes.web;
 
 import java.util.List;
+import java.util.Collections;
+import java.util.Map;
+import java.text.SimpleDateFormat;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -17,6 +20,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 import com.jeesite.common.config.Global;
 import com.jeesite.common.collect.ListUtils;
+import com.jeesite.common.collect.MapUtils;
 import com.jeesite.common.entity.Page;
 import com.jeesite.common.lang.DateUtils;
 import com.jeesite.common.utils.excel.ExcelExport;
@@ -87,6 +91,56 @@ public class MesHeatProcessController extends BaseController {
 		return ListUtils.isEmpty(list) ? new MesHeatProcess() : list.get(0);
 	}
 
+	/**
+	 * 看板炉温曲线 - 取最近 N 条采集记录的温度时序数据
+	 */
+	@RequiresPermissions("mes:mesHeatProcess:view")
+	@RequestMapping(value = "trendData")
+	@ResponseBody
+	public Map<String, Object> trendData(HttpServletRequest request) {
+		// 默认 60 个点;前端可传 size 调整,最大限制 500,避免一次拉太多
+		int size = 60;
+		String sizeParam = request.getParameter("size");
+		if (sizeParam != null && !sizeParam.isEmpty()) {
+			try { size = Integer.parseInt(sizeParam); } catch (Exception ignore) {}
+		}
+		if (size <= 0) size = 60;
+		if (size > 500) size = 500;
+
+		MesHeatProcess query = new MesHeatProcess();
+		query.setPage(new Page<>(1, size));
+		Page<MesHeatProcess> page = mesHeatProcessService.findPage(query);
+		List<MesHeatProcess> list = page.getList();
+		Collections.reverse(list);
+
+		SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss");
+		List<String> labels = new java.util.ArrayList<>();
+		List<Double> z1TcPv = new java.util.ArrayList<>();
+		List<Double> z1MonPv = new java.util.ArrayList<>();
+		List<Double> z2TcPv = new java.util.ArrayList<>();
+		List<Double> z2MonPv = new java.util.ArrayList<>();
+		for (MesHeatProcess p : list) {
+			labels.add(p.getCreateDate() == null ? "" : fmt.format(p.getCreateDate()));
+			z1TcPv.add(parseD(p.getZone1TcPv()));
+			z1MonPv.add(parseD(p.getZone1MonPv()));
+			z2TcPv.add(parseD(p.getZone2TcPv()));
+			z2MonPv.add(parseD(p.getZone2MonPv()));
+		}
+		Map<String, Object> ret = MapUtils.newHashMap();
+		ret.put("labels", labels);
+		ret.put("zone1TcPv", z1TcPv);
+		ret.put("zone1MonPv", z1MonPv);
+		ret.put("zone2TcPv", z2TcPv);
+		ret.put("zone2MonPv", z2MonPv);
+		return ret;
+	}
+
+	/** 把字符串转 Double,空/异常返回 null(前端会按 null 跳点处理) */
+	private Double parseD(String v) {
+		if (v == null || v.isEmpty()) return null;
+		try { return Double.parseDouble(v); } catch (Exception e) { return null; }
+	}
+
 	@RequiresPermissions("mes:mesHeatProcess:edit")
 	@PostMapping(value = "save")
 	@ResponseBody

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

@@ -55,6 +55,19 @@
 	</div>
 
 	<div class="section">
+		<div class="section-title">炉温曲线(最近 60 个采集点)</div>
+		<div style="position:relative; height:340px; border:1px solid rgba(0,160,255,0.25); border-radius:10px; padding:14px 16px 8px; background:rgba(8,26,56,0.4);">
+			<div style="margin-bottom:6px; font-size:12px; color:#9cc4f0;">
+				<span style="margin-right:18px;"><i style="display:inline-block;width:14px;height:3px;background:#ffb454;vertical-align:middle;margin-right:4px;"></i>一区温控PV</span>
+				<span style="margin-right:18px;"><i style="display:inline-block;width:14px;height:3px;background:#5ab8ff;vertical-align:middle;margin-right:4px;"></i>二区温控PV</span>
+				<span style="margin-right:18px;"><i style="display:inline-block;width:14px;height:3px;background:#41e6c3;vertical-align:middle;margin-right:4px;"></i>一区监控PV</span>
+				<span><i style="display:inline-block;width:14px;height:3px;background:#ff7a9c;vertical-align:middle;margin-right:4px;"></i>二区监控PV</span>
+			</div>
+			<canvas id="trendChart" style="width:100%; height:300px; display:block;"></canvas>
+		</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="z1TcPv">--</span><span class="card-unit">℃</span></div></div>
@@ -124,11 +137,121 @@
 			error: function(){ setConn(false); }
 		});
 	}
+
+	// ===== 炉温曲线 =====
+	var trendCache = null;
+	function loadTrend(){
+		$.ajax({
+			url: CTX + '/mes/mesHeatProcess/trendData',
+			type: 'POST',
+			dataType: 'json',
+			data: { size: 60 },
+			success: function(data){
+				if(!data) return;
+				trendCache = data;
+				drawTrend();
+			}
+		});
+	}
+
+	function drawTrend(){
+		var c = document.getElementById('trendChart');
+		if(!c || !trendCache) return;
+		// 适配 DPR + 自适应宽度
+		var dpr = window.devicePixelRatio || 1;
+		var w = c.clientWidth, h = c.clientHeight;
+		c.width = w * dpr; c.height = h * dpr;
+		var ctx = c.getContext('2d');
+		ctx.setTransform(dpr,0,0,dpr,0,0);
+		ctx.clearRect(0,0,w,h);
+
+		var labels = trendCache.labels || [];
+		var series = [
+			{ name:'一区温控PV', color:'#ffb454', data: trendCache.zone1TcPv || [] },
+			{ name:'二区温控PV', color:'#5ab8ff', data: trendCache.zone2TcPv || [] },
+			{ name:'一区监控PV', color:'#41e6c3', data: trendCache.zone1MonPv || [] },
+			{ name:'二区监控PV', color:'#ff7a9c', data: trendCache.zone2MonPv || [] }
+		];
+		var n = labels.length;
+		if(n < 2){
+			ctx.fillStyle = '#6f93c4'; ctx.font='14px Microsoft YaHei';
+			ctx.textAlign = 'center';
+			ctx.fillText('数据不足,等待采集...', w/2, h/2);
+			return;
+		}
+
+		// 计算 Y 轴范围
+		var minV = Number.POSITIVE_INFINITY, maxV = Number.NEGATIVE_INFINITY;
+		for(var i=0;i<series.length;i++){
+			for(var j=0;j<series[i].data.length;j++){
+				var v = series[i].data[j];
+				if(v == null) continue;
+				if(v < minV) minV = v;
+				if(v > maxV) maxV = v;
+			}
+		}
+		if(!isFinite(minV) || !isFinite(maxV)){ minV = 0; maxV = 100; }
+		if(maxV === minV){ maxV = minV + 10; }
+		// 上下留一点空间
+		var range = maxV - minV;
+		minV = Math.floor(minV - range * 0.1);
+		maxV = Math.ceil(maxV + range * 0.1);
+
+		var padL = 50, padR = 14, padT = 10, padB = 28;
+		var cw = w - padL - padR, ch = h - padT - padB;
+
+		// 网格 + Y 轴
+		ctx.strokeStyle = 'rgba(0,143,253,0.18)';
+		ctx.fillStyle = 'rgba(120,180,230,0.85)';
+		ctx.font = '11px Microsoft YaHei';
+		ctx.textAlign = 'right';
+		ctx.lineWidth = 1;
+		for(var g=0; g<=4; g++){
+			var y = padT + ch * g / 4;
+			var val = Math.round(maxV - (maxV - minV) * g / 4);
+			ctx.beginPath();
+			ctx.moveTo(padL, y); ctx.lineTo(padL + cw, y);
+			ctx.stroke();
+			ctx.fillText(val, padL - 5, y + 4);
+		}
+
+		// X 轴标签:等距挑 6 个
+		ctx.textAlign = 'center';
+		var xCount = Math.min(6, n);
+		for(var t=0; t<xCount; t++){
+			var idx = Math.round((n - 1) * t / (xCount - 1));
+			var x = padL + (cw * idx) / (n - 1);
+			ctx.fillText(labels[idx] || '', x, padT + ch + 18);
+		}
+
+		// 折线
+		for(var s=0; s<series.length; s++){
+			var sr = series[s];
+			ctx.strokeStyle = sr.color;
+			ctx.lineWidth = 2;
+			ctx.beginPath();
+			var started = false;
+			for(var k=0; k<n; k++){
+				var v = sr.data[k];
+				if(v == null){ started = false; continue; }
+				var x = padL + (cw * k) / (n - 1);
+				var y = padT + ch - ((v - minV) / (maxV - minV)) * ch;
+				if(!started){ ctx.moveTo(x,y); started = true; }
+				else { ctx.lineTo(x,y); }
+			}
+			ctx.stroke();
+		}
+	}
+
+	// 窗口尺寸变化重绘
+	window.addEventListener('resize', drawTrend);
 	$(function(){
 		updateClock();
 		setInterval(updateClock, 1000);
 		loadData();
+		loadTrend();
 		setInterval(loadData, 5000);
+		setInterval(loadTrend, 5000);
 	});
 </script>
 </body>