我们先做批量导出,为什么做批量导出,因为批量导出是作为批量导入的模板,你批量导入是从外面把excel的内容读起过来,然后导入系统里面去,excel模板,你需要通过批量导出来作为excel的模板,导出来的作为模板,所以我们先做批量导出,开始做一个全量的数据导出,就是把表格里面所有的数据全部导出来,这个是如何做的呢?
hutool官网
https://doc.hutool.cn/pages/index/
Excel读取-ExcelReader
介绍
读取Excel内容的封装,通过构造ExcelReader对象,指定被读取的Excel文件、流或工作簿,然后调用readXXX方法读取内容为指定格式。
使用
-
读取Excel中所有行和列,用二维列表表示
ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List<List<Object>> readAll = reader.read();
-
读取为Map列表,默认第一行为标题行,数据从第二行开始,一个Map表示一行,Map中的key为标题,value为标题对应的单元格值。
ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List<Map<String, Object>> readAll = reader.readAll();
-
读取为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判断是新增还


5810

被折叠的 条评论
为什么被折叠?



