使用EasyExcel导出表格,实现动态获取下拉框内容

需求:导出数据并且指定的列通过下拉框展示,不可以自定义编辑

一、导出数据

1.1、创建Vo类,添加@ExcelProperty注解,实现表头展示

@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ZnCarParkVehicleRecord.class)
public class ZnCarParkVehicleRecordVo implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 唯一标识
     */
    @ExcelProperty(value = "唯一标识")
    private Long id;

    /**
     * 停车场id
     */
    @ExcelProperty(value = "停车场id")
    private Long modelId;

    /**
     * 停车场收费规则
     */
    @ExcelProperty(value = "停车场收费规则")
    private String parkingLotConfig;

    /**
     * 车位id
     */
    @ExcelProperty(value = "车位id")
    private Long parkingSpaceId;

    /**
     * 车牌号
     */
    @ExcelProperty(value = "车牌号")
    private String carPlateNumber;

    /**
     * 记录状态 0免费 1未缴费 2已交费3待缴费
     */
    @ExcelProperty(value = "记录状态 0免费 1未缴费 2已交费3待缴费")
    private Integer status;

    /**
     * 进出状态:0进 1出
     */
    @ExcelProperty(value = "进出状态:0进 1出")
    private Integer inOut;

    /**
     * 进场时间
     */
    @ExcelProperty(value = "进场时间")
    private Date inTime;

}

