gaoluyang
2026-10-16 7d6825d2344db398b7cd142fea419242db033c54
fix(excel): 修复导入模板下载失败的两个问题

1. 新增 LocalTimeConvert,解决 FastExcel 不支持 LocalTime 导致的
「Can not find 'Converter' support class LocalTime」,考勤规则模板可下载;
2. 委外检测/试验单模板的 sheet 名含 /,POI 校验不通过,改为无斜杠名称。

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加1个文件
已修改2个文件
79 ■■■■■ 文件已修改
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/LocalTimeConvert.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleImportExcelVO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/LocalTimeConvert.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package cn.iocoder.yudao.framework.excel.core.convert;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import java.math.BigDecimal;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
 * LocalTime è½¬æ¢å™¨
 *
 * FastExcel(cn.idev.excel)内置不支持 LocalTime,未指定 converter æ—¶è¯»å†™éƒ½ä¼šæŠ›å‡º
 * `Can not find 'Converter' support class LocalTime`。这里补上「HH:mm:ss å­—符串」与
 * ã€ŒExcel æ—¶é—´æ•°å€¼ï¼ˆä¸€å¤©çš„占比)」两种写法的读写支持。
 *
 * @author è¶…级管理员
 */
public class LocalTimeConvert implements Converter<LocalTime> {
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
    @Override
    public Class<LocalTime> supportJavaTypeKey() {
        return LocalTime.class;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
    @Override
    public LocalTime convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,
                                       GlobalConfiguration globalConfiguration) {
        // æƒ…况一:Excel é‡Œå¡«çš„æ˜¯æ—¶é—´æ•°å€¼ï¼ˆä¸€å¤©çš„占比)
        if (CellDataTypeEnum.NUMBER.equals(readCellData.getType())) {
            BigDecimal numberValue = readCellData.getNumberValue();
            if (numberValue == null) {
                return null;
            }
            long secondOfDay = Math.round(numberValue.doubleValue() * 24 * 60 * 60) % (24 * 60 * 60);
            return LocalTime.ofSecondOfDay(secondOfDay);
        }
        // æƒ…况二:Excel é‡Œå¡«çš„æ˜¯æ–‡æœ¬ï¼Œå¦‚ 8:30 / 08:30:00
        String text = readCellData.getStringValue();
        if (StrUtil.isBlank(text)) {
            return null;
        }
        try {
            String[] parts = text.trim().replace(':', ':').split(":");
            int hour = Integer.parseInt(parts[0].trim());
            int minute = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 0;
            int second = parts.length > 2 ? Integer.parseInt(parts[2].trim()) : 0;
            return LocalTime.of(hour, minute, second);
        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
            return null;
        }
    }
    @Override
    public WriteCellData<String> convertToExcelData(LocalTime value, ExcelContentProperty contentProperty,
                                                    GlobalConfiguration globalConfiguration) {
        return new WriteCellData<>(value == null ? "" : value.format(TIME_FORMATTER));
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleImportExcelVO.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.convert.LocalTimeConvert;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -24,10 +25,10 @@
    @ExcelProperty("部门名称")
    private String deptName;
    @ExcelProperty("上班时间")
    @ExcelProperty(value = "上班时间", converter = LocalTimeConvert.class)
    private LocalTime workStartTime;
    @ExcelProperty("下班时间")
    @ExcelProperty(value = "下班时间", converter = LocalTimeConvert.class)
    private LocalTime workEndTime;
    @ExcelProperty("迟到允许分钟数")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/outsourcetest/MesQcOutsourceTestController.java
@@ -145,7 +145,7 @@
                        .build()
        );
        // è¾“出
        ExcelUtils.write(response, "委外检测/试验单导入模板.xls", "委外检测/试验单列表", MesQcOutsourceTestImportExcelVO.class, list);
        ExcelUtils.write(response, "委外检测试验单导入模板.xls", "委外检测试验单列表", MesQcOutsourceTestImportExcelVO.class, list);
    }
    @PostMapping("/import")