15. SpringBoot3+Vue3实现数据批量导入导出功能
部门相关的增删改查
Department.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.DepartmentMapper">
<select id="selectAll" resultType="com.example.entity.Department">
select * from department
<where>
<if test="name != null">name like concat('%', #{name}, '%')</if>
</where>
order by id desc
</select>
<insert id="insert" parameterType="com.example.entity.Department">
insert into `department` (name)
values (#{name})
</insert>
<update id="updateById" parameterType="com.example.entity.Department">
update `department` set name = #{name}
where id = #{id}
</update>
</mapper>
Department.vue
<template>
<div>
<div class="card" style="margin-bottom: 5px">
<el-input style="width: 240px; margin-right: 10px" v-model="data.name" placeholder="请输入名称查询" prefix-icon="Search"></el-input>
<el-button type="primary" @click="load">查 询</el-button>
<el-button type="warning" @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="delBatch">批量删除</el-button>
</div>
<div class="card" style="margin-bottom: 5px">
<el-table :data="data.tableData" stripe @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="名称" prop="name" />
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button @click="handleUpdate(scope.row)" type="primary" :icon="Edit" circle></el-button>
<el-button @click="del(scope.row.id)" type="danger" :icon="Delete" circle></el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 15px">
<el-pagination
@size-change="load"
@current-change="load"
v-model:current-page="data.pageNum"
v-model:page-size="data.pageSize"
:page-sizes="[5, 10, 15, 20]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="data.total"
/>
</div>
</div>
<el-dialog title="部门信息" v-model="data.formVisible" width="500" destroy-on-close>
<el-form ref="formRef" :rules="data.rules" :model="data.form" label-width="80px" style="padding-right: 40px; padding-top: 20px">
<el-form-item label="名称" prop="name">
<el-input v-model="data.form.name" autocomplete="off" placeholder="请输入名称" />
</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 {Edit, Delete, Search} from "@element-plus/icons-vue"
import request from "@/utils/request.js";
import {ElMessage, ElMessageBox} from "element-plus";
const data = reactive({
name: null,
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
formVisible: false,
form: {},
ids: [],
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
}
})
const formRef = ref()
const load = () => {
request.get('/department/selectPage', { // ?pageNum=1&pageSize=10
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
name: data.name
}
}).then(res => {
data.tableData = res.data.list
data.total = res.data.total
})
}
load()
const reset = () => {
data.name = null
load()
}
const handleAdd = () => {
data.formVisible = true
data.form = {}
}
const save = () => { // 在一个保存方法里面做2个操作 一个是新增 一个是编辑
formRef.value.validate((valid) => {
if (valid) {
data.form.id ? update() : add()
}
})
}
const add = () => {
request.post('/department/add', data.form).then(res => { // 新增的对象里面没有id
if (res.code === '200') {
data.formVisible = false
ElMessage.success('操作成功')
load() // 新增后一定要重新加载最新的数据
} else {
ElMessage.error(res.msg)
}
})
}
const handleUpdate = (row) => {
data.form = JSON.parse(JSON.stringify(row)) // 深拷贝一个新的对象 用于编辑 这样就不会影响行对象
data.formVisible = true
}
const update = () => {
request.put('/department/update', data.form).then(res => { // 编辑的对象里面包含id
if (res.code === '200') {
data.formVisible = false
ElMessage.success('操作成功')
load() // 更新后一定要重新加载最新的数据
} else {
ElMessage.error(res.msg)
}
})
}
const del = (id) => {
ElMessageBox.confirm('删除数据后无法恢复,您确认删除吗?', '删除确认', { type: 'warning' }).then(() => {
request.delete('/department/deleteById/' +id).then(res => {
if (res.code === '200') {
ElMessage.success('操作成功')
load() // 删除后一定要重新加载最新的数据
} else {
ElMessage.error(res.msg)
}
})
}).catch()
}
const handleSelectionChange = (rows) => { // 返回所有选中的行对象数组
// 从选中的行数组里面取出所有行的id组成一个新的数组
data.ids = rows.map(row => row.id)
console.log(data.ids)
}
const delBatch = () => {
if (data.ids.length === 0) {
ElMessage.warning('请选择数据')
return
}
ElMessageBox.confirm('删除数据后无法恢复,您确认删除吗?', '删除确认', { type: 'warning' }).then(() => {
request.delete('/department/deleteBatch', { data: data.ids }).then(res => {
if (res.code === '200') {
ElMessage.success('操作成功')
load() // 删除后一定要重新加载最新的数据
} else {
ElMessage.error(res.msg)
}
})
}).catch()
}
</script>
员工关联部门
<el-form-item label="部门">
<el-select style="width: 100%" v-model="data.form.departmentId">
<el-option v-for="item in data.departmentList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
insert 写错了

