zhanghe hace 1 día
padre
commit
19a7a6a057

+ 19 - 1
src/main/java/com/jeesite/modules/mes/entity/MesLineProcess.java

@@ -67,6 +67,8 @@ public class MesLineProcess extends DataEntity<MesLineProcess> {
 	private String pid;
 	private Integer shiftCount;
 	private Integer dayCount;
+	private Integer shiftOkCount;
+	private Integer shiftNgCount;
 	private Integer shift5305;
 	private Integer day5305;
 	private Integer shift5308;
@@ -212,6 +214,22 @@ public class MesLineProcess extends DataEntity<MesLineProcess> {
 		this.dayCount = dayCount;
 	}
 
+	public Integer getShiftOkCount() {
+		return shiftOkCount;
+	}
+
+	public void setShiftOkCount(Integer shiftOkCount) {
+		this.shiftOkCount = shiftOkCount;
+	}
+
+	public Integer getShiftNgCount() {
+		return shiftNgCount;
+	}
+
+	public void setShiftNgCount(Integer shiftNgCount) {
+		this.shiftNgCount = shiftNgCount;
+	}
+
 	public Integer getShift5305() {
 		return shift5305;
 	}
@@ -363,4 +381,4 @@ public class MesLineProcess extends DataEntity<MesLineProcess> {
 	public void setMesLineProcessUserList(List<MesLineProcessUser> mesLineProcessUserList) {
 		this.mesLineProcessUserList = mesLineProcessUserList;
 	}
-}
+}

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

@@ -201,7 +201,7 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 		mesProductRecord.setLineSn(cate);
 		return mesProductRecordDao.findInfo(mesProductRecord);
 	}
