因工作需要导出数据,导出到excel的格式如下:
需求1:假设有A、B员工,那么就相当于有两张工作表【sheet】
需求2:假设A员工要展示一年的工资,要那么同一工作表【sheet】就得有多个表
再此先奉献上EasyExcel语雀 或者 EasyExcel官网
我最开始看的官网,但是官网又没有语雀写的多。但是耐不住官网的简洁。嗯…我觉得简洁
package com.admin.ctt.entity.excelPojo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 导出员工的合同提成及绩效
* @author LunarYouI
* @create 2022-06-28 17:05
*/
@Data
@EqualsAndHashCode
//所有标题的高
@HeadRowHeight(30)
//所有内容的高
@ContentRowHeight(25)
//所有单元格列宽
@ColumnWidth(25)
public class EmployeeSalaryPojo {
// @ColumnWidth(50) //指定某一个单元格列宽
// @ExcelProperty({"员工工资表", "标题1", "合同提成/绩效", "字符串标题"})
// private String string;
//
// @ExcelProperty({"员工工资表", "标题1", "合同提成/绩效", "日期标题"})
// private Date date;
//
// @ExcelProperty({"员工工资表", "标题1", "合同提成/绩效", "数字标题"})
// private Double doubleData;
//员工名称
private String name;
//员工日期
private Date date;
//员工工资
private Double doubleData;
}
/**
* 数据
* LunarYouI
*/
private static List<EmployeeSalaryPojo> data() {
List<EmployeeSalaryPojo> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
EmployeeSalaryPojo data = new EmployeeSalaryPojo();
data.setName("某人" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
/**
* 标题
* @return
*/
private static Map<String, List<List<String>>> head() {
String a = "管理员:张某 工资时间:2022.01 职位:员工/执行人 姓名:王某";
String b = "管理员:李某 工资时间:2022.01 职位:员工/执行人 姓名:李某";
Map<String, List<List<String>>> mp = new HashMap<>();
/**
* 人1
*/
List<List<String>> head = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("员工工资表");
list1.add(a);
list1.add("合同提成/绩效");
list1.add("员工名称");
head.add(list1);
List<String> list2 = new ArrayList<>();
list2.add("员工工资表");
list2.add(a);
list2.add("合同提成/绩效");
list2.add("员工日期");
head.add(list2);
List<String> list3 = new ArrayList<>();
list3.add("员工工资表");
list3.add(a);
list3.add("合同提成/绩效");
list3.add("员工工资");
head.add(list3);
mp.put("r1",head);
/**
* 人2
*/
List<List<String>> head1 = new ArrayList<>();
List<String> list4 = new ArrayList<>();
list4.add("员工工资表");
list4.add(b);
list4.add("合同提成/绩效");
list4.add("员工名称");
head1.add(list4);
List<String> list5 = new ArrayList<>();
list5.add("员工工资表");
list5.add(b);
list5.add("合同提成/绩效");
list5.add("员工日期");
head1.add(list5);
List<String> list6 = new ArrayList<>();
list6.add("员工工资表");
list6.add(b);
list6.add("合同提成/绩效");
list6.add("员工工资");
head1.add(list6);
mp.put("r2",head1);
return mp;
}
public static void simpleWrite() {
ExcelWriter excelWriter = null;
// 方法2: 如果写到不同的sheet 同一个对象
String fileName = "F:\\temporary\\" + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try {
excelWriter = EasyExcel.write(fileName, EmployeeSalaryPojo.class).build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
//写入到excel和上面空开几行
writeSheet.setRelativeHeadRowIndex(1);
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<EmployeeSalaryPojo> data = data();
excelWriter.write(data, writeSheet);
}
} finally {
if(excelWriter!=null){
excelWriter.finish();
}
}
}
这里没有标题是因为 EmployeeSalaryPojo实体类中的前字段三个被注释了,解开前三个注释后三个就可以了
public static void simpleWrite() {
ExcelWriter excelWriter = null;
// 方法2: 如果写到不同的sheet 同一个对象
String fileName = "F:\\temporary\\" + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try {
excelWriter = EasyExcel.write(fileName, EmployeeSalaryPojo.class).build();
// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();
//距离上边空一行
writeSheet.setRelativeHeadRowIndex(10);
// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();
WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();
// 第一次写入会创建头
excelWriter.write(data(), writeSheet, writeTable0);
// 第二次写如也会创建头,然后在第一次的后面写入数据
excelWriter.write(data(), writeSheet, writeTable1);
} finally {
if(excelWriter!=null){
excelWriter.finish();
}
}
}
public static void simpleWrite() {
ExcelWriter excelWriter = null;
// 方法2: 如果写到不同的sheet 同一个对象
String fileName = "F:\\temporary\\" + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try {
excelWriter = EasyExcel.write(fileName, EmployeeSalaryPojo.class).build();
//设置动态标题
Map<String, List<List<String>>> head = head();
int i = 0;
for (String key : head.keySet()) {
i++;
/**
* 1、获取到value; 获取到的List>就表示是同一个sheet的数据
*/
List<List<String>> lists = head.get(key);
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样【这里必须要加.needHead(Boolean.FALSE),否则会多处标题而没数据】
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).needHead(Boolean.FALSE).build();
//动态标题
writeSheet.setHead(lists);
//写入到excel和上面空开几行
writeSheet.setRelativeHeadRowIndex(1);
/**
* 2、相同数据写在同一个sheet里面
* 这里的j表示1个sheet会有几个表单数据
*/
for (int j = 0;j<3;j++) {
WriteTable writeTable = EasyExcel.writerTable(j).needHead(Boolean.TRUE).build();
excelWriter.write(data(), writeSheet, writeTable);
}
}
} finally {
if(excelWriter!=null){
excelWriter.finish();
}
}
}
下面就是我根据上面的逻辑和自己的实际业务导出来的表,因为这个项目是二开(前任开发留下的问题,导致很多逻辑业务走不通),所以下面代码是我根据设计图然后屎山堆出来的
这里主要看点就动态标题(因为码代码的时候发现一个sheet下面的所有子表全部都显示6月份,按理来说应该是6月份-5月份一直推下去)与单元格合并(因为我这里是每个子表最后一行代码进行合并,所以我重写了AbstractMergeStrategy);如果不考虑一些特殊的合并,那么使用这两个都够了OnceAbsoluteMergeStrategy与LoopMergeStrategy,可自行百度查看
/**
* LunarYouI 导出数据 导出员工的合同与绩效
* package com.admin.ctt.entity.excelPojo;
* @param params
* @param response
*/
@PostMapping("/export")
@RequiresRoles(Role.ADMIN)
public R export(@RequestParam Map<String, Object> params, HttpServletResponse response) throws IOException {
/**
* 思路:
* 1、获取到所有的用户(员工),10个员工就是10个sheet
* 2、按照月份获取每个员工每月对应的数据,获取到就立即输出。【条件为:每个月份显示的数据不同】
*/
ExcelWriter excelWriter = null;
//文件名与后缀名
String fileName = "F:\\temporary\\" + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
try {
//指定文件
excelWriter = EasyExcel
.write(fileName, ExprotEntity.class)
.build();
/**
* 导出的具体数据=============================================================================================
*/
//1、获取当前用户的id
SysUserEntity user = ShiroUtils.getObject();
String id = user.getId();
//2、根据id获取到当前管理员下的人有哪些,前提是没有被删除
sysClientuserService.byManagerIdRecursion(id);
Set<String> set = SysClientuserServiceImpl.set;
List<String> list = new ArrayList<String>(set);
//3、从list筛选出当前管理员下的所有员工
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String ex = iterator.next();
int count = sysUserRoleService.count(new QueryWrapper<SysUserRoleEntity>().eq("user_id", ex).eq("role_id", "8"));
if (count==0) {
iterator.remove();
}
}
//3、循环遍历id;这里有多少个员工就表示有多少个sheet(前提是能在employee_attendance_record表中找到当前员工)
int iii = 0;
for (String userId : list) {
int select = 0;
iii++;
//获取员工的上级(管理员)
String sj = employeeAttendanceRecordService.selectByStaffId(userId);
if(!(sj.equals("0"))){
SysClientuserEntity one = sysClientuserService.getOne(new QueryWrapper<SysClientuserEntity>().eq("account_id", sj).select("true_name"));
sj = one.getTrueName();
}
//员工的姓名
SysClientuserEntity clientuser = sysClientuserService.getOne(new QueryWrapper<SysClientuserEntity>().eq("account_id", userId).select("true_name"));
String ygName = clientuser.getTrueName();
//4、根据员工id获取对应员工的出勤月份(最多12个月); 这里有几个月就表示一个sheet有几个表
QueryWrapper<EmployeeAttendanceRecordEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.select("DATE_FORMAT(attendance_months,\"%Y-%m\") as attendanceMonths")
.eq("importor_id",userId)
.groupBy("attendance_months")
.orderByDesc("attendance_months")
.last("limit 12");
List<EmployeeAttendanceRecordEntity> yue = employeeAttendanceRecordService.list(queryWrapper);
/**
* excel 设置不同的sheet
*/
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样【这里必须要加.needHead(Boolean.FALSE),否则会多处标题而没数据】
WriteSheet writeSheet = EasyExcel.writerSheet(iii, ygName).needHead(Boolean.FALSE).build();
//动态标题
List<List<String>> lists = new ArrayList<>();
//写入到excel和上面空开几行
writeSheet.setRelativeHeadRowIndex(1);
//5、根据月份获取对应的数据
List<List<ExprotEntity>> list1 = new ArrayList<>();//下面有很多个月的数据,每生成一月数据就存储到这里的List>,以便后面循环
// int abc = 0;
for (EmployeeAttendanceRecordEntity y : yue) {
//当前年月(根据这个条件获取对应月份数据)
String attendanceMonths = y.getAttendanceMonths();
//获取当前用户 管理员的名字、年月、职位、姓名
String biaoti = "管理员:"+sj+" 工资时间:"+attendanceMonths+" 姓名:"+ygName;
// if(abc == 0){
// head.clear();
// lists.clear();
//循环6次,因为有6个字段
for(int i = 0;i<6;i++){
List<String> head = new ArrayList<>();
head.add("员工工资表");
head.add(biaoti);
head.add("合同提成/绩效");
switch (i){
case 0:
head.add(ExcelText.NUM);
break;
case 1:
head.add(ExcelText.COMPANY_NAME);
break;
case 2:
head.add(ExcelText.NUMBER);
break;
case 3:
head.add(ExcelText.MONEY);
break;
case 4:
head.add(ExcelText.RETURNED_MONEY);
break;
case 5:
head.add(ExcelText.PUSH_MONEY);
break;
}
lists.add(head);
}
// }
// else{
// lists.set(1,);
// }
// abc+=1;
//【共有字段】获取到了当前用户所有合同的信息【序号、单位名称、合同编号、金额(这里的金额只是合同主产品的金额,服务项目里的是次产品)】
List<ExprotEntity> exprotEntity = contractService.exportSelect(userId);
for (ExprotEntity data : exprotEntity) {
//合同id
String contractId = data.getContractId();
/**
* 获取到当前合同id下的次产品的总金额:金额(次+主)、回款金额、提成金额
*/
//金额(次+主)
ServiceItemEntity contractIdDataOne = serviceItemService.getOne(new QueryWrapper<ServiceItemEntity>().eq("contract_id", contractId).select("sum(price) as sumAll"));
if (contractIdDataOne != null) {
String s = new BigDecimal(contractIdDataOne.getSumAll()).add(new BigDecimal(data.getMoney())).toString();
data.setMoney(s);
}
//回款金额(指定月份 且状态为通过1)
PaybackEntity contractIdDataTwo = paybackService.getOne(new QueryWrapper<PaybackEntity>().eq("contract_id", contractId).eq("status", "1").eq("DATE_FORMAT(create_time,\"%Y-%m\")", attendanceMonths).select("sum(money) as sumAll"));
if (contractIdDataTwo != null) {
data.setReturnedMoney(contractIdDataTwo.getSumAll());
}
/**
* 提成金额(当前合同每一笔回款*对应的产品的比列=一笔提成金额;所有提成金额总和)
* 回款-提成类型(0普通百分比提成,1高提成,2单价提成)-提成价格(百分比/具体价格)
*/
List<Map<String, Object>> push = contractService.push(contractId, attendanceMonths);
BigDecimal sumAll = new BigDecimal("0");
for (Map<String, Object> je : push) {
BigDecimal total = new BigDecimal(je.get("money").toString());
String commissionType = je.get("commission_type").toString();
BigDecimal commissionPrice = new BigDecimal(je.get("commission_price").toString());
//0普通百分比提成,1高提成 都是百分比计算
if(commissionType.equals("0") || commissionType.equals("1")){
BigDecimal divide = commissionPrice.divide(new BigDecimal("100"), 2, BigDecimal.ROUND_DOWN);
BigDecimal multiply = total.multiply(divide);
sumAll = sumAll.add(multiply);
}
//2单价提成 拿实际价格
else{
sumAll = sumAll.add(commissionPrice);
}
}
data.setPushMoney(sumAll);
}
//回款为null就删除当前对象
BigDecimal pushMoney = new BigDecimal("0");
BigDecimal perf = new BigDecimal("0");
Iterator<ExprotEntity> it = exprotEntity.iterator();
Map<String, String> objectObjectHashMap = new HashMap<>();
while (it.hasNext()) {
ExprotEntity ex = it.next();
if (ex.getReturnedMoney()==null) {
it.remove();
}else{
/**
* 每个合同产生的绩效提成
*/
BigDecimal money = new BigDecimal(ex.getMoney());
//获取合同绩效分成比列
List<DataInfoEntity> dataInfo = dataInfoService.list();
if(dataInfo.size()>0){
List<Map<String, Object>> perfItemList = new ArrayList<>();
for (DataInfoEntity m : dataInfo) {
Map<String, Object> item = new HashMap<>();
JSONObject object = JSON.parseObject(m.getItemValue());
if (m.getScope().equals(DataInfoItemKey.Perf)) {
if (object.containsKey(DataInfoItemKey.Perf_Start)) {
item.put(DataInfoItemKey.Perf_Start, new BigDecimal(object.get(DataInfoItemKey.Perf_Start).toString()));
}
if (object.containsKey(DataInfoItemKey.Perf_End)) {
item.put(DataInfoItemKey.Perf_End, new BigDecimal(object.get(DataInfoItemKey.Perf_End).toString()));
}
if (object.containsKey(DataInfoItemKey.Perf_Percent)) {
item.put(DataInfoItemKey.Perf_Percent, new BigDecimal(object.get(DataInfoItemKey.Perf_Percent).toString()));
}
perfItemList.add(item);
}
}
//对value值进行排序
Collections.sort(perfItemList, new Comparator<Map<String, Object>>() {
@Override
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
BigDecimal end1 = new BigDecimal(o1.get("end").toString());
BigDecimal end2 = new BigDecimal(o2.get("end").toString());
return end2.compareTo(end1);
}
});
//最终绩效金额【如果最大的都小了就取第一个,否则向下走】
for (Map<String, Object> mp : perfItemList) {
BigDecimal end = new BigDecimal(mp.get("end").toString());
BigDecimal start = new BigDecimal(mp.get("start").toString());
if(money.compareTo(end) == 1){
//1/100 * 金额 = 绩效
BigDecimal percent1 = new BigDecimal(mp.get("percent").toString());
BigDecimal percent2 = percent1.divide(new BigDecimal("100"),2,BigDecimal.ROUND_DOWN);
BigDecimal divide = money.multiply(percent2);
ex.setPerf(divide.toString());
break;
}else if(start.compareTo(money) == -1){
BigDecimal percent1 = new BigDecimal(mp.get("percent").toString());
BigDecimal percent2 = percent1.divide(new BigDecimal("100"),2,BigDecimal.ROUND_DOWN);
BigDecimal divide = money.multiply(percent2);
ex.setPerf(divide.toString());
break;
}
}
}else{
throw new RRException("请检查=》数据管理—数据信息-绩效");
}
//获取合同金额给到map(重复的合同金额会被顶替)
objectObjectHashMap.put(ex.getContractId(),ex.getMoney());
//不为null的就累加提成金额
pushMoney = pushMoney.add(ex.getPushMoney());
}
}
Iterator<ExprotEntity> itTwo = exprotEntity.iterator();
while (itTwo.hasNext()) {
ExprotEntity ex = itTwo.next();
//不为null的就累加提成金额
perf = perf.add(new BigDecimal(ex.getPerf()));
}
//将objectObjectHashMap的合同金额遍历相加
BigDecimal bl = new BigDecimal("0");
for (String k : objectObjectHashMap.keySet()) {
bl = bl.add(new BigDecimal(objectObjectHashMap.get(k)));
}
//追加一行空的用于后面合并单元格
ExprotEntity exprotEntity1 = new ExprotEntity();
exprotEntity1.setNum("绩效合计:"+bl);
exprotEntity1.setCompanyName("绩效提成:"+perf);
exprotEntity1.setNumber("合同提成合计:");
exprotEntity1.setMoney("4");
exprotEntity1.setReturnedMoney("5");
exprotEntity1.setPushMoney(pushMoney);
exprotEntity.add(exprotEntity1);
list1.add(exprotEntity);
}
// //添加动态标题
// writeSheet.setHead(lists);
/**
* excel 给同一个sheet给数据 .registerWriteHandler(loopMergeStrategy)
*/
for (int i = 0;i<list1.size();i++) {
int size = list1.get(i).size();
//因为上面每次显示都只显示一个月份,所以就把头放到这儿了,但是一个表有很多月份表头,所以这里就重新获取了一下表头数据
List<List<String>> head2 = new ArrayList<>();
for (int abc = 0; abc < 6 ; abc++) {
head2.add(lists.get(select));
select = select+1;
}
// OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(6,6,2,4);
// LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
MyMergeStrategy my = new MyMergeStrategy(size,size,2,4);
WriteTable writeTable = EasyExcel.writerTable(i).needHead(Boolean.TRUE).head(head2).registerWriteHandler(my).build();
excelWriter.write(list1.get(i), writeSheet, writeTable);
}
}
}finally {
if(excelWriter!=null){
excelWriter.finish();
}
}
return R.ok();
}
根据自己业务重写了AbstractMergeStrategy(每个子表最后一行的指定行列进行单元合并)
package com.admin.slr.responseEntity;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.ArrayList;
import java.util.List;
/**
* @author LunarYouI
* @create 2022-07-06 16:03
*/
public class MyMergeStrategy extends AbstractMergeStrategy {
private int firstRowIndex;
private int lastRowIndex;
private int firstColumnIndex;
private int lastColumnIndex;
public MyMergeStrategy(int firstRowIndex, int lastRowIndex, int firstColumnIndex, int lastColumnIndex) {
if (firstRowIndex >= 0 && lastRowIndex >= 0 && firstColumnIndex >= 0 && lastColumnIndex >= 0) {
this.firstRowIndex = firstRowIndex;
this.lastRowIndex = lastRowIndex;
this.firstColumnIndex = firstColumnIndex;
this.lastColumnIndex = lastColumnIndex;
} else {
throw new IllegalArgumentException("All parameters must be greater than 0");
}
}
//导出数据需要导入 6 个字段
private static int num = 6;
private static int i = 1;
//同一sheet不同表单的行号
private static int trueLine = 0;
//保存下一次行的行号
private static int line = 0;
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
//获取当前的行
int rowIndex = cell.getRowIndex();
//获取当前的列
int columnIndex = cell.getColumnIndex();
if(line != 0){
//如果当前行 等于 上一次记录的行数,那代表还是一个sheet里的第一张表
if(rowIndex == line){
//是否进入下一行
boolean flag = bbbb();
if(flag){
// System.out.println("================================111111");
//保存下一次行号
line = rowIndex+1;
//调用接口
aaaaa(sheet,trueLine,rowIndex,columnIndex);
//记录当前行号(为真就表示当前行已经结束,开始下一行了)
trueLine = trueLine+1;
}else{
// System.out.println("================================222222");
//调用接口
aaaaa(sheet,trueLine,rowIndex,columnIndex);
}
}
//否则第二张表
else{
// System.out.println("================================333333");
//1、调用接口
bbbb();
//2、记录当前行号(不等就表示开始下一行,下一行起始为1)
trueLine = 1;
//3、保存下一次行号(这里没法判断是否该下一行号,所以这里就定为rowIndex的值)
line = rowIndex;
//4、调用接口
aaaaa(sheet,trueLine,rowIndex,columnIndex);
}
}else{
// System.out.println("================================444444 ");
bbbb();
//1、记录当前行号
trueLine = 1;
//2、保存下一次行号(这里没法判断是否该下一行号,所以这里就定为rowIndex的值)
line = rowIndex;
//3、调用接口
aaaaa(sheet,trueLine,rowIndex,columnIndex);
}
}
/**
* 合并数据
* @param sheet
* @param a 修改行
* @param b 原本行
* @param c 原本列
*/
private void aaaaa(Sheet sheet, int a, int b, int c) {
// System.out.println("修改行:"+a);
// System.out.println("原本行:"+b);
if (a == this.firstRowIndex && c == this.firstColumnIndex) {
//这里的两个b表示 将第N行~N行的第N列~N列进行合并
CellRangeAddress cellRangeAddress = new CellRangeAddress(b, b, this.firstColumnIndex, this.lastColumnIndex);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
}
/**
* 表示一行数据有N个字段,每走一次消耗掉一个字段
* @return
*/
private boolean bbbb() {
if(i<6){
i = i+1;
return false;
}else{
i = 1;
return true;
}
}
}