소스 검색

总成检具NG点位参数

dzc 1 일 전
부모
커밋
f1184a09f9

+ 110 - 0
db/mysql/mes_gauge_points.sql

@@ -0,0 +1,110 @@
+-- ============================================================
+-- 总成检具(OP320)NG点位表
+-- 对照 GP12:
+--   mes_gp12_items      -> mes_gauge_points      (点位/不良项字典)
+--   mes_product_gp12    -> mes_product_gauge_ng  (工件NG记录,多选点位)
+-- 设备界面点位:J1 ~ J56
+-- ============================================================
+
+-- 1) 点位字典表(下拉复选数据源,可后续扩展描述)
+CREATE TABLE IF NOT EXISTS `mes_gauge_points` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `point_code` varchar(20) NOT NULL COMMENT '点位编码,如 J1',
+  `point_name` varchar(100) DEFAULT NULL COMMENT '点位名称/说明',
+  `oprno` varchar(20) DEFAULT NULL COMMENT '适用工位,空=通用',
+  `sort` int(11) DEFAULT 0 COMMENT '排序号',
+  `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态(0正常 1删除 2停用)',
+  `create_by` varchar(64) NOT NULL DEFAULT 'system' COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL DEFAULT 'system' COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  `remarks` varchar(500) DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_mes_gauge_points_code` (`point_code`),
+  KEY `idx_mes_gauge_points_sort` (`sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='总成检具点位字典';
+
+-- 2) 工件 NG 点位记录表(点 NG 并复选后写入)
+-- ng_points 存多选结果,逗号分隔,例如:J1,J3,J15
+CREATE TABLE IF NOT EXISTS `mes_product_gauge_ng` (
+  `id` varchar(64) NOT NULL COMMENT '主键',
+  `sn` varchar(64) NOT NULL COMMENT '壳体号/工件码',
+  `oprno` varchar(20) DEFAULT NULL COMMENT '工位号',
+  `line_sn` varchar(10) DEFAULT NULL COMMENT '产线编号',
+  `result` varchar(10) NOT NULL DEFAULT 'NG' COMMENT '判定结果,默认NG',
+  `ng_points` varchar(1000) NOT NULL COMMENT 'NG点位,逗号分隔,如 J1,J5,J12',
+  `ng_point_count` int(11) DEFAULT 0 COMMENT 'NG点位数量',
+  `ucode` varchar(64) DEFAULT NULL COMMENT '操作人',
+  `remark` varchar(1000) DEFAULT NULL COMMENT '备注',
+  `create_by` varchar(64) NOT NULL DEFAULT 'system' COMMENT '创建者',
+  `create_date` datetime NOT NULL COMMENT '创建时间',
+  `update_by` varchar(64) NOT NULL DEFAULT 'system' COMMENT '更新者',
+  `update_date` datetime NOT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_mes_product_gauge_ng_sn` (`sn`),
+  KEY `idx_mes_product_gauge_ng_oprno` (`oprno`),
+  KEY `idx_mes_product_gauge_ng_create_date` (`create_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='总成检具工件NG点位记录';
+
+-- 3) 初始化 J1 ~ J56 点位字典
+DELETE FROM `mes_gauge_points` WHERE `point_code` REGEXP '^J([1-9]|[1-4][0-9]|5[0-6])$';
+
+INSERT INTO `mes_gauge_points`
+(`id`, `point_code`, `point_name`, `oprno`, `sort`, `status`, `create_by`, `create_date`, `update_by`, `update_date`, `remarks`)
+VALUES
+('gauge_pt_J01', 'J1',  '检具点位J1',  NULL, 1,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J02', 'J2',  '检具点位J2',  NULL, 2,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J03', 'J3',  '检具点位J3',  NULL, 3,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J04', 'J4',  '检具点位J4',  NULL, 4,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J05', 'J5',  '检具点位J5',  NULL, 5,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J06', 'J6',  '检具点位J6',  NULL, 6,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J07', 'J7',  '检具点位J7',  NULL, 7,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J08', 'J8',  '检具点位J8',  NULL, 8,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J09', 'J9',  '检具点位J9',  NULL, 9,  '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J10', 'J10', '检具点位J10', NULL, 10, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J11', 'J11', '检具点位J11', NULL, 11, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J12', 'J12', '检具点位J12', NULL, 12, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J13', 'J13', '检具点位J13', NULL, 13, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J14', 'J14', '检具点位J14', NULL, 14, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J15', 'J15', '检具点位J15', NULL, 15, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J16', 'J16', '检具点位J16', NULL, 16, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J17', 'J17', '检具点位J17', NULL, 17, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J18', 'J18', '检具点位J18', NULL, 18, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J19', 'J19', '检具点位J19', NULL, 19, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J20', 'J20', '检具点位J20', NULL, 20, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J21', 'J21', '检具点位J21', NULL, 21, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J22', 'J22', '检具点位J22', NULL, 22, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J23', 'J23', '检具点位J23', NULL, 23, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J24', 'J24', '检具点位J24', NULL, 24, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J25', 'J25', '检具点位J25', NULL, 25, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J26', 'J26', '检具点位J26', NULL, 26, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J27', 'J27', '检具点位J27', NULL, 27, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J28', 'J28', '检具点位J28', NULL, 28, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J29', 'J29', '检具点位J29', NULL, 29, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J30', 'J30', '检具点位J30', NULL, 30, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J31', 'J31', '检具点位J31', NULL, 31, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J32', 'J32', '检具点位J32', NULL, 32, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J33', 'J33', '检具点位J33', NULL, 33, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J34', 'J34', '检具点位J34', NULL, 34, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J35', 'J35', '检具点位J35', NULL, 35, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J36', 'J36', '检具点位J36', NULL, 36, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J37', 'J37', '检具点位J37', NULL, 37, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J38', 'J38', '检具点位J38', NULL, 38, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J39', 'J39', '检具点位J39', NULL, 39, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J40', 'J40', '检具点位J40', NULL, 40, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J41', 'J41', '检具点位J41', NULL, 41, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J42', 'J42', '检具点位J42', NULL, 42, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J43', 'J43', '检具点位J43', NULL, 43, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J44', 'J44', '检具点位J44', NULL, 44, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J45', 'J45', '检具点位J45', NULL, 45, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J46', 'J46', '检具点位J46', NULL, 46, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J47', 'J47', '检具点位J47', NULL, 47, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J48', 'J48', '检具点位J48', NULL, 48, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J49', 'J49', '检具点位J49', NULL, 49, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J50', 'J50', '检具点位J50', NULL, 50, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J51', 'J51', '检具点位J51', NULL, 51, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J52', 'J52', '检具点位J52', NULL, 52, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J53', 'J53', '检具点位J53', NULL, 53, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J54', 'J54', '检具点位J54', NULL, 54, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J55', 'J55', '检具点位J55', NULL, 55, '0', 'system', NOW(), 'system', NOW(), NULL),
+('gauge_pt_J56', 'J56', '检具点位J56', NULL, 56, '0', 'system', NOW(), 'system', NOW(), NULL);

+ 34 - 0
db/mysql/mes_product_gauge_ng_menu.sql

@@ -0,0 +1,34 @@
+-- 总成检具NG点位参数:菜单 + 角色授权(整段执行)
+-- menu_code 必须是:2099040100000000001(注意末尾有 4 个 0 再 1)
+
+DELETE FROM `js_sys_role_menu` WHERE `menu_code` = '2099040100000000001';
+DELETE FROM `js_sys_menu` WHERE `menu_code` = '2099040100000000001';
+
+INSERT INTO `js_sys_menu`
+(`menu_code`, `parent_code`, `parent_codes`, `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`,
+ `tree_names`, `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`, `menu_color`, `menu_title`,
+ `permission`, `weight`, `is_show`, `sys_code`, `module_codes`, `component`, `params`, `status`,
+ `create_by`, `create_date`, `update_by`, `update_date`, `remarks`)
+VALUES
+('2099040100000000001', '2042038662094802944', '0,1672118505143263232,2042038662094802944,',
+ 220, '0000009290,0000000660,0000000220,', '1', 2,
+ '生产管理/工艺过程参数/总成检具NG点位参数', '总成检具NG点位参数', '1',
+ '/mes/mesProductGaugeNg/list', '', '', '', '',
+ 'mes:mesProductGaugeNg:view,mes:mesProductGaugeNg:edit',
+ 40, '1', 'default', 'core', NULL, NULL, '0',
+ 'system', NOW(), 'system', NOW(), '总成检具NG点位参数');
+
+-- 给所有角色都授权这个菜单(超级管理员也能看到)
+INSERT INTO `js_sys_role_menu` (`role_code`, `menu_code`)
+SELECT r.`role_code`, '2099040100000000001'
+FROM `js_sys_role` r
+WHERE r.`status` = '0'
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_role_menu` x
+    WHERE x.`role_code` = r.`role_code`
+      AND x.`menu_code` = '2099040100000000001'
+  );
+
+-- 执行后自检(应各有结果)
+SELECT menu_code, menu_name, menu_href FROM js_sys_menu WHERE menu_code = '2099040100000000001';
+SELECT COUNT(*) AS role_menu_count FROM js_sys_role_menu WHERE menu_code = '2099040100000000001';

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 7191 - 0
effective-pom-tmp


BIN
material_report_qrcode.png


+ 18 - 0
pom.xml

@@ -24,6 +24,12 @@
 		
 		<finalName>web</finalName><!-- war or jar 包的名称 -->
 		<start-class>com.jeesite.modules.Application</start-class>
+
+		<!-- 按 JDK 8 编译(覆盖 parent 中的 11) -->
+		<java.version>1.8</java.version>
+		<maven.compiler.source>1.8</maven.compiler.source>
+		<maven.compiler.target>1.8</maven.compiler.target>
+		<maven.compiler.release></maven.compiler.release>
 		
 		<!-- environment setting -->
 		<eclipse-plugin-download-sources>false</eclipse-plugin-download-sources>
@@ -252,6 +258,18 @@
 		<outputDirectory>${project.basedir}/src/main/webapp/WEB-INF/classes/</outputDirectory>
 		<plugins>
 			
+			<!-- 强制按 JDK 8 编译 -->
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.8</source>
+					<target>1.8</target>
+					<release combine.self="override"></release>
+					<encoding>UTF-8</encoding>
+				</configuration>
+			</plugin>
+
 			<!-- Spring Boot -->
 			<plugin>
 				<groupId>org.springframework.boot</groupId>

+ 5 - 14
src/main/java/com/jeesite/modules/Application.java

@@ -7,7 +7,6 @@ package com.jeesite.modules;
 import java.net.ServerSocket;
 
 import com.alibaba.fastjson.parser.ParserConfig;
-import com.jeesite.modules.utils.LdNettyServer;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.SpringApplication;
@@ -45,29 +44,21 @@ public class Application extends SpringBootServletInitializer implements Command
 		return builder.sources(Application.class);
 	}
 
