导出功能优化
This commit is contained in:
parent
3a8f9d3710
commit
7ae5fead6c
|
|
@ -1,44 +1,108 @@
|
|||
package com.bonus.business.controller.tool;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import com.bonus.digital.dao.ExportMonthPlanPersonVo;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 运检人员月度安排导出工具类
|
||||
*/
|
||||
public class MonthPlanExcelExporter {
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
// 固定列索引常量
|
||||
private static final int COL_SERIAL = 0; // 序号列
|
||||
private static final int COL_STATION = 1; // 运检站列
|
||||
private static final int COL_NAME = 2; // 姓名列
|
||||
private static final int COL_SEX = 3; // 性别列
|
||||
private static final int COL_POSITION = 4; // 岗位列
|
||||
private static final int COL_NATURE = 5; // 人员性质列
|
||||
private static final int COL_CLASSIFY = 6; // 分类列
|
||||
private static final int FIRST_DATE_COL = 7;// 第一个日期列(1日)的索引
|
||||
|
||||
// 列宽兜底值(字符数,避免自适应过窄/过宽)
|
||||
private static final int MIN_COL_WIDTH = 8; // 最小列宽(字符)
|
||||
private static final int MAX_COL_WIDTH = 30; // 最大列宽(字符)
|
||||
|
||||
/**
|
||||
* 将原始数据转换为按日期展开的 Map 列表(用于 EasyExcel 导出)
|
||||
*
|
||||
* @param dataList 原始数据列表
|
||||
* @param year 年份,如 2025
|
||||
* @param month 月份,如 10
|
||||
* @return List<Map<String, Object>> 用于 EasyExcel writeMap
|
||||
* 导出运检人员月度安排Excel
|
||||
* @param response 响应对象
|
||||
* @param dataList 人员月度安排数据
|
||||
* @param year 年份
|
||||
* @param month 月份
|
||||
* @param sheetName sheet名称
|
||||
* @throws Exception 导出异常
|
||||
*/
|
||||
public static List<Map<String, Object>> convertToDailyMapList(
|
||||
public static void exportToExcel(HttpServletResponse response,
|
||||
List<ExportMonthPlanPersonVo> dataList,
|
||||
int year, int month,
|
||||
String sheetName) throws Exception {
|
||||
// 1. 初始化参数 + 年月合法性校验
|
||||
if (year <= 0 || month < 1 || month > 12) {
|
||||
LocalDate now = LocalDate.now();
|
||||
year = now.getYear();
|
||||
month = now.getMonthValue();
|
||||
}
|
||||
List<ExportMonthPlanPersonVo> finalData = dataList == null ? new ArrayList<>() : dataList;
|
||||
int daysInMonth = LocalDate.of(year, month, 1).lengthOfMonth();
|
||||
String title = year + "年" + month + "月运检人员安排明细";
|
||||
|
||||
// 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. 转换原始数据为行映射(按运检站+姓名分组)
|
||||
List<Map<String, Object>> rowDataList = convertToDailyMapList(finalData, year, month);
|
||||
|
||||
// 5. 构建表头(标题行 + 列头行)
|
||||
int headerRowCount = buildHeader(sheet, titleStyle, headerStyle, title, daysInMonth);
|
||||
|
||||
// 6. 填充数据行
|
||||
int dataStartRow = headerRowCount;
|
||||
fillDataRows(sheet, contentStyle, rowDataList, dataStartRow, daysInMonth);
|
||||
|
||||
// 7. 合并日期列相同内容的连续单元格
|
||||
mergeSameContentCells(sheet, rowDataList, dataStartRow, daysInMonth);
|
||||
|
||||
// 8. 核心:自适应列宽(替代固定列宽)
|
||||
autoAdjustColumnWidths(sheet, daysInMonth);
|
||||
|
||||
// 9. 响应输出
|
||||
response.reset();
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
String fileName = URLEncoder.encode(sheetName, "UTF-8");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||
|
||||
OutputStream os = response.getOutputStream();
|
||||
workbook.write(os);
|
||||
os.flush();
|
||||
os.close();
|
||||
workbook.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换原始数据为按行映射的结构(按运检站+姓名分组)
|
||||
*/
|
||||
private static List<Map<String, Object>> convertToDailyMapList(
|
||||
List<ExportMonthPlanPersonVo> dataList, int year, int month) {
|
||||
|
||||
// 获取该月最大天数
|
||||
int daysInMonth = LocalDate.of(year, month, 1).lengthOfMonth();
|
||||
|
||||
// 构建表头顺序(固定列 + 动态日期列)
|
||||
List<String> headers = Arrays.asList(
|
||||
"运检站", "姓名", "性别", "岗位", "人员性质", "分类"
|
||||
);
|
||||
List<String> dateHeaders = new ArrayList<>();
|
||||
for (int d = 1; d <= daysInMonth; d++) {
|
||||
dateHeaders.add(d + "日");
|
||||
}
|
||||
|
||||
// 按 (运检站 + 姓名) 分组
|
||||
Map<String, Map<String, Object>> rowMap = new LinkedHashMap<>();
|
||||
|
||||
for (ExportMonthPlanPersonVo item : dataList) {
|
||||
|
|
@ -47,37 +111,41 @@ public class MonthPlanExcelExporter {
|
|||
// 初始化行数据
|
||||
rowMap.computeIfAbsent(key, k -> {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("运检站", item.getInspectionStationName());
|
||||
row.put("姓名", item.getName());
|
||||
row.put("性别", item.getSex());
|
||||
row.put("岗位", item.getPositionName());
|
||||
row.put("人员性质", item.getPersonnelNatureName());
|
||||
row.put("分类", item.getPersonnelClassificationName());
|
||||
// 固定列(空值兜底,避免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());
|
||||
|
||||
// 初始化所有日期列为 ""
|
||||
for (String dh : dateHeaders) {
|
||||
row.put(dh, "");
|
||||
// 初始化日期列
|
||||
Map<Integer, String> dailyContent = new HashMap<>();
|
||||
for (int d = 1; d <= daysInMonth; d++) {
|
||||
dailyContent.put(d, "");
|
||||
}
|
||||
row.put("dailyContent", dailyContent);
|
||||
return row;
|
||||
});
|
||||
|
||||
// 解析日期范围
|
||||
// 填充日期内容(空值校验:避免日期解析失败)
|
||||
Map<Integer, String> dailyContent = (Map<Integer, String>) rowMap.get(key).get("dailyContent");
|
||||
if (item.getPlannedStartTime() == null || item.getPlannedEndTime() == null) {
|
||||
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 dateKey = day + "日";
|
||||
String currentContent = (String) rowMap.get(key).get(dateKey);
|
||||
String newContent = item.getWorkContent();
|
||||
String currentContent = dailyContent.get(day);
|
||||
String newContent = item.getWorkContent() == null ? "" : item.getWorkContent();
|
||||
|
||||
// 如果当天已有内容,用换行拼接
|
||||
if (currentContent == null || currentContent.trim().isEmpty()) {
|
||||
rowMap.get(key).put(dateKey, newContent);
|
||||
dailyContent.put(day, newContent);
|
||||
} else {
|
||||
rowMap.get(key).put(dateKey, currentContent + "\n" + newContent);
|
||||
dailyContent.put(day, currentContent + "\n" + newContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,26 +155,258 @@ public class MonthPlanExcelExporter {
|
|||
}
|
||||
|
||||
/**
|
||||
* 导出 Excel 到 HttpServletResponse(Web 场景)
|
||||
* 构建标题样式(含自动换行 + 居中)
|
||||
*/
|
||||
public static void exportToExcel(HttpServletResponse response,
|
||||
List<ExportMonthPlanPersonVo> dataList,
|
||||
int year, int month,
|
||||
String sheetName) throws IOException {
|
||||
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);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
||||
// 核心:单元格自动换行(解决长内容遮挡)
|
||||
style.setWrapText(true);
|
||||
return style;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> mapList = convertToDailyMapList(dataList, year, month);
|
||||
/**
|
||||
* 构建表头样式(含自动换行 + 居中)
|
||||
*/
|
||||
private static CellStyle buildHeaderStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
Font font = workbook.createFont();
|
||||
font.setBold(true);
|
||||
font.setFontName("宋体");
|
||||
font.setFontHeightInPoints((short) 12);
|
||||
style.setFont(font);
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setWrapText(true); // 自动换行
|
||||
return style;
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
String fileName = URLEncoder.encode(sheetName, "UTF-8");
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||
/**
|
||||
* 构建内容样式(含自动换行 + 垂直居中)
|
||||
*/
|
||||
private static CellStyle buildContentStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
Font font = workbook.createFont();
|
||||
font.setFontName("宋体");
|
||||
font.setFontHeightInPoints((short) 11);
|
||||
style.setFont(font);
|
||||
// 水平+垂直居中(解决内容上下/左右遮挡)
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
||||
style.setWrapText(true); // 自动换行
|
||||
return style;
|
||||
}
|
||||
|
||||
// 使用 EasyExcel 导出
|
||||
EasyExcel.write(response.getOutputStream())
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.autoCloseStream(true)
|
||||
.sheet(sheetName)
|
||||
.doWrite(mapList);
|
||||
/**
|
||||
* 构建表头(标题行 + 列头行)
|
||||
* @return 表头总行数
|
||||
*/
|
||||
private static int buildHeader(Sheet sheet, CellStyle titleStyle, CellStyle headerStyle,
|
||||
String title, int daysInMonth) {
|
||||
// 计算总列数:固定列(7列) + 日期列
|
||||
int totalCols = FIRST_DATE_COL + daysInMonth;
|
||||
|
||||
// 行0:标题行(合并所有列)
|
||||
Row titleRow = sheet.createRow(0);
|
||||
titleRow.setHeightInPoints(40); // 加高标题行(适配换行)
|
||||
Cell titleCell = titleRow.createCell(0);
|
||||
titleCell.setCellValue(title);
|
||||
titleCell.setCellStyle(titleStyle);
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, totalCols - 1));
|
||||
|
||||
// 行1:列头行
|
||||
Row headerRow = sheet.createRow(1);
|
||||
headerRow.setHeightInPoints(30); // 加高表头行
|
||||
// 固定列头
|
||||
createCell(headerRow, COL_SERIAL, "序号", headerStyle);
|
||||
createCell(headerRow, COL_STATION, "运检站", headerStyle);
|
||||
createCell(headerRow, COL_NAME, "姓名", headerStyle);
|
||||
createCell(headerRow, COL_SEX, "性别", headerStyle);
|
||||
createCell(headerRow, COL_POSITION, "岗位", headerStyle);
|
||||
createCell(headerRow, COL_NATURE, "人员性质", headerStyle);
|
||||
createCell(headerRow, COL_CLASSIFY, "分类", headerStyle);
|
||||
// 日期列头
|
||||
for (int d = 1; d <= daysInMonth; d++) {
|
||||
int colIndex = FIRST_DATE_COL + (d - 1);
|
||||
createCell(headerRow, colIndex, d + "日", headerStyle);
|
||||
}
|
||||
|
||||
return 2; // 表头总行数:标题行(0) + 列头行(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充数据行(加高行高,适配自动换行)
|
||||
*/
|
||||
private static void fillDataRows(Sheet sheet, CellStyle contentStyle,
|
||||
List<Map<String, Object>> rowDataList,
|
||||
int startRowNum, int daysInMonth) {
|
||||
int serialNum = 1; // 序号
|
||||
|
||||
for (int i = 0; i < rowDataList.size(); i++) {
|
||||
int rowNum = startRowNum + i;
|
||||
Row row = sheet.createRow(rowNum);
|
||||
row.setHeightInPoints(35); // 加高数据行(解决换行内容遮挡)
|
||||
Map<String, Object> rowData = rowDataList.get(i);
|
||||
|
||||
// 填充固定列(空值兜底)
|
||||
createCell(row, COL_SERIAL, serialNum++, contentStyle);
|
||||
createCell(row, COL_STATION, rowData.get("运检站"), contentStyle);
|
||||
createCell(row, COL_NAME, rowData.get("姓名"), contentStyle);
|
||||
createCell(row, COL_SEX, rowData.get("性别"), contentStyle);
|
||||
createCell(row, COL_POSITION, rowData.get("岗位"), contentStyle);
|
||||
createCell(row, COL_NATURE, rowData.get("人员性质"), contentStyle);
|
||||
createCell(row, COL_CLASSIFY, rowData.get("分类"), contentStyle);
|
||||
|
||||
// 填充日期列
|
||||
Map<Integer, String> dailyContent = (Map<Integer, String>) rowData.get("dailyContent");
|
||||
for (int d = 1; d <= daysInMonth; d++) {
|
||||
int colIndex = FIRST_DATE_COL + (d - 1);
|
||||
String content = dailyContent.get(d) == null ? "" : dailyContent.get(d);
|
||||
createCell(row, colIndex, content, contentStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并日期列中相同内容的连续单元格
|
||||
*/
|
||||
private static void mergeSameContentCells(Sheet sheet, List<Map<String, Object>> rowDataList,
|
||||
int dataStartRow, int daysInMonth) {
|
||||
// 遍历每一行
|
||||
for (int rowIdx = 0; rowIdx < rowDataList.size(); rowIdx++) {
|
||||
int currentRowNum = dataStartRow + rowIdx;
|
||||
Map<String, Object> rowData = rowDataList.get(rowIdx);
|
||||
Map<Integer, String> dailyContent = (Map<Integer, String>) rowData.get("dailyContent");
|
||||
|
||||
// 遍历日期列,查找连续相同内容的单元格
|
||||
for (int d = 1; d <= daysInMonth; ) {
|
||||
String baseContent = dailyContent.get(d) == null ? "" : dailyContent.get(d);
|
||||
if (baseContent.trim().isEmpty()) {
|
||||
d++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查找连续相同内容的结束日期
|
||||
int endDay = d;
|
||||
while (endDay + 1 <= daysInMonth) {
|
||||
String nextContent = dailyContent.get(endDay + 1) == null ? "" : dailyContent.get(endDay + 1);
|
||||
if (baseContent.equals(nextContent)) {
|
||||
endDay++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 若有连续相同内容,合并单元格
|
||||
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 // 起始列-结束列
|
||||
));
|
||||
// 跳过已合并的列
|
||||
d = endDay + 1;
|
||||
} else {
|
||||
d++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心:自适应列宽(替代固定列宽)
|
||||
* 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;
|
||||
} else if (currentWidth > MAX_COL_WIDTH) {
|
||||
targetWidth = MAX_COL_WIDTH;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单元格并设置值和样式(空值兜底)
|
||||
*/
|
||||
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);
|
||||
} else if (cellValue instanceof Integer) {
|
||||
cell.setCellValue((Integer) cellValue);
|
||||
} else if (cellValue instanceof Long) {
|
||||
cell.setCellValue((Long) cellValue);
|
||||
} else if (cellValue instanceof Double) {
|
||||
cell.setCellValue((Double) cellValue);
|
||||
} else {
|
||||
cell.setCellValue(cellValue.toString());
|
||||
}
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import java.io.OutputStream;
|
|||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 工作量+用车清单导出工具类
|
||||
|
|
@ -19,6 +18,8 @@ import java.util.Objects;
|
|||
* 1. 标题移除年份前缀(仅保留月份)
|
||||
* 2. 移除所有样式的灰色背景色
|
||||
* 3. 修复工作量/用车标题边框缺失问题(为合并区域所有单元格应用样式+显式设置边框颜色)
|
||||
* 4. 修复用车汇总合计行J列(管理用车天数)合计值缺失问题(调整合并区域)
|
||||
* 5. 用车表格序号为0的行:序号、管理用车天数、分包用车天数置为空
|
||||
*/
|
||||
public class WorkloadAndCarSummaryExcelExporter {
|
||||
|
||||
|
|
@ -226,6 +227,7 @@ public class WorkloadAndCarSummaryExcelExporter {
|
|||
|
||||
/**
|
||||
* 填充数据行(从行2开始)
|
||||
* 核心修改:用车数据行序号为0时,序号、管理用车天数、分包用车天数置为空
|
||||
*/
|
||||
private static void fillDataRows(Sheet sheet, CellStyle contentStyle,
|
||||
List<WorkloadSummaryExcelVo> workloadData,
|
||||
|
|
@ -244,12 +246,29 @@ public class WorkloadAndCarSummaryExcelExporter {
|
|||
createCell(row, COL_E, workloadVo.getUnitPrice() == null ? 0 : workloadVo.getUnitPrice(), contentStyle);
|
||||
createCell(row, COL_F, workloadVo.getTotalAmount() == null ? 0 : workloadVo.getTotalAmount(), contentStyle);
|
||||
|
||||
// 填充用车数据
|
||||
// 填充用车数据(核心修改:序号为0时置空)
|
||||
CarUseSummaryExcelVo carVo = i < carData.size() ? carData.get(i) : new CarUseSummaryExcelVo();
|
||||
createCell(row, COL_H, carVo.getSerialNumber() == null ? 0 : carVo.getSerialNumber(), contentStyle);
|
||||
Integer carSerialNum = carVo.getSerialNumber();
|
||||
// 序号:为0或null时置空,否则填实际值
|
||||
if (carSerialNum == null || carSerialNum == 0) {
|
||||
createCell(row, COL_H, "", contentStyle);
|
||||
} else {
|
||||
createCell(row, COL_H, carSerialNum, contentStyle);
|
||||
}
|
||||
// 运检站:保留原有逻辑
|
||||
createCell(row, COL_I, carVo.getInspectionStationName() == null ? "" : carVo.getInspectionStationName(), contentStyle);
|
||||
createCell(row, COL_J, carVo.getTotalManageCarDays() == null ? 0 : carVo.getTotalManageCarDays(), contentStyle);
|
||||
createCell(row, COL_K, carVo.getTotalSubCarDays() == null ? 0 : carVo.getTotalSubCarDays(), contentStyle);
|
||||
// 管理用车天数:序号为0/null时置空,否则填实际值(无值则0)
|
||||
if (carSerialNum == null || carSerialNum == 0) {
|
||||
createCell(row, COL_J, "", contentStyle);
|
||||
} else {
|
||||
createCell(row, COL_J, carVo.getTotalManageCarDays() == null ? 0 : carVo.getTotalManageCarDays(), contentStyle);
|
||||
}
|
||||
// 分包用车天数:序号为0/null时置空,否则填实际值(无值则0)
|
||||
if (carSerialNum == null || carSerialNum == 0) {
|
||||
createCell(row, COL_K, "", contentStyle);
|
||||
} else {
|
||||
createCell(row, COL_K, carVo.getTotalSubCarDays() == null ? 0 : carVo.getTotalSubCarDays(), contentStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,14 +297,19 @@ public class WorkloadAndCarSummaryExcelExporter {
|
|||
int subCarTotal = 0;
|
||||
for (CarUseSummaryExcelVo vo : carData) {
|
||||
if (vo != null) {
|
||||
if (vo.getTotalManageCarDays() != null) {
|
||||
manageCarTotal += vo.getTotalManageCarDays();
|
||||
}
|
||||
if (vo.getTotalSubCarDays() != null) {
|
||||
subCarTotal += vo.getTotalSubCarDays();
|
||||
// 仅统计序号非0的有效数据
|
||||
Integer serialNum = vo.getSerialNumber();
|
||||
if (serialNum != null && serialNum != 0) {
|
||||
if (vo.getTotalManageCarDays() != null) {
|
||||
manageCarTotal += vo.getTotalManageCarDays();
|
||||
}
|
||||
if (vo.getTotalSubCarDays() != null) {
|
||||
subCarTotal += vo.getTotalSubCarDays();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 确保J列(管理用车天数合计)和K列(分包用车天数合计)正常赋值
|
||||
createCell(totalRow, COL_J, manageCarTotal, totalStyle);
|
||||
createCell(totalRow, COL_K, subCarTotal, totalStyle);
|
||||
|
||||
|
|
@ -298,7 +322,7 @@ public class WorkloadAndCarSummaryExcelExporter {
|
|||
}
|
||||
|
||||
/**
|
||||
* 设置合并区域
|
||||
* 设置合并区域(核心修改:调整用车合计行的合并范围,避免覆盖J列合计值)
|
||||
*/
|
||||
private static void setMergedRegions(Sheet sheet, int totalRowNum) {
|
||||
// 一级标题合并
|
||||
|
|
@ -306,7 +330,8 @@ public class WorkloadAndCarSummaryExcelExporter {
|
|||
sheet.addMergedRegion(new CellRangeAddress(0, 0, COL_H, COL_K));
|
||||
// 合计行合并
|
||||
sheet.addMergedRegion(new CellRangeAddress(totalRowNum, totalRowNum, COL_A, COL_E));
|
||||
sheet.addMergedRegion(new CellRangeAddress(totalRowNum, totalRowNum, COL_H, COL_J));
|
||||
// 核心修改:用车合计行仅合并H-I列,不再合并J列,确保J列和K列的合计值正常显示
|
||||
sheet.addMergedRegion(new CellRangeAddress(totalRowNum, totalRowNum, COL_H, COL_I));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -226,12 +226,8 @@
|
|||
left join tb_plan_management pm
|
||||
on pm.plan_management_id = tmp.plan_management_id
|
||||
where tmp.is_active = '1'
|
||||
<!-- 计划日期筛选 -->
|
||||
<if test="startTime != null and startTime != ''">
|
||||
AND tdp.day_plan >= #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null and endTime != ''">
|
||||
AND tdp.day_plan <= #{endTime}
|
||||
<if test="startTime != null and startTime != '' and endTime != null and endTime != ''">
|
||||
AND DATE_FORMAT(tdp.day_plan, '%Y-%m-%d') BETWEEN #{startTime} AND #{endTime}
|
||||
</if>
|
||||
GROUP BY
|
||||
tmp.inspection_station_id,
|
||||
|
|
|
|||
|
|
@ -193,27 +193,6 @@
|
|||
ORDER BY tmp.inspection_station_name ASC;
|
||||
</select>
|
||||
|
||||
<select id="getOverallSummary" resultType="com.bonus.digital.dao.MonthlyPlanVo">
|
||||
select
|
||||
tis.inspection_station_id,
|
||||
tis.inspection_station_name,
|
||||
from tb_inspection_station tis
|
||||
left join tb_monthly_plan tmp
|
||||
on tis.inspection_station_id = tmp.inspection_station_id
|
||||
where tis.category = '0'
|
||||
-- 关联计划管理表
|
||||
left join tb_plan_management pm
|
||||
on pm.plan_management_id = tmp.plan_management_id
|
||||
where tmp.is_active = '1'
|
||||
<!-- 计划月份筛选 -->
|
||||
<if test="monthlyPlan != null and monthlyPlan != ''">
|
||||
AND tmp.monthly_plan = #{monthlyPlan}
|
||||
</if>
|
||||
GROUP BY
|
||||
tis.inspection_station_id,
|
||||
tis.inspection_station_name
|
||||
ORDER BY tmp.inspection_station_name ASC;
|
||||
</select>
|
||||
|
||||
<select id="getResourceSummary" resultType="com.bonus.digital.dao.ResourceSummaryVo">
|
||||
<![CDATA[
|
||||
|
|
@ -231,69 +210,69 @@
|
|||
IFNULL(base_data.rest_total_days, 0) AS rest_total_days,
|
||||
IFNULL(base_data.rest_person_count, 0) AS rest_person_count,
|
||||
IFNULL(base_data.rest_workday, 0) AS rest_workday,
|
||||
ROUND(IFNULL(base_data.rest_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS rest_ratio,
|
||||
ROUND(IFNULL(base_data.rest_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS rest_avg,
|
||||
ROUND(IFNULL(base_data.rest_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS rest_ratio,
|
||||
ROUND(IFNULL(base_data.rest_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS rest_avg,
|
||||
|
||||
-- 培训相关
|
||||
IFNULL(base_data.train_total_days, 0) AS train_total_days,
|
||||
IFNULL(base_data.train_person_count, 0) AS train_person_count,
|
||||
IFNULL(base_data.train_workday, 0) AS train_workday,
|
||||
ROUND(IFNULL(base_data.train_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS train_ratio,
|
||||
ROUND(IFNULL(base_data.train_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS train_avg,
|
||||
ROUND(IFNULL(base_data.train_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS train_ratio,
|
||||
ROUND(IFNULL(base_data.train_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS train_avg,
|
||||
|
||||
-- 运行相关
|
||||
IFNULL(base_data.run_total_days, 0) AS run_total_days,
|
||||
IFNULL(base_data.run_person_count, 0) AS run_person_count,
|
||||
IFNULL(base_data.run_workday, 0) AS run_workday,
|
||||
ROUND(IFNULL(base_data.run_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS run_ratio,
|
||||
ROUND(IFNULL(base_data.run_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS run_avg,
|
||||
ROUND(IFNULL(base_data.run_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS run_ratio,
|
||||
ROUND(IFNULL(base_data.run_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS run_avg,
|
||||
|
||||
-- 运行(视频)相关
|
||||
IFNULL(base_data.run_video_total_days, 0) AS run_video_total_days,
|
||||
IFNULL(base_data.run_video_person_count, 0) AS run_video_person_count,
|
||||
IFNULL(base_data.run_video_workday, 0) AS run_video_workday,
|
||||
ROUND(IFNULL(base_data.run_video_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS run_video_ratio,
|
||||
ROUND(IFNULL(base_data.run_video_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS run_video_avg,
|
||||
ROUND(IFNULL(base_data.run_video_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS run_video_ratio,
|
||||
ROUND(IFNULL(base_data.run_video_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS run_video_avg,
|
||||
|
||||
-- 检修相关
|
||||
IFNULL(base_data.maintain_total_days, 0) AS maintain_total_days,
|
||||
IFNULL(base_data.maintain_person_count, 0) AS maintain_person_count,
|
||||
IFNULL(base_data.maintain_workday, 0) AS maintain_workday,
|
||||
ROUND(IFNULL(base_data.maintain_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS maintain_ratio,
|
||||
ROUND(IFNULL(base_data.maintain_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS maintain_avg,
|
||||
ROUND(IFNULL(base_data.maintain_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS maintain_ratio,
|
||||
ROUND(IFNULL(base_data.maintain_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS maintain_avg,
|
||||
|
||||
-- 值班相关
|
||||
IFNULL(base_data.duty_total_days, 0) AS duty_total_days,
|
||||
IFNULL(base_data.duty_person_count, 0) AS duty_person_count,
|
||||
IFNULL(base_data.duty_workday, 0) AS duty_workday,
|
||||
ROUND(IFNULL(base_data.duty_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS duty_ratio,
|
||||
ROUND(IFNULL(base_data.duty_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS duty_avg,
|
||||
ROUND(IFNULL(base_data.duty_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS duty_ratio,
|
||||
ROUND(IFNULL(base_data.duty_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS duty_avg,
|
||||
|
||||
-- 抢修相关
|
||||
IFNULL(base_data.repair_total_days, 0) AS repair_total_days,
|
||||
IFNULL(base_data.repair_person_count, 0) AS repair_person_count,
|
||||
IFNULL(base_data.repair_workday, 0) AS repair_workday,
|
||||
ROUND(IFNULL(base_data.repair_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS repair_ratio,
|
||||
ROUND(IFNULL(base_data.repair_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS repair_avg,
|
||||
ROUND(IFNULL(base_data.repair_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS repair_ratio,
|
||||
ROUND(IFNULL(base_data.repair_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS repair_avg,
|
||||
|
||||
-- 学习相关
|
||||
IFNULL(base_data.study_total_days, 0) AS study_total_days,
|
||||
IFNULL(base_data.study_person_count, 0) AS study_person_count,
|
||||
IFNULL(base_data.study_workday, 0) AS study_workday,
|
||||
ROUND(IFNULL(base_data.study_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS study_ratio,
|
||||
ROUND(IFNULL(base_data.study_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS study_avg,
|
||||
ROUND(IFNULL(base_data.study_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS study_ratio,
|
||||
ROUND(IFNULL(base_data.study_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS study_avg,
|
||||
|
||||
-- 其他相关
|
||||
IFNULL(base_data.other_total_days, 0) AS other_total_days,
|
||||
IFNULL(base_data.other_person_count, 0) AS other_person_count,
|
||||
IFNULL(base_data.other_workday, 0) AS other_workday,
|
||||
ROUND(IFNULL(base_data.other_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS other_ratio,
|
||||
ROUND(IFNULL(base_data.other_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS other_avg,
|
||||
ROUND(IFNULL(base_data.other_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS other_ratio,
|
||||
ROUND(IFNULL(base_data.other_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS other_avg,
|
||||
|
||||
-- 未安排相关
|
||||
IFNULL(base_data.unarrange_workday, 0) AS unarrange_workday,
|
||||
ROUND(IFNULL(base_data.unarrange_workday / NULLIF(base_data.total_workday, 0), 0), 2) AS unarrange_ratio,
|
||||
ROUND(IFNULL(base_data.unarrange_workday / NULLIF(base_data.actual_station_num, 0), 0), 1) AS unarrange_avg,
|
||||
ROUND(IFNULL(base_data.unarrange_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS unarrange_ratio,
|
||||
ROUND(IFNULL(base_data.unarrange_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS unarrange_avg,
|
||||
|
||||
-- 分包用工
|
||||
IFNULL(base_data.subcontract_workday, 0) AS subcontract_workday
|
||||
|
|
@ -328,8 +307,13 @@
|
|||
AND p.is_active = '1'), 0) AS actual_station_num,
|
||||
|
||||
-- 总工日(乘法结果兜底,避免null)
|
||||
IFNULL((SELECT DATEDIFF(LAST_DAY(STR_TO_DATE(#{monthlyPlan}, '%Y-%m')),
|
||||
DATE_FORMAT(STR_TO_DATE(#{monthlyPlan}, '%Y-%m'), '%Y-%m-01')) + 1), 0) *
|
||||
IFNULL(
|
||||
(SELECT DATEDIFF(
|
||||
LAST_DAY(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d')),
|
||||
DATE_FORMAT(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d'), '%Y-%m-01')
|
||||
) + 1),
|
||||
0
|
||||
) *
|
||||
IFNULL((
|
||||
IFNULL((SELECT COUNT(p.id)
|
||||
FROM tb_personnel p
|
||||
|
|
@ -366,7 +350,6 @@
|
|||
AND pm.plan_major_name = '休假'
|
||||
AND pm.category = '0'), 0) AS rest_person_count,
|
||||
|
||||
-- 休假总工时(乘法结果兜底)
|
||||
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
|
||||
FROM tb_monthly_plan tmp
|
||||
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
|
||||
|
|
@ -740,11 +723,16 @@
|
|||
AND pm.category = '0'), 0) AS other_workday,
|
||||
|
||||
-- ======================================
|
||||
-- 类型10:未安排(减法结果兜底)
|
||||
-- 类型10:未安排(核心修改:移除“其他”类型工日的扣除)
|
||||
-- ======================================
|
||||
IFNULL(
|
||||
IFNULL((SELECT DATEDIFF(LAST_DAY(STR_TO_DATE(#{monthlyPlan}, '%Y-%m')),
|
||||
DATE_FORMAT(STR_TO_DATE(#{monthlyPlan}, '%Y-%m'), '%Y-%m-01')) + 1), 0) *
|
||||
IFNULL(
|
||||
(SELECT DATEDIFF(
|
||||
LAST_DAY(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d')),
|
||||
DATE_FORMAT(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d'), '%Y-%m-01')
|
||||
) + 1),
|
||||
0
|
||||
) *
|
||||
IFNULL((
|
||||
IFNULL((SELECT COUNT(p.id)
|
||||
FROM tb_personnel p
|
||||
|
|
@ -923,34 +911,14 @@
|
|||
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
|
||||
AND tmp.monthly_plan = #{monthlyPlan}
|
||||
AND pm.plan_major_name = '学习'
|
||||
AND pm.category = '0'), 0) +
|
||||
|
||||
-- 其他工时
|
||||
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
|
||||
FROM tb_monthly_plan tmp
|
||||
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
|
||||
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
|
||||
AND tmp.monthly_plan = #{monthlyPlan}
|
||||
AND pm.plan_major_name = '其他'
|
||||
AND pm.category = '0'), 0) *
|
||||
IFNULL((SELECT IFNULL(SUM(
|
||||
CASE
|
||||
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
|
||||
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
|
||||
END
|
||||
), 0)
|
||||
FROM tb_monthly_plan tmp
|
||||
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
|
||||
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
|
||||
AND tmp.monthly_plan = #{monthlyPlan}
|
||||
AND pm.plan_major_name = '其他'
|
||||
AND pm.category = '0'), 0)
|
||||
-- 移除:其他工时的累加(核心修改点)
|
||||
), 0), 0) AS unarrange_workday,
|
||||
|
||||
-- ======================================
|
||||
-- 类型11:分包用工
|
||||
-- ======================================
|
||||
IFNULL((SELECT IFNULL(SUM(tmp.plan_skilled_worker_num + tmp.plan_auxiliary_worker_num), 0)
|
||||
IFNULL((SELECT IFNULL(SUM(tmp.plan_skilled_worker_day + tmp.plan_auxiliary_worker_day), 0)
|
||||
FROM tb_monthly_plan tmp
|
||||
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
|
||||
AND tmp.monthly_plan = #{monthlyPlan}), 0) AS subcontract_workday
|
||||
|
|
|
|||
Loading…
Reference in New Issue