关于springboot+vue的excel导入导出

       在做项目的过程中,外面经常会遇到一个问题,怎么才能把excel表中的数据实现批量的导入导出,使用的是EasyExcel进行操作。

一、项目前准备

1、依赖导入

在pom文件中添加对应的依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.3</version>
</dependency>

2、实体类准备

原数据库字段过多,这边就截取一部分进行展示

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("emp_user")
public class EmpUserEntity implements Serializable {
	private static final long serialVersionUID = 1L;
	/**
	 * 人员id
	 */
	@ExcelIgnore
	private Long id;
	/**
	 * 用户ID
	 */
	@ExcelIgnore
	private Long userId;
	/**
	 * 密码
	 */
	@TableField(exist = false)
	@ExcelIgnore
	private String password;
	/**
	 * 姓名
	 */
	@ExcelProperty(value = {"姓名"}, index = 0)
	private String name;
	/**
	 * 性别:1:男  0:女
	 */
	@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class)
	//静态下拉框
	@ExcelSelected(source = {"男","女"})
	private Integer gender;
	/**
	 * 年龄(计算退休时间)
	 */
	@ExcelProperty(value = {"年龄"}, index = 2)
	private Integer age;
	/**
	 * 电话
	 */
	@ExcelProperty(value = {"电话"}, index = 3)
	@ColumnWidth(20)
	private String tel;
	/**
	 * 邮箱
	 */
	@ExcelProperty(value = {"邮箱"}, index = 4)
	@ColumnWidth(25)
	private String email;
}

注释解释:

@ExcelIgnore:EasyExcel框架中的注解,用于标识实体类中不需要导出或导入的属性。

@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class):指定实体类与表格之间的映射关系

       value表示excel表格的标题列。

       index表示excel表格中的列索引。

       converter = SexConvert.class:指定类型转换器,将Excel表格中的数据转换成Java对象。
@ExcelSelected(source = {"男","女"}):导出下拉框自定义注解

3、类型转换器

       有些数据外面存入数据库的是数字,例如一般男生我们选用1,女生选用0,当时当我们进行表的输出和输入时,excel表中用的是男、女,我们采用类型转换器进行数据的转化。

(1)实体类中添加注释
@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class)
//SexConvert.class定义类转化器的类
(2)转换类的编写
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

public class SexConvert implements Converter<Integer> {
    @Override
    //指定该转换器支持的 Java 类型。在本例中,返回 Integer.class,表示该转换器用于将 Excel 中的数据转换为整数类型。
    public Class supportJavaTypeKey() {
        return Integer.class;
    }

    @Override
    //指定该转换器支持的 Excel 数据类型。在本例中,返回 CellDataTypeEnum.STRING,表示该转换器处理的是字符串类型的 Excel 数据。
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    //将 Excel 中的数据转换为 Java 对象的方法。在本例中,该方法尚未实现,因此返回 null。你需要根据实际需求,实现具体的转换逻辑。
    public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return "男".equals(cellData.getStringValue()) ? 1 : 0;
    }

    @Override
    //将 Java 对象转换为 Excel 数据的方法。
    public CellData convertToExcelData(Integer value, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        if (value == 1) {
            return new CellData("男");
        } else if (value == 0) {
            return new CellData("女");
        }
        // 如果值不是 Integer 或者不是合法的性别值,则抛出异常
        throw new IllegalArgumentException("无效的性别值");
    }
}

  其中第三个和第四个方法代表 java对象与excel表里的相互转换。

*注意包不要导错喔

二、导入功能的实现

1、后端功能实现:

(1)自定义监听器,对下载的excel中的数据进行校验。
public class WebUserListener<T> extends AnalysisEventListener<T> {

    //首先使用 Logger 记录当前读取的数据信息,然后将读取到的数据对象 t 加入到 list 列表中,以备后续的获取
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    public List<T> list = new ArrayList<>();

    /**
     * 该方法在读取到表格中的一条数据时被调用。其中 t 参数表示当前行的数据对象,analysisContext 参数表示解析过程的上下文对象。
     * @param t
     * @param
     */
    @Override
    public void invoke(T t, AnalysisContext analysisContext) {
        log.info("读取表格[{}]",t);
        list.add(t);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
   //获取数据,供controller层调用
    public List<T> getExcelData(){
        return list;
    }

}
(2)controller层:
@RestController
@RequestMapping("emp/excel")
public class EmpUserImport extends AbstractController {

    @Autowired
    private EmpUserService empUserService;