-	//启动端口号3000 tcp服务端
+	/** 启动端口 3000 的 TCP/Netty 服务,供 OP030 等客户端质检/上传 */
 	@Override
 	public void run(String... args) throws Exception {
-		// TODO Auto-generated method stub
 		new Thread(() -> {
 			try {
 				new NettyServer(3000).run();
-			}catch(Exception e) {
+			} catch (Exception e) {
 				e.printStackTrace();
 			}
-		}).start();
-
-//		new Thread(() -> {
-//			try {
-//				new LdNettyServer(7890).run();
-//			}catch(Exception e) {
-//				e.printStackTrace();
-//			}
-//		}).start();
+		}, "mes-netty-3000").start();
 	}
+
 	public static void initDatabaseConnection() {
 		System.out.println(BaseConfig.url);
 	}
 
 
-}
+}

+ 12 - 0
src/main/java/com/jeesite/modules/mes/dao/MesProductGaugeNgDao.java

@@ -0,0 +1,12 @@
+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.MesProductGaugeNg;
+
+/**
+ * 总成检具NG点位参数DAO
+ */
+@MyBatisDao
+public interface MesProductGaugeNgDao extends CrudDao<MesProductGaugeNg> {
+}

+ 152 - 0
src/main/java/com/jeesite/modules/mes/entity/MesProductGaugeNg.java

