07,Springboot3+vue3实现excel批量导入导出

我们先做批量导出,为什么做批量导出,因为批量导出是作为批量导入的模板,你批量导入是从外面把excel的内容读起过来,然后导入系统里面去,excel模板,你需要通过批量导出来作为excel的模板,导出来的作为模板,所以我们先做批量导出,开始做一个全量的数据导出,就是把表格里面所有的数据全部导出来,这个是如何做的呢?
hutool官网
https://doc.hutool.cn/pages/index/

Excel读取-ExcelReader

介绍

读取Excel内容的封装,通过构造ExcelReader对象,指定被读取的Excel文件、流或工作簿,然后调用readXXX方法读取内容为指定格式。

使用

  1. 读取Excel中所有行和列,用二维列表表示

ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List<List<Object>> readAll = reader.read();
  1. 读取为Map列表,默认第一行为标题行,数据从第二行开始,一个Map表示一行,Map中的key为标题,value为标题对应的单元格值。

ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List<Map<String, Object>> readAll = reader.readAll();
  1. 读取为Bean列表,Bean中的字段名为标题,字段值为标题对应的单元格值。

ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List<Person> all = reader.readAll(Person.class);

Excel 导出

注意: 数据导出是通过输出流的方式导出的
导出数据流程
1, 拿到所有数据
2, 构建ExcelWriter
3, 设置中文表头
4, 写出数据到 writer
5, 设置输出的文件名称以及输出流的头信息
6, 写出到输出流,并关闭writer

按 ctrl+p 快捷键 查看参数

按 ctrl+p 快捷键 查看参数

去后端 controller/AdminController.java 构建数据导出 实现数据全量导出代码如下
controller/AdminController.java 

/**
 * 实现数据全量导出
 * */
@GetMapping("/export")
public void exportData(HttpServletResponse response) throws IOException {
    // 1, 拿到所有的数据
    List<Admin> list = adminService.selectAll();
    // 2, 构建 Writer 对象
    ExcelWriter writer = ExcelUtil.getWriter(true);
    // 3, 设置中文的表头
    writer.addHeaderAlias("username","账号");
    writer.addHeaderAlias("name","名称");
    writer.addHeaderAlias("phone","电话");
    writer.addHeaderAlias("email","邮箱");
    // 4, 写出数据到 writer
    writer.write(list);
    // 5, 设置输出文件的名称以及输出流的头信息
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
    String fileName = URLEncoder.encode("管理员信息", StandardCharsets.UTF_8);
    response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx");
    // 6, 写出到输出流,并关闭 writer
    ServletOutputStream os = response.getOutputStream();
    // 通过writer将os输出到os里面去
    writer.flush(os);
    // 关闭
    writer.close();
    os.close();
}

启动后端 mvn spring-boot:run
访问 http://localhost:9999/admin/export

异常处理 缺少 poi-ooxml 依赖
<!--处理office文件的依赖-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>

老师的

自己的

打开'管理员信息.xlsx' 表内容如下,

实现只导出别名之方法是添加writer.setOnlyAlias(true);这个方法
package com.longchi.controller;

import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.github.pagehelper.PageInfo;
import com.longchi.common.Result;
import com.longchi.entity.Admin;
import com.longchi.service.AdminService;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @Description com.longchi.controller
 * @Author zengguoqing
 * @Date 2026-03-18 21:27
 **/

// 暴露查询接口
@RestController
@RequestMapping("/admin") // 添加统一前缀
public class AdminController {

    @Resource
    AdminService adminService;

    @PostMapping("/add")
    public Result add(@RequestBody Admin admin) { // @RequestBody 接收前端传来的 json 参数
        adminService.add(admin);
        return Result.success();
    }