    @PostMapping("/Import")
    //@RequiresPermissions("emp:user:excelImport")
    public EmpUserEntity redExcel(MultipartFile multipartFile) throws IOException {
        //输入流指向了上传的 Excel 文件
        InputStream inputStream = multipartFile.getInputStream();
        //实例是一个 EasyExcel 的监听器,用于解析 Excel 数据。
        WebUserListener<EmpUserEntity> webListener = new WebUserListener<>();
        //用于处理 Excel 数据的监听器,表示对 Excel 中所有的 sheet 进行解析。
        EasyExcel.read(inputStream,EmpUserEntity.class,webListener).sheet().doRead();
        //获取数据对象列表。
        List<EmpUserEntity> excelData = webListener.getExcelData();
       //将获取到的数据填入数据库
       for (EmpUserEntity user : excelData) {
            empUserService.save(user);
        }
     return excelData ;
    }

2、前端代码

(1)触发按钮
<el-form-item >
        <el-upload
          class="upload-demo"
          :action="excelImports"
          :headers="tokenInfo"
          :on-preview="handlePreview"
          :on-remove="handleRemove"
          :on-success="uploadSuccess"
          multiple
          name="multipartFile"
          :limit="3"
        >
          <el-button  type="success" >点击上传</el-button>
        </el-upload>
    </el-form-item>
(2)接口定义
data () {
      return {
        excelImports: this.$http.adornUrl('/emp/excel/Import'),
        tokenInfo: {
          'token': this.$cookie.get('token')         
        },
      dataForm: {
          deptId: ''  //部门id
        },
    .........
      }
}
(3)方法补充
 uploadSuccess (response, file, fileList) {
      this.getDataList()
      console.log(response)
     },
           
 handleRemove (file, fileList) {
     console.log(file, fileList)
      },
           
  handlePreview (file) {
     console.log(file)
   }

三、导出功能实现

 可实现导出附加功能:

  • 可实现按部门进行导入导出,该页面显示按照部门id进行输入选择,可选择其他条件进行筛选,或自行设置级联选择。
  • 让导出的excel表实现下拉框的选择

1、后端代码

(1)创建实现下拉框选择的注解

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {

    /**
     * 固定下拉内容
     */
    String[] source() default {};

    /**
     * 设置下拉框的起始行,默认为第二行
     */
    int firstRow() default 1;

    /**
     * 设置下拉框的结束行,默认为最后一行
     */
    int lastRow() default 0x10000;

}

(2)自定义注解使用

	/**
	 * 性别:1:男  0:女
	 */
	@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class)
	//静态下拉框
	@ExcelSelected(source = {"男","女"})
	private Integer gender;

(2)创建导出工具类

@Slf4j
public class EasyExcelUtils {
    /**
     * 导出单sheet页且sheet页中含有下拉框的excel文件
     *
     * @param response  HttpServletResponse
     * @param fileName  文件名
     * @param sheetName sheet页名
     * @param data      要导出的数据
     */
    public static <T> void writeExcelBySelect(HttpServletResponse response, String fileName, String sheetName, List<T> data) {
        try {
            encodeFileName(response, fileName);
            Map<Integer, ExcelSelected> integerExcelSelectedMap = resolveSelectedAnnotation(IterUtil.getElementType(data));
            EasyExcel.write(response.getOutputStream(), IterUtil.getElementType(data))
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .registerWriteHandler(selectedSheetWriteHandler(integerExcelSelectedMap))
                    .sheet(StringUtils.isEmpty(sheetName) ? "Sheet1" : sheetName)
                    .doWrite(data);
        } catch (IOException e) {
            log.error("导出excel文件异常", e);
        }
    }

