From 996432feb8501216a3127b74140a81bbebdfcb84 Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Tue, 23 Jun 2026 16:08:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProductionPlanController.java | 21 ++ .../mapper/ProductionPlanMapper.java | 2 + .../service/ProductionPlanService.java | 9 + .../impl/ProductionPlanServiceImpl.java | 284 ++++++++++++++++++ .../longchuang/ProductionPlanMapper.xml | 79 +++++ 5 files changed, 395 insertions(+) diff --git a/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java b/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java index 82d6129..799c3c5 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java @@ -14,6 +14,7 @@ import com.xujie.sys.modules.longchuang.data.ProductionPlanReportLogMyQueryData; import com.xujie.sys.modules.longchuang.data.ProductionPlanReportLogQueryData; import com.xujie.sys.modules.longchuang.data.ProductionPlanRoleQueryData; import com.xujie.sys.modules.longchuang.service.ProductionPlanService; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ModelAttribute; @@ -38,6 +39,11 @@ public class ProductionPlanController { return R.ok().put("page", page); } + @PostMapping("/homeLiftOrder/export") + public void exportHomeLiftOrder(@RequestBody(required = false) ProductionPlanOrderQueryData data, HttpServletResponse response) { + productionPlanService.exportHomeLiftOrder(data, response); + } + @PostMapping("/homeLiftOrder/save") public R saveHomeLiftOrder(@RequestBody ProductionPlanOrderSaveData data) { productionPlanService.saveHomeLiftOrder(data); @@ -68,6 +74,11 @@ public class ProductionPlanController { return R.ok().put("page", page); } + @PostMapping("/cableCopTask/export") + public void exportCableCopTask(@RequestBody(required = false) ProductionPlanOrderQueryData data, HttpServletResponse response) { + productionPlanService.exportCableCopTask(data, response); + } + @PostMapping("/cableCopTask/save") public R saveCableCopTask(@RequestBody ProductionPlanOrderSaveData data) { productionPlanService.saveCableCopTask(data); @@ -98,6 +109,11 @@ public class ProductionPlanController { return R.ok().put("page", page); } + @PostMapping("/renovationOrder/export") + public void exportRenovationOrder(@RequestBody(required = false) ProductionPlanOrderQueryData data, HttpServletResponse response) { + productionPlanService.exportRenovationOrder(data, response); + } + @PostMapping("/renovationOrder/save") public R saveRenovationOrder(@RequestBody ProductionPlanOrderSaveData data) { productionPlanService.saveRenovationOrder(data); @@ -128,6 +144,11 @@ public class ProductionPlanController { return R.ok().put("page", page); } + @PostMapping("/machiningTask/export") + public void exportMachiningTask(@RequestBody(required = false) ProductionPlanOrderQueryData data, HttpServletResponse response) { + productionPlanService.exportMachiningTask(data, response); + } + @PostMapping("/machiningTask/save") public R saveMachiningTask(@RequestBody ProductionPlanOrderSaveData data) { productionPlanService.saveMachiningTask(data); diff --git a/src/main/java/com/xujie/sys/modules/longchuang/mapper/ProductionPlanMapper.java b/src/main/java/com/xujie/sys/modules/longchuang/mapper/ProductionPlanMapper.java index 9fc6af4..4a1a390 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/mapper/ProductionPlanMapper.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/mapper/ProductionPlanMapper.java @@ -25,6 +25,8 @@ import java.util.List; public interface ProductionPlanMapper { IPage queryOrderPage(Page page, @Param("query") ProductionPlanOrderQueryData data); + List queryOrderList(@Param("query") ProductionPlanOrderQueryData data); + IPage queryWorkReportOrderPage(Page page, @Param("query") ProductionPlanOrderQueryData data, @Param("userId") Long userId); diff --git a/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java b/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java index 58e101a..c1662ea 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java @@ -16,6 +16,7 @@ import com.xujie.sys.modules.longchuang.data.ProductionPlanReportLogQueryData; import com.xujie.sys.modules.longchuang.data.ProductionPlanRoleData; import com.xujie.sys.modules.longchuang.data.ProductionPlanRoleQueryData; import com.xujie.sys.modules.longchuang.data.ProductionPlanUserOptionData; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -24,6 +25,8 @@ import java.util.Map; public interface ProductionPlanService { PageUtils queryHomeLiftOrderPage(ProductionPlanOrderQueryData data); + void exportHomeLiftOrder(ProductionPlanOrderQueryData data, HttpServletResponse response); + void saveHomeLiftOrder(ProductionPlanOrderSaveData data); void finishHomeLiftOrder(String orderNo); @@ -34,6 +37,8 @@ public interface ProductionPlanService { PageUtils queryCableCopTaskPage(ProductionPlanOrderQueryData data); + void exportCableCopTask(ProductionPlanOrderQueryData data, HttpServletResponse response); + void saveCableCopTask(ProductionPlanOrderSaveData data); void finishCableCopTask(String orderNo); @@ -44,6 +49,8 @@ public interface ProductionPlanService { PageUtils queryRenovationOrderPage(ProductionPlanOrderQueryData data); + void exportRenovationOrder(ProductionPlanOrderQueryData data, HttpServletResponse response); + void saveRenovationOrder(ProductionPlanOrderSaveData data); void finishRenovationOrder(String orderNo); @@ -54,6 +61,8 @@ public interface ProductionPlanService { PageUtils queryMachiningTaskPage(ProductionPlanOrderQueryData data); + void exportMachiningTask(ProductionPlanOrderQueryData data, HttpServletResponse response); + void saveMachiningTask(ProductionPlanOrderSaveData data); void finishMachiningTask(String orderNo); diff --git a/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java b/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java index d4f66f0..01764cc 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java @@ -26,12 +26,27 @@ import com.xujie.sys.modules.longchuang.service.ProductionPlanService; import com.xujie.sys.modules.oss.entity.SysOssEntity; import com.xujie.sys.modules.oss.service.SysOssService; import com.xujie.sys.modules.sys.entity.SysUserEntity; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.bytedeco.ffmpeg.global.avcodec; import org.bytedeco.ffmpeg.global.avutil; import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.FFmpegFrameRecorder; import org.bytedeco.javacv.Frame; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.shiro.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -41,7 +56,10 @@ import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.io.File; +import java.io.IOException; import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -56,6 +74,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.BiFunction; import java.util.stream.Collectors; @Slf4j @@ -110,6 +129,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { return queryOrderPage(data, ORDER_TYPE_HOME_LIFT); } + @Override + public void exportHomeLiftOrder(ProductionPlanOrderQueryData data, HttpServletResponse response) { + exportOrderExcel(data, ORDER_TYPE_HOME_LIFT, "家用梯销售订单", "家用梯销售订单", response); + } + @Override @Transactional(rollbackFor = Exception.class) public void saveHomeLiftOrder(ProductionPlanOrderSaveData data) { @@ -139,6 +163,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { return queryOrderPage(data, ORDER_TYPE_CABLE_COP); } + @Override + public void exportCableCopTask(ProductionPlanOrderQueryData data, HttpServletResponse response) { + exportOrderExcel(data, ORDER_TYPE_CABLE_COP, "线缆COP任务单", "线缆COP任务单", response); + } + @Override @Transactional(rollbackFor = Exception.class) public void saveCableCopTask(ProductionPlanOrderSaveData data) { @@ -168,6 +197,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { return queryOrderPage(data, ORDER_TYPE_RENOVATION); } + @Override + public void exportRenovationOrder(ProductionPlanOrderQueryData data, HttpServletResponse response) { + exportOrderExcel(data, ORDER_TYPE_RENOVATION, "改造项目订单", "改造项目订单", response); + } + @Override @Transactional(rollbackFor = Exception.class) public void saveRenovationOrder(ProductionPlanOrderSaveData data) { @@ -197,6 +231,11 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { return queryOrderPage(data, ORDER_TYPE_MACHINING); } + @Override + public void exportMachiningTask(ProductionPlanOrderQueryData data, HttpServletResponse response) { + exportOrderExcel(data, ORDER_TYPE_MACHINING, "机加工任务单", "机加工任务单", response); + } + @Override @Transactional(rollbackFor = Exception.class) public void saveMachiningTask(ProductionPlanOrderSaveData data) { @@ -578,6 +617,230 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { return new PageUtils(page); } + private void exportOrderExcel(ProductionPlanOrderQueryData data, + String orderType, + String fileNamePrefix, + String sheetName, + HttpServletResponse response) { + List orderRows = queryOrderListForExport(data, orderType); + List baseColumns = buildExportBaseColumns(orderType); + List headerList = new ArrayList<>(); + for (OrderExportColumn baseColumn : baseColumns) { + headerList.add(baseColumn.getHeader()); + } + headerList.add("节点名称"); + headerList.add("报工状态"); + headerList.add("报工时间"); + headerList.add("负责人"); + headerList.add("报工备注"); + + try (XSSFWorkbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet(sheetName); + CellStyle headerStyle = createExportHeaderStyle(workbook); + CellStyle contentStyle = createExportContentStyle(workbook); + + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headerList.size(); i++) { + createExportCell(headerRow, i, headerList.get(i), headerStyle); + sheet.setColumnWidth(i, resolveExportColumnWidth(headerList.get(i)) * 256); + } + sheet.createFreezePane(0, 1); + + int rowIndex = 1; + int orderIndex = 1; + for (ProductionPlanOrderRowData order : orderRows) { + List nodeList = normalizeExportNodeList(order == null ? null : order.getNodeList()); + int startRowIndex = rowIndex; + for (ProductionPlanOrderNodeData node : nodeList) { + Row dataRow = sheet.createRow(rowIndex++); + int colIndex = 0; + for (OrderExportColumn baseColumn : baseColumns) { + createExportCell(dataRow, colIndex++, baseColumn.resolve(orderIndex, order), contentStyle); + } + createExportCell(dataRow, colIndex++, safeExportText(node == null ? null : node.getNodeName()), contentStyle); + createExportCell(dataRow, colIndex++, safeExportText(node == null ? null : node.getStatus()), contentStyle); + createExportCell(dataRow, colIndex++, safeExportText(node == null ? null : node.getLastReportTime()), contentStyle); + createExportCell(dataRow, colIndex++, safeExportText(node == null ? null : node.getAssigneeUserName()), contentStyle); + createExportCell(dataRow, colIndex, safeExportText(node == null ? null : node.getLastReportRemark()), contentStyle); + } + int endRowIndex = rowIndex - 1; + if (endRowIndex > startRowIndex) { + for (int colIndex = 0; colIndex < baseColumns.size(); colIndex++) { + sheet.addMergedRegion(new CellRangeAddress(startRowIndex, endRowIndex, colIndex, colIndex)); + } + } + orderIndex++; + } + + writeExportWorkbook(response, workbook, fileNamePrefix); + } catch (IOException e) { + log.error("生产计划导出失败,orderType={}, error={}", orderType, e.getMessage(), e); + throw new XJException("导出失败,请稍后重试"); + } + } + + private List queryOrderListForExport(ProductionPlanOrderQueryData data, String orderType) { + ProductionPlanOrderQueryData query = data == null ? new ProductionPlanOrderQueryData() : data; + query.setOrderType(orderType); + List rows = productionPlanMapper.queryOrderList(query); + List result = rows == null ? new ArrayList<>() : new ArrayList<>(rows); + attachNodeList(result); + return result; + } + + private List buildExportBaseColumns(String orderType) { + List columns = new ArrayList<>(); + if (ORDER_TYPE_HOME_LIFT.equals(orderType) || ORDER_TYPE_RENOVATION.equals(orderType)) { + columns.add(new OrderExportColumn("项目号", (index, row) -> safeExportText(row == null ? null : row.getProjectNo()))); + columns.add(new OrderExportColumn("型号", (index, row) -> safeExportText(row == null ? null : row.getModelNo()))); + columns.add(new OrderExportColumn("颜色", (index, row) -> safeExportText(row == null ? null : row.getColor()))); + columns.add(new OrderExportColumn("层数", (index, row) -> safeExportText(row == null ? null : row.getFloorCount()))); + columns.add(new OrderExportColumn("特殊要求", (index, row) -> safeExportText(row == null ? null : row.getSpecialRequirement()))); + columns.add(new OrderExportColumn("计划发货日期", (index, row) -> safeExportText(row == null ? null : row.getPlanDeliveryDate()))); + columns.add(new OrderExportColumn("当前节点", (index, row) -> safeExportText(row == null ? null : row.getCurrentNode()))); + columns.add(new OrderExportColumn("节点进度", (index, row) -> formatExportNodeProgress(row))); + columns.add(new OrderExportColumn("状态", (index, row) -> safeExportText(row == null ? null : row.getStatus()))); + columns.add(new OrderExportColumn("完工日期", (index, row) -> safeExportText(row == null ? null : row.getFinishDate()))); + return columns; + } + if (ORDER_TYPE_CABLE_COP.equals(orderType)) { + columns.add(new OrderExportColumn("项目号", (index, row) -> safeExportText(row == null ? null : row.getProjectNo()))); + columns.add(new OrderExportColumn("任务类型", (index, row) -> safeExportText(row == null ? null : row.getTaskType()))); + columns.add(new OrderExportColumn("类别", (index, row) -> safeExportText(row == null ? null : row.getSourceProjectNo()))); + columns.add(new OrderExportColumn("物料名称", (index, row) -> safeExportText(row == null ? null : row.getMaterialName()))); + columns.add(new OrderExportColumn("规格", (index, row) -> safeExportText(row == null ? null : row.getMaterialSpec()))); + columns.add(new OrderExportColumn("累计报工数量", (index, row) -> formatExportDecimal(row == null ? null : row.getReportQty()))); + columns.add(new OrderExportColumn("计划完成日期", (index, row) -> safeExportText(row == null ? null : row.getPlanFinishDate()))); + columns.add(new OrderExportColumn("当前节点", (index, row) -> safeExportText(row == null ? null : row.getCurrentNode()))); + columns.add(new OrderExportColumn("节点进度", (index, row) -> formatExportNodeProgress(row))); + columns.add(new OrderExportColumn("状态", (index, row) -> safeExportText(row == null ? null : row.getStatus()))); + columns.add(new OrderExportColumn("完工日期", (index, row) -> safeExportText(row == null ? null : row.getFinishDate()))); + return columns; + } + columns.add(new OrderExportColumn("物料号", (index, row) -> safeExportText(row == null ? null : row.getModelNo()))); + columns.add(new OrderExportColumn("物料名称", (index, row) -> safeExportText(row == null ? null : row.getMaterialName()))); + columns.add(new OrderExportColumn("规格", (index, row) -> safeExportText(row == null ? null : row.getMaterialSpec()))); + columns.add(new OrderExportColumn("计划数量", (index, row) -> formatExportDecimal(row == null ? null : row.getTaskQty()))); + columns.add(new OrderExportColumn("累计报工数量", (index, row) -> formatExportDecimal(row == null ? null : row.getReportQty()))); + columns.add(new OrderExportColumn("计划完工日期", (index, row) -> safeExportText(row == null ? null : row.getPlanFinishDate()))); + columns.add(new OrderExportColumn("当前节点", (index, row) -> safeExportText(row == null ? null : row.getCurrentNode()))); + columns.add(new OrderExportColumn("节点进度", (index, row) -> formatExportNodeProgress(row))); + columns.add(new OrderExportColumn("状态", (index, row) -> safeExportText(row == null ? null : row.getStatus()))); + columns.add(new OrderExportColumn("完工日期", (index, row) -> safeExportText(row == null ? null : row.getFinishDate()))); + return columns; + } + + private List normalizeExportNodeList(List nodeList) { + if (nodeList == null || nodeList.isEmpty()) { + ProductionPlanOrderNodeData emptyNode = new ProductionPlanOrderNodeData(); + emptyNode.setNodeName("-"); + emptyNode.setStatus("-"); + emptyNode.setLastReportTime(""); + emptyNode.setAssigneeUserName(""); + emptyNode.setLastReportRemark(""); + return Collections.singletonList(emptyNode); + } + return nodeList; + } + + private String formatExportNodeProgress(ProductionPlanOrderRowData row) { + if (row == null) { + return "0/0"; + } + Integer doneCount = row.getNodeDoneCount(); + Integer totalCount = row.getNodeTotalCount(); + int done = doneCount == null ? 0 : doneCount; + int total = totalCount == null ? 0 : totalCount; + if (total <= 0 && row.getNodeList() != null) { + total = row.getNodeList().size(); + done = (int) row.getNodeList().stream().filter(item -> STATUS_DONE.equals(item.getStatus())).count(); + } + return done + "/" + total; + } + + private String formatExportDecimal(BigDecimal value) { + if (value == null) { + return ""; + } + BigDecimal normalized = value.stripTrailingZeros(); + if (normalized.scale() < 0) { + normalized = normalized.setScale(0); + } + return normalized.toPlainString(); + } + + private String safeExportText(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private CellStyle createExportHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + applyExportBorder(style); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + return style; + } + + private CellStyle createExportContentStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + applyExportBorder(style); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setWrapText(true); + return style; + } + + private void applyExportBorder(CellStyle style) { + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + } + + private void createExportCell(Row row, int colIndex, String value, CellStyle style) { + Cell cell = row.createCell(colIndex); + cell.setCellStyle(style); + cell.setCellValue(value); + } + + private int resolveExportColumnWidth(String header) { + if (!StringUtils.hasText(header)) { + return 14; + } + if (header.contains("备注") || header.contains("特殊要求")) { + return 30; + } + if (header.contains("物料名称") || header.contains("规格") || header.contains("负责人")) { + return 20; + } + if (header.contains("节点名称")) { + return 18; + } + if (header.contains("日期") || header.contains("时间")) { + return 18; + } + return 14; + } + + private void writeExportWorkbook(HttpServletResponse response, XSSFWorkbook workbook, String fileNamePrefix) throws IOException { + String fileName = fileNamePrefix + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx"; + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20"); + response.reset(); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + encodedFileName); + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); + try (ServletOutputStream outputStream = response.getOutputStream()) { + workbook.write(outputStream); + outputStream.flush(); + } + } + private void attachNodeList(List rows) { if (rows == null || rows.isEmpty()) { return; @@ -1630,4 +1893,25 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { } return NODE_REPORT_MODE_PARALLEL; } + + private static class OrderExportColumn { + private final String header; + private final BiFunction valueResolver; + + private OrderExportColumn(String header, BiFunction valueResolver) { + this.header = header; + this.valueResolver = valueResolver; + } + + private String getHeader() { + return header; + } + + private String resolve(int rowIndex, ProductionPlanOrderRowData row) { + if (valueResolver == null) { + return ""; + } + return valueResolver.apply(rowIndex, row); + } + } } diff --git a/src/main/resources/mapper/longchuang/ProductionPlanMapper.xml b/src/main/resources/mapper/longchuang/ProductionPlanMapper.xml index cd5ea0b..21e61a9 100644 --- a/src/main/resources/mapper/longchuang/ProductionPlanMapper.xml +++ b/src/main/resources/mapper/longchuang/ProductionPlanMapper.xml @@ -82,6 +82,85 @@ isnull(a.plan_delivery_date, a.plan_finish_date) asc + +