    @PutMapping("/update")
    public Result update(@RequestBody Admin admin) { // @RequestBody 接收前端传来的 json 参数
        adminService.update(admin);
        return Result.success();
    }

    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable Integer id) { // @PathVariable 接收前端传来的路径参数
        adminService.deleteById(id);
        return  Result.success();
    }

    @DeleteMapping("/deleteBatch")
    public Result deleteBatch(@RequestBody List<Admin> list) { // @RequestBody 接收前端传来的 json 数组
        adminService.deleteBatch(list);
        return  Result.success();
    }

    @GetMapping("/selectAll") // 完整的请求路径 http://ip:port/admin/selectAll
    public Result selectAll() {
        List<Admin> adminList = adminService.selectAll();
        return Result.success(adminList);
    }

    /**
     * 分页查询
     * pageNum: 当前的页码
     * pageSize: 每页的个数
     * */
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, Admin admin) {
        PageInfo<Admin> pageInfo = adminService.selectPage(pageNum, pageSize, admin);
        return Result.success(pageInfo);
        // 返回的分页的对象
    }

    /**
     * 实现数据全量导出
     * */
    @GetMapping("/export")
    public void exportData(HttpServletResponse response) throws IOException {
        // 1, 拿到所有的数据
        List<Admin> list = adminService.selectAll();
        // 2, 构建 Writer 对象
        ExcelWriter writer = ExcelUtil.getWriter(true);
        // 3, 设置中文的表头
        writer.addHeaderAlias("username","账号");
        writer.addHeaderAlias("name","名称");
        writer.addHeaderAlias("phone","电话");
        writer.addHeaderAlias("email","邮箱");
        // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
        writer.setOnlyAlias(true);
        // 4, 写出数据到 writer
        writer.write(list);
        // 5, 设置输出文件的名称以及输出流的头信息
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("管理员信息", StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx");
        // 6, 写出到输出流,并关闭 writer
        ServletOutputStream os = response.getOutputStream();
        writer.flush(os); // 通过writer将os输出到os里面去
        writer.close(); // 关闭
        os.close();
    }
}

注意:

如果不进行说明的话,默认的导出是导出所有字段,所以id和password也导出来了.默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之

 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
writer.setOnlyAlias(true);

访问: http://localhost:9999/admin/export

打开'管理员信息.xlsx' 表内容如下,此时id,password已经没有了

条件导出 概述
我们查询账号3或者'李'的这个数有两条,如果我们只想导出这两条数据,我们有该如何处理?我们应该把条件处理写出来,让后台通过条件来查询到这个数据,然后将符合条件的数据导出来
去前端
去前端 调用(通过window.open()这个方法) 实现批量导出
1, 去 src/views/Admin.vue 的 template 添加点击事件
<el-button type="info" @click="exportData">批量导出</el-button>

2, 去 去 src/views/Admin.vue 的 script 添加如下代码
const wxportData = () => {
	window.open("http://localhost:9999/admin/export")
}

===
完整代码: 
1, src/views/Admin.vue
<template>
	<div>
		<div class="card" style="margin-bottom: 5px">
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.username" placeholder="请输入用户名查询" :prefix-icon="Search"></el-input>
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.name" placeholder="请输入名称查询" :prefix-icon="Search"></el-input>
			<el-button type="primary" @click="load">查 询</el-button>
			<el-button @click="reset">重 置</el-button>
		</div>
		<div class="card" style="margin-bottom: 5px">
			<el-button type="primary" @click="handleAdd">新增</el-button>
			<el-button type="danger" @click="deleteBatch">批量删除</el-button>
			<el-button type="info" @click="exportDataw">批量导出</el-button>
			<el-button type="success">批量导入</el-button>			
		</div>

		<div class="card" style="margin-bottom: 5px">
			<el-table :data="data.tableData" style="width: 100%" @selection-change="handleSelectionChange" :header-cell-style="{color: '#333', backgroundColor: 'eaf4ff'}">
				<el-table-column type="selection" width="55" />
				<el-table-column prop="username" label="账号" width="188" />
				<el-table-column prop="name" label="名称" width="188" />
				<el-table-column prop="phone" label="电话" width="188" />
				<el-table-column prop="email" label="邮箱" width="188" />
				<el-table-column label="操作" width="100">
					<template #default="scope">
						<el-button type="primary" icon="Edit" circle @click="handleEdit(scope.row)"></el-button>
						<el-button type="danger" icon="Delete" circle @click="del(scope.row.id)"></el-button>
					</template>
				</el-table-column>
			</el-table>
		</div>
		<div class="card">
			<el-pagination 
                v-model:current-page="data.pageNum" 
				v-model:page-size="data.pageSize" 
                layout="total, sizes, prev, pager, next, jumper" 
				:page-sizes="[5, 10, 20]"
                :total="data.total"
				@current-change="load"
				@size-change="load"
            />
		</div>
		<!--弹窗开始-->
		<el-dialog title="管理员信息" v-model="data.FormVisible" width="40%" destroy-on-close>
			<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
				<el-form-item prop="username" label="账号">
					<el-input v-model="data.form.username" autocomplete="off" />
				</el-form-item>
				<el-form-item prop="name" label="名称">
					<el-input v-model="data.form.name" autocomplete="off" />
				</el-form-item>
				<el-form-item prop="phone" label="电话">
					<el-input v-model="data.form.phone" autocomplete="off" />
				</el-form-item>
				<el-form-item prop="email" label="邮箱">
					<el-input v-model="data.form.email" autocomplete="off" />
				</el-form-item>
			</el-form>
			<template #footer>
				<div class="dialog-footer">
					<el-button @click="data.FormVisible = false">取消</el-button>
					<el-button type="primary" @click="save">保存</el-button>
				</div>
			</template>
		</el-dialog>
		<!--弹窗结束-->
	</div>