-	
+
 	/**
 	 * 查询分页数据
 	 * @param mesProductRecord 查询条件
@@ -301,7 +301,7 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 			mesProductService.save(mesProduct);
 		}
 	}
-	
+
 	/**
 	 * 保存数据(插入或更新) 生产订单,仅生成模板
 	 * @param mesProductRecord
@@ -1001,7 +1001,7 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 
 
 	}
-	
+
 	/**
 	 * 更新状态
 	 * @param mesProductRecord
@@ -1011,7 +1011,7 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 	public void updateStatus(MesProductRecord mesProductRecord) {
 		super.updateStatus(mesProductRecord);
 	}
-	
+
 	/**
 	 * 删除数据
 	 * @param mesProductRecord
@@ -1195,4 +1195,4 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 	public void updateResultByCheckFirst(MesProductRecord mesProductRecord){
 		mesProductRecordDao.updateResultByCheckFirst(mesProductRecord);
 	}
-}
+}

+ 213 - 65
src/main/java/com/jeesite/modules/mes/web/MesProductController.java

@@ -319,91 +319,239 @@ public class MesProductController extends BaseController {
 	@ResponseBody
 	public CommonResp screenData() {
 		CommonResp<List<MesLineProcess>> resp = new CommonResp<>();
-		// 获取当前日期
+		resp.setData(getScreenProcessList());
+		resp.setResult(Global.TRUE);
+		return resp;
+	}
+
+	@RequestMapping(value = "screenDashboardData")
+	@ResponseBody
+	public CommonResp screenDashboardData() {
+		CommonResp<Map<String, Object>> resp = new CommonResp<>();
+		List<MesLineProcess> lists = getScreenProcessList();
+		resp.setData(buildScreenDashboardData(lists));
+		resp.setResult(Global.TRUE);
+		return resp;
+	}
+
+	private List<MesLineProcess> getScreenProcessList() {
 		LocalDate today = LocalDate.now();
-		// 获取明天的日期
 		LocalDate tomorrow = today.plusDays(1);
-		// 定义日期格式
 		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-		// 格式化并打印今天的日期
 		String formattedToday = today.format(formatter);
-		// 格式化并打印明天的日期
 		String formattedTomorrow = tomorrow.format(formatter);
-
-		String sdate1 = formattedToday+" 08:00:00";
-		String edate1 = formattedTomorrow+" 08:00:00";
-
+		String sdate1 = formattedToday + " 08:00:00";
+		String edate1 = formattedTomorrow + " 08:00:00";
 		MesShiftDataResp mesShiftDataResp = mesShiftService.getCurShift();
+		List<MesProductRecord> shiftRecords = ListUtils.newArrayList();
+		List<MesProductRecord> dayRecords = getDashboardProductRecords(sdate1, edate1);
 
-		MesLineProcess mesLineProcess = new MesLineProcess();
-		mesLineProcess.setStatus("0");
-		mesLineProcess.getSqlMap().getWhere().and("pid",QueryType.IS_NULL,"");
-		List<MesLineProcess> lists = mesLineProcessService.findList(mesLineProcess);
-		for(MesLineProcess mesLineProcess2 : lists){
-			if(ObjectUtils.isEmpty(mesShiftDataResp)){
-				mesLineProcess2.setShiftCount(0);
-				mesLineProcess2.setShift5305(0);
-				mesLineProcess2.setShift5308(0);
-				mesLineProcess2.setShift8301(0);
-				mesLineProcess2.setShift8302(0);
-				mesLineProcess2.setShift7933(0);
-				mesLineProcess2.setShift7939(0);
-				mesLineProcess2.setShiftyn(0);
-			}else{
-				String shiftOks = mesProductRecordService.getSjOk(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd());
-				mesLineProcess2.setShiftCount(Integer.valueOf(shiftOks));
-
-				String shiftOks5305 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd(),"000020015305-");
-				mesLineProcess2.setShift5305(Integer.valueOf(shiftOks5305));
-
-				String shiftOks5308 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd(),"000020015308-");
-				mesLineProcess2.setShift5308(Integer.valueOf(shiftOks5308));
-
-				String shiftOks8301 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd(),"000020018301-");
-				mesLineProcess2.setShift8301(Integer.valueOf(shiftOks8301));
-
-				String shiftOks8302 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd(),"000020018302-");
-				mesLineProcess2.setShift8302(Integer.valueOf(shiftOks8302));
+		if (!ObjectUtils.isEmpty(mesShiftDataResp)) {
+			shiftRecords = getDashboardProductRecords(mesShiftDataResp.getStart(), mesShiftDataResp.getEnd());
+		}
 
-				String shiftOks7939 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd(),"000020017939-");
-				mesLineProcess2.setShift7939(Integer.valueOf(shiftOks7939));
+		if (ListUtils.isEmpty(shiftRecords) && ListUtils.isEmpty(dayRecords)) {
+			MesProductRecord latestRecord = getLatestDashboardRecord();
+			if (!ObjectUtils.isEmpty(latestRecord) && latestRecord.getUpdateDate() != null) {
+				Map<String, String> rangeMap = getBusinessDayRange(latestRecord.getUpdateDate());
+				dayRecords = getDashboardProductRecords(rangeMap.get("start"), rangeMap.get("end"));
+				shiftRecords = dayRecords;
+			}
+		}
 
-				String shiftOks7933 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd(),"000020017933-");
-				mesLineProcess2.setShift7933(Integer.valueOf(shiftOks7933));
+		Map<String, MesLineProcess> processMap = new LinkedHashMap<>();
+		Map<String, String> titleCache = new HashMap<>();
+		if (!ListUtils.isEmpty(shiftRecords)) {
+			fillScreenProcessCount(processMap, shiftRecords, true, titleCache);
+		}
+		if (!ListUtils.isEmpty(dayRecords)) {
+			fillScreenProcessCount(processMap, dayRecords, false, titleCache);
+		}
 
-				String shiftOksYn = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),mesShiftDataResp.getStart(),mesShiftDataResp.getEnd(),"2520200FF3");
-				mesLineProcess2.setShiftyn(Integer.valueOf(shiftOksYn));
+		List<MesLineProcess> lists = ListUtils.newArrayList();
+		lists.addAll(processMap.values());
+		Collections.sort(lists, new Comparator<MesLineProcess>() {
+			@Override
+			public int compare(MesLineProcess o1, MesLineProcess o2) {
+				return extractOprnoSortValue(o1.getOprno()).compareTo(extractOprnoSortValue(o2.getOprno()));
 			}
+		});
+		return lists;
+	}
 
+	private List<MesProductRecord> getDashboardProductRecords(String start, String end) {
+		MesProductRecord mesProductRecord = new MesProductRecord();
+		mesProductRecord.setCraft("100000");
+		mesProductRecord.getSqlMap().getWhere()
+				.and("a.content", QueryType.NE, "UDF")
+				.and("a.update_date", QueryType.GTE, start)
+				.and("a.update_date", QueryType.LT, end);
+		return mesProductRecordService.findList(mesProductRecord);
+	}
 
-			String dayOks = mesProductRecordService.getSjOk(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1);
-			mesLineProcess2.setDayCount(Integer.valueOf(dayOks));
-
-			String dayOks5305 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1,"000020015305-");
-			mesLineProcess2.setDay5305(Integer.valueOf(dayOks5305));
+	private MesProductRecord getLatestDashboardRecord() {
+		MesProductRecord mesProductRecord = new MesProductRecord();
+		mesProductRecord.setCraft("100000");
+		mesProductRecord.getSqlMap().getWhere().and("a.content", QueryType.NE, "UDF");
+		mesProductRecord.getSqlMap().getOrder().setOrderBy("a.update_date desc limit 1");
+		List<MesProductRecord> records = mesProductRecordService.findList(mesProductRecord);
+		return ListUtils.isEmpty(records) ? null : records.get(0);
+	}
 
-			String dayOks5308 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1,"000020015308-");
-			mesLineProcess2.setDay5308(Integer.valueOf(dayOks5308));
+	private Map<String, String> getBusinessDayRange(Date date) {
+		Calendar startCalendar = Calendar.getInstance();
+		startCalendar.setTime(date);
+		if (startCalendar.get(Calendar.HOUR_OF_DAY) < 8) {
+			startCalendar.add(Calendar.DATE, -1);
+		}
+		startCalendar.set(Calendar.HOUR_OF_DAY, 8);
+		startCalendar.set(Calendar.MINUTE, 0);
+		startCalendar.set(Calendar.SECOND, 0);
+		startCalendar.set(Calendar.MILLISECOND, 0);
+
+		Calendar endCalendar = Calendar.getInstance();
+		endCalendar.setTime(startCalendar.getTime());
+		endCalendar.add(Calendar.DATE, 1);
+
+		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		Map<String, String> rangeMap = MapUtils.newHashMap();
+		rangeMap.put("start", simpleDateFormat.format(startCalendar.getTime()));
+		rangeMap.put("end", simpleDateFormat.format(endCalendar.getTime()));
+		return rangeMap;
+	}
 
-			String dayOks8301 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1,"000020018301-");
-			mesLineProcess2.setDay8301(Integer.valueOf(dayOks8301));
+	private void fillScreenProcessCount(Map<String, MesLineProcess> processMap, List<MesProductRecord> records, boolean shiftFlag, Map<String, String> titleCache) {
+		for (MesProductRecord record : records) {
+			String oprno = CommonUitl.formatOprno(record.getOprno());
+			if (StringUtils.isEmpty(oprno)) {
+				continue;
+			}
+			MesLineProcess process = processMap.get(oprno);
+			if (process == null) {
+				process = new MesLineProcess();
+				process.setOprno(oprno);
+				process.setTitle(resolveScreenTitle(oprno, record, titleCache));
+				process.setShiftCount(0);
+				process.setDayCount(0);
+				process.setShiftOkCount(0);
+				process.setShiftNgCount(0);
+				processMap.put(oprno, process);
+			}
+			if (shiftFlag) {
+				process.setShiftCount(process.getShiftCount() + 1);
+				if ("OK".equals(record.getContent())) {
+					process.setShiftOkCount(process.getShiftOkCount() + 1);
+				} else if ("NG".equals(record.getContent())) {
+					process.setShiftNgCount(process.getShiftNgCount() + 1);
+				}
+			} else {
+				process.setDayCount(process.getDayCount() + 1);
+			}
+		}
+	}
 
-			String dayOks8302 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1,"000020018302-");
-			mesLineProcess2.setDay8302(Integer.valueOf(dayOks8302));
+	private String resolveScreenTitle(String oprno, MesProductRecord record, Map<String, String> titleCache) {
+		String lineSn = record.getLineSn() == null ? "" : record.getLineSn();
+		String cacheKey = oprno + "_" + lineSn;
+		if (titleCache.containsKey(cacheKey)) {
+			return titleCache.get(cacheKey);
+		}
+		String title = "";
+		if (!StringUtils.isEmpty(lineSn)) {
+			MesLineProcess mesLineProcess = mesLineProcessService.findInfoByOprnoLineSn(oprno, lineSn);
+			if (!ObjectUtils.isEmpty(mesLineProcess)) {
+				title = mesLineProcess.getTitle();
+			}
+		}
+		if (StringUtils.isEmpty(title) && !StringUtils.isEmpty(record.getTid())) {
+			MesTemplateItems mesTemplateItems = mesTemplateItemsService.get(record.getTid());
+			if (!ObjectUtils.isEmpty(mesTemplateItems)) {
+				title = mesTemplateItems.getTitle();
+			}
+		}
+		if (StringUtils.isEmpty(title)) {
+			title = oprno;
+		}
+		titleCache.put(cacheKey, title);
+		return title;
+	}
 
-			String dayOks7939 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1,"000020017939-");
-			mesLineProcess2.setDay7939(Integer.valueOf(dayOks7939));
+	private Integer extractOprnoSortValue(String oprno) {
+		if (StringUtils.isEmpty(oprno)) {
+			return Integer.MAX_VALUE;
+		}
+		String value = oprno.replaceAll("[^0-9]", "");
+		if (StringUtils.isEmpty(value)) {
+			return Integer.MAX_VALUE;
+		}
+		return Integer.valueOf(value);
+	}
 
-			String dayOks7933 = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1,"000020017933-");
-			mesLineProcess2.setDay7933(Integer.valueOf(dayOks7933));
+	private Map<String, Object> buildScreenDashboardData(List<MesLineProcess> lists) {
+		Map<String, Object> data = MapUtils.newHashMap();
+		Map<String, Object> overview = MapUtils.newHashMap();
+		List<Map<String, Object>> detailList = ListUtils.newArrayList();
+		List<Map<String, Object>> topList = ListUtils.newArrayList();
+		DecimalFormat decimalFormat = new DecimalFormat("0.0");
+		int totalShift = 0;
+		int totalDay = 0;
+		int totalShiftOk = 0;
+		int totalShiftNg = 0;
+
+		for (MesLineProcess item : lists) {
+			int shiftCount = item.getShiftCount() == null ? 0 : item.getShiftCount();
+			int dayCount = item.getDayCount() == null ? 0 : item.getDayCount();
+			int shiftOkCount = item.getShiftOkCount() == null ? 0 : item.getShiftOkCount();
+			int shiftNgCount = item.getShiftNgCount() == null ? 0 : item.getShiftNgCount();
+			int shiftQualityBase = shiftOkCount + shiftNgCount;
+			double qualityRate = shiftQualityBase > 0 ? shiftOkCount * 100D / shiftQualityBase : 0D;
+			totalShift += shiftCount;
+			totalDay += dayCount;
+			totalShiftOk += shiftOkCount;
+			totalShiftNg += shiftNgCount;
+
+			Map<String, Object> detail = MapUtils.newHashMap();
+			detail.put("oprno", item.getOprno());
+			detail.put("title", item.getTitle());
+			detail.put("shiftCount", shiftCount);
+			detail.put("dayCount", dayCount);
+			detail.put("shiftOkCount", shiftOkCount);
+			detail.put("shiftNgCount", shiftNgCount);
+			detail.put("qualityRate", Double.valueOf(decimalFormat.format(qualityRate)));
+			detailList.add(detail);
+
+			Map<String, Object> topItem = MapUtils.newHashMap();
+			topItem.put("oprno", item.getOprno());
+			topItem.put("title", item.getTitle());
+			topItem.put("dayCount", dayCount);
+			topList.add(topItem);
+		}
 
-			String dayOksYn = mesProductRecordService.getSjOkType(mesLineProcess2.getOprno(),mesLineProcess2.getLineSn(),sdate1,edate1,"2520200FF3");
-			mesLineProcess2.setDayyn(Integer.valueOf(dayOksYn));
+		Collections.sort(topList, new Comparator<Map<String, Object>>() {
+			@Override
+			public int compare(Map<String, Object> o1, Map<String, Object> o2) {
+				Integer count1 = Integer.valueOf(String.valueOf(o1.get("dayCount")));
+				Integer count2 = Integer.valueOf(String.valueOf(o2.get("dayCount")));
+				return count2.compareTo(count1);
+			}
+		});
+		if (topList.size() > 6) {
+			topList = topList.subList(0, 6);
 		}
 
-		resp.setData(lists);
-		resp.setResult(Global.TRUE);
-		return resp;
+		int totalQualityBase = totalShiftOk + totalShiftNg;
+		double totalRate = totalQualityBase > 0 ? totalShiftOk * 100D / totalQualityBase : 0D;
+		overview.put("count", lists.size());
+		overview.put("totalShift", totalShift);
+		overview.put("totalDay", totalDay);
+		overview.put("shiftOkCount", totalShiftOk);
+		overview.put("shiftNgCount", totalShiftNg);
+		overview.put("qualityRate", Double.valueOf(decimalFormat.format(totalRate)));
+
+		data.put("overview", overview);
+		data.put("list", detailList);
+		data.put("topList", topList);
+		return data;
 	}
 
 	@RequestMapping(value = "indexTj")
@@ -567,4 +715,4 @@ public class MesProductController extends BaseController {
 		resp.setData(map);
 		return resp;
 	}
-}
+}

+ 15 - 12
src/main/resources/config/application.yml

@@ -55,12 +55,12 @@ jdbc:
   type: mysql
   driver: com.mysql.cj.jdbc.Driver
 #  url: jdbc:mysql://127.0.0.1:3306/mescloud?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
-#  url: jdbc:mysql://192.168.21.99:3306/mescloud?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
-  url: jdbc:mysql://127.0.0.1:3306/mes_cloud_x13?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
-#  username: mescloud
-#  password: 8neywEN86NLam3ts
-  username: root
-  password: root
+  url: jdbc:mysql://192.168.21.99:3306/mescloud?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+#  url: jdbc:mysql://127.0.0.1:3390/mes-x13?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+  username: mescloud
+  password: 8neywEN86NLam3ts
+#  username: root
+#  password: root
 #
   testSql: SELECT 1
 
@@ -622,6 +622,8 @@ shiro:
     ${adminPath}/mes/mesProductRecord/device = anon
     ${adminPath}/mes/mesProduct/screen = anon
     ${adminPath}/mes/mesProduct/screenData = anon
+    ${adminPath}/mes/mesProduct/screenDashboardData = anon
+    ${adminPath}/mes/mesProduct/alarmData = anon
     ${adminPath}/mes/mesProductCcd/testDate = anon
     ${adminPath}/mes/mesProductCcd/add = anon
     ${adminPath}/mes/mesProductRecord/currentProduct = anon
@@ -669,6 +671,7 @@ shiro:
     ${adminPath}/mes/mesProcessAlarm/add = anon
     ${adminPath}/mes/mesProductRecord/checkDJ = anon
     ${adminPath}/mes/mesGp12Record/upload = anon
+    ${adminPath}/mes/mes/mesProduct/screenDashboardData = anon
     ${adminPath}/** = user
 
 
@@ -930,14 +933,14 @@ file:
 
 # 视频转码
 #video:
-#  
+#
 #  # 视频格式转换  ffmpeg.exe 所放的路径
 #  ffmpegFile: d:/tools/video/ffmpeg-4.9/bin/ffmpeg.exe
 #  #ffmpegFile: d:/tools/video/libav-10.6-win64/bin/avconv.exe
-#  
+#
 #  # 视频格式转换  mencoder.exe 所放的路径
 #  mencoderFile: d:/tools/video/mencoder-4.9/mencoder.exe
-#  
+#
 #  # 将mp4视频的元数据信息转到视频第一帧
 #  qtFaststartFile: d:/tools/video/qt-faststart/qt-faststart.exe
 
@@ -964,10 +967,10 @@ msg:
 #      corePoolSize: 5
 #      maxPoolSize: 20
 #      keepAliveSeconds: 60
-#  
+#
 #  # 推送失败次数,如果推送次数超过了设定次数,仍不成功,则放弃并保存到历史
 #  pushFailNumber: 3
-#  
+#
 #  # 邮件发送参数
 #  email:
 #    beanName: emailSendService
@@ -976,7 +979,7 @@ msg:
 #    fromHostName: smtp.163.com
 #    sslOnConnect: false
 #    sslSmtpPort: 994
-#  
+#
 #  # 短信网关
 #  sms:
 #    beanName: smsSendService

+ 532 - 173
src/main/resources/views/modules/mes/mesScreen2.html

@@ -8,120 +8,226 @@
 	<link rel="stylesheet" href="${ctxStatic}/bootstrap/css/bootstrap.min.css">
 	<link rel="stylesheet" href="${ctxStatic}/screen/style.css">
 	<style>
-		.oprnobox{
+		.dashboard{
 			position: absolute;
-			/*width: 930px;*/
-			height: 688px;
-			z-index: 100;
-		}
-		.oprnobox.box1{
-			top: 171px;
+			top: 132px;
 			left: 25px;
 			right: 25px;
+			bottom: 110px;
+			z-index: 100;
 		}
