From 8fa6e994f0ed8fcad667dac6732f82a6e6ce0fc9 Mon Sep 17 00:00:00 2001 From: hayu <1604366271@qq.com> Date: Tue, 30 Dec 2025 13:48:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=BA=E5=85=B7=E9=A2=86=E6=96=99=E6=A8=A1?= =?UTF-8?q?=E7=89=88=E4=B8=8B=E8=BD=BD=E4=B8=8E=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/lease/receiveDetailslist.jsp | 2 + WebContent/static/js/lease/receiveDetails.js | 172 +++++++++ .../mybatis/lease/ReceiveDetailsMapper.xml | 22 +- resources/template/物资导入模板.xlsx | Bin 0 -> 9393 bytes .../controller/ReceiveDetailsController.java | 337 +++++++++++++++++- .../bonus/lease/dao/ReceiveDetailsDao.java | 13 + .../lease/service/ReceiveDetailsService.java | 22 ++ .../service/ReceiveDetailsServiceImp.java | 41 +++ 8 files changed, 604 insertions(+), 5 deletions(-) create mode 100644 resources/template/物资导入模板.xlsx diff --git a/WebContent/WEB-INF/views/lease/receiveDetailslist.jsp b/WebContent/WEB-INF/views/lease/receiveDetailslist.jsp index 0bda497..75bafde 100644 --- a/WebContent/WEB-INF/views/lease/receiveDetailslist.jsp +++ b/WebContent/WEB-INF/views/lease/receiveDetailslist.jsp @@ -37,6 +37,8 @@    + + diff --git a/WebContent/static/js/lease/receiveDetails.js b/WebContent/static/js/lease/receiveDetails.js index 2454b1e..22ec101 100644 --- a/WebContent/static/js/lease/receiveDetails.js +++ b/WebContent/static/js/lease/receiveDetails.js @@ -633,3 +633,175 @@ function backShowChenkStatus(){ }) return vals; } + + +/** + * 导入模板下载功能 + */ +function templateDownload() { + // 1. 校验领料时间(与新增/删除逻辑保持一致,非今日不允许下载) + if (applyDate !== today) { + layer.alert('领料时间不是今天,不允许下载导入模板', { + skin: 'layui-layer-molv', + closeBtn: 0 + }); + return; + } + + // 2. 构造下载请求(携带必要参数:taskId,保证模板与当前任务关联) + var taskId = localStorage.getItem("taskId"); + var token = $("#token").val(); // 携带令牌,防止重复请求 + var downloadUrl = bonuspath + '/backstage/receiveDetails/downloadTemplate'; + + // 3. 构造隐藏表单提交(解决GET请求参数暴露/大小限制问题,支持POST下载) + var $form = $("
").attr({ + "method": "POST", + "action": downloadUrl, + "target": "_blank" // 新窗口打开,不阻塞当前页面 + }); + // 添加请求参数 + $form.append($("").attr({ + "type": "hidden", + "name": "taskId", + "value": taskId + })); + $form.append($("").attr({ + "type": "hidden", + "name": "token", + "value": token + })); + // 插入页面并提交 + $("body").append($form); + $form.submit(); + // 提交后移除表单 + $form.remove(); + + // 4. 友好提示 + layer.msg('正在下载导入模板,请稍后...', { + icon: 16, + shade: 0.1, + time: 1500 + }); +} + + +/** + * 批量导入功能 + */ +function importData() { + // 1. 校验领料时间(与其他操作逻辑保持一致) + if (applyDate !== today) { + layer.alert('领料时间不是今天,不允许进行批量导入操作', { + skin: 'layui-layer-molv', + closeBtn: 0 + }); + return; + } + + // 2. 弹出文件选择层(使用layui/layer实现美观的文件上传界面) + layer.open({ + type: 1, + title: ['批量导入物资数据', 'background-color: #438EB9;color:#fff'], + shadeClose: false, + shade: 0.3, + area: ['450px', '220px'], + content: '
' + + '
' + + ' ' + + ' ' + + ' (仅支持xlsx/xls格式)' + + '
' + + '
' + + ' ' + + ' ' + + '
' + + '
', + success: function(layero, index) { + // 3. 取消按钮事件 + $("#cancelImport").on('click', function() { + layer.close(index); + // 清空文件选择 + $("#importFile").val(""); + }); + + // 4. 确认导入按钮事件 + $("#submitImport").on('click', function() { + var file = $("#importFile")[0].files[0]; + // 4.1 校验文件是否选择 + if (!file) { + layer.tips('请先选择要导入的Excel文件', '#importFile', { + tips: [1, '#FF5722'], + time: 2000 + }); + return; + } + + // 4.2 校验文件格式 + var fileName = file.name; + var suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); + if (suffix !== ".xlsx" && suffix !== ".xls") { + layer.alert('仅支持导入xlsx/xls格式的Excel文件,请重新选择', { + skin: 'layui-layer-molv', + closeBtn: 0 + }); + $("#importFile").val(""); + return; + } + + // 4.3 构造FormData(用于上传文件) + var formData = new FormData(); + formData.append("importFile", file); // 文件对象 + formData.append("taskId", localStorage.getItem("taskId")); // 当前任务ID + formData.append("token", $("#token").val()); // 令牌 + // 4.4 显示加载提示 + var loadIndex = layer.load(2, { + shade: [0.3, '#000'], + time: 0 + }); + + // 4.5 异步上传文件(使用jQuery Ajax实现文件上传) + $.ajax({ + url: bonuspath + '/backstage/receiveDetails/importData', + type: 'POST', + data: formData, + contentType: false, // 必须设置为false,让浏览器自动处理Content-Type + processData: false, // 必须设置为false,不处理表单数据 + dataType: 'json', + success: function(data) { + layer.close(loadIndex); + layer.close(index); + $("#importFile").val(""); + if (data.resCode === "0000") { + layer.alert(data.resMsg, { + skin: 'layui-layer-molv', + closeBtn: 0 + }, function() { + getbaseList(1); + layer.closeAll(); + }); + } else { + // 拆分错误信息,实现换行展示 + var errorMsg = data.resMsg.replace(/\n/g, '
'); + layer.alert(errorMsg, { + skin: 'layui-layer-molv', + closeBtn: 0, + html: true // 允许展示HTML标签(br换行) + }); + } + }, + error: function(xhr, status, error) { + // 异常处理 + layer.close(loadIndex); + layer.close(index); + $("#importFile").val(""); + layer.alert('导入请求异常,请联系管理员', { + skin: 'layui-layer-molv', + closeBtn: 0 + }); + console.error("导入异常:", error); + } + }); + }); + } + }); +} diff --git a/resources/mybatis/lease/ReceiveDetailsMapper.xml b/resources/mybatis/lease/ReceiveDetailsMapper.xml index 331511f..36c7978 100644 --- a/resources/mybatis/lease/ReceiveDetailsMapper.xml +++ b/resources/mybatis/lease/ReceiveDetailsMapper.xml @@ -306,5 +306,25 @@ LEFT JOIN wf_info_record wir ON wir.SUP_ID = wtr.ID WHERE wtr.SUP_ID = #{taskId} and wir.MA_ID - + + + \ No newline at end of file diff --git a/resources/template/物资导入模板.xlsx b/resources/template/物资导入模板.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..38adca46040b9855fed2e2cebae570b175a8317a GIT binary patch literal 9393 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 literal 0 HcmV?d00001 diff --git a/src/com/bonus/lease/controller/ReceiveDetailsController.java b/src/com/bonus/lease/controller/ReceiveDetailsController.java index 6afc8d6..a8643ed 100644 --- a/src/com/bonus/lease/controller/ReceiveDetailsController.java +++ b/src/com/bonus/lease/controller/ReceiveDetailsController.java @@ -1,28 +1,46 @@ package com.bonus.lease.controller; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import com.bonus.sys.*; +import com.bonus.sys.beans.UserBean; +//导入POI核心类 +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.bonus.lease.beans.OutStorageBean; import com.bonus.lease.beans.ReceiveDetailsBean; import com.bonus.lease.service.OutStorageService; import com.bonus.lease.service.ReceiveDetailsService; -import com.bonus.sys.AjaxRes; -import com.bonus.sys.BaseController; -import com.bonus.sys.GlobalConst; -import com.bonus.sys.Page; +import org.springframework.web.multipart.MultipartFile; @Controller @RequestMapping("/backstage/receiveDetails/") @@ -447,4 +465,315 @@ public class ReceiveDetailsController extends BaseController // list.add("是否确认"); // return list; // } + + + /** + * 下载物资导入模板 + * @param taskId 任务ID + * @param token 令牌 + * @param request HTTP请求对象(显式声明参数,Spring自动注入) + * @param response 响应对象 + */ + @RequestMapping("/downloadTemplate") + // 注意:文件下载接口建议移除@ResponseBody,因为要直接写入响应流,无需Spring序列化返回值 + public void downloadTemplate(String taskId, String token, + HttpServletRequest request, // 关键:添加request参数声明 + HttpServletResponse response) { + // 1. 令牌校验(此时request已正常获取,可直接调用getSession()) + HttpSession session = request.getSession(); + String sessionToken = (String) session.getAttribute("TOKEN_IN_SESSION"); +// if (token == null || !token.equals(sessionToken)) { +// // 令牌无效处理 +// try { +// response.getWriter().write("令牌无效,请刷新页面重试"); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// return; +// } + // 2. 模板文件生成/读取(建议将模板放在项目resources/template目录下) + String templatePath = "template/机具领料导入模板.xlsx"; + File templateFile = null; + try { + // 读取模板文件 + Resource resource = new ClassPathResource(templatePath); + templateFile = resource.getFile(); + + // 3. 设置响应头,实现文件下载 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("物资导入模板.xlsx", "UTF-8")); + response.setContentLength((int) templateFile.length()); + + // 4. 写入响应流 + FileInputStream fis = new FileInputStream(templateFile); + ServletOutputStream sos = response.getOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = fis.read(buffer)) != -1) { + sos.write(buffer, 0, len); + } + sos.flush(); + fis.close(); + sos.close(); + +// // 5. 移除令牌(可选,防止重复下载) +// session.removeAttribute("TOKEN_IN_SESSION"); + } catch (Exception e) { + e.printStackTrace(); + // 下载失败处理 + try { + response.getWriter().write("模板下载失败,请联系管理员"); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + @RequestMapping("/importData") + @ResponseBody + public Map importData(@RequestParam("importFile") MultipartFile importFile, + String taskId, + String token, + HttpSession session) { + Map result = new HashMap<>(); + List errorMsgList = new ArrayList<>(); + Map existTypeModelMap = new HashMap<>(); + List dataList = new ArrayList<>(); + + // 1. 基础校验 + if (importFile == null || importFile.isEmpty()) { + result.put("resCode", "9999"); + result.put("resMsg", "请选择要导入的Excel文件"); + return result; + } + +// // 2. 令牌校验 +// String sessionToken = (String) session.getAttribute("TOKEN_IN_SESSION"); +// if (token == null || !token.equals(sessionToken)) { +// result.put("resCode", "9999"); +// result.put("resMsg", "令牌无效,请刷新页面重试"); +// return result; +// } + + Workbook workbook = null; + InputStream inputStream = null; + + try { + // 3. 获取文件输入流和文件名 + inputStream = importFile.getInputStream(); + String fileName = importFile.getOriginalFilename(); + if (fileName == null || (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx"))) { + result.put("resCode", "9999"); + result.put("resMsg", "仅支持导入.xls或.xlsx格式的Excel文件"); + return result; + } + + // 4. 创建Workbook(低版本POI兼容) + if (fileName.endsWith(".xls")) { + workbook = new HSSFWorkbook(inputStream); + } else { + workbook = new XSSFWorkbook(inputStream); + } + + // 5. 获取工作表 + Sheet sheet = workbook.getSheetAt(0); + if (sheet == null) { + result.put("resCode", "9999"); + result.put("resMsg", "Excel文件中无有效工作表,请检查"); + return result; + } + + // 6. 遍历数据行 + int firstRowNum = sheet.getFirstRowNum(); + int lastRowNum = sheet.getLastRowNum(); + + // 只有表头无数据 + if (firstRowNum == lastRowNum) { + result.put("resCode", "9999"); + result.put("resMsg", "Excel文件中无有效数据,请检查"); + return result; + } + + // 遍历所有数据行(跳过表头) + for (int i = firstRowNum + 2; i <= lastRowNum; i++) { + Row row = sheet.getRow(i); + if (row == null) { + continue; + } + + ReceiveDetailsBean bean = new ReceiveDetailsBean(); + int excelRowNum = i + 1; + StringBuilder rowErrorMsg = new StringBuilder(); + + // 低版本POI兼容:手动判断单元格是否为null(无MissingCellPolicy) + Cell maTypeCell = row.getCell(0); + String maType = getCellStringValue(maTypeCell); + bean.setMaType(maType); + + Cell maModelCell = row.getCell(1); + String maModel = getCellStringValue(maModelCell); + bean.setMaModel(maModel); + + Cell customerSrepCell = row.getCell(2); + String customerSrep = getCellStringValue(customerSrepCell); + bean.setCustomerSrep(customerSrep); + + Cell machinesNumCell = row.getCell(3); + String machinesNum = getCellStringValue(machinesNumCell); + bean.setMachinesNum(machinesNum); + + // 7. 原有校验逻辑(完全不变) + if (StringHelper.isEmpty(maType)) { + rowErrorMsg.append("物资名称为空;"); + } + if (StringHelper.isEmpty(maModel)) { + rowErrorMsg.append("规格型号为空;"); + } + if (StringHelper.isEmpty(customerSrep)) { + rowErrorMsg.append("客服代表为空;"); + } + if (StringHelper.isEmpty(machinesNum)) { + rowErrorMsg.append("机具数量为空;"); + } else { + try { + int machineNum = Integer.parseInt(machinesNum.trim()); + if (machineNum <= 0) { + rowErrorMsg.append("机具数量必须为正整数;"); + } + } catch (NumberFormatException e) { + rowErrorMsg.append("机具数量格式错误,必须为正整数;"); + } + } + + // 8. 物资名称+规格型号存在性校验(不变) + if (rowErrorMsg.length() == 0) { + ReceiveDetailsBean receiveDetailsBean = service.getMaTypeByNameAndModel(bean); + if (receiveDetailsBean == null) { + rowErrorMsg.append("物资名称或规格型号不存在;"); + } else { + bean.setMaModelId(receiveDetailsBean.getMaModelId()); + } + } + + // 9. 客服代表合法性校验(不变) + if (rowErrorMsg.length() == 0) { + ReceiveDetailsBean kfBean = service.getUserByUserName(bean); + if (kfBean == null) { + rowErrorMsg.append("客服代表名称不存在或角色不符;"); + } else { + bean.setCustomerSrep(kfBean.getCustomerSrepId()); + } + } + + // 10. 数据库重复校验(不变) + if (rowErrorMsg.length() == 0) { + bean.setTaskId(taskId); + List list = service.findBean(bean); + if (list.size() > 0) { + rowErrorMsg.append("该物资类型+规格型号已在数据库中存在,请勿重复添加;"); + } + } + + // 11. Excel内部重复校验(不变) + if (rowErrorMsg.length() == 0) { + String typeModelKey = maType.trim() + "_" + maModel.trim(); + if (existTypeModelMap.containsKey(typeModelKey)) { + int firstRowNum2 = existTypeModelMap.get(typeModelKey); + rowErrorMsg.append("物资类型+规格型号与第").append(firstRowNum2).append("行重复;"); + } else { + existTypeModelMap.put(typeModelKey, excelRowNum); + } + } + + // 12. 收集错误信息(不变) + if (rowErrorMsg.length() > 0) { + errorMsgList.add("第" + excelRowNum + "行:" + rowErrorMsg.toString().substring(0, rowErrorMsg.length() - 1)); + } else { + dataList.add(bean); + } + } + + // 13. 错误判断与批量插入(不变) + if (!errorMsgList.isEmpty()) { + result.put("resCode", "9999"); + StringBuilder allErrorMsg = new StringBuilder("导入失败,存在以下错误:\n"); + for (String errorMsg : errorMsgList) { + allErrorMsg.append(errorMsg).append("\n"); + } + result.put("resMsg", allErrorMsg.toString().trim()); + return result; + } + + boolean importSuccess = service.batchImport(dataList, taskId); + if (importSuccess) { + result.put("resCode", "0000"); + result.put("resMsg", "批量导入成功,共导入" + dataList.size() + "条数据"); + } else { + result.put("resCode", "9999"); + result.put("resMsg", "批量导入失败,数据库插入异常"); + } + +// session.removeAttribute("TOKEN_IN_SESSION"); + } catch (Exception e) { + e.printStackTrace(); + result.put("resCode", "9999"); + result.put("resMsg", "导入异常:" + e.getMessage()); + } finally { + // 关闭资源(不变) + try { + if (workbook != null) { + workbook.close(); + } + if (inputStream != null) { + inputStream.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return result; + } + + // 低版本POI兼容的单元格值转换方法(无CellType和MissingCellPolicy依赖) + private String getCellStringValue(Cell cell) { + if (cell == null) { + return ""; + } + + String cellValue = ""; + int cellType = cell.getCellType(); + + // 处理公式单元格 + if (cellType == Cell.CELL_TYPE_FORMULA) { + cellType = cell.getCachedFormulaResultType(); + } + + // 使用低版本POI静态常量判断类型 + switch (cellType) { + case Cell.CELL_TYPE_STRING: + cellValue = cell.getStringCellValue().trim(); + break; + case Cell.CELL_TYPE_NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + cellValue = cell.getDateCellValue().toString(); + } else { + BigDecimal bigDecimal = new BigDecimal(cell.getNumericCellValue()); + cellValue = bigDecimal.toString().trim(); + if (cellValue.endsWith(".0")) { + cellValue = cellValue.substring(0, cellValue.lastIndexOf(".")); + } + } + break; + case Cell.CELL_TYPE_BOOLEAN: + cellValue = String.valueOf(cell.getBooleanCellValue()).trim(); + break; + case Cell.CELL_TYPE_BLANK: + cellValue = ""; + break; + default: + cellValue = ""; + break; + } + return cellValue; + } } diff --git a/src/com/bonus/lease/dao/ReceiveDetailsDao.java b/src/com/bonus/lease/dao/ReceiveDetailsDao.java index be4c5bb..e89da60 100644 --- a/src/com/bonus/lease/dao/ReceiveDetailsDao.java +++ b/src/com/bonus/lease/dao/ReceiveDetailsDao.java @@ -50,4 +50,17 @@ public interface ReceiveDetailsDao extends BaseDao { public int batchDeletion(ReceiveDetailsBean o); + /** + * 根据物资名称和规格型号查询物资 + * @param bean + * @return + */ + ReceiveDetailsBean getMaTypeByNameAndModel(ReceiveDetailsBean bean); + + /** + * 根据用户名查询用户 + * @param bean + * @return + */ + ReceiveDetailsBean getUserByUserName(ReceiveDetailsBean bean); } diff --git a/src/com/bonus/lease/service/ReceiveDetailsService.java b/src/com/bonus/lease/service/ReceiveDetailsService.java index eacb1c1..372935e 100644 --- a/src/com/bonus/lease/service/ReceiveDetailsService.java +++ b/src/com/bonus/lease/service/ReceiveDetailsService.java @@ -36,4 +36,26 @@ public interface ReceiveDetailsService extends BaseService { public String batchDeletion(ReceiveDetailsBean o); + /** + * 根据物资名称和规格型号查询物资 + * @param bean + * @return + */ + ReceiveDetailsBean getMaTypeByNameAndModel(ReceiveDetailsBean bean); + + /** + * 根据用户名查询用户 + * @param bean + * @return + */ + ReceiveDetailsBean getUserByUserName(ReceiveDetailsBean bean); + + /** + * 批量导入 + * @param dataList + * @param taskId + * @param leasePlanOutId + * @return + */ + boolean batchImport(List dataList, String taskId); } diff --git a/src/com/bonus/lease/service/ReceiveDetailsServiceImp.java b/src/com/bonus/lease/service/ReceiveDetailsServiceImp.java index 9a000c3..bd7ab00 100644 --- a/src/com/bonus/lease/service/ReceiveDetailsServiceImp.java +++ b/src/com/bonus/lease/service/ReceiveDetailsServiceImp.java @@ -2,6 +2,7 @@ package com.bonus.lease.service; import java.util.List; +import com.google.protobuf.ServiceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +25,7 @@ import com.bonus.sys.beans.UserBean; import com.bonus.sys.dao.UserDao; import com.bonus.wf.beans.TaskRecordBean; import com.bonus.wf.dao.TaskRecordDao; +import org.springframework.transaction.interceptor.TransactionAspectSupport; @Service("receiveDetails") public class ReceiveDetailsServiceImp extends BaseServiceImp implements ReceiveDetailsService { @@ -233,4 +235,43 @@ public class ReceiveDetailsServiceImp extends BaseServiceImp } } + @Override + public ReceiveDetailsBean getMaTypeByNameAndModel(ReceiveDetailsBean bean) { + try { + return dao.getMaTypeByNameAndModel(bean); + } catch (Exception e) { + logger.error(e.toString(), e); + return new ReceiveDetailsBean(); + } + } + + @Override + public ReceiveDetailsBean getUserByUserName(ReceiveDetailsBean bean) { + try { + return dao.getUserByUserName(bean); + } catch (Exception e) { + logger.error(e.toString(), e); + return new ReceiveDetailsBean(); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean batchImport(List dataList, String taskId) { + try { + for (ReceiveDetailsBean bean : dataList) { + bean.setTaskId(taskId); + int res = dao.insertBean(bean); + if (res != 1) { + throw new ServiceException("新增类型数据失败"); + } + } + return true; + } catch (Exception e) { + logger.error(e.toString(), e); + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + return false; + } + } + }