@@ -0,0 +1,152 @@
+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.mybatis.mapper.query.QueryType;
+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;
+
+import java.util.Date;
+
+/**
+ * 总成检具NG点位参数Entity
+ * @author mes
+ * @version 2026-09-04
+ */
+@Table(name="mes_product_gauge_ng", alias="a", label="总成检具NG点位参数", columns={
+		@Column(name="id", attrName="id", label="主键", isPK=true),
+		@Column(name="sn", attrName="sn", label="壳体号", queryType=QueryType.LIKE),
+		@Column(name="oprno", attrName="oprno", label="工位号", queryType=QueryType.LIKE),
+		@Column(name="line_sn", attrName="lineSn", label="产线编号"),
+		@Column(name="result", attrName="result", label="判定结果"),
+		@Column(name="ng_points", attrName="ngPoints", label="NG点位"),
+		@Column(name="ng_point_count", attrName="ngPointCount", label="NG点位数量", isUpdateForce=true),
+		@Column(name="ucode", attrName="ucode", label="操作人"),
+		@Column(name="remark", attrName="remark", label="备注"),
+		@Column(name="create_by", attrName="createBy", label="创建者", isUpdate=false, isQuery=false),
+		@Column(name="create_date", attrName="createDate", label="创建时间", isUpdate=false, isUpdateForce=true),
+		@Column(name="update_by", attrName="updateBy", label="更新者", isQuery=false),
+		@Column(name="update_date", attrName="updateDate", label="更新时间", isQuery=false, isUpdateForce=true),
+	}, orderBy="a.create_date DESC"
+)
+public class MesProductGaugeNg extends DataEntity<MesProductGaugeNg> {
+
+	private static final long serialVersionUID = 1L;
+	private String sn;
+	private String oprno;
+	private String lineSn;
+	private String result;
+	private String ngPoints;
+	private Integer ngPointCount;
+	private String ucode;
+	private String remark;
+
+	@ExcelFields({
+		@ExcelField(title="壳体号", attrName="sn", align=Align.CENTER, sort=10),
+		@ExcelField(title="工位号", attrName="oprno", align=Align.CENTER, sort=20),
+		@ExcelField(title="产线编号", attrName="lineSn", align=Align.CENTER, sort=30),
+		@ExcelField(title="判定结果", attrName="result", align=Align.CENTER, sort=40),
+		@ExcelField(title="NG点位", attrName="ngPoints", align=Align.LEFT, sort=50),
+		@ExcelField(title="NG点位数", attrName="ngPointCount", align=Align.CENTER, sort=60),
+		@ExcelField(title="操作人", attrName="ucode", align=Align.CENTER, sort=70),
+		@ExcelField(title="创建时间", attrName="createDate", align=Align.CENTER, sort=80, dataFormat="yyyy-MM-dd HH:mm:ss"),
+	})
+	public MesProductGaugeNg() {
+		this(null);
+	}
+
+	public MesProductGaugeNg(String id) {
+		super(id);
+	}
+
+	@Size(min=0, max=64, message="壳体号长度不能超过 64 个字符")
+	public String getSn() {
+		return sn;
+	}
+
+	public void setSn(String sn) {
+		this.sn = sn;
+	}
+
+	@Size(min=0, max=20, message="工位号长度不能超过 20 个字符")
+	public String getOprno() {
+		return oprno;
+	}
+
+	public void setOprno(String oprno) {
+		this.oprno = oprno;
+	}
+
+	@Size(min=0, max=10, message="产线编号长度不能超过 10 个字符")
+	public String getLineSn() {
+		return lineSn;
+	}
+
+	public void setLineSn(String lineSn) {
+		this.lineSn = lineSn;
+	}
+
+	@Size(min=0, max=10, message="判定结果长度不能超过 10 个字符")
+	public String getResult() {
+		return result;
+	}
+
+	public void setResult(String result) {
+		this.result = result;
+	}
+
+	@Size(min=0, max=1000, message="NG点位长度不能超过 1000 个字符")
+	public String getNgPoints() {
+		return ngPoints;
+	}
+
+	public void setNgPoints(String ngPoints) {
+		this.ngPoints = ngPoints;
+	}
+
+	public Integer getNgPointCount() {
+		return ngPointCount;
+	}
+
+	public void setNgPointCount(Integer ngPointCount) {
+		this.ngPointCount = ngPointCount;
+	}
+
+	@Size(min=0, max=64, message="操作人长度不能超过 64 个字符")
+	public String getUcode() {
+		return ucode;
+	}
+
+	public void setUcode(String ucode) {
+		this.ucode = ucode;
+	}
+
+	@Size(min=0, max=1000, message="备注长度不能超过 1000 个字符")
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public Date getCreateDate_gte() {
+		return sqlMap.getWhere().getValue("create_date", QueryType.GTE);
+	}
+
+	public void setCreateDate_gte(Date createDate) {
+		sqlMap.getWhere().and("create_date", QueryType.GTE, createDate);
+	}
+
+	public Date getCreateDate_lte() {
+		return sqlMap.getWhere().getValue("create_date", QueryType.LTE);
+	}
+
+	public void setCreateDate_lte(Date createDate) {
+		sqlMap.getWhere().and("create_date", QueryType.LTE, createDate);
+	}
+}

+ 69 - 0
src/main/java/com/jeesite/modules/mes/service/MesProductGaugeNgService.java

@@ -0,0 +1,69 @@
+package com.jeesite.modules.mes.service;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.mes.dao.MesProductGaugeNgDao;
+import com.jeesite.modules.mes.entity.MesProductGaugeNg;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 总成检具NG点位参数Service
+ */
+@Service
+public class MesProductGaugeNgService extends CrudService<MesProductGaugeNgDao, MesProductGaugeNg> {
+
+	@Override
+	public MesProductGaugeNg get(MesProductGaugeNg mesProductGaugeNg) {
+		return super.get(mesProductGaugeNg);
+	}
+
+	@Override
+	public Page<MesProductGaugeNg> findPage(MesProductGaugeNg mesProductGaugeNg) {
+		return super.findPage(mesProductGaugeNg);
+	}
+
+	@Override
+	@Transactional
+	public void save(MesProductGaugeNg mesProductGaugeNg) {
+		super.save(mesProductGaugeNg);
+	}
+
+	/**
+	 * 客户端提交 NG 点位记录(只存勾选的 NG 点)
+	 */
+	@Transactional
+	public void saveNgPoints(String sn, String oprno, String lineSn, String ucode, String ngPoints) {
+		if (StringUtils.isBlank(sn) || StringUtils.isBlank(ngPoints)) {
+			return;
+		}
+		String points = ngPoints.trim().replace(",", ",");
+		while (points.contains(",,")) {
+			points = points.replace(",,", ",");
+		}
+		if (points.startsWith(",")) {
+			points = points.substring(1);
+		}
+		if (points.endsWith(",")) {
+			points = points.substring(0, points.length() - 1);
+		}
+		if (StringUtils.isBlank(points)) {
+			return;
+		}
+		int count = points.split(",").length;
+		MesProductGaugeNg record = new MesProductGaugeNg();
+		record.setSn(sn.trim());
+		record.setOprno(oprno);
+		record.setLineSn(lineSn);
+		record.setResult("NG");
+		record.setNgPoints(points);
+		record.setNgPointCount(count);
+		record.setUcode(ucode);
+		if (StringUtils.isNotBlank(ucode)) {
+			record.setCreateBy(ucode);
+			record.setUpdateBy(ucode);
+		}
+		super.save(record);
+	}
+}

+ 17 - 2
src/main/java/com/jeesite/modules/mes/web/MesMaterialCheckRecordController.java

