فهرست منبع

feature:
1、需求功能提交
2、Netty服务端重构

yingjian.wu 5 ماه پیش
والد
کامیت
952e9054ac
46فایلهای تغییر یافته به همراه7160 افزوده شده و 179 حذف شده
  1. 1 1
      src/main/java/com/qlm/controller/core/QrcodeAddController.java
  2. 2 2
      src/main/java/com/qlm/controller/jinzai/JxsNewController.java
  3. 160 2
      src/main/java/com/qlm/controller/jinzai/NewReportController.java
  4. 264 17
      src/main/java/com/qlm/controller/jinzai/ProdBatchController.java
  5. 1 1
      src/main/java/com/qlm/controller/jinzai/ProductController.java
  6. 196 0
      src/main/java/com/qlm/controller/jinzai/QrcodeApplyController.java
  7. 317 0
      src/main/java/com/qlm/controller/system/UserNewController.java
  8. 51 0
      src/main/java/com/qlm/dto/DeptDto.java
  9. 13 0
      src/main/java/com/qlm/dto/DeviceMonitorRecordDto.java
  10. 107 0
      src/main/java/com/qlm/dto/ItemModifyRecordDto.java
  11. 131 0
      src/main/java/com/qlm/dto/ItemModifyRecordVO.java
  12. 13 0
      src/main/java/com/qlm/dto/ProductionLineDto.java
  13. 158 0
      src/main/java/com/qlm/dto/ProductionTotalVO.java
  14. 224 0
      src/main/java/com/qlm/dto/QrcodeApplyDto.java
  15. 192 0
      src/main/java/com/qlm/dto/UserDto.java
  16. 77 0
      src/main/java/com/qlm/dto/UserExportDto.java
  17. 103 0
      src/main/java/com/qlm/dto/UserImportDto.java
  18. 14 13
      src/main/java/com/qlm/jfinal/JfinalConfig.java
  19. 4 3
      src/main/java/com/qlm/netty/NettyServer.java
  20. 40 0
      src/main/java/com/qlm/netty/constant/CommandType.java
  21. 33 0
      src/main/java/com/qlm/netty/constant/NettyAttributes.java
  22. 64 0
      src/main/java/com/qlm/netty/constant/NettyTopic.java
  23. 270 29
      src/main/java/com/qlm/netty/handler/NettyServerHandler.java
  24. 111 0
      src/main/java/com/qlm/netty/manager/DeviceSessionManager.java
  25. 0 27
      src/main/java/com/qlm/netty/model/IndustrialControlData.java
  26. 147 0
      src/main/java/com/qlm/netty/model/NettyMessage.java
  27. 75 0
      src/main/java/com/qlm/netty/util/WhitelistValidator.java
  28. 10 6
      src/main/java/com/qlm/oss/OssUtil.java
  29. 1 1
      src/main/java/com/qlm/service/IQrcodeAddService.java
  30. 17 0
      src/main/java/com/qlm/service/IQrcodeApplyService.java
  31. 45 0
      src/main/java/com/qlm/service/IUserService.java
  32. 8 8
      src/main/java/com/qlm/service/impl/LineProductServiceImpl.java
  33. 17 16
      src/main/java/com/qlm/service/impl/ProductionLineServiceImpl.java
  34. 92 20
      src/main/java/com/qlm/service/impl/QrcodeAddServiceImpl.java
  35. 163 0
      src/main/java/com/qlm/service/impl/QrcodeApplyServiceImpl.java
  36. 392 0
      src/main/java/com/qlm/service/impl/UserServiceImpl.java
  37. 4 1
      src/main/resources/config.properties
  38. 1 1
      src/main/resources/logback.xml
  39. 414 0
      src/main/webapp/page/jinzai/deptManagement.jsp
  40. 15 29
      src/main/webapp/page/jinzai/deviceMonitor.jsp
  41. 833 0
      src/main/webapp/page/jinzai/itemChangeRecord.jsp
  42. 2 2
      src/main/webapp/page/jinzai/productTypeList.jsp
  43. 570 0
      src/main/webapp/page/jinzai/productionStatisticsReport.jsp
  44. 5 0
      src/main/webapp/page/jinzai/production_line.jsp
  45. 722 0
      src/main/webapp/page/jinzai/qrcodeApply.jsp
  46. 1081 0
      src/main/webapp/page/jinzai/userManagement.jsp

+ 1 - 1
src/main/java/com/qlm/controller/core/QrcodeAddController.java

@@ -57,7 +57,7 @@ public class QrcodeAddController extends CommonController{
 		String area = getPara("area");
 		AdminView loginUser2 = getLoginUser();
 		String filePath = getSession().getServletContext().getRealPath("/") +  "QRCode";
-		String zipFilePath = qrcodeAddService.getZipFilePath(url,code, number, filePath,addNum,id,type,area,loginUser2);		
+		String zipFilePath = qrcodeAddService.getZipFilePath(url,code, number, filePath,addNum,id,type,area,loginUser2,false);
 		renderText(zipFilePath);
 	}
 	

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

@@ -178,8 +178,8 @@ public class JxsNewController extends CommonController {
             String ossUrl = OssUtil.upload(inputStream, fileName);
             apiResponse = ApiResponse.success(ossUrl);
         } catch (Exception e) {
-            logger.error("导出品相列表异常:", e);
-            renderJson(ApiResponse.error("导出品相列表失败:" + e.getMessage()));
+            logger.error("导出经销商列表异常:", e);
+            renderJson(ApiResponse.error("导出经销商列表失败:" + e.getMessage()));
         }
         renderJson(apiResponse);
     }

+ 160 - 2
src/main/java/com/qlm/controller/jinzai/NewReportController.java

@@ -1,7 +1,22 @@
 package com.qlm.controller.jinzai;
 
+import cn.hutool.core.date.DateTime;
+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.PageResult;
 import com.qlm.controller.common.CommonController;
+import com.qlm.controller.organize.ProductionLineController;
+import com.qlm.dto.ProdTaskUploadRecordDto;
+import com.qlm.dto.ProductionTotalVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @program: jinzaifoodadmin
@@ -13,14 +28,138 @@ import com.qlm.controller.common.CommonController;
  **/
 @RequestUrl("/newReport")
 public class NewReportController extends CommonController {
+    private static final Logger logger = LoggerFactory.getLogger(NewReportController.class);
 
     /**
      * 生产统计报表路由
      */
     public void getProdTotalReportPage() {
-        renderJsp("/page/jinzai/prodTotalReport.jsp");
+        renderJsp("/page/jinzai/productionStatisticsReport.jsp");
+    }
+
+    public void getProdTotalReport() {
+        try {
+            int pageNumber = getParaToInt("pageNumber", 1);
+            int pageSize = getParaToInt("pageSize", 10);
+            String startTime = getPara("startTime");
+            String endTime = getPara("endTime");
+            Long factoryId = getParaToLong("factoryId");
+            Long workshopId = getParaToLong("workshopId");
+            if (StrKit.isBlank(startTime) || StrKit.isBlank(endTime)) {
+                DateTime end = DateUtil.endOfDay(DateUtil.date());
+                DateTime start = DateUtil.beginOfDay(DateUtil.offsetDay(end, -6));
+                startTime = DateUtil.formatDateTime(start);
+                endTime = DateUtil.formatDateTime(end);
+            }
+            String selectColumns = "select distinct task_no, d.factory_name,e.workshop_name, a.sku,c.item_name, produce_date, DATE_FORMAT(guo_time, '%Y-%m-%d') AS guo_date";
+            StringBuilder sql = new StringBuilder("from jinzai_upload_master_copy a");
+            sql.append(" left join t_jz_device b on a.device_no = b.device_no");
+            sql.append(" left join t_jz_item c on a.sku = c.sku");
+            sql.append(" left join t_factory d on b.factory_id = d.id");
+            sql.append(" left join t_Workshop e on b.workshop_id = e.id");
+            sql.append(" where a.produce_date between '").append(startTime).append("' and '").append(endTime).append("'");
+            List<Object> params = new ArrayList<>();
+            if(factoryId != null){
+                sql.append(" and b.factory_id = ?");
+                params.add(factoryId);
+            }
+            if(workshopId != null){
+                sql.append(" and b.workshop_id = ?");
+                params.add(workshopId);
+            }
+            sql.append(" order by a.produce_date desc");
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, selectColumns, sql.toString(), params.toArray());
+            if(paginate  == null){
+                renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+                return;
+            }
+            List<Record> list = paginate.getList();
+            List<ProductionTotalVO> productionTotalVOS = new ArrayList<>();
+            ProductionTotalVO productionTotalVO;
+            for (Record record : list) {
+                productionTotalVO = getProductionTotalVO(record);
+                productionTotalVOS.add(productionTotalVO);
+            }
+            renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, productionTotalVOS));
+        }catch (Exception e){
+            logger.error("查询生产统计报表异常:", e);
+            renderJson(new PageResult<>(0, 1, 10, new ArrayList<>()));
+        }
     }
 
+    /**
+     * 根据任务单号和sku查询箱码
+     */
+    public void getBoxCodeByTaskNoAndSku() {
+        try {
+            int pageNumber = getParaToInt("pageNumber", 1);
+            int pageSize = getParaToInt("pageSize", 10);
+            String boxCode = getPara("code");
+            String taskNo = getPara("task_no");
+            String sku = getPara("sku");
+            String selectColumns = "select id as code";
+            StringBuilder sql = new StringBuilder(" from jinzai_upload_master_copy where task_no = ? and sku = ?");
+            List<Object> params = new ArrayList<>();
+            params.add(taskNo);
+            params.add(sku);
+            if(StrKit.notBlank(boxCode)){
+                sql.append(" and id like ?");
+                params.add("%" + boxCode + "%");
+            }
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, selectColumns, sql.toString(), params.toArray());
+            if(paginate  == null){
+                renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+            }
+            List<Record> list = paginate.getList();
+            List<Record> boxCodeList = new ArrayList<>(list);
+            renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, boxCodeList));
+        } catch (Exception e) {
+            logger.error("查询箱码异常:", e);
+            renderJson(new PageResult<>(0, 1, 10, new ArrayList<>()));
+        }
+    }
+
+    /**
+     * 根据任务单号和sku查询出托码
+     */
+    public void getToCodeByTaskNoAndSku() {
+        try {
+            int pageNumber = getParaToInt("pageNumber", 1);
+            int pageSize = getParaToInt("pageSize", 10);
+            String toCode = getPara("code");
+            String taskNo = getPara("task_no");
+            String sku = getPara("sku");
+            // 构建查询条件
+            StringBuilder whereSql = new StringBuilder(" where task_no = ? and sku = ?");
+            List<Object> params = new ArrayList<>();
+            params.add(taskNo);
+            params.add(sku);
+            if(StrKit.notBlank(toCode)){
+                whereSql.append(" and duo_code like ?");
+                params.add("%" + toCode + "%");
+            }
+
+            // 先查询总数(使用DISTINCT)
+            String countSql = "select count(distinct duo_code) from jinzai_upload_master_copy" + whereSql;
+            Long totalRow = Db.queryLong(countSql, params.toArray());
+
+            // 再查询分页数据
+            String selectColumns = "select distinct duo_code as code";
+            String dataSql = " from jinzai_upload_master_copy" + whereSql;
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, selectColumns, dataSql, params.toArray());
+
+            if(paginate == null){
+                renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+                return;
+            }
+            List<Record> list = paginate.getList();
+            List<Record> toCodeList = new ArrayList<>(list);
+            renderJson(new PageResult<>(totalRow, pageNumber, pageSize, toCodeList));
+        } catch (Exception e) {
+            logger.error("查询托码异常:", e);
+            renderJson(new PageResult<>(0, 1, 10, new ArrayList<>()));
+        }
+    }
 
     /**
      * 出库统计报表路由
@@ -29,5 +168,24 @@ public class NewReportController extends CommonController {
         renderJsp("/page/jinzai/outTotalReport.jsp");
     }
 
-
+    private static ProductionTotalVO getProductionTotalVO(Record record) {
+        ProductionTotalVO productionTotalVO;
+        productionTotalVO = new ProductionTotalVO();
+        productionTotalVO.setBatchNo(record.getStr("task_no"));
+        productionTotalVO.setSku(record.getStr("sku"));
+        productionTotalVO.setFactoryName(record.getStr("factory_name"));
+        productionTotalVO.setItemName(record.getStr("item_name"));
+        productionTotalVO.setProduceDate(DateUtil.format(record.getDate("produce_date"), "yyyy-MM-dd"));
+        productionTotalVO.setGuoDate(record.getStr("guo_date"));
+        productionTotalVO.setWorkshopName(record.getStr("workshop_name"));
+        //根据taskNo 和 Sku  查询数量
+        Long quantity = Db.queryLong("select count(id) quantity from jinzai_upload_master_copy where task_no = ? and sku = ?", record.getStr("task_no"), record.getStr("sku"));
+        if(quantity == null){
+            quantity = 0L;
+        }
+        productionTotalVO.setQuantity(quantity.intValue());
+        productionTotalVO.setCreateTime(DateUtil.format(record.getDate("create_time"), "yyyy-MM-dd HH:mm:ss"));
+        productionTotalVO.setUpdateTime(DateUtil.format(record.getDate("update_time"), "yyyy-MM-dd HH:mm:ss"));
+        return productionTotalVO;
+    }
 }

+ 264 - 17
src/main/java/com/qlm/controller/jinzai/ProdBatchController.java

@@ -1,6 +1,10 @@
 package com.qlm.controller.jinzai;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateUtil;
+import com.alibaba.fastjson.JSON;
+import com.jfinal.aop.Clear;
+import com.jfinal.kit.HttpKit;
 import com.jfinal.kit.StrKit;
 import com.jfinal.plugin.activerecord.Db;
 import com.jfinal.plugin.activerecord.Page;
@@ -10,13 +14,24 @@ 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.ItemModifyRecordDto;
+import com.qlm.dto.ItemModifyRecordVO;
 import com.qlm.dto.ProdTaskUploadRecordDto;
+import com.qlm.netty.constant.CommandType;
+import com.qlm.netty.manager.DeviceSessionManager;
+import com.qlm.netty.util.WhitelistValidator;
 import com.qlm.tools.WxUtil;
+import com.qlm.view.core.AdminView;
+import io.netty.channel.ChannelHandlerContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.math.BigInteger;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * @program: jinzaifoodadmin
@@ -50,7 +65,7 @@ public class ProdBatchController extends CommonController {
         String productName = getPara("productName");
         String wmsStatus = getPara("wmsStatus");
         try {
-            StringBuilder whereSql = new StringBuilder(" from jinzai_upload_master where 1=1");
+            StringBuilder whereSql = new StringBuilder(" from jinzai_upload_master_copy where 1=1");
             List<Object> params = new ArrayList<>();
             if(StrKit.notBlank(createTime)){
                 //创建时间 =
@@ -113,14 +128,13 @@ public class ProdBatchController extends CommonController {
             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());
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, "select tdd.*,tjd.id as deviceId, tjd.device_no as deviceNo,tjd.desc as device_name, tf.factory_name, tw.workshop_name, tjp.product_name", fromSql.toString(), params.toArray());
             if (paginate == null) {
                 renderJson(ApiResponse.success(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>())));
                 return;
@@ -140,25 +154,36 @@ public class ProdBatchController extends CommonController {
     /**
      * 刷新设备详细信息
      */
+    @Clear
     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);