1.2、controller

    /**
     * 导出停车场车辆出入记录列表
     */
    @SaCheckPermission("system:carParkVehicleRecord:export")
    @Log(title = "停车场车辆出入记录", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(ZnCarParkVehicleRecordBo bo, HttpServletResponse response) {
        List<ZnCarParkVehicleRecordVo> list = znCarParkVehicleRecordService.queryList(bo);
        ExcelUtil.exportExcel(list, "停车场车辆出入记录", ZnCarParkVehicleRecordVo.class, response);
    }

1.3、 工具类

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {
/**
 * 导出excel
 *
 * @param list      导出数据集合
 * @param sheetName 工作表的名称
 * @param clazz     实体类
 * @param response  响应体
 */
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
    try {
        resetResponse(sheetName, response);
        ServletOutputStream os = response.getOutputStream();
        exportExcel(list, sheetName, clazz, false, os, null);
    } catch (IOException e) {
        throw new RuntimeException("导出Excel异常");
    }
}

通过以上代码,就可以实现数据的导出。

二、导出数据(某列动态展示,展示内容通过数据库查询)

注:此处省去Vo类的创建,可参考上面的Vo类。

2.1、controller

/**
 * 导出Excel功能(指定列下拉框)
 *
 * @author Lion Li
 */
@RestController
@RequestMapping("/excel")
public class EasyExcelController {

    @Autowired
    private IEasyExcelService excelService;

    /**
     * 导出下拉框
     *
     * @param response /
     */
    @GetMapping("/exportWithOptions")
    public void exportWithOption(HttpServletResponse response) {
        excelService.getEasyExcelExcel(response);
    }
}

2.2、 service

public interface IEasyExcelService {

    /**
     * 导出下拉框
     *
     * @param response /
     */
    void getEasyExcelExcel(HttpServletResponse response);
}

2.3 serviceImpl

/**
 * 导出下拉框Excel示例
 *
 * @author Emil.Zhang
 */
@Service
@RequiredArgsConstructor
public class EasyExcelServiceImpl implements IEasyExcelService {

    @Resource
    private IZnCarService znCarService;

    @Override
    public void getEasyExcelExcel(HttpServletResponse response) {
        //查询数据库数据 excelDataList
        ZnCarBo znCarBo = new ZnCarBo();
        List<ZnCarVo> excelDataList = znCarService.queryList(znCarBo);


        //查询数据库数据 carList
        List<ZnCarVo> carList = getProvinceList();

        //存储要展示下拉框的数据 parkingNameList
        ArrayList<String> parkingNameList = new ArrayList<>();
        for (ZnCarVo znCarVo : carList) {
            parkingNameList.add(znCarVo.getParkingLotName());
        }

        //对parkingNameList中重复的数据去重
        List<String> collectList = parkingNameList.stream().distinct().collect(Collectors.toList());

        //下拉框所在的列以及数据
        DropDownOptions dropDown = new DropDownOptions(2, collectList);

        // 把所有的下拉框存储
        List<DropDownOptions> options = new ArrayList<>();
        options.add(dropDown);

        //将Excel中的展示数据转换为对应的下拉选
        List<ZnCarVo> znCarVoList = StreamUtils.toList(excelDataList, everyRowData -> {
            everyRowData.setParkingLotName(everyRowData.getParkingLotName());
            return everyRowData;
        });


        ExcelUtil.exportExcel(znCarVoList,"下拉框示例",ZnCarVo.class,response,options);

    }


    private List<ZnCarVo> getProvinceList() {
        ZnCarBo znCarBo = new ZnCarBo();
        List<ZnCarVo> znCarVos = znCarService.queryList(znCarBo);
        return znCarVos;
    }
}
DropDownOptions.class代码如下
@Data
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("unused")
public class DropDownOptions {
    /**
     * 一级下拉所在列index,从0开始算
     */
    private int index = 0;
    /**
     * 二级下拉所在的index,从0开始算,不能与一级相同
     */
    private int nextIndex = 0;
    /**
     * 一级下拉所包含的数据
     */
    private List<String> options = new ArrayList<>();
    /**
     * 二级下拉所包含的数据Map
     * <p>以每一个一级选项值为Key,每个一级选项对应的二级数据为Value</p>
     */
    private Map<String, List<String>> nextOptions = new HashMap<>();
    /**
     * 分隔符
     */
    private static final String DELIMITER = "_";

    /**
     * 创建只有一级的下拉选
     */
    public DropDownOptions(int index, List<String> options) {
        this.index = index;
        this.options = options;
    }

    /**
     * <h2>创建每个选项可选值</h2>
     * <p>注意:不能以数字,特殊符号开头,选项中不可以包含任何运算符号</p>
     *
     * @param vars 可选值内包含的参数
     * @return 合规的可选值
     */
    public static String createOptionValue(Object... vars) {
        StringBuilder stringBuffer = new StringBuilder();
        String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
        for (int i = 0; i < vars.length; i++) {
            String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
            if (!var.matches(regex)) {
                throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
            }
            stringBuffer.append(var);
            if (i < vars.length - 1) {
                // 直至最后一个前,都以_作为切割线
                stringBuffer.append(DELIMITER);
            }
        }
        if (stringBuffer.toString().matches("^\\d_*$")) {
            throw new ServiceException("禁止以数字开头");
        }
        return stringBuffer.toString();
    }

    /**
     * 将处理后合理的可选值解析为原始的参数
     *
     * @param option 经过处理后的合理的可选项
     * @return 原始的参数
     */
    public static List<String> analyzeOptionValue(String option) {
        return StrUtil.split(option, DELIMITER, true, true);
    }

    /**
     * 创建级联下拉选项
     *
     * @param parentList                  父实体可选项原始数据
     * @param parentIndex                 父下拉选位置
     * @param sonList                     子实体可选项原始数据
     * @param sonIndex                    子下拉选位置
     * @param parentHowToGetIdFunction    父类如何获取唯一标识
     * @param sonHowToGetParentIdFunction 子类如何获取父类的唯一标识
     * @param howToBuildEveryOption       如何生成下拉选内容
     * @return 级联下拉选项
     */
    public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
                                                         int parentIndex,
                                                         List<T> sonList,
                                                         int sonIndex,
                                                         Function<T, Number> parentHowToGetIdFunction,
                                                         Function<T, Number> sonHowToGetParentIdFunction,
                                                         Function<T, String> howToBuildEveryOption) {
        DropDownOptions parentLinkSonOptions = new DropDownOptions();
        // 先创建父类的下拉
        parentLinkSonOptions.setIndex(parentIndex);
        parentLinkSonOptions.setOptions(
            parentList.stream()
                .map(howToBuildEveryOption)
                .collect(Collectors.toList())
        );
        // 提取父-子级联下拉
        Map<String, List<String>> sonOptions = new HashMap<>();
        // 父级依据自己的ID分组
        Map<Number, List<T>> parentGroupByIdMap =
            parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
        // 遍历每个子集,提取到Map中
        sonList.forEach(everySon -> {
            if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
                // 找到对应的上级
                T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
                // 提取名称和ID作为Key
                String key = howToBuildEveryOption.apply(parentObj);
                // Key对应的Value
                List<String> thisParentSonOptionList;
                if (sonOptions.containsKey(key)) {
                    thisParentSonOptionList = sonOptions.get(key);
                } else {
                    thisParentSonOptionList = new ArrayList<>();
                    sonOptions.put(key, thisParentSonOptionList);
                }
                // 往Value中添加当前子集选项
                thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
            }
        });
        parentLinkSonOptions.setNextIndex(sonIndex);
        parentLinkSonOptions.setNextOptions(sonOptions);
        return parentLinkSonOptions;
    }
}