@@ -243,7 +243,7 @@ public class MesMaterialCheckRecordController extends BaseController {
 			mesMaterialCheckRecordItems.setResult(mecheck.getJSONObject(i).getString("result"));
 			mesMaterialCheckRecordItemsService.save(mesMaterialCheckRecordItems);
 			String org = mecheck.getJSONObject(i).getString("org");
-			if (StringUtils.isNotBlank(getPerfOrgValue()) && getPerfOrgValue().equals(org)) {
+			if (isFileUploadOrg(org)) {
 				FileUploadUtils.saveFileUpload(mesMaterialCheckRecordItems, mesMaterialCheckRecordItems.getId(),
 						getPerfFileFieldName(checkId));
 			}
@@ -560,6 +560,7 @@ public class MesMaterialCheckRecordController extends BaseController {
 	}
 
 	private String perfOrgValue;
+	private String appearanceOrgValue;
 
 	private String getPerfOrgValue() {
 		if (perfOrgValue == null) {
@@ -568,6 +569,20 @@ public class MesMaterialCheckRecordController extends BaseController {
 		return perfOrgValue;
 	}
 
+	private String getAppearanceOrgValue() {
+		if (appearanceOrgValue == null) {
+			appearanceOrgValue = getOrgDictValue("外观");
+		}
+		return appearanceOrgValue;
+	}
+
+	private boolean isFileUploadOrg(String org) {
+		if (StringUtils.isBlank(org)) {
+			return false;
+		}
+		return org.equals(getPerfOrgValue()) || org.equals(getAppearanceOrgValue());
+	}
+
 	private String getOrgDictValue(String label) {
 		DictData dictData = new DictData();
 		dictData.setDictType("mes_material_org");
@@ -935,7 +950,7 @@ public class MesMaterialCheckRecordController extends BaseController {
 			mesMaterialCheckRecordItemsService.save(items);
 			String org = mecheck.getJSONObject(i).getString("org");
 			String checkId = mecheck.getJSONObject(i).getString("checkId");
-			if (StringUtils.isNotBlank(getPerfOrgValue()) && getPerfOrgValue().equals(org)) {
+			if (isFileUploadOrg(org)) {
 				FileUploadUtils.saveFileUpload(items, items.getId(), getPerfFileFieldName(checkId));
 			}
 		}

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

@@ -28,7 +28,6 @@ import com.jeesite.modules.mes.util.CommonUitl;
 import com.jeesite.modules.sys.entity.EmpUser;
 import com.jeesite.modules.sys.service.EmpUserService;
 import com.jeesite.modules.sys.utils.DictUtils;
-import com.jeesite.modules.utils.NettyServerHandler;
 import org.apache.poi.ss.formula.functions.T;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;

+ 111 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductGaugeNgController.java

@@ -0,0 +1,111 @@
+package com.jeesite.modules.mes.web;
+
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.jeesite.common.lang.DateUtils;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.utils.excel.ExcelExport;
+import com.jeesite.modules.mes.req.MesSearchDateReq;
+import com.jeesite.modules.mes.util.CommonUitl;
+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 com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesProductGaugeNg;
+import com.jeesite.modules.mes.service.MesProductGaugeNgService;
+
+/**
+ * 总成检具NG点位参数Controller(工艺过程参数)
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesProductGaugeNg")
+public class MesProductGaugeNgController extends BaseController {
+
+	@Autowired
+	private MesProductGaugeNgService mesProductGaugeNgService;
+
+	@ModelAttribute
+	public MesProductGaugeNg get(String id, boolean isNewRecord) {
+		return mesProductGaugeNgService.get(id, isNewRecord);
+	}
+
+	@RequiresPermissions("mes:mesProductGaugeNg:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesProductGaugeNg mesProductGaugeNg, Model model) {
+		MesSearchDateReq dateMap = CommonUitl.getSearchDate();
+		mesProductGaugeNg.setCreateDate_gte(dateMap.getStartDate());
+		mesProductGaugeNg.setCreateDate_lte(dateMap.getEndtDate());
+		model.addAttribute("mesProductGaugeNg", mesProductGaugeNg);
+		return "modules/mes/mesProductGaugeNgList";
+	}
+
+	@RequiresPermissions("mes:mesProductGaugeNg:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesProductGaugeNg> listData(MesProductGaugeNg mesProductGaugeNg, HttpServletRequest request, HttpServletResponse response) {
+		mesProductGaugeNg.setPage(new Page<>(request, response));
+		return mesProductGaugeNgService.findPage(mesProductGaugeNg);
+	}
+
+	@RequiresPermissions("mes:mesProductGaugeNg:view")
+	@RequestMapping(value = "form")
+	public String form(MesProductGaugeNg mesProductGaugeNg, Model model) {
+		model.addAttribute("mesProductGaugeNg", mesProductGaugeNg);
+		return "modules/mes/mesProductGaugeNgForm";
+	}
+
+	@RequiresPermissions("mes:mesProductGaugeNg:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesProductGaugeNg mesProductGaugeNg) {
+		mesProductGaugeNgService.save(mesProductGaugeNg);
+		return renderResult(Global.TRUE, text("保存总成检具NG点位参数成功!"));
+	}
+
+	@RequiresPermissions("mes:mesProductGaugeNg:view")
+	@RequestMapping(value = "exportData")
+	public void exportData(MesProductGaugeNg mesProductGaugeNg, HttpServletResponse response) {
+		List<MesProductGaugeNg> list = mesProductGaugeNgService.findList(mesProductGaugeNg);
+		String fileName = "总成检具NG点位参数" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
+		try (ExcelExport ee = new ExcelExport("总成检具NG点位参数", MesProductGaugeNg.class)) {
+			ee.setDataList(list).write(response, fileName);
+		}
+	}
+
+	/**
+	 * 客户端上传 NG 点位(不校验菜单权限,走工位客户端调用)
+	 */
+	@PostMapping(value = "pcsave")
+	@ResponseBody
+	public String pcsave(HttpServletRequest request) {
+		String sn = request.getParameter("sn");
+		String oprno = request.getParameter("oprno");
+		String lineSn = request.getParameter("lineSn");
+		String ucode = request.getParameter("ucode");
+		String ngPoints = request.getParameter("ngPoints");
+		if (StringUtils.isBlank(sn)) {
+			return renderResult(Global.FALSE, text("壳体号不能为空"));
+		}
+		if (StringUtils.isBlank(ngPoints)) {
+			return renderResult(Global.FALSE, text("请至少选择一个NG点位"));
+		}
+		try {
+			mesProductGaugeNgService.saveNgPoints(sn, oprno, lineSn, ucode, ngPoints);
+			return renderResult(Global.TRUE, text("NG点位保存成功"));
+		} catch (Exception e) {
+			logger.error("保存总成检具NG点位失败", e);
+			return renderResult(Global.FALSE, text("NG点位保存失败"));
+		}
+	}
+}

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

@@ -65,7 +65,6 @@ import com.jeesite.modules.sys.service.ConfigService;
 import com.jeesite.modules.sys.utils.DictUtils;
 import com.jeesite.modules.sys.utils.UserUtils;
 import com.jeesite.modules.utils.*;
-import io.netty.buffer.ByteBuf;
 import io.swagger.models.auth.In;
 import org.apache.commons.csv.CSVFormat;
 import org.apache.commons.csv.CSVPrinter;

+ 6 - 6
src/main/resources/config/application.yml

@@ -54,12 +54,12 @@ jdbc:
   # Mysql 数据库配置
   type: mysql
   driver: com.mysql.cj.jdbc.Driver
-#  url: jdbc:mysql://192.168.16.100:3306/mes_cloud?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_199_0y7a?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
-#  username: mes_cloud
-#  password: Aw3EYLj5GcHnrFm8
-  username: root
-  password: 123456
+  url: jdbc:mysql://192.168.16.100:3306/mes_cloud?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_199_0y7a?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+  username: mes_cloud
+  password: Aw3EYLj5GcHnrFm8
+  #username: root
+  #password: 123456
 
   testSql: SELECT 1
 

+ 92 - 23
src/main/resources/views/modules/mes/mesMaterialCheckRecordForm.html

@@ -91,7 +91,7 @@
 										<th v-for="n in sampleCount" :key="'h'+n">实测值{{n}}</th>
 										<th>结果</th>
 									</tr>
-									<tr v-for="(item,index) in options" :key="index">
+									<tr v-for="(item,index) in options" :key="item.id || index">
 										<td>{{item.orgName}}</td>
 										<td style="word-wrap: break-word;overflow-wrap: break-word;">{{item.content}}</td>
 										<td>
@@ -105,9 +105,12 @@
 											</td>
 										</template>
 										<td v-else-if="isPerfItem(item)" :colspan="sampleCount">
-											<div :id="'perfUpload_'+item.id"></div>
+											<div :id="'perfUpload_'+item.id" v-once></div>
 										</td>
-										<td v-else :colspan="sampleCount" class="text-center text-muted" style="vertical-align: middle;">人工判定</td>
+										<td v-else-if="isAppearanceItem(item)" :colspan="sampleCount">
+											<div :id="'appearanceUpload_'+item.id" class="appearance-upload-left" v-once></div>
+										</td>
+										<td v-else :colspan="sampleCount" class="text-center text-muted" style="vertical-align: middle;"></td>
 										<td>
 											<div v-if="isSizeItem(item) && item.type == 1">
 												<label v-if="item.result == 1"><input type="radio" v-model="item.result" value="1">合格</label>
@@ -155,6 +158,27 @@
 	</div>
 </div>
 <% } %>
+<style>
+.appearance-upload-left,
+.appearance-upload-left .queueList,
+.appearance-upload-left .placeholder,
+.appearance-upload-left .filelist,
+.appearance-upload-left .btns,
+.appearance-upload-left .statusBar,
+.appearance-upload-left .webuploader-container,
+.appearance-upload-left .webuploader-pick {
+	text-align: left !important;
+	margin-left: 0 !important;
+	margin-right: auto !important;
+	float: none !important;
+}
+.appearance-upload-left .placeholder {
+	min-height: auto !important;
+	padding-top: 0 !important;
+	padding-bottom: 0 !important;
+	background: none !important;
+}
+</style>
 <link rel="stylesheet" href="${ctxStatic}/common/elementui2.15.5/index.css"/>
 <link rel="stylesheet" href="${ctxStatic}/common/elementui2.15.5/style/theme/index.css">
 <script src="${ctxStatic}/common/elementui2.15.5/vue-2.6.14.js"></script>
@@ -171,29 +195,56 @@ function getOrgDictValue(label) {
 }
 var ORG_SIZE = getOrgDictValue('尺寸');
 var ORG_PERF = getOrgDictValue('性能');
+var ORG_APPEARANCE = getOrgDictValue('外观');
 var PERF_FILE_BIZ_TYPE = 'mesMaterialCheckRecord_file';
 
 function getPerfFileFieldName(checkId) {
 	return 'perfFileList[' + checkId + '].' + PERF_FILE_BIZ_TYPE;
 }
 
+function hasUploadFile(containerSelector, fieldName) {
+	var container = $(containerSelector);
+	if (!container.length) {
+		return false;
+	}
+	var hasFile = false;
+	container.find('input').each(function () {
+		if (this.name === fieldName && $.trim($(this).val())) {
+			hasFile = true;
+			return false;
+		}
+	});
+	if (hasFile) {
+		return true;
+	}
+	// 兼容 webuploader / jeesite 上传成功后的常见节点
+	if (container.find('.file-item, .files .template-download, .files .template-upload, .wu-example .filelist li, .uploader-list .file-item, .uploader-list .item, a[href*="fileUpload"], img.preview, .preview img').length > 0) {
+		return true;
+	}
+	container.find('input[type="hidden"]').each(function () {
+		if (this.name && this.name.indexOf(PERF_FILE_BIZ_TYPE) >= 0 && $.trim(this.value)) {
+			hasFile = true;
+			return false;
+		}
+	});
+	return hasFile;
+}
+
 $("#inputForm").validate({
 	submitHandler: function(form){
 		if (vm && vm.options) {
 			for (let i = 0; i < vm.options.length; i++) {
 				var item = vm.options[i];
 				if (vm.isPerfItem(item)) {
-					var fieldName = getPerfFileFieldName(item.id);
-					var hasFile = false;
-					$('input[name="' + fieldName + '"]').each(function () {
-						if ($(this).val()) {
-							hasFile = true;
-						}
-					});
-					if (!hasFile && $('#perfUpload_' + item.id).find('.file-item, .files .template-download').length === 0) {
+					if (!hasUploadFile('#perfUpload_' + item.id, getPerfFileFieldName(item.id))) {
 						js.showMessage('请上传性能检查文件:' + item.content);
 						return false;
 					}
+				} else if (vm.isAppearanceItem(item)) {
+					if (!hasUploadFile('#appearanceUpload_' + item.id, getPerfFileFieldName(item.id))) {
+						js.showMessage('请上传外观图片:' + item.content);
+						return false;
+					}
 				}
 			}
 		}
@@ -252,24 +303,37 @@ var vm = new Vue({
 		isPerfItem(item) {
 			return item.org === ORG_PERF;
 		},
+		isAppearanceItem(item) {
+			return item.org === ORG_APPEARANCE;
+		},
 		initPerfFileUploads() {
 			var that = this;
 			this.$nextTick(function () {
 				for (let o in that.options) {
 					var item = that.options[o];
-					if (!that.isPerfItem(item)) {
-						continue;
-					}
-					var container = $('#perfUpload_' + item.id);
-					if (!container.length) {
-						continue;
+					if (that.isPerfItem(item)) {
+						var perfContainer = $('#perfUpload_' + item.id);
+						if (perfContainer.length && !perfContainer.data('uploadInited')) {
+							perfContainer.html(js.template('perfFileuploadTpl', {
+								id: 'perfFile_' + item.id,
+								bizKey: item.recordItemId || '',
+								bizType: getPerfFileFieldName(item.id),
+								readonly: false
+							}));
+							perfContainer.data('uploadInited', true);
+						}
+					} else if (that.isAppearanceItem(item)) {
+						var appearanceContainer = $('#appearanceUpload_' + item.id);
+						if (appearanceContainer.length && !appearanceContainer.data('uploadInited')) {
+							appearanceContainer.html(js.template('appearanceImageuploadTpl', {
+								id: 'appearanceFile_' + item.id,
+								bizKey: item.recordItemId || '',
+								bizType: getPerfFileFieldName(item.id),
+								readonly: false
+							}));
+							appearanceContainer.data('uploadInited', true);
+						}
 					}
-					container.html(js.template('perfFileuploadTpl', {
-						id: 'perfFile_' + item.id,
-						bizKey: item.recordItemId || '',
-						bizType: getPerfFileFieldName(item.id),
-						readonly: false
-					}));
 				}
 			});
 		},
@@ -476,6 +540,7 @@ var vm = new Vue({
 										 }
 									 }
 								 }
+								 that.initPerfFileUploads();
 								 console.log(res);
 								 console.log("--");
 								 that.formatData();
@@ -491,4 +556,8 @@ var vm = new Vue({
 <script id="perfFileuploadTpl" type="text/template">//<!--<div>
 <#form:fileupload id="{{d.id}}" bizKey="{{d.bizKey}}" bizType="{{d.bizType}}"
 	uploadType="all" class="" isMini="true" preview="true" readonly="{{d.readonly}}" maxUploadNum="1"/>
+</div>//--></script>
+<script id="appearanceImageuploadTpl" type="text/template">//<!--<div>
+<#form:fileupload id="{{d.id}}" bizKey="{{d.bizKey}}" bizType="{{d.bizType}}"
+	uploadType="image" class="" isMini="true" preview="true" readonly="{{d.readonly}}" maxUploadNum="1"/>
 </div>//--></script>

+ 82 - 13
src/main/resources/views/modules/mes/mesMaterialCheckRecordForm2.html

@@ -91,7 +91,10 @@
 										<td v-else-if="isPerfItem(item)" :colspan="sampleCount">
 											<div :id="'perfUpload_'+item.id"></div>
 										</td>
-										<td v-else :colspan="sampleCount" class="text-center text-muted" style="vertical-align: middle;">人工判定</td>
+										<td v-else-if="isAppearanceItem(item)" :colspan="sampleCount">
+											<div :id="'appearanceUpload_'+item.id" class="appearance-upload-left"></div>
+										</td>
+										<td v-else :colspan="sampleCount" class="text-center text-muted" style="vertical-align: middle;"></td>
 										<td>
 											<div v-if="isSizeItem(item) && item.type == 1">
 												<label v-if="item.result == 1"><input type="radio" v-model="item.result" value="1">合格</label>
@@ -128,6 +131,27 @@
 	</div>
 </div>
 <% } %>
+<style>
+.appearance-upload-left,
+.appearance-upload-left .queueList,
+.appearance-upload-left .placeholder,
+.appearance-upload-left .filelist,
+.appearance-upload-left .btns,
+.appearance-upload-left .statusBar,
+.appearance-upload-left .webuploader-container,
+.appearance-upload-left .webuploader-pick {
+	text-align: left !important;
+	margin-left: 0 !important;
+	margin-right: auto !important;
+	float: none !important;
+}
+.appearance-upload-left .placeholder {
+	min-height: auto !important;
+	padding-top: 0 !important;
+	padding-bottom: 0 !important;
+	background: none !important;
+}
+</style>
 <link rel="stylesheet" href="${ctxStatic}/common/elementui2.15.5/index.css"/>
 <link rel="stylesheet" href="${ctxStatic}/common/elementui2.15.5/style/theme/index.css">
 <script src="${ctxStatic}/common/elementui2.15.5/vue-2.6.14.js"></script>
@@ -144,14 +168,44 @@ function getOrgDictValue(label) {
 }
 var ORG_SIZE = getOrgDictValue('尺寸');
 var ORG_PERF = getOrgDictValue('性能');
+var ORG_APPEARANCE = getOrgDictValue('外观');
 var PERF_FILE_BIZ_TYPE = 'mesMaterialCheckRecord_file';
 
 function getPerfFileFieldName(checkId) {
 	return 'perfFileList[' + checkId + '].' + PERF_FILE_BIZ_TYPE;
 }
 
+function hasUploadFile(containerSelector, fieldName) {
+	var hasFile = false;
+	$('input[name="' + fieldName + '"]').each(function () {
+		if ($(this).val()) {
+			hasFile = true;
+		}
+	});
+	if (hasFile) {
+		return true;
+	}
+	return $(containerSelector).find('.file-item, .files .template-download, .files .template-upload, img.preview, .preview img').length > 0;
+}
+
 $("#inputForm").validate({
 	submitHandler: function(form){
+		if (vm && vm.options) {
+			for (let i = 0; i < vm.options.length; i++) {
+				var item = vm.options[i];
+				if (vm.isPerfItem(item)) {
+					if (!hasUploadFile('#perfUpload_' + item.id, getPerfFileFieldName(item.id))) {
+						js.showMessage('请上传性能检查文件:' + item.content);
+						return false;
+					}
+				} else if (vm.isAppearanceItem(item)) {
+					if (!hasUploadFile('#appearanceUpload_' + item.id, getPerfFileFieldName(item.id))) {
+						js.showMessage('请上传外观图片:' + item.content);
+						return false;
+					}
+				}
+			}
+		}
 		js.ajaxSubmitForm($(form), function(data){
 			js.showMessage(data.message);
 			if(data.result == Global.TRUE){
@@ -206,24 +260,35 @@ var vm = new Vue({
 		isPerfItem(item) {
 			return item.org === ORG_PERF;
 		},
+		isAppearanceItem(item) {
+			return item.org === ORG_APPEARANCE;
+		},
 		initPerfFileUploads() {
 			var that = this;
 			this.$nextTick(function () {
 				for (let o in that.options) {
 					var item = that.options[o];
-					if (!that.isPerfItem(item)) {
-						continue;
-					}
-					var container = $('#perfUpload_' + item.id);
-					if (!container.length) {
-						continue;
+					if (that.isPerfItem(item)) {
+						var perfContainer = $('#perfUpload_' + item.id);
+						if (perfContainer.length) {
+							perfContainer.html(js.template('perfFileuploadTpl', {
+								id: 'perfFile_' + item.id,
+								bizKey: item.recordItemId || '',
+								bizType: getPerfFileFieldName(item.id),
+								readonly: false
+							}));
+						}
+					} else if (that.isAppearanceItem(item)) {
+						var appearanceContainer = $('#appearanceUpload_' + item.id);
+						if (appearanceContainer.length) {
+							appearanceContainer.html(js.template('appearanceImageuploadTpl', {
+								id: 'appearanceFile_' + item.id,
+								bizKey: item.recordItemId || '',
+								bizType: getPerfFileFieldName(item.id),
+								readonly: false
+							}));
+						}
 					}
-					container.html(js.template('perfFileuploadTpl', {
-						id: 'perfFile_' + item.id,
-						bizKey: item.recordItemId || '',
-						bizType: getPerfFileFieldName(item.id),
-						readonly: false
-					}));
 				}
 			});
 		},
@@ -387,4 +452,8 @@ var vm = new Vue({
 <script id="perfFileuploadTpl" type="text/template">//<!--<div>
 <#form:fileupload id="{{d.id}}" bizKey="{{d.bizKey}}" bizType="{{d.bizType}}"
 	uploadType="all" class="" isMini="true" preview="true" readonly="{{d.readonly}}" maxUploadNum="1"/>
+</div>//--></script>
+<script id="appearanceImageuploadTpl" type="text/template">//<!--<div>
+<#form:fileupload id="{{d.id}}" bizKey="{{d.bizKey}}" bizType="{{d.bizType}}"
+	uploadType="image" class="" isMini="true" preview="true" readonly="{{d.readonly}}" maxUploadNum="1"/>
 </div>//--></script>

+ 88 - 13
src/main/resources/views/modules/mes/mesMaterialCheckRecordFormEdit.html

@@ -122,7 +122,13 @@
                                         </div>
                                         <div :id="'perfUpload_'+item.checkId"></div>
                                     </td>
-                                    <td v-else :colspan="sampleCount" class="text-center text-muted" style="vertical-align: middle;">人工判定</td>
+                                    <td v-else-if="isAppearanceItem(item)" :colspan="sampleCount">
+                                        <div v-if="item.perfFiles && item.perfFiles.length" style="margin-bottom: 8px;">
+                                            <a v-for="(f, idx) in item.perfFiles" :key="idx" :href="f.url" target="_blank" style="margin-right: 10px;">{{f.name}}</a>
+                                        </div>
+                                        <div :id="'appearanceUpload_'+item.checkId" class="appearance-upload-left"></div>
+                                    </td>
+                                    <td v-else :colspan="sampleCount" class="text-center text-muted" style="vertical-align: middle;"></td>
                                     <td>
                                             <label>
                                                 <input type="radio"  v-model="item.result" value="1">
@@ -153,6 +159,27 @@
     </div>
 </div>
 <% } %>
+<style>
+.appearance-upload-left,
+.appearance-upload-left .queueList,
+.appearance-upload-left .placeholder,
+.appearance-upload-left .filelist,
+.appearance-upload-left .btns,
+.appearance-upload-left .statusBar,
+.appearance-upload-left .webuploader-container,
+.appearance-upload-left .webuploader-pick {
+	text-align: left !important;
+	margin-left: 0 !important;
+	margin-right: auto !important;
+	float: none !important;
+}
+.appearance-upload-left .placeholder {
+	min-height: auto !important;
+	padding-top: 0 !important;
+	padding-bottom: 0 !important;
+	background: none !important;
+}
+</style>
 <link rel="stylesheet" href="${ctxStatic}/common/elementui2.15.5/index.css"/>
 <link rel="stylesheet" href="${ctxStatic}/common/elementui2.15.5/style/theme/index.css">
 <script src="${ctxStatic}/common/elementui2.15.5/vue-2.6.14.js"></script>
@@ -170,14 +197,47 @@
     }
     var ORG_SIZE = getOrgDictValue('尺寸');
     var ORG_PERF = getOrgDictValue('性能');
+    var ORG_APPEARANCE = getOrgDictValue('外观');
     var PERF_FILE_BIZ_TYPE = 'mesMaterialCheckRecord_file';
 
     function getPerfFileFieldName(checkId) {
         return 'perfFileList[' + checkId + '].' + PERF_FILE_BIZ_TYPE;
     }
 
+    function hasUploadFile(containerSelector, fieldName, existingFiles) {
+        if (existingFiles && existingFiles.length) {
+            return true;
+        }
+        var hasFile = false;
+        $('input[name="' + fieldName + '"]').each(function () {
+            if ($(this).val()) {
+                hasFile = true;
+            }
+        });
+        if (hasFile) {
+            return true;
+        }
+        return $(containerSelector).find('.file-item, .files .template-download, .files .template-upload, img.preview, .preview img').length > 0;
+    }
+
     $("#inputForm").validate({
         submitHandler: function(form){
+            if (vm && vm.options) {
+                for (let i = 0; i < vm.options.length; i++) {
+                    var item = vm.options[i];
+                    if (vm.isPerfItem(item)) {
+                        if (!hasUploadFile('#perfUpload_' + item.checkId, getPerfFileFieldName(item.checkId), item.perfFiles)) {
+                            js.showMessage('请上传性能检查文件:' + (item.materialCheckTitle || item.content || ''));
+                            return false;
+                        }
+                    } else if (vm.isAppearanceItem(item)) {
+                        if (!hasUploadFile('#appearanceUpload_' + item.checkId, getPerfFileFieldName(item.checkId), item.perfFiles)) {
+                            js.showMessage('请上传外观图片:' + (item.materialCheckTitle || item.content || ''));
+                            return false;
+                        }
+                    }
+                }
+            }
             $('#checkList').val(JSON.stringify(vm.options));
             js.ajaxSubmitForm($(form), function(data){
                 js.showMessage(data.message);
@@ -228,24 +288,35 @@
             isPerfItem(item) {
                 return item.org === ORG_PERF;
             },
+            isAppearanceItem(item) {
+                return item.org === ORG_APPEARANCE;
+            },
             initPerfFileUploads() {
                 var that = this;
                 this.$nextTick(function () {
                     for (let o in that.options) {
                         var item = that.options[o];
-                        if (!that.isPerfItem(item)) {
-                            continue;
+                        if (that.isPerfItem(item)) {
+                            var perfContainer = $('#perfUpload_' + item.checkId);
+                            if (perfContainer.length) {
+                                perfContainer.html(js.template('perfFileuploadTpl', {
+                                    id: 'perfFile_' + item.checkId,
+                                    bizKey: '',
+                                    bizType: getPerfFileFieldName(item.checkId),
+                                    readonly: false
+                                }));
+                            }
+                        } else if (that.isAppearanceItem(item)) {
+                            var appearanceContainer = $('#appearanceUpload_' + item.checkId);
+                            if (appearanceContainer.length) {
+                                appearanceContainer.html(js.template('appearanceImageuploadTpl', {
+                                    id: 'appearanceFile_' + item.checkId,
+                                    bizKey: '',
+                                    bizType: getPerfFileFieldName(item.checkId),
+                                    readonly: false
+                                }));
+                            }
                         }
-                        var container = $('#perfUpload_' + item.checkId);
-                        if (!container.length) {
-                            continue;
-                        }
-                        container.html(js.template('perfFileuploadTpl', {
-                            id: 'perfFile_' + item.checkId,
-                            bizKey: '',
-                            bizType: getPerfFileFieldName(item.checkId),
-                            readonly: false
-                        }));
                     }
                 });
             },
@@ -258,4 +329,8 @@
 <script id="perfFileuploadTpl" type="text/template">//<!--<div>
 <#form:fileupload id="{{d.id}}" bizKey="{{d.bizKey}}" bizType="{{d.bizType}}"
 	uploadType="all" class="" isMini="true" preview="true" readonly="{{d.readonly}}" maxUploadNum="1"/>
+</div>//--></script>
+<script id="appearanceImageuploadTpl" type="text/template">//<!--<div>
+<#form:fileupload id="{{d.id}}" bizKey="{{d.bizKey}}" bizType="{{d.bizType}}"
+	uploadType="image" class="" isMini="true" preview="true" readonly="{{d.readonly}}" maxUploadNum="1"/>
 </div>//--></script>

+ 2 - 2
src/main/resources/views/modules/mes/mesMaterialCheckRecordInfo.html

@@ -97,14 +97,14 @@
 								<% if(sampleCount >= 6){ %><td>${item.a6!}</td><% } %>
 								<% if(sampleCount >= 7){ %><td>${item.a7!}</td><% } %>
 								<% if(sampleCount >= 8){ %><td>${item.a8!}</td><% } %>
-								<% } else if(item.orgName == '性能'){ %>
+								<% } else if(item.orgName == '性能' || item.orgName == '外观'){ %>
 								<td colspan="${sampleCount}">
 									<% for(var perfFile in item.perfFiles){ %>
 									<a href="${perfFile.url}" target="_blank">${perfFile.name}</a>&nbsp;
 									<% } %>
 								</td>
 								<% } else { %>
-								<td colspan="${sampleCount}" class="text-center text-muted">人工判定</td>
+								<td colspan="${sampleCount}" class="text-center text-muted"></td>
 								<% } %>
 								<td>
 									<% if (item.result == '1'){ %>

+ 85 - 0
src/main/resources/views/modules/mes/mesProductGaugeNgForm.html

@@ -0,0 +1,85 @@
+<% layout('/layouts/default.html', {title: '总成检具NG点位参数详情', 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('总成检具NG点位参数详情')}
+			</div>
+			<div class="box-tools pull-right">
+				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
+			</div>
+		</div>
+		<#form:form id="inputForm" model="${mesProductGaugeNg}" action="${ctx}/mes/mesProductGaugeNg/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('壳体号')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="sn" maxlength="64" class="form-control" readonly="true"/>
+							</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="oprno" maxlength="20" class="form-control" readonly="true"/>
+							</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="lineSn" maxlength="10" class="form-control" readonly="true"/>
+							</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="result" maxlength="10" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2">${text('NG点位')}:</label>
+							<div class="col-sm-10">
+								<#form:textarea path="ngPoints" rows="3" maxlength="1000" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('NG点位数')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="ngPointCount" class="form-control" readonly="true"/>
+							</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="ucode" maxlength="64" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<button type="button" class="btn btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="glyphicon glyphicon-remove"></i> ${text('关闭')}</button>
+					</div>
+				</div>
+			</div>
+		</#form:form>
+	</div>
+</div>
+<% } %>

+ 88 - 0
src/main/resources/views/modules/mes/mesProductGaugeNgList.html

@@ -0,0 +1,88 @@
+<% layout('/layouts/default.html', {title: '总成检具NG点位参数', 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('总成检具NG点位参数')}
+			</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="btnExport"><i class="glyphicon glyphicon-export"></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="${mesProductGaugeNg}" action="${ctx}/mes/mesProductGaugeNg/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('壳体号')}:</label>
+					<div class="control-inline">
+						<#form:input path="sn" maxlength="64" class="form-control width-260"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('工位号')}:</label>
+					<div class="control-inline">
+						<#form:input path="oprno" maxlength="20" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('产线编号')}:</label>
+					<div class="control-inline">
+						<#form:input path="lineSn" maxlength="10" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('NG点位')}:</label>
+					<div class="control-inline">
+						<#form:input path="ngPoints" maxlength="200" class="form-control width-180"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('创建时间')}:</label>
+					<div class="control-inline">
+						<#form:input path="createDate_gte" readonly="true" maxlength="20" class="form-control width-160 Wdate"
+							onclick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm',isShowClear:true});"/>
+						&nbsp;~&nbsp;
+						<#form:input path="createDate_lte" readonly="true" maxlength="20" class="form-control width-160 Wdate"
+							onclick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm',isShowClear:true});"/>
+					</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:'sn', index:'a.sn', width:280, align:"center", frozen:true},
+		{header:'${text("工位号")}', name:'oprno', index:'a.oprno', width:100, align:"center"},
+		{header:'${text("产线编号")}', name:'lineSn', index:'a.line_sn', width:80, align:"center"},
+		{header:'${text("判定结果")}', name:'result', index:'a.result', width:80, align:"center"},
+		{header:'${text("NG点位")}', name:'ngPoints', index:'a.ng_points', width:320, align:"left"},
+		{header:'${text("NG点位数")}', name:'ngPointCount', index:'a.ng_point_count', width:90, align:"center"},
+		{header:'${text("操作人")}', name:'ucode', index:'a.ucode', width:100, align:"center"},
+		{header:'${text("创建时间")}', name:'createDate', index:'a.create_date', width:160, align:"center"},
+		{header:'${text("操作")}', name:'actions', width:80, formatter: function(val, obj, row, act){
+			var actions = [];
+			actions.push('<a href="${ctx}/mes/mesProductGaugeNg/form?id='+row.id+'" class="btnList" title="${text("详情")}"><i class="fa fa-fw fa-eye"></i></a>');
+			return actions.join('');
+		}}
+	],
+	ajaxSuccess: function(data){}
+});
+$('#btnExport').click(function(){
+	js.ajaxSubmitForm($('#searchForm'), {
+		url:'${ctx}/mes/mesProductGaugeNg/exportData',
+		downloadFile:true
+	});
+});
+</script>