ソースを参照

feature:
产品分类重构、工控机监控页面开发,生产任务上传记录开发

yingjian.wu 4 ヶ月 前
コミット
b161f69074

+ 2 - 1
src/main/java/com/qlm/controller/jinzai/ItemNewController.java

@@ -8,6 +8,7 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.collection.CollUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.jfinal.aop.Clear;
 import com.jfinal.kit.HttpKit;
 import com.jfinal.kit.JsonKit;
@@ -38,7 +39,7 @@ import org.slf4j.LoggerFactory;
  */
 @RequestUrl("/itemNew")
 public class ItemNewController extends CommonController {
-    private static final Logger logger = LoggerFactory.getLogger(ItemController.class);
+    private static final Logger logger = LoggerFactory.getLogger(ItemNewController.class);
     private final IItemService itemService = new ItemServiceImpl();
 
     public void getItemList() {

+ 33 - 0
src/main/java/com/qlm/controller/jinzai/NewReportController.java

@@ -0,0 +1,33 @@
+package com.qlm.controller.jinzai;
+
+import com.qlm.annotation.RequestUrl;
+import com.qlm.controller.common.CommonController;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: NewReportController
+ * @description: TODO
+ * @author: 吴英健
+ * @create: 2025-08-27 14:41
+ * @Version 1.0
+ **/
+@RequestUrl("/newReport")
+public class NewReportController extends CommonController {
+
+    /**
+     * 生产统计报表路由
+     */
+    public void getProdTotalReportPage() {
+        renderJsp("/page/jinzai/prodTotalReport.jsp");
+    }
+
+
+    /**
+     * 出库统计报表路由
+     */
+    public void getOutTotalReportPage() {
+        renderJsp("/page/jinzai/outTotalReport.jsp");
+    }
+
+
+}

+ 229 - 0
src/main/java/com/qlm/controller/jinzai/ProdBatchController.java

@@ -0,0 +1,229 @@
+package com.qlm.controller.jinzai;
+
+import cn.hutool.core.date.DateUtil;
+import com.jfinal.kit.StrKit;
+import com.jfinal.plugin.activerecord.Db;
+import com.jfinal.plugin.activerecord.Page;
+import com.jfinal.plugin.activerecord.Record;
+import com.qlm.annotation.RequestUrl;
+import com.qlm.common.ApiResponse;
+import com.qlm.common.PageResult;
+import com.qlm.controller.common.CommonController;
+import com.qlm.dto.DeviceMonitorRecordDto;
+import com.qlm.dto.ProdTaskUploadRecordDto;
+import com.qlm.tools.WxUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: ProdBatchController
+ * @description: 生产批次管理
+ * @author: 吴英健
+ * @create: 2025-08-27 14:41
+ * @Version 1.0
+ **/
+@RequestUrl("/prodBatch")
+public class ProdBatchController extends CommonController {
+    private static final Logger logger = LoggerFactory.getLogger(ProdBatchController.class);
+
+    /**
+     * 生产任务上传记录路由
+     */
+    public void getProdUploadRecordPage() {
+        renderJsp("/page/jinzai/production_task_upload.jsp");
+    }
+
+    /**
+     * 生产任务上传记录查询
+     */
+    public void getProdUploadRecord() {
+        //分页参数
+        int pageNumber = getParaToInt("pageNumber", 1);
+        int pageSize = getParaToInt("pageSize", 10);
+        //创建时间、工厂ID、产线ID、WMS状态
+        String createTime = getPara("createTime");
+        String factoryId = getPara("factoryId");
+        String lineId = getPara("lineId");
+        String wmsStatus = getPara("wmsStatus");
+        try {
+            StringBuilder whereSql = new StringBuilder(" from jinzai_upload_master where 1=1");
+            List<Object> params = new ArrayList<>();
+            if(StrKit.notBlank(createTime)){
+                //创建时间 =
+                whereSql.append(" and create_time = ?");
+                params.add(createTime);
+            }
+//            if(StrKit.notBlank(factoryId)){
+//                whereSql.append(" and factory_id = ?");
+//                params.add(factoryId);
+//            }
+//            if(StrKit.notBlank(lineId)){
+//                whereSql.append(" and line_id = ?");
+//                params.add(lineId);
+//            }
+//            if(StrKit.notBlank(wmsStatus)){
+//                whereSql.append(" and wms_status = ?");
+//            }
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, "select *", whereSql.toString(), params.toArray());
+            if(paginate  == null){
+                renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+                return;
+            }
+            List<Record> list = paginate.getList();
+            List<ProdTaskUploadRecordDto> prodTaskUploadRecordDtoList = new ArrayList<>();
+            for (Record record : list) {
+                prodTaskUploadRecordDtoList.add(convertRecordToDto(record));
+            }
+            renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, prodTaskUploadRecordDtoList));
+        }catch (Exception e){
+            logger.error("查询生产任务上传记录异常:", e);
+            renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+        }
+    }
+
+    /**
+     * 补打信息记录路由
+     */
+    public void getProdRePrintRecordPage() {
+        renderJsp("/page/jinzai/reprint_record.jsp");
+    }
+
+    /**
+     * 工控机监控记录路由
+     */
+    public void getProdMonitorRecordPage() {
+        renderJsp("/page/jinzai/deviceMonitor.jsp");
+    }
+
+    /**
+     * 查询工控机监控记录
+     */
+    public void getDeviceMonitorList() {
+        // 获取分页参数
+        int pageNumber = getParaToInt("pageNumber", 1);
+        int pageSize = getParaToInt("pageSize", 10);
+        Integer deviceId =  getParaToInt("deviceId");
+        try {
+            StringBuilder fromSql = new StringBuilder(" from t_device_detail tdd");
+            fromSql.append(" inner join t_jz_device tjd on tdd.device_id = tjd.id");
+            fromSql.append(" left join t_factory tf on tjd.factory_id = tf.id");
+            fromSql.append(" left join t_workshop tw on tjd.workshop_id = tw.id");
+            fromSql.append(" left join t_production_line tpl on tjd.line_id = tpl.id");
+            fromSql.append(" left join t_jz_product tjp on tdd.product_id = tjp.id");
+            List<Object> params = new ArrayList<>();
+            if (deviceId != null) {
+                fromSql.append(" where tdd.device_id = ?");
+                params.add(deviceId);
+            }
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, "select tdd.*,tjd.id as deviceId, tjd.desc as device_name, tf.factory_name, tw.workshop_name, tpl.line_name, tjp.product_name", fromSql.toString(), params.toArray());
+            if (paginate == null) {
+                renderJson(ApiResponse.success(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>())));
+                return;
+            }
+            List<Record> list = paginate.getList();
+            List<DeviceMonitorRecordDto> deviceMonitorRecordDtoList = new ArrayList<>();
+            for (Record record : list) {
+                deviceMonitorRecordDtoList.add(convertRecordToDeviceMonitorRecordDto(record));
+            }
+            renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, deviceMonitorRecordDtoList));
+        } catch (Exception e) {
+            logger.error("查询设备监控记录列表异常:", e);
+            renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+        }
+    }
+
+    /**
+     * 刷新设备详细信息
+     */
+    public void refreshDeviceDetail() {
+        ApiResponse apiResponse = new ApiResponse();
+        Integer deviceId = getParaToInt("deviceId");
+        try {
+            StringBuilder sql = new StringBuilder("select tdd.*,tjd.id as deviceId, tjd.desc as device_name, tf.factory_name, tw.workshop_name, tpl.line_name, tjp.product_name");
+            sql.append(" from t_device_detail tdd");
+            sql.append(" inner join t_jz_device tjd on tdd.device_id = tjd.id");
+            sql.append(" left join t_factory tf on tjd.factory_id = tf.id");
+            sql.append(" left join t_workshop tw on tjd.workshop_id = tw.id");
+            sql.append(" left join t_production_line tpl on tjd.line_id = tpl.id");
+            sql.append(" left join t_jz_product tjp on tdd.product_id = tjp.id");
+            sql.append(" where tdd.device_id = ?");
+            Record record = Db.findFirst(sql.toString(), deviceId);
+            if (record == null) {
+                apiResponse.setCode(500);
+                apiResponse.setMsg("系统异常:设备不存在");
+            } else {
+                DeviceMonitorRecordDto deviceMonitorRecordDto = convertRecordToDeviceMonitorRecordDto(record);
+                apiResponse.setData(deviceMonitorRecordDto);
+            }
+        }catch (Exception e) {
+            logger.error("刷新设备详细信息异常:", e);
+            apiResponse.setCode(500);
+            apiResponse.setMsg("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
+    /**
+     * 获取设备信息
+     */
+    public void getDeviceList() {
+        ApiResponse apiResponse = new ApiResponse();
+        String sql = "select id,`desc` as name from t_jz_device";
+        try {
+            List<Record> list = Db.find(sql);
+            apiResponse.setData(list);
+        } catch (Exception e) {
+            logger.error("获取设备列表异常:", e);
+            apiResponse.setCode(500);
+            apiResponse.setMsg("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
+    /**
+     * 品相信息更改记录路由
+     */
+    public void getProdItemChangeRecordPage() {
+        renderJsp("/page/jinzai/item_change_record.jsp");
+    }
+
+
+    private ProdTaskUploadRecordDto convertRecordToDto(Record record) {
+        ProdTaskUploadRecordDto dto = new ProdTaskUploadRecordDto();
+        dto.setFactoryName(record.getStr("factory_name"));
+        dto.setLineName(record.getStr("line_name"));
+        dto.setProductName(record.getStr("pinxiang"));
+        dto.setBatchNo(record.getStr("task_no"));
+        dto.setCreateTime(DateUtil.format(record.getDate("create_time"), "yyyy-MM-dd HH:mm:ss"));
+        if(record.getDate("upload_time") != null){
+            dto.setUploadTime(DateUtil.format(record.getDate("upload_time"), "yyyy-MM-dd HH:mm:ss"));
+            dto.setIsUpload("1");
+            dto.setWmsStatus("1");
+        }
+        dto.setTransferredToDataCenter(1);
+        return dto;
+    }
+
+    private DeviceMonitorRecordDto convertRecordToDeviceMonitorRecordDto(Record record) {
+        DeviceMonitorRecordDto deviceMonitorRecordDto = new DeviceMonitorRecordDto();
+        deviceMonitorRecordDto.setDeviceName(record.getStr("device_name"));
+        deviceMonitorRecordDto.setCupUsage(record.getBigDecimal("cpu_usage").toString());
+        deviceMonitorRecordDto.setMemoryUsage(record.getBigDecimal("memory_usage").toString());
+        deviceMonitorRecordDto.setDiskSurplus(record.getStr("disk_free"));
+        deviceMonitorRecordDto.setProductionTaskNum(record.getInt("current_task_quantity").toString());
+        deviceMonitorRecordDto.setFactory(record.getStr("factory_name"));
+        deviceMonitorRecordDto.setWorkshop(record.getStr("workshop_name"));
+        deviceMonitorRecordDto.setLine(record.getStr("line_name"));
+        deviceMonitorRecordDto.setProduct(record.getStr("product_name"));
+        deviceMonitorRecordDto.setNetworkDelay(record.getStr("network_delay"));
+        deviceMonitorRecordDto.setProductionNum(record.getInt("current_quantity").toString());
+        deviceMonitorRecordDto.setTotalNum(record.getInt("total_quantity").toString());
+        deviceMonitorRecordDto.setPlatformNum(record.getInt("uploaded_quantity").toString());
+        deviceMonitorRecordDto.setDeviceId(WxUtil.getInt("deviceId", record));
+        return deviceMonitorRecordDto;
+    }
+}

+ 178 - 0
src/main/java/com/qlm/controller/jinzai/ProductTypeNewController.java

@@ -0,0 +1,178 @@
+package com.qlm.controller.jinzai;
+
+import com.jfinal.aop.Clear;
+import com.jfinal.kit.HttpKit;
+import com.qlm.annotation.RequestUrl;
+import com.qlm.common.ApiResponse;
+import com.qlm.controller.common.CommonController;
+import com.qlm.dto.ProductTypeDto;
+import com.qlm.service.IProductTypeService;
+import com.qlm.service.impl.ProductTypeServiceImpl;
+import com.qlm.tools.WxUtil;
+import com.alibaba.fastjson.JSON;
+import com.qlm.view.core.AdminView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author wuyingjianwu
+ * @date 2025/xx/xx
+ * 产品分类管理控制器
+ */
+@RequestUrl("/productTypeNew")
+public class ProductTypeNewController extends CommonController {
+    private static final Logger logger = LoggerFactory.getLogger(ProductTypeNewController.class);
+    private final IProductTypeService productTypeService = new ProductTypeServiceImpl();
+
+    public void getProductTypeList() {
+        renderJsp("/page/jinzai/productTypeList.jsp");
+    }
+
+    /**
+     * 保存产品分类信息
+     */
+    @Override
+    public void save() {
+        ApiResponse apiResponse;
+        AdminView loginUser = getLoginUser();
+        try {
+            // 使用HttpKit.readData获取请求Body
+            String bodyData = HttpKit.readData(getRequest());
+            // 将JSON字符串转换为ProductTypeDto对象
+            ProductTypeDto productTypeDto = JSON.parseObject(bodyData, ProductTypeDto.class);
+
+            // 数据验证
+            if (WxUtil.isNull(productTypeDto.getTypeName())) {
+                apiResponse = ApiResponse.error("分类名称不能为空");
+                renderJson(apiResponse);
+                return;
+            }
+
+            if (WxUtil.isNull(productTypeDto.getTypeNo())) {
+                apiResponse = ApiResponse.error("分类编号不能为空");
+                renderJson(apiResponse);
+                return;
+            }
+
+            // 调用Service层保存数据
+            apiResponse = productTypeService.saveProductType(productTypeDto,loginUser);
+        } catch (Exception e) {
+            logger.error("保存产品分类信息异常:", e);
+            apiResponse = ApiResponse.error("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
+    /**
+     * 更新产品分类信息
+     */
+    public void update() {
+        AdminView loginUser = getLoginUser();
+        ApiResponse apiResponse;
+        try {
+            String bodyData = HttpKit.readData(getRequest());
+            ProductTypeDto productTypeDto = JSON.parseObject(bodyData, ProductTypeDto.class);
+
+            if (productTypeDto.getId() == null) {
+                apiResponse = ApiResponse.error("分类ID不能为空");
+                renderJson(apiResponse);
+                return;
+            }
+
+            apiResponse = productTypeService.updateProductType(productTypeDto,loginUser);
+        } catch (Exception e) {
+            logger.error("更新产品分类信息异常:", e);
+            apiResponse = ApiResponse.error("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
+    /**
+     * 删除产品分类信息
+     */
+    public void delete() {
+        ApiResponse apiResponse;
+        try {
+            Integer id = getParaToInt("id");
+            if (id == null) {
+                apiResponse = ApiResponse.error("分类ID不能为空");
+                renderJson(apiResponse);
+                return;
+            }
+
+            apiResponse = productTypeService.deleteProductType(id);
+        } catch (Exception e) {
+            logger.error("删除产品分类信息异常:", e);
+            apiResponse = ApiResponse.error("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
+    /**
+     * 根据ID获取产品分类信息
+     */
+    @Override
+    public void getById() {
+        ApiResponse apiResponse;
+        try {
+            Integer id = getParaToInt("id");
+            if (id == null) {
+                apiResponse = ApiResponse.error("分类ID不能为空");
+                renderJson(apiResponse);
+                return;
+            }
+
+            apiResponse = productTypeService.getProductTypeById(id);
+        } catch (Exception e) {
+            logger.error("获取产品分类信息异常:", e);
+            apiResponse = ApiResponse.error("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
+    /**
+     * 产品分类列表查询
+     */
+    @Override
+    public void list() {
+        try {
+            // 获取分页参数
+            int pageNumber = getParaToInt("pageNumber", 1);
+            int pageSize = getParaToInt("pageSize", 10);
+            // 获取查询条件
+            String typeName = getPara("typeName");
+
+            // 调用Service层查询列表
+            renderJson(productTypeService.listProductTypes(pageNumber, pageSize, typeName ));
+        } catch (Exception e) {
+            logger.error("查询产品分类列表异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    /**
+     * 获取产品分类树形结构(用于下拉选择和层级展示)
+     */
+    public void getProductTypeTree() {
+        try {
+            ApiResponse apiResponse = productTypeService.getProductTypeTree();
+            renderJson(apiResponse);
+        } catch (Exception e) {
+            logger.error("获取产品分类树形结构异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    /**
+     * 获取所有可用的产品分类(不带树形结构)
+     */
+    public void getAllProductTypes() {
+        try {
+            ApiResponse apiResponse = productTypeService.getAllProductTypes();
+            renderJson(apiResponse);
+        } catch (Exception e) {
+            logger.error("获取产品分类列表异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+}

+ 142 - 0
src/main/java/com/qlm/dto/DeviceMonitorRecordDto.java

@@ -0,0 +1,142 @@
+package com.qlm.dto;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: DeviceMonitorRecordDto
+ * @description: 设备监控记录返回
+ * @author: wuyingjian
+ * @create: 2025-08-28 09:46
+ * @Version 1.0
+ **/
+public class DeviceMonitorRecordDto {
+
+    /**
+     * 设备名称、CUP使用率、内存使用率、磁盘剩余容量、正在进行的生产任务数量、工厂、车间、产线、产品、网络延迟、目前生产数量、总件数、已传平台数量
+     */
+    private Integer deviceId;
+    private String deviceName;
+    private String cupUsage;
+    private String memoryUsage;
+    private String diskSurplus;
+    private String productionTaskNum;
+    private String factory;
+    private String workshop;
+    private String line;
+    private String product;
+    private String networkDelay;
+    private String productionNum;
+    private String totalNum;
+    private String platformNum;
+
+    public String getDeviceName() {
+        return deviceName;
+    }
+
+    public void setDeviceName(String deviceName) {
+        this.deviceName = deviceName;
+    }
+
+    public String getCupUsage() {
+        return cupUsage;
+    }
+
+    public void setCupUsage(String cupUsage) {
+        this.cupUsage = cupUsage;
+    }
+
+    public String getMemoryUsage() {
+        return memoryUsage;
+    }
+
+    public void setMemoryUsage(String memoryUsage) {
+        this.memoryUsage = memoryUsage;
+    }
+
+    public String getDiskSurplus() {
+        return diskSurplus;
+    }
+
+    public void setDiskSurplus(String diskSurplus) {
+        this.diskSurplus = diskSurplus;
+    }
+
+    public String getProductionTaskNum() {
+        return productionTaskNum;
+    }
+
+    public void setProductionTaskNum(String productionTaskNum) {
+        this.productionTaskNum = productionTaskNum;
+    }
+
+    public String getFactory() {
+        return factory;
+    }
+
+    public void setFactory(String factory) {
+        this.factory = factory;
+    }
+
+    public String getWorkshop() {
+        return workshop;
+    }
+
+    public void setWorkshop(String workshop) {
+        this.workshop = workshop;
+    }
+
+    public String getLine() {
+        return line;
+    }
+
+    public void setLine(String line) {
+        this.line = line;
+    }
+
+    public String getProduct() {
+        return product;
+    }
+
+    public void setProduct(String product) {
+        this.product = product;
+    }
+
+    public String getNetworkDelay() {
+        return networkDelay;
+    }
+
+    public void setNetworkDelay(String networkDelay) {
+        this.networkDelay = networkDelay;
+    }
+
+    public String getProductionNum() {
+        return productionNum;
+    }
+
+    public void setProductionNum(String productionNum) {
+        this.productionNum = productionNum;
+    }
+
+    public String getTotalNum() {
+        return totalNum;
+    }
+
+    public void setTotalNum(String totalNum) {
+        this.totalNum = totalNum;
+    }
+
+    public String getPlatformNum() {
+        return platformNum;
+    }
+
+    public void setPlatformNum(String platformNum) {
+        this.platformNum = platformNum;
+    }
+
+    public Integer getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(Integer deviceId) {
+        this.deviceId = deviceId;
+    }
+}

+ 107 - 0
src/main/java/com/qlm/dto/ProdTaskUploadRecordDto.java

@@ -0,0 +1,107 @@
+package com.qlm.dto;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: ProdTaskUploadRecordDto
+ * @description: 生产任务上传记录返回
+ * @author: 吴英健
+ * @create: 2025-08-27 15:16
+ * @Version 1.0
+ **/
+public class ProdTaskUploadRecordDto implements java.io.Serializable{
+    /**
+     * 工厂名称、产线名称、产品名称、批次号、创建时间、上传时间、WMS状态、是否已传到平台
+     */
+    private String factoryName;
+
+    private String lineName;
+
+    private String productName;
+
+    private String batchNo;
+
+    private String createTime;
+
+    private String uploadTime;
+
+    private String wmsStatus;
+
+    private String isUpload;
+
+    /**
+     * 是否传到数据中台
+     */
+    private Integer transferredToDataCenter;
+
+    public String getFactoryName() {
+        return factoryName;
+    }
+
+    public void setFactoryName(String factoryName) {
+        this.factoryName = factoryName;
+    }
+
+    public String getLineName() {
+        return lineName;
+    }
+
+    public void setLineName(String lineName) {
+        this.lineName = lineName;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public void setProductName(String productName) {
+        this.productName = productName;
+    }
+
+    public String getBatchNo() {
+        return batchNo;
+    }
+
+    public void setBatchNo(String batchNo) {
+        this.batchNo = batchNo;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUploadTime() {
+        return uploadTime;
+    }
+
+    public void setUploadTime(String uploadTime) {
+        this.uploadTime = uploadTime;
+    }
+
+    public String getWmsStatus() {
+        return wmsStatus;
+    }
+
+    public void setWmsStatus(String wmsStatus) {
+        this.wmsStatus = wmsStatus;
+    }
+
+    public String getIsUpload() {
+        return isUpload;
+    }
+
+    public void setIsUpload(String isUpload) {
+        this.isUpload = isUpload;
+    }
+
+    public Integer getTransferredToDataCenter() {
+        return transferredToDataCenter;
+    }
+
+    public void setTransferredToDataCenter(Integer transferredToDataCenter) {
+        this.transferredToDataCenter = transferredToDataCenter;
+    }
+}

+ 86 - 0
src/main/java/com/qlm/dto/ProductTypeDto.java

@@ -0,0 +1,86 @@
+package com.qlm.dto;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 产品分类数据传输对象
+ */
+public class ProductTypeDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+    
+    private Integer id;                 // 分类ID
+    private String typeName;            // 分类名称
+    private Integer parentId;           // 父类ID,顶级分类为NULL
+    private String parentTypeName;      // 父分类名称
+    private String createTime;          // 创建时间
+    private Integer status;             // 状态(0禁用 1启用)
+    private String typeNo;              // 编号
+    private List<ProductTypeDto> children; // 子分类列表
+
+    // getter和setter方法
+    public Integer getId() {
+        return id;
+    }
+    
+    public void setId(Integer id) {
+        this.id = id;
+    }
+    
+    public String getTypeName() {
+        return typeName;
+    }
+    
+    public void setTypeName(String typeName) {
+        this.typeName = typeName;
+    }
+    
+    public Integer getParentId() {
+        return parentId;
+    }
+    
+    public void setParentId(Integer parentId) {
+        this.parentId = parentId;
+    }
+    
+    public String getParentTypeName() {
+        return parentTypeName;
+    }
+    
+    public void setParentTypeName(String parentTypeName) {
+        this.parentTypeName = parentTypeName;
+    }
+    
+    public String getCreateTime() {
+        return createTime;
+    }
+    
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+    
+    public Integer getStatus() {
+        return status;
+    }
+    
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+    
+    public String getTypeNo() {
+        return typeNo;
+    }
+    
+    public void setTypeNo(String typeNo) {
+        this.typeNo = typeNo;
+    }
+    
+    public List<ProductTypeDto> getChildren() {
+        return children;
+    }
+    
+    public void setChildren(List<ProductTypeDto> children) {
+        this.children = children;
+    }
+}

+ 60 - 0
src/main/java/com/qlm/service/IProductTypeService.java

@@ -0,0 +1,60 @@
+package com.qlm.service;
+
+import com.jfinal.plugin.activerecord.Record;
+import com.qlm.common.ApiResponse;
+import com.qlm.common.PageResult;
+import com.qlm.dto.ProductTypeDto;
+import com.qlm.view.core.AdminView;
+
+import java.util.List;
+
+/**
+ * 产品分类服务接口
+ */
+public interface IProductTypeService {
+
+    /**
+     * 分页查询产品分类列表
+     */
+    PageResult<ProductTypeDto> listProductTypes(int pageNumber, int pageSize, String typeName);
+    
+    /**
+     * 根据ID获取产品分类信息
+     */
+    ApiResponse getProductTypeById(Integer id);
+    
+    /**
+     * 保存产品分类信息
+     */
+    ApiResponse saveProductType(ProductTypeDto productTypeDto, AdminView loginUser);
+    
+    /**
+     * 更新产品分类信息
+     */
+    ApiResponse updateProductType(ProductTypeDto productTypeDto, AdminView loginUser);
+    
+    /**
+     * 删除产品分类信息
+     */
+    ApiResponse deleteProductType(Integer id);
+    
+    /**
+     * 获取所有产品分类列表(用于下拉选择)
+     */
+    ApiResponse getAllProductTypes();
+    
+    /**
+     * 获取树形结构的产品分类列表
+     */
+    ApiResponse getProductTypeTree();
+    
+    /**
+     * 检查分类名称是否已存在
+     */
+    boolean checkTypeNameExists(String typeName, Integer id, Integer parentId);
+    
+    /**
+     * 检查分类编号是否已存在
+     */
+    boolean checkTypeNoExists(String typeNo, Integer id);
+}

+ 344 - 0
src/main/java/com/qlm/service/impl/ProductTypeServiceImpl.java

@@ -0,0 +1,344 @@
+package com.qlm.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import com.jfinal.plugin.activerecord.Db;
+import com.jfinal.plugin.activerecord.Page;
+import com.jfinal.plugin.activerecord.Record;
+import com.qlm.common.ApiResponse;
+import com.qlm.common.PageResult;
+import com.qlm.dto.ProductTypeDto;
+import com.qlm.service.IProductTypeService;
+import com.qlm.tools.WxUtil;
+import com.qlm.view.core.AdminView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * 产品分类服务实现类
+ */
+public class ProductTypeServiceImpl implements IProductTypeService {
+
+    private static final Logger logger = LoggerFactory.getLogger(ProductTypeServiceImpl.class);
+
+    @Override
+    public PageResult<ProductTypeDto> listProductTypes(int pageNumber, int pageSize, String typeName) {
+        try {
+            // 构建查询条件
+            StringBuilder sqlWhere = new StringBuilder();
+            List<Object> params = new ArrayList<>();
+
+            if (typeName != null && !typeName.isEmpty()) {
+                sqlWhere.append(" and t.type_name like ? ");
+                params.add("%" + typeName + "%");
+            }
+
+            // 执行分页查询
+            Page<Record> recordPage = Db.paginate(pageNumber, pageSize,
+                    "select t.*, pt.type_name as parentTypeName ",
+                    "from t_jz_product_type t " +
+                            "left join t_jz_product_type pt on t.parent_id = pt.id where 1=1 " + sqlWhere,
+                    params.toArray());
+
+            if (recordPage == null) {
+                return new PageResult<>(0, pageNumber, pageSize, new ArrayList<>());
+            }
+
+            // 转换为Dto对象
+            List<ProductTypeDto> productTypeDtoList = new ArrayList<>();
+            for (Record record : recordPage.getList()) {
+                productTypeDtoList.add(convertRecordToDto(record));
+            }
+
+            // 创建新的Page对象,包含Dto列表
+            return new PageResult<>(recordPage.getTotalRow(),
+                    recordPage.getPageNumber(),
+                    recordPage.getPageSize(),
+                    productTypeDtoList);
+
+        } catch (Exception e) {
+            logger.error("查询产品分类列表异常:", e);
+            return new PageResult<>(0, pageNumber, pageSize, new ArrayList<>());
+        }
+    }
+
+    @Override
+    public ApiResponse getProductTypeById(Integer id) {
+        try {
+            Record record = Db.findFirst("select t.*, pt.type_name as parentTypeName from t_jz_product_type t left join t_jz_product_type pt on t.parent_id = pt.id where t.id = ?", id);
+            if (record == null) {
+                return ApiResponse.error("产品分类不存在");
+            }
+            return ApiResponse.success(convertRecordToDto(record));
+        } catch (Exception e) {
+            logger.error("根据ID获取产品分类信息异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public ApiResponse saveProductType(ProductTypeDto productTypeDto, AdminView loginUser) {
+        try {
+            // 数据验证
+            if (WxUtil.isNull(productTypeDto.getTypeName())) {
+                return ApiResponse.error("分类名称不能为空");
+            }
+
+            if (WxUtil.isNull(productTypeDto.getTypeNo())) {
+                return ApiResponse.error("分类编号不能为空");
+            }
+
+            // 检查分类名称是否已存在
+            if (checkTypeNameExists(productTypeDto.getTypeName(), null, productTypeDto.getParentId())) {
+                return ApiResponse.error("分类名称已存在");
+            }
+
+            // 检查分类编号是否已存在
+            if (checkTypeNoExists(productTypeDto.getTypeNo(), null)) {
+                return ApiResponse.error("分类编号已存在");
+            }
+
+            // 检查父分类是否存在
+            if (productTypeDto.getParentId() != null) {
+                Record parentRecord = Db.findById("t_jz_product_type", productTypeDto.getParentId());
+                if (parentRecord == null) {
+                    return ApiResponse.error("父分类不存在");
+                }
+            }
+
+            // 创建新记录
+            Record record = new Record();
+            record.set("type_name", productTypeDto.getTypeName());
+            record.set("parent_id", productTypeDto.getParentId());
+            record.set("type_no", productTypeDto.getTypeNo());
+            record.set("status", productTypeDto.getStatus() != null ? productTypeDto.getStatus() : 1);
+            record.set("create_time", new Date());
+
+            boolean save = Db.save("t_jz_product_type", record);
+            return save ? ApiResponse.success() : ApiResponse.error("保存产品分类信息失败");
+        } catch (Exception e) {
+            logger.error("保存产品分类信息异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public ApiResponse updateProductType(ProductTypeDto productTypeDto, AdminView loginUser) {
+        try {
+            // 检查ID是否存在
+            if (productTypeDto.getId() == null) {
+                return ApiResponse.error("分类ID不能为空");
+            }
+
+            Record record = Db.findById("t_jz_product_type", productTypeDto.getId());
+            if (record == null) {
+                return ApiResponse.error("产品分类不存在");
+            }
+
+            // 数据验证
+            if (WxUtil.isNull(productTypeDto.getTypeName())) {
+                return ApiResponse.error("分类名称不能为空");
+            }
+
+            if (WxUtil.isNull(productTypeDto.getTypeNo())) {
+                return ApiResponse.error("分类编号不能为空");
+            }
+
+            // 检查分类名称是否已存在(排除当前记录)
+            if (checkTypeNameExists(productTypeDto.getTypeName(), productTypeDto.getId(), productTypeDto.getParentId())) {
+                return ApiResponse.error("分类名称已存在");
+            }
+
+            // 检查分类编号是否已存在(排除当前记录)
+            if (checkTypeNoExists(productTypeDto.getTypeNo(), productTypeDto.getId())) {
+                return ApiResponse.error("分类编号已存在");
+            }
+
+            // 检查父分类是否存在
+            if (productTypeDto.getParentId() != null) {
+                Record parentRecord = Db.findById("t_jz_product_type", productTypeDto.getParentId());
+                if (parentRecord == null) {
+                    return ApiResponse.error("父分类不存在");
+                }
+                // 检查是否循环引用
+                if (productTypeDto.getParentId().equals(productTypeDto.getId())) {
+                    return ApiResponse.error("不能将自己设置为父分类");
+                }
+            }
+
+            // 更新记录
+            record.set("type_name", productTypeDto.getTypeName());
+            record.set("parent_id", productTypeDto.getParentId());
+            record.set("type_no", productTypeDto.getTypeNo());
+            record.set("status", productTypeDto.getStatus() != null ? productTypeDto.getStatus() : 1);
+
+            boolean update = Db.update("t_jz_product_type", record);
+            return update ? ApiResponse.success() : ApiResponse.error("更新产品分类信息失败");
+        } catch (Exception e) {
+            logger.error("更新产品分类信息异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public ApiResponse deleteProductType(Integer id) {
+        try {
+            // 检查ID是否存在
+            if (id == null) {
+                return ApiResponse.error("分类ID不能为空");
+            }
+
+            Record record = Db.findById("t_jz_product_type", id);
+            if (record == null) {
+                return ApiResponse.error("产品分类不存在");
+            }
+
+            // 检查是否有子分类
+            List<Record> children = Db.find("select id from t_jz_product_type where parent_id = ?", id);
+            if (CollUtil.isNotEmpty(children)) {
+                return ApiResponse.error("该分类下存在子分类,不能删除");
+            }
+
+            // 检查是否有产品使用该分类
+            List<Record> products = Db.find("select id from t_jz_product where product_type = ?", id);
+            if (CollUtil.isNotEmpty(products)) {
+                return ApiResponse.error("该分类下存在产品,不能删除");
+            }
+
+            boolean delete = Db.deleteById("t_jz_product_type", id);
+            return delete ? ApiResponse.success() : ApiResponse.error("删除产品分类信息失败");
+        } catch (Exception e) {
+            logger.error("删除产品分类信息异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public ApiResponse getAllProductTypes() {
+        try {
+            List<Record> recordList = Db.find("select id, type_name from t_jz_product_type where status = 1 and parent_id = 0");
+            return ApiResponse.success(recordList);
+        } catch (Exception e) {
+            logger.error("获取产品分类列表异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public ApiResponse getProductTypeTree() {
+        try {
+            // 获取所有产品分类
+            List<Record> allTypes = Db.find("select t.*, pt.type_name as parentTypeName from t_jz_product_type t left join t_jz_product_type pt on t.parent_id = pt.id order by id");
+            
+            // 转换为DTO列表
+            List<ProductTypeDto> typeDtoList = new ArrayList<>();
+            for (Record record : allTypes) {
+                typeDtoList.add(convertRecordToDto(record));
+            }
+            
+            // 构建树形结构
+            List<ProductTypeDto> tree = buildTree(typeDtoList);
+            
+            return ApiResponse.success(tree);
+        } catch (Exception e) {
+            logger.error("获取产品分类树形结构异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public boolean checkTypeNameExists(String typeName, Integer id, Integer parentId) {
+        try {
+            StringBuilder sql = new StringBuilder("select count(1) from t_jz_product_type where type_name = ?");
+            List<Object> params = new ArrayList<>();
+            params.add(typeName);
+            
+            if (id != null) {
+                sql.append(" and id != ?");
+                params.add(id);
+            }
+            
+            if (parentId != null) {
+                sql.append(" and parent_id = ?");
+                params.add(parentId);
+            } else {
+                sql.append(" and parent_id is null");
+            }
+            
+            Long count = Db.queryLong(sql.toString(), params.toArray());
+            return count != null && count > 0;
+        } catch (Exception e) {
+            logger.error("检查分类名称是否存在异常:", e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean checkTypeNoExists(String typeNo, Integer id) {
+        try {
+            StringBuilder sql = new StringBuilder("select count(1) from t_jz_product_type where type_no = ?");
+            List<Object> params = new ArrayList<>();
+            params.add(typeNo);
+            
+            if (id != null) {
+                sql.append(" and id != ?");
+                params.add(id);
+            }
+            
+            Long count = Db.queryLong(sql.toString(), params.toArray());
+            return count != null && count > 0;
+        } catch (Exception e) {
+            logger.error("检查分类编号是否存在异常:", e);
+            return false;
+        }
+    }
+
+    /**
+     * 将Record转换为ProductTypeDto的工具方法
+     */
+    private ProductTypeDto convertRecordToDto(Record record) {
+        ProductTypeDto dto = new ProductTypeDto();
+        dto.setId(record.getInt("id"));
+        dto.setTypeName(record.getStr("type_name"));
+        dto.setParentId(record.getInt("parent_id"));
+        dto.setParentTypeName(record.getStr("parentTypeName"));
+        dto.setTypeNo(record.getStr("type_no"));
+        dto.setStatus(record.getInt("status"));
+        dto.setCreateTime(DateUtil.format(record.getDate("create_time"), "yyyy-MM-dd HH:mm:ss"));
+        return dto;
+    }
+
+    /**
+     * 构建树形结构
+     */
+    private List<ProductTypeDto> buildTree(List<ProductTypeDto> typeList) {
+        List<ProductTypeDto> tree = new ArrayList<>();
+        Map<Integer, ProductTypeDto> typeMap = new HashMap<>();
+        
+        // 构建ID到DTO的映射
+        for (ProductTypeDto type : typeList) {
+            typeMap.put(type.getId(), type);
+        }
+        
+        // 构建树形结构
+        for (ProductTypeDto type : typeList) {
+            if (type.getParentId() == null) {
+                // 顶级分类
+                tree.add(type);
+            } else {
+                // 非顶级分类,添加到父分类的children中
+                ProductTypeDto parent = typeMap.get(type.getParentId());
+                if (parent != null) {
+                    if (parent.getChildren() == null) {
+                        parent.setChildren(new ArrayList<>());
+                    }
+                    parent.getChildren().add(type);
+                }
+            }
+        }
+        
+        return tree;
+    }
+}

+ 674 - 0
src/main/webapp/page/jinzai/deviceMonitor.jsp

@@ -0,0 +1,674 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+         pageEncoding="UTF-8" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<c:set value="<%=request.getContextPath()%>" var="ctx"></c:set>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>工控机监控记录</title>
+    <meta name="keywords" content="工控机监控">
+    <meta name="description" content="工控机监控记录页面">
+    <link href="${ctx}/css/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="${ctx}/css/font-awesome.css?v=4.4.0" rel="stylesheet">
+    <link href="${ctx}/css/bootstrap-select.min.css" rel="stylesheet">
+    <link href="${ctx}/css/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="${ctx}/css/animate.css" rel="stylesheet">
+    <link href="${ctx}/css/style.css?v=4.1.0" rel="stylesheet">
+    <style>
+        .form-inline-flex {
+            display: flex;
+            flex-wrap: wrap;
+            align-items: center;
+            gap: 5px;
+        }
+        
+        /* 小色块样式 */
+        .status-badge {
+            display: inline-block;
+            padding: 4px 8px;
+            border-radius: 4px;
+            color: white;
+            font-weight: bold;
+            text-align: center;
+            min-width: 80px;
+        }
+        
+        .status-green {
+            background-color: #1ab394;
+        }
+        
+        .status-yellow {
+            background-color: #f8ac59;
+        }
+        
+        .status-red {
+            background-color: red;
+        }
+        
+        /* 磁盘分区小色块样式 */
+        .disk-badge {
+            display: inline-block;
+            padding: 4px 8px;
+            border-radius: 4px;
+            color: white;
+            font-weight: bold;
+            text-align: center;
+            margin-right: 4px;
+            margin-bottom: 4px;
+        }
+        
+        .disk-c {
+            background-color: #1ab394; /* 绿色 */
+        }
+        
+        .disk-d {
+            background-color: #1c84c6; /* 蓝色 */
+        }
+        
+        .disk-e {
+            background-color: #f8ac59; /* 黄色 */
+        }
+        
+        .disk-f {
+            background-color: #842020; /* 红色 */
+        }
+        
+        .disk-other {
+            background-color: #999; /* 灰色 */
+        }
+        
+        /* 刷新按钮样式 */
+        .refresh-btn {
+            margin-left: 5px;
+            padding: 2px 6px;
+            font-size: 12px;
+        }
+        
+        @media (max-width: 768px) {
+            .form-inline-flex {
+                flex-direction: column;
+                align-items: stretch;
+            }
+            
+            .form-inline-flex .form-control,
+            .form-inline-flex .btn,
+            .form-inline-flex .selectpicker {
+                width: 100% !important;
+                margin-right: 0 !important;
+            }
+        }
+    </style>
+    <style type="text/css">
+        /* 小色块样式 */
+        .status-badge {
+            display: inline-block;
+            padding: 4px 8px;
+            border-radius: 4px;
+            color: white;
+            font-weight: bold;
+            text-align: center;
+            min-width: 80px;
+        }
+        
+        .status-green {
+            background-color: #1ab394;
+        }
+        
+        .status-yellow {
+            background-color: #f8ac59;
+        }
+        
+        .status-red {
+            background-color: red;
+        }
+        
+        /* 网络延迟颜色样式 - 使用更明显的颜色 */
+        .delay-good {
+            background-color: #28a745; /* 深绿色 */
+        }
+        
+        .delay-medium {
+            background-color: #ffc107; /* 明黄色 */
+        }
+        
+        .delay-bad {
+            background-color: #dc3545; /* 深红色 */
+        }
+        
+        /* 磁盘分区小色块样式 */
+        .disk-badge {
+            display: inline-block;
+            padding: 4px 8px;
+            border-radius: 4px;
+            color: white;
+            font-weight: bold;
+            text-align: center;
+            margin-right: 4px;
+            margin-bottom: 4px;
+        }
+        
+        .disk-c {
+            background-color: #1ab394; /* 绿色 */
+        }
+        
+        .disk-d {
+            background-color: #1c84c6; /* 蓝色 */
+        }
+        
+        .disk-e {
+            background-color: #f8ac59; /* 黄色 */
+        }
+        
+        .disk-f {
+            background-color: #842020; /* 红色 */
+        }
+        
+        .disk-other {
+            background-color: #999; /* 灰色 */
+        }
+        
+        /* 刷新按钮样式 */
+        .refresh-btn {
+            margin-left: 5px;
+            padding: 2px 6px;
+            font-size: 12px;
+        }
+        
+        @media (max-width: 768px) {
+            .form-inline-flex {
+                flex-direction: column;
+                align-items: stretch;
+            }
+            
+            .form-inline-flex .form-control,
+            .form-inline-flex .btn,
+            .form-inline-flex .selectpicker {
+                width: 100% !important;
+                margin-right: 0 !important;
+            }
+        }
+    </style>
+    <style type="text/css">
+        /* 小色块样式 */
+        .status-badge {
+            display: inline-block;
+            padding: 4px 8px;
+            border-radius: 4px;
+            color: white;
+            font-weight: bold;
+            text-align: center;
+            min-width: 80px;
+        }
+        
+        .status-green {
+            background-color: #1ab394;
+        }
+        
+        .status-yellow {
+            background-color: #f8ac59;
+        }
+        
+        .status-red {
+            background-color: red;
+        }
+        
+        /* 磁盘分区小色块样式 */
+        .disk-badge {
+            display: inline-block;
+            padding: 4px 8px;
+            border-radius: 4px;
+            color: white;
+            font-weight: bold;
+            text-align: center;
+            margin-right: 4px;
+            margin-bottom: 4px;
+        }
+        
+        .disk-c {
+            background-color: #1ab394; /* 绿色 */
+        }
+        
+        .disk-d {
+            background-color: #1c84c6; /* 蓝色 */
+        }
+        
+        .disk-e {
+            background-color: #f8ac59; /* 黄色 */
+        }
+        
+        .disk-f {
+            background-color: #842020; /* 红色 */
+        }
+        
+        .disk-other {
+            background-color: #999; /* 灰色 */
+        }
+        
+        /* 刷新按钮样式 */
+        .refresh-btn {
+            margin-left: 5px;
+            padding: 2px 6px;
+            font-size: 12px;
+        }
+        
+        @media (max-width: 768px) {
+            .form-inline-flex {
+                flex-direction: column;
+                align-items: stretch;
+            }
+            
+            .form-inline-flex .form-control,
+            .form-inline-flex .btn,
+            .form-inline-flex .selectpicker {
+                width: 100% !important;
+                margin-right: 0 !important;
+            }
+        }
+    </style>
+</head>
+
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-12">
+            <div class="ibox">
+                <div class="ibox-title">
+                    <div class="row">
+                        <div class="col-sm-10">
+                            <h3>工控机监控记录</h3>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="ibox-content">
+                    <div class="row row-lg mb-4">
+                        <div class="col-lg-12">
+                            <div class="form-inline-flex">
+                                <div style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="deviceId" class="form-control selectpicker" data-live-search="true" title="请选择工控机">
+                                        <!-- 工控机下拉选项将通过JavaScript动态加载 -->
+                                    </select>
+                                </div>
+                                <button type="button" id="searchBtn" class="btn btn-success" onclick="search();return false;" style="margin-right: 5px; margin-bottom: 5px;">
+                                    查询
+                                </button>
+                                <button type="button" id="resetBtn" class="btn btn-default" style="margin-right: 5px; margin-bottom: 5px;">
+                                    重置
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="row row-lg mt-3">
+                        <div class="col-sm-12">
+                            <table id="table" data-toggle="table" data-mobile-responsive="true"></table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="${ctx}/js/jquery.min.js?v=2.1.4"></script>
+<script src="${ctx}/js/bootstrap.min.js?v=3.3.6"></script>
+<script src="${ctx}/js/plugins/bootstrap-table/bootstrap-table.min.js"></script>
+<script src="${ctx}/js/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
+<script src="${ctx}/js/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
+<script src="${ctx}/js/plugins/layer/layer.min.js"></script>
+<script src="${ctx}/js/plugins/bootstrap-select/bootstrap-select.min.js"></script>
+<script src="${ctx}/js/plugins/validate/jquery.validate.min.js"></script>
+<script src="${ctx}/js/plugins/validate/messages_zh.min.js"></script>
+
+<script type="text/javascript">
+    var table = null;
+    
+    $(function() {
+        // 初始化工控机下拉列表
+        initDeviceList();
+        // 初始化表格
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+        // 绑定重置按钮点击事件
+        $('#resetBtn').on('click', function() {
+            reset();
+        });
+    });
+    
+    // 初始化工控机下拉列表
+    function initDeviceList() {
+        $.ajax({
+            url: '${ctx}/prodBatch/getDeviceList',
+            type: 'GET',
+            dataType: 'json',
+            success: function (data) {
+                if (data.code === 0) {
+                    var devices = data.data;
+                    var deviceSelect = $('#deviceId');
+                    deviceSelect.empty();
+                    deviceSelect.append('<option value="">请选择工控机</option>');
+
+                    $.each(devices, function(index, device) {
+                        deviceSelect.append('<option value="' + device.id + '">' + device.name + '</option>');
+                    });
+
+                    deviceSelect.selectpicker('refresh');
+                } else {
+                    layer.msg(data.msg, {icon: 5});
+                }
+            },
+            error: function () {
+                layer.msg('加载产品数据失败', {icon: 5});
+            }
+        });
+    }
+    
+    // 初始化表格
+    function initTable() {
+        // 记录开始时间
+        var startTime = new Date().getTime();
+        
+        table = $('#table').bootstrapTable({
+            method: 'get',
+            sortable: true,
+            toolbar: '#toolbar',    //工具按钮用哪个容器
+            striped: true,      //是否显示行间隔色
+            cache: false,      //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
+            pagination: true,     //是否显示分页(*)
+            pageNumber: 1,      //初始化加载第一页,默认第一页
+            pageSize: 10,      //每页的记录行数(*)
+            pageList: [10, 25, 50, 100],  //可供选择的每页的行数(*)
+            url: '${ctx}/prodBatch/getDeviceMonitorList',//这个接口需要处理bootstrap table传递的固定参数
+            queryParamsType: '', //默认值为 'limit' ,在默认情况下 传给服务端的参数为:offset,limit,sort
+            // 设置为 ''  在这种情况下传给服务器的参数为:pageSize,pageNumber
+
+            queryParams: queryParams,//前端调用服务时,会默认传递上边所述的参数,如果需要添加自定义参数,可以自定义一个函数返回请求参数
+            sidePagination: "server",   //分页方式:client客户端分页,server服务端分页(*)
+            strictSearch: false,
+            minimumCountColumns: 2,    //最少允许的列数
+            clickToSelect: true,    //是否启用点击选中行
+            searchOnEnterKey: true,
+            idField: "deviceId",
+            // 添加onBeforeLoad回调,在实际发起请求时记录开始时间
+            onBeforeLoad: function(params) {
+                // 存储开始时间到表格实例中
+                $(this).data('requestStartTime', new Date().getTime());
+                return true;
+            },
+            responseHandler: function (res) {
+                // 从表格实例中获取开始时间
+                var startTime = $(this).data('requestStartTime') || new Date().getTime();
+                // 计算网络延迟(毫秒转换为秒)
+                var endTime = new Date().getTime();
+                var delay = (endTime - startTime) / 1000;
+
+                // 如果返回的数据中有记录,为每条记录添加延迟字段
+                if (res.records && res.records.length > 0) {
+                    res.records.forEach(function(record) {
+                        // 确保正确设置networkDelay字段
+                        record.networkDelay = delay;
+                    });
+                }
+                // 这里假设接口返回的data就是我们需要的表格数据
+                return {
+                    total: res.total,  // 总记录数
+                    rows: res.records  // 数据列表
+                };
+            },
+            columns: [
+                {
+                    field: 'id',
+                    title: '序号',
+                    formatter: function (value, row, index) {
+                        // 使用this关键字访问表格实例
+                        var pageNumber = this.pageNumber || 1;
+                        var pageSize = this.pageSize || 10;
+                        return (pageNumber - 1) * pageSize + index + 1;
+                    },
+                    align: 'center',
+                    width: '60px'
+                },
+                {
+                    field: 'deviceName',
+                    title: '工控机名称',
+                    align: 'center'
+                },
+                {
+                    field: 'cupUsage',
+                    title: 'CPU使用率',
+                    align: 'center',
+                    formatter: function (value) {
+                        // 根据CPU使用率显示不同颜色的小色块
+                        var numValue = parseFloat(value);
+                        var statusClass = '';
+                        if (numValue < 50) {
+                            statusClass = 'status-green';
+                        } else {
+                            statusClass = 'status-red';
+                        }
+                        return '<div class="status-badge ' + statusClass + '">' + value + '</div>';
+                    }
+                },
+                {
+                    field: 'memoryUsage',
+                    title: '内存使用率',
+                    align: 'center',
+                    formatter: function (value) {
+                        // 根据内存使用率显示不同颜色的小色块
+                        var numValue = parseFloat(value);
+                        var statusClass = '';
+                        if (numValue < 80) {
+                            statusClass = 'status-green';
+                        } else {
+                            statusClass = 'status-red';
+                        }
+                        return '<div class="status-badge ' + statusClass + '">' + value + '</div>';
+                    }
+                },
+                {
+                    field: 'diskSurplus',
+                    title: '剩余硬盘容量(GB)',
+                    align: 'center',
+                    formatter: function (value) {
+                        // 解析JSON格式的磁盘剩余容量,并显示为不同颜色的小色块
+                        try {
+                            var diskData = JSON.parse(value);
+                            var result = [];
+                            for (var disk in diskData) {
+                                if (diskData.hasOwnProperty(disk)) {
+                                    // 根据磁盘分区显示不同颜色
+                                    var diskClass = '';
+                                    switch (disk.toUpperCase()) {
+                                        case 'C':
+                                            diskClass = 'disk-c';
+                                            break;
+                                        case 'D':
+                                            diskClass = 'disk-d';
+                                            break;
+                                        case 'E':
+                                            diskClass = 'disk-e';
+                                            break;
+                                        case 'F':
+                                            diskClass = 'disk-f';
+                                            break;
+                                        default:
+                                            diskClass = 'disk-other';
+                                    }
+                                    result.push('<div class="disk-badge ' + diskClass + '">' + disk + ':' + diskData[disk] + '</div>');
+                                }
+                            }
+                            return result.join('');
+                        } catch (e) {
+                            return value; // 如果解析失败,显示原始值
+                        }
+                    }
+                },
+                {
+                    field: 'factory',
+                    title: '工厂',
+                    align: 'center'
+                },
+                {
+                    field: 'workshop',
+                    title: '车间',
+                    align: 'center'
+                },
+                {
+                    field: 'line',
+                    title: '产线',
+                    align: 'center'
+                },
+                {
+                    field: 'productionTaskNum',
+                    title: '正在进行的生产任务',
+                    align: 'center'
+                },
+                {
+                    field: 'product',
+                    title: '产品名称',
+                    align: 'center'
+                },
+                {
+                    // 修改表格中的networkDelay列,添加formatter函数
+                    field: 'networkDelay',
+                    title: '延迟(S)',
+                    align: 'center',
+                    formatter: function (value) {
+                        // 根据延迟时间显示不同颜色的小色块,调整阈值为6秒以上显示红色
+                        var numValue = parseFloat(value) || 0;
+                        var delayClass = '';
+                        if (numValue < 0.5) {
+                            delayClass = 'delay-good';
+                        } else if (numValue < 6) {
+                            delayClass = 'delay-medium';
+                        } else {
+                            delayClass = 'delay-bad';
+                        }
+                        return '<div class="status-badge ' + delayClass + '">' + numValue.toFixed(2) + '</div>';
+                    }
+                },
+                {
+                    field: 'productionNum',
+                    title: '目前生产数量(件)',
+                    align: 'center'
+                },
+                {
+                    field: 'totalNum',
+                    title: '总数',
+                    align: 'center'
+                },
+                {
+                    field: 'platformNum',
+                    title: '已传平台数量',
+                    align: 'center'
+                },
+                {
+                    field: 'action',
+                    title: '操作',
+                    align: 'center',
+                    width: '80px',
+                    formatter: function (value, row) {
+                        return '<button type="button" class="btn btn-xs btn-info" onclick="refreshRow(\'' + row.deviceId + '\')">' +
+                               '<i class="fa fa-refresh"></i> 刷新</button>';
+                    }
+                }
+            ]
+        });
+    }
+    
+    // 构建查询参数
+    function queryParams(params) {
+        var deviceId = $.trim($("#deviceId").val());
+        
+        var queryParams = {
+            pageSize: params.pageSize,
+            pageNumber: params.pageNumber
+        };
+        
+        if (deviceId) {
+            queryParams.deviceId = deviceId;
+        }
+        
+        return queryParams;
+    }
+    
+    // 查询函数
+    function search() {
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+    }
+    
+    // 重置函数
+    function reset() {
+        $("#deviceId").val('').selectpicker('refresh');
+    }
+    
+    // 刷新表格
+    function refreshTable() {
+        $('#table').bootstrapTable('refresh');
+        layer.msg('刷新成功', {icon: 1});
+    }
+    
+    // 刷新整行数据
+    function refreshRow(deviceId) {
+        // 显示加载中提示
+        var loadingIndex = layer.load(1, {shade: [0.5, '#fff']});
+        
+        // 记录开始时间
+        var startTime = new Date().getTime();
+        
+        // 发送请求刷新单行数据
+        $.ajax({
+            url: '${ctx}/prodBatch/refreshDeviceDetail',
+            type: 'GET',
+            data: {
+                deviceId: deviceId
+            },
+            success: function (result) {
+                if (result.code === 0 && result.data) {
+                    // 计算网络延迟(毫秒转换为秒)
+                    var endTime = new Date().getTime();
+                    var delay = (endTime - startTime) / 1000;
+
+                    result.data.networkDelay = delay;
+                    
+                    // 获取表格数据
+                    var data = $('#table').bootstrapTable('getData');
+                    
+                    // 查找对应行的索引
+                    var rowIndex = -1;
+                    for (var i = 0; i < data.length; i++) {
+                        if (String(data[i].deviceId) === String(deviceId)) {
+                            rowIndex = i;
+                            break;
+                        }
+                    }
+                    
+                    if (rowIndex !== -1) {
+                        // 使用updateRow方法更新行数据(兼容更多版本)
+                        $('#table').bootstrapTable('updateRow', {
+                            index: rowIndex,
+                            row: result.data
+                        });
+                    } else {
+                        // 如果找不到对应行,刷新整个表格
+                        $('#table').bootstrapTable('refresh');
+                    }
+                    
+                    layer.msg('刷新成功', {icon: 1});
+                } else {
+                    layer.msg(result.msg || '刷新失败', {icon: 2});
+                }
+            },
+            error: function () {
+                layer.msg('刷新失败', {icon: 2});
+            },
+            complete: function () {
+                layer.close(loadingIndex);
+            }
+        });
+    }
+</script>
+</body>
+</html>

+ 512 - 0
src/main/webapp/page/jinzai/productTypeList.jsp

@@ -0,0 +1,512 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<c:set value="<%=request.getContextPath()%>" var="ctx"></c:set>
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>产品分类管理</title>
+    <meta name="keywords" content="产品分类管理">
+    <meta name="description" content="产品分类信息管理页面">
+    <link href="${ctx}/css/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="${ctx}/css/font-awesome.css?v=4.4.0" rel="stylesheet">
+    <link href="${ctx}/css/bootstrap-select.min.css" rel="stylesheet">
+    <link href="${ctx}/css/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="${ctx}/css/animate.css" rel="stylesheet">
+    <link href="${ctx}/css/style.css?v=4.1.0" rel="stylesheet">
+    <style>
+        .required::before {
+            content: '*';
+            color: red;
+            margin-right: 5px;
+        }
+
+        .row.mb-4 {
+            margin-bottom: 1.5rem;
+        }
+
+        .form-group {
+            margin-bottom: 1.5rem;
+        }
+
+        .toggle-switch {
+            position: relative;
+            display: inline-block;
+            width: 60px;
+            height: 34px;
+        }
+
+        .toggle-switch input {
+            opacity: 0;
+            width: 0;
+            height: 0;
+        }
+
+        .slider {
+            position: absolute;
+            cursor: pointer;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background-color: #ccc;
+            transition: .4s;
+            border-radius: 34px;
+        }
+
+        .slider:before {
+            position: absolute;
+            content: "";
+            height: 26px;
+            width: 26px;
+            left: 4px;
+            bottom: 4px;
+            background-color: white;
+            transition: .4s;
+            border-radius: 50%;
+        }
+
+        input:checked + .slider {
+            background-color: #2196F3;
+        }
+
+        input:checked + .slider:before {
+            transform: translateX(26px);
+        }
+
+        .form-container {
+            padding: 20px;
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+            background-color: #fff;
+        }
+
+        .form-inline-flex {
+            display: flex;
+            flex-wrap: wrap;
+            align-items: center;
+            gap: 5px;
+        }
+
+        @media (max-width: 768px) {
+            .form-inline-flex {
+                flex-direction: column;
+                align-items: stretch;
+            }
+
+            .form-inline-flex .form-control,
+            .form-inline-flex .btn,
+            .form-inline-flex .selectpicker {
+                width: 100% !important;
+                margin-right: 0 !important;
+            }
+        }
+    </style>
+</head>
+
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-12">
+            <div class="ibox">
+                <div class="ibox-title">
+                    <div class="row">
+                        <div class="col-sm-10">
+                            <h3>产品分类管理</h3>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="ibox-content">
+                    <div class="row row-lg mb-4">
+                        <div class="col-lg-12">
+                            <div class="form-inline-flex">
+                                <input id="typeName" class="form-control" style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;" placeholder="请输入分类名称"/>
+                                <button type="button" id="searchBtn" class="btn btn-success" onclick="search();return false;" style="margin-right: 5px; margin-bottom: 5px;">查询
+                                </button>
+                                <button type="button" id="resetBtn" class="btn btn-default" style="margin-right: 5px; margin-bottom: 5px;">重置
+                                </button>
+                                <button type="button" class="btn btn-primary" onclick="showAddModal();" style="margin-bottom: 5px;">新增
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="row row-lg mt-3">
+                        <div class="col-sm-12">
+                            <table id="table" data-toggle="table" data-mobile-responsive="true"></table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 新增/编辑产品分类模态框 -->
+<div class="modal fade" id="productTypeModal" tabindex="-1" role="dialog" aria-labelledby="productTypeModalLabel">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                <h4 class="modal-title" id="productTypeModalLabel">新增产品分类</h4>
+            </div>
+            <div class="modal-body">
+                <div class="form-container">
+                    <form id="productTypeForm">
+                        <input type="hidden" id="typeId" name="typeId">
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalTypeNo" class="required control-label">类别编号</label>
+                                <input type="text" class="form-control" id="modalTypeNo" name="modalTypeNo" placeholder="请输入" required>
+                            </div>
+                            <div class="col-md-6">
+                                <label for="modalTypeName" class="required control-label">类别名称</label>
+                                <input type="text" class="form-control" id="modalTypeName" name="modalTypeName" placeholder="请输入" required>
+                            </div>
+                        </div>
+
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalParentId" class="control-label">上级类别</label>
+                                <select id="modalParentId" class="form-control selectpicker" data-live-search="true" title="请选择上级类别">
+                                    <option value="0">无</option>
+                                    <!-- 上级类别下拉框选项将通过JavaScript动态加载 -->
+                                </select>
+                            </div>
+                            <div class="col-md-6">
+                                <label class="required control-label">状态</label>
+                                <div class="d-flex align-items-center gap-2">
+                                    <label class="toggle-switch">
+                                        <input type="checkbox" id="modalStatus" name="modalStatus" checked>
+                                        <span class="slider"></span>
+                                    </label>
+                                    <span id="statusText">启用</span>
+                                    <div class="text-muted">状态:启用、禁用,默认启用</div>
+                                </div>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary" onclick="saveProductType();">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 全局js -->
+<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+<script src="${ctx}/js/jquery.min.js?v=2.1.4"></script>
+<script src="${ctx}/js/bootstrap.min.js?v=3.3.6"></script>
+<script src="${ctx}/js/bootstrap-select.min.js"></script>
+<!-- 自定义js -->
+<script src="${ctx}/js/content.js?v=1.0.0"></script>
+<!-- jQuery Validation plugin javascript-->
+<script src="${ctx}/js/plugins/validate/jquery.validate.min.js"></script>
+<script src="${ctx}/js/plugins/validate/messages_zh.min.js"></script>
+<script src="${ctx}/js/plugins/bootstrap-table/bootstrap-table.min.js"></script>
+<script src="${ctx}/js/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
+<script src="${ctx}/js/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
+<script src="${ctx}/js/common.js"></script>
+<script src="${ctx}/js/plugins/layer/layer.min.js"></script>
+<script src="${ctx}/js/Math.uuid.js"></script>
+</body>
+<script>
+    var table = null;
+    var productTypes = []; // 存储产品分类数据
+
+    $(document).ready(function () {
+        // 加载产品分类数据
+        loadProductTypes();
+
+        // 初始化表格
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+        initValidation();
+
+        // 绑定重置按钮点击事件
+        $('#resetBtn').on('click', function() {
+            reset();
+        });
+
+        // 状态切换按钮
+        const statusToggle = document.getElementById('modalStatus');
+        const statusText = document.getElementById('statusText');
+
+        statusToggle.addEventListener('change', function () {
+            if (this.checked) {
+                statusText.textContent = '启用';
+            } else {
+                statusText.textContent = '禁用';
+            }
+        });
+    });
+
+    // 加载产品分类数据
+    function loadProductTypes() {
+        $.ajax({
+            url: '${ctx}/productTypeNew/getAllProductTypes',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#modalParentId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#modalParentId').empty();
+                if (res.data && res.data.length) {
+                    res.data.forEach(item => {
+                        $('#modalParentId').append('<option value="' + item.id + '">' + item.type_name + '</option>');
+                    });
+                }
+                $('#modalParentId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#modalParentId').empty().append('<option value="">加载失败</option>');
+                $('#modalParentId').selectpicker('refresh');
+                layer.msg('获取品牌数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#modalParentId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+
+    // 初始化表格
+    function initTable() {
+        table = $('#table').bootstrapTable({
+            method: 'get',
+            sortable: true,
+            toolbar: '#toolbar',    //工具按钮用哪个容器
+            striped: true,      //是否显示行间隔色
+            cache: false,      //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
+            pagination: true,     //是否显示分页(*)
+            pageNumber: 1,      //初始化加载第一页,默认第一页
+            pageSize: 10,      //每页的记录行数(*)
+            pageList: [10, 25, 50, 100],  //可供选择的每页的行数(*)
+            url: '${ctx}/productTypeNew/list',//这个接口需要处理bootstrap table传递的固定参数
+            queryParamsType: '', //默认值为 'limit' ,在默认情况下 传给服务端的参数为:offset,limit,sort
+            // 设置为 ''  在这种情况下传给服务器的参数为:pageSize,pageNumber
+
+            queryParams: queryParams,//前端调用服务时,会默认传递上边所述的参数,如果需要添加自定义参数,可以自定义一个函数返回请求参数
+            sidePagination: "server",   //分页方式:client客户端分页,server服务端分页(*)
+            strictSearch: false,
+            minimumCountColumns: 2,    //最少允许的列数
+            clickToSelect: true,    //是否启用点击选中行
+            searchOnEnterKey: true,
+            idField: "id",
+            responseHandler: function (res) {
+                // 这里假设接口返回的data就是我们需要的表格数据
+                return {
+                    total: res.total,  // 总记录数
+                    rows: res.records  // 数据列表
+                };
+            },
+            columns: [
+                {
+                    field: 'id',
+                    title: '序号',
+                    formatter: function (value, row, index) {
+                        // 使用this关键字访问表格实例
+                        var pageNumber = this.pageNumber || 1;
+                        var pageSize = this.pageSize || 10;
+                        return (pageNumber - 1) * pageSize + index + 1;
+                    },
+                    align: 'center',
+                },
+                {
+                    field: 'typeName',
+                    title: '类别名称',
+                    align: 'center'
+                },
+                {
+                    field: 'typeNo',
+                    title: '类别编号',
+                    align: 'center'
+                },
+                {
+                    field: 'parentTypeName',
+                    title: '上级分类',
+                    align: 'center'
+                },
+                {
+                    field: 'status',
+                    title: '状态',
+                    align: 'center',
+                    formatter: function(value) {
+                        return value === 1 ? '<span class="label label-primary">启用</span>' : '<span class="label label-danger">禁用</span>';
+                    }
+                },
+                {
+                    field: 'action',
+                    title: '操作',
+                    align: 'center',
+                    formatter: function(value, row) {
+                        let actions = '';
+                        actions += '<button type="button" class="btn btn-primary btn-xs" onclick="showEditModal(' + row.id + ')">编辑</button> ';
+                        actions += '<button type="button" class="btn btn-danger btn-xs" onclick="deleteProductType(' + row.id + ')">删除</button>';
+                        return actions;
+                    }
+                }
+            ]
+        });
+    }
+
+    // 查询参数
+    function queryParams(params) {
+        return {
+            pageNumber: params.pageNumber,
+            pageSize: params.pageSize,
+            typeName: $('#typeName').val()
+        };
+    }
+
+    // 初始化表单验证
+    function initValidation() {
+        $('#productTypeForm').validate({
+            rules: {
+                modalTypeNo: {
+                    required: true,
+                    maxlength: 20
+                },
+                modalTypeName: {
+                    required: true,
+                    maxlength: 50
+                }
+            },
+            messages: {
+                modalTypeNo: {
+                    required: "请输入类别编号",
+                    maxlength: "类别编号不能超过20个字符"
+                },
+                modalTypeName: {
+                    required: "请输入类别名称",
+                    maxlength: "类别名称不能超过50个字符"
+                }
+            }
+        });
+    }
+
+    // 搜索
+    function search() {
+        table.bootstrapTable('refresh');
+    }
+
+    // 重置
+    function reset() {
+        $('#typeName').val('');
+        table.bootstrapTable('refresh');
+    }
+
+    // 显示新增模态框
+    function showAddModal() {
+        $('#productTypeModalLabel').text('新增产品分类');
+        $('#productTypeForm')[0].reset();
+        $('#typeId').val('');
+        $('#modalStatus').prop('checked', true);
+        $('#statusText').text('启用');
+        loadProductTypes(); // 重新加载分类数据以确保最新
+        $('#productTypeModal').modal('show');
+    }
+
+    // 显示编辑模态框
+    function showEditModal(id) {
+        loadProductTypes();
+        $.ajax({
+            url: '${ctx}/productTypeNew/getById?id=' + id,
+            type: 'GET',
+            dataType: 'json',
+            success: function (data) {
+                if (data.code === 0 && data.data) {
+                    const typeData = data.data;
+                    $('#productTypeModalLabel').text('编辑产品分类');
+                    $('#typeId').val(typeData.id);
+                    $('#modalTypeNo').val(typeData.typeNo);
+                    $('#modalTypeName').val(typeData.typeName);
+                    $('#modalStatus').prop('checked', typeData.status === 1);
+                    $('#statusText').text(typeData.status === 1 ? '启用' : '禁用');
+                    $('#productTypeModal').modal('show');
+
+                    // 设置销售地区,需要等待selectpicker初始化完成后再设置
+                    setTimeout(function () {
+                        if (typeData.parentId) {
+                            $('#modalParentId').val(typeData.parentId);
+                            $('#modalParentId').selectpicker('refresh');
+                        }
+                    }, 500);
+                } else {
+                    layer.alert(data.msg || '获取分类信息失败', {icon: 2});
+                }
+            },
+            error: function () {
+                layer.alert('获取分类信息失败', {icon: 2});
+            }
+        });
+    }
+
+    // 保存产品分类
+    function saveProductType() {
+        if (!$('#productTypeForm').valid()) {
+            return;
+        }
+
+        const typeId = $('#typeId').val();
+        const url = typeId ? '${ctx}/productTypeNew/update' : '${ctx}/productTypeNew/save';
+
+        const productTypeData = {
+            id: typeId ? parseInt(typeId) : null,
+            typeNo: $('#modalTypeNo').val(),
+            typeName: $('#modalTypeName').val(),
+            parentId: parseInt($('#modalParentId').val()),
+            status: $('#modalStatus').is(':checked') ? 1 : 0
+        };
+
+        $.ajax({
+            url: url,
+            type: 'POST',
+            data: JSON.stringify(productTypeData),
+            contentType: 'application/json',
+            dataType: 'json',
+            success: function (data) {
+                if (data.code === 0) {
+                    layer.alert(data.msg || '保存成功', {icon: 1});
+                    $('#productTypeModal').modal('hide');
+                    table.bootstrapTable('refresh');
+                } else {
+                    layer.alert(data.msg || '保存失败', {icon: 2});
+                }
+            },
+            error: function () {
+                layer.alert('保存失败', {icon: 2});
+            }
+        });
+    }
+
+    // 删除产品分类
+    function deleteProductType(id) {
+        layer.confirm('确定要删除该分类吗?删除后可能影响相关产品数据!', {
+            btn: ['确定', '取消']
+        }, function() {
+            $.ajax({
+                url: '${ctx}/productTypeNew/delete?id=' + id,
+                type: 'GET',
+                dataType: 'json',
+                success: function (data) {
+                    if (data.code === 0) {
+                        layer.alert(data.msg || '删除成功', {icon: 1});
+                        table.bootstrapTable('refresh');
+                    } else {
+                        layer.alert(data.msg || '删除失败', {icon: 2});
+                    }
+                },
+                error: function () {
+                    layer.alert('删除失败', {icon: 2});
+                }
+            });
+        });
+    }
+</script>
+</html>

+ 218 - 109
src/main/webapp/page/jinzai/production_task_upload.jsp

@@ -10,10 +10,8 @@
     <title>生产任务上传记录</title>
     <meta name="keywords" content="生产任务,上传记录">
     <meta name="description" content="生产任务上传记录管理页面">
-    <!-- 引入Bootstrap CSS -->
-    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
-    <!-- 引入Font Awesome图标 -->
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
+    <link href="${ctx}/css/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="${ctx}/css/font-awesome.css?v=4.4.0" rel="stylesheet">
     <link href="${ctx}/css/bootstrap-select.min.css" rel="stylesheet">
     <link href="${ctx}/css/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
     <link href="${ctx}/css/animate.css" rel="stylesheet">
@@ -25,16 +23,86 @@
             margin-right: 5px;
         }
 
+        .row.mb-4 {
+            margin-bottom: 1.5rem;
+        }
+
         .form-group {
             margin-bottom: 1.5rem;
         }
 
+        .toggle-switch {
+            position: relative;
+            display: inline-block;
+            width: 60px;
+            height: 34px;
+        }
+
+        .toggle-switch input {
+            opacity: 0;
+            width: 0;
+            height: 0;
+        }
+
+        .slider {
+            position: absolute;
+            cursor: pointer;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background-color: #ccc;
+            transition: .4s;
+            border-radius: 34px;
+        }
+
+        .slider:before {
+            position: absolute;
+            content: "";
+            height: 26px;
+            width: 26px;
+            left: 4px;
+            bottom: 4px;
+            background-color: white;
+            transition: .4s;
+            border-radius: 50%;
+        }
+
+        input:checked + .slider {
+            background-color: #2196F3;
+        }
+
+        input:checked + .slider:before {
+            transform: translateX(26px);
+        }
+
         .form-container {
             padding: 20px;
             border-radius: 8px;
             box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
             background-color: #fff;
         }
+
+        .form-inline-flex {
+            display: flex;
+            flex-wrap: wrap;
+            align-items: center;
+            gap: 5px;
+        }
+
+        @media (max-width: 768px) {
+            .form-inline-flex {
+                flex-direction: column;
+                align-items: stretch;
+            }
+
+            .form-inline-flex .form-control,
+            .form-inline-flex .btn,
+            .form-inline-flex .selectpicker {
+                width: 100% !important;
+                margin-right: 0 !important;
+            }
+        }
     </style>
 </head>
 
@@ -54,41 +122,30 @@
                 <div class="ibox-content">
                     <div class="row row-lg mb-4">
                         <div class="col-lg-12">
-                            <form id="searchForm" class="form-horizontal">
-                                <div class="row mb-3">
-                                    <div class="col-sm-3">
-                                        <label for="createTime" class="form-label">创建时间</label>
-                                        <input type="date" class="form-control" id="createTime" name="createTime">
-                                    </div>
-                                    <div class="col-sm-3">
-                                        <label for="factoryName" class="form-label">工厂名称</label>
-                                        <input type="text" class="form-control" id="factoryName" name="factoryName" placeholder="请输入工厂名称">
-                                    </div>
-                                    <div class="col-sm-3">
-                                        <label for="productName" class="form-label">产品名称</label>
-                                        <input type="text" class="form-control" id="productName" name="productName" placeholder="请输入产品名称">
-                                    </div>
-                                    <div class="col-sm-3">
-                                        <label for="wmsStatus" class="form-label">WMS状态</label>
-                                        <select class="form-select" id="wmsStatus" name="wmsStatus">
-                                            <option value="">全部</option>
-                                            <option value="1">成功</option>
-                                            <option value="2">失败</option>
-                                            <option value="3">重试中</option>
-                                        </select>
-                                    </div>
+                            <div class="form-inline-flex">
+                                <input type="date" class="form-control" id="createTime" name="createTime" style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="factoryId" class="form-control selectpicker" data-live-search="true" title="请选择工厂">
+                                    </select>
                                 </div>
-                                <div class="row">
-                                    <div class="col-sm-12 text-right">
-                                        <button type="button" id="searchBtn" class="btn btn-success ms-2" onclick="search();return false;">
-                                            <i class="fas fa-search"></i> 查询
-                                        </button>
-                                        <button type="button" id="resetBtn" class="btn btn-secondary ms-2" onclick="reset();return false;">
-                                            <i class="fas fa-sync-alt"></i> 重置
-                                        </button>
-                                    </div>
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="productId" class="form-control selectpicker" data-live-search="true" title="请选择产品">
+                                    </select>
                                 </div>
-                            </form>
+                                <select id="wmsStatus" class="form-control" style="width: 120px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <option value="">全部</option>
+                                    <option value="1">成功</option>
+                                    <option value="2">失败</option>
+                                    <option value="3">重试中</option>
+                                </select>
+                                <button type="button" id="searchBtn" class="btn btn-success"
+                                        onclick="search();return false;" style="margin-right: 5px; margin-bottom: 5px;">
+                                    查询
+                                </button>
+                                <button type="button" id="resetBtn" class="btn btn-default"
+                                        onclick="reset();return false;" style="margin-right: 5px; margin-bottom: 5px;">重置
+                                </button>
+                            </div>
                         </div>
                     </div>
                     <div class="row row-lg mt-3">
@@ -103,9 +160,8 @@
 </div>
 
 <!-- 全局js -->
-<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
-<!-- 引入Bootstrap JS -->
-<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
+<script src="${ctx}/js/jquery.min.js?v=2.1.4"></script>
+<script src="${ctx}/js/bootstrap.min.js?v=3.3.6"></script>
 <script src="${ctx}/js/bootstrap-select.min.js"></script>
 <!-- jQuery Validation plugin javascript-->
 <script src="${ctx}/js/plugins/validate/jquery.validate.min.js"></script>
@@ -125,23 +181,35 @@
     $(document).ready(function () {
         table = $('#table').bootstrapTable("destroy");
         initTable();
+        $('.selectpicker').selectpicker({
+            liveSearch: true,
+            size: 5,
+            actionsBox: true,
+            selectedTextFormat: 'count > 2'
+        });
+        initFactorySelect();
+        initProductSelect();
+        // 绑定重置按钮点击事件
+        $('#resetBtn').on('click', function () {
+            reset();
+        });
     });
 
     function queryParams(param) {
         // 获取查询参数
         let createTime = $.trim($("#createTime").val());
-        let factoryName = $.trim($("#factoryName").val());
-        let productName = $.trim($("#productName").val());
+        let factoryId = $.trim($("#factoryId").val());
+        let productId = $.trim($("#productId").val());
         let wmsStatus = $.trim($("#wmsStatus").val());
 
         if (createTime) {
             param['createTime'] = createTime;
         }
-        if (factoryName) {
-            param['factoryName'] = factoryName;
+        if (factoryId) {
+            param['factoryId'] = factoryId;
         }
-        if (productName) {
-            param['productName'] = productName;
+        if (productId) {
+            param['productId'] = productId;
         }
         if (wmsStatus) {
             param['wmsStatus'] = wmsStatus;
@@ -151,14 +219,18 @@
     }
 
     function search() {
-        table = $('#table').bootstrapTable("destroy");
-        initTable();
+        table = $('#table').bootstrapTable("refresh");
     }
 
     function reset() {
-        $("#searchForm")[0].reset();
-        table = $('#table').bootstrapTable("destroy");
-        initTable();
+        // 重置表单
+        $("#createTime").val("");
+        $("#factoryId").selectpicker('val', '');
+        $("#productId").selectpicker('val', '');
+        $("#wmsStatus").val("");
+
+        // 刷新表格
+        table = $('#table').bootstrapTable("refresh");
     }
 
     function reupload(id) {
@@ -174,31 +246,90 @@
         });
     }
 
-    function initTable() {
-        // 模拟数据
-        var mockData = generateMockData();
+    // 加载工厂数据
+    function initFactorySelect() {
+        $.ajax({
+            url: '${ctx}/lineProduct/getFactoryList',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#factoryId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#factoryId').empty();
+                if (res.data && res.data.length) {
+                    res.data.forEach(item => {
+                        $('#factoryId').append('<option value="' + item.id + '">' + item.factory_name + '</option>');
+                    });
+                }
+                $('#factoryId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#factoryId').empty().append('<option value="">加载失败</option>');
+                $('#factoryId').selectpicker('refresh');
+                layer.msg('获取工厂数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#factoryId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
 
+    // 加载产品数据
+    function initProductSelect() {
+        $.ajax({
+            url: '${ctx}/lineProduct/getProductList',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#productId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#productId').empty();
+                if (res.data && res.data.length) {
+                    res.data.forEach(item => {
+                        $('#productId').append('<option value="' + item.id + '">' + item.product_name + '</option>');
+                    });
+                }
+                $('#productId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#productId').empty().append('<option value="">加载失败</option>');
+                $('#productId').selectpicker('refresh');
+                layer.msg('获取产品数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#productId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    function initTable() {
         table = $('#table').bootstrapTable({
-            data: mockData,
+            url: '${ctx}/prodBatch/getProdUploadRecord',
             method: 'get',
             sortable: true,
-            toolbar: '#toolbar',    //工具按钮用哪个容器
-            striped: true,      //是否显示行间隔色
-            cache: false,      //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
-            pagination: true,     //是否显示分页(*)
-            pageNumber: 1,      //初始化加载第一页,默认第一页
-            pageSize: 10,      //每页的记录行数(*)
-            pageList: [10, 25, 50, 100],  //可供选择的每页的行数(*)
-            queryParamsType: '', //默认值为 'limit' ,在默认情况下 传给服务端的参数为:offset,limit,sort
-            // 设置为 ''  在这种情况下传给服务器的参数为:pageSize,pageNumber
-            queryParams: queryParams,//前端调用服务时,会默认传递上边提到的参数,如果需要添加自定义参数,可以自定义一个函数返回请求参数
-            sidePagination: "client",   //客户端分页
+            toolbar: '#toolbar',
+            striped: true,
+            cache: false,
+            pagination: true,
+            pageNumber: 1,
+            pageSize: 10,
+            pageList: [10, 25, 50, 100],
+            queryParamsType: '',
+            queryParams: queryParams,
+            sidePagination: "server",
             strictSearch: false,
-            minimumCountColumns: 2,    //最少允许的列数
-            clickToSelect: true,    //是否启用点击选中行
+            minimumCountColumns: 2,
+            clickToSelect: true,
             searchOnEnterKey: true,
             idField: "id",
-            paginationDetailHAlign: 'right', // 将分页详细信息放在右边
+            responseHandler: function (res) {
+                return {
+                    total: res.total,
+                    rows: res.records
+                };
+            },
             columns: [{
                 field: 'checkbox',
                 title: '<input type="checkbox" id="checkAll">',
@@ -214,7 +345,6 @@
                 title: '序号',
                 align: 'center',
                 formatter: function (value, row, index) {
-                    // 使用this关键字访问表格实例
                     var pageNumber = this.pageNumber || 1;
                     var pageSize = this.pageSize || 10;
                     return (pageNumber - 1) * pageSize + index + 1;
@@ -231,7 +361,12 @@
                 align: 'center',
                 width: "15%"
             }, {
-                field: 'batchNumber',
+                field: 'productName',
+                title: '产品名称',
+                align: 'center',
+                width: "15%"
+            }, {
+                field: 'batchNo',
                 title: '批次号',
                 align: 'center',
                 width: "15%"
@@ -257,23 +392,25 @@
                 title: 'WMS状态',
                 align: 'center',
                 formatter: function (value) {
-                    if (value === 1) {
-                        return '<span class="badge bg-success">成功</span>';
-                    } else if (value === 2) {
-                        return '<span class="badge bg-danger">失败</span>';
-                    } else if (value === 3) {
-                        return '<span class="badge bg-warning">重试中</span>';
+                    // 修复:后端返回的是字符串,需要使用字符串比较
+                    if (value === '1') {
+                        return '<span class="label label-success">成功</span>';
+                    } else if (value === '2') {
+                        return '<span class="label label-danger">失败</span>';
+                    } else if (value === '3') {
+                        return '<span class="label label-warning">重试中</span>';
                     } else {
                         return '-';
                     }
                 },
                 width: "10%"
             }, {
-                field: 'transferredToPlatform',
+                field: 'isUpload',
                 title: '已传到平台',
                 align: 'center',
                 formatter: function (value) {
-                    if (value === 1) {
+                    // 修复:后端返回的是字符串,需要使用字符串比较
+                    if (value === '1') {
                         return '<span class="label label-success">是</span>';
                     } else {
                         return '<span class="label label-danger">否</span>';
@@ -285,8 +422,8 @@
                 align: 'center',
                 width: "10%",
                 formatter: function (value, row) {
-                    // 只有失败和重试中可以重传
-                    if (row.wmsStatus === 2 || row.wmsStatus === 3) {
+                    // 修复:后端返回的是字符串,需要使用字符串比较
+                    if (row.wmsStatus === '2' || row.wmsStatus === '3') {
                         return '<button class="btn btn-primary btn-sm" onclick="reupload(' + row.id + ')">重传</button>';
                     } else {
                         return '-';
@@ -309,33 +446,5 @@
             }
         });
     }
-
-    // 生成模拟数据
-    function generateMockData() {
-        var factories = ['金华工厂', '杭州工厂', '上海工厂', '苏州工厂', '广州工厂'];
-        var lines = ['A线', 'B线', 'C线', 'D线', 'E线'];
-        var products = ['食品A', '食品B', '食品C', '食品D', '食品E'];
-        var mockData = [];
-
-        for (var i = 1; i <= 50; i++) {
-            var wmsStatus = Math.floor(Math.random() * 3) + 1; // 1-3
-            var date = new Date();
-            date.setDate(date.getDate() - Math.floor(Math.random() * 30));
-            var formattedDate = date.toISOString().split('T')[0];
-
-            mockData.push({
-                id: i,
-                factoryName: factories[Math.floor(Math.random() * factories.length)],
-                lineName: '产线' + lines[Math.floor(Math.random() * lines.length)],
-                batchNumber: 'BAT' + Date.now().toString().slice(-6) + i,
-                transferredToDataCenter: Math.floor(Math.random() * 2), // 0或1
-                createTime: formattedDate,
-                wmsStatus: wmsStatus,
-                transferredToPlatform: Math.floor(Math.random() * 2) // 0或1
-            });
-        }
-
-        return mockData;
-    }
 </script>
 </html>