通过以上代码,停车场名字这一列展示的将会如下图所示,停车场名字将通过从数据库中查询出来进行动态的展示。

三、导出数据(多列动态展示,展示内容通过数据库查询)

3.1、 controller

    /**
     * 导出下拉框
     *
     * @param response /
     */
    @GetMapping("/exportWithOptions")
    public void exportWithOptions(HttpServletResponse response) {
        exportExcelService.exportWithOptions(response);
    }

3.2、service

/**
 * 导出下拉框Excel示例
 *
 * @author Emil.Zhang
 */
public interface IExportExcelService {

    /**
     * 导出下拉框
     *
     * @param response /
     */
    void exportWithOptions(HttpServletResponse response);
}

3.3、serviceImpl

/**
 * 导出下拉框Excel示例
 *
 * @author Emil.Zhang
 */
@Service
@RequiredArgsConstructor
public class ExportExcelServiceImpl implements IExportExcelService {

    @Override
    public void exportWithOptions(HttpServletResponse response) {
        // 创建表格数据,业务中一般通过数据库查询
        List<ExportDemoVo> excelDataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            // 模拟数据库中的一条数据
            ExportDemoVo everyRowData = new ExportDemoVo();
            everyRowData.setNickName("用户-" + i);
            everyRowData.setUserStatus(UserStatus.OK.getCode());
            everyRowData.setGender("1");
            everyRowData.setPhoneNumber(String.format("175%08d", i));
            everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
            everyRowData.setProvinceId(i);
            everyRowData.setCityId(i);
            everyRowData.setAreaId(i);
            excelDataList.add(everyRowData);
        }

        // 通过@ExcelIgnoreUnannotated配合@ExcelProperty合理显示需要的列
        // 并通过@DropDown注解指定下拉值,或者通过创建ExcelOptions来指定下拉框
        // 使用ExcelOptions时建议指定列index,防止出现下拉列解析不对齐

        // 首先从数据库中查询下拉框内的可选项
        // 这里模拟查询结果
        List<DemoCityData> provinceList = getProvinceList(),
            cityList = getCityList(provinceList),
            areaList = getAreaList(cityList);
        int provinceIndex = 5, cityIndex = 6, areaIndex = 7;

        DropDownOptions provinceToCity = DropDownOptions.buildLinkedOptions(
            provinceList,
            provinceIndex,
            cityList,
            cityIndex,
            DemoCityData::getId,
            DemoCityData::getPid,
            everyOptions -> DropDownOptions.createOptionValue(
                everyOptions.getName(),
                everyOptions.getId()
            )
        );

        DropDownOptions cityToArea = DropDownOptions.buildLinkedOptions(
            cityList,
            cityIndex,
            areaList,
            areaIndex,
            DemoCityData::getId,
            DemoCityData::getPid,
            everyOptions -> DropDownOptions.createOptionValue(
                everyOptions.getName(),
                everyOptions.getId()
            )
        );

        // 把所有的下拉框存储
        List<DropDownOptions> options = new ArrayList<>();
        options.add(provinceToCity);
        options.add(cityToArea);

        // 到此为止所有的下拉框可选项已全部配置完毕

        // 接下来需要将Excel中的展示数据转换为对应的下拉选
        List<ExportDemoVo> outList = StreamUtils.toList(excelDataList, everyRowData -> {
            // 只需要处理没有使用@ExcelDictFormat注解的下拉框
            // 一般来说,可以直接在数据库查询即查询出省市县信息,这里通过模拟操作赋值
            everyRowData.setProvince(buildOptions(provinceList, everyRowData.getProvinceId()));
            everyRowData.setCity(buildOptions(cityList, everyRowData.getCityId()));
            everyRowData.setArea(buildOptions(areaList, everyRowData.getAreaId()));
            return everyRowData;
        });