</template>
<script setup>
import {reactive,ref} from "vue";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request.js";
import { ElMessageBox,ElMessage } from "element-plus";

// 定义数据对象 , 页面渲染的数据都在这里面定义
// 定义表单验证规则 触发表单验证事件 , 提交表单事件 , 表单验证规则等都在这里面定义
const data = reactive({
	username: null,
	name: null,
	pageNum: 1,
	pageSize: 5,
	total: 6,
	tableData: [],
	FormVisible: false,
	form: {},
	rules: {
		username: [
			{required: true, message: '请输入账号', trigger: 'blur'},
			{min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur'}],
		name: [
			{required: true, message: '请输入名称', trigger: 'blur'},
			{min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur'}],
	
		phone: [
			{required: true, message: '请输入电话', trigger: 'blur'},
			{min: 11, max: 11, message: '长度为 11 个字符', trigger: 'blur'}],
	
		email: [
			{required: true, message: '请输入邮箱', trigger: 'blur'},
			{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']},
			{min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur'},
			{pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
		]
	},
	rows: []
})

// 定义表单引用
const formRef = ref()

// 分页查询 拿到后端接口数据(后端给前端返回的数据, 赋值给前端页面展示的数据)
const load = () => {
	request.get('/admin/selectPage',{
		params: {
			pageNum: data.pageNum,
			pageSize: data.pageSize,
			username: data.username,
			name: data.name
		}
	}).then(res => {
		if (res.code === '200') {
			data.tableData = res.data.list
			data.total = res.data.total
		} else {
			ElMessage.error(res.msg)
		}
	})
}

load()

// 重置查询条件
const reset = () => {
	data.username = null
	data.name = null
	load()
}

// 新增管理员信息弹窗展示
const handleAdd = () => {
	data.FormVisible = true
	data.form = {}
}

// 提交表单验证

// 新增管理员信息
const add = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.post('/admin/add', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('新增成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			})
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

const handleEdit = (row) => {
	// data.form = row // 浅拷贝,修改原始数据
	data.form = JSON.parse(JSON.stringify(row)) // 深拷贝,防止修改原始数据
	data.FormVisible = true
}

// 修改管理员信息 前端调用后端接口 修改管理员信息 并刷新页面数据 刷新页面数据有两种方式 
// 一种是通过前端调用后端接口 直接修改数据库中的数据 然后重新查询一遍数据 
// 另一种是直接通过前端调用后端接口 直接修改数据库中的数据 但是不重新查询一遍数据 
// 直接在前端更新数据 这样效率更高 因为不需要重新请求服务器 
const update = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.put('/admin/update', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('修改成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			}) 
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

// 保存按钮点击事件,根据表单中的id判断是新增还
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值