diff --git a/bonus-business/src/main/java/com/bonus/business/controller/tool/MonthPlanExcelExporter.java b/bonus-business/src/main/java/com/bonus/business/controller/tool/MonthPlanExcelExporter.java index 5d268bb..a307d4e 100644 --- a/bonus-business/src/main/java/com/bonus/business/controller/tool/MonthPlanExcelExporter.java +++ b/bonus-business/src/main/java/com/bonus/business/controller/tool/MonthPlanExcelExporter.java @@ -11,9 +11,10 @@ import java.net.URLEncoder; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; /** - * 运检人员月度安排导出工具类 + * 运检人员月度安排导出工具类(关联人员安排表) */ public class MonthPlanExcelExporter { @@ -34,9 +35,9 @@ public class MonthPlanExcelExporter { private static final int MAX_COL_WIDTH = 30; // 最大列宽(字符) /** - * 导出运检人员月度安排Excel + * 导出运检人员月度安排Excel(关联人员安排表) * @param response 响应对象 - * @param dataList 人员月度安排数据 + * @param dataList 人员月度安排数据(已关联人员安排表) * @param year 年份 * @param month 月份 * @param sheetName sheet名称 @@ -59,14 +60,13 @@ public class MonthPlanExcelExporter { // 2. 创建Workbook和Sheet Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet(sheetName); - // 移除错误的 sheet.setWrapText(true); 该方法不存在!自动换行通过 CellStyle 设置 // 3. 构建样式 CellStyle titleStyle = buildTitleStyle(workbook); // 标题样式(含自动换行) CellStyle headerStyle = buildHeaderStyle(workbook); // 表头样式(含自动换行) CellStyle contentStyle = buildContentStyle(workbook); // 内容样式(含自动换行) - // 4. 转换原始数据为行映射(按运检站+姓名分组) + // 4. 转换原始数据为行映射(按运检站+姓名分组,按人员安排表的day填充) List> rowDataList = convertToDailyMapList(finalData, year, month); // 5. 构建表头(标题行 + 列头行) @@ -76,10 +76,10 @@ public class MonthPlanExcelExporter { int dataStartRow = headerRowCount; fillDataRows(sheet, contentStyle, rowDataList, dataStartRow, daysInMonth); - // 7. 合并日期列相同内容的连续单元格 + // 7. 合并日期列相同内容的连续单元格(基于人员安排表的day) mergeSameContentCells(sheet, rowDataList, dataStartRow, daysInMonth); - // 8. 核心:自适应列宽(替代固定列宽) + // 8. 自适应列宽 autoAdjustColumnWidths(sheet, daysInMonth); // 9. 响应输出 @@ -97,7 +97,7 @@ public class MonthPlanExcelExporter { } /** - * 转换原始数据为按行映射的结构(按运检站+姓名分组) + * 转换原始数据为按行映射的结构(按运检站+姓名分组,基于人员安排表的day) */ private static List> convertToDailyMapList( List dataList, int year, int month) { @@ -109,44 +109,51 @@ public class MonthPlanExcelExporter { String key = item.getInspectionStationName() + "_" + item.getName(); // 初始化行数据 - rowMap.computeIfAbsent(key, k -> { - Map row = new LinkedHashMap<>(); - // 固定列(空值兜底,避免null) - row.put("运检站", item.getInspectionStationName() == null ? "" : item.getInspectionStationName()); - row.put("姓名", item.getName() == null ? "" : item.getName()); - row.put("性别", item.getSex() == null ? "" : item.getSex()); - row.put("岗位", item.getPositionName() == null ? "" : item.getPositionName()); - row.put("人员性质", item.getPersonnelNatureName() == null ? "" : item.getPersonnelNatureName()); - row.put("分类", item.getPersonnelClassificationName() == null ? "" : item.getPersonnelClassificationName()); + Map row = rowMap.computeIfAbsent(key, k -> { + Map newRow = new LinkedHashMap<>(); + // 固定列(空值兜底) + newRow.put("运检站", item.getInspectionStationName() == null ? "" : item.getInspectionStationName()); + newRow.put("姓名", item.getName() == null ? "" : item.getName()); + newRow.put("性别", item.getSex() == null ? "" : item.getSex()); + newRow.put("岗位", item.getPositionName() == null ? "" : item.getPositionName()); + newRow.put("人员性质", item.getPersonnelNatureName() == null ? "" : item.getPersonnelNatureName()); + newRow.put("分类", item.getPersonnelClassificationName() == null ? "" : item.getPersonnelClassificationName()); - // 初始化日期列 + // 初始化日期列(默认空) Map dailyContent = new HashMap<>(); for (int d = 1; d <= daysInMonth; d++) { dailyContent.put(d, ""); } - row.put("dailyContent", dailyContent); - return row; + newRow.put("dailyContent", dailyContent); + return newRow; }); - // 填充日期内容(空值校验:避免日期解析失败) - Map dailyContent = (Map) rowMap.get(key).get("dailyContent"); - if (item.getPlannedStartTime() == null || item.getPlannedEndTime() == null) { + // 填充人员安排表的day对应的内容(核心修改) + Map dailyContent = (Map) row.get("dailyContent"); + if (item.getArrangeDays() == null || item.getArrangeDays().isEmpty()) { continue; } - LocalDate start = LocalDate.parse(item.getPlannedStartTime(), DATE_FORMATTER); - LocalDate end = LocalDate.parse(item.getPlannedEndTime(), DATE_FORMATTER); - for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) { - if (date.getYear() == year && date.getMonthValue() == month) { - int day = date.getDayOfMonth(); - String currentContent = dailyContent.get(day); - String newContent = item.getWorkContent() == null ? "" : item.getWorkContent(); + // 遍历该人员的所有安排日期,填充工作内容 + for (String dayStr : item.getArrangeDays()) { + try { + LocalDate arrangeDay = LocalDate.parse(dayStr, DATE_FORMATTER); + // 仅处理当月日期 + if (arrangeDay.getYear() == year && arrangeDay.getMonthValue() == month) { + int day = arrangeDay.getDayOfMonth(); + String currentContent = dailyContent.get(day); + String newContent = item.getWorkContent() == null ? "" : item.getWorkContent(); - if (currentContent == null || currentContent.trim().isEmpty()) { - dailyContent.put(day, newContent); - } else { - dailyContent.put(day, currentContent + "\n" + newContent); + // 多段工作内容换行拼接 + if (currentContent == null || currentContent.trim().isEmpty()) { + dailyContent.put(day, newContent); + } else { + dailyContent.put(day, currentContent + "\n" + newContent); + } } + } catch (Exception e) { + // 日期格式错误跳过 + continue; } } } @@ -159,16 +166,13 @@ public class MonthPlanExcelExporter { */ private static CellStyle buildTitleStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); - // 字体配置 Font font = workbook.createFont(); font.setBold(true); font.setFontName("宋体"); font.setFontHeightInPoints((short) 16); style.setFont(font); - // 对齐方式(垂直+水平居中) style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); - // 边框 style.setBorderTop(BorderStyle.THIN); style.setBorderBottom(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN); @@ -177,7 +181,6 @@ public class MonthPlanExcelExporter { style.setBottomBorderColor(IndexedColors.BLACK.getIndex()); style.setLeftBorderColor(IndexedColors.BLACK.getIndex()); style.setRightBorderColor(IndexedColors.BLACK.getIndex()); - // 核心:单元格自动换行(解决长内容遮挡) style.setWrapText(true); return style; } @@ -202,7 +205,7 @@ public class MonthPlanExcelExporter { style.setBottomBorderColor(IndexedColors.BLACK.getIndex()); style.setLeftBorderColor(IndexedColors.BLACK.getIndex()); style.setRightBorderColor(IndexedColors.BLACK.getIndex()); - style.setWrapText(true); // 自动换行 + style.setWrapText(true); return style; } @@ -215,7 +218,6 @@ public class MonthPlanExcelExporter { font.setFontName("宋体"); font.setFontHeightInPoints((short) 11); style.setFont(font); - // 水平+垂直居中(解决内容上下/左右遮挡) style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setBorderTop(BorderStyle.THIN); @@ -226,7 +228,7 @@ public class MonthPlanExcelExporter { style.setBottomBorderColor(IndexedColors.BLACK.getIndex()); style.setLeftBorderColor(IndexedColors.BLACK.getIndex()); style.setRightBorderColor(IndexedColors.BLACK.getIndex()); - style.setWrapText(true); // 自动换行 + style.setWrapText(true); return style; } @@ -241,7 +243,7 @@ public class MonthPlanExcelExporter { // 行0:标题行(合并所有列) Row titleRow = sheet.createRow(0); - titleRow.setHeightInPoints(40); // 加高标题行(适配换行) + titleRow.setHeightInPoints(40); Cell titleCell = titleRow.createCell(0); titleCell.setCellValue(title); titleCell.setCellStyle(titleStyle); @@ -249,7 +251,7 @@ public class MonthPlanExcelExporter { // 行1:列头行 Row headerRow = sheet.createRow(1); - headerRow.setHeightInPoints(30); // 加高表头行 + headerRow.setHeightInPoints(30); // 固定列头 createCell(headerRow, COL_SERIAL, "序号", headerStyle); createCell(headerRow, COL_STATION, "运检站", headerStyle); @@ -264,24 +266,24 @@ public class MonthPlanExcelExporter { createCell(headerRow, colIndex, d + "日", headerStyle); } - return 2; // 表头总行数:标题行(0) + 列头行(1) + return 2; } /** - * 填充数据行(加高行高,适配自动换行) + * 填充数据行 */ private static void fillDataRows(Sheet sheet, CellStyle contentStyle, List> rowDataList, int startRowNum, int daysInMonth) { - int serialNum = 1; // 序号 + int serialNum = 1; for (int i = 0; i < rowDataList.size(); i++) { int rowNum = startRowNum + i; Row row = sheet.createRow(rowNum); - row.setHeightInPoints(35); // 加高数据行(解决换行内容遮挡) + row.setHeightInPoints(35); Map rowData = rowDataList.get(i); - // 填充固定列(空值兜底) + // 填充固定列 createCell(row, COL_SERIAL, serialNum++, contentStyle); createCell(row, COL_STATION, rowData.get("运检站"), contentStyle); createCell(row, COL_NAME, rowData.get("姓名"), contentStyle); @@ -290,7 +292,7 @@ public class MonthPlanExcelExporter { createCell(row, COL_NATURE, rowData.get("人员性质"), contentStyle); createCell(row, COL_CLASSIFY, rowData.get("分类"), contentStyle); - // 填充日期列 + // 填充日期列(仅人员安排表有数据的日期显示内容) Map dailyContent = (Map) rowData.get("dailyContent"); for (int d = 1; d <= daysInMonth; d++) { int colIndex = FIRST_DATE_COL + (d - 1); @@ -301,11 +303,10 @@ public class MonthPlanExcelExporter { } /** - * 合并日期列中相同内容的连续单元格 + * 合并日期列中相同内容的连续单元格(基于人员安排表的day) */ private static void mergeSameContentCells(Sheet sheet, List> rowDataList, int dataStartRow, int daysInMonth) { - // 遍历每一行 for (int rowIdx = 0; rowIdx < rowDataList.size(); rowIdx++) { int currentRowNum = dataStartRow + rowIdx; Map rowData = rowDataList.get(rowIdx); @@ -330,15 +331,14 @@ public class MonthPlanExcelExporter { } } - // 若有连续相同内容,合并单元格 + // 合并连续单元格 if (endDay > d) { int startCol = FIRST_DATE_COL + (d - 1); int endCol = FIRST_DATE_COL + (endDay - 1); sheet.addMergedRegion(new CellRangeAddress( - currentRowNum, currentRowNum, // 同一行 - startCol, endCol // 起始列-结束列 + currentRowNum, currentRowNum, + startCol, endCol )); - // 跳过已合并的列 d = endDay + 1; } else { d++; @@ -348,25 +348,15 @@ public class MonthPlanExcelExporter { } /** - * 核心:自适应列宽(替代固定列宽) - * 1. 自动计算每列的最大内容宽度 - * 2. 限制最小/最大宽度,避免过窄/过宽 - * 3. 兼容合并单元格的列宽计算 + * 自适应列宽 */ private static void autoAdjustColumnWidths(Sheet sheet, int daysInMonth) { - // 计算总列数:固定列(7列) + 日期列 int totalCols = FIRST_DATE_COL + daysInMonth; - // 遍历所有列,自适应宽度 for (int colIndex = 0; colIndex < totalCols; colIndex++) { - // 第一步:自动计算列宽(POI原生方法,基于列中最长内容) - // true:忽略合并单元格,仅计算真实内容(避免合并单元格导致列宽计算异常) sheet.autoSizeColumn(colIndex, true); - - // 第二步:转换列宽单位(POI的列宽单位是1/256个字符宽度) int currentWidth = sheet.getColumnWidth(colIndex) / 256; - // 第三步:限制最小/最大宽度(兜底) int targetWidth; if (currentWidth < MIN_COL_WIDTH) { targetWidth = MIN_COL_WIDTH; @@ -376,13 +366,11 @@ public class MonthPlanExcelExporter { targetWidth = currentWidth; } - // 第四步:设置最终列宽(加2个字符余量,避免中文内容紧贴边框) sheet.setColumnWidth(colIndex, (targetWidth + 2) * 256); } - // 特殊优化:序号列强制最小宽度(避免过窄) + // 特殊列宽优化 sheet.setColumnWidth(COL_SERIAL, (MIN_COL_WIDTH + 1) * 256); - // 特殊优化:运检站列放宽最大宽度(适配长名称) int stationWidth = sheet.getColumnWidth(COL_STATION) / 256; if (stationWidth > MAX_COL_WIDTH) { sheet.setColumnWidth(COL_STATION, (MAX_COL_WIDTH + 5) * 256); @@ -390,11 +378,10 @@ public class MonthPlanExcelExporter { } /** - * 创建单元格并设置值和样式(空值兜底) + * 创建单元格并设置值和样式 */ private static void createCell(Row row, int colIndex, Object value, CellStyle style) { Cell cell = row.createCell(colIndex); - // 空值兜底,避免null导致单元格无内容 Object cellValue = value == null ? "" : value; if (cellValue instanceof String) { cell.setCellValue((String) cellValue); diff --git a/bonus-business/src/main/java/com/bonus/digital/controller/MonthlyPlanController.java b/bonus-business/src/main/java/com/bonus/digital/controller/MonthlyPlanController.java index 1d0d265..1605e1c 100644 --- a/bonus-business/src/main/java/com/bonus/digital/controller/MonthlyPlanController.java +++ b/bonus-business/src/main/java/com/bonus/digital/controller/MonthlyPlanController.java @@ -135,7 +135,7 @@ public class MonthlyPlanController extends BaseController { int month = LocalDate.now().getMonthValue(); String monthlyPlan = monthlyPlanVo.getMonthlyPlan(); - if (monthlyPlan != null && monthlyPlan.matches("\\d{4}-\\d{1,2}")) { // 校验格式:yyyy-MM + if (monthlyPlan != null && monthlyPlan.matches("\\d{4}-\\d{1,2}")) { String[] yearMonthArr = monthlyPlan.split("-"); year = Integer.parseInt(yearMonthArr[0]); month = Integer.parseInt(yearMonthArr[1]); diff --git a/bonus-business/src/main/java/com/bonus/digital/dao/ExportMonthPlanPersonVo.java b/bonus-business/src/main/java/com/bonus/digital/dao/ExportMonthPlanPersonVo.java index cafebdd..2596923 100644 --- a/bonus-business/src/main/java/com/bonus/digital/dao/ExportMonthPlanPersonVo.java +++ b/bonus-business/src/main/java/com/bonus/digital/dao/ExportMonthPlanPersonVo.java @@ -3,6 +3,8 @@ package com.bonus.digital.dao; import com.bonus.common.annotation.Excel; import lombok.Data; +import java.util.List; + /** * @Author:liang.chao * @Date:2025/12/17 - 15:32 @@ -28,4 +30,5 @@ public class ExportMonthPlanPersonVo { private String personnelNatureName; @Excel(name = "分类") private String personnelClassificationName; + private List arrangeDays; } diff --git a/bonus-business/src/main/java/com/bonus/digital/dao/MonthlyPlanVo.java b/bonus-business/src/main/java/com/bonus/digital/dao/MonthlyPlanVo.java index 65320e4..4ce63f1 100644 --- a/bonus-business/src/main/java/com/bonus/digital/dao/MonthlyPlanVo.java +++ b/bonus-business/src/main/java/com/bonus/digital/dao/MonthlyPlanVo.java @@ -203,6 +203,10 @@ public class MonthlyPlanVo { */ private String keyWord; + private String arrangeDay; + + private String personnelNames; + /** * 当天时间 */ diff --git a/bonus-business/src/main/java/com/bonus/digital/mapper/MonthlyPlanMapper.java b/bonus-business/src/main/java/com/bonus/digital/mapper/MonthlyPlanMapper.java index 95a2686..e80baa8 100644 --- a/bonus-business/src/main/java/com/bonus/digital/mapper/MonthlyPlanMapper.java +++ b/bonus-business/src/main/java/com/bonus/digital/mapper/MonthlyPlanMapper.java @@ -20,6 +20,12 @@ public interface MonthlyPlanMapper { */ List getCarUseSummary(MonthlyPlanVo monthlyPlanVo); + /** + * 查询人员安排表关联数据 + * + */ + List getPlanWithPersonArrangement(MonthlyPlanVo monthlyPlanVo); + /** * 月计划汇总 */ diff --git a/bonus-business/src/main/java/com/bonus/digital/service/impl/MonthlyPlanServiceImpl.java b/bonus-business/src/main/java/com/bonus/digital/service/impl/MonthlyPlanServiceImpl.java index 4375b27..178602c 100644 --- a/bonus-business/src/main/java/com/bonus/digital/service/impl/MonthlyPlanServiceImpl.java +++ b/bonus-business/src/main/java/com/bonus/digital/service/impl/MonthlyPlanServiceImpl.java @@ -131,31 +131,64 @@ public class MonthlyPlanServiceImpl implements MonthlyPlanService { @Override public List exportMonthlyPlanPerson(MonthlyPlanVo monthlyPlanVo) { - List monthlyPlanVoList = monthlyPlanMapper.getPlanMajorListByMonth(monthlyPlanVo); - List plannedList= new ArrayList<>(); - for (MonthlyPlanVo monthlyPlanVo2 : monthlyPlanVoList) { - //获取每个月计划投入的管理人员 - List plannedIdList = Arrays.asList(monthlyPlanVo2.getPlanPersonnel().split(",")); - PersonnelVo personnelVo = new PersonnelVo(); - personnelVo.setIdList(plannedIdList); - List personnelList = personnelMapper.getPersonnelList(personnelVo); - for (PersonnelVo vo : personnelList) { - ExportMonthPlanPersonVo exportMonthPlanPersonVo = new ExportMonthPlanPersonVo(); - exportMonthPlanPersonVo.setInspectionStationName(monthlyPlanVo2.getInspectionStationName()); - exportMonthPlanPersonVo.setWorkContent(monthlyPlanVo2.getWorkContent()); - exportMonthPlanPersonVo.setPlannedStartTime(monthlyPlanVo2.getPlannedStartTime()); - exportMonthPlanPersonVo.setPlannedEndTime(monthlyPlanVo2.getPlannedEndTime()); - exportMonthPlanPersonVo.setName(vo.getName()); - exportMonthPlanPersonVo.setSex(vo.getSex()); - exportMonthPlanPersonVo.setPositionName(vo.getPositionName()); - exportMonthPlanPersonVo.setPersonnelNatureName(vo.getPersonnelNatureName()); - exportMonthPlanPersonVo.setPersonnelClassificationName(vo.getPersonnelClassificationName()); - plannedList.add(exportMonthPlanPersonVo); + // 1. 查询月度计划+人员安排表关联数据 + List planWithArrangementList = monthlyPlanMapper.getPlanWithPersonArrangement(monthlyPlanVo); + List plannedList = new ArrayList<>(); + + // 2. 按人员安排表的personnel_names拆分人员,关联人员信息 + for (MonthlyPlanVo planVo : planWithArrangementList) { + String personnelNames = planVo.getPersonnelNames(); + String arrangeDay = planVo.getArrangeDay(); + if (personnelNames == null || personnelNames.trim().isEmpty() || arrangeDay == null) { + continue; + } + + // 拆分人员姓名(逗号分隔) + List nameList = Arrays.asList(personnelNames.split(",")); + for (String name : nameList) { + if (name.trim().isEmpty()) { + continue; + } + + // 3. 查询人员详情 + PersonnelVo personnelVo = new PersonnelVo(); + personnelVo.setName(name.trim()); + List personnelList = personnelMapper.getPersonnelList(personnelVo); + if (personnelList.isEmpty()) { + continue; + } + + // 4. 封装导出VO(关联人员安排表的日期) + for (PersonnelVo personnel : personnelList) { + ExportMonthPlanPersonVo exportVo = new ExportMonthPlanPersonVo(); + exportVo.setInspectionStationName(planVo.getInspectionStationName()); + exportVo.setWorkContent(planVo.getWorkContent()); + exportVo.setName(personnel.getName()); + exportVo.setSex(personnel.getSex()); + exportVo.setPositionName(personnel.getPositionName()); + exportVo.setPersonnelNatureName(personnel.getPersonnelNatureName()); + exportVo.setPersonnelClassificationName(personnel.getPersonnelClassificationName()); + + // 关键:设置人员安排表的日期(支持多个日期) + List arrangeDays = new ArrayList<>(); + arrangeDays.add(arrangeDay); + // 若同一人员有多个安排日期,合并到arrangeDays(需根据实际业务调整) + Optional existVo = plannedList.stream() + .filter(v -> v.getName().equals(personnel.getName()) + && v.getInspectionStationName().equals(planVo.getInspectionStationName())) + .findFirst(); + if (existVo.isPresent()) { + existVo.get().getArrangeDays().add(arrangeDay); + } else { + exportVo.setArrangeDays(arrangeDays); + plannedList.add(exportVo); + } + } } } + return plannedList; } - /** * 导出工作量汇总Excel * @param monthlyPlanVo 筛选条件 diff --git a/bonus-business/src/main/resources/mapper/MonthPlanMapper.xml b/bonus-business/src/main/resources/mapper/MonthPlanMapper.xml index 66bbeeb..1f1356b 100644 --- a/bonus-business/src/main/resources/mapper/MonthPlanMapper.xml +++ b/bonus-business/src/main/resources/mapper/MonthPlanMapper.xml @@ -176,6 +176,20 @@ where tmp.is_active = '1' and tmp.monthly_plan = #{monthlyPlan} + + +