        ExcelUtil.exportExcel(outList, "下拉框示例", ExportDemoVo.class, response, options);
    }

    private String buildOptions(List<DemoCityData> cityDataList, Integer id) {
        Map<Integer, List<DemoCityData>> groupByIdMap =
            cityDataList.stream().collect(Collectors.groupingBy(DemoCityData::getId));
        if (groupByIdMap.containsKey(id)) {
            DemoCityData demoCityData = groupByIdMap.get(id).get(0);
            return DropDownOptions.createOptionValue(demoCityData.getName(), demoCityData.getId());
        } else {
            return StrUtil.EMPTY;
        }
    }

    /**
     * 模拟查询数据库操作
     *
     * @return /
     */
    private List<DemoCityData> getProvinceList() {
        List<DemoCityData> provinceList = new ArrayList<>();

        // 实际业务中一般采用数据库读取的形式,这里直接拼接创建
        provinceList.add(new DemoCityData(0, null, "安徽省"));
        provinceList.add(new DemoCityData(1, null, "江苏省"));

        return provinceList;
    }

    /**
     * 模拟查找数据库操作,需要连带查询出省的数据
     *
     * @param provinceList 模拟的父省数据
     * @return /
     */
    private List<DemoCityData> getCityList(List<DemoCityData> provinceList) {
        List<DemoCityData> cityList = new ArrayList<>();

        // 实际业务中一般采用数据库读取的形式,这里直接拼接创建
        cityList.add(new DemoCityData(0, 0, "合肥市"));
        cityList.add(new DemoCityData(1, 0, "芜湖市"));
        cityList.add(new DemoCityData(2, 1, "南京市"));
        cityList.add(new DemoCityData(3, 1, "无锡市"));
        cityList.add(new DemoCityData(4, 1, "徐州市"));

        selectParentData(provinceList, cityList);

        return cityList;
    }

    /**
     * 模拟查找数据库操作,需要连带查询出市的数据
     *
     * @param cityList 模拟的父市数据
     * @return /
     */
    private List<DemoCityData> getAreaList(List<DemoCityData> cityList) {
        List<DemoCityData> areaList = new ArrayList<>();

        // 实际业务中一般采用数据库读取的形式,这里直接拼接创建
        areaList.add(new DemoCityData(0, 0, "瑶海区"));
        areaList.add(new DemoCityData(1, 0, "庐江区"));
        areaList.add(new DemoCityData(2, 1, "南宁县"));
        areaList.add(new DemoCityData(3, 1, "镜湖区"));
        areaList.add(new DemoCityData(4, 2, "玄武区"));
        areaList.add(new DemoCityData(5, 2, "秦淮区"));
        areaList.add(new DemoCityData(6, 3, "宜兴市"));
        areaList.add(new DemoCityData(7, 3, "新吴区"));
        areaList.add(new DemoCityData(8, 4, "鼓楼区"));
        areaList.add(new DemoCityData(9, 4, "丰县"));

        selectParentData(cityList, areaList);

        return areaList;
    }

    /**
     * 模拟数据库的查询父数据操作
     *
     * @param parentList /
     * @param sonList    /
     */
    private void selectParentData(List<DemoCityData> parentList, List<DemoCityData> sonList) {
        Map<Integer, List<DemoCityData>> parentGroupByIdMap =
            parentList.stream().collect(Collectors.groupingBy(DemoCityData::getId));

        sonList.forEach(everySon -> {
            if (parentGroupByIdMap.containsKey(everySon.getPid())) {
                everySon.setPData(parentGroupByIdMap.get(everySon.getPid()).get(0));
            }
        });
    }

    /**
     * 模拟的数据库省市县
     */
    @Data
    private static class DemoCityData {
        /**
         * 数据库id字段
         */
        private Integer id;
        /**
         * 数据库pid字段
         */
        private Integer pid;
        /**
         * 数据库name字段
         */
        private String name;
        /**
         * MyBatisPlus连带查询父数据
         */
        private DemoCityData pData;

        public DemoCityData(Integer id, Integer pid, String name) {
            this.id = id;
            this.pid = pid;
            this.name = name;
        }
    }
}

注:参考以上代码,替换成从数据库查询并替换成正确的Vo类,即可实现功能。

如果没有权限验证,可以直接通过浏览器测试,如果涉及到权限验证,可以通过Postman测试

键为Authorization,值为你获取到的token

  • 13
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值