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 0000000..38adca4 Binary files /dev/null and b/resources/template/物资导入模板.xlsx differ 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; + } + } + }