+            String sqlOne = "select * from t_jz_device where id = ?";
+            Record record = Db.findFirst(sqlOne, deviceId);
             if (record == null) {
                 apiResponse.setCode(500);
                 apiResponse.setMsg("系统异常:设备不存在");
             } else {
-                DeviceMonitorRecordDto deviceMonitorRecordDto = convertRecordToDeviceMonitorRecordDto(record);
-                apiResponse.setData(deviceMonitorRecordDto);
+                boolean result = DeviceSessionManager.getInstance().sendCommand(record.getStr("device_no"),null, CommandType.DEVICE_INFO_REPORT.getCode());
+                if (result) {
+                    apiResponse.setMsg("刷新成功");
+                    apiResponse.setCode(0);
+                    Thread.sleep(3000);
+                    StringBuilder sql = new StringBuilder("select tjd.*,tdd.id as deviceId,tdd.device_no as deviceNo, tdd.desc as device_name, tf.factory_name, tw.workshop_name, tjp.product_name");
+                    sql.append(" from t_jz_device tdd");
+                    sql.append(" inner join t_device_detail tjd on tdd.id = tjd.device_id");
+                    sql.append(" left join t_factory tf on tdd.factory_id = tf.id");
+                    sql.append(" left join t_workshop tw on tdd.workshop_id = tw.id");
+                    sql.append(" left join t_jz_product tjp on tjd.product_id = tjp.id");
+                    sql.append(" where tdd.id = ?");
+                    record = Db.findFirst(sql.toString(), deviceId);
+                    DeviceMonitorRecordDto deviceMonitorRecordDto = convertRecordToDeviceMonitorRecordDto(record);
+                    apiResponse.setData(deviceMonitorRecordDto);
+                } else {
+                    apiResponse.setCode(500);
+                    apiResponse.setMsg("刷新失败");
+                }
             }
         }catch (Exception e) {
             logger.error("刷新设备详细信息异常:", e);
@@ -189,9 +214,221 @@ public class ProdBatchController extends CommonController {
      * 品相信息更改记录路由
      */
     public void getProdItemChangeRecordPage() {
-        renderJsp("/page/jinzai/item_change_record.jsp");
+        renderJsp("/page/jinzai/itemChangeRecord.jsp");
     }
 
+    /**
+     * 查询品相信息更改记录
+     */
+    public void getItemChangeRecordList() {
+        // 获取分页参数
+        int pageNumber = getParaToInt("pageNumber", 1);
+        int pageSize = getParaToInt("pageSize", 10);
+        //工厂ID、车间、产线、开始结束时间
+        Integer factoryId = getParaToInt("factoryId");
+        Integer workshopId = getParaToInt("workshopId");
+        Integer lineId = getParaToInt("lineId");
+        String startTime = getPara("startDate");
+        String endTime = getPara("endDate");
+        if (StrKit.notBlank(startTime)) {
+            startTime = startTime + " 00:00:00";
+        }
+        if (StrKit.notBlank(endTime)) {
+            endTime = endTime + " 23:59:59";
+        }
+        try {
+            String selectColumns = "select r.modify_type,r.reference_code,f.factory_name,w.workshop_name,bi.item_name as before_item_name,ai.item_name as after_item_name,r.modify_time,r.modify_user";
+            StringBuilder fromSql = new StringBuilder(" from t_item_modify_record r");
+            fromSql.append(" left join t_factory f on r.factory_id = f.id");
+            fromSql.append(" left join t_workshop w on r.workshop_id = w.id");
+            fromSql.append(" left join t_jz_device l on r.line_id = l.id");
+            fromSql.append(" left join t_jz_item bi on r.before_item_id = bi.id");
+            fromSql.append(" left join t_jz_item ai on r.after_item_id = ai.id");
+            fromSql.append(" where 1=1");
+            List<Object> params = new ArrayList<>();
+            if (factoryId != null) {
+                fromSql.append(" and r.factory_id = ?");
+                params.add(factoryId);
+            }
+            if (workshopId != null) {
+                fromSql.append(" and r.workshop_id = ?");
+                params.add(workshopId);
+            }
+            if (lineId != null) {
+                fromSql.append(" and r.line_id = ?");
+                params.add(lineId);
+            }
+            if (StrKit.notBlank(startTime)) {
+                fromSql.append(" and r.modify_time >= ?");
+                params.add(startTime);
+            }
+            if (StrKit.notBlank(endTime)) {
+                fromSql.append(" and r.modify_time <= ?");
+                params.add(endTime);
+            }
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, selectColumns, fromSql.toString(), params.toArray());
+            if(paginate  == null){
+                renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+                return;
+            }
+            List<Record> list = paginate.getList();
+            List<ItemModifyRecordVO> itemModifyRecordVOList = new ArrayList<>();
+            for (Record record : list) {
+                ItemModifyRecordVO itemModifyRecordVO = new ItemModifyRecordVO();
+                itemModifyRecordVO.setModifyType(WxUtil.getInt("modify_type",record));
+                itemModifyRecordVO.setReferenceCode(record.getStr("reference_code"));
+                itemModifyRecordVO.setFactoryName(record.getStr("factory_name"));
+                itemModifyRecordVO.setWorkshopName(record.getStr("workshop_name"));
+                itemModifyRecordVO.setBeforeItemName(record.getStr("before_item_name"));
+                itemModifyRecordVO.setAfterItemName(record.getStr("after_item_name"));
+                itemModifyRecordVO.setModifyTime(DateUtil.format(record.getDate("modify_time"), "yyyy-MM-dd HH:mm:ss"));
+                itemModifyRecordVO.setModifyUser(record.getStr("modify_user"));
+                itemModifyRecordVOList.add(itemModifyRecordVO);
+            }
+            renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, itemModifyRecordVOList));
+        } catch (Exception e) {
+            logger.error("查询品相信息更改记录列表异常:", e);
+            renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+        }
+    }
+
+    /**
+     * 变更品相
+     */
+    @Clear
+    public void changeItem() {
+        ApiResponse apiResponse = new ApiResponse();
+        //AdminView loginUser = getLoginUser();
+        AdminView loginUser = new  AdminView();
+        loginUser.setUsername("测试管理员");
+        try {
+            String bodyData = HttpKit.readData(getRequest());
+            ItemModifyRecordDto itemModifyRecordDto = JSON.parseObject(bodyData, ItemModifyRecordDto.class);
+            apiResponse = actionChangeItem(itemModifyRecordDto,loginUser);
+        } catch (Exception e) {
+            logger.error("品相信息更改异常:", e);
+            apiResponse.setCode(500);
+            apiResponse.setMsg("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
+    private ApiResponse actionChangeItem(ItemModifyRecordDto itemModifyRecordDto,AdminView loginUser) {
+        Integer modifyType = itemModifyRecordDto.getModifyType();
+        List<String> referenceCodes = itemModifyRecordDto.getReferenceCodes();
+        //单号/箱码/托码 对应的修改之前的 品项sku
+        Map<String,String> referenceCodeAndSku = new HashMap<>();
+        if(modifyType.compareTo(1) == 0){
+            Record first = Db.findFirst("select * from jinzai_upload_master_copy where task_no = ?", referenceCodes.get(0));
+            if(first == null){
+                return ApiResponse.error("单号不存在");
+            }
+            referenceCodeAndSku.put(referenceCodes.get(0),first.getStr("sku"));
+        }else if(modifyType.compareTo(2) == 0){
+            String codeList = referenceCodes.stream()
+                    .map(code -> "'" + code + "'")
+                    .collect(java.util.stream.Collectors.joining(","));
+            List<Record> masterRecords = Db.find("select * from jinzai_upload_master_copy where id in(" + codeList + ")");
+            if(masterRecords.size() != referenceCodes.size()){
+                return ApiResponse.error("有箱码不存在");
+            }
+            masterRecords.forEach(record -> {
+                referenceCodeAndSku.put(record.getStr("id"),record.getStr("sku"));
+            });
+        }else if(modifyType.compareTo(3) == 0){
+            String codeList = referenceCodes.stream()
+                    .map(code -> "'" + code + "'")
+                    .collect(java.util.stream.Collectors.joining(","));
+            List<Record> duoRecords = Db.find("select * from jinzai_upload_master_copy where duo_code in(" + codeList + ")");
+            if(duoRecords.size() != referenceCodes.size()){
+                return ApiResponse.error("有托码不存在");
+            }
+            duoRecords.forEach(record -> {
+                referenceCodeAndSku.put(record.getStr("duo_code"),record.getStr("sku"));
+            });
+        }
+        boolean tx = Db.tx(() -> {
+            try {
+                //转成字符串,逗号拼接
+                String addReferenceCode = referenceCodes.stream()
+                        .map(code -> "'" + code + "'")
+                        .collect(java.util.stream.Collectors.joining(","));
+                List<Record> itemList = Db.find("select * from t_jz_item");
+                Map<Integer, Record> itemMap = new HashMap<>();
+                Map<String,Record> itemSkuMap = new HashMap<>();
+                if (CollUtil.isNotEmpty(itemList)) {
+                    itemMap = itemList.stream().collect(Collectors.toMap(
+                            record -> WxUtil.getInt("id", record),
+                            record -> record,
+                            (existing, replacement) -> existing
+                    ));
+                    itemSkuMap = itemList.stream().collect(Collectors.toMap(
+                            record -> record.getStr("sku"),
+                            record -> record,
+                            (existing, replacement) -> existing
+                    ));
+                }
+                Record itemRecord = itemMap.get(itemModifyRecordDto.getAfterItemId());
+                String itemName = itemRecord.getStr("item_name");
+                String itemKouWei = itemRecord.getStr("kouwei");
+                String sku = itemRecord.getStr("sku");
+                //批量插入品项更新记录
+                for (String referenceCode : referenceCodes) {
+                    //根据单号/箱码/托码 获取sku
+                    String beforeSku = referenceCodeAndSku.get(referenceCode);
+                    if(StrKit.isBlank(beforeSku)){
+                        logger.error("单号/箱码/托码:{}未获取到sku", referenceCode);
+                        continue;
+                    }
+                    //根据 beforeSku 获取 beforeItemId
+                    Record beforeItemRecord = itemSkuMap.get(beforeSku);
+                    if(beforeItemRecord == null){
+                        logger.error("SKU:{}未获取到品项ID", referenceCode);
+                        continue;
+                    }
+                    //插入品相信息修改记录
+                    Record record = new Record();
+                    record.set("modify_type", modifyType);
+                    record.set("reference_code", referenceCode);
+                    record.set("factory_id", itemModifyRecordDto.getFactoryId());
+                    record.set("workshop_id", itemModifyRecordDto.getWorkshopId());
+                    record.set("line_id", itemModifyRecordDto.getLineId());
+                    record.set("before_item_id", WxUtil.getInt("id", beforeItemRecord));
+                    record.set("after_item_id", itemModifyRecordDto.getAfterItemId());
+                    record.set("modify_user", loginUser.getUsername());
+                    Db.save("t_item_modify_record", record);
+                }
+                if (modifyType.compareTo(1) == 0) {
+                    Db.update("update jinzai_upload_master_copy set upload_time = null,pinxiang = ?,kouwei = ?,sku = ? where task_no = ?", itemName, itemKouWei,sku, referenceCodes.get(0));
+                } else if (modifyType.compareTo(2) == 0) {
+                    Db.update("update jinzai_upload_master_copy set upload_time = null,pinxiang = ?,kouwei = ?,sku = ? where id in(" + addReferenceCode + ")", itemName, itemKouWei,sku);
+                } else if (modifyType.compareTo(3) == 0) {
+                    Db.update("update jinzai_upload_master_copy set upload_time = null,pinxiang = ?,kouwei = ?,sku = ? where duo_code in(" + addReferenceCode + ")", itemName, itemKouWei,sku);
+                }
+                return true;
+            } catch (Exception e) {
+                logger.error("品相信息更改异常:", e);
+                return false;
+            }
+        });
+        if (!tx) {
+            return ApiResponse.error("品相信息更改失败");
+        }
+        return ApiResponse.success();
+    }
+
+    @Clear
+    public void refreshWhitelist() {
+        try {
+            WhitelistValidator.reloadWhitelist();
+            renderJson((ApiResponse.success()));
+        } catch (Exception e) {
+            logger.error("刷新白名单失败: {}", e.getMessage(), e);
+            renderJson(ApiResponse.error("刷新白名单失败"));
+        }
+    }
+
+
 
     private ProdTaskUploadRecordDto convertRecordToDto(Record record) {
         ProdTaskUploadRecordDto dto = new ProdTaskUploadRecordDto();
@@ -218,13 +455,23 @@ public class ProdBatchController extends CommonController {
         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.setLine(record.getStr("device_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));
+        Long delay = record.getLong("network_delay");
+        if(delay == null){
+            deviceMonitorRecordDto.setNetworkDelay("0");
+        }else{
+            //是毫秒,得转成秒,小数也要显示,默认支持三位小数
+            deviceMonitorRecordDto.setNetworkDelay(String.format("%.3f", delay / 1000.0));
+        }
+        ChannelHandlerContext deviceNo = DeviceSessionManager.getInstance().getDeviceSession(record.getStr("deviceNo"));
+        if(deviceNo != null){
+            deviceMonitorRecordDto.setOnline(1);
+        }
         return deviceMonitorRecordDto;
     }
 }

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

@@ -38,7 +38,7 @@ public class ProductController extends CommonController{
 	@Clear
 	public void getProduceData(){
 		JSONObject obj = new JSONObject();
-		
+		//todo 可能会传产线ID ,没有传就全部 兼容老流程
 		List<Record> t_jz_productList = Db.find("SELECT i.sku,i.kouwei,p.product_name,i.id,i.product_id,p.xiang_num,p.tuo_num from t_jz_item i left join t_jz_product p on (i.product_id = p.id) where p.line_on = 1 and i.sku is not null");
 	
 		

+ 196 - 0
src/main/java/com/qlm/controller/jinzai/QrcodeApplyController.java

@@ -0,0 +1,196 @@
+package com.qlm.controller.jinzai;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import com.alibaba.fastjson.JSON;
+import com.jfinal.aop.Clear;
+import com.jfinal.kit.HttpKit;
+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.dto.ProductTypeDto;
+import com.qlm.dto.QrcodeApplyDto;
+import com.qlm.service.IProductTypeService;
+import com.qlm.service.IQrcodeApplyService;
+import com.qlm.service.impl.ProductTypeServiceImpl;
+import com.qlm.service.impl.QrcodeApplyServiceImpl;
+import com.qlm.tools.WxUtil;
+import com.qlm.view.core.AdminView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: QrcodeApplyController
+ * @description: 二维码申请
+ * @author: 吴英健
+ * @create: 2025-08-27 14:41
+ * @Version 1.0
+ **/
+@RequestUrl("/qrcodeApply")
+public class QrcodeApplyController extends CommonController {
+    private static final Logger logger = LoggerFactory.getLogger(QrcodeApplyController.class);
+    private final IQrcodeApplyService qrcodeApplyService = new QrcodeApplyServiceImpl();
+
+    /**
+     * 码申请路由
+     */
+    public void getProdUploadRecordPage() {
+        renderJsp("/page/jinzai/qrcodeApply.jsp");
+    }
+
+    /**
+     * 码申请记录查询
+     */
+    public void getProdUploadRecord() {
+        //分页参数
+        int pageNumber = getParaToInt("pageNumber", 1);
+        int pageSize = getParaToInt("pageSize", 10);
+        //申请时间范围、品牌ID、产品ID
+        String applyStartTime= getPara("startDate");
+        if(StrKit.notBlank(applyStartTime)){
+            applyStartTime += " 00:00:00";
+        }
+        String applyEndTime= getPara("endDate");
+        if(StrKit.notBlank(applyEndTime)){
+            applyEndTime += " 23:59:59";
+        }
+        Integer brandId = getParaToInt("brandId");
+        Integer productId = getParaToInt("productId");
+        try {
+            String selectColumn = "select tqa.id,tqa.apply_no,tb.brand_name,tji.item_name,tqa.file_name,tqa.applicant,tqa.code_quantity,tqa.apply_time,tqa.status,tqa.download_url";
+            StringBuilder whereSql = new StringBuilder(" from t_qrcode_apply tqa");
+            whereSql.append(" left join t_brand tb on tqa.brand_id = tb.id");
+            whereSql.append(" left join t_jz_item tji on tqa.product_id = tji.id");
+            whereSql.append(" where 1=1");
+            List<Object> params = new ArrayList<>();
+            if(StrKit.notBlank(applyStartTime) && StrKit.notBlank(applyEndTime)){
+                whereSql.append(" and apply_time >= ? and apply_time <= ?");
+                params.add(applyStartTime);
+                params.add(applyEndTime);
+            }
+            if(brandId != null){
+                whereSql.append(" and tqa.brand_id = ?");
+                params.add(brandId);
+            }
+            if(productId != null){
+                whereSql.append(" and tqa.product_id = ?");
+                params.add(productId);
+            }
+            whereSql.append(" order by apply_time desc");
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, selectColumn, whereSql.toString(), params.toArray());
+            if(paginate  == null){
+                renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+                return;
+            }
+            List<Record> list = paginate.getList();
+            List<QrcodeApplyDto> qrcodeApplyDtoList = new ArrayList<>();
+            for (Record record : list) {
+               qrcodeApplyDtoList.add(convertRecordToDto(record));
+            }
+            renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, qrcodeApplyDtoList));
+        }catch (Exception e){
+            logger.error("查询生产任务上传记录异常:", e);
+            renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+        }
+    }
+
+    @Clear
+    public void applyQrcode(){
+        ApiResponse apiResponse;
+        //AdminView loginUser = getLoginUser();
+        AdminView loginUser = new AdminView();
+        loginUser.setUsername("测试管理员");
+        try {
+            // 使用HttpKit.readData获取请求Body
+            String bodyData = HttpKit.readData(getRequest());
+            QrcodeApplyDto qrcodeApplyDto = JSON.parseObject(bodyData, QrcodeApplyDto.class);
+            apiResponse = qrcodeApplyService.applyQrcode(qrcodeApplyDto, loginUser);
+        } catch (Exception e) {
+            logger.error("申请二维码异常:", e);
+            apiResponse = ApiResponse.error("申请二维码异常");
+        }
+        renderJson(apiResponse);
+    }
+
+
+    public void getItemAll(){
+        ApiResponse apiResponse;
+        List<Record> list = Db.find("select id,item_name from t_jz_item where Status =1 and (item_name like '%140g%' or item_name like '%70g%' or item_name like '%鹌鹑蛋%')");
+        if(CollUtil.isEmpty(list)){
+            apiResponse = ApiResponse.success(new ArrayList<>());
+            renderJson(apiResponse);
+            return;
+        }
+        apiResponse = ApiResponse.success(list);
+        renderJson(apiResponse);
+    }
+
+    public void getDetailByApplyId(){
+        ApiResponse apiResponse;
+        try {
+            Integer applyId = getParaToInt("applyId");
+            //查询二维码申请详情
+            StringBuilder whereSql = new StringBuilder("select tqa.supplier_name,tqa.code_config,tqa.remark,tqa.package_unit,tqa.id,tqa.apply_no,tb.brand_name,tji.item_name,tqa.file_name,tqa.applicant,tqa.code_quantity,tqa.apply_time,tqa.status,tqa.download_url");
+            whereSql.append(" from t_qrcode_apply tqa");
+            whereSql.append(" left join t_brand tb on tqa.brand_id = tb.id");
+            whereSql.append(" left join t_jz_item tji on tqa.product_id = tji.id");
+            whereSql.append(" where tqa.id = ?");
+            Record record = Db.findFirst(whereSql.toString(), applyId);
+            if(record == null){
+                apiResponse = ApiResponse.success(new QrcodeApplyDto());
+                renderJson(apiResponse);
+                return;
+            }
+            QrcodeApplyDto qrcodeApplyDto = convertRecordToDto(record);
+            apiResponse = ApiResponse.success(qrcodeApplyDto);
+        } catch (Exception e) {
+            logger.error("查询二维码申请详情异常:", e);
+            apiResponse = ApiResponse.error("查询二维码申请详情异常");
+        }
+        renderJson(apiResponse);
+    }
+
+    /**
+     * 将Record对象转换为QrcodeApplyDto对象
+     * @param record 数据库查询结果记录
+     * @return QrcodeApplyDto对象
+     */
+    private QrcodeApplyDto convertRecordToDto(Record record) {
+        QrcodeApplyDto dto = new QrcodeApplyDto();
+        // 设置申请单号
+        dto.setApplyNo(record.getStr("apply_no"));
+        // 设置申请企业(品牌名称)
+        dto.setApplyCompany(record.getStr("brand_name"));
+        // 设置产品名称
+        dto.setProductName(record.getStr("item_name"));
+        // 设置码包文件名
+        dto.setPackageFileName(record.getStr("file_name"));
+        // 设置申请人
+        dto.setApplicant(record.getStr("applicant"));
+        // 设置申请时间
+        dto.setApplyTime(DateUtil.format(record.getDate("apply_time"), "yyyy-MM-dd HH:mm:ss"));
+        // 设置申请数量
+        dto.setApplyQuantity(record.getInt("code_quantity"));
+        // 设置状态
+        dto.setStatus(WxUtil.getInt("status",record));
+        // 设置码下载链接
+        dto.setDownloadUrl(record.getStr("download_url"));
+        dto.setId(WxUtil.getInt("id",record));
+        dto.setApplyConfig(WxUtil.getInt("code_config",record));
+        dto.setSupplier(record.getStr("supplier_name"));
+        dto.setPackagingUnit(record.getStr("package_unit"));
+        dto.setRemark(record.getStr("remark"));
+        return dto;
+    }
+}

+ 317 - 0
src/main/java/com/qlm/controller/system/UserNewController.java

@@ -0,0 +1,317 @@
+package com.qlm.controller.system;
+
+import cn.hutool.core.collection.CollUtil;
+import com.alibaba.fastjson.JSON;
+import com.jfinal.aop.Clear;
+import com.jfinal.kit.HttpKit;
+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.jfinal.upload.UploadFile;
+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.*;
+import com.qlm.oss.OssUtil;
+import com.qlm.service.IUserService;
+import com.qlm.service.impl.UserServiceImpl;
+import com.qlm.tools.EasyExcelUtil;
+import com.qlm.tools.WxUtil;
+import com.qlm.view.core.AdminView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户管理控制器
+ */
+@RequestUrl("/userNew")
+public class UserNewController extends CommonController {
+    private static final Logger logger = LoggerFactory.getLogger(UserNewController.class);
+    private final IUserService userService = new UserServiceImpl();
+
+    public void getUserList() {
+        renderJsp("/page/jinzai/userManagement.jsp");
+    }
+
+    public void getDeptListPagePath() {
+        renderJsp("/page/jinzai/deptManagement.jsp");
+    }
+
+    /**
+     * 保存用户信息
+     */
+    @Override
+    public void save() {
+        AdminView loginUser = getLoginUser();
+        try {
+            // 使用HttpKit.readData获取请求Body
+            String bodyData = HttpKit.readData(getRequest());
+            // 将JSON字符串转换为UserDto对象
+            UserDto userDto = JSON.parseObject(bodyData, UserDto.class);
+
+            ApiResponse apiResponse = userService.saveUser(userDto, loginUser);
+            renderJson(apiResponse);
+        } catch (Exception e) {
+            logger.error("保存用户信息异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    /**
+     * 更新用户信息
+     */
+    public void update() {
+        try {
+            // 使用HttpKit.readData获取请求Body
+            String bodyData = HttpKit.readData(getRequest());
+            // 将JSON字符串转换为UserDto对象
+            UserDto userDto = JSON.parseObject(bodyData, UserDto.class);
+
+            ApiResponse apiResponse = userService.updateUser(userDto);
+            renderJson(apiResponse);
+        } catch (Exception e) {
+            logger.error("更新用户信息异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    /**
+     * 删除用户信息
+     */
+    public void delete() {
+        try {
+            Integer id = getParaToInt("id");
+            ApiResponse apiResponse = userService.deleteUser(id);
+            renderJson(apiResponse);
+        } catch (Exception e) {
+            logger.error("删除用户信息异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    /**
+     * 根据ID获取用户信息
+     */
+    public void getById() {
+        try {
+            Integer id = getParaToInt("id");
+            ApiResponse apiResponse = userService.getUserById(id);
+            renderJson(apiResponse);
+        } catch (Exception e) {
+            logger.error("获取用户信息异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    /**
+     * 用户列表查询
+     */
+    @Override
+    public void list() {
+        try {
+            // 获取分页参数
+            int pageNumber = getParaToInt("pageNumber", 1);
+            int pageSize = getParaToInt("pageSize", 10);
+
+            // 获取查询条件
+            String username = getPara("username");
+            String realName = getPara("realName");
+            Integer departmentId = getParaToInt("deptId");
+            Integer status = getParaToInt("status");
+
+            // 调用Service层查询列表
+            PageResult<UserDto> pageResult = userService.listUser(pageNumber, pageSize, username, realName, departmentId, status);
+            renderJson(pageResult);
+        } catch (Exception e) {
+            logger.error("查询用户列表异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+
+    /**
+     * 导入经销商
+     */
+    @Clear
+    public void importUser() {
+        try {
+            // 获取当前登录用户
+            AdminView loginUser = getLoginUser();
+            // 获取上传的文件
+            UploadFile uploadFile = getFile("file");
+            if (uploadFile == null) {
+                renderJson(ApiResponse.error("请选择要导入的文件"));
+                return;
+            }
+            // 使用EasyExcelUtil解析文件
+            InputStream inputStream = Files.newInputStream(uploadFile.getFile().toPath());
+            List<UserImportDto> importList = EasyExcelUtil.getImportData(inputStream, UserImportDto.class);
+            inputStream.close();
+            // 调用Service层导入数据
+            ApiResponse apiResponse = userService.importUser(importList, loginUser.getUsername());
+            renderJson(apiResponse);
+        } catch (Exception e) {
+            logger.error("导入用户信息异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    /**
+     * 导出经销商列表
+     */
+    public void exportUserList() {
+        ApiResponse apiResponse = ApiResponse.success();
+        try {
+            // 获取查询条件
+            String userName = getPara("userName");
+
+            // 调用Service层获取导出数据
+            List<UserExportDto> dtos = userService.exportUserList(userName);
+
+            if (CollUtil.isEmpty(dtos)) {
+                renderJson(ApiResponse.error("没有要导出的数据"));
+                return;
+            }
+            // 使用EasyExcelUtil生成Excel文件流
+            InputStream inputStream = EasyExcelUtil.export(dtos, "用户列表", UserExportDto.class);
+            String fileName = URLEncoder.encode("用户列表.xlsx", "UTF-8");
+            String ossUrl = OssUtil.upload(inputStream, fileName);
+            apiResponse = ApiResponse.success(ossUrl);
+        } catch (Exception e) {
+            logger.error("导出用户列表异常:", e);
+            renderJson(ApiResponse.error("导出用户列表失败:" + e.getMessage()));
+        }
+        renderJson(apiResponse);
+    }
+
+
+    public void getDeptList(){
+        List<Record> records = Db.find("select * from t_department where status = 0");
+        if(records.isEmpty()){
+            renderJson(ApiResponse.success());
+            return;
+        }
+        renderJson(ApiResponse.success(records));
+    }
+
+    public void getRoleList(){
+        List<Record> records = Db.find("select * from t_role");
+        if(records.isEmpty()){
+            renderJson(ApiResponse.success());
+            return;
+        }
+        renderJson(ApiResponse.success(records));
+    }
+
+    public void deptList(){
+        int pageNumber = getParaToInt("pageNumber", 1);
+        int pageSize = getParaToInt("pageSize", 10);
+        String name = getPara("deptName");
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append(" from t_department");
+        sqlBuilder.append("  where status = 0 ");
+        List<Object> params = new ArrayList<>();
+        if (StrKit.notBlank(name)){
+            sqlBuilder.append("and name like ? ");
+            params.add("%" + name + "%");
+        }
+        Page<Record> paginate = Db.paginate(pageNumber, pageSize, "select *", sqlBuilder.toString(), params.toArray());
+        if(paginate  == null){
+            renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+            return;
+        }
+        List<Record> list = paginate.getList();
+        List<DeptDto> deptDtoList = new ArrayList<>();
+        for (Record record : list) {
+            DeptDto deptDto = new DeptDto();
+            deptDto.setId(WxUtil.getInt("id",record));
+            deptDto.setName(record.getStr("dept_name"));
+            deptDto.setRemark(record.getStr("remark"));
+            deptDtoList.add(deptDto);
+        }
+        renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, deptDtoList));
+    }
+
+    public void getDeptById(){
+        try {
+            Integer id = getParaToInt("id");
+            Record record = Db.findById("t_department", id);
+            DeptDto deptDto = new DeptDto();
+            deptDto.setId(WxUtil.getInt("id",record));
+            deptDto.setName(record.getStr("dept_name"));
+            deptDto.setRemark(record.getStr("remark"));
+            renderJson(ApiResponse.success(deptDto));
+        }catch (Exception e) {
+            logger.error("获取部门信息异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    public void addDept(){
+        try {
+            // 使用HttpKit.readData获取请求Body
+            String bodyData = HttpKit.readData(getRequest());
+            // 将JSON字符串转换为UserDto对象
+            DeptDto deptDto = JSON.parseObject(bodyData, DeptDto.class);
+            Record record = new Record();
+            record.set("dept_name", deptDto.getName());
+            record.set("remark", deptDto.getRemark());
+            record.set("status", 0);
+            boolean save = Db.save("t_department", record);
+            if(save){
+                renderJson(ApiResponse.success("添加成功"));
+            }else {
+                renderJson(ApiResponse.error("添加失败"));
+            }
+        }catch (Exception e) {
+            logger.error("添加部门异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    public void editDept(){
+        try {
+            // 使用HttpKit.readData获取请求Body
+            String bodyData = HttpKit.readData(getRequest());
+            // 将JSON字符串转换为UserDto对象
+            DeptDto deptDto = JSON.parseObject(bodyData, DeptDto.class);
+            Record record = new Record();
+            record.set("id", deptDto.getId());
+            record.set("dept_name", deptDto.getName());
+            record.set("remark", deptDto.getRemark());
+            record.set("status", deptDto.getStatus());
+            record.set("update_time", new Date());
+            boolean update = Db.update("t_department", "id", record);
+            if(update){
+                renderJson(ApiResponse.success("修改成功"));
+            }else {
+                renderJson(ApiResponse.error("修改失败"));
+            }
+        }catch (Exception e) {
+            logger.error("修改部门异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+
+    public void deleteDept(){
+        try {
+            Integer id = getParaToInt("id");
+            Record record = new Record();
+            record.set("update_time", System.currentTimeMillis());
+            Db.update("update t_department set status = 1 where id = ?",id);
+            renderJson(ApiResponse.success("删除成功"));
+        }catch (Exception e) {
+            logger.error("删除部门异常:", e);
+            renderJson(ApiResponse.error("系统异常:" + e.getMessage()));
+        }
+    }
+}

+ 51 - 0
src/main/java/com/qlm/dto/DeptDto.java

@@ -0,0 +1,51 @@
+package com.qlm.dto;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: DeptDto
+ * @description: TODO
+ * @author: 吴英健
+ * @create: 2025-09-04 09:47
+ * @Version 1.0
+ **/
+public class DeptDto {
+    private Integer id;
+
+    private String name;
+
+    private String remark;
+
+    private Integer status;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

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

@@ -28,6 +28,11 @@ public class DeviceMonitorRecordDto {
     private String totalNum;
     private String platformNum;
 
+    /**
+     * 设备是否在线
+     */
+    private Integer online;
+
     public String getDeviceName() {
         return deviceName;
     }
@@ -139,4 +144,12 @@ public class DeviceMonitorRecordDto {
     public void setDeviceId(Integer deviceId) {
         this.deviceId = deviceId;
     }
+
+    public Integer getOnline() {
+        return online;
+    }
+
+    public void setOnline(Integer online) {
+        this.online = online;
+    }
 }

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

@@ -0,0 +1,107 @@
+package com.qlm.dto;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: ItemModifyRecordDto
+ * @description: 品相更改
+ * @author: wuyingjian
+ * @create: 2025-09-04 15:59
+ * @Version 1.0
+ **/
+public class ItemModifyRecordDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 修改品项方式(1:按单号,2:按箱码,3:按托码)
+     */
+    private Integer modifyType;
+
+    /**
+     * 对应的单号/箱码/托码
+     */
+    private List<String> referenceCodes;
+
+    /**
+     * 工厂ID
+     */
+    private Integer factoryId;
+
+    /**
+     * 车间ID
+     */
+    private Integer workshopId;
+
+    /**
+     * 产线ID
+     */
+    private Integer lineId;
+
+    /**
+     * 修改前品项ID
+     */
+    private Integer beforeItemId;
+
+    /**
+     * 修改后品项ID
+     */
+    private Integer afterItemId;
+
+    public Integer getModifyType() {
+        return modifyType;
+    }
+
+    public void setModifyType(Integer modifyType) {
+        this.modifyType = modifyType;
+    }
+
+    public List<String> getReferenceCodes() {
+        return referenceCodes;
+    }
+
+    public void setReferenceCodes(List<String> referenceCodes) {
+        this.referenceCodes = referenceCodes;
+    }
+
+    public Integer getFactoryId() {
+        return factoryId;
+    }
+
+    public void setFactoryId(Integer factoryId) {
+        this.factoryId = factoryId;
+    }
+
+    public Integer getWorkshopId() {
+        return workshopId;
+    }
+
+    public void setWorkshopId(Integer workshopId) {
+        this.workshopId = workshopId;
+    }
+
+    public Integer getLineId() {
+        return lineId;
+    }
+
+    public void setLineId(Integer lineId) {
+        this.lineId = lineId;
+    }
+
+    public Integer getBeforeItemId() {
+        return beforeItemId;
+    }
+
+    public void setBeforeItemId(Integer beforeItemId) {
+        this.beforeItemId = beforeItemId;
+    }
+
+    public Integer getAfterItemId() {
+        return afterItemId;
+    }
+
+    public void setAfterItemId(Integer afterItemId) {
+        this.afterItemId = afterItemId;
+    }
+}

+ 131 - 0
src/main/java/com/qlm/dto/ItemModifyRecordVO.java

@@ -0,0 +1,131 @@
+package com.qlm.dto;
+
+import java.io.Serializable;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: ItemModifyRecordVO
+ * @description: 品项修改记录返回
+ * @author: wuyingjian
+ * @create: 2025-09-04 15:48
+ * @Version 1.0
+ **/
+public class ItemModifyRecordVO implements Serializable {
+    /**
+     * 修改品项方式(1:按单号,2:按箱码,3:按托码)
+     */
+    private Integer modifyType;
+
+    /**
+     * 对应的单号/箱码/托码
+     */
+    private String referenceCode;
+
+    /**
+     * 工厂名称
+     */
+    private String factoryName;
+
+    /**
+     * 车间名称
+     */
+    private String workshopName;
+
+    /**
+     * 修改前品项名称
+     */
+    private String beforeItemName;
+
+    /**
+     * 修改后品项名称
+     */
+    private String afterItemName;
+
+    /**
+     * 修改时间
+     */
+    private String modifyTime;
+
+    /**
+     * 修改人
+     */
+    private String modifyUser;
+
+    public Integer getModifyType() {
+        return modifyType;
+    }
+
+    public void setModifyType(Integer modifyType) {
+        this.modifyType = modifyType;
+    }
+
+    public String getReferenceCode() {
+        return referenceCode;
+    }
+
+    public void setReferenceCode(String referenceCode) {
+        this.referenceCode = referenceCode;
+    }
+
+    public String getFactoryName() {
+        return factoryName;
+    }
+
+    public void setFactoryName(String factoryName) {
+        this.factoryName = factoryName;
+    }
+
+    public String getWorkshopName() {
+        return workshopName;
+    }
+
+    public void setWorkshopName(String workshopName) {
+        this.workshopName = workshopName;
+    }
+
+    public String getBeforeItemName() {
+        return beforeItemName;
+    }
+
+    public void setBeforeItemName(String beforeItemName) {
+        this.beforeItemName = beforeItemName;
+    }
+
+    public String getAfterItemName() {
+        return afterItemName;
+    }
+
+    public void setAfterItemName(String afterItemName) {
+        this.afterItemName = afterItemName;
+    }
+
+    public String getModifyTime() {
+        return modifyTime;
+    }
+
+    public void setModifyTime(String modifyTime) {
+        this.modifyTime = modifyTime;
+    }
+
+    public String getModifyUser() {
+        return modifyUser;
+    }
+
+    public void setModifyUser(String modifyUser) {
+        this.modifyUser = modifyUser;
+    }
+
+    @Override
+    public String toString() {
+        return "ItemModifyRecordVO{" +
+                "modifyType=" + modifyType +
+                ", referenceCode='" + referenceCode + '\'' +
+                ", factoryName='" + factoryName + '\'' +
+                ", workshopName='" + workshopName + '\'' +
+                ", beforeItemName='" + beforeItemName + '\'' +
+                ", afterItemName='" + afterItemName + '\'' +
+                ", modifyTime='" + modifyTime + '\'' +
+                ", modifyUser='" + modifyUser + '\'' +
+                '}';
+    }
+}

+ 13 - 0
src/main/java/com/qlm/dto/ProductionLineDto.java

@@ -79,6 +79,11 @@ public class ProductionLineDto implements Serializable {
      */
     private String operator;
 
+    /**
+     * 远程详情
+     */
+    private String operDetail;
+
     // getter和setter方法
     public Long getId() {
         return id;
@@ -191,4 +196,12 @@ public class ProductionLineDto implements Serializable {
     public void setOperator(String operator) {
         this.operator = operator;
     }
+
+    public String getOperDetail() {
+        return operDetail;
+    }
+
+    public void setOperDetail(String operDetail) {
+        this.operDetail = operDetail;
+    }
 }

+ 158 - 0
src/main/java/com/qlm/dto/ProductionTotalVO.java

@@ -0,0 +1,158 @@
+package com.qlm.dto;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: ProductionTotalVO
+ * @description: 生产任务统计报表
+ * @author: wuyingjian
+ * @create: 2025-09-05 11:53
+ * @Version 1.0
+ **/
+public class ProductionTotalVO {
+    /**
+     * 工厂名称
+     */
+    private String factoryName;
+
+    /**
+     * 生产批号
+     */
+    private String batchNo;
+
+    /**
+     * 品项名称
+     */
+    private String itemName;
+
+
+    /**
+     * sku
+     */
+    private String sku;
+
+    /**
+     * 生产日期
+     */
+    private String produceDate;
+
+    /**
+     * 过机日期
+     */
+    private String guoDate;
+
+    /**
+     * 生产车间
+     */
+    private String workshopName;
+
+    /**
+     * 生产数量
+     */
+    private Integer quantity;
+
+    /**
+     * 创建时间
+     */
+    private String createTime;
+
+    /**
+     * 更新时间
+     */
+    private String updateTime;
+
+    public String getFactoryName() {
+        return factoryName;
+    }
+
+    public void setFactoryName(String factoryName) {
+        this.factoryName = factoryName;
+    }
+
+    public String getBatchNo() {
+        return batchNo;
+    }
+
+    public void setBatchNo(String batchNo) {
+        this.batchNo = batchNo;
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public void setItemName(String itemName) {
+        this.itemName = itemName;
+    }
+
+    public String getProduceDate() {
+        return produceDate;
+    }
+
+    public void setProduceDate(String produceDate) {
+        this.produceDate = produceDate;
+    }
+
+    public String getGuoDate() {
+        return guoDate;
+    }
+
+    public void setGuoDate(String guoDate) {
+        this.guoDate = guoDate;
+    }
+
+    public String getWorkshopName() {
+        return workshopName;
+    }
+
+    public void setWorkshopName(String workshopName) {
+        this.workshopName = workshopName;
+    }
+
+    public Integer getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(Integer quantity) {
+        this.quantity = quantity;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(String updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getSku() {
+        return sku;
+    }
+
+    public void setSku(String sku) {
+        this.sku = sku;
+    }
+
+    @Override
+    public String toString() {
+        return "ProductionTotalVO{" +
+                "factoryName='" + factoryName + '\'' +
+                ", batchNo='" + batchNo + '\'' +
+                ", itemName='" + itemName + '\'' +
+                ", sku='" + sku + '\'' +
+                ", produceDate='" + produceDate + '\'' +
+                ", guoDate='" + guoDate + '\'' +
+                ", workshopName='" + workshopName + '\'' +
+                ", quantity=" + quantity +
+                ", createTime='" + createTime + '\'' +
+                ", updateTime='" + updateTime + '\'' +
+                '}';
+    }
+}

+ 224 - 0
src/main/java/com/qlm/dto/QrcodeApplyDto.java

@@ -0,0 +1,224 @@
+
+package com.qlm.dto;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: QrcodeApplyDto
+ * @description: 二维码申请记录
+ * @author: 吴英健
+ * @create: 2025-08-27 15:16
+ * @Version 1.0
+ **/
+public class QrcodeApplyDto implements Serializable {
+
+    /**
+     * 主键ID
+     */
+    private Integer id;
+
+    /**
+     * 申请单号
+     */
+    private String applyNo;
+
+    /**
+     * 申请企业
+     */
+    private String applyCompany;
+
+    /**
+     * 企业ID
+     */
+    private Long companyId;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     * 产品ID
+     */
+    private Long productId;
+
+    /**
+     * 码包文件名
+     */
+    private String packageFileName;
+
+    /**
+     * 申请人
+     */
+    private String applicant;
+
+    /**
+     * 申请时间
+     */
+    private String applyTime;
+
+    /**
+     * 申请数量
+     */
+    private Integer applyQuantity = 1;
+
+    /**
+     * 状态
+     */
+    private Integer status;
+
+    /**
+     * 码下载链接
+     */
+    private String downloadUrl;
+
+    /**
+     * 码申请配置
+     */
+    private Integer applyConfig;
+
+    /**
+     * 供应商
+     */
+    private String supplier;
+
+    /**
+     * 包装单位
+     */
+    private String packagingUnit;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    public String getApplyNo() {
+        return applyNo;
+    }
+
+    public void setApplyNo(String applyNo) {
+        this.applyNo = applyNo;
+    }
+
+    public String getApplyCompany() {
+        return applyCompany;
+    }
+
+    public void setApplyCompany(String applyCompany) {
+        this.applyCompany = applyCompany;
+    }
+
+    public Long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(Long companyId) {
+        this.companyId = companyId;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public void setProductName(String productName) {
+        this.productName = productName;
+    }
+
+    public Long getProductId() {
+        return productId;
+    }
+
+    public void setProductId(Long productId) {
+        this.productId = productId;
+    }
+
+    public String getPackageFileName() {
+        return packageFileName;
+    }
+
+    public void setPackageFileName(String packageFileName) {
+        this.packageFileName = packageFileName;
+    }
+
+    public String getApplicant() {
+        return applicant;
+    }
+
+    public void setApplicant(String applicant) {
+        this.applicant = applicant;
+    }
+
+    public String getApplyTime() {
+        return applyTime;
+    }
+
+    public void setApplyTime(String applyTime) {
+        this.applyTime = applyTime;
+    }
+
+    public Integer getApplyQuantity() {
+        return applyQuantity;
+    }
+
+    public void setApplyQuantity(Integer applyQuantity) {
+        this.applyQuantity = applyQuantity;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public String getDownloadUrl() {
+        return downloadUrl;
+    }
+
+    public void setDownloadUrl(String downloadUrl) {
+        this.downloadUrl = downloadUrl;
+    }
+
+    public Integer getApplyConfig() {
+        return applyConfig;
+    }
+
+    public void setApplyConfig(Integer applyConfig) {
+        this.applyConfig = applyConfig;
+    }
+
+    public String getSupplier() {
+        return supplier;
+    }
+
+    public void setSupplier(String supplier) {
+        this.supplier = supplier;
+    }
+
+    public String getPackagingUnit() {
+        return packagingUnit;
+    }
+
+    public void setPackagingUnit(String packagingUnit) {
+        this.packagingUnit = packagingUnit;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}

+ 192 - 0
src/main/java/com/qlm/dto/UserDto.java

@@ -0,0 +1,192 @@
+package com.qlm.dto;
+
+import java.io.Serializable;
+
+/**
+ * 用户信息DTO
+ */
+public class UserDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    private Integer id;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 姓名
+     */
+    private String realName;
+
+    /**
+     * 手机号码
+     */
+    private String phone;
+
+    /**
+     * 所属部门ID
+     */
+    private Integer departmentId;
+
+    /**
+     * 所属部门名称
+     */
+    private String departmentName;
+
+    /**
+     * 用户角色ID
+     */
+    private Integer role;
+
+    /**
+     * 用户角色名称
+     */
+    private String roleName;
+
+    /**
+     * 状态 0停用 1启用
+     */
+    private Integer status;
+
+    /**
+     * 昵称
+     */
+    private String nickName;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+
+    /**
+     * 创建时间
+     */
+    private String createTime;
+
+    /**
+     * 创建人
+     */
+    private String createUser;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public Integer getDepartmentId() {
+        return departmentId;
+    }
+
+    public void setDepartmentId(Integer departmentId) {
+        this.departmentId = departmentId;
+    }
+
+    public String getDepartmentName() {
+        return departmentName;
+    }
+
+    public void setDepartmentName(String departmentName) {
+        this.departmentName = departmentName;
+    }
+
+    public Integer getRole() {
+        return role;
+    }
+
+    public void setRole(Integer role) {
+        this.role = role;
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(String roleName) {
+        this.roleName = roleName;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(String createUser) {
+        this.createUser = createUser;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

+ 77 - 0
src/main/java/com/qlm/dto/UserExportDto.java

@@ -0,0 +1,77 @@
+package com.qlm.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+import java.io.Serializable;
+
+/**
+ * 用户导出DTO
+ * 用于导出用户列表数据
+ */
+public class UserExportDto implements Serializable {
+    @ExcelProperty(value = "用户名")
+    private String username;
+
+    @ExcelProperty(value = "姓名")
+    private String realName;
+
+    @ExcelProperty(value = "部门")
+    private String departmentName;
+
+    @ExcelProperty(value = "角色")
+    private String roleName;
+
+    @ExcelProperty(value = "手机")
+    private String mobile;
+
+    @ExcelProperty(value = "状态")
+    private String statusName;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName;
+    }
+
+    public String getDepartmentName() {
+        return departmentName;
+    }
+
+    public void setDepartmentName(String departmentName) {
+        this.departmentName = departmentName;
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(String roleName) {
+        this.roleName = roleName;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+
+    public String getStatusName() {
+        return statusName;
+    }
+
+    public void setStatusName(String statusName) {
+        this.statusName = statusName;
+    }
+}

+ 103 - 0
src/main/java/com/qlm/dto/UserImportDto.java

@@ -0,0 +1,103 @@
+package com.qlm.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+import java.io.Serializable;
+
+/**
+ * 用户导入DTO
+ */
+public class UserImportDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty("用户名")
+    private String username;
+
+    @ExcelProperty("密码")
+    private String password;
+
+    @ExcelProperty("姓名")
+    private String name;
+
+    @ExcelProperty("手机号")
+    private String mobile;
+
+    @ExcelProperty("部门名称")
+    private String deptName;
+
+    @ExcelProperty("角色名称")
+    private String roleName;
+
+    @ExcelProperty("状态")
+    private String status;
+
+    /**
+     * 用于存储导入错误信息
+     */
+    private String errorMsg;
+
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+
+    public String getDeptName() {
+        return deptName;
+    }
+
+    public void setDeptName(String deptName) {
+        this.deptName = deptName;
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(String roleName) {
+        this.roleName = roleName;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+}

+ 14 - 13
src/main/java/com/qlm/jfinal/JfinalConfig.java

@@ -147,20 +147,21 @@ public class JfinalConfig extends JFinalConfig {
 	
 	@Override
 	public void afterJFinalStart() {
-		// TODO Auto-generated method stub
-		super.afterJFinalStart();
-		com.qlm.log.Log.info("当前服务启动成功");
+	    // TODO Auto-generated method stub
+	    super.afterJFinalStart();
+	    com.qlm.log.Log.info("当前服务启动成功");
 	
-		// 启动Netty服务
-		new Thread(() -> {
-		    try {
-		        int port = 8888;
-		        NettyServer server = new NettyServer(port);
-		        server.start();
-		    } catch (InterruptedException e) {
-		        com.qlm.log.Log.error("Netty服务启动失败: {}", e);
-		    }
-		}).start();
+	    // 启动Netty服务
+	    new Thread(() -> {
+	        try {
+	            // 从配置文件读取端口
+	            int port = PropKit.getInt("netty.port", 8888);
+	            NettyServer server = new NettyServer(port);
+	            server.start();
+	        } catch (InterruptedException e) {
+	            com.qlm.log.Log.error("Netty服务启动失败: {}", e);
+	        }
+	    }).start();
 	}
 
 	private void scanClass(Routes me) {

+ 4 - 3
src/main/java/com/qlm/netty/NettyServer.java

@@ -1,5 +1,6 @@
 package com.qlm.netty;
 
+import com.jfinal.kit.PropKit;
 import com.qlm.netty.codec.StringDecoder;
 import com.qlm.netty.codec.StringEncoder;
 import com.qlm.netty.handler.NettyServerHandler;
@@ -41,9 +42,9 @@ public class NettyServer {
                     .childOption(ChannelOption.SO_KEEPALIVE, true)
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         @Override
-                        protected void initChannel(SocketChannel ch) throws Exception {
-                            // 添加空闲状态处理器(60秒超时)
-                            ch.pipeline().addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
+                        protected void initChannel(SocketChannel ch) {
+                            // 添加空闲状态处理器(读超时时间设为30秒,每30秒检查一次心跳状态
+                            ch.pipeline().addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
                             // 添加自定义编解码器
                             ch.pipeline().addLast(new StringDecoder());
                             ch.pipeline().addLast(new StringEncoder());

+ 40 - 0
src/main/java/com/qlm/netty/constant/CommandType.java

@@ -0,0 +1,40 @@
+package com.qlm.netty.constant;
+
+/**
+ * Netty 指令下发类型的枚举
+ */
+public enum CommandType {
+    /**
+     * 设备信息上报指令
+     */
+    DEVICE_INFO_REPORT("DEVICE_INFO_REPORT", "设备信息上报指令"),
+    ;
+    
+    private final String code;
+    private final String desc;
+    
+    CommandType(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+    
+    public String getCode() {
+        return code;
+    }
+    
+    public String getDesc() {
+        return desc;
+    }
+    
+    /**
+     * 根据code获取枚举
+     */
+    public static CommandType fromCode(String code) {
+        for (CommandType type : values()) {
+            if (type.code.equalsIgnoreCase(code)) {
+                return type;
+            }
+        }
+        throw new IllegalArgumentException("未知的指令类型: " + code);
+    }
+}

+ 33 - 0
src/main/java/com/qlm/netty/constant/NettyAttributes.java

@@ -0,0 +1,33 @@
+package com.qlm.netty.constant;
+
+import io.netty.util.AttributeKey;
+
+/**
+ * @author wuyingjianwu
+ */
+public class NettyAttributes {
+    /**
+     * 请求发送时间属性键
+     */
+    public static final AttributeKey<Long> REQUEST_SEND_TIME = AttributeKey.newInstance("requestSendTime");
+    /**
+     * 设备ID属性键
+     */
+    public static final AttributeKey<String> DEVICE_ID = AttributeKey.newInstance("deviceId");
+    /**
+     * 最后心跳时间属性键
+     */
+    public static final AttributeKey<Long> LAST_HEARTBEAT_TIME = AttributeKey.newInstance("lastHeartbeatTime");
+    /**
+     * 通信延迟属性键
+     */
+    public static final AttributeKey<Long> COMMUNICATION_DELAY = AttributeKey.newInstance("communicationDelay");
+    /**
+     * 是否可疑连接标记
+     */
+    public static final AttributeKey<Boolean> IS_SUSPICIOUS = AttributeKey.newInstance("isSuspicious");
+    /**
+     * 可疑连接标记时间
+     */
+    public static final AttributeKey<Long> SUSPICIOUS_TIME = AttributeKey.newInstance("suspiciousTime");
+}

+ 64 - 0
src/main/java/com/qlm/netty/constant/NettyTopic.java

@@ -0,0 +1,64 @@
+package com.qlm.netty.constant;
+
+/**
+ * Netty 交互主题的枚举
+ */
+public enum NettyTopic {
+    /**
+     * 设备主动上报
+     */
+    DEVICE_ACTIVE_REPORT("DEVICE_ACTIVE_REPORT", "设备主动上报"),
+    
+    /**
+     * 心跳检测请求
+     */
+    HEARTBEAT_REQUEST("HEARTBEAT_REQUEST", "心跳检测请求"),
+    
+    /**
+     * 心跳检测响应
+     */
+    HEARTBEAT_RESPONSE("HEARTBEAT_RESPONSE", "心跳检测响应"),
+    
+    /**
+     * 指令下发请求
+     */
+    COMMAND_DOWNLOAD_REQUEST("COMMAND_DOWNLOAD_REQUEST", "指令下发请求"),
+    
+    /**
+     * 指令下发响应
+     */
+    COMMAND_DOWNLOAD_RESPONSE("COMMAND_DOWNLOAD_RESPONSE", "指令下发响应"),
+    
+    /**
+     * 通用应答
+     */
+    COMMON_RESPONSE("COMMON_RESPONSE", "通用应答");
+    
+    private final String code;
+    private final String desc;
+    
+    NettyTopic(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+    
+    public String getCode() {
+        return code;
+    }
+    
+    public String getDesc() {
+        return desc;
+    }
+    
+    /**
+     * 根据code获取枚举
+     */
+    public static NettyTopic fromCode(String code) {
+        for (NettyTopic topic : values()) {
+            if (topic.code.equalsIgnoreCase(code)) {
+                return topic;
+            }
+        }
+        throw new IllegalArgumentException("未知的交互主题: " + code);
+    }
+}

+ 270 - 29
src/main/java/com/qlm/netty/handler/NettyServerHandler.java

@@ -1,9 +1,16 @@
 package com.qlm.netty.handler;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
 import com.jfinal.plugin.activerecord.Db;
 import com.jfinal.plugin.activerecord.Record;
+import com.qlm.netty.constant.NettyAttributes;
+import com.qlm.netty.constant.NettyTopic;
+import com.qlm.netty.manager.DeviceSessionManager;
 import com.qlm.netty.model.IndustrialControlData;
+import com.qlm.netty.model.NettyMessage;
+import com.qlm.netty.util.WhitelistValidator;
+import com.qlm.tools.WxUtil;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.timeout.IdleStateEvent;
@@ -11,54 +18,118 @@ import io.netty.util.ReferenceCountUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.net.InetSocketAddress;
 import java.util.Date;
+import java.util.concurrent.ScheduledFuture;
+
+import static com.qlm.netty.constant.CommandType.DEVICE_INFO_REPORT;
 
 /**
- * @author wuyingjianwu
- * @date 2025/08/29 10:05
- * 描述 : Netty服务端处理类
+ * Netty服务端处理类
  */
 public class NettyServerHandler extends ChannelInboundHandlerAdapter {
     private static final Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);
+    private ScheduledFuture<?> requestTask;
+    // 心跳常量定义
+    private static final long HEARTBEAT_INTERVAL = 30000; // 30秒检查一次
+    private static final long SUSPICIOUS_TIMEOUT = 90000; // 90秒无心跳标记为可疑
+    private static final long CLOSE_TIMEOUT = 120000; // 120秒无心跳关闭连接
 
     @Override
     public void channelActive(ChannelHandlerContext ctx) {
+        // 获取客户端IP并验证白名单
+        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
+        if (!WhitelistValidator.isValidIp(address)) {
+            logger.warn("拒绝非白名单IP连接: {}", address.getAddress().getHostAddress());
+            ctx.close();
+            return;
+        }
+        
         logger.info("客户端连接成功: {}", ctx.channel().remoteAddress());
-        ctx.writeAndFlush("你已成功连接Netty服务端!");
-
+        
+        // 更新最后心跳时间和初始化可疑标记
+        ctx.channel().attr(NettyAttributes.LAST_HEARTBEAT_TIME).set(System.currentTimeMillis());
+        ctx.channel().attr(NettyAttributes.IS_SUSPICIOUS).set(false);
     }
 
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) {
-        logger.info("收到来自工控机程序的消息");
         try {
-            logger.info("收到来自工控机程序的消息: {}",msg);
-            IndustrialControlData data = JSON.parseObject(msg.toString(), IndustrialControlData.class);
-            // 这里可以添加数据处理逻辑,例如存储到数据库
-            processIndustrialData(data);
-            // 回复确认消息
-            ctx.writeAndFlush("SUCCESS");
-        }catch (Exception e){
-            logger.error("处理数据异常: {}",e);
-            ctx.writeAndFlush("ERROR");
-        }
-        finally {
+            String message = msg.toString();
+            logger.info("收到客户端消息: {}", message);
+            // 尝试解析为统一格式消息
+            try {
+                NettyMessage nettyMessage = JSON.parseObject(message, new TypeReference<NettyMessage>() {});
+                handleNettyMessage(ctx, nettyMessage);
+            } catch (Exception e) {
+                // 如果不是统一格式消息,记录警告但不中断处理
+                logger.warn("接收到非统一格式消息: {}", e.getMessage());
+            }
+        } finally {
             // 释放资源
             ReferenceCountUtil.release(msg);
         }
     }
+    
+    /**
+     * 处理统一格式的Netty消息
+     */
+    private void handleNettyMessage(ChannelHandlerContext ctx, NettyMessage nettyMessage) {
+        String topic = nettyMessage.getTopic();
+        String deviceNo = nettyMessage.getDeviceNo();
+        String messageId = nettyMessage.getMessageId();
+        String commandType = nettyMessage.getCommandType();
+
+        // 将会话与会话管理器关联
+        if (deviceNo != null && !deviceNo.isEmpty()) {
+            DeviceSessionManager.getInstance().addDeviceSession(deviceNo, ctx);
+            ctx.channel().attr(NettyAttributes.DEVICE_ID).set(deviceNo);
+        }
+        
+        // 根据消息主题处理不同类型的消息
+        try {
+            switch (NettyTopic.fromCode(topic)) {
+                case HEARTBEAT_RESPONSE:
+                    handleHeartbeat(ctx, nettyMessage);
+                    break;
+                case HEARTBEAT_REQUEST:
+                    // 客户端发起的心跳请求,立即响应
+                    handleClientHeartbeatRequest(ctx, nettyMessage);
+                    break;
+                case DEVICE_ACTIVE_REPORT:
+                    handleDeviceActiveReport(ctx, nettyMessage);
+                    break;
+                case COMMAND_DOWNLOAD_RESPONSE:
+                    if(commandType.equals(DEVICE_INFO_REPORT.getCode())){
+                        handleDeviceInfoResponse(ctx, nettyMessage);
+                    }
+                    break;
+                case COMMON_RESPONSE:
+                    handleCommonResponse(ctx, nettyMessage);
+                    break;
+                default:
+                    logger.warn("未处理的消息主题: {}", topic);
+                    // 返回未支持的主题响应
+                    sendResponse(ctx, messageId, deviceNo, 1, "未支持的消息主题", null,NettyTopic.COMMON_RESPONSE.getCode());
+                    break;
+            }
+        } catch (IllegalArgumentException e) {
+            logger.error("处理消息异常: {}", e.getMessage());
+            sendResponse(ctx, messageId, deviceNo, 2, "处理消息异常: " + e.getMessage(), null,NettyTopic.COMMON_RESPONSE.getCode());
+        }
+    }
 
     @Override
-    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
         if (evt instanceof IdleStateEvent) {
             IdleStateEvent event = (IdleStateEvent) evt;
             switch (event.state()) {
                 case READER_IDLE:
-                    logger.info("客户端 {} 读取超时,关闭连接", ctx.channel().remoteAddress());
-                    ctx.close();
+                    // 读取超时,检查是否有心跳
+                    checkHeartbeat(ctx);
                     break;
                 case WRITER_IDLE:
-                    // 写超时,可发送心跳
+                    // 写超时
                     break;
                 case ALL_IDLE:
                     // 所有超时
@@ -67,30 +138,201 @@ public class NettyServerHandler extends ChannelInboundHandlerAdapter {
             }
         }
     }
+    
+    /**
+     * 检查心跳状态,实现90秒标记可疑、120秒关闭连接的逻辑
+     */
+    private void checkHeartbeat(ChannelHandlerContext ctx) {
+        Long lastHeartbeatTime = ctx.channel().attr(NettyAttributes.LAST_HEARTBEAT_TIME).get();
+        if (lastHeartbeatTime != null) {
+            long idleTime = System.currentTimeMillis() - lastHeartbeatTime;
+            
+            // 如果超过120秒没有心跳,则关闭连接
+            if (idleTime > CLOSE_TIMEOUT) {
+                logger.info("客户端 {} 120秒无心跳,关闭连接并标记设备离线", ctx.channel().remoteAddress());
+                removeDeviceSession(ctx);
+                ctx.close();
+                return;
+            }
+            
+            // 如果超过90秒没有心跳,标记为可疑并发送主动心跳请求确认
+            if (idleTime > SUSPICIOUS_TIMEOUT) {
+                Boolean isSuspicious = ctx.channel().attr(NettyAttributes.IS_SUSPICIOUS).get();
+                if (isSuspicious == null || !isSuspicious) {
+                    logger.info("客户端 {} 90秒无心跳,标记为可疑连接", ctx.channel().remoteAddress());
+                    ctx.channel().attr(NettyAttributes.IS_SUSPICIOUS).set(true);
+                    ctx.channel().attr(NettyAttributes.SUSPICIOUS_TIME).set(System.currentTimeMillis());
+                }
+                
+                // 发送结构化的心跳请求进行确认
+                String deviceNo = ctx.channel().attr(NettyAttributes.DEVICE_ID).get();
+                NettyMessage heartbeatRequest = new NettyMessage(
+                        NettyTopic.HEARTBEAT_REQUEST.getCode(), deviceNo);
+                ctx.writeAndFlush(JSON.toJSONString(heartbeatRequest));
+                logger.debug("向可疑客户端 {} 发送主动心跳请求进行确认", ctx.channel().remoteAddress());
+                return;
+            }
+            
+            // 对于正常连接,每30秒也发送一次心跳请求
+            String deviceNo = ctx.channel().attr(NettyAttributes.DEVICE_ID).get();
+            NettyMessage heartbeatRequest = new NettyMessage(
+                    NettyTopic.HEARTBEAT_REQUEST.getCode(), deviceNo);
+            ctx.writeAndFlush(JSON.toJSONString(heartbeatRequest));
+            logger.debug("向客户端 {} 发送定期心跳请求", ctx.channel().remoteAddress());
+        } else {
+            ctx.close();
+        }
+    }
+    
+    /**
+     * 处理客户端发起的心跳请求
+     */
+    private void handleClientHeartbeatRequest(ChannelHandlerContext ctx, NettyMessage request) {
+        // 更新最后心跳时间
+        ctx.channel().attr(NettyAttributes.LAST_HEARTBEAT_TIME).set(System.currentTimeMillis());
+        // 重置可疑标记
+        ctx.channel().attr(NettyAttributes.IS_SUSPICIOUS).set(false);
+        
+        // 立即回复心跳响应
+        NettyMessage response = new NettyMessage(
+                NettyTopic.HEARTBEAT_RESPONSE.getCode(), request.getDeviceNo());
+        response.setMessageId(request.getMessageId());
+        response.setStatusCode(0);
+        response.setMessage("心跳响应成功");
+        ctx.writeAndFlush(JSON.toJSONString(response));
+    }
 
+    /**
+     * 移除设备会话
+     */
+    private void removeDeviceSession(ChannelHandlerContext ctx) {
+        String deviceId = ctx.channel().attr(NettyAttributes.DEVICE_ID).get();
+        if (deviceId != null) {
+            // 可以在这里添加设备离线的业务逻辑
+            logger.info("设备 {} 已离线", deviceId);
+            DeviceSessionManager.getInstance().removeDeviceSession(deviceId);
+        }
+    }
+    
     @Override
-    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
         logger.error("发生异常: {}", cause.getMessage(), cause);
+        removeDeviceSession(ctx);
         ctx.close();
     }
 
     @Override
-    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+    public void channelInactive(ChannelHandlerContext ctx) {
         logger.info("客户端断开连接: {}", ctx.channel().remoteAddress());
+        removeDeviceSession(ctx);
+    }
+    
+    /**
+     * 发送响应消息
+     */
+    private <T> void sendResponse(ChannelHandlerContext ctx, String messageId, String deviceNo,
+                               int statusCode, String message, T data,String topicType) {
+        NettyMessage response = new NettyMessage (topicType, deviceNo);
+        response.setMessageId(messageId);
+        response.setStatusCode(statusCode);
+        response.setMessage(message);
+        response.setData(data);
+        
+        ctx.writeAndFlush(JSON.toJSONString(response));
+        logger.debug("发送响应: {}",JSON.toJSONString(response));
+    }
+    
+    /**
+     * 处理心跳消息(新版)
+     */
+    private void handleHeartbeat(ChannelHandlerContext ctx, NettyMessage message) {
+        // 更新最后心跳时间
+        ctx.channel().attr(NettyAttributes.LAST_HEARTBEAT_TIME).set(System.currentTimeMillis());
+        // 重置可疑标记
+        ctx.channel().attr(NettyAttributes.IS_SUSPICIOUS).set(false);
+        logger.debug("收到来自 {} 的心跳,消息ID: {}", 
+                    ctx.channel().remoteAddress(), message.getMessageId());
+        // 回复心跳确认
+        //sendResponse(ctx, message.getMessageId(), message.getDeviceNo(),0, "心跳接收成功", null, NettyTopic.HEARTBEAT_RESPONSE.getCode());
+    }
+    
+    /**
+     * 处理设备主动上报
+     */
+    private void handleDeviceActiveReport(ChannelHandlerContext ctx, NettyMessage message) {
+        try {
+            IndustrialControlData data = JSON.parseObject(message.getData().toString(), IndustrialControlData.class);
+            // 处理工业数据
+            processIndustrialData(data,message.getDeviceNo());
+            // 发送成功响应
+            sendResponse(ctx, message.getMessageId(), message.getDeviceNo(), 
+                       0, "数据上报成功", null,NettyTopic.COMMON_RESPONSE.getCode());
+        } catch (Exception e) {
+            logger.error("处理设备主动上报异常: {}", e.getMessage());
+            sendResponse(ctx, message.getMessageId(), message.getDeviceNo(), 
+                       3, "处理数据异常: " + e.getMessage(), null,NettyTopic.COMMON_RESPONSE.getCode());
+        }
+    }
+    
+    /**
+     * 处理设备信息响应,计算通信延迟
+     */
+    private void handleDeviceInfoResponse(ChannelHandlerContext ctx, NettyMessage message) {
+        Long sendTime = ctx.channel().attr(NettyAttributes.REQUEST_SEND_TIME).get();
+        if (sendTime != null) {
+            long delay = System.currentTimeMillis() - sendTime;
+            ctx.channel().attr(NettyAttributes.COMMUNICATION_DELAY).set(delay);
+            logger.info("设备信息响应延迟: {}ms,消息ID: {}", delay, message.getMessageId());
+            
+            // 获取设备ID
+            String deviceId = message.getDeviceNo();
+            if (deviceId != null) {
+                try {
+                    // 查询设备是否存在
+                    Record device = Db.findFirst("select * from t_jz_device where device_no = ?", deviceId);
+                    if (device != null) {
+                        // 查询设备详细信息
+                        Record deviceDetail = Db.findFirst("select * from t_device_detail where device_id = ?", WxUtil.getInt("id", device));
+                        if (deviceDetail != null) {
+                            // 更新设备延迟信息
+                            deviceDetail.set("network_delay", delay);
+                            deviceDetail.set("updated_time", new Date());
+                            Db.update("t_device_detail", "device_id", deviceDetail);
+                        }
+                    }
+                } catch (Exception e) {
+                    logger.error("保存通信延迟信息失败: {}", e.getMessage());
+                }
+            }
+        }
+        
+        // 发送响应
+        sendResponse(ctx, message.getMessageId(), message.getDeviceNo(), 
+                   0, "设备信息接收成功", null, NettyTopic.COMMON_RESPONSE.getCode());
+    }
+    
+    /**
+     * 处理通用响应
+     */
+    private void handleCommonResponse(ChannelHandlerContext ctx, NettyMessage message) {
+        logger.debug("收到客户端响应 - 消息ID: {}, 状态码: {}, 消息: {}",
+                    message.getMessageId(), message.getStatusCode(), message.getMessage());
+        
+        // 可以根据实际需求处理客户端的响应
+        // 例如:更新指令执行状态等
     }
 
     /**
      * 处理工控机数据
-     * @param data
      */
-    private void processIndustrialData(IndustrialControlData data) {
+    private void processIndustrialData(IndustrialControlData data,String deviceNo) {
         // 这里实现数据存储或其他业务逻辑
-        String deviceId = data.getDeviceId();
-        Record device = Db.findFirst("select * from t_jz_device where id = ?", deviceId);
+        Record device = Db.findFirst("select * from t_jz_device where device_no = ?", deviceNo);
         if(device == null){
-            logger.info("设备不存在: {}", deviceId);
+            logger.info("设备不存在: {}", deviceNo);
             return;
         }
+        Integer deviceId = WxUtil.getInt("id", device);
         //查询设备详细信息
         Record deviceDetail = Db.findFirst("select * from t_device_detail where device_id = ?", deviceId);
         Record deviceDetailDb = new Record();
@@ -103,7 +345,6 @@ public class NettyServerHandler extends ChannelInboundHandlerAdapter {
         deviceDetailDb.set("current_quantity", data.getProductionTaskNum());
         deviceDetailDb.set("total_quantity", data.getProductionTaskTotal());
         deviceDetailDb.set("uploaded_quantity", data.getProductionTaskTransferNum());
-        deviceDetailDb.set("product_id",data.getProductId());
         if (deviceDetail == null) {
             Db.save("t_device_detail", deviceDetailDb);
         }else{

+ 111 - 0
src/main/java/com/qlm/netty/manager/DeviceSessionManager.java

@@ -0,0 +1,111 @@
+package com.qlm.netty.manager;
+
+import com.alibaba.fastjson.JSON;
+import com.qlm.netty.constant.NettyAttributes;
+import com.qlm.netty.constant.NettyTopic;
+import com.qlm.netty.model.NettyMessage;
+import io.netty.channel.ChannelHandlerContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 设备会话管理器
+ */
+public class DeviceSessionManager {
+    private static final Logger logger = LoggerFactory.getLogger(DeviceSessionManager.class);
+    private static final DeviceSessionManager instance = new DeviceSessionManager();
+    
+    // 存储设备ID和ChannelHandlerContext的映射关系
+    private final Map<String, ChannelHandlerContext> deviceSessions = new ConcurrentHashMap<>();
+    
+    private DeviceSessionManager() {
+    }
+    
+    public static DeviceSessionManager getInstance() {
+        return instance;
+    }
+    
+    /**
+     * 添加设备会话
+     */
+    public void addDeviceSession(String deviceId, ChannelHandlerContext ctx) {
+        if (deviceId != null && ctx != null) {
+            //判断设备是否存在
+            if (deviceSessions.containsKey(deviceId)) {
+                return;
+            }
+            deviceSessions.put(deviceId, ctx);
+            logger.info("添加设备会话: {}, 当前会话数: {}", deviceId, deviceSessions.size());
+        }
+    }
+    
+    /**
+     * 移除设备会话
+     */
+    public void removeDeviceSession(String deviceId) {
+        if (deviceId != null) {
+            deviceSessions.remove(deviceId);
+            logger.info("移除设备会话: {}, 当前会话数: {}", deviceId, deviceSessions.size());
+        }
+    }
+    
+    /**
+     * 获取设备会话
+     */
+    public ChannelHandlerContext getDeviceSession(String deviceId) {
+        if (deviceId != null) {
+            return deviceSessions.get(deviceId);
+        }
+        return null;
+    }
+    
+    /**
+     * 获取所有设备ID
+     */
+    public Set<String> getAllDeviceIds() {
+        return deviceSessions.keySet();
+    }
+    
+    /**
+     * 发送消息给指定设备
+     */
+    public <T> boolean sendMessage(String deviceId, String topic, T data,String commandType) {
+        ChannelHandlerContext ctx = getDeviceSession(deviceId);
+        if (ctx != null && ctx.channel().isActive()) {
+            try {
+                NettyMessage message = new NettyMessage(topic, deviceId, data);
+                message.setCommandType(commandType);
+                ctx.writeAndFlush(JSON.toJSONString(message));
+                //记录发送时间键值
+                ctx.channel().attr(NettyAttributes.REQUEST_SEND_TIME).set(System.currentTimeMillis());
+                logger.debug("向设备 {} 发送消息,主题: {}", deviceId, topic);
+                return true;
+            } catch (Exception e) {
+                logger.error("向设备 {} 发送消息失败: {}", deviceId, e.getMessage());
+            }
+        } else {
+            logger.warn("设备 {} 会话不存在或已关闭", deviceId);
+            // 会话不存在或已关闭时移除
+            removeDeviceSession(deviceId);
+        }
+        return false;
+    }
+    
+    /**
+     * 发送指令给指定设备
+     */
+    public boolean sendCommand(String deviceId, Object commandData,String commandType) {
+        return sendMessage(deviceId, NettyTopic.COMMAND_DOWNLOAD_REQUEST.getCode(), commandData,commandType);
+    }
+    
+    /**
+     * 获取当前会话数量
+     */
+    public int getSessionCount() {
+        return deviceSessions.size();
+    }
+}

+ 0 - 27
src/main/java/com/qlm/netty/model/IndustrialControlData.java

@@ -6,10 +6,6 @@ import java.util.Date;
 public class IndustrialControlData implements Serializable {
     private static final long serialVersionUID = 1L;
 
-    /**
-     * 设备ID
-     */
-    private String deviceId;
     /**
      * CPU使用率 (%)
      */
@@ -48,14 +44,6 @@ public class IndustrialControlData implements Serializable {
      */
     private String productionTaskTransferNum;
 
-    public String getDeviceId() {
-        return deviceId;
-    }
-
-    public void setDeviceId(String deviceId) {
-        this.deviceId = deviceId;
-    }
-
     public String getCpuUsage() {
         return cpuUsage;
     }
@@ -119,19 +107,4 @@ public class IndustrialControlData implements Serializable {
     public void setProductionTaskTransferNum(String productionTaskTransferNum) {
         this.productionTaskTransferNum = productionTaskTransferNum;
     }
-
-    @Override
-    public String toString() {
-        return "IndustrialControlData{" +
-                "deviceId='" + deviceId + '\'' +
-                ", cpuUsage='" + cpuUsage + '\'' +
-                ", memoryUsage='" + memoryUsage + '\'' +
-                ", diskFreeSpace='" + diskFreeSpace + '\'' +
-                ", productId='" + productId + '\'' +
-                ", productionTaskCount='" + productionTaskCount + '\'' +
-                ", productionTaskNum='" + productionTaskNum + '\'' +
-                ", productionTaskTotal='" + productionTaskTotal + '\'' +
-                ", productionTaskTransferNum='" + productionTaskTransferNum + '\'' +
-                '}';
-    }
 }

+ 147 - 0
src/main/java/com/qlm/netty/model/NettyMessage.java

@@ -0,0 +1,147 @@
+package com.qlm.netty.model;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * 统一的Netty消息模型
+ */
+public class NettyMessage implements Serializable {
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 消息主题类型
+     */
+    private String topic;
+    
+    /**
+     * 设备编号
+     */
+    private String deviceNo;
+    
+    /**
+     * 消息唯一ID
+     */
+    private String messageId;
+    
+    /**
+     * 状态码
+     * 0: 成功
+     * 非0: 失败
+     */
+    private int statusCode = 0;
+    
+    /**
+     * 消息描述
+     */
+    private String message;
+
+    /**
+     * 指令下发类型 1、设备信息上报
+     */
+    private String commandType;
+    
+    /**
+     * 数据内容
+     */
+    private Object data;
+    
+    /**
+     * 时间戳
+     */
+    private long timestamp;
+    
+    public NettyMessage() {
+        this.messageId = UUID.randomUUID().toString();
+        this.timestamp = System.currentTimeMillis();
+    }
+    
+    public NettyMessage(String topic, String deviceNo) {
+        this();
+        this.topic = topic;
+        this.deviceNo = deviceNo;
+    }
+    
+    public NettyMessage(String topic, String deviceNo, Object data) {
+        this(topic, deviceNo);
+        this.data = data;
+    }
+    
+    public String getTopic() {
+        return topic;
+    }
+    
+    public void setTopic(String topic) {
+        this.topic = topic;
+    }
+    
+    public String getDeviceNo() {
+        return deviceNo;
+    }
+    
+    public void setDeviceNo(String deviceNo) {
+        this.deviceNo = deviceNo;
+    }
+    
+    public String getMessageId() {
+        return messageId;
+    }
+    
+    public void setMessageId(String messageId) {
+        this.messageId = messageId;
+    }
+    
+    public int getStatusCode() {
+        return statusCode;
+    }
+    
+    public void setStatusCode(int statusCode) {
+        this.statusCode = statusCode;
+    }
+    
+    public String getMessage() {
+        return message;
+    }
+    
+    public void setMessage(String message) {
+        this.message = message;
+    }
+    
+    public Object getData() {
+        return data;
+    }
+    
+    public void setData(Object data) {
+        this.data = data;
+    }
+    
+    public long getTimestamp() {
+        return timestamp;
+    }
+    
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getCommandType() {
+        return commandType;
+    }
+
+    public void setCommandType(String commandType) {
+        this.commandType = commandType;
+    }
+
+    @Override
+    public String toString() {
+        return "NettyMessage{" +
+                "topic='" + topic + '\'' +
+                ", deviceNo='" + deviceNo + '\'' +
+                ", messageId='" + messageId + '\'' +
+                ", statusCode=" + statusCode +
+                ", message='" + message + '\'' +
+                ", commandType='" + commandType + '\'' +
+                ", data=" + data +
+                ", timestamp=" + timestamp +
+                '}';
+    }
+}

+ 75 - 0
src/main/java/com/qlm/netty/util/WhitelistValidator.java

@@ -0,0 +1,75 @@
+package com.qlm.netty.util;
+
+import com.jfinal.plugin.activerecord.Db;
+import com.jfinal.plugin.activerecord.Record;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class WhitelistValidator {
+    private static final Logger logger = LoggerFactory.getLogger(WhitelistValidator.class);
+    private static final Set<String> whitelistIps = new HashSet<>();
+    private static final String WHITELIST_TABLE = "device_netty_whitelist";
+    
+    static {
+        // 从数据库加载白名单
+        loadWhitelist();
+    }
+    
+    /**
+     * 加载IP白名单
+     */
+    private static void loadWhitelist() {
+        try {
+            // 从数据库查询所有白名单IP
+            List<Record> records = Db.find("SELECT ip_address FROM " + WHITELIST_TABLE);
+            
+            if (records != null && !records.isEmpty()) {
+                for (Record record : records) {
+                    String ip = record.getStr("ip_address");
+                    if (ip != null && !ip.trim().isEmpty()) {
+                        whitelistIps.add(ip.trim());
+                    }
+                }
+                logger.info("已从数据库加载IP白名单,共{}个IP", whitelistIps.size());
+            } else {
+                logger.warn("数据库中未配置IP白名单,允许所有IP连接");
+            }
+        } catch (Exception e) {
+            logger.error("从数据库加载白名单失败: {}", e.getMessage(), e);
+            // 加载失败时清空白名单,允许所有IP连接
+            whitelistIps.clear();
+        }
+    }
+    
+    /**
+     * 验证IP是否在白名单中
+     */
+    public static boolean isValidIp(InetSocketAddress address) {
+        // 如果白名单为空,则允许所有IP
+        if (whitelistIps.isEmpty()) {
+            return true;
+        }
+        
+        String clientIp = address.getAddress().getHostAddress();
+        boolean isValid = whitelistIps.contains(clientIp);
+        
+        if (!isValid) {
+            logger.warn("IP[{}]不在白名单中,拒绝连接", clientIp);
+        }
+        
+        return isValid;
+    }
+    
+    /**
+     * 重新加载白名单
+     */
+    public static void reloadWhitelist() {
+        whitelistIps.clear();
+        loadWhitelist();
+    }
+}

+ 10 - 6
src/main/java/com/qlm/oss/OssUtil.java

@@ -101,12 +101,16 @@ public class OssUtil {
             throw new RuntimeException("不支持的文件类型,仅支持: " + String.join(", ", OssConfig.ALLOWED_TYPES));
         }
 
-        // 生成新的文件名
-        String newFileName = UUID.randomUUID().toString().replace("-", "") + fileExt;
-
-        // OSS文件路径
-        String ossPath = OssConfig.MKDIR + DateUtil.format(new Date(), "yyyyMMdd") + "/" + newFileName;
-
+        //不是zip包的情况下
+        String ossPath = "";
+        if (!filename.endsWith(".zip")) {
+            // 生成新的文件名
+            String newFileName = UUID.randomUUID().toString().replace("-", "") + fileExt;
+            // OSS文件路径
+            ossPath = OssConfig.MKDIR + DateUtil.format(new Date(), "yyyyMMdd") + "/" + newFileName;
+        }else {
+            ossPath = OssConfig.MKDIR + DateUtil.format(new Date(), "yyyyMMdd") + "/" + filename;
+        }
         // 创建OSS客户端
         OSS ossClient = new OSSClientBuilder().build(
                 OssConfig.ENDPOINT,

+ 1 - 1
src/main/java/com/qlm/service/IQrcodeAddService.java

@@ -8,7 +8,7 @@ public interface IQrcodeAddService {
 	
 	public List<String> getPropertiesUrl();
 	
-	public String getZipFilePath(String url,String code,Integer num,String filePath,Integer addNum,String id,Integer type,String area,AdminView loginUser);
+	public String getZipFilePath(String url,String code,Integer num,String filePath,Integer addNum,String id,Integer type,String area,AdminView loginUser,boolean isApply);
 	
 	public Integer getProgress(String id);
 }

+ 17 - 0
src/main/java/com/qlm/service/IQrcodeApplyService.java

@@ -0,0 +1,17 @@
+package com.qlm.service;
+
+import com.qlm.common.ApiResponse;
+import com.qlm.dto.QrcodeApplyDto;
+import com.qlm.view.core.AdminView;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: IQrcodeApplyService
+ * @description: TODO
+ * @author: wuyingjian
+ * @create: 2025-09-02 11:16
+ * @Version 1.0
+ **/
+public interface IQrcodeApplyService {
+    ApiResponse applyQrcode(QrcodeApplyDto dto, AdminView applyUser);
+}

+ 45 - 0
src/main/java/com/qlm/service/IUserService.java

@@ -0,0 +1,45 @@
+package com.qlm.service;
+
+import com.qlm.common.ApiResponse;
+import com.qlm.common.PageResult;
+import com.qlm.dto.UserDto;
+import com.qlm.dto.UserExportDto;
+import com.qlm.dto.UserImportDto;
+import com.qlm.view.core.AdminView;
+
+import java.util.List;
+
+/**
+ * 用户管理Service接口
+ */
+public interface IUserService {
+
+    /**
+     * 保存用户信息
+     */
+    ApiResponse saveUser(UserDto userDto, AdminView loginUser);
+
+    /**
+     * 更新用户信息
+     */
+    ApiResponse updateUser(UserDto userDto);
+
+    /**
+     * 删除用户信息
+     */
+    ApiResponse deleteUser(Integer id);
+
+    /**
+     * 根据ID获取用户信息
+     */
+    ApiResponse getUserById(Integer id);
+
+    /**
+     * 用户列表查询
+     */
+    PageResult<UserDto> listUser(int pageNumber, int pageSize, String username, String realName, Integer departmentId, Integer status);
+
+    ApiResponse importUser(List<UserImportDto> importList, String username);
+
+    List<UserExportDto> exportUserList(String userName);
+}

+ 8 - 8
src/main/java/com/qlm/service/impl/LineProductServiceImpl.java

@@ -70,7 +70,7 @@ public class LineProductServiceImpl implements ILineProductService {
             }
 
             // 获取产线名称
-            Record line = Db.findById("t_production_line", lineProductDto.getLineId());
+            Record line = Db.findById("t_jz_device", lineProductDto.getLineId());
             if (line == null) {
                 return ApiResponse.error("未找到指定的产线信息");
             }
@@ -88,7 +88,7 @@ public class LineProductServiceImpl implements ILineProductService {
             record.set("workshop_id", lineProductDto.getWorkshopId());
             record.set("workshop_name", workshop.getStr("workshop_name"));
             record.set("line_id", lineProductDto.getLineId());
-            record.set("line_name", line.getStr("line_name"));
+            record.set("line_name", line.getStr("desc"));
             record.set("product_id", lineProductDto.getProductId());
             record.set("product_name", product.getStr("product_name"));
             record.set("operator", lineProductDto.getOperator());
@@ -235,7 +235,7 @@ public class LineProductServiceImpl implements ILineProductService {
     @Override
     public ApiResponse getLinesByWorkshopId(Long workshopId) {
         try {
-            StringBuilder sql = new StringBuilder("select id, line_name as lineName from t_production_line where status = 1");
+            StringBuilder sql = new StringBuilder("select id, `desc` as lineName from t_jz_device where status = 1");
             List<Object>params = new ArrayList<>();
             if(workshopId != null){
                 sql.append(" and workshop_id = ? ");
@@ -342,7 +342,7 @@ public class LineProductServiceImpl implements ILineProductService {
             }
 
             // 获取产线名称
-            Record line = Db.findById("t_production_line", lineProductDto.getLineId());
+            Record line = Db.findById("t_jz_device", lineProductDto.getLineId());
             if (line == null) {
                 return ApiResponse.error("未找到指定的产线信息");
             }
@@ -361,7 +361,7 @@ public class LineProductServiceImpl implements ILineProductService {
             record.set("workshop_id", lineProductDto.getWorkshopId());
             record.set("workshop_name", workshop.getStr("workshop_name"));
             record.set("line_id", lineProductDto.getLineId());
-            record.set("line_name", line.getStr("line_name"));
+            record.set("line_name", line.getStr("desc"));
             record.set("product_id", lineProductDto.getProductId());
             record.set("product_name", product.getStr("product_name"));
             record.set("operator", lineProductDto.getOperator());
@@ -455,7 +455,7 @@ public class LineProductServiceImpl implements ILineProductService {
                 // 获取名称信息
                 Record factory = Db.findById("t_factory", factoryId);
                 Record workshop = Db.findById("t_workshop", workshopId);
-                Record line = Db.findById("t_production_line", lineId);
+                Record line = Db.findById("t_jz_device", lineId);
                 Record product = Db.findById("t_jz_product", productId);
 
                 // 保存数据
@@ -465,7 +465,7 @@ public class LineProductServiceImpl implements ILineProductService {
                 addrRecord.set("workshop_id", workshopId);
                 addrRecord.set("workshop_name", workshop.getStr("workshop_name"));
                 addrRecord.set("line_id", lineId);
-                addrRecord.set("line_name", line.getStr("line_name"));
+                addrRecord.set("line_name", line.getStr("desc"));
                 addrRecord.set("product_id", productId);
                 addrRecord.set("product_name", product.getStr("product_name"));
                 addrRecord.set("operator", operator);
@@ -516,7 +516,7 @@ public class LineProductServiceImpl implements ILineProductService {
 
     @Override
     public Long getLineIdByCode(String lineCode) {
-        Record record = Db.findFirst("select id from t_production_line where line_code = ?", lineCode);
+        Record record = Db.findFirst("select id from t_jz_device where device_no = ?", lineCode);
         return record != null ? WxUtil.getInt("id", record).longValue() : null;
     }
 

+ 17 - 16
src/main/java/com/qlm/service/impl/ProductionLineServiceImpl.java

@@ -43,15 +43,15 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             }
 
             // 检查产线编号是否已存在
-            Record existRecord = Db.findFirst("select id from t_production_line where line_code = ?", productionLineDto.getLineCode());
+            Record existRecord = Db.findFirst("select id from t_jz_device where device_no = ?", productionLineDto.getLineCode());
             if (existRecord != null) {
                 return ApiResponse.error("产线编号已存在");
             }
 
             // 保存数据
             Record record = new Record();
-            record.set("line_code", productionLineDto.getLineCode());
-            record.set("line_name", productionLineDto.getLineName());
+            record.set("device_no", productionLineDto.getLineCode());
+            record.set("desc", productionLineDto.getLineName());
             record.set("factory_id", productionLineDto.getFactoryId());
             record.set("workshop_id", productionLineDto.getWorkshopId());
             record.set("contact_name", productionLineDto.getContactName());
@@ -60,7 +60,7 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             record.set("status", productionLineDto.getStatus() != null ? productionLineDto.getStatus() : 1);
             record.set("operator", productionLineDto.getOperator());
 
-            boolean result = Db.save("t_production_line", record);
+            boolean result = Db.save("t_jz_device", record);
             if (result) {
                 return ApiResponse.success("保存成功");
             } else {
@@ -97,7 +97,7 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             }
 
             // 检查产线编号是否已存在(排除当前记录)
-            Record existRecord = Db.findFirst("select id from t_production_line where line_code = ? and id != ?", 
+            Record existRecord = Db.findFirst("select id from t_jz_device where device_no = ? and id != ?",
                     productionLineDto.getLineCode(), productionLineDto.getId());
             if (existRecord != null) {
                 return ApiResponse.error("产线编号已存在");
@@ -106,8 +106,8 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             // 更新数据
             Record record = new Record();
             record.set("id", productionLineDto.getId());
-            record.set("line_code", productionLineDto.getLineCode());
-            record.set("line_name", productionLineDto.getLineName());
+            record.set("device_no", productionLineDto.getLineCode());
+            record.set("desc", productionLineDto.getLineName());
             record.set("factory_id", productionLineDto.getFactoryId());
             record.set("workshop_id", productionLineDto.getWorkshopId());
             record.set("contact_name", productionLineDto.getContactName());
@@ -116,7 +116,7 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             record.set("status", productionLineDto.getStatus());
             record.set("operator", productionLineDto.getOperator());
 
-            boolean result = Db.update("t_production_line", record);
+            boolean result = Db.update("t_jz_device", record);
             if (result) {
                 return ApiResponse.success("更新成功");
             } else {
@@ -138,7 +138,7 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             // 检查是否存在关联数据(如果有)
             // TODO: 如果有其他表关联到产线表,需要先检查并处理
 
-            boolean result = Db.deleteById("t_production_line", id);
+            boolean result = Db.deleteById("t_jz_device", id);
             if (result) {
                 return ApiResponse.success("删除成功");
             } else {
@@ -158,7 +158,7 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             }
 
             // 关联查询工厂和车间名称
-            String sql = "select pl.*, f.factory_name, w.workshop_name from t_production_line pl " +
+            String sql = "select pl.*, f.factory_name, w.workshop_name from t_jz_device pl " +
                          "left join t_factory f on pl.factory_id = f.id " +
                          "left join t_workshop w on pl.workshop_id = w.id " +
                          "where pl.id = ?";
@@ -191,7 +191,7 @@ public class ProductionLineServiceImpl implements IProductionLineService {
             record.set("status", status);
             record.set("updated_time", WxUtil.getNowTime());
 
-            boolean result = Db.update("t_production_line", record);
+            boolean result = Db.update("t_jz_device", record);
             if (result) {
                 return ApiResponse.success("状态更新成功");
             } else {
@@ -207,19 +207,19 @@ public class ProductionLineServiceImpl implements IProductionLineService {
     public PageResult<ProductionLineDto> listProductionLine(int pageNumber, int pageSize, String lineName, String lineCode, Long factoryId, Long workshopId) {
         try {
             // 构建查询条件
-            StringBuilder sqlWhere = new StringBuilder(" from t_production_line pl " +
+            StringBuilder sqlWhere = new StringBuilder(" from t_jz_device pl " +
                                              "left join t_factory f on pl.factory_id = f.id " +
                                              "left join t_workshop w on pl.workshop_id = w.id " +
                                              "where 1=1 ");
             List<Object> params = new ArrayList<>();
 
             if (StrKit.notBlank(lineName)) {
-                sqlWhere.append(" and pl.line_name like ? ");
+                sqlWhere.append(" and pl.desc like ? ");
                 params.add("%" + lineName + "%");
             }
 
             if (StrKit.notBlank(lineCode)) {
-                sqlWhere.append(" and pl.line_code like ? ");
+                sqlWhere.append(" and pl.device_no like ? ");
                 params.add("%" + lineCode + "%");
             }
 
@@ -315,8 +315,8 @@ public class ProductionLineServiceImpl implements IProductionLineService {
     private ProductionLineDto convertRecordToDto(Record record) {
         ProductionLineDto dto = new ProductionLineDto();
         dto.setId(WxUtil.getInt("id",record).longValue());
-        dto.setLineCode(record.getStr("line_code"));
-        dto.setLineName(record.getStr("line_name"));
+        dto.setLineCode(record.getStr("device_no"));
+        dto.setLineName(record.getStr("desc"));
         dto.setFactoryId(WxUtil.getInt("factory_id", record).longValue());
         dto.setFactoryName(record.getStr("factory_name"));
         dto.setWorkshopId(WxUtil.getInt("workshop_id", record).longValue());
@@ -324,6 +324,7 @@ public class ProductionLineServiceImpl implements IProductionLineService {
         dto.setContactName(record.getStr("contact_name"));
         dto.setContactPhone(record.getStr("contact_phone"));
         dto.setAccountId(WxUtil.getStr("account_id", record));
+        dto.setOperDetail(record.getStr("oper_detail"));
         dto.setStatus(record.getInt("status"));
         dto.setCreatedTime(record.getDate("created_time"));
         dto.setUpdatedTime(record.getDate("updated_time"));

+ 92 - 20
src/main/java/com/qlm/service/impl/QrcodeAddServiceImpl.java

@@ -1,8 +1,8 @@
 package com.qlm.service.impl;
 
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -11,7 +11,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.locks.ReentrantLock;
 
+import com.qlm.oss.OssUtil;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipOutputStream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -36,8 +40,8 @@ public class QrcodeAddServiceImpl implements IQrcodeAddService {
 	protected final static Logger logger = LoggerFactory
 			.getLogger(QrcodeAddServiceImpl.class);
 	private static Map<String, Integer> map = new HashMap<String, Integer>();
-	
-	static boolean isRunning = false;
+
+	private static final ReentrantLock lock = new ReentrantLock();
 	
 	private IProductService productService = Enhancer.enhance(ProductServiceImpl.class);
 
@@ -47,8 +51,8 @@ public class QrcodeAddServiceImpl implements IQrcodeAddService {
 	@Override
 	@Before(Tx.class)
 	public String getZipFilePath(String url, String code, Integer num,
-			String filePath, Integer addNum, String id,Integer type,String area,AdminView loginUser) {
-		if(isRunning){
+			String filePath, Integer addNum, String id,Integer type,String area,AdminView loginUser,boolean isApply) {
+		if(!lock.tryLock()){
 			return "有任务正在执行,请稍后";
 		}
 		logger.info("method getZipFilePath url=" + url);
@@ -61,39 +65,48 @@ public class QrcodeAddServiceImpl implements IQrcodeAddService {
 		logger.info("method getZipFilePath area=" + area);
 		// 判断传入参数是否正确
 		if (!Common.isEmptyString(code)) {
+			lock.unlock(); // 释放锁
 			return "网络连接错误";
 		}
 		if (!Common.isEmptyString(id)) {
+			lock.unlock(); // 释放锁
 			return "网络连接错误";
 		}
 		if (!Common.isEmptyString(filePath)) {
+			lock.unlock(); // 释放锁
 			return "网络连接错误";
 		}
 		if (Common.isNullOrEmpty(addNum)) {
+			lock.unlock(); // 释放锁
 			return "网络连接错误";
 		}
 		if (Common.isNullOrEmpty(type)) {
+			lock.unlock(); // 释放锁
 			return "网络连接错误";
 		}
 		if (Common.isNullOrEmpty(num)) {
+			lock.unlock(); // 释放锁
 			return "网络连接错误";
 		}
 		if (Common.isNullOrEmpty(area)) {
+			lock.unlock(); // 释放锁
 			return "网络连接错误";
 		}
 		if (num > Define.ADD_QRCODE_MAX) {
+			lock.unlock(); // 释放锁
 			return "产码数量超过500W";
 		}
 		if (num < Define.ADD_QRCODE_MIN) {
+			lock.unlock(); // 释放锁
 			return "产码数量小于1";
 		}
 
 		try{
-			isRunning = true;
 			int uid = loginUser.getId();
 			code=code.toLowerCase();
 			String dbname = PropKit.use("config.properties").get("dbname");
-			
+
+			File qrcodeZipFile = null;
 			String sourceFilePath = null;// TXT文件路径
 			String tablename = "t_qcode_"+code;// 二维码表名
 			String hbtable = "t_qcode_hb_"+code;// 红包表名
@@ -161,24 +174,24 @@ public class QrcodeAddServiceImpl implements IQrcodeAddService {
 				Db.update(sb.toString());
 				insertCount = num;
 				put(map, id, insertCount);
-				sourceFilePath = getTxtPath(num, filePath, maxNum, tablename,code,
-						addNum, createTime, url ,type);
+				if(!isApply){
+					sourceFilePath = getTxtPath(num, filePath, maxNum, tablename,code,
+							addNum, createTime, url ,type);
+				}else{
+					qrcodeZipFile = generateQrcodeZip(num,filePath,maxNum,tablename,code);
+				}
 			}
-			
-			
-			
-			
 			map.remove(id);
+			if(isApply){
+				return uploadToOSSWithInputStream(filePath,qrcodeZipFile);
+			}
 			String productName = Db.queryStr("select item_name name_ from t_jz_item where id = ?",type);
 			return getFilePath(filePath, sourceFilePath,num,productName);
 		}catch (Exception e) {
-			e.printStackTrace();
-			isRunning = false;
-			logger.error("产码错误",e);
+			throw  new RuntimeException("产码错误:"+e);
 		}finally{
-			isRunning = false;
+			lock.unlock(); // 释放锁
 		}
-		return "网络连接错误";
 	}
 	  public static String getRandomNumber(int length) { //length��ʾ�����ַ����ij���
 		    String base = "0123456789";   
@@ -293,6 +306,50 @@ public class QrcodeAddServiceImpl implements IQrcodeAddService {
 		return null;
 	}
 
+
+
+	private File generateQrcodeZip(int count, String zipFilePath, long maxNum,
+								   String tableName, String url) {
+		String txtFileName = DateUtils.getStringFullDate() + "-" + count+".txt";
+		// SQL 查询
+		String sql = "select id, num_, random_ from " + tableName + " where num_ > ? order by num_ asc";
+		List<Record> idList = Db.find(sql, maxNum);
+
+		if (idList == null || idList.isEmpty()) {
+			return null;
+		}
+
+		File zipFile = new File(zipFilePath);
+
+		try (FileOutputStream fos = new FileOutputStream(zipFile);
+			 ZipOutputStream zos = new ZipOutputStream(fos)) {
+
+			// zip里放一个txt文件
+			ZipEntry entry = new ZipEntry(txtFileName);
+			zos.putNextEntry(entry);
+
+			int size = Math.min(count, idList.size());
+			for (int j = 0; j < size; j++) {
+				String strId = idList.get(j).get("id").toString();
+				zos.write(strId.toLowerCase().getBytes(StandardCharsets.UTF_8));
+
+				// 除了最后一行,都换行
+				if (j < size - 1) {
+					zos.write("\n".getBytes(StandardCharsets.UTF_8));
+				}
+			}
+
+			zos.closeEntry();
+			zos.flush();
+
+			return zipFile;
+		} catch (Exception e) {
+			logger.error("创建zip文件失败", e);
+		}
+		return null;
+	}
+
+
 	/**
 	 * 获取产码数量
 	 */
@@ -419,4 +476,19 @@ public class QrcodeAddServiceImpl implements IQrcodeAddService {
 	        return string;
 	}
 
+	/**
+	 * 上传ZIP文件到OSS(使用InputStream方式)
+	 * @param file 需要打包的文件
+	 * @param fileName 文件名
+	 * @return OSS访问地址
+	 */
+	private String uploadToOSSWithInputStream(String fileName,File zipFile){
+		try {
+			InputStream inputStream  = Files.newInputStream(zipFile.toPath());
+			return OssUtil.upload(inputStream, fileName);
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+    }
+
 }

+ 163 - 0
src/main/java/com/qlm/service/impl/QrcodeApplyServiceImpl.java

@@ -0,0 +1,163 @@
+package com.qlm.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import com.jfinal.aop.Enhancer;
+import com.jfinal.kit.PropKit;
+import com.jfinal.kit.StrKit;
+import com.jfinal.plugin.activerecord.Db;
+import com.jfinal.plugin.activerecord.Record;
+import com.qlm.common.ApiResponse;
+import com.qlm.dto.QrcodeApplyDto;
+import com.qlm.service.IQrcodeAddService;
+import com.qlm.service.IQrcodeApplyService;
+import com.qlm.tools.Common;
+import com.qlm.tools.DateUtils;
+import com.qlm.tools.WxUtil;
+import com.qlm.view.core.AdminView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static com.jfinal.aop.Enhancer.enhance;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: QrcodeApplyServiceImpl
+ * @description: 二维码申请服务实现类
+ * @author: wuyingjian
+ * @create: 2025-09-02 11:17
+ * @Version 1.0
+ **/
+public class QrcodeApplyServiceImpl implements IQrcodeApplyService {
+
+    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyMMdd");
+    private static final Logger logger = LoggerFactory.getLogger(QrcodeApplyServiceImpl.class);
+    private IQrcodeAddService qrcodeAddService = enhance(QrcodeAddServiceImpl.class);
+    /**
+     * 创建线程池用于异步处理二维码生成
+     */
+    private static final ExecutorService executorService = Executors.newFixedThreadPool(5);
+
+    @Override
+    public ApiResponse applyQrcode(QrcodeApplyDto dto, AdminView applyUser) {
+        try {
+            // 2. 生成申请单号
+            String applyNo = generateApplyNo();
+            dto.setApplyNo(applyNo);
+            dto.setApplicant(applyUser.getUsername());
+            dto.setApplyTime(DateUtils.getDateFormatStr(DateUtils.PATTEN_HMS));
+
+            // 3. 保存申请记录到数据库,状态设置为"生成中"
+            Integer applyQuantity = calculateActualQuantity(dto.getApplyQuantity());
+            dto.setApplyQuantity(applyQuantity);
+            Record record = new Record();
+            record.set("apply_no", dto.getApplyNo());
+            record.set("code_config", dto.getApplyConfig());
+            record.set("brand_id", dto.getCompanyId());
+            record.set("product_id", dto.getProductId());
+            record.set("supplier_name", dto.getSupplier());
+            record.set("package_unit", dto.getPackagingUnit());
+            record.set("code_quantity", applyQuantity);
+            record.set("remark", dto.getRemark());
+            record.set("applicant", dto.getApplicant());
+            record.set("apply_time", dto.getApplyTime());
+            record.set("status", 2);
+            Db.save("t_qrcode_apply", record);
+
+            // 获取刚插入的记录ID
+            Long applyId = WxUtil.getInt("id", record).longValue();
+            dto.setId(applyId.intValue());
+
+            // 4. 异步生成二维码包
+            CompletableFuture.runAsync(() -> {
+                try {
+                    generateQrcodePackage(dto, applyUser);
+                } catch (Exception e) {
+                    logger.error("生成二维码包失败,申请单号:" + dto.getApplyNo(), e);
+                    // 更新状态为"待处理",以便后续重试
+                    Db.update("UPDATE t_qrcode_apply SET status = 0 WHERE id = ?", applyId);
+                }
+            }, executorService);
+
+            // 5. 返回申请成功的响应
+            return ApiResponse.success("二维码申请已提交,正在生成中,请稍后查看");
+
+        } catch (Exception e) {
+            logger.error("二维码申请失败", e);
+            return ApiResponse.error("二维码申请失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 异步生成二维码包
+     */
+    private void generateQrcodePackage(QrcodeApplyDto dto, AdminView applyUser) {
+        try {
+            //生成文件名称:DateUtils.getStringFullDate()+"-" + quantity
+            String fileName = DateUtils.getStringFullDate() + "-" + dto.getApplyQuantity()+".zip";
+            logger.info("开始生成二维码包,申请单号:" + dto.getApplyNo());
+            String zipFilePath = qrcodeAddService.getZipFilePath("11", "a", dto.getApplyQuantity(), fileName,
+                    0, UUID.randomUUID().toString(),
+                    dto.getProductId().intValue(), "-1", applyUser, true);
+            if (StrKit.notBlank(zipFilePath) && zipFilePath.startsWith("https")) {
+                // 更新申请记录状态为"已生成",并设置文件名和下载地址
+                Db.update("UPDATE t_qrcode_apply SET status = 1, file_name = ?, download_url = ? WHERE id = ?",
+                        fileName, zipFilePath, dto.getId());
+
+                logger.info("二维码包生成成功,申请单号:{},文件路径:{}", dto.getApplyNo(), zipFilePath);
+            } else {
+                logger.error("二维码包生成失败,申请单号:{}",dto.getApplyNo());
+                // 更新状态为"待处理",以便后续重试
+                Db.update("UPDATE t_qrcode_apply SET status = 0 WHERE id = ?", dto.getId());
+            }
+        } catch (Exception e) {
+            logger.error("生成二维码包异常,申请单号:" + dto.getApplyNo(), e);
+            // 更新状态为"待处理",以便后续重试
+            Db.update("UPDATE t_qrcode_apply SET status = 0 WHERE id = ?", dto.getId());
+        }
+    }
+
+
+    /**
+     * 生成唯一订单号
+     * @return 符合格式的订单号
+     */
+    public String generateApplyNo() {
+        String dateStr = DATE_FORMAT.format(new Date());
+        String prefix = "QRC" + dateStr;
+
+        // 查询当天最大订单号
+        String maxNo = Db.queryStr("SELECT MAX(apply_no) FROM t_qrcode_apply WHERE apply_no LIKE ?", prefix + "%");
+
+        int sequence = 1;
+        if (maxNo != null) {
+            String sequenceStr = maxNo.substring(prefix.length());
+            sequence = Integer.parseInt(sequenceStr) + 1;
+        }
+
+        // 格式化为4位流水号
+        String sequenceStr = String.format("%0" + 4 + "d", sequence);
+        return prefix + sequenceStr;
+    }
+
+    /**
+     * 计算实际申请数量(增加10%余量)
+     * @param quantity 填写的申请数量
+     * @return 实际申请数量(整数)
+     */
+    private static Integer calculateActualQuantity(Integer quantity) {
+        if (quantity == null || quantity <= 0) {
+            return 1;
+        }
+
+        // 计算增加10%后的数量,并向上取整
+        double actualQuantity = quantity * 1.1;
+        return (int) Math.ceil(actualQuantity);
+    }
+}

+ 392 - 0
src/main/java/com/qlm/service/impl/UserServiceImpl.java

@@ -0,0 +1,392 @@
+package com.qlm.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import com.jfinal.kit.HashKit;
+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.common.ApiResponse;
+import com.qlm.common.PageResult;
+import com.qlm.controller.system.UserNewController;
+import com.qlm.dto.*;
+import com.qlm.oss.OssUtil;
+import com.qlm.service.IUserService;
+import com.qlm.tools.EasyExcelUtil;
+import com.qlm.tools.WxUtil;
+import com.qlm.view.core.AdminView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ * 用户管理Service实现类
+ */
+public class UserServiceImpl implements IUserService {
+    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
+
+    @Override
+    public ApiResponse saveUser(UserDto userDto, AdminView loginUser) {
+        try {
+            // 检查用户名是否已存在
+            Record existingUser = Db.findFirst("select * from t_admin where username = ?", userDto.getUsername());
+            if (existingUser != null) {
+                return ApiResponse.error("用户名已存在");
+            }
+
+            // 创建用户记录
+            Record user = new Record();
+            user.set("username", userDto.getUsername());
+            user.set("nickname", userDto.getNickName());
+            user.set("email", userDto.getEmail());
+            user.set("real_name", userDto.getRealName());
+            user.set("password", HashKit.md5(userDto.getPassword()));
+            user.set("real_name", userDto.getRealName());
+            user.set("tel_", userDto.getPhone());
+            user.set("role", userDto.getRole());
+            user.set("status", userDto.getStatus() != null ? userDto.getStatus() : 1);
+            user.set("create_time", new Date());
+
+            // 保存用户信息
+            boolean saved = Db.save("t_admin", user);
+            if (!saved) {
+                return ApiResponse.error("保存失败");
+            }
+
+            // 如果有部门信息,保存部门关联
+            Integer userId = WxUtil.getInt("id",user);
+            if (userId != null && userDto.getDepartmentId() != null) {
+                Record userDept = new Record();
+                userDept.set("user_id", userId);
+                userDept.set("dept_id", userDto.getDepartmentId());
+                Db.save("t_user_department", userDept);
+            }
+        } catch (Exception e) {
+            logger.error("系统异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+        return ApiResponse.success("保存成功");
+    }
+
+    @Override
+    public ApiResponse updateUser(UserDto userDto) {
+        try {
+            // 检查用户名是否已存在
+            Record existingUser = Db.findFirst("select * from t_admin where username = ? and id != ?", userDto.getUsername(),userDto.getId());
+            if (existingUser != null) {
+                return ApiResponse.error("用户名已存在");
+            }
+
+            Record editUser = new Record();
+            // 更新用户信息
+            editUser.set("id", userDto.getId());
+            editUser.set("username", userDto.getUsername());
+            editUser.set("nickname", userDto.getNickName());
+            editUser.set("email", userDto.getEmail());
+            editUser.set("real_name", userDto.getRealName());
+            editUser.set("password", HashKit.md5(userDto.getPassword()));
+            editUser.set("real_name", userDto.getRealName());
+            editUser.set("tel_", userDto.getPhone());
+            editUser.set("role", userDto.getRole());
+            editUser.set("status", userDto.getStatus() != null ? userDto.getStatus() : 1);
+            boolean update = Db.update("t_admin", "id", editUser);
+            if (!update) {
+                return ApiResponse.error("更新失败");
+            }
+            // 更新部门关联
+            Integer userId = userDto.getId();
+            Integer departmentId = userDto.getDepartmentId();
+            // 删除旧的部门关联
+            Db.update("delete from t_user_department where user_id = ?", userId);
+            // 添加新的部门关联
+            if (departmentId != null) {
+                Record userDept = new Record();
+                userDept.set("user_id", userId);
+                userDept.set("dept_id", departmentId);
+                Db.save("t_user_department", userDept);
+            }
+        } catch (Exception e) {
+            logger.error("系统异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+        return ApiResponse.success("更新成功");
+    }
+
+    @Override
+    public ApiResponse deleteUser(Integer id) {
+        try {
+            // 执行逻辑删除
+            int count = Db.update("update t_admin set deleted = -1 where id = ?", id);
+            if (count == 0) {
+                return ApiResponse.error("删除失败,用户不存在");
+            }
+            // 删除部门关联
+            Db.update("delete from t_user_department where user_id = ?", id);
+            return ApiResponse.success("删除成功");
+        } catch (Exception e) {
+            logger.error("删除用户信息异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public ApiResponse getUserById(Integer id) {
+        try {
+            UserDto userDto = getUserDtoById(id);
+            if (userDto == null) {
+                return ApiResponse.error("用户不存在");
+            }
+            return ApiResponse.success(userDto);
+        } catch (Exception e) {
+            logger.error("获取用户信息异常:", e);
+            return ApiResponse.error("系统异常:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public PageResult<UserDto> listUser(int pageNumber, int pageSize, String username, String realName, Integer departmentId, Integer status) {
+        try {
+            StringBuilder sqlBuilder = new StringBuilder();
+            sqlBuilder.append(" from t_admin a ");
+            sqlBuilder.append(" left join t_role r on a.role = r.id ");
+            sqlBuilder.append(" left join t_user_department ud on a.id = ud.user_id ");
+            sqlBuilder.append(" left join t_department d on ud.dept_id = d.id ");
+            sqlBuilder.append(" where a.deleted != -1 ");
+
+            List<Object> params = new ArrayList<>();
+
+            // 添加查询条件
+            if (username != null && !username.trim().isEmpty()) {
+                sqlBuilder.append("and a.username like ? ");
+                params.add("%" + username + "%");
+            }
+
+            if (departmentId != null) {
+                sqlBuilder.append("and ud.department_id = ? ");
+                params.add(departmentId);
+            }
+
+            // 排序
+            sqlBuilder.append("order by a.create_time desc");
+
+            Page<Record> paginate = Db.paginate(pageNumber, pageSize, "select a.*, r.role_name", sqlBuilder.toString(), params.toArray());
+            if(paginate  == null){
+                return new PageResult<>(0, pageNumber, pageSize, new ArrayList<>());
+            }
+
+            List<Record> list = paginate.getList();
+            // 转换为DTO列表
+            List<UserDto> userDtos = new ArrayList<>();
+            for (Record record : list) {
+                UserDto userDto = convertRecordToDto(record);
+                userDtos.add(userDto);
+            }
+            return new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, userDtos);
+        } catch (Exception e) {
+            logger.error("查询用户列表异常:", e);
+            // 返回空的分页结果
+            return new PageResult<>();
+        }
+    }
+
+    @Override
+    public ApiResponse importUser(List<UserImportDto> importList, String username) {
+        List<UserImportDto> errorList = new ArrayList<>();
+        int successCount = 0;
+        ApiResponse response = ApiResponse.success();
+        Record record = new Record();
+        record.set("successCount", successCount);
+        record.set("errorCount", 0);
+        for (UserImportDto importDto : importList) {
+            try {
+                // 验证必填字段
+                if (StrKit.isBlank(importDto.getUsername())) {
+                    importDto.setErrorMsg("用户名不能为空");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                if (StrKit.isBlank(importDto.getPassword())) {
+                    importDto.setErrorMsg("密码不能为空");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                if (StrKit.isBlank(importDto.getName())) {
+                    importDto.setErrorMsg("名称不能为空");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                if (StrKit.isBlank(importDto.getMobile())) {
+                    importDto.setErrorMsg("手机号不能为空");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                int status;
+                if(StrKit.notBlank(importDto.getStatus())){
+                    // 状态转换: 有效->1, 锁定->0
+                    if ("有效".equals(importDto.getStatus())) {
+                        status = 1;
+                    } else {
+                        status = 0;
+                        if ("锁定".equals(importDto.getStatus())) {
+                        } else {
+                            importDto.setErrorMsg("状态值无效,请使用'有效'或'锁定'");
+                            errorList.add(importDto);
+                            continue;
+                        }
+                    }
+                }else {
+                    importDto.setErrorMsg("状态不能为空");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                Record existRecord = Db.findFirst("select 1 from t_admin where username = ?", importDto.getUsername());
+                if (existRecord != null) {
+                    importDto.setErrorMsg("用户名已存在");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                //找到部门ID
+                Integer deptId = WxUtil.getInt("id", Db.findFirst("select id from t_department where dept_name = ?", importDto.getDeptName()));
+                if (deptId == null || deptId == 0) {
+                    importDto.setErrorMsg("部门不存在");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                //找到角色ID
+                Integer roleId = WxUtil.getInt("id", Db.findFirst("select id from t_role where role_name = ?", importDto.getRoleName()));
+                if (roleId == null || roleId == 0) {
+                    importDto.setErrorMsg("角色不存在");
+                    errorList.add(importDto);
+                    continue;
+                }
+
+                boolean result= Db.tx(() -> {
+                    // 创建用户记录
+                    Record user = new Record();
+                    user.set("username", importDto.getUsername());
+                    user.set("real_name", importDto.getName());
+                    user.set("password", HashKit.md5(importDto.getPassword()));
+                    user.set("tel_", importDto.getMobile());
+                    user.set("role", roleId);
+                    user.set("status", status);
+                    user.set("create_time", new Date());
+
+                    // 保存用户信息
+                    Db.save("t_admin", user);
+                    // 如果有部门信息,保存部门关联
+                    Integer userId = WxUtil.getInt("id",user);
+                    Record userDept = new Record();
+                    userDept.set("user_id", userId);
+                    userDept.set("dept_id", deptId);
+                    Db.save("t_user_department", userDept);
+                    return true;
+                });
+                if (result) {
+                    successCount++;
+                } else {
+                    importDto.setErrorMsg("保存失败");
+                    errorList.add(importDto);
+                }
+            } catch (Exception e) {
+                logger.error("导入用户信息异常:", e);
+                importDto.setErrorMsg("系统异常:" + e.getMessage());
+                errorList.add(importDto);
+            }
+        }
+        record.set("successCount", successCount);
+        if (!errorList.isEmpty()) {
+            record.set("errorCount", errorList.size());
+            try {
+                String errorFileName = "item_import_error_" + System.currentTimeMillis() + ".xlsx";
+                InputStream errorInputStream = EasyExcelUtil.export(errorList, "导入错误数据", UserImportDto.class);
+                // 上传到OSS
+                String ossUrl = OssUtil.upload(errorInputStream, errorFileName);
+                // 添加到响应
+                record.set("errorFileUrl", ossUrl);
+            } catch (Exception e) {
+                logger.error("导出错误数据并上传到OSS异常:", e);
+                record.set("errorMsg", "导出错误数据并上传到OSS异常:" + e.getMessage());
+            }
+        }
+        response.setData(record);
+        return response;
+    }
+
+    @Override
+    public List<UserExportDto> exportUserList(String userName) {
+        // 构建查询条件
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append(" select a.*, r.role_name,d.dept_name from t_admin a ");
+        sqlBuilder.append(" left join t_role r on a.role = r.id ");
+        sqlBuilder.append(" left join t_user_department ud on a.id = ud.user_id ");
+        sqlBuilder.append(" left join t_department d on ud.dept_id = d.id ");
+        sqlBuilder.append(" where a.deleted != -1 ");
+        List<Object> params = new ArrayList<>();
+        if (StrKit.notBlank(userName)) {
+            sqlBuilder.append(" and username like ? ");
+            params.add("%" + userName + "%");
+        }
+        List<Record> records = Db.find(sqlBuilder.toString(), params.toArray());
+        // 转换为Dto对象列表
+        List<UserExportDto> userExportDtos = new ArrayList<>();
+        if(CollUtil.isEmpty(records)){
+            return null;
+        }
+
+        // 转换经销商信息并设置区域
+        for (Record record : records) {
+            UserExportDto userExportDto = new     UserExportDto();
+            userExportDto.setUsername(record.getStr("username"));
+            userExportDto.setRealName(record.getStr("real_name"));
+            userExportDto.setMobile(record.getStr("tel_"));
+            userExportDto.setDepartmentName(record.getStr("dept_name"));
+            userExportDto.setRoleName(record.getStr("role_name"));
+            userExportDto.setStatusName(record.getInt("status") == 1 ? "正常" : "锁定");
+            userExportDtos.add(userExportDto);
+        }
+        // 创建返回结果
+        return userExportDtos;
+    }
+
+    /**
+     * 将Record转换为UserDto的工具方法
+     */
+    private UserDto convertRecordToDto(Record record) {
+        UserDto userDto = new UserDto();
+        userDto.setId(record.getInt("id"));
+        userDto.setUsername(record.getStr("username"));
+        userDto.setRealName(record.getStr("real_name"));
+        userDto.setPhone(record.getStr("tel_"));
+        userDto.setRole(record.getInt("role"));
+        userDto.setRoleName(record.getStr("role_name"));
+        userDto.setDepartmentId(WxUtil.getInt("department_id", record));
+        userDto.setDepartmentName(record.getStr("department_name"));
+        userDto.setStatus(record.getInt("status"));
+        userDto.setCreateTime(DateUtil.format(record.getDate("create_time"), "yyyy-MM-dd HH:mm:ss"));
+        return userDto;
+    }
+
+    /**
+     * 根据ID获取用户DTO
+     */
+    private UserDto getUserDtoById(Integer id) {
+        String sql = "select a.*, r.role_name, d.id as department_id, d.dept_name " +
+                "from t_admin a " +
+                "left join t_role r on a.role = r.id " +
+                "left join t_user_department ud on a.id = ud.user_id " +
+                "left join t_department d on ud.dept_id = d.id " +
+                "where a.id = ? and a.status != -1";
+        Record record = Db.findFirst(sql, id);
+        return record != null ? convertRecordToDto(record) : null;
+    }
+}

+ 4 - 1
src/main/resources/config.properties

@@ -28,5 +28,8 @@ oss.domain=https://hyscancode.oss-cn-hangzhou.aliyuncs.com
 oss.mkdir=miniscancode/
 # 10MB
 oss.maxSize=10485760
-oss.allowedTypes=.jpg,.jpeg,.png,.gif,.xlsx,.xls
+oss.allowedTypes=.jpg,.jpeg,.png,.gif,.xlsx,.xls,.zip
+
+# Netty\u914D\u7F6E
+netty.port=8888
 

+ 1 - 1
src/main/resources/logback.xml

@@ -25,7 +25,7 @@
   <logger name="jdbc.sqlonly" level="debug" additivitt="true"/>  
     <logger name="jdbc.sqltiming" level="debug" additivitt="true"/>  
 	<!-- 输出日志到控制台 TRACE、DEBUG、INFO、WARN、ERROR -->
-	<root level="info">
+	<root level="DEBUG">
 		<appender-ref ref="console" />
 		<appender-ref ref="FILE" />
 	</root>

+ 414 - 0
src/main/webapp/page/jinzai/deptManagement.jsp

@@ -0,0 +1,414 @@
+<%@ 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="deptName" 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="deptModal" tabindex="-1" role="dialog" aria-labelledby="deptModalLabel">
+    <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="deptModalLabel">新增部门</h4>
+            </div>
+            <div class="modal-body">
+                <div class="form-container">
+                    <form id="deptForm">
+                        <input type="hidden" id="deptId" name="deptId">
+                        <div class="row mb-4">
+                            <div class="col-md-12">
+                                <label for="modalDeptName" class="required control-label">部门名称</label>
+                                <input type="text" class="form-control" id="modalDeptName" name="modalDeptName" placeholder="请输入" required>
+                            </div>
+                        </div>
+
+                        <div class="row mb-4">
+                            <div class="col-md-12">
+                                <label for="modalRemark" class="control-label">备注</label>
+                                <textarea class="form-control" id="modalRemark" name="modalRemark" rows="3" placeholder="请输入备注信息"></textarea>
+                            </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="saveDept();">确定</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;
+
+    $(document).ready(function () {
+        // 初始化表格
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+        initValidation();
+
+        // 绑定重置按钮点击事件
+        $('#resetBtn').on('click', function() {
+            reset();
+        });
+    });
+
+    // 初始化表格
+    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}/userNew/deptList',//这个接口需要处理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: 'name',
+                    title: '部门名称',
+                    align: 'center'
+                },
+                {
+                    field: 'remark',
+                    title: '备注',
+                    align: 'center',
+                    formatter: function(value) {
+                        return value || '-';
+                    }
+                },
+                {
+                    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="deleteDept(' + row.id + ')">删除</button>';
+                        return actions;
+                    }
+                }
+            ]
+        });
+    }
+
+    // 查询参数
+    function queryParams(params) {
+        return {
+            pageNumber: params.pageNumber,
+            pageSize: params.pageSize,
+            name: $('#deptName').val()
+        };
+    }
+
+    // 初始化表单验证
+    function initValidation() {
+        $('#deptForm').validate({
+            rules: {
+                modalDeptName: {
+                    required: true,
+                    maxlength: 50
+                }
+            },
+            messages: {
+                modalDeptName: {
+                    required: "请输入部门名称",
+                    maxlength: "部门名称不能超过50个字符"
+                }
+            }
+        });
+    }
+
+    // 搜索
+    function search() {
+        table.bootstrapTable('refresh');
+    }
+
+    // 重置
+    function reset() {
+        $('#deptName').val('');
+        table.bootstrapTable('refresh');
+    }
+
+    // 显示新增模态框
+    function showAddModal() {
+        $('#deptModalLabel').text('新增部门');
+        $('#deptForm')[0].reset();
+        $('#deptId').val('');
+        $('#deptModal').modal('show');
+    }
+
+    // 显示编辑模态框
+    function showEditModal(id) {
+        $.ajax({
+            url: '${ctx}/userNew/getDeptById?id=' + id,
+            type: 'GET',
+            dataType: 'json',
+            success: function (data) {
+                if (data.code === 0 && data.data) {
+                    const deptData = data.data;
+                    $('#deptModalLabel').text('编辑部门');
+                    $('#deptId').val(deptData.id);
+                    $('#modalDeptName').val(deptData.name);
+                    $('#modalRemark').val(deptData.remark || '');
+                    $('#deptModal').modal('show');
+                } else {
+                    layer.alert(data.msg || '获取部门信息失败', {icon: 2});
+                }
+            },
+            error: function () {
+                layer.alert('获取部门信息失败', {icon: 2});
+            }
+        });
+    }
+
+    // 保存部门
+    function saveDept() {
+        if (!$('#deptForm').valid()) {
+            return;
+        }
+
+        const deptId = $('#deptId').val();
+        const url = deptId ? '${ctx}/userNew/editDept' : '${ctx}/userNew/addDept';
+
+        const deptData = {
+            id: deptId ? parseInt(deptId) : null,
+            name: $('#modalDeptName').val(),
+            remark: $('#modalRemark').val()
+        };
+
+        $.ajax({
+            url: url,
+            type: 'POST',
+            data: JSON.stringify(deptData),
+            contentType: 'application/json',
+            dataType: 'json',
+            success: function (data) {
+                if (data.code === 0) {
+                    layer.msg('保存成功', {icon: 6});
+                    $('#deptModal').modal('hide');
+                    table.bootstrapTable('refresh');
+                } else {
+                    layer.msg('保存失败', {icon: 5});
+                }
+            },
+            error: function () {
+                layer.alert('保存失败', {icon: 2});
+            }
+        });
+    }
+
+    // 删除部门
+    function deleteDept(id) {
+        layer.confirm('确定要删除该部门吗?删除后可能影响相关用户数据!', {
+            btn: ['确定', '取消']
+        }, function() {
+            $.ajax({
+                url: '${ctx}/userNew/deleteDept?id=' + id,
+                type: 'GET',
+                dataType: 'json',
+                success: function (data) {
+                    if (data.code === 0) {
+                        layer.msg('删除成功', {icon: 6});
+                        table.bootstrapTable('refresh');
+                    } else {
+                        layer.msg('删除失败', {icon: 5});
+                    }
+                },
+                error: function () {
+                    layer.msg('删除失败', {icon: 5});
+                }
+            });
+        });
+    }
+</script>
+</html>

+ 15 - 29
src/main/webapp/page/jinzai/deviceMonitor.jsp

@@ -390,26 +390,7 @@
             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,  // 总记录数
@@ -548,6 +529,21 @@
                         return '<div class="status-badge ' + delayClass + '">' + numValue.toFixed(2) + '</div>';
                     }
                 },
+                {
+                    field: 'online',
+                    title: '是否在线',
+                    align: 'center',
+                    formatter: function (value) {
+                        // 根据是否在线显示不同颜色
+                        var statusClass = '';
+                        if (value === 1) {
+                            statusClass = 'status-green';
+                        } else {
+                            statusClass = 'status-red';
+                        }
+                        return '<div class="status-badge ' + statusClass + '">' + (value === 1 ? '在线' : '离线') + '</div>';
+                    }
+                },
                 {
                     field: 'productionNum',
                     title: '目前生产数量(件)',
@@ -614,10 +610,6 @@
     function refreshRow(deviceId) {
         // 显示加载中提示
         var loadingIndex = layer.load(1, {shade: [0.5, '#fff']});
-        
-        // 记录开始时间
-        var startTime = new Date().getTime();
-        
         // 发送请求刷新单行数据
         $.ajax({
             url: '${ctx}/prodBatch/refreshDeviceDetail',
@@ -627,12 +619,6 @@
             },
             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');
                     

+ 833 - 0
src/main/webapp/page/jinzai/itemChangeRecord.jsp

@@ -0,0 +1,833 @@
+<%@ 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;
+        }
+
+        .form-group {
+            margin-bottom: 1.5rem;
+        }
+
+        .row.mb-4 {
+            margin-bottom: 1.5rem;
+        }
+
+        .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">
+                                <div style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="factoryId" class="form-control selectpicker" data-live-search="true" title="请选择工厂">
+                                        <option value="">请选择工厂</option>
+                                    </select>
+                                </div>
+                                <div style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="workshopId" class="form-control selectpicker" data-live-search="true" title="请选择车间">
+                                        <option value="">请选择车间</option>
+                                    </select>
+                                </div>
+                                <div style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="productionLineId" class="form-control selectpicker" data-live-search="true" title="请选择产线">
+                                        <option value="">请选择产线</option>
+                                    </select>
+                                </div>
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <input type="date" id="startDate" class="form-control" placeholder="修改开始时间" />
+                                </div>
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <input type="date" id="endDate" class="form-control" placeholder="修改结束时间" />
+                                </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>
+                                <button type="button" class="btn btn-primary" onclick="showAddModal();" 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>
+
+<!-- 新增模态框 -->
+<div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="addModalLabel">
+    <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="addModalLabel">新增品相更改记录</h4>
+            </div>
+            <div class="modal-body">
+                <div class="form-container">
+                    <form id="addForm">
+                        <input type="hidden" id="recordId" name="recordId">
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalChangeType" class="required control-label">修改品项方式</label>
+                                <select id="modalChangeType" class="form-control" required>
+                                    <option value="">请选择修改品项方式</option>
+                                    <option value="1">按单号</option>
+                                    <option value="2">按箱码</option>
+                                    <option value="3">按托码</option>
+                                </select>
+                            </div>
+                            <div class="col-md-6">
+                                <label for="modalChangeValueInput" class="required control-label">修改值</label>
+                                <input type="text" class="form-control" id="modalChangeValueInput" name="modalChangeValueInput" placeholder="请输入单号" required>
+                                <textarea class="form-control" id="modalChangeValueTextarea" name="modalChangeValueTextarea" placeholder="请输入箱码或托码,多行以逗号分隔" rows="3" style="display: none;"></textarea>
+                            </div>
+                        </div>
+
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalFactoryId" class="required control-label">工厂</label>
+                                <select id="modalFactoryId" class="form-control selectpicker" required>
+                                    <option value="">请选择工厂</option>
+                                    <!-- 工厂下拉框选项将通过JavaScript动态加载 -->
+                                </select>
+                            </div>
+                            <div class="col-md-6">
+                                <label for="modalWorkshopId" class="required control-label">车间</label>
+                                <select id="modalWorkshopId" class="form-control selectpicker" required>
+                                    <option value="">请选择车间</option>
+                                    <!-- 车间下拉框选项将通过JavaScript动态加载 -->
+                                </select>
+                            </div>
+                        </div>
+
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalProductionLineId" class="required control-label">产线</label>
+                                <select id="modalProductionLineId" class="form-control selectpicker" required>
+                                    <option value="">请选择产线</option>
+                                    <!-- 产线下拉框选项将通过JavaScript动态加载 -->
+                                </select>
+                            </div>
+                            <div class="col-md-6">
+                                <label for="modalAfterItemId" class="required control-label">修改后品相</label>
+                                <select id="modalAfterItemId" class="form-control selectpicker" required>
+                                    <option value="">请选择修改后品相</option>
+                                    <!-- 修改后品相下拉框选项将通过JavaScript动态加载 -->
+                                </select>
+                            </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="saveChangeRecord();">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 全局js -->
+<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+<!-- 全局js -->
+<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 factories = [];
+    var workshops = [];
+    var productionLines = [];
+    var items = [];
+
+    $(document).ready(function () {
+        $('.selectpicker').selectpicker({
+            liveSearch: true,
+            size: 5,
+            actionsBox: true,
+            selectedTextFormat: 'count > 2'
+        });
+        // 加载下拉框数据
+        loadFactories();
+        loadWorkshopsByFactoryId();
+        loadProductionLinesByWorkshopId();
+        loadItems();
+
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+        initValidation();
+
+        // 绑定重置按钮点击事件
+        $('#resetBtn').on('click', function() {
+            reset();
+        });
+
+        // 监听模态框中工厂选择变化
+        $(document).on('change', '#modalFactoryId', function() {
+            const factoryId = $(this).val();
+            if (factoryId) {
+                loadModalWorkshopsByFactoryId(factoryId);
+            } else {
+                $('#modalWorkshopId').empty().append('<option value="">请选择车间</option>');
+                $('#modalWorkshopId').selectpicker('refresh');
+                $('#modalProductionLineId').empty().append('<option value="">请选择产线</option>');
+                $('#modalProductionLineId').selectpicker('refresh');
+            }
+        });
+
+        // 监听模态框中车间选择变化
+        $(document).on('change', '#modalWorkshopId', function() {
+            const workshopId = $(this).val();
+            if (workshopId) {
+                loadModalProductionLinesByWorkshopId(workshopId);
+            } else {
+                $('#modalProductionLineId').empty().append('<option value="">请选择产线</option>');
+                $('#modalProductionLineId').selectpicker('refresh');
+            }
+        });
+
+        // 监听修改品项方式变化
+        $(document).on('change', '#modalChangeType', function() {
+            toggleChangeValueInput();
+        });
+    });
+
+    // 加载工厂数据
+    function loadFactories() {
+        $.ajax({
+            url: '${ctx}/lineProduct/getFactoryList',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#factoryId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#factoryId').empty();
+                factories = [];
+                if (res.data && res.data.length) {
+                    factories = res.data;
+                    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');
+            }
+        });
+    }
+
+    // 根据工厂ID加载车间数据
+    function loadWorkshopsByFactoryId(factoryId) {
+        $.ajax({
+            url: '${ctx}/lineProduct/getWorkshopList',
+            type: 'POST',
+            data: {
+                factoryId: factoryId
+            },
+            dataType: 'json',
+            beforeSend: function () {
+                $('#workshopId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#workshopId').empty();
+                workshops = [];
+                if (res.data && res.data.length) {
+                    workshops = res.data;
+                    res.data.forEach(item => {
+                        $('#workshopId').append('<option value="' + item.id + '">' + item.workshop_name + '</option>');
+                    });
+                }
+                $('#workshopId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#workshopId').empty().append('<option value="">加载失败</option>');
+                $('#workshopId').selectpicker('refresh');
+                layer.msg('获取车间数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#workshopId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    // 根据车间ID加载产线数据
+    function loadProductionLinesByWorkshopId(workshopId) {
+        $.ajax({
+            url: '${ctx}/lineProduct/getLineList',
+            type: 'POST',
+            data: {
+                workshopId: workshopId
+            },
+            dataType: 'json',
+            beforeSend: function () {
+                $('#productionLineId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#productionLineId').empty();
+                productionLines = [];
+                if (res.data && res.data.length) {
+                    productionLines = res.data;
+                    res.data.forEach(item => {
+                        $('#productionLineId').append('<option value="' + item.id + '">' + item.lineName + '</option>');
+                    });
+                }
+                $('#productionLineId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#productionLineId').empty().append('<option value="">加载失败</option>');
+                $('#productionLineId').selectpicker('refresh');
+                layer.msg('获取产线数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#productionLineId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    // 加载品相数据
+    function loadItems() {
+        $.ajax({
+            url: '${ctx}/qrcodeApply/getItemAll',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                // 可以添加加载状态
+            },
+            success: function (res) {
+                items = [];
+                if (res.data && res.data.length) {
+                    items = res.data;
+                }
+            },
+            error: function (xhr) {
+                layer.msg('获取品相数据失败: ' + xhr.statusText);
+            }
+        });
+    }
+
+
+    // 监听工厂选择变化
+    $(document).on('change', '#factoryId', function() {
+        const factoryId = $(this).val();
+        if (factoryId) {
+            loadWorkshopsByFactoryId(factoryId);
+        } else {
+            $('#workshopId').empty().append('<option value="">请选择车间</option>');
+            $('#workshopId').selectpicker('refresh');
+            $('#lineId').empty().append('<option value="">请选择产线</option>');
+            $('#lineId').selectpicker('refresh');
+        }
+    });
+
+    // 监听车间选择变化
+    $(document).on('change', '#workshopId', function() {
+        const workshopId = $(this).val();
+        if (workshopId) {
+            loadLinesByWorkshopId(workshopId);
+        } else {
+            $('#lineId').empty().append('<option value="">请选择产线</option>');
+            $('#lineId').selectpicker('refresh');
+        }
+    });
+
+    // 切换修改值输入框类型
+    function toggleChangeValueInput() {
+        const changeType = $('#modalChangeType').val();
+        const inputElement = $('#modalChangeValueInput');
+        const textareaElement = $('#modalChangeValueTextarea');
+        
+        if (changeType === '1') {
+            inputElement.show();
+            textareaElement.hide();
+            inputElement.prop('required', true);
+            textareaElement.prop('required', false);
+        } else {
+            inputElement.hide();
+            textareaElement.show();
+            inputElement.prop('required', false);
+            textareaElement.prop('required', true);
+        }
+        // 重置验证状态
+        if ($('#addForm').data('validator')) {
+            $('#addForm').data('validator').resetForm();
+        }
+    }
+
+    function queryParams(param) {
+        let factoryId = $.trim($("#factoryId").val());
+        let workshopId = $.trim($("#workshopId").val());
+        let productionLineId = $.trim($("#productionLineId").val());
+        let startDate = $.trim($("#startDate").val());
+        let endDate = $.trim($("#endDate").val());
+
+        if (factoryId) {
+            param['factoryId'] = factoryId;
+        }
+        if (workshopId) {
+            param['workshopId'] = workshopId;
+        }
+        if (productionLineId) {
+            param['productionLineId'] = productionLineId;
+        }
+        if (startDate) {
+            param['startDate'] = startDate;
+        }
+        if (endDate) {
+            param['endDate'] = endDate;
+        }
+
+        return param;
+    }
+
+    function search() {
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+    }
+
+    function reset() {
+        $("#factoryId").val('');
+        $("#factoryId").selectpicker('val', '');
+        $("#factoryId").selectpicker('deselectAll');
+        $("#factoryId").selectpicker('refresh');
+        
+        $("#workshopId").val('');
+        $("#workshopId").selectpicker('val', '');
+        $("#workshopId").selectpicker('deselectAll');
+        $("#workshopId").selectpicker('refresh');
+        
+        $("#productionLineId").val('');
+        $("#productionLineId").selectpicker('val', '');
+        $("#productionLineId").selectpicker('deselectAll');
+        $("#productionLineId").selectpicker('refresh');
+        
+        $("#startDate").val('');
+        $("#endDate").val('');
+        
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+    }
+
+    function showAddModal() {
+        loadModalFactories();
+        loadModalItems();
+        // 清空表单
+        $('#addForm')[0].reset();
+        $('#recordId').val('');
+        $('#addModalLabel').text('新增品相更改记录');
+
+        // 显式重置所有联动下拉框并刷新selectpicker
+        $('#modalFactoryId').val('').selectpicker('refresh');
+        $('#modalWorkshopId').val('').selectpicker('refresh');
+        $('#modalProductionLineId').val('').selectpicker('refresh');
+        $('#modalAfterItemId').val('').selectpicker('refresh');
+
+        // 重置修改值输入框
+        toggleChangeValueInput();
+
+        // 重置表单验证状态
+        if ($('#addForm').data('validator')) {
+            $('#addForm').data('validator').resetForm();
+        }
+
+        // 清除所有错误类
+        $('#addForm .error').removeClass('error');
+        $('#addForm label.error').remove();
+
+        // 显示模态框
+        $('#addModal').modal('show');
+    }
+
+    function saveChangeRecord() {
+        if (!$('#addForm').valid()) {
+            return;
+        }
+
+        const changeType = $('#modalChangeType').val();
+        let changeValue = '';
+        let referenceCodesArray = [];
+        if (changeType === '1') {
+            // 按单号方式,直接作为一个元素的数组
+            changeValue = $('#modalChangeValueInput').val();
+            referenceCodesArray = [changeValue];
+        } else {
+            // 按箱码或托码方式,按逗号分割成数组
+            changeValue = $('#modalChangeValueTextarea').val();
+            if (changeValue) {
+                // 按逗号分割,并去除每个元素的前后空格
+                referenceCodesArray = changeValue.split(',').map(code => code.trim()).filter(code => code.length > 0);
+            } else {
+                referenceCodesArray = [];
+            }
+        }
+
+        const formData = {
+            recordId: $('#recordId').val(),
+            modifyType: changeType,
+            referenceCodes: referenceCodesArray,
+            factoryId: $('#modalFactoryId').val(),
+            workshopId: $('#modalWorkshopId').val(),
+            lineId: $('#modalProductionLineId').val(),
+            afterItemId: $('#modalAfterItemId').val()
+        };
+
+        $.ajax({
+            url: '${ctx}/prodBatch/changeItem',
+            type: 'POST',
+            data: JSON.stringify(formData),
+            contentType: 'application/json',
+            success: function (data) {
+                if (data.code === 0) {
+                    layer.msg('保存成功', {icon: 6});
+                    // 关闭模态框
+                    $('#addModal').modal('hide');
+                    // 刷新表格
+                    search();
+                } else {
+                    layer.msg(data.msg, {icon: 5});
+                }
+            },
+            error: function () {
+                layer.msg('保存失败', {icon: 5});
+            }
+        });
+    }
+
+    // 加载模态框中的工厂数据
+    function loadModalFactories() {
+        const modalFactorySelect = $('#modalFactoryId');
+        modalFactorySelect.empty().append('<option value="">请选择工厂</option>');
+
+        if (factories.length > 0) {
+            factories.forEach(factory => {
+                modalFactorySelect.append('<option value="' + factory.id + '">' + factory.factory_name + '</option>');
+            });
+        }
+        // 确保selectpicker刷新
+        modalFactorySelect.selectpicker('refresh');
+    }
+
+    // 模态框中根据工厂ID加载车间数据
+    function loadModalWorkshopsByFactoryId(factoryId) {
+        $.ajax({
+            url: '${ctx}/lineProduct/getWorkshopList',
+            type: 'POST',
+            data: {
+                factoryId: factoryId
+            },
+            dataType: 'json',
+            beforeSend: function () {
+                $('#modalWorkshopId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#modalWorkshopId').empty();
+                $('#modalWorkshopId').append('<option value="">请选择车间</option>');
+                if (res.data && res.data.length) {
+                    res.data.forEach(item => {
+                        $('#modalWorkshopId').append('<option value="' + item.id + '">' + item.workshop_name + '</option>');
+                    });
+                }
+                $('#modalWorkshopId').selectpicker('refresh');
+                // 清空并重置产线选择框
+                $('#modalProductionLineId').empty().append('<option value="">请选择产线</option>');
+                $('#modalProductionLineId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#modalWorkshopId').empty().append('<option value="">加载失败</option>');
+                $('#modalWorkshopId').selectpicker('refresh');
+                layer.msg('获取车间数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#modalWorkshopId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    // 模态框中根据车间ID加载产线数据
+    function loadModalProductionLinesByWorkshopId(workshopId) {
+        $.ajax({
+            url: '${ctx}/lineProduct/getLineList',
+            type: 'POST',
+            data: {
+                workshopId: workshopId
+            },
+            dataType: 'json',
+            beforeSend: function () {
+                $('#modalProductionLineId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#modalProductionLineId').empty();
+                $('#modalProductionLineId').append('<option value="">请选择产线</option>');
+                if (res.data && res.data.length) {
+                    res.data.forEach(item => {
+                        $('#modalProductionLineId').append('<option value="' + item.id + '">' + item.lineName + '</option>');
+                    });
+                }
+                $('#modalProductionLineId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#modalProductionLineId').empty().append('<option value="">加载失败</option>');
+                $('#modalProductionLineId').selectpicker('refresh');
+                layer.msg('获取产线数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#modalProductionLineId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    // 加载模态框中的品相数据
+    function loadModalItems() {
+        const modalAfterItemSelect = $('#modalAfterItemId');
+        
+        modalAfterItemSelect.empty().append('<option value="">请选择修改后品相</option>');
+
+        if (items.length > 0) {
+            items.forEach(item => {
+                modalAfterItemSelect.append('<option value="' + item.id + '">' + item.item_name + '</option>');
+            });
+        }
+        // 确保selectpicker刷新
+        modalAfterItemSelect.selectpicker('refresh');
+    }
+
+    function initValidation() {
+        $('#addForm').validate({
+            rules: {
+                modalChangeType: {
+                    required: true
+                },
+                modalChangeValueInput: {
+                    required: function() {
+                        return $('#modalChangeType').val() === '1';
+                    }
+                },
+                modalChangeValueTextarea: {
+                    required: function() {
+                        return $('#modalChangeType').val() !== '1' && $('#modalChangeType').val() !== '';
+                    }
+                },
+                modalFactoryId: {
+                    required: true
+                },
+                modalWorkshopId: {
+                    required: true
+                },
+                modalProductionLineId: {
+                    required: true
+                },
+                modalAfterItemId: {
+                    required: true
+                }
+            },
+            messages: {
+                modalChangeType: {
+                    required: '请选择修改品项方式'
+                },
+                modalChangeValueInput: {
+                    required: '请输入单号'
+                },
+                modalChangeValueTextarea: {
+                    required: '请输入箱码或托码'
+                },
+                modalFactoryId: {
+                    required: '请选择工厂'
+                },
+                modalWorkshopId: {
+                    required: '请选择车间'
+                },
+                modalProductionLineId: {
+                    required: '请选择产线'
+                },
+                modalAfterItemId: {
+                    required: '请选择修改后品相'
+                }
+            },
+            ignore: [] // 必须设置,否则不会验证隐藏的元素
+        });
+    }
+
+    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}/prodBatch/getItemChangeRecordList',//这个接口需要处理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: [{
+                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;
+                },
+                width: "5%"
+            }, {
+                field: 'modifyType',
+                title: '修改品项方式',
+                align: 'center',
+                width: "10%",
+                formatter: function (value) {
+                    if (value === 1) {
+                        return '按单号';
+                    } else if (value === 2) {
+                        return '按箱码';
+                    } else if (value === 3) {
+                        return '按托码';
+                    } else {
+                        return '未知';
+                    }
+                }
+            }, {
+                field: 'referenceCode',
+                title: '对应的单号/箱码/托码',
+                align: 'center',
+                width: "15%"
+            }, {
+                field: 'factoryName',
+                title: '工厂名称',
+                align: 'center',
+                width: "10%"
+            }, {
+                field: 'workshopName',
+                title: '车间名称',
+                align: 'center',
+                width: "10%"
+            }, {
+                field: 'beforeItemName',
+                title: '修改前品项名称',
+                align: 'center',
+                width: "15%"
+            }, {
+                field: 'afterItemName',
+                title: '修改后品项名称',
+                align: 'center',
+                width: "15%"
+            }, {
+                field: 'modifyTime',
+                title: '修改时间',
+                align: 'center',
+                width: "15%"
+            }, {
+                field: 'modifyUser',
+                title: '修改人',
+                align: 'center',
+                width: "10%"
+            }],
+            onLoadSuccess: function (data) {
+                console.log("数据加载成功", data);
+            },
+            onLoadError: function () {
+                layer.msg('数据加载失败', {icon: 2});
+            }
+        });
+    }
+</script>
+</html>

+ 2 - 2
src/main/webapp/page/jinzai/productTypeList.jsp

@@ -472,11 +472,11 @@
             dataType: 'json',
             success: function (data) {
                 if (data.code === 0) {
-                    layer.alert(data.msg || '保存成功', {icon: 1});
+                    layer.msg('保存成功', {icon: 6});
                     $('#productTypeModal').modal('hide');
                     table.bootstrapTable('refresh');
                 } else {
-                    layer.alert(data.msg || '保存失败', {icon: 2});
+                    layer.msg('保存失败', {icon: 5});
                 }
             },
             error: function () {

+ 570 - 0
src/main/webapp/page/jinzai/productionStatisticsReport.jsp

@@ -0,0 +1,570 @@
+<%@ 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;
+        }
+
+        .form-group {
+            margin-bottom: 1.5rem;
+        }
+
+        .row.mb-4 {
+            margin-bottom: 1.5rem;
+        }
+
+        .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">
+                                <div style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="factoryId" class="form-control selectpicker" data-live-search="true" title="请选择工厂">
+                                        <option value="">请选择工厂</option>
+                                    </select>
+                                </div>
+                                <div style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="workshopId" class="form-control selectpicker" data-live-search="true" title="请选择车间">
+                                        <option value="">请选择车间</option>
+                                    </select>
+                                </div>
+                                <div style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="itemId" class="form-control selectpicker" data-live-search="true" title="请选择品项">
+                                        <option value="">请选择品项</option>
+                                    </select>
+                                </div>
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <input type="date" id="startDate" class="form-control" placeholder="生产开始时间" />
+                                </div>
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <input type="date" id="endDate" class="form-control" placeholder="生产结束时间" />
+                                </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>
+
+<!-- 查看码值模态框 -->
+<div class="modal fade" id="codeModal" tabindex="-1" role="dialog" aria-labelledby="codeModalLabel">
+    <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="codeModalLabel">码值信息</h4>
+            </div>
+            <div class="modal-body">
+                <div class="form-container">
+                    <div class="row mb-4">
+                        <div class="col-md-12">
+                            <div class="form-inline-flex">
+                                <div style="width: 300px; flex-shrink: 0; margin-right: 10px;">
+                                    <input type="text" id="codeSearchInput" class="form-control" placeholder="请输入码值关键词搜索" />
+                                </div>
+                                <button type="button" id="codeSearchBtn" class="btn btn-success" onclick="searchCode();">搜索
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="row">
+                        <div class="col-md-12">
+                            <table id="codeTable" data-toggle="table" data-mobile-responsive="true"></table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 全局js -->
+<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+<!-- 全局js -->
+<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 codeTable = null;
+    var factories = [];
+    var workshops = [];
+    var items = [];
+    var currentBatchId = '';
+    var currentSku = '';
+    var currentCodeType = ''; // 'box' 或 'pallet'
+
+    $(document).ready(function () {
+        $('.selectpicker').selectpicker({
+            liveSearch: true,
+            size: 5,
+            actionsBox: true,
+            selectedTextFormat: 'count > 2'
+        });
+        // 加载下拉框数据
+        loadFactories();
+        loadWorkshopsByFactoryId();
+        loadItems();
+
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+
+        // 绑定重置按钮点击事件
+        $('#resetBtn').on('click', function() {
+            reset();
+        });
+
+        // 监听工厂选择变化
+        $(document).on('change', '#factoryId', function() {
+            const factoryId = $(this).val();
+            if (factoryId) {
+                loadWorkshopsByFactoryId(factoryId);
+            } else {
+                $('#workshopId').empty().append('<option value="">请选择车间</option>');
+                $('#workshopId').selectpicker('refresh');
+            }
+        });
+
+        // 监听码值模态框关闭事件
+        $('#codeModal').on('hidden.bs.modal', function () {
+            // 销毁表格实例
+            if (codeTable) {
+                codeTable.bootstrapTable('destroy');
+                codeTable = null;
+            }
+            // 清空搜索框
+            $('#codeSearchInput').val('');
+        });
+    });
+
+    // 加载工厂数据
+    function loadFactories() {
+        $.ajax({
+            url: '${ctx}/lineProduct/getFactoryList',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#factoryId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#factoryId').empty();
+                factories = [];
+                if (res.data && res.data.length) {
+                    factories = res.data;
+                    res.data.forEach(item => {
+                        $('#factoryId').append('<option value="' + item.id + '">' + item.factory_name + '</option>');
+                    });
+                }
+                $('#factoryId').append('<option value="">请选择工厂</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');
+            }
+        });
+    }
+
+    // 根据工厂ID加载车间数据
+    function loadWorkshopsByFactoryId(factoryId) {
+        $.ajax({
+            url: '${ctx}/lineProduct/getWorkshopList',
+            type: 'POST',
+            data: {
+                factoryId: factoryId
+            },
+            dataType: 'json',
+            beforeSend: function () {
+                $('#workshopId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#workshopId').empty();
+                workshops = [];
+                if (res.data && res.data.length) {
+                    workshops = res.data;
+                    res.data.forEach(item => {
+                        $('#workshopId').append('<option value="' + item.id + '">' + item.workshop_name + '</option>');
+                    });
+                }
+                $('#workshopId').append('<option value="">请选择车间</option>');
+                $('#workshopId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#workshopId').empty().append('<option value="">加载失败</option>');
+                $('#workshopId').selectpicker('refresh');
+                layer.msg('获取车间数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#workshopId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    // 加载品相数据
+    function loadItems() {
+        $.ajax({
+            url: '${ctx}/qrcodeApply/getItemAll',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#itemId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#itemId').empty();
+                items = [];
+                if (res.data && res.data.length) {
+                    items = res.data;
+                    res.data.forEach(item => {
+                        $('#itemId').append('<option value="' + item.id + '">' + item.item_name + '</option>');
+                    });
+                }
+                $('#itemId').append('<option value="">请选择品项</option>');
+                $('#itemId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#itemId').empty().append('<option value="">加载失败</option>');
+                $('#itemId').selectpicker('refresh');
+                layer.msg('获取品相数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#itemId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    function queryParams(param) {
+        let factoryId = $.trim($("#factoryId").val());
+        let workshopId = $.trim($("#workshopId").val());
+        let itemId = $.trim($("#itemId").val());
+        let startDate = $.trim($("#startDate").val());
+        let endDate = $.trim($("#endDate").val());
+
+        if (factoryId) {
+            param['factoryId'] = factoryId;
+        }
+        if (workshopId) {
+            param['workshopId'] = workshopId;
+        }
+        if (itemId) {
+            param['itemId'] = itemId;
+        }
+        if (startDate) {
+            param['startTime'] = startDate;
+        }
+        if (endDate) {
+            param['endTime'] = endDate;
+        }
+
+        return param;
+    }
+
+    function search() {
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+    }
+
+    function reset() {
+        $("#factoryId").val('');
+        $("#factoryId").selectpicker('val', '');
+        $("#factoryId").selectpicker('refresh');
+        
+        $("#workshopId").val('');
+        $("#workshopId").selectpicker('val', '');
+        $("#workshopId").selectpicker('refresh');
+        
+        $("#itemId").val('');
+        $("#itemId").selectpicker('val', '');
+        $("#itemId").selectpicker('refresh');
+        
+        $("#startDate").val('');
+        $("#endDate").val('');
+        
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+    }
+
+    // 初始化表格
+    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}/newReport/getProdTotalReport',//这个接口需要处理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: [{
+                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;
+                },
+                width: "5%"
+            }, {
+                field: 'factoryName',
+                title: '工厂名称',
+                align: 'center',
+                sortable: true
+            }, {
+                field: 'batchNo',
+                title: '生产批号',
+                align: 'center',
+                sortable: true
+            }, {
+                field: 'itemName',
+                title: '品项',
+                align: 'center',
+                sortable: true
+            }, {
+                field: 'produceDate',
+                title: '生产日期',
+                align: 'center',
+                sortable: true,
+                formatter: function (value) {
+                    return value ? value : '-';
+                }
+            }, {
+                field: 'guoDate',
+                title: '过机日期',
+                align: 'center',
+                sortable: true,
+                formatter: function (value) {
+                    return value ? value : '-';
+                }
+            }, {
+                field: 'workshopName',
+                title: '生产车间',
+                align: 'center',
+                sortable: true
+            }, {
+                field: 'quantity',
+                title: '生产数量',
+                align: 'center',
+                sortable: true
+            }, {
+                field: 'createTime',
+                title: '创建时间',
+                align: 'center',
+                sortable: true,
+                formatter: function (value) {
+                    return value ? value : '-';
+                }
+            }, {
+                field: 'updateTime',
+                title: '更新时间',
+                align: 'center',
+                sortable: true,
+                formatter: function (value) {
+                    return value ? value : '-';
+                }
+            }, {
+                field: 'action',
+                title: '操作',
+                width: 150,
+                align: 'center',
+                formatter: function (value, row) {
+                    let html = '';
+                    // 传递两个参数:batchId和另一个参数(例如itemId)
+                    html += '<button type="button" class="btn btn-primary btn-sm" onclick="viewBoxCodes(\'' + row.batchNo + '\', \'' + row.sku + '\')">查看箱码</button>';
+                    html += '&nbsp;&nbsp;';
+                    html += '<button type="button" class="btn btn-success btn-sm" onclick="viewPalletCodes(\'' + row.batchNo + '\', \'' + row.sku + '\')">查看托码</button>';
+                    return html;
+                }
+            }]
+        });
+    }
+
+    // 查看箱码
+    function viewBoxCodes(taskNo,sku) {
+        currentBatchId = taskNo;
+        currentSku = sku;
+        currentCodeType = 'box';
+        $('#codeModalLabel').text('箱码信息');
+        $('#codeModal').modal('show');
+        codeTable = $('#codeTable').bootstrapTable("destroy");
+        initCodeTable();
+    }
+
+    // 查看托码
+    function viewPalletCodes(taskNo,sku) {
+        currentBatchId = taskNo;
+        currentSku = sku;
+        currentCodeType = 'pallet';
+        $('#codeModalLabel').text('托码信息');
+        $('#codeModal').modal('show');
+        codeTable = $('#codeTable').bootstrapTable("destroy");
+        initCodeTable();
+    }
+
+    function queryCodeParams(param){
+        let code = $.trim($("#codeSearchInput").val());
+        if (code) {
+            param['code'] = code;
+        }
+        param['task_no'] = currentBatchId;
+        param['sku'] = currentSku;
+        return param;
+    }
+
+    // 初始化码值表格
+    function initCodeTable() {
+        const codeUrl = currentCodeType === 'box' ? '${ctx}/newReport/getBoxCodeByTaskNoAndSku' : '${ctx}/newReport/getToCodeByTaskNoAndSku';
+        codeTable = $('#codeTable').bootstrapTable({
+            method: 'get',
+            sortable: true,
+            toolbar: '#toolbar',    //工具按钮用哪个容器
+            striped: true,      //是否显示行间隔色
+            cache: false,      //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
+            pagination: true,     //是否显示分页(*)
+            pageNumber: 1,      //初始化加载第一页,默认第一页
+            pageSize: 10,      //每页的记录行数(*)
+            pageList: [10, 25, 50, 100],  //可供选择的每页的行数(*)
+            url: codeUrl,//这个接口需要处理bootstrap table传递的固定参数
+            queryParamsType: '', //默认值为 'limit' ,在默认情况下 传给服务端的参数为:offset,limit,sort
+            // 设置为 ''  在这种情况下传给服务器的参数为:pageSize,pageNumber
+
+            queryParams: queryCodeParams,//前端调用服务时,会默认传递上边所述的参数,如果需要添加自定义参数,可以自定义一个函数返回请求参数
+            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: 'code',
+                title: '码值',
+                align: 'center',
+                formatter: function (value) {
+                    return value ? value : '-';
+                }
+            }]
+        });
+    }
+
+    // 搜索码值
+    function searchCode() {
+        codeTable = $('#codeTable').bootstrapTable("destroy");
+        initCodeTable();
+    }
+</script>
+</html>

+ 5 - 0
src/main/webapp/page/jinzai/production_line.jsp

@@ -279,6 +279,11 @@
                 title: '所属车间',
                 align: 'center',
                 width: "15%"
+            }, {
+                field: 'operDetail',
+                title: '远程详情',
+                align: 'center',
+                width: "15%"
             }, {
                 field: 'status',
                 title: '状态',

+ 722 - 0
src/main/webapp/page/jinzai/qrcodeApply.jsp

@@ -0,0 +1,722 @@
+<%@ 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;
+        }
+
+        .form-group {
+            margin-bottom: 1.5rem;
+        }
+
+        .row.mb-4 {
+            margin-bottom: 1.5rem;
+        }
+
+        .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">
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <input type="date" id="startDate" class="form-control" placeholder="开始日期" />
+                                </div>
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <input type="date" id="endDate" class="form-control" placeholder="结束日期" />
+                                </div>
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="brandId" class="form-control selectpicker" data-live-search="true" title="请选择品牌">
+                                    </select>
+                                </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>
+                                <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="showApplyModal();" 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>
+
+<!-- 码申请模态框 -->
+<div class="modal fade" id="applyModal" tabindex="-1" role="dialog" aria-labelledby="applyModalLabel">
+    <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="applyModalLabel">码申请</h4>
+            </div>
+            <div class="modal-body">
+                <div class="form-container">
+                    <form id="applyForm">
+                        <input type="hidden" id="applyId" name="applyId">
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalConfigType" class="required control-label">码申请配置</label>
+                                <select id="modalConfigType" class="form-control" required>
+                                    <option value="">请选择配置</option>
+                                    <option value="1">盒码</option>
+                                    <option value="2">袋码</option>
+                                </select>
+                            </div>
+                            <div class="col-md-6">
+                                <label for="modalBrandId" class="required control-label">申请品牌</label>
+                                <select id="modalBrandId" class="form-control" required>
+                                    <option value="">请选择品牌</option>
+                                    <!-- 品牌下拉框选项将通过JavaScript动态加载 -->
+                                </select>
+                            </div>
+                        </div>
+
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalSupplier" class="required control-label">包材供应商</label>
+                                <input type="text" class="form-control" id="modalSupplier" name="modalSupplier" placeholder="请输入" required>
+                            </div>
+                            <div class="col-md-6">
+                                <label for="modalPackUnit" class="required control-label">包装单位</label>
+                                <input type="text" class="form-control" id="modalPackUnit" name="modalPackUnit" placeholder="请输入" required>
+                            </div>
+                        </div>
+
+                        <div class="row mb-4">
+                            <div class="col-md-6">
+                                <label for="modalQuantity" class="required control-label">码数量</label>
+                                <input type="number" class="form-control" id="modalQuantity" name="modalQuantity" placeholder="请输入" required min="1">
+                            </div>
+                            <div class="col-md-6">
+                                <label for="modalProductIdApply" class="required control-label">申请产品</label>
+                                <select id="modalProductIdApply" class="form-control" required>
+                                    <option value="">请选择产品</option>
+                                    <!-- 产品下拉框选项将通过JavaScript动态加载 -->
+                                </select>
+                            </div>
+                        </div>
+
+                        <div class="row mb-4">
+                            <div class="col-md-12">
+                                <label for="modalRemark" class="control-label">备注</label>
+                                <textarea class="form-control" id="modalRemark" name="modalRemark" placeholder="请输入备注信息" rows="3"></textarea>
+                            </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="saveApply();">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 详情模态框 -->
+<div class="modal fade" id="detailModal" tabindex="-1" role="dialog" aria-labelledby="detailModalLabel">
+    <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="detailModalLabel">申请详情</h4>
+            </div>
+            <div class="modal-body">
+                <div class="form-container">
+                    <div class="row mb-4">
+                        <div class="col-md-6">
+                            <p><strong>申请单号:</strong><span id="detailApplyNo"></span></p>
+                        </div>
+                        <div class="col-md-6">
+                            <p><strong>申请企业:</strong><span id="detailApplyCompany"></span></p>
+                        </div>
+                    </div>
+                    <div class="row mb-4">
+                        <div class="col-md-6">
+                            <p><strong>产品名称:</strong><span id="detailProductName"></span></p>
+                        </div>
+                        <div class="col-md-6">
+                            <p><strong>申请人:</strong><span id="detailApplicant"></span></p>
+                        </div>
+                    </div>
+                    <div class="row mb-4">
+                        <div class="col-md-6">
+                            <p><strong>申请时间:</strong><span id="detailApplyTime"></span></p>
+                        </div>
+                        <div class="col-md-6">
+                            <p><strong>码申请配置:</strong><span id="detailApplyConfig"></span></p>
+                        </div>
+                    </div>
+                    <div class="row mb-4">
+                        <div class="col-md-6">
+                            <p><strong>包材供应商:</strong><span id="detailSupplier"></span></p>
+                        </div>
+                        <div class="col-md-6">
+                            <p><strong>包装单位:</strong><span id="detailPackUnit"></span></p>
+                        </div>
+                    </div>
+                    <div class="row mb-4">
+                        <div class="col-md-6">
+                            <p><strong>品牌名称:</strong><span id="detailBrandName"></span></p>
+                        </div>
+                        <div class="col-md-6">
+                            <p><strong>申请码数量:</strong><span id="detailApplyQuantity"></span></p>
+                        </div>
+                    </div>
+                    <div class="row mb-4">
+                        <div class="col-md-6">
+                            <p><strong>码包接收人:</strong><span id="detailReceiver">操作人</span></p>
+                        </div>
+                        <div class="col-md-6">
+                            <p><strong>状态:</strong><span id="detailStatus"></span></p>
+                        </div>
+                    </div>
+                    <div class="row mb-4">
+                        <div class="col-md-12">
+                            <p><strong>备注:</strong><span id="detailRemark"></span></p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 全局js -->
+<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+<!-- 全局js -->
+<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 brands = []; // 存储品牌数据
+    var products = []; // 存储产品数据
+
+    $(document).ready(function () {
+        $('.selectpicker').selectpicker({
+            liveSearch: true,
+            size: 5,
+            actionsBox: true,
+            selectedTextFormat: 'count > 2'
+        });
+        // 加载品牌和产品数据
+        loadBrands();
+        loadProducts();
+
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+        initValidation();
+
+        // 绑定重置按钮点击事件
+        $('#resetBtn').on('click', function() {
+            reset();
+        });
+    });
+
+    // 加载品牌数据
+    function loadBrands() {
+        $.ajax({
+            url: '${ctx}/productNew/getBrandList',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#brandId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#brandId').empty();
+                brands = [];
+                if (res.data && res.data.length) {
+                    brands = res.data;
+                    res.data.forEach(item => {
+                        $('#brandId').append('<option value="' + item.id + '">' + item.brand_name + '</option>');
+                    });
+                }
+                $('#brandId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#brandId').empty().append('<option value="">加载失败</option>');
+                $('#brandId').selectpicker('refresh');
+                layer.msg('获取品牌数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#brandId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
+
+    // 加载产品数据
+    function loadProducts() {
+        $.ajax({
+            url: '${ctx}/qrcodeApply/getItemAll',
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#productId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#productId').empty();
+                products = [];
+                if (res.data && res.data.length) {
+                    products = res.data;
+                    res.data.forEach(item => {
+                        $('#productId').append('<option value="' + item.id + '">' + item.item_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 loadModalBrands() {
+        const modalBrandSelect = $('#modalBrandId');
+        modalBrandSelect.empty().append('<option value="">请选择品牌</option>');
+
+        if (brands.length > 0) {
+            brands.forEach(brand => {
+                modalBrandSelect.append('<option value="' + brand.id + '">' + brand.brand_name + '</option>');
+            });
+        }
+    }
+
+    // 加载模态框中的产品数据
+    function loadModalProducts() {
+        const modalProductSelect = $('#modalProductIdApply');
+        modalProductSelect.empty().append('<option value="">请选择产品</option>');
+
+        if (products.length > 0) {
+            products.forEach(product => {
+                modalProductSelect.append('<option value="' + product.id + '">' + product.item_name + '</option>');
+            });
+        }
+    }
+
+    function queryParams(param) {
+        let applyStartTime = $.trim($("#startDate").val());
+        let applyEndTime = $.trim($("#endDate").val());
+        let brandId = $.trim($("#brandId").val());
+        let productId = $.trim($("#productId").val());
+
+        if (applyStartTime) {
+            param['startDate'] = applyStartTime;
+        }
+        if (applyEndTime) {
+            param['endDate'] = applyEndTime;
+        }
+        if (brandId) {
+            param['brandId'] = brandId;
+        }
+        if (productId) {
+            param['productId'] = productId;
+        }
+
+        return param;
+    }
+
+    function search() {
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+    }
+
+    function reset() {
+        $("#startDate").val('');
+        $("#endDate").val('');
+        $("#brandId").val('');
+        $("#brandId").selectpicker('val', '');
+        $("#brandId").selectpicker('deselectAll');
+        $("#brandId").selectpicker('refresh');
+        $("#productId").val('');
+        $("#productId").selectpicker('val', '');
+        $("#productId").selectpicker('deselectAll');
+        $("#productId").selectpicker('refresh');
+        table = $('#table').bootstrapTable("destroy");
+        initTable();
+    }
+
+    function showApplyModal() {
+        loadModalBrands();
+        loadModalProducts();
+        // 清空表单
+        $('#applyForm')[0].reset();
+        $('#applyId').val('');
+        $('#applyModalLabel').text('新增码申请');
+
+        // 重置表单验证状态
+        if ($('#applyForm').data('validator')) {
+            $('#applyForm').data('validator').resetForm();
+        }
+
+        // 清除所有错误类
+        $('#applyForm .error').removeClass('error');
+        $('#applyForm label.error').remove();
+
+        // 显示模态框
+        $('#applyModal').modal('show');
+    }
+
+    function saveApply() {
+        if (!$('#applyForm').valid()) {
+            return;
+        }
+
+        const formData = {
+            applyConfig: $('#modalConfigType').val(),
+            companyId: $('#modalBrandId').val(),
+            supplier: $('#modalSupplier').val(),
+            packagingUnit: $('#modalPackUnit').val(),
+            applyQuantity: $('#modalQuantity').val(),
+            productId: $('#modalProductIdApply').val(),
+            remark: $('#modalRemark').val()
+        };
+
+        $.ajax({
+            url: '${ctx}/qrcodeApply/applyQrcode',
+            type: 'POST',
+            data: JSON.stringify(formData),
+            contentType: 'application/json',
+            success: function (data) {
+                if (data.code === 0) {
+                    layer.msg('保存成功', {icon: 6});
+                    // 关闭模态框
+                    $('#applyModal').modal('hide');
+                    // 刷新表格
+                    search();
+                } else {
+                    layer.msg(data.msg, {icon: 5});
+                }
+            },
+            error: function () {
+                layer.msg('保存失败', {icon: 5});
+            }
+        });
+    }
+
+    function initValidation() {
+        $('#applyForm').validate({
+            rules: {
+                modalConfigType: {
+                    required: true
+                },
+                modalBrandId: {
+                    required: true
+                },
+                modalSupplier: {
+                    required: true,
+                    maxlength: 100
+                },
+                modalPackUnit: {
+                    required: true,
+                    maxlength: 50
+                },
+                modalQuantity: {
+                    required: true,
+                    number: true,
+                    min: 1
+                },
+                modalProductIdApply: {
+                    required: true
+                },
+                modalRemark: {
+                    maxlength: 500
+                }
+            },
+            messages: {
+                modalConfigType: {
+                    required: '请选择码申请配置'
+                },
+                modalBrandId: {
+                    required: '请选择申请品牌'
+                },
+                modalSupplier: {
+                    required: '请输入包材供应商',
+                    maxlength: '包材供应商最多100个字符'
+                },
+                modalPackUnit: {
+                    required: '请输入包装单位',
+                    maxlength: '包装单位最多50个字符'
+                },
+                modalQuantity: {
+                    required: '请输入码数量',
+                    number: '请输入有效的数字',
+                    min: '码数量至少为1'
+                },
+                modalProductIdApply: {
+                    required: '请选择申请产品'
+                },
+                modalRemark: {
+                    maxlength: '备注最多500个字符'
+                }
+            }
+        });
+    }
+
+    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}/qrcodeApply/getProdUploadRecord',//这个接口需要处理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: [{
+                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;
+                },
+                width: "5%"
+            }, {
+                field: 'applyNo',
+                title: '申请单号',
+                align: 'center',
+                width: "15%",
+                formatter: function (value, row) {
+                    // 添加点击事件,显示详情
+                    return '<a href="javascript:void(0);" onclick="showDetailModal(' + row.id + ');">' + value + '</a>';
+                }
+            }, {
+                field: 'applyCompany',
+                title: '申请品牌',
+                align: 'center',
+                width: "10%"
+            }, {
+                field: 'productName',
+                title: '产品名称',
+                align: 'center',
+                width: "15%"
+            }, {
+                field: 'packageFileName',
+                title: '码包文件名称',
+                align: 'center',
+                width: "15%"
+            }, {
+                field: 'applicant',
+                title: '申请人',
+                align: 'center',
+                width: "10%"
+            }, {
+                field: 'applyQuantity',
+                title: '申请码数量',
+                align: 'center',
+                width: "10%"
+            }, {
+                field: 'applyTime',
+                title: '申请时间',
+                align: 'center',
+                width: "10%"
+            }, {
+                field: 'status',
+                title: '状态',
+                align: 'center',
+                formatter: function (value) {
+                    if (value === 0) {
+                        return '<span class="label label-warning">待处理</span>';
+                    } else if (value === 1) {
+                        return '<span class="label label-success">已生成</span>';
+                    } else if (value === 2) {
+                        return '<span class="label label-danger">生成中</span>';
+                    } else {
+                        return '<span class="label label-default">未知</span>';
+                    }
+                },
+                width: "10%"
+            }, {
+                title: '操作',
+                align: 'center',
+                width: "10%",
+                formatter: function (value, row) {
+                    //只有状态为已生成才有下载按钮
+                    if (row.status === 1) {
+                        return '<a class="btn btn-primary btn-sm" href="' + row.downloadUrl + '">下载</a>';
+                    }
+                }
+            }],
+            onLoadSuccess: function (data) {
+                console.log("数据加载成功", data);
+            },
+            onLoadError: function () {
+                layer.msg('数据加载失败', {icon: 2});
+            }
+        });
+    }
+    
+    // 显示详情模态框
+    function showDetailModal(applyId) {
+        // 显示加载中提示
+        let loadingIndex = layer.load(1, {shade: [0.5, '#fff']});
+        
+        // 请求详情数据
+        $.ajax({
+            url: '${ctx}/qrcodeApply/getDetailByApplyId',
+            type: 'POST',
+            data: {
+                applyId: applyId
+            },
+            dataType: 'json',
+            success: function (res) {
+                layer.close(loadingIndex);
+                if (res.code === 0 && res.data) {
+                    let data = res.data;
+                    // 填充详情数据
+                    $('#detailApplyNo').text(data.applyNo || '-');
+                    $('#detailApplyCompany').text(data.applyCompany || '-');
+                    $('#detailProductName').text(data.productName || '-');
+                    $('#detailApplicant').text(data.applicant || '-');
+                    $('#detailApplyTime').text(data.applyTime || '-');
+                    
+                    // 处理码申请配置显示
+                    let configText = '-';
+                    if (data.applyConfig === 1) {
+                        configText = '盒码';
+                    } else if (data.applyConfig === 2) {
+                        configText = '袋码';
+                    }
+                    $('#detailApplyConfig').text(configText);
+                    $('#detailSupplier').text(data.supplier || '-');
+                    $('#detailPackUnit').text(data.packagingUnit || '-');
+                    $('#detailBrandName').text(data.applyCompany || '-');
+                    $('#detailApplyQuantity').text(data.applyQuantity || '-');
+                    // 处理状态显示
+                    let statusText = '-';
+                    if (data.status === 0) {
+                        statusText = '<span class="label label-warning">待处理</span>';
+                    } else if (data.status === 1) {
+                        statusText = '<span class="label label-success">已生成</span>';
+                    } else if (data.status === 2) {
+                        statusText = '<span class="label label-danger">生成中</span>';
+                    }
+                    $('#detailStatus').html(statusText);
+                    $('#detailRemark').text(data.remark || '-');
+                    // 显示模态框
+                    $('#detailModal').modal('show');
+                } else {
+                    layer.msg(res.msg || '获取详情失败', {icon: 5});
+                }
+            },
+            error: function () {
+                layer.close(loadingIndex);
+                layer.msg('获取详情请求失败', {icon: 5});
+            }
+        });
+    }
+</script>
+</html>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1081 - 0
src/main/webapp/page/jinzai/userManagement.jsp