优化
This commit is contained in:
		| @ -6,6 +6,7 @@ import jakarta.validation.constraints.NotNull; | |||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| import org.dromara.common.core.domain.R; | import org.dromara.common.core.domain.R; | ||||||
| import org.dromara.common.excel.utils.ExcelUtil; | import org.dromara.common.excel.utils.ExcelUtil; | ||||||
|  | import org.dromara.common.idempotent.annotation.RepeatSubmit; | ||||||
| import org.dromara.common.log.annotation.Log; | import org.dromara.common.log.annotation.Log; | ||||||
| import org.dromara.common.log.enums.BusinessType; | import org.dromara.common.log.enums.BusinessType; | ||||||
| import org.dromara.common.mybatis.core.page.PageQuery; | import org.dromara.common.mybatis.core.page.PageQuery; | ||||||
| @ -25,6 +26,7 @@ import org.springframework.web.multipart.MultipartFile; | |||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 员工每日工资 |  * 员工每日工资 | ||||||
| @ -87,10 +89,10 @@ public class SubUserSalaryDetailController extends BaseController { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @PutMapping("/import") |     @PutMapping("/import") | ||||||
|     public R<Void> importData(@RequestParam("file") MultipartFile file, |     @RepeatSubmit(interval = 1, timeUnit = TimeUnit.MINUTES,message = "正在导入,请勿重复提交") | ||||||
|                               @RequestParam("month") String month) { |     public R<Boolean> importData(@RequestParam("file") MultipartFile file) { | ||||||
|         subUserSalaryDetailService.importData(file, month); |         subUserSalaryDetailService.importData(file, null); | ||||||
|         return R.ok(); |         return R.ok(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @GetMapping("/detailList") |     @GetMapping("/detailList") | ||||||
|  | |||||||
| @ -16,7 +16,8 @@ public class DynamicSalaryData { | |||||||
|     private String idCard;        // 身份证号 |     private String idCard;        // 身份证号 | ||||||
|     private Double total;        // 合计出勤天数 |     private Double total;        // 合计出勤天数 | ||||||
|     private String isLeave;       // 是否离场(未离场/已离场) |     private String isLeave;       // 是否离场(未离场/已离场) | ||||||
|     private String signature;     // 签字(可选) |     private String signature; | ||||||
|  |     private String month;// 签字(可选) | ||||||
|  |  | ||||||
|     // 动态日期考勤:key=完整日期(如“2025-09-01”),value=考勤状态(0=未出勤,1=出勤) |     // 动态日期考勤:key=完整日期(如“2025-09-01”),value=考勤状态(0=未出勤,1=出勤) | ||||||
|     private Map<String, Double> dailyAttendance = new LinkedHashMap<>(); |     private Map<String, Double> dailyAttendance = new LinkedHashMap<>(); | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ package org.dromara.contractor.excel; | |||||||
| import com.alibaba.excel.context.AnalysisContext; | import com.alibaba.excel.context.AnalysisContext; | ||||||
| import com.alibaba.excel.event.AnalysisEventListener; | import com.alibaba.excel.event.AnalysisEventListener; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.dromara.common.core.exception.ServiceException; | ||||||
| import org.dromara.common.utils.IdCardEncryptorUtil; | import org.dromara.common.utils.IdCardEncryptorUtil; | ||||||
|  |  | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
| @ -13,7 +14,7 @@ import java.util.Map; | |||||||
| @Slf4j | @Slf4j | ||||||
| public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, String>> { | public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, String>> { | ||||||
|     private final List<DynamicSalaryData> allAttendanceList; |     private final List<DynamicSalaryData> allAttendanceList; | ||||||
|     private final String month; |     private String month; | ||||||
|     private static final int DATE_COL_START_INDEX = 3; // 日期列起始索引(第4列) |     private static final int DATE_COL_START_INDEX = 3; // 日期列起始索引(第4列) | ||||||
|     private int dateColEndIndex; |     private int dateColEndIndex; | ||||||
|     private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |     private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | ||||||
| @ -21,22 +22,37 @@ public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, St | |||||||
|     // 写死表头行号(0-based,对应Excel第3行) |     // 写死表头行号(0-based,对应Excel第3行) | ||||||
|     private static final int FIXED_HEAD_ROW_NUMBER = 2; |     private static final int FIXED_HEAD_ROW_NUMBER = 2; | ||||||
|  |  | ||||||
|     public DynamicSalaryListener(List<DynamicSalaryData> allAttendanceList, String month) { |     public DynamicSalaryListener(List<DynamicSalaryData> allAttendanceList) { | ||||||
|         this.allAttendanceList = allAttendanceList; |         this.allAttendanceList = allAttendanceList; | ||||||
|         this.month = month; |  | ||||||
|         // 月份格式校验 |  | ||||||
|         try { |  | ||||||
|             LocalDate.parse(month + "-01", dateFormatter); |  | ||||||
|         } catch (Exception e) { |  | ||||||
|             throw new IllegalArgumentException("月份格式错误!需传入yyyy-MM格式(如2025-09)"); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { |     public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { | ||||||
|         // 仅处理固定行号的表头,且仅初始化1次 |  | ||||||
|  |         // 处理第一行标题,提取月份 | ||||||
|         int currentRowIndex = context.readRowHolder().getRowIndex(); |         int currentRowIndex = context.readRowHolder().getRowIndex(); | ||||||
|  |         if (currentRowIndex == 0) { // 第一行标题 | ||||||
|  |             // 从标题中提取月份,如"建筑施工企业现场人员工资表(2025年09月)" | ||||||
|  |             String title = getCellValue(headMap, 0); | ||||||
|  |             if (title != null && title.contains("(") && title.contains(")")) { | ||||||
|  |                 try { | ||||||
|  |                     // 提取年月部分,如"2025-09" | ||||||
|  |                     // 转换为yyyy-MM格式,如"2025-09" | ||||||
|  |  | ||||||
|  |                     this.month = title.substring(title.indexOf("(") + 1, title.indexOf(")")); | ||||||
|  |                     // 校验格式 | ||||||
|  |                     LocalDate.parse(this.month + "-01", dateFormatter); | ||||||
|  |                     log.info("从标题行解析到月份:{}", this.month); | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     throw new ServiceException("未解析到年月"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // 仅处理固定行号的表头,且仅初始化1次 | ||||||
|         if (currentRowIndex != FIXED_HEAD_ROW_NUMBER || isHeadInitialized) { |         if (currentRowIndex != FIXED_HEAD_ROW_NUMBER || isHeadInitialized) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -68,6 +84,10 @@ public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, St | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void invoke(Map<Integer, String> rowData, AnalysisContext context) { |     public void invoke(Map<Integer, String> rowData, AnalysisContext context) { | ||||||
|  |         // 确保month已初始化 | ||||||
|  |         if (this.month == null || this.month.isEmpty()) { | ||||||
|  |             throw new RuntimeException("月份信息未正确解析,请检查Excel标题格式"); | ||||||
|  |         } | ||||||
|         // 【核心优化:强化无效行过滤】 |         // 【核心优化:强化无效行过滤】 | ||||||
|         // 1. 获取第1列(序号)和第2列(姓名)的值,用于判断是否为有效数据行 |         // 1. 获取第1列(序号)和第2列(姓名)的值,用于判断是否为有效数据行 | ||||||
|         String serialNumberStr = getCellValue(rowData, 0); |         String serialNumberStr = getCellValue(rowData, 0); | ||||||
| @ -99,7 +119,7 @@ public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, St | |||||||
|         attendance.setTotal(parseDouble(getCellValue(rowData, dateColEndIndex + 1))); |         attendance.setTotal(parseDouble(getCellValue(rowData, dateColEndIndex + 1))); | ||||||
|         attendance.setIsLeave(getCellValue(rowData, dateColEndIndex + 2)); |         attendance.setIsLeave(getCellValue(rowData, dateColEndIndex + 2)); | ||||||
|         attendance.setSignature(getCellValue(rowData, dateColEndIndex + 3)); |         attendance.setSignature(getCellValue(rowData, dateColEndIndex + 3)); | ||||||
|  |         attendance.setMonth(month); | ||||||
|         // 4. 解析动态日期列 |         // 4. 解析动态日期列 | ||||||
|         for (int colIndex = DATE_COL_START_INDEX; colIndex <= dateColEndIndex; colIndex++) { |         for (int colIndex = DATE_COL_START_INDEX; colIndex <= dateColEndIndex; colIndex++) { | ||||||
|             int day = colIndex - DATE_COL_START_INDEX + 1; |             int day = colIndex - DATE_COL_START_INDEX + 1; | ||||||
| @ -120,6 +140,7 @@ public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, St | |||||||
|  |  | ||||||
|  |  | ||||||
|     // ------------------- 新增工具方法:判断整行是否为空 ------------------- |     // ------------------- 新增工具方法:判断整行是否为空 ------------------- | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 判断行数据是否所有列均为空(过滤空行) |      * 判断行数据是否所有列均为空(过滤空行) | ||||||
|      */ |      */ | ||||||
| @ -167,6 +188,7 @@ public class DynamicSalaryListener extends AnalysisEventListener<Map<Integer, St | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ------------------- 重置表头初始化状态(用于多Sheet遍历) ------------------- |     // ------------------- 重置表头初始化状态(用于多Sheet遍历) ------------------- | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 切换Sheet时调用,重置表头初始化状态(确保新Sheet重新解析表头) |      * 切换Sheet时调用,重置表头初始化状态(确保新Sheet重新解析表头) | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ public class SalaryExcelReader { | |||||||
|      */ |      */ | ||||||
|     public static List<DynamicSalaryData> readAllAttendanceByStream(InputStream inputStream, String month) { |     public static List<DynamicSalaryData> readAllAttendanceByStream(InputStream inputStream, String month) { | ||||||
|         List<DynamicSalaryData> allAttendanceList = new ArrayList<>(); |         List<DynamicSalaryData> allAttendanceList = new ArrayList<>(); | ||||||
|         DynamicSalaryListener listener = new DynamicSalaryListener(allAttendanceList, month); |         DynamicSalaryListener listener = new DynamicSalaryListener(allAttendanceList); | ||||||
|  |  | ||||||
|         try ( |         try ( | ||||||
|             // 1. 构建Excel读取器:读取源为 InputStream,全局设置表头行号 |             // 1. 构建Excel读取器:读取源为 InputStream,全局设置表头行号 | ||||||
| @ -60,7 +60,7 @@ public class SalaryExcelReader { | |||||||
|  |  | ||||||
|     public static List<DynamicSalaryData> readAllAttendance(String excelFilePath, String month) { |     public static List<DynamicSalaryData> readAllAttendance(String excelFilePath, String month) { | ||||||
|         List<DynamicSalaryData> allAttendanceList = new ArrayList<>(); |         List<DynamicSalaryData> allAttendanceList = new ArrayList<>(); | ||||||
|         DynamicSalaryListener listener = new DynamicSalaryListener(allAttendanceList, month); |         DynamicSalaryListener listener = new DynamicSalaryListener(allAttendanceList); | ||||||
|  |  | ||||||
|         try ( |         try ( | ||||||
|             // 【核心修改1:在读取器初始化时就全局设置表头行号】 |             // 【核心修改1:在读取器初始化时就全局设置表头行号】 | ||||||
|  | |||||||
| @ -549,6 +549,7 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet | |||||||
|         Map<String, Map<String, Double>> dataMap =  dataList.stream().collect(Collectors.toMap(vo -> idCardEncryptorUtil.encrypt(vo.getIdCard()), DynamicSalaryData::getDailyAttendance)); |         Map<String, Map<String, Double>> dataMap =  dataList.stream().collect(Collectors.toMap(vo -> idCardEncryptorUtil.encrypt(vo.getIdCard()), DynamicSalaryData::getDailyAttendance)); | ||||||
|         Set<String> cards = dataMap.keySet(); |         Set<String> cards = dataMap.keySet(); | ||||||
|  |  | ||||||
|  |         month = dataList.getFirst().getMonth(); | ||||||
|         YearMonth parse = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM")); |         YearMonth parse = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM")); | ||||||
|         LocalDate start = parse.atDay(1); |         LocalDate start = parse.atDay(1); | ||||||
|         LocalDate end = parse.atEndOfMonth(); |         LocalDate end = parse.atEndOfMonth(); | ||||||
|  | |||||||
| @ -1252,7 +1252,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B | |||||||
|             // ==================== 表头部分 ==================== |             // ==================== 表头部分 ==================== | ||||||
|             Row titleRow = sheet.createRow(0); |             Row titleRow = sheet.createRow(0); | ||||||
|             Cell titleCell = titleRow.createCell(0); |             Cell titleCell = titleRow.createCell(0); | ||||||
|             titleCell.setCellValue("建筑施工企业现场人员考勤表(" + clockDate + "月)"); |             titleCell.setCellValue("建筑施工企业现场人员考勤表(" + clockDate + ")"); | ||||||
|             titleCell.setCellStyle(createTitleCellStyle(workbook)); |             titleCell.setCellStyle(createTitleCellStyle(workbook)); | ||||||
|  |  | ||||||
|             // 合并标题行,横跨所有列 |             // 合并标题行,横跨所有列 | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 zt
					zt