فهرست منبع

Merge branch 'master' of http://git.dnzc.vip:3000/tmzn/jinzaifoodadmin.git

wzh 2 ماه پیش
والد
کامیت
b0fcd42109

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

@@ -264,7 +264,7 @@ public class ItemNewController extends CommonController {
 
     public void getItemAll(){
         ApiResponse apiResponse;
-        List<Record> list = Db.find("select id,item_name from t_jz_item where Status =1");
+        List<Record> list = Db.find("select sku,id,item_name from t_jz_item where Status =1");
         if(CollUtil.isEmpty(list)){
             apiResponse = ApiResponse.success(new ArrayList<>());
             renderJson(apiResponse);

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

@@ -4,6 +4,7 @@ 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.PathKit;
 import com.jfinal.upload.UploadFile;
 import com.qlm.annotation.RequestUrl;
 import com.qlm.common.ApiResponse;
@@ -18,6 +19,8 @@ import com.qlm.view.core.AdminView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.servlet.ServletOutputStream;
+import java.io.File;
 import java.io.InputStream;
 import java.net.URLEncoder;
 import java.nio.file.Files;
@@ -146,7 +149,7 @@ public class JxsNewController extends CommonController {
             List<JxsImportDto> importList = EasyExcelUtil.getImportData(inputStream, JxsImportDto.class);
             inputStream.close();
             // 调用Service层导入数据
-            ApiResponse apiResponse = jxsService.importJxs(importList, loginUser.getUsername());
+            ApiResponse apiResponse = jxsService.importJxs(importList, loginUser.getUsername(), getRequest().getContextPath());
             renderJson(apiResponse);
         } catch (Exception e) {
             logger.error("导入产线产品关联信息异常:", e);
@@ -155,32 +158,86 @@ public class JxsNewController extends CommonController {
     }
 
     /**
-     * 导出经销商列表
+     * 导出经销商列表 - 直接流式下载
      */
     public void exportJxsList() {
-        ApiResponse apiResponse = ApiResponse.success();
-        try {
-            // 获取查询条件
-            String jxsName = getPara("jxsName");
-            String code = getPara("code");
-            Integer status = getParaToInt("status");
-
-            // 调用Service层获取导出数据
-            List<JxsExportDto> dtos = jxsService.exportJxsList(jxsName, code,status);
-
-            if (CollUtil.isEmpty(dtos)) {
-                renderJson(ApiResponse.error("没有要导出的数据"));
+        // 使用 try-with-resources 确保 InputStream 和 OutputStream 都能被正确关闭
+        try (InputStream inputStream = generateExportInputStream()) {
+            if (inputStream == null) {
                 return;
             }
-            // 使用EasyExcelUtil生成Excel文件流
-            InputStream inputStream = EasyExcelUtil.export(dtos, "经销商列表", JxsExportDto.class);
+
+            // 1. 设置响应头,告诉浏览器这是一个需要下载的文件
             String fileName = URLEncoder.encode("经销商列表.xlsx", "UTF-8");
-            String ossUrl = OssUtil.upload(inputStream, fileName);
-            apiResponse = ApiResponse.success(ossUrl);
+            getResponse().setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
+
+            // 2. 设置文件的MIME类型
+            getResponse().setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
+
+            // 3. 手动将文件流写入响应的输出流
+            try (ServletOutputStream sos = getResponse().getOutputStream()) {
+                byte[] buffer = new byte[1024];
+                int len;
+                // 循环读取输入流,并写入到输出流
+                while ((len = inputStream.read(buffer)) != -1) {
+                    sos.write(buffer, 0, len);
+                }
+                sos.flush(); // 确保所有数据都被发送
+            }
+            renderNull();
         } catch (Exception e) {
             logger.error("导出经销商列表异常:", e);
-            renderJson(ApiResponse.error("导出经销商列表失败:" + e.getMessage()));
+            // 确保在异常情况下也返回一个标准的JSON错误
+            if (!getResponse().isCommitted()) {
+                renderJson(ApiResponse.error("导出经销商列表失败:" + e.getMessage()));
+            }
         }
-        renderJson(apiResponse);
+    }
+
+    /**
+     * 下载导入时生成的错误文件
+     */
+    public void downloadErrorFile() {
+        String fileId = getPara("fileId");
+
+        // 安全性检查:防止路径遍历攻击
+        if (fileId == null || fileId.contains("..") || fileId.contains("/")) {
+            renderJson(ApiResponse.error("无效的文件ID"));
+            return;
+        }
+
+        String tempDir = PathKit.getWebRootPath() + "/import_error_files/";
+        File file = new File(tempDir + fileId);
+
+        if (file.exists() && file.isFile()) {
+            // 使用 JFinal 的 renderFile 方法直接将文件流式返回给客户端
+            // 这是 renderFile 最标准的用法
+            renderFile(file);
+        } else {
+            renderJson(ApiResponse.error("文件不存在或已被清理"));
+        }
+    }
+
+
+    /**
+     * 辅助方法:生成导出文件的输入流
+     * @return 如果有数据则返回InputStream,否则返回null并直接渲染错误信息
+     */
+    private InputStream generateExportInputStream() throws Exception {
+        // 获取查询条件
+        String jxsName = getPara("jxsName");
+        String code = getPara("code");
+        Integer status = getParaToInt("status");
+
+        // 调用Service层获取导出数据
+        List<JxsExportDto> dtos = jxsService.exportJxsList(jxsName, code, status);
+
+        if (CollUtil.isEmpty(dtos)) {
+            renderJson(ApiResponse.error("没有要导出的数据"));
+            return null;
+        }
+
+        // 使用EasyExcelUtil生成Excel文件流
+        return EasyExcelUtil.export(dtos, "经销商列表", JxsExportDto.class);
     }
 }

+ 30 - 16
src/main/java/com/qlm/controller/jinzai/NewReportController.java

@@ -12,6 +12,7 @@ import com.qlm.controller.common.CommonController;
 import com.qlm.controller.organize.ProductionLineController;
 import com.qlm.dto.ProdTaskUploadRecordDto;
 import com.qlm.dto.ProductionTotalVO;
+import com.qlm.tools.WxUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,12 +52,11 @@ public class NewReportController extends CommonController {
                 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 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");
+            String selectColumns = "select a.task_no,a.sku,d.factory_name,e.workshop_name,a.device_no,a.produce_date,COUNT(*) AS total_count,MAX(guo_time) as guo_time";
+            StringBuilder sql = new StringBuilder(" from jinzai_upload_master a");
+            sql.append(" LEFT JOIN t_jz_device b ON a.device_no = b.device_no");
+            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){
@@ -67,7 +67,7 @@ public class NewReportController extends CommonController {
                 sql.append(" and b.workshop_id = ?");
                 params.add(workshopId);
             }
-            sql.append(" order by a.produce_date desc");
+            sql.append(" GROUP BY a.task_no, d.factory_name, e.workshop_name,a.sku, a.device_no, a.produce_date 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<>()));
@@ -174,18 +174,32 @@ public class NewReportController extends CommonController {
         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.setGuoDate(DateUtil.format(record.getDate("guo_time"),"yyyy-MM-dd HH:mm:ss"));
         productionTotalVO.setWorkshopName(record.getStr("workshop_name"));
-        //根据taskNo 和 Sku  查询数量
-        Long quantity = Db.queryLong("select count(id) quantity from jinzai_upload_master where task_no = ? and sku = ?", record.getStr("task_no"), record.getStr("sku"));
-        if(quantity == null){
-            quantity = 0L;
+        productionTotalVO.setQuantity(WxUtil.getInt("total_count", record));
+        try {
+            StringBuilder itemNameSql = new StringBuilder("select c.* from t_jz_device a");
+            itemNameSql.append(" inner join t_line_product b on a.id = b.line_id");
+            itemNameSql.append(" inner join t_jz_item c on b.product_id = c.product_id");
+            itemNameSql.append(" where a.device_no = ? and c.sku = ?");
+            Record first = Db.findFirst(itemNameSql.toString(), record.getStr("device_no"), record.getStr("sku"));
+            String itemName = "";
+            if(first != null){
+                itemName = first.getStr("item_name");
+            }
+            productionTotalVO.setItemName(itemName);
+        }catch (Exception e){
+            logger.error("查询品相名称异常:", e);
         }
-        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"));
+        //根据taskNo 和 Sku  查询数量
+//        Long quantity = Db.queryLong("select count(id) quantity from jinzai_upload_master 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;
     }
 }

+ 159 - 25
src/main/java/com/qlm/controller/jinzai/ProdBatchController.java

@@ -13,16 +13,14 @@ 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.ItemModifyRecordDto;
-import com.qlm.dto.ItemModifyRecordVO;
-import com.qlm.dto.ProdTaskUploadRecordDto;
+import com.qlm.dto.*;
 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.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,17 +70,21 @@ public class ProdBatchController extends CommonController {
                 whereSql.append(" and upload_time = ?");
                 params.add(createTime);
             }
-//            if(StrKit.notBlank(factoryId)){
-//                whereSql.append(" and factory_id = ?");
-//                params.add(factoryId);
-//            }
+            if(StrKit.notBlank(factoryId)){
+                whereSql.append(" and device_no in (select device_no from t_jz_device where factory_id = ?)");
+                params.add(factoryId);
+            }
             if(StrKit.notBlank(productName)){
                 whereSql.append(" and pinxiang like ?");
                 params.add("%" + productName + "%");
             }
-//            if(StrKit.notBlank(wmsStatus)){
-//                whereSql.append(" and wms_status = ?");
-//            }
+            if(StrKit.notBlank(wmsStatus)){
+                if("1".equals(wmsStatus)){
+                    whereSql.append(" and upload_time is not null");
+                }else if("2".equals(wmsStatus)){
+                    whereSql.append(" and upload_time is null");
+                }
+            }
             whereSql.append(" order by create_time desc");
             Page<Record> paginate = Db.paginate(pageNumber, pageSize, "select *", whereSql.toString(), params.toArray());
             if(paginate  == null){
@@ -90,9 +92,28 @@ public class ProdBatchController extends CommonController {
                 return;
             }
             List<Record> list = paginate.getList();
+
+            Map<String,Record> recordMap = new HashMap<>();
+            String sql = "select a.device_no, b.factory_name, c.workshop_name from t_jz_device a left join t_factory b on a.factory_id = b.id left join t_workshop c on a.workshop_id = c.id";
+            List<Record> records = Db.find(sql);
+            if(CollUtil.isNotEmpty(records)){
+                recordMap = records.stream().collect(Collectors.toMap(record -> record.getStr("device_no"), record -> record));
+            }
             List<ProdTaskUploadRecordDto> prodTaskUploadRecordDtoList = new ArrayList<>();
             for (Record record : list) {
-                prodTaskUploadRecordDtoList.add(convertRecordToDto(record));
+                ProdTaskUploadRecordDto prodTaskUploadRecordDto = convertRecordToDto(record);
+                String factoryName = "";
+                String workshopName = "";
+                if(recordMap.containsKey(record.getStr("device_no"))){
+                    Record recordDetail = recordMap.get(record.getStr("device_no"));
+                    if(recordDetail != null){
+                        factoryName = recordDetail.getStr("factory_name");
+                        workshopName = recordDetail.getStr("workshop_name");
+                    }
+                }
+                prodTaskUploadRecordDto.setFactoryName(factoryName);
+                prodTaskUploadRecordDto.setLineName(workshopName);
+                prodTaskUploadRecordDtoList.add(prodTaskUploadRecordDto);
             }
             renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, prodTaskUploadRecordDtoList));
         }catch (Exception e){
@@ -108,6 +129,76 @@ public class ProdBatchController extends CommonController {
         renderJsp("/page/jinzai/reprint_record.jsp");
     }
 
+
+
+    public void getReprintRecord() {
+        // 1. 获取前端分页和查询参数
+        int pageNumber = getParaToInt("pageNumber", 1);
+        int pageSize = getParaToInt("pageSize", 10);
+        // 查询参数
+        String reprintTime = getPara("reprintTime");
+        String factoryId = getPara("factoryId");
+        String sku = getPara("sku");
+
+        String selectSql = "select b.task_no as taskOrderNo, b.device_no,d.id as deviceId, b.sku, a.box_code as boxCode, b.duo_code as palletCode, a.reprint_time as reprintTime, e.workshop_name as workshopName, f.factory_name as factoryName,d.`desc` as lineName";
+        StringBuilder fromSql = new StringBuilder("FROM t_reprint_record a ");
+        fromSql.append(" inner join jinzai_upload_master b on a.box_code = b.id");
+        fromSql.append(" left join t_jz_device d on b.device_no = d.device_no");
+        fromSql.append(" left join t_workshop e on d.workshop_id = e.id");
+        fromSql.append(" left join t_factory f on e.factory_id = f.id");
+
+        // WHERE 条件和参数
+        fromSql.append(" WHERE 1=1");
+        List<Object> params = new ArrayList<>();
+
+        // 补打时间查询
+        if (StringUtils.isNotBlank(reprintTime)) {
+            fromSql.append(" AND DATE(a.reprint_time) = ? ");
+            params.add(reprintTime);
+        }
+
+        // 工厂名称查询
+        if (StringUtils.isNotBlank(factoryId)) {
+            fromSql.append(" AND d.factory_id = ? ");
+            params.add(factoryId);
+        }
+
+        // 产品名称查询
+        if (StringUtils.isNotBlank(sku)) {
+            fromSql.append(" AND b.sku = ? ");
+            params.add(sku);
+        }
+
+        fromSql.append(" ORDER BY a.reprint_time DESC ");
+
+        // 3. 执行分页查询
+        Page<Record> page = Db.paginate(pageNumber, pageSize, selectSql, fromSql.toString(), params.toArray());
+        if (page == null) {
+            renderJson(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>()));
+            return;
+        }
+
+        List<Record> list = page.getList();
+        try {
+            for (Record record : list) {
+                StringBuilder productSql = new StringBuilder("select b.*");
+                productSql.append(" from t_line_product a");
+                productSql.append(" inner join t_jz_product b on a.product_id = b.id");
+                productSql.append(" inner join t_jz_item c on b.id = c.product_id");
+                productSql.append(" where a.line_id = ? and c.sku = ?");
+                Record item = Db.findFirst(productSql.toString(), WxUtil.getInt("deviceId", record), record.getStr("sku"));
+                String productName =  "";
+                if(item != null){
+                    productName = item.getStr("product_name");
+                }
+                record.set("productName", productName);
+            }
+        }catch (Exception e){
+            logger.error("查询补打信息记录异常:", e);
+        }
+        renderJson(new PageResult<>(page.getTotalRow(), pageNumber, pageSize, list));
+    }
+
     /**
      * 工控机监控记录路由
      */
@@ -128,13 +219,12 @@ 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_jz_item tjp on tdd.product_sku = tjp.sku");
             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.device_no as deviceNo,tjd.desc as device_name, tf.factory_name, tw.workshop_name, tjp.item_name as 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", fromSql.toString(), params.toArray());
             if (paginate == null) {
                 renderJson(ApiResponse.success(new PageResult<>(0, pageNumber, pageSize, new ArrayList<>())));
                 return;
@@ -142,7 +232,8 @@ public class ProdBatchController extends CommonController {
             List<Record> list = paginate.getList();
             List<DeviceMonitorRecordDto> deviceMonitorRecordDtoList = new ArrayList<>();
             for (Record record : list) {
-                deviceMonitorRecordDtoList.add(convertRecordToDeviceMonitorRecordDto(record));
+                DeviceMonitorRecordDto deviceMonitorRecordDto = convertRecordToDeviceMonitorRecordDto(record);
+                deviceMonitorRecordDtoList.add(deviceMonitorRecordDto);
             }
             renderJson(new PageResult<>(paginate.getTotalRow(), pageNumber, pageSize, deviceMonitorRecordDtoList));
         } catch (Exception e) {
@@ -169,15 +260,15 @@ public class ProdBatchController extends CommonController {
                 if (result) {
                     apiResponse.setMsg("刷新成功");
                     apiResponse.setCode(0);
-                    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.item_name as product_name");
+                    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");
                     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_item tjp on tjd.product_sku = tjp.sku");
                     sql.append(" where tdd.id = ?");
                     record = Db.findFirst(sql.toString(), deviceId);
                     DeviceMonitorRecordDto deviceMonitorRecordDto = convertRecordToDeviceMonitorRecordDto(record);
+
                     apiResponse.setData(deviceMonitorRecordDto);
                 } else {
                     apiResponse.setCode(500);
@@ -330,9 +421,6 @@ public class ProdBatchController extends CommonController {
                     .map(code -> "'" + code + "'")
                     .collect(Collectors.joining(","));
             List<Record> masterRecords = Db.find("select * from jinzai_upload_master where id in(" + codeList + ")");
-            if(masterRecords.size() != referenceCodes.size()){
-                return ApiResponse.error("有箱码不存在");
-            }
             masterRecords.forEach(record -> {
                 referenceCodeAndSku.put(record.getStr("id"),record.getStr("sku"));
                 referenceCodeAndDeviceNo.put(record.getStr("id"),record.getStr("device_no"));
@@ -341,9 +429,13 @@ public class ProdBatchController extends CommonController {
             String codeList = referenceCodes.stream()
                     .map(code -> "'" + code + "'")
                     .collect(Collectors.joining(","));
-            List<Record> duoRecords = Db.find("select * from jinzai_upload_master where duo_code in(" + codeList + ")");
-            if(duoRecords.size() != referenceCodes.size()){
-                return ApiResponse.error("有托码不存在");
+            List<Record> duoRecords = Db.find("select * from jinzai_upload_master1 where duo_code in(" + codeList + ")");
+            Map<String,List<Record>> duoMap = duoRecords.parallelStream()
+                    .collect(Collectors.groupingBy(r->r.getStr("duo_code")));
+            for(String duoCode:referenceCodes){
+                if(!duoMap.containsKey(duoCode)){
+                    return ApiResponse.error("有托码不存在");
+                }
             }
             duoRecords.forEach(record -> {
                 referenceCodeAndSku.put(record.getStr("duo_code"),record.getStr("sku"));
@@ -427,7 +519,7 @@ public class ProdBatchController extends CommonController {
                 } else if (modifyType.compareTo(2) == 0) {
                     Db.update("update jinzai_upload_master 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 set upload_time = null,pinxiang = ?,kouwei = ?,sku = ? where duo_code in(" + addReferenceCode + ")", itemName, itemKouWei,sku);
+                    Db.update("update jinzai_upload_master1 set upload_time = null,pinxiang = ?,kouwei = ?,sku = ? where duo_code in(" + addReferenceCode + ")", itemName, itemKouWei,sku);
                 }
                 return true;
             } catch (Exception e) {
@@ -452,6 +544,33 @@ public class ProdBatchController extends CommonController {
         }
     }
 
+    @Clear
+    public void reprint(){
+        ApiResponse apiResponse = new ApiResponse();
+        try {
+            String bodyData = HttpKit.readData(getRequest());
+            ReprintDto reprintDto = JSON.parseObject(bodyData, ReprintDto.class);
+            Record record = new Record();
+            record.set("box_code",reprintDto.getBoxCode());
+            if(StringUtils.isNotBlank(reprintDto.getReprintTime())){
+                record.set("reprint_time", DateUtil.parse(reprintDto.getReprintTime()));
+            }
+            boolean save = Db.save("t_reprint_record", record);
+            if(save){
+                apiResponse.setCode(200);
+                apiResponse.setMsg("补打成功");
+            }else{
+                apiResponse.setCode(500);
+                apiResponse.setMsg("补打失败");
+            }
+        } catch (Exception e) {
+            logger.error("补打异常:", e);
+            apiResponse.setCode(500);
+            apiResponse.setMsg("系统异常:" + e.getMessage());
+        }
+        renderJson(apiResponse);
+    }
+
 
 
     private ProdTaskUploadRecordDto convertRecordToDto(Record record) {
@@ -480,7 +599,6 @@ public class ProdBatchController extends CommonController {
         deviceMonitorRecordDto.setFactory(record.getStr("factory_name"));
         deviceMonitorRecordDto.setWorkshop(record.getStr("workshop_name"));
         deviceMonitorRecordDto.setLine(record.getStr("device_name"));
-        deviceMonitorRecordDto.setProduct(record.getStr("product_name"));
         deviceMonitorRecordDto.setProductionNum(record.getInt("current_quantity").toString());
         deviceMonitorRecordDto.setTotalNum(record.getInt("total_quantity").toString());
         deviceMonitorRecordDto.setPlatformNum(record.getInt("uploaded_quantity").toString());
@@ -496,6 +614,22 @@ public class ProdBatchController extends CommonController {
         if(deviceNo != null){
             deviceMonitorRecordDto.setOnline(1);
         }
+
+        try {
+            StringBuilder productSql = new StringBuilder("select b.*");
+            productSql.append(" from t_line_product a");
+            productSql.append(" inner join t_jz_product b on a.product_id = b.id");
+            productSql.append(" inner join t_jz_item c on b.id = c.product_id");
+            productSql.append(" where a.line_id = ? and c.sku = ?");
+            Record item = Db.findFirst(productSql.toString(), WxUtil.getInt("deviceId", record), record.getStr("product_sku"));
+            String productName =  "";
+            if(item != null){
+                productName = item.getStr("product_name");
+            }
+            deviceMonitorRecordDto.setProduct(productName);
+        }catch (Exception e){
+            logger.error("工控机记录查询获取品相异常:", e);
+        }
         return deviceMonitorRecordDto;
     }
 }

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

@@ -51,7 +51,7 @@ public class ProductController extends CommonController{
 			sql.append(" where tjd.device_no = ? and p.line_on = 1 and i.sku is not null");
 			t_jz_productList = Db.find(sql.toString(), device_no);
 		}else{
-			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");
+			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) and p.product_name  not like '%北海%' where p.line_on = 1 and i.sku is not null");
 		}
 		obj.put("products", t_jz_productList);
 		renderJson(obj);

+ 37 - 0
src/main/java/com/qlm/dto/ReprintDto.java

@@ -0,0 +1,37 @@
+package com.qlm.dto;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: ReprintDto
+ * @description: TODO
+ * @author: yingjian.wu
+ * @create: 2025-10-14 10:48
+ * @Version 1.0
+ **/
+public class ReprintDto {
+    /**
+     * 补打时间
+     */
+    private String reprintTime;
+
+    /**
+     * 箱码
+     */
+    private String boxCode;
+
+    public String getReprintTime() {
+        return reprintTime;
+    }
+
+    public void setReprintTime(String reprintTime) {
+        this.reprintTime = reprintTime;
+    }
+
+    public String getBoxCode() {
+        return boxCode;
+    }
+
+    public void setBoxCode(String boxCode) {
+        this.boxCode = boxCode;
+    }
+}

+ 121 - 0
src/main/java/com/qlm/dto/ReprintRecordVO.java

@@ -0,0 +1,121 @@
+package com.qlm.dto;
+
+import java.io.Serializable;
+
+/**
+ * @program: jinzaifoodadmin
+ * @ClassName: ReprintRecordVO
+ * @description: TODO
+ * @author: yingjian.wu
+ * @create: 2025-10-13 18:29
+ * @Version 1.0
+ **/
+public class ReprintRecordVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 任务单号
+     */
+    private String taskOrderNo;
+
+    /**
+     * 工厂名称
+     */
+    private String factoryName;
+
+    /**
+     * 生产车间
+     */
+    private String workshopName;
+
+    /**
+     * 产线
+     */
+    private String lineName;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     * 箱码
+     */
+    private String boxCode;
+
+    /**
+     * 托码
+     */
+    private String palletCode;
+
+    /**
+     * 补打时间
+     */
+    private String reprintTime;
+
+    // --- Standard Getters and Setters ---
+
+    public String getTaskOrderNo() {
+        return taskOrderNo;
+    }
+
+    public void setTaskOrderNo(String taskOrderNo) {
+        this.taskOrderNo = taskOrderNo;
+    }
+
+    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 getLineName() {
+        return lineName;
+    }
+
+    public void setLineName(String lineName) {
+        this.lineName = lineName;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public void setProductName(String productName) {
+        this.productName = productName;
+    }
+
+    public String getBoxCode() {
+        return boxCode;
+    }
+
+    public void setBoxCode(String boxCode) {
+        this.boxCode = boxCode;
+    }
+
+    public String getPalletCode() {
+        return palletCode;
+    }
+
+    public void setPalletCode(String palletCode) {
+        this.palletCode = palletCode;
+    }
+
+    public String getReprintTime() {
+        return reprintTime;
+    }
+
+    public void setReprintTime(String reprintTime) {
+        this.reprintTime = reprintTime;
+    }
+}

+ 30 - 8
src/main/java/com/qlm/netty/NettyServer.java

@@ -1,8 +1,5 @@
 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;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.ChannelFuture;
@@ -12,7 +9,12 @@ import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.codec.LengthFieldPrepender;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
 import io.netty.handler.timeout.IdleStateHandler;
+import io.netty.util.CharsetUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,12 +45,32 @@ public class NettyServer {
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         @Override
                         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());
-                            // 添加业务处理器
+                            // --- ChannelPipeline 顺序非常重要 ---
+                            // 【入站 Inbound - 负责解码】
+                            // 1. 【核心解码】LengthFieldBasedFrameDecoder (基于长度字段的帧解码器)
+                            //    maxFrameLength: 8192 (单条消息最大长度)
+                            //    lengthFieldOffset: 0 (长度字段的偏移量,即长度头从第0个字节开始)
+                            //    lengthFieldLength: 4 (长度字段占4个字节)
+                            //    lengthAdjustment: 0 (长度调整值,数据包长度=长度头的值+adjustment)
+                            //    initialBytesToStrip: 4 (解码后,跳过前面4个字节的长度头,只保留数据体)
+                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(8192, 0, 4, 0, 4));
+                            // 2. 【解码】StringDecoder (字符串解码器)
+                            //    将 LengthFieldBasedFrameDecoder 传来的、完整的 ByteBuf 数据体按 UTF-8 解码成字符串
+                            ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
+                            // 【出站 Outbound - 负责编码】(注意:出站是反向执行的)
+                            // 1. 【核心编码】LengthFieldPrepender (长度字段前置器)
+                            //    指定用 4 个字节来存放长度。
+                            //    当您 write(String) 时,它会等 StringEncoder 把 String 转成 ByteBuf (数据体)后,
+                            //    自动计算这个 ByteBuf 的长度,并把长度(4字节)加到这个 ByteBuf 的最前面。
+                            ch.pipeline().addLast(new LengthFieldPrepender(4));
+                            // 2. 【编码】StringEncoder (字符串编码器)
+                            //    将您在 Handler 中 write 的 String 按 UTF-8 编码成 ByteBuf (数据体)
+                            ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
+                            // 【入站】
+                            // 3. 您的业务 Handler
+                            //    它收到的 msg 就是 StringDecoder 解码后的完整 JSON 字符串
                             ch.pipeline().addLast(new NettyServerHandler());
                         }
                     });

+ 3 - 2
src/main/java/com/qlm/netty/handler/NettyServerHandler.java

@@ -15,6 +15,7 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.timeout.IdleStateEvent;
 import io.netty.util.ReferenceCountUtil;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -330,8 +331,8 @@ public class NettyServerHandler extends ChannelInboundHandlerAdapter {
         Record deviceDetailDb = new Record();
         deviceDetailDb.set("device_id", deviceId);
         deviceDetailDb.set("product_sku", data.getProductSku());
-        deviceDetailDb.set("cpu_usage", data.getCpuUsage());
-        deviceDetailDb.set("memory_usage", data.getMemoryUsage());
+        deviceDetailDb.set("cpu_usage", StringUtils.isNotBlank(data.getCpuUsage())?data.getCpuUsage():"0.00");
+        deviceDetailDb.set("memory_usage", StringUtils.isNotBlank(data.getMemoryUsage())?data.getMemoryUsage():"0.00");
         deviceDetailDb.set("disk_free", data.getDiskFreeSpace());
         deviceDetailDb.set("current_task_quantity", data.getProductionTaskCount());
         deviceDetailDb.set("current_quantity", data.getProductionTaskNum());

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

@@ -41,7 +41,7 @@ public interface IJxsService {
      */
     JxsDto convertRecordToDto(com.jfinal.plugin.activerecord.Record record);
 
-    ApiResponse importJxs(List<JxsImportDto> importList, String username);
+    ApiResponse importJxs(List<JxsImportDto> importList, String username,String contextPath);
 
     List<JxsExportDto> exportJxsList(String jxsName, String code, Integer status);
 }

+ 32 - 9
src/main/java/com/qlm/service/impl/JxsServiceImpl.java

@@ -2,6 +2,7 @@ package com.qlm.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateUtil;
+import com.jfinal.kit.PathKit;
 import com.jfinal.kit.StrKit;
 import com.qlm.common.ApiResponse;
 import com.qlm.common.PageResult;
@@ -16,6 +17,8 @@ import com.qlm.view.core.AdminView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -272,7 +275,7 @@ public class JxsServiceImpl implements IJxsService {
     }
 
     @Override
-    public ApiResponse importJxs(List<JxsImportDto> importList, String username) {
+    public ApiResponse importJxs(List<JxsImportDto> importList, String username,String contextPath) {
         List<JxsImportDto> errorList = new ArrayList<>();
         int successCount = 0;
         ApiResponse response = ApiResponse.success();
@@ -388,15 +391,35 @@ public class JxsServiceImpl implements IJxsService {
         if (!errorList.isEmpty()) {
             record.set("errorCount", errorList.size());
             try {
-                String errorFileName = "item_import_error_" + System.currentTimeMillis() + ".xlsx";
-                InputStream errorInputStream = EasyExcelUtil.export(errorList, "导入错误数据", JxsImportDto.class);
-                // 上传到OSS
-                String ossUrl = OssUtil.upload(errorInputStream, errorFileName);
-                // 添加到响应
-                record.set("errorFileUrl", ossUrl);
+                // 1. 生成一个唯一的文件名,避免冲突
+                String uniqueFileName = "import_jxs_error" + UUID.randomUUID() + ".xlsx";
+
+                // 2. 定义临时文件存储路径 (例如:/tomcat/webapps/your_project/temp_files/)
+                //    确保这个目录存在并且应用有写入权限
+                String tempDir = PathKit.getWebRootPath() + "/import_error_files/";
+                File dir = new File(tempDir);
+                if (!dir.exists()) {
+                    dir.mkdirs();
+                }
+                File errorFile = new File(tempDir + uniqueFileName);
+
+                // 3. 使用 EasyExcel 生成文件流并写入到本地文件
+                try (InputStream errorInputStream = EasyExcelUtil.export(errorList, "导入错误数据", JxsImportDto.class);
+                     FileOutputStream fos = new FileOutputStream(errorFile)) {
+
+                    byte[] buffer = new byte[1024];
+                    int len;
+                    while ((len = errorInputStream.read(buffer)) != -1) {
+                        fos.write(buffer, 0, len);
+                    }
+                }
+
+                String downloadUrl =contextPath+"/jxsNew/downloadErrorFile?fileId=" + uniqueFileName;
+                record.set("errorFileUrl", downloadUrl);
+
             } catch (Exception e) {
-                logger.error("导出错误数据并上传到OSS异常:", e);
-                record.set("errorMsg", "导出错误数据并上传到OSS异常:" + e.getMessage());
+                logger.error("生成导入错误文件异常:", e);
+                record.set("errorMsg", "生成导入错误文件异常:" + e.getMessage());
             }
         }
         response.setData(record);

+ 108 - 29
src/main/webapp/page/jinzai/jxsNew.jsp

@@ -704,6 +704,7 @@
 
     // 在initTable()函数之后添加以下代码
 
+    // 导出数据功能实现
     // 导出数据功能实现
     function exportData() {
         // 显示加载中提示
@@ -714,44 +715,122 @@
         // 获取查询条件
         const jxsCode = $.trim($("#jxsCode").val());
         const jxsName = $.trim($("#jxsName").val());
+        const status = $("#status").val(); // 假设你有一个ID为status的查询条件
 
         // 构建查询参数
         const params = {
-            jxsCode: jxsCode,
-            jxsName: jxsName
+            jxsName: jxsName,
+            code: jxsCode, // 注意:后端代码里用的是 code, 而不是 jxsCode
+            status: status
         };
 
-        // 发送导出请求
-        $.ajax({
-            url: '${ctx}/jxsNew/exportJxsList',
-            type: 'POST',
-            data: JSON.stringify(params),
-            contentType: 'application/json',
-            success: function (res) {
-                // 关闭加载提示
-                layer.close(loadingIndex);
+        // 使用 fetch API 发送请求,因为它可以处理二进制文件流
+        fetch('${ctx}/jxsNew/exportJxsList', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify(params)
+        })
+            .then(response => {
+                // 首先检查响应是否成功
+                if (!response.ok) {
+                    // 如果服务器返回了错误(例如,没有数据),它会是JSON格式
+                    // 我们需要解析这个JSON来获取错误信息
+                    return response.json().then(err => {
+                        throw new Error(err.msg || '导出失败');
+                    });
+                }
 
-                if (res.code === 0) {
-                    // 获取导出URL并打开
-                    let exportUrl = res.data;
-                    if (exportUrl) {
-                        // 使用新窗口打开URL进行下载
-                        window.open(exportUrl, '_blank');
-                        layer.msg('导出成功,请在新窗口中查看下载文件', {icon: 1});
-                    } else {
-                        layer.msg('未获取到有效的导出URL', {icon: 5});
+                // 从响应头中尝试获取文件名
+                const disposition = response.headers.get('Content-Disposition');
+                let fileName = '经销商列表.xlsx'; // 默认文件名
+                if (disposition && disposition.indexOf('attachment') !== -1) {
+                    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
+                    const matches = filenameRegex.exec(disposition);
+                    if (matches != null && matches[1]) {
+                        fileName = decodeURI(matches[1].replace(/['"]/g, ''));
                     }
-                } else {
-                    layer.msg(res.msg || '导出失败', {icon: 5});
                 }
-            },
-            error: function () {
-                // 关闭加载提示
+
+                // 将响应体转换为Blob对象(二进制文件)
+                return response.blob().then(blob => ({ blob, fileName }));
+            })
+            .then(({ blob, fileName }) => {
+                // 创建一个指向Blob的URL
+                const url = window.URL.createObjectURL(blob);
+
+                // 创建一个隐藏的<a>标签用于触发下载
+                const a = document.createElement('a');
+                a.style.display = 'none';
+                a.href = url;
+                a.download = fileName; // 设置下载的文件名
+
+                // 将<a>标签添加到页面中,模拟点击,然后移除
+                document.body.appendChild(a);
+                a.click();
+
+                // 清理
+                window.URL.revokeObjectURL(url);
+                document.body.removeChild(a);
+
+                // 关闭加载提示并显示成功信息
                 layer.close(loadingIndex);
-                layer.msg('导出请求失败', {icon: 5});
-            }
-        });
+                layer.msg('导出成功,请检查你的下载内容', { icon: 1 });
+            })
+            .catch(error => {
+                // 捕获任何在过程中发生的错误
+                layer.close(loadingIndex);
+                layer.msg(error.message || '导出请求失败', { icon: 5 });
+            });
     }
+    <%--function exportData() {--%>
+    <%--    // 显示加载中提示--%>
+    <%--    var loadingIndex = layer.load(1, {--%>
+    <%--        shade: [0.2, '#000']--%>
+    <%--    });--%>
+
+    <%--    // 获取查询条件--%>
+    <%--    const jxsCode = $.trim($("#jxsCode").val());--%>
+    <%--    const jxsName = $.trim($("#jxsName").val());--%>
+
+    <%--    // 构建查询参数--%>
+    <%--    const params = {--%>
+    <%--        jxsCode: jxsCode,--%>
+    <%--        jxsName: jxsName--%>
+    <%--    };--%>
+
+    <%--    // 发送导出请求--%>
+    <%--    $.ajax({--%>
+    <%--        url: '${ctx}/jxsNew/exportJxsList',--%>
+    <%--        type: 'POST',--%>
+    <%--        data: JSON.stringify(params),--%>
+    <%--        contentType: 'application/json',--%>
+    <%--        success: function (res) {--%>
+    <%--            // 关闭加载提示--%>
+    <%--            layer.close(loadingIndex);--%>
+
+    <%--            if (res.code === 0) {--%>
+    <%--                // 获取导出URL并打开--%>
+    <%--                let exportUrl = res.data;--%>
+    <%--                if (exportUrl) {--%>
+    <%--                    // 使用新窗口打开URL进行下载--%>
+    <%--                    window.open(exportUrl, '_blank');--%>
+    <%--                    layer.msg('导出成功,请在新窗口中查看下载文件', {icon: 1});--%>
+    <%--                } else {--%>
+    <%--                    layer.msg('未获取到有效的导出URL', {icon: 5});--%>
+    <%--                }--%>
+    <%--            } else {--%>
+    <%--                layer.msg(res.msg || '导出失败', {icon: 5});--%>
+    <%--            }--%>
+    <%--        },--%>
+    <%--        error: function () {--%>
+    <%--            // 关闭加载提示--%>
+    <%--            layer.close(loadingIndex);--%>
+    <%--            layer.msg('导出请求失败', {icon: 5});--%>
+    <%--        }--%>
+    <%--    });--%>
+    <%--}--%>
 
     // 导入功能实现
     function importData() {
@@ -762,7 +841,7 @@
                     <i class="fa fa-cloud-upload" style="font-size: 50px; color: #ccc;"></i>
                 </div>
                 <p style="margin-bottom: 10px;">将文件拖到此处,或 <span style="color: #0066cc; cursor: pointer;" id="clickUpload">点击上传</span></p>
-                <p style="font-size: 12px; color: #999;">仅允许导入xls、xlsx格式文件。<a href="https://hyscancode.oss-cn-hangzhou.aliyuncs.com/jinzaiimport/%E7%BB%8F%E9%94%80%E5%95%86%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx" style="color: #0066cc;">下载模板</a></p>
+                <p style="font-size: 12px; color: #999;">仅允许导入xls、xlsx格式文件。<a href="${ctx}/templates/经销商导入模板.xlsx" style="color: #0066cc;">下载模板</a></p>
                 <div id="selectedFileName" style="margin-top: 15px; font-size: 12px; color: #333; display: none; justify-content: center; align-items: center;"></div>
                 <input type="file" id="fileUpload" accept=".xlsx, .xls" style="display: none;">
             </div>

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

@@ -454,22 +454,6 @@
                 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: '操作',

+ 1 - 2
src/main/webapp/page/jinzai/production_task_upload.jsp

@@ -135,8 +135,7 @@
                                 <select id="wmsStatus" class="form-control" style="width: 120px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
                                     <option value="">全部</option>
                                     <option value="1">成功</option>
-                                    <option value="2">失败</option>
-                                    <option value="3">重试中</option>
+                                    <option value="2">上传中</option>
                                 </select>
                                 <button type="button" id="searchBtn" class="btn btn-success"
                                         onclick="search();return false;" style="margin-right: 5px; margin-bottom: 5px;">

+ 159 - 165
src/main/webapp/page/jinzai/reprint_record.jsp

@@ -8,32 +8,38 @@
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>补打信息记录</title>
-    <meta name="keywords" content="补打记录,信息管理">
+    <meta name="keywords" content="补打信息,记录">
     <meta name="description" content="补打信息记录管理页面">
-    <!-- 引入Bootstrap CSS -->
-    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
-    <!-- 引入Font Awesome图标 -->
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
+    <link href="${ctx}/css/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="${ctx}/css/font-awesome.css?v=4.4.0" rel="stylesheet">
     <link href="${ctx}/css/bootstrap-select.min.css" rel="stylesheet">
     <link href="${ctx}/css/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
     <link href="${ctx}/css/animate.css" rel="stylesheet">
     <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;
+        .form-inline-flex {
+            display: flex;
+            flex-wrap: wrap;
+            align-items: center;
+            gap: 5px;
         }
 
-        .form-container {
-            padding: 20px;
-            border-radius: 8px;
-            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
-            background-color: #fff;
+        @media (max-width: 768px) {
+            .form-inline-flex {
+                flex-direction: column;
+                align-items: stretch;
+            }
+
+            .form-inline-flex .form-control,
+            .form-inline-flex .btn,
+            .form-inline-flex .selectpicker {
+                width: 100% !important;
+                margin-right: 0 !important;
+            }
         }
     </style>
 </head>
@@ -54,30 +60,27 @@
                 <div class="ibox-content">
                     <div class="row row-lg mb-4">
                         <div class="col-lg-12">
-                            <form id="searchForm" class="form-horizontal">
-                                <div class="row mb-3">
-                                    <div class="col-sm-3">
-                                        <label for="reprintTime" class="form-label">补打时间</label>
-                                        <input type="date" class="form-control" id="reprintTime" name="reprintTime">
-                                    </div>
-                                    <div class="col-sm-3">
-                                        <label for="factoryName" class="form-label">工厂名称</label>
-                                        <input type="text" class="form-control" id="factoryName" name="factoryName" placeholder="请输入工厂名称">
-                                    </div>
-                                    <div class="col-sm-3">
-                                        <label for="productName" class="form-label">产品名称</label>
-                                        <input type="text" class="form-control" id="productName" name="productName" placeholder="请输入产品名称">
-                                    </div>
-                                    <div class="col-sm-3" style="display: flex; align-items: flex-end;">
-                                        <button type="button" id="searchBtn" class="btn btn-success ms-2" onclick="search();return false;">
-                                            <i class="fas fa-search"></i> 查询
-                                        </button>
-                                        <button type="button" id="resetBtn" class="btn btn-secondary ms-2" onclick="reset();return false;">
-                                            <i class="fas fa-sync-alt"></i> 重置
-                                        </button>
-                                    </div>
+                            <div class="form-inline-flex">
+                                <input type="date" class="form-control" id="reprintTime" name="reprintTime" style="width: 180px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+
+                                <div style="width: 200px; flex-shrink: 0; margin-right: 10px; margin-bottom: 5px;">
+                                    <select id="factoryId" class="form-control selectpicker" data-live-search="true" title="请选择工厂">
+                                    </select>
                                 </div>
-                            </form>
+
+                                <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"
+                                        onclick="reset();return false;" style="margin-right: 5px; margin-bottom: 5px;">重置
+                                </button>
+                            </div>
                         </div>
                     </div>
                     <div class="row row-lg mt-3">
@@ -91,12 +94,9 @@
     </div>
 </div>
 
-<!-- 全局js -->
-<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
-<!-- 引入Bootstrap JS -->
-<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
+<script src="${ctx}/js/jquery.min.js?v=2.1.4"></script>
+<script src="${ctx}/js/bootstrap.min.js?v=3.3.6"></script>
 <script src="${ctx}/js/bootstrap-select.min.js"></script>
-<!-- jQuery Validation plugin javascript-->
 <script src="${ctx}/js/plugins/validate/jquery.validate.min.js"></script>
 <script src="${ctx}/js/plugins/validate/messages_zh.min.js"></script>
 <script src="${ctx}/js/plugins/bootstrap-table/bootstrap-table.min.js"></script>
@@ -105,127 +105,184 @@
 <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>
-<!-- 自定义js -->
 <script src="${ctx}/js/content.js?v=1.0.0"></script>
 </body>
 <script>
     var table = null;
-
     $(document).ready(function () {
         table = $('#table').bootstrapTable("destroy");
         initTable();
+        $('.selectpicker').selectpicker({
+            liveSearch: true,
+            size: 5,
+            actionsBox: true,
+            selectedTextFormat: 'count > 2'
+        });
+        initFactorySelect();
+        initProductSelect();
+        // Bind reset button click event
+        $('#resetBtn').on('click', function () {
+            reset();
+        });
     });
 
     function queryParams(param) {
-        // 获取查询参数
+        // Get search parameters
         let reprintTime = $.trim($("#reprintTime").val());
-        let factoryName = $.trim($("#factoryName").val());
-        let productName = $.trim($("#productName").val());
+        let factoryId = $.trim($("#factoryId").val());
+        let productId = $.trim($("#productId").val());
 
         if (reprintTime) {
             param['reprintTime'] = reprintTime;
         }
-        if (factoryName) {
-            param['factoryName'] = factoryName;
+        if (factoryId) {
+            param['factoryId'] = factoryId;
         }
-        if (productName) {
-            param['productName'] = productName;
+        if (productId) {
+            param['sku'] = productId;
         }
 
         return param;
     }
 
     function search() {
-        table = $('#table').bootstrapTable("destroy");
-        initTable();
+        table = $('#table').bootstrapTable("refresh");
     }
 
     function reset() {
-        $("#searchForm")[0].reset();
-        table = $('#table').bootstrapTable("destroy");
-        initTable();
+        // Reset form
+        $("#reprintTime").val("");
+        $("#factoryId").selectpicker('val', '');
+        $("#productId").selectpicker('val', '');
+
+        // Refresh table
+        table = $('#table').bootstrapTable("refresh");
     }
 
-    function initTable() {
-        // 模拟数据
-        var mockData = generateMockData();
+    // Load factory data (reused from original file)
+    function initFactorySelect() {
+        $.ajax({
+            url: '${ctx}/lineProduct/getFactoryList', // Assuming same endpoint
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#factoryId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#factoryId').empty();
+                if (res.data && res.data.length) {
+                    res.data.forEach(item => {
+                        $('#factoryId').append('<option value="' + item.id + '">' + item.factory_name + '</option>');
+                    });
+                }
+                $('#factoryId').selectpicker('refresh');
+            },
+            error: function (xhr) {
+                $('#factoryId').empty().append('<option value="">加载失败</option>');
+                $('#factoryId').selectpicker('refresh');
+                layer.msg('获取工厂数据失败: ' + xhr.statusText);
+            },
+            complete: function () {
+                $('#factoryId').prop('disabled', false).selectpicker('refresh');
+            }
+        });
+    }
 
+    // Load product data (reused from original file)
+    function initProductSelect() {
+        $.ajax({
+            url: '${ctx}/itemNew/getItemAll', // Assuming same endpoint
+            type: 'POST',
+            dataType: 'json',
+            beforeSend: function () {
+                $('#productId').prop('disabled', true).selectpicker('refresh');
+            },
+            success: function (res) {
+                $('#productId').empty();
+                if (res.data && res.data.length) {
+                    res.data.forEach(item => {
+                        $('#productId').append('<option value="' + item.sku + '">' + 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 initTable() {
         table = $('#table').bootstrapTable({
-            data: mockData,
+            url: '${ctx}/prodBatch/getReprintRecord', // ** IMPORTANT: Use the new data endpoint **
             method: 'get',
             sortable: true,
-            toolbar: '#toolbar',    //工具按钮用哪个容器
-            striped: true,      //是否显示行间隔色
-            cache: false,      //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
-            pagination: true,     //是否显示分页(*)
-            pageNumber: 1,      //初始化加载第一页,默认第一页
-            pageSize: 10,      //每页的记录行数(*)
-            pageList: [10, 25, 50, 100],  //可供选择的每页的行数(*)
-            queryParamsType: '', //默认值为 'limit' ,在默认情况下 传给服务端的参数为:offset,limit,sort
-            // 设置为 ''  在这种情况下传给服务器的参数为:pageSize,pageNumber
-            queryParams: queryParams,//前端调用服务时,会默认传递上边提到的参数,如果需要添加自定义参数,可以自定义一个函数返回请求参数
-            sidePagination: "client",   //客户端分页
+            striped: true,
+            cache: false,
+            pagination: true,
+            pageNumber: 1,
+            pageSize: 10,
+            pageList: [10, 25, 50, 100],
+            queryParamsType: '',
+            queryParams: queryParams,
+            sidePagination: "server",
             strictSearch: false,
-            minimumCountColumns: 2,    //最少允许的列数
-            clickToSelect: true,    //是否启用点击选中行
+            minimumCountColumns: 2,
+            clickToSelect: true,
             searchOnEnterKey: true,
-            idField: "id",
-            paginationDetailHAlign: 'right', // 将分页详细信息放在右边
+            idField: "id", // Assuming there is a unique ID field
+            responseHandler: function (res) {
+                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: 'productionOrder',
-                title: '生产单号',
-                align: 'center',
-                width: "10%"
+                field: 'taskOrderNo',
+                title: '任务单号',
+                align: 'center'
             }, {
                 field: 'factoryName',
                 title: '工厂名称',
-                align: 'center',
-                width: "10%"
+                align: 'center'
             }, {
-                field: 'workshop',
+                field: 'workshopName',
                 title: '生产车间',
-                align: 'center',
-                width: "10%"
+                align: 'center'
             }, {
                 field: 'lineName',
                 title: '产线',
-                align: 'center',
-                width: "10%"
+                align: 'center'
             }, {
                 field: 'productName',
                 title: '产品名称',
-                align: 'center',
-                width: "10%"
-            }, {
-                field: 'reprintCount',
-                title: '补打次数',
-                align: 'center',
-                width: "8%"
+                align: 'center'
             }, {
                 field: 'boxCode',
                 title: '箱码',
-                align: 'center',
-                width: "12%"
+                align: 'center'
             }, {
                 field: 'palletCode',
                 title: '托码',
-                align: 'center',
-                width: "12%"
+                align: 'center'
             }, {
                 field: 'reprintTime',
                 title: '补打时间',
-                align: 'center',
-                width: "13%"
+                align: 'center'
             }],
             onLoadSuccess: function (data) {
                 console.log("数据加载成功", data);
@@ -234,69 +291,6 @@
                 layer.msg('数据加载失败', {icon: 2});
             }
         });
-
-        // 注释:当后端接口准备好后,可以替换为以下代码调用真实接口
-        /*
-        table = $('#table').bootstrapTable({
-            method: 'get',
-            sortable: true,
-            toolbar: '#toolbar',
-            striped: true,
-            cache: false,
-            pagination: true,
-            pageNumber: 1,
-            pageSize: 10,
-            pageList: [10, 25, 50, 100],
-            url: '${ctx}/reprint/list', // 后端接口URL
-            queryParamsType: '',
-            queryParams: queryParams,
-            sidePagination: "server",
-            strictSearch: false,
-            minimumCountColumns: 2,
-            clickToSelect: true,
-            searchOnEnterKey: true,
-            idField: "id",
-            responseHandler: function (res) {
-                return {
-                    total: res.total,
-                    rows: res.records
-                };
-            },
-            columns: [// 与上面相同的列定义...]
-        });
-        */
-    }
-
-    // 生成模拟数据
-    function generateMockData() {
-        var factories = ['金华工厂', '杭州工厂', '上海工厂', '苏州工厂', '广州工厂'];
-        var workshops = ['第一车间', '第二车间', '第三车间', '第四车间', '第五车间'];
-        var lines = ['A线', 'B线', 'C线', 'D线', 'E线'];
-        var products = ['食品A', '食品B', '食品C', '食品D', '食品E', '食品F', '食品G'];
-        var mockData = [];
-
-        for (var i = 1; i <= 50; i++) {
-            var date = new Date();
-            date.setDate(date.getDate() - Math.floor(Math.random() * 30));
-            var formattedDate = date.toISOString().split('T')[0];
-            var time = Math.floor(Math.random() * 24) + ':' + (Math.floor(Math.random() * 60) < 10 ? '0' : '') + Math.floor(Math.random() * 60);
-            var reprintTime = formattedDate + ' ' + time;
-
-            mockData.push({
-                id: i,
-                productionOrder: 'PO' + Date.now().toString().slice(-6) + i,
-                factoryName: factories[Math.floor(Math.random() * factories.length)],
-                workshop: workshops[Math.floor(Math.random() * workshops.length)],
-                lineName: lines[Math.floor(Math.random() * lines.length)],
-                productName: products[Math.floor(Math.random() * products.length)],
-                reprintCount: Math.floor(Math.random() * 5) + 1, // 1-5次
-                boxCode: 'BOX' + Math.floor(Math.random() * 1000000000),
-                palletCode: 'PAL' + Math.floor(Math.random() * 1000000000),
-                reprintTime: reprintTime
-            });
-        }
-
-        return mockData;
     }
 </script>
 </html>

BIN
src/main/webapp/templates/经销商导入模板.xlsx