    /**
     * 设置文件名
     *
     * @param response HttpServletResponse
     * @param fileName 文件名
     */
    private static void encodeFileName(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s\"", fileName + ".xlsx"));
        response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
        response.setHeader(HttpHeaders.PRAGMA, "no-cache");
        response.setDateHeader(HttpHeaders.EXPIRES, -1);
    }

    /**
     * 解析表头类中的下拉注解
     *
     * @param head 表头类
     * @return Map<下拉框列索引, 下拉框内容> map
     */
    private static <T> Map<Integer, ExcelSelected> resolveSelectedAnnotation(Class<T> head) {
        Map<Integer, ExcelSelected> selectedMap = new HashMap<>(16);
        Field[] fields = head.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (selected != null) {
                if (property != null && property.index() >= 0) {
                    selectedMap.put(property.index(), selected);
                }
            }
        }
        return selectedMap;
    }

    /**
     * 为excel创建下拉框
     *
     * @param selectedMap 下拉框配置数据 Map<下拉框列索引, 下拉框内容>
     * @return intercepts handle sheet creation
     */
    private static SheetWriteHandler selectedSheetWriteHandler(Map<Integer, ExcelSelected> selectedMap) {
        return new SheetWriteHandler() {
            @Override
            public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

            }

            @Override
            public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
                //获取正在处理的工作表对象
                Sheet sheet = writeSheetHolder.getSheet();
                //获得一个用于创建数据验证规则的辅助对象,用于创建和配置数据验证规则
                DataValidationHelper helper = sheet.getDataValidationHelper();
                selectedMap.forEach((k, v) -> {//其中 (k, v) 表示 Map 的键和值,在每次迭代时会传入这两个参数。
                    // 获取固定下拉框的内容
                    List<String> source = new ArrayList<>();
                    //如果 ExcelSelected 注解中的 source 数组不为空,则将其转换为 List,并添加到 source 变量中
                    if (v.source().length > 0) {
                        source.addAll(Arrays.asList(v.source()));
                    }

                    //CollUtil.isNotEmpty(source) 是一个对集合 source 进行非空判断的工具方法。
                    if (CollUtil.isNotEmpty(source)) {
                        /**
                         * 代码创建一个 CellRangeAddressList 对象 rangeList,用于定义下拉框的范围。
                         * 其中,v.firstRow() 和 v.lastRow() 分别表示下拉框的起始行和结束行,k 表示下拉框所在的列
                         */
                        CellRangeAddressList rangeList = new CellRangeAddressList(v.firstRow(), v.lastRow(), k, k);
                        //用于定义下拉框的约束。该约束指定下拉框的选项为 source 列表中的内容
                        DataValidationConstraint constraint = helper.createExplicitListConstraint(source.toArray(new String[0]));
                        DataValidation validation = helper.createValidation(constraint, rangeList);
                        //代码设置数据验证规则的错误样式为停止(DataValidation.ErrorStyle.STOP)
                        validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
                        //显示错误提示框(validation.setShowErrorBox(true))
                        validation.setShowErrorBox(true);
                        //隐藏下拉箭头(validation.setSuppressDropDownArrow(true))
                        validation.setSuppressDropDownArrow(true);
                        validation.createErrorBox("提示", "请输入下拉选项中的内容");
                        sheet.addValidationData(validation);
                    }
                });
            }
        };
    }
}

 (3)controller层

@GetMapping("/export")
    public void exportData(@RequestParam(required = false) String deptId,//查询条件可不存在
                           HttpServletResponse response) {
        // Data
        QueryWrapper<EmpUserEntity> wrapper = new QueryWrapper<>();
        wrapper.like(StrUtil.isNotBlank(deptId), "dept_id", deptId);

        //将部门id变为部门名称
        List<EmpUserEntity> list = empUserService.list(wrapper);

        EasyExcelUtils.writeExcelBySelect(response, "测试", "用户信息表", list);
}

2、前端代码

(1)按钮展示

<el-form-item style="float:right">
        <el-button type="success" @click="exportUser()">导出</el-button>
      </el-form-item>
      <el-form-item style="float:right">
        <el-input v-model="dataForm.deptId" placeholder="请选择部门进行导出" style="width: 200px" clearable></el-input>
      </el-form-item>

(2)方法定义

// 导出用户,通过blob
exportUser() {
       this.$axios({        
          method: 'get', 
          url: this.$http.adornUrl('/emp/user/export?deptId='+this.dataForm.deptId),
          responseType: 'blob',
        }).then(function (response){
          let blob = new Blob([response.data], { type: 'application/vnd.ms-excel;charset=utf-8' })
                let downloadElement = document.createElement('a');//创建一个 <a> 元素,用于执行文件下载操作。
                let href = window.URL.createObjectURL(blob); //创建下载的链接
                downloadElement.href = href;// 将下载链接指定给 <a> 元素的 href 属性。
                downloadElement.download = 'test.xlsx'; //下载后文件名
                document.body.appendChild(downloadElement);// 将 <a> 元素添加到页面的 <body> 元素中。
                downloadElement.click(); //点击下载
                document.body.removeChild(downloadElement); //下载完成移除元素
                window.URL.revokeObjectURL(href); //释放掉blob对象
        }).catch(function(error){
          this.$message.error(error)
        })		
			},		

3、实物展示

 

----------------------------------------------------------------------------

   本页面的导出下拉框,是固定写入,若需要动态导入,可参考以下大佬文章:

   http://t.csdnimg.cn/zBVD0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值