关联查询员工表数据和 部门的名称
<select id="selectAll" resultType="com.example.entity.Employee">
select employee.*, department.name as departmentName from employee
left join department on employee.department_id = department.id
<where>
<if test="name != null">employee.name like concat('%', #{name}, '%')</if>
</where>
order by employee.id desc
</select>

导出数据到 Excel
/**
* 导出excel
*/
@GetMapping("/export")
public void export(HttpServletResponse response) throws Exception {
// 1. 拿到所有的员工数据
List<Employee> employeeList = employeeService.selectAll(null);
// 2. 构建 ExcelWriter
// 在内存操作,写出到浏览器
ExcelWriter writer = ExcelUtil.getWriter(true);
// 3. 设置中文表头
writer.addHeaderAlias("username", "账号");
writer.addHeaderAlias("name", "名称");
writer.addHeaderAlias("sex", "性别");
writer.addHeaderAlias("no", "工号");
writer.addHeaderAlias("age", "年龄");
writer.addHeaderAlias("description", "个人介绍");
writer.addHeaderAlias("departmentName", "部门");
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
writer.setOnlyAlias(true);
// 4. 写出数据到writer
writer.write(employeeList, true);
// 5. 设置输出的文件的名称 以及输出流的头信息
// 设置浏览器响应的格式
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
String fileName = URLEncoder.encode("员工信息", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
// 6. 写出到输出流 并关闭 writer
ServletOutputStream os = response.getOutputStream();
writer.flush(os);
writer.close();
}
导出的完整的 excel

在 页面加上这个导出的方法
const exportData = () => {
// 导出数据 是通过流的形式下载 excel 打开流的链接,浏览器会自动帮我们下载文件
window.open('http://localhost:9090/employee/export')
}
报错了:

引入 poi-ooxml
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.3.0</version>
</dependency>
从 Excel 导入数据
/**
* excel 导入
*/
@PostMapping("/import")
public Result importData(MultipartFile file) throws Exception {
// 1. 拿到输入流 构建 reader
InputStream inputStream = file.getInputStream();
ExcelReader reader = ExcelUtil.getReader(inputStream);
// 2. 读取 excel里面的数据
reader.addHeaderAlias("账号", "username");
reader.addHeaderAlias("名称", "name");
reader.addHeaderAlias("性别", "sex");
reader.addHeaderAlias("工号", "no");
reader.addHeaderAlias("年龄", "age");
reader.addHeaderAlias("个人介绍", "description");
reader.addHeaderAlias( "部门", "departmentName");
List<Employee> employeeList = reader.readAll(Employee.class);
// 3. 写入list数据到数据库
for (Employee employee : employeeList) {
employeeService.add(employee);
}
return Result.success();
}
前端页面导入按钮
<el-upload
style="display: inline-block; margin: 0 10px"
action="http://localhost:9090/employee/import"
:show-file-list="false"
:on-success="importSuccess"
>
<el-button type="info">导入</el-button>
</el-upload>
const importSuccess = (res) => {
if (res.code === '200') {
ElMessage.success('批量导入数据成功')
load()
} else {
ElMessage.error(res.msg)
}
}