-		.oprnobox.box2{
-			top: 171px;
-			right: 25px;
+		.overview-row{
+			display: flex;
+			gap: 18px;
+			height: 126px;
+			margin-bottom: 18px;
 		}
-		.oprnobox-header{
+		.overview-card{
+			position: relative;
+			flex: 1;
+			padding: 24px 28px;
+			background: linear-gradient(135deg, rgba(17, 46, 109, 0.88), rgba(7, 22, 54, 0.78));
+			border: 1px solid rgba(64, 173, 255, 0.35);
+			box-shadow: inset 0 0 26px rgba(56, 160, 255, 0.12), 0 10px 24px rgba(0, 10, 35, 0.4);
+			overflow: hidden;
+		}
+		.overview-card:before{
+			content: "";
+			position: absolute;
+			top: 0;
+			left: 0;
 			width: 100%;
+			height: 3px;
+			background: linear-gradient(90deg, rgba(55, 180, 255, 0.1), rgba(55, 180, 255, 0.9), rgba(55, 180, 255, 0.1));
+		}
+		.overview-label{
+			font-size: 18px;
+			color: #79CFFF;
+			letter-spacing: 2px;
+		}
+		.overview-value{
+			margin-top: 16px;
+			font-size: 46px;
+			line-height: 1;
+			font-weight: bold;
+			color: #FFFFFF;
+			font-family: FZChaoCuHei-M10S;
+		}
+		.overview-sub{
+			margin-top: 14px;
+			font-size: 16px;
+			color: rgba(196, 231, 255, 0.72);
+		}
+		.dashboard-main{
+			display: flex;
+			gap: 18px;
+			height: calc(100% - 144px);
+		}
+		.panel-box{
+			background: linear-gradient(180deg, rgba(10, 28, 70, 0.92), rgba(5, 15, 38, 0.88));
+			border: 1px solid rgba(64, 173, 255, 0.28);
+			box-shadow: inset 0 0 32px rgba(60, 143, 255, 0.12), 0 12px 30px rgba(0, 0, 0, 0.22);
+		}
+		.table-panel{
+			flex: 0 0 1210px;
+			padding: 22px 22px 18px;
+		}
+		.side-panel{
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			gap: 18px;
+		}
+		.panel-title{
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			height: 42px;
+			line-height: 42px;
+			margin-bottom: 16px;
+			padding-left: 16px;
+			padding-right: 16px;
+			background: linear-gradient(90deg, rgba(24, 78, 166, 0.58), rgba(24, 78, 166, 0.08));
+			color: #7ED4FF;
+			font-size: 22px;
+			font-weight: bold;
+		}
+		.panel-title span{
+			font-size: 16px;
+			font-weight: normal;
+			color: rgba(182, 227, 255, 0.75);
+		}
+		.oprnobox-header{
+			display: flex;
+			align-items: center;
 			height: 58px;
-			line-height: 58px;
-			overflow: hidden;
-			background-color: rgba(20, 55, 120, 0.8);
+			padding: 0 12px;
+			background: rgba(20, 55, 120, 0.85);
 			font-size: 20px;
-			color: #008FFD;
+			color: #3DB3FF;
 			font-weight: bold;
 			text-align: center;
+			border: 1px solid rgba(73, 163, 255, 0.2);
 		}
-		.oprnobox-header .oprno-num{
-			width: 178px;
-			/*background-color: #00F7DE;*/
-			float: left;
+		.oprnobox-header > div{
+			flex-shrink: 0;
 		}
-		.oprnobox-header .oprno-title{
-			width: 397px;
-			/*background-color: red;*/
-			float: left;
+		.oprno-num{
+			width: 150px;
 		}
-		.oprnobox-header .oprno-classes{
-			width: 180px;
-			/*background-color: green;*/
-			float: left;
+		.oprno-title{
+			width: 330px;
 		}
-		.oprnobox-header .oprno-day{
-			width: 180px;
-			/*background-color: salmon;*/
-			float: left;
+		.oprno-shift{
+			width: 170px;
 		}
-
-		.oprno-val{
-			width: 161px;
-			float: left;
+		.oprno-day{
+			width: 170px;
+		}
+		.oprno-rate{
+			flex: 1;
+			min-width: 0;
+		}
+		#numbox{
+			margin-top: 14px;
 		}
-		
 		.oprnobox-list{
-			width: 100%;
-			height: 80px;
-			line-height: 80px;
-			overflow: hidden;
-			font-size: 36px;
+			display: flex;
+			align-items: center;
+			height: 68px;
+			padding: 0 12px;
+			margin-bottom: 12px;
+			background: linear-gradient(90deg, rgba(14, 49, 108, 0.86), rgba(8, 25, 59, 0.78));
+			border: 1px solid rgba(62, 163, 255, 0.18);
+			box-shadow: inset 0 0 18px rgba(56, 160, 255, 0.08);
+			font-size: 28px;
 			text-align: center;
 			font-family: FZChaoCuHei-M10S;
 			font-weight: 400;
 			color: #FFFFFF;
 		}
+		.oprnobox-list:nth-child(even){
+			background: linear-gradient(90deg, rgba(11, 41, 92, 0.92), rgba(6, 22, 51, 0.82));
+		}
 		.oprnobox-list .oprno-num{
-			background-image: url("${ctxStatic}/screen/imgs/lbg.png");
-			background-size: 100% 100%;
-			background-repeat: no-repeat;
-			float: left;
-			width: 178px;
-			height: 80px;
-		}
-		.oprnobox-list .oprno-right{
+			font-size: 24px;
+			color: #6CD6FF;
+		}
+		.oprnobox-list .oprno-title{
+			padding: 0 12px;
+			font-size: 24px;
+			text-align: left;
+			white-space: nowrap;
+			overflow: hidden;
+			text-overflow: ellipsis;
+		}
+		.oprnobox-list .oprno-shift,
+		.oprnobox-list .oprno-day{
+			font-size: 28px;
+			color: #FFFFFF;
+		}
+		.rate-box{
+			display: flex;
+			align-items: center;
+			gap: 14px;
+			padding-right: 8px;
+		}
+		.rate-track{
 			position: relative;
+			flex: 1;
+			height: 14px;
+			border-radius: 10px;
+			background: rgba(255, 255, 255, 0.08);
 			overflow: hidden;
-			background-image: url("${ctxStatic}/screen/imgs/rbg.png");
-			background-size: 100% 100%;
-			background-repeat: no-repeat;
-			float: right;
-			/*width: 752px;*/
-			width: 1692px;
-			height: 80px;
-		}
-		.oprnobox-list .oprno-right .oprno-title{
-			width: 392px;
-			height: 80px;
-			float: left;
-		}
-		.oprnobox-list .oprno-right .oprno-classes{
-			width: 180px;
-			height: 80px;
-			float: left;
-		}
-		.oprnobox-list .oprno-right .oprno-day{
-			width: 180px;
-			float: left;
-			height: 80px;
 		}
-
+		.rate-bar{
+			height: 100%;
+			border-radius: 10px;
+			background: linear-gradient(90deg, #1ED4FF, #2FF4A6);
+			box-shadow: 0 0 12px rgba(47, 244, 166, 0.35);
+		}
+		.rate-text{
+			width: 76px;
+			font-size: 22px;
+			color: #8AE4FF;
+		}
+		.chart-panel{
+			flex: 1;
+			padding: 18px 18px 10px;
+		}
+		.chart-box{
+			height: calc(100% - 58px);
+		}
+		.empty-box{
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			height: calc(100% - 58px);
+			font-size: 28px;
+			color: rgba(185, 223, 255, 0.6);
+			letter-spacing: 4px;
+		}
 		.dangerbox{
 			position: absolute;
 			z-index: 100000;
 			bottom: 25px;
 			left: 25px;
-			width:1880px;
-			height:70px;
-			line-height: 70px;
+			width: 1870px;
+			height: 64px;
+			line-height: 64px;
+			padding: 0 20px;
 			text-align: center;
-			background: rgba(255,0,0,0.2);
-			border: 1px solid #FF0000;
-			color: #D13D3E;
+			background: rgba(255, 0, 0, 0.15);
+			border: 1px solid rgba(255, 74, 74, 0.6);
+			box-shadow: inset 0 0 20px rgba(255, 0, 0, 0.08);
+			color: #FF8F8F;
 			font-family: Microsoft YaHei;
 			font-weight: 400;
-			font-size: 34px;
+			font-size: 28px;
 			display: none;
+			overflow: hidden;
+			white-space: nowrap;
+			text-overflow: ellipsis;
 		}
 	</style>
 </head>
@@ -133,64 +239,69 @@
 <div class="screenbox1">
 	<div class="screenmain">
 		<div class="screen-title">MES系统生产数据大屏</div>
-
-		<div class="oprnobox box1">
-			<div class="oprnobox-header">
-				<div class="oprno-num">工位</div>
-				<div class="oprno-title">工位名称</div>
-				<div class="oprno-val">当班/当日产值</div>
-				<div class="oprno-val">5308产值</div>
-				<div class="oprno-val">5305产值</div>
-				<div class="oprno-val">8302产值</div>
-				<div class="oprno-val">8301产值</div>
-				<div class="oprno-val">7939产值</div>
-				<div class="oprno-val">7933产值</div>
-				<div class="oprno-val">印尼产值</div>
+		<div class="dashboard">
+			<div class="overview-row">
+				<div class="overview-card">
+					<div class="overview-label">工位总数</div>
+					<div class="overview-value" id="overviewCount">46</div>
+					<div class="overview-sub" id="overviewPageCount">当前页 46 个工位</div>
+				</div>
+				<div class="overview-card">
+					<div class="overview-label">当班总产值</div>
+					<div class="overview-value" id="overviewShift">0</div>
+					<div class="overview-sub">当前班次累计</div>
+				</div>
+				<div class="overview-card">
+					<div class="overview-label">当日总产值</div>
+					<div class="overview-value" id="overviewDay">0</div>
+					<div class="overview-sub">08:00 至次日 08:00</div>
+				</div>
+				<div class="overview-card">
+					<div class="overview-label">当班合格率</div>
+					<div class="overview-value" id="overviewRate">0%</div>
+					<div class="overview-sub" id="overviewPage">OK 0 / NG 0</div>
+				</div>
 			</div>
-
-			<div id="numbox">
-				<!--<div class="oprnobox-list">
-					<div class="oprno-num">OP060</div>
-					<div class="oprno-right">
-						<div class="oprno-title">后梁FSW</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
+			<div class="dashboard-main">
+				<div class="table-panel panel-box">
+					<div class="panel-title">工位产值明细 </div>
+					<div class="oprnobox-header">
+						<div class="oprno-num">工位</div>
+						<div class="oprno-title">工位名称</div>
+						<div class="oprno-shift">当班产值</div>
+						<div class="oprno-day">当日产值</div>
+						<div class="oprno-rate">合格率</div>
 					</div>
+					<div id="numbox"></div>
 				</div>
-				<div class="oprnobox-list">
-					<div class="oprno-num">OP060</div>
-					<div class="oprno-right">
-						<div class="oprno-title">后梁FSW</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
-						<div class="oprno-val">0/0</div>
+				<div class="side-panel">
+					<div class="chart-panel panel-box">
+						<div class="panel-title">当前页工位对比 <span>当班 / 当日</span></div>
+						<div id="shiftDayChart" class="chart-box"></div>
 					</div>
-				</div>-->
+					<div class="chart-panel panel-box">
+						<div class="panel-title">日产值 TOP 6 <span>全工位排名</span></div>
+						<div id="topChart" class="chart-box"></div>
+					</div>
+				</div>
 			</div>
-
 		</div>
 
 		<div class="dangerbox" id="dangerbox">
-<!--			2025-05-12 11:23:30 【OP060A-后梁FSW】异常报警:000020015308-0100101425042600115-->
 		</div>
 	</div>
 </div>
 
 <script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
-<!--<script src="${ctxStatic}/common/vue2.7.14.min.js"></script>-->
+<script src="${ctxStatic}/leaflet/js/echarts.min.js"></script>
 <script>
 	let lists = [];
-	let info = null;
-	let curIdx = 0;
+	let overviewData = {};
+	let topListData = [];
+	let pageSize = 9;
+	let pageIndex = 0;
+	let shiftDayChart = null;
+	let topChart = null;
 
 	$(window).resize(function(){
 		location.reload()
@@ -198,6 +309,7 @@
 
 	$(function () {
 		changeZoom();
+		initCharts();
 
 		getData();
 		getAlarmData();
@@ -212,11 +324,21 @@
 		},1000*10);
 	});
 
+	function initCharts() {
+		if (window.echarts) {
+			shiftDayChart = echarts.init(document.getElementById('shiftDayChart'));
+			topChart = echarts.init(document.getElementById('topChart'));
+		}
+	}
+
 	function getData() {
-		let url = "/js/a/mes/mesProduct/screenData";
+		let url = "/js/a/mes/mesProduct/screenDashboardData";
 		$.post(url,function (ret) {
-			lists = ret.data;
-			curIdx = 0;
+			let data = ret.data || {};
+			lists = $.isArray(data.list) ? data.list : [];
+			overviewData = data.overview || {};
+			topListData = $.isArray(data.topList) ? data.topList : [];
+			pageIndex = 0;
 			formatData();
 		});
 	}
@@ -224,64 +346,301 @@
 	function getAlarmData() {
 		let url = "/js/a/mes/mesProduct/alarmData";
 		$.post(url,function (ret) {
-			console.log(ret); //2025-05-12 11:23:30 【OP060A-后梁FSW】异常报警:000020015308-0100101425042600115
 			if(ret.result == "true"){
-				str = ret.data.createDate+" 【"+ret.data.oprno+"-"+ret.data.title+"】"+ret.data.remark+":"+ret.data.sn;
-				$("#dangerbox").show().html(str)
+				let str = ret.data.createDate+" 【"+ret.data.oprno+"-"+ret.data.title+"】"+ret.data.remark+""+ret.data.sn;
+				$("#dangerbox").show().text(str);
 			}else{
-				$("#dangerbox").hide().html("");
+				$("#dangerbox").hide().text("");
 			}
 		});
 	}
 
 	function formatData(){
+		let pageData = getPageData();
+		renderOverview(pageData);
+		renderTable(pageData.list);
+		renderCharts(pageData.list);
+		if (pageData.totalPages > 1) {
+			pageIndex = (pageIndex + 1) % pageData.totalPages;
+		}
+	}
+
+	function getPageData() {
+		let totalPages = Math.ceil(lists.length / pageSize);
+		if (totalPages < 1) {
+			totalPages = 1;
+		}
+		if (pageIndex >= totalPages) {
+			pageIndex = 0;
+		}
+		let start = pageIndex * pageSize;
+		let end = start + pageSize;
+		return {
+			list: lists.slice(start, end),
+			totalPages: totalPages,
+			currentPage: lists.length ? pageIndex + 1 : 0
+		};
+	}
+
+	function renderOverview(pageData) {
+		let totalShift = normalizeNumber(overviewData.totalShift);
+		let totalDay = normalizeNumber(overviewData.totalDay);
+		let shiftOkCount = normalizeNumber(overviewData.shiftOkCount);
+		let shiftNgCount = normalizeNumber(overviewData.shiftNgCount);
+		let rate = normalizeDecimal(overviewData.qualityRate).toFixed(1);
+		$("#overviewCount").text(normalizeNumber(46));
+		$("#overviewPageCount").text("当前页 " + pageData.list.length + " 个工位");
+		$("#overviewShift").text(totalShift);
+		$("#overviewDay").text(totalDay);
+		$("#overviewRate").text(rate + "%");
+		$("#overviewPage").text("OK " + shiftOkCount + " / NG " + shiftNgCount);
+	}
+
+	function renderTable(dataList) {
 		let str = '';
-		if(curIdx == 0){
-			curIdx = 1;
-			let i = 0;
-			for (let o in lists) {
-				if(i < 9){
-					str += '<div class="oprnobox-list">';
-					str += '<div class="oprno-num">'+lists[o].oprno+'</div>';
-					str += '<div class="oprno-right">';
-					str += '<div class="oprno-title">'+lists[o].title+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shiftCount+'/'+lists[o].dayCount+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift5308+'/'+lists[o].day5308+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift5305+'/'+lists[o].day5305+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift8302+'/'+lists[o].day8302+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift8301+'/'+lists[o].day8301+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift7939+'/'+lists[o].day7939+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift7933+'/'+lists[o].day7933+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shiftyn+'/'+lists[o].dayyn+'</div>';
-					str += '</div>';
-					str += '</div>';
+		if (!dataList.length) {
+			str = '<div class="empty-box">暂无生产数据</div>';
+			$("#numbox").html(str);
+			return;
+		}
+		$.each(dataList, function (idx, item) {
+			let shiftCount = normalizeNumber(item.shiftCount);
+			let dayCount = normalizeNumber(item.dayCount);
+			let rateValue = normalizeDecimal(item.qualityRate);
+			let rateWidth = Math.max(6, Math.min(rateValue, 100));
+			str += '<div class="oprnobox-list">';
+			str += '<div class="oprno-num">' + escapeHtml(item.oprno) + '</div>';
+			str += '<div class="oprno-title">' + escapeHtml(item.title) + '</div>';
+			str += '<div class="oprno-shift">' + shiftCount + '</div>';
+			str += '<div class="oprno-day">' + dayCount + '</div>';
+			str += '<div class="oprno-rate">';
+			str += '<div class="rate-box">';
+			str += '<div class="rate-track"><div class="rate-bar" style="width:' + rateWidth + '%;"></div></div>';
+			str += '<div class="rate-text">' + rateValue.toFixed(1) + '%</div>';
+			str += '</div>';
+			str += '</div>';
+			str += '</div>';
+		});
+		$("#numbox").html(str);
+	}
+
+	function renderCharts(dataList) {
+		renderShiftDayChart(dataList);
+		renderTopChart();
+	}
+
+	function renderShiftDayChart(dataList) {
+		if (!shiftDayChart) {
+			return;
+		}
+		if (!dataList.length) {
+			shiftDayChart.clear();
+			return;
+		}
+		let categories = [];
+		let shiftData = [];
+		let dayData = [];
+		$.each(dataList, function (idx, item) {
+			categories.push(item.oprno || '');
+			shiftData.push(normalizeNumber(item.shiftCount));
+			dayData.push(normalizeNumber(item.dayCount));
+		});
+		shiftDayChart.setOption({
+			backgroundColor: 'transparent',
+			tooltip: {
+				trigger: 'axis'
+			},
+			legend: {
+				top: 6,
+				right: 10,
+				textStyle: {
+					color: '#BFE8FF'
+				},
+				data: ['当班产值', '当日产值']
+			},
+			grid: {
+				top: 46,
+				left: 44,
+				right: 22,
+				bottom: 42,
+				borderWidth: 0
+			},
+			xAxis: [
+				{
+					type: 'category',
+					data: categories,
+					axisLine: {
+						lineStyle: {
+							color: 'rgba(120, 187, 255, 0.35)'
+						}
+					},
+					axisLabel: {
+						textStyle: {
+							color: '#CDEFFF',
+							fontSize: 12
+						}
+					}
 				}
-				i++;
-			}
-		}else{
-			curIdx = 0;
-			let i = 0;
-			for (let o in lists) {
-				if(i >= 9){
-					str += '<div class="oprnobox-list">';
-					str += '<div class="oprno-num">'+lists[o].oprno+'</div>';
-					str += '<div class="oprno-right">';
-					str += '<div class="oprno-title">'+lists[o].title+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shiftCount+'/'+lists[o].dayCount+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift5308+'/'+lists[o].day5308+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift5305+'/'+lists[o].day5305+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift8302+'/'+lists[o].day8302+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift8301+'/'+lists[o].day8301+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift7939+'/'+lists[o].day7939+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shift7933+'/'+lists[o].day7933+'</div>';
-					str += '<div class="oprno-val">'+lists[o].shiftyn+'/'+lists[o].dayyn+'</div>';
-					str += '</div>';
-					str += '</div>';
+			],
+			yAxis: [
+				{
+					type: 'value',
+					axisLine: {
+						lineStyle: {
+							color: 'rgba(120, 187, 255, 0.35)'
+						}
+					},
+					splitLine: {
+						lineStyle: {
+							color: 'rgba(120, 187, 255, 0.12)'
+						}
+					},
+					axisLabel: {
+						textStyle: {
+							color: '#CDEFFF',
+							fontSize: 12
+						}
+					}
 				}
-				i++;
-			}
+			],
+			series: [
+				{
+					name: '当班产值',
+					type: 'bar',
+					barMaxWidth: 18,
+					itemStyle: {
+						normal: {
+							color: '#1FD6FF'
+						}
+					},
+					data: shiftData
+				},
+				{
+					name: '当日产值',
+					type: 'bar',
+					barMaxWidth: 18,
+					itemStyle: {
+						normal: {
+							color: '#35E595'
+						}
+					},
+					data: dayData
+				}
+			]
+		}, true);
+	}
+
+	function renderTopChart() {
+		if (!topChart) {
+			return;
 		}
-		$("#numbox").html(str);
+		if (!topListData.length) {
+			topChart.clear();
+			return;
+		}
+		let categories = [];
+		let values = [];
+		$.each(topListData, function (idx, item) {
+			categories.push(item.oprno || '');
+			values.push(normalizeNumber(item.dayCount));
+		});
+		topChart.setOption({
+			backgroundColor: 'transparent',
+			tooltip: {
+				trigger: 'axis',
+				axisPointer: {
+					type: 'shadow'
+				}
+			},
+			grid: {
+				top: 16,
+				left: 90,
+				right: 30,
+				bottom: 18,
+				borderWidth: 0
+			},
+			xAxis: [
+				{
+					type: 'value',
+					splitLine: {
+						lineStyle: {
+							color: 'rgba(120, 187, 255, 0.12)'
+						}
+					},
+					axisLine: {
+						lineStyle: {
+							color: 'rgba(120, 187, 255, 0.35)'
+						}
+					},
+					axisLabel: {
+						textStyle: {
+							color: '#CDEFFF',
+							fontSize: 12
+						}
+					}
+				}
+			],
+			yAxis: [
+				{
+					type: 'category',
+					data: categories,
+					axisLine: {
+						lineStyle: {
+							color: 'rgba(120, 187, 255, 0.35)'
+						}
+					},
+					axisTick: {
+						show: false
+					},
+					axisLabel: {
+						textStyle: {
+							color: '#CDEFFF',
+							fontSize: 12
+						}
+					}
+				}
+			],
+			series: [
+				{
+					name: '当日产值',
+					type: 'bar',
+					barMaxWidth: 18,
+					itemStyle: {
+						normal: {
+							color: '#3C86FF',
+							label: {
+								show: true,
+								position: 'right',
+								textStyle: {
+									color: '#E4F7FF'
+								}
+							}
+						}
+					},
+					data: values
+				}
+			]
+		}, true);
+	}
+
+	function normalizeNumber(value) {
+		let num = parseInt(value, 10);
+		return isNaN(num) ? 0 : num;
+	}
+
+	function normalizeDecimal(value) {
+		let num = parseFloat(value);
+		return isNaN(num) ? 0 : num;
+	}
+
+	function escapeHtml(value) {
+		let text = value == null ? '' : String(value);
+		return text
+			.replace(/&/g, '&amp;')
+			.replace(/</g, '&lt;')
+			.replace(/>/g, '&gt;')
+			.replace(/"/g, '&quot;')
+			.replace(/'/g, '&#39;');
 	}
 
 	function changeZoom() {
@@ -300,4 +659,4 @@
 
 </script>
 </body>
-</html>
+</html>