[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-public-nal0yVxM":3,"public-project-articles-nal0yVxM":17},{"id":4,"uuid":5,"project_id":6,"title":7,"content":8,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},555,"nal0yVxM",47,"15. SpringBoot3+Vue3实现数据批量导入导出功能","## 部门相关的增删改查\n\nDepartment.xml\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003C!DOCTYPE mapper\n        PUBLIC \"-\u002F\u002Fmybatis.org\u002F\u002FDTD Mapper 3.0\u002F\u002FEN\"\n        \"http:\u002F\u002Fmybatis.org\u002Fdtd\u002Fmybatis-3-mapper.dtd\">\n\u003Cmapper namespace=\"com.example.mapper.DepartmentMapper\">\n\n    \u003Cselect id=\"selectAll\" resultType=\"com.example.entity.Department\">\n        select * from department\n        \u003Cwhere>\n            \u003Cif test=\"name != null\">name like concat('%', #{name}, '%')\u003C\u002Fif>\n        \u003C\u002Fwhere>\n        order by id desc\n    \u003C\u002Fselect>\n    \n    \u003Cinsert id=\"insert\" parameterType=\"com.example.entity.Department\">\n        insert into `department` (name)\n        values (#{name})\n    \u003C\u002Finsert>\n\n    \u003Cupdate id=\"updateById\" parameterType=\"com.example.entity.Department\">\n        update `department` set name = #{name}\n        where id = #{id}\n    \u003C\u002Fupdate>\n\n\u003C\u002Fmapper>\n```\n\nDepartment.vue\n\n```vue\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv class=\"card\" style=\"margin-bottom: 5px\">\n      \u003Cel-input style=\"width: 240px; margin-right: 10px\" v-model=\"data.name\" placeholder=\"请输入名称查询\" prefix-icon=\"Search\">\u003C\u002Fel-input>\n      \u003Cel-button type=\"primary\" @click=\"load\">查 询\u003C\u002Fel-button>\n      \u003Cel-button type=\"warning\" @click=\"reset\">重 置\u003C\u002Fel-button>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"card\" style=\"margin-bottom: 5px\">\n      \u003Cel-button type=\"primary\" @click=\"handleAdd\">新 增\u003C\u002Fel-button>\n      \u003Cel-button type=\"danger\" @click=\"delBatch\">批量删除\u003C\u002Fel-button>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"card\" style=\"margin-bottom: 5px\">\n      \u003Cel-table :data=\"data.tableData\" stripe @selection-change=\"handleSelectionChange\">\n        \u003Cel-table-column type=\"selection\" width=\"55\" \u002F>\n        \u003Cel-table-column label=\"名称\" prop=\"name\" \u002F>\n        \u003Cel-table-column label=\"操作\" width=\"120\">\n          \u003Ctemplate #default=\"scope\">\n            \u003Cel-button @click=\"handleUpdate(scope.row)\" type=\"primary\" :icon=\"Edit\" circle>\u003C\u002Fel-button>\n            \u003Cel-button @click=\"del(scope.row.id)\" type=\"danger\" :icon=\"Delete\" circle>\u003C\u002Fel-button>\n          \u003C\u002Ftemplate>\n        \u003C\u002Fel-table-column>\n      \u003C\u002Fel-table>\n      \u003Cdiv style=\"margin-top: 15px\">\n        \u003Cel-pagination\n            @size-change=\"load\"\n            @current-change=\"load\"\n            v-model:current-page=\"data.pageNum\"\n            v-model:page-size=\"data.pageSize\"\n            :page-sizes=\"[5, 10, 15, 20]\"\n            background\n            layout=\"total, sizes, prev, pager, next, jumper\"\n            :total=\"data.total\"\n        \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cel-dialog title=\"部门信息\" v-model=\"data.formVisible\" width=\"500\" destroy-on-close>\n      \u003Cel-form ref=\"formRef\" :rules=\"data.rules\" :model=\"data.form\" label-width=\"80px\" style=\"padding-right: 40px; padding-top: 20px\">\n        \u003Cel-form-item label=\"名称\" prop=\"name\">\n          \u003Cel-input v-model=\"data.form.name\" autocomplete=\"off\" placeholder=\"请输入名称\" \u002F>\n        \u003C\u002Fel-form-item>\n      \u003C\u002Fel-form>\n      \u003Ctemplate #footer>\n        \u003Cdiv class=\"dialog-footer\">\n          \u003Cel-button @click=\"data.formVisible = false\">取 消\u003C\u002Fel-button>\n          \u003Cel-button type=\"primary\" @click=\"save\">保 存\u003C\u002Fel-button>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftemplate>\n    \u003C\u002Fel-dialog>\n\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { reactive, ref } from \"vue\";\nimport {Edit, Delete, Search} from \"@element-plus\u002Ficons-vue\"\nimport request from \"@\u002Futils\u002Frequest.js\";\nimport {ElMessage, ElMessageBox} from \"element-plus\";\n\nconst data = reactive({\n  name: null,\n  tableData: [],\n  pageNum: 1,\n  pageSize: 10,\n  total: 0,\n  formVisible: false,\n  form: {},\n  ids: [],\n  rules: {\n    name: [\n      { required: true, message: '请输入名称', trigger: 'blur' }\n    ],\n  }\n})\n\nconst formRef = ref()\n\nconst load = () => {\n  request.get('\u002Fdepartment\u002FselectPage', { \u002F\u002F ?pageNum=1&pageSize=10\n    params: {\n      pageNum: data.pageNum,\n      pageSize: data.pageSize,\n      name: data.name\n    }\n  }).then(res => {\n    data.tableData = res.data.list\n    data.total = res.data.total\n  })\n}\nload()\n\nconst reset = () => {\n  data.name = null\n  load()\n}\n\nconst handleAdd = () => {\n  data.formVisible = true\n  data.form = {}\n}\n\nconst save = () => { \u002F\u002F 在一个保存方法里面做2个操作  一个是新增 一个是编辑\n  formRef.value.validate((valid) => {\n    if (valid) {\n      data.form.id ? update() : add()\n    }\n  })\n}\n\nconst add = () => {\n  request.post('\u002Fdepartment\u002Fadd', data.form).then(res => {   \u002F\u002F 新增的对象里面没有id\n    if (res.code === '200') {\n      data.formVisible = false\n      ElMessage.success('操作成功')\n      load()  \u002F\u002F 新增后一定要重新加载最新的数据\n    } else {\n      ElMessage.error(res.msg)\n    }\n  })\n}\n\nconst handleUpdate = (row) => {\n  data.form = JSON.parse(JSON.stringify(row)) \u002F\u002F 深拷贝一个新的对象 用于编辑  这样就不会影响行对象\n  data.formVisible = true\n}\n\nconst update = () => {\n  request.put('\u002Fdepartment\u002Fupdate', data.form).then(res => {  \u002F\u002F 编辑的对象里面包含id\n    if (res.code === '200') {\n      data.formVisible = false\n      ElMessage.success('操作成功')\n      load()  \u002F\u002F 更新后一定要重新加载最新的数据\n    } else {\n      ElMessage.error(res.msg)\n    }\n  })\n}\n\nconst del = (id) => {\n  ElMessageBox.confirm('删除数据后无法恢复，您确认删除吗？', '删除确认', { type: 'warning' }).then(() => {\n    request.delete('\u002Fdepartment\u002FdeleteById\u002F' +id).then(res => {\n      if (res.code === '200') {\n        ElMessage.success('操作成功')\n        load()  \u002F\u002F 删除后一定要重新加载最新的数据\n      } else {\n        ElMessage.error(res.msg)\n      }\n    })\n  }).catch()\n}\n\nconst handleSelectionChange = (rows) => {  \u002F\u002F 返回所有选中的行对象数组\n  \u002F\u002F 从选中的行数组里面取出所有行的id组成一个新的数组\n  data.ids = rows.map(row => row.id)\n  console.log(data.ids)\n}\n\nconst delBatch = () => {\n  if (data.ids.length === 0) {\n    ElMessage.warning('请选择数据')\n    return\n  }\n  ElMessageBox.confirm('删除数据后无法恢复，您确认删除吗？', '删除确认', { type: 'warning' }).then(() => {\n    request.delete('\u002Fdepartment\u002FdeleteBatch', { data: data.ids }).then(res => {\n      if (res.code === '200') {\n        ElMessage.success('操作成功')\n        load()  \u002F\u002F 删除后一定要重新加载最新的数据\n      } else {\n        ElMessage.error(res.msg)\n      }\n    })\n  }).catch()\n}\n\u003C\u002Fscript>\n```\n\n## 员工关联部门\n\n```vue\n\u003Cel-form-item label=\"部门\">\n  \u003Cel-select style=\"width: 100%\" v-model=\"data.form.departmentId\">\n    \u003Cel-option v-for=\"item in data.departmentList\" :key=\"item.id\" :label=\"item.name\" :value=\"item.id\">\u003C\u002Fel-option>\n  \u003C\u002Fel-select>\n\u003C\u002Fel-form-item>\n```\n\n\n\ninsert 写错了\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2024\u002Fpng\u002F751015\u002F1730105310953-78d66a51-7e3c-4886-b878-3ab0b33a832b.png)\n\n\n\n关联查询员工表数据和 部门的名称\n\n```xml\n\u003Cselect id=\"selectAll\" resultType=\"com.example.entity.Employee\">\n  select employee.*, department.name as departmentName from employee\n  left join department on employee.department_id = department.id\n  \u003Cwhere>\n    \u003Cif test=\"name != null\">employee.name like concat('%', #{name}, '%')\u003C\u002Fif>\n  \u003C\u002Fwhere>\n  order by employee.id desc\n\u003C\u002Fselect>\n```\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2024\u002Fpng\u002F751015\u002F1730105531518-e1cb18f0-d017-48ae-b38b-5661d6b3d17d.png)\n\n## 导出数据到 Excel\n\n```java\n\u002F**\n * 导出excel\n *\u002F\n@GetMapping(\"\u002Fexport\")\npublic void export(HttpServletResponse response) throws Exception {\n    \u002F\u002F 1. 拿到所有的员工数据\n    List\u003CEmployee> employeeList = employeeService.selectAll(null);\n    \u002F\u002F 2. 构建 ExcelWriter\n    \u002F\u002F 在内存操作，写出到浏览器\n    ExcelWriter writer = ExcelUtil.getWriter(true);\n    \u002F\u002F 3. 设置中文表头\n    writer.addHeaderAlias(\"username\", \"账号\");\n    writer.addHeaderAlias(\"name\", \"名称\");\n    writer.addHeaderAlias(\"sex\", \"性别\");\n    writer.addHeaderAlias(\"no\", \"工号\");\n    writer.addHeaderAlias(\"age\", \"年龄\");\n    writer.addHeaderAlias(\"description\", \"个人介绍\");\n    writer.addHeaderAlias(\"departmentName\", \"部门\");\n    \u002F\u002F 默认的，未添加alias的属性也会写出，如果想只写出加了别名的字段，可以调用此方法排除之\n    writer.setOnlyAlias(true);\n    \u002F\u002F 4. 写出数据到writer\n    writer.write(employeeList, true);\n    \u002F\u002F 5. 设置输出的文件的名称  以及输出流的头信息\n    \u002F\u002F 设置浏览器响应的格式\n    response.setContentType(\"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8\");\n    String fileName = URLEncoder.encode(\"员工信息\", \"UTF-8\");\n    response.setHeader(\"Content-Disposition\", \"attachment;filename=\" + fileName + \".xlsx\");\n    \u002F\u002F 6. 写出到输出流 并关闭 writer\n    ServletOutputStream os = response.getOutputStream();\n    writer.flush(os);\n    writer.close();\n}\n```\n\n导出的完整的 excel\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2024\u002Fpng\u002F751015\u002F1730106843808-97b5c7cb-4465-47d7-9ec5-2043c3575c07.png)\n\n\n\n在 页面加上这个导出的方法\n\n```vue\nconst exportData = () => {\n  \u002F\u002F 导出数据 是通过流的形式下载 excel   打开流的链接，浏览器会自动帮我们下载文件\n  window.open('http:\u002F\u002Flocalhost:9090\u002Femployee\u002Fexport')\n}\n```\n\n报错了：\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2024\u002Fpng\u002F751015\u002F1730106402817-04154f7e-da3d-44c7-aacb-ae2f3d1dafc4.png)\n\n引入 poi-ooxml\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>org.apache.poi\u003C\u002FgroupId>\n    \u003CartifactId>poi-ooxml\u003C\u002FartifactId>\n    \u003Cversion>5.3.0\u003C\u002Fversion>\n\u003C\u002Fdependency>\n```\n\n## 从 Excel 导入数据\n\n```java\n\u002F**\n * excel 导入\n *\u002F\n@PostMapping(\"\u002Fimport\")\npublic Result importData(MultipartFile file) throws Exception {\n    \u002F\u002F 1. 拿到输入流 构建 reader\n    InputStream inputStream = file.getInputStream();\n    ExcelReader reader = ExcelUtil.getReader(inputStream);\n    \u002F\u002F 2. 读取 excel里面的数据\n    reader.addHeaderAlias(\"账号\", \"username\");\n    reader.addHeaderAlias(\"名称\", \"name\");\n    reader.addHeaderAlias(\"性别\", \"sex\");\n    reader.addHeaderAlias(\"工号\", \"no\");\n    reader.addHeaderAlias(\"年龄\", \"age\");\n    reader.addHeaderAlias(\"个人介绍\", \"description\");\n    reader.addHeaderAlias( \"部门\", \"departmentName\");\n    List\u003CEmployee> employeeList = reader.readAll(Employee.class);\n    \u002F\u002F 3. 写入list数据到数据库\n    for (Employee employee : employeeList) {\n        employeeService.add(employee);\n    }\n    return Result.success();\n}\n```\n\n前端页面导入按钮\n\n```vue\n\u003Cel-upload\n    style=\"display: inline-block; margin: 0 10px\"\n    action=\"http:\u002F\u002Flocalhost:9090\u002Femployee\u002Fimport\"\n    :show-file-list=\"false\"\n    :on-success=\"importSuccess\"\n>\n  \u003Cel-button type=\"info\">导入\u003C\u002Fel-button>\n\u003C\u002Fel-upload>\n\n              \nconst importSuccess = (res) => {\n  if (res.code === '200') {\n    ElMessage.success('批量导入数据成功')\n    load()\n  } else {\n    ElMessage.error(res.msg)\n  }\n}\n```\n\n","coding",1,3577,1090,"2024-10-28 17:39:21","2026-05-03 22:49:02","1天学会SpringBoot3+Vue3实战项目开发","learn-springboot-vue",{"project":18,"items":19},{"id":6,"title":15,"slug":16},[20,27,34,42,49,56,63,70,77,84,91,98,105,112,119,120],{"id":21,"uuid":22,"project_id":6,"title":23,"type":9,"status":10,"public_enabled":10,"views":24,"sort":25,"created_at":26,"updated_at":14,"project_title":15,"project_slug":16},475,"OgrBbww7","01. 1天学会SpringBoot3+Vue3实战项目课程介绍",17862,910,"2024-10-11 16:50:50",{"id":28,"uuid":29,"project_id":6,"title":30,"type":9,"status":10,"public_enabled":10,"views":31,"sort":32,"created_at":33,"updated_at":14,"project_title":15,"project_slug":16},476,"U58ISSFR","02. 从0带你搭建Vue3工程",12899,911,"2024-10-11 16:47:49",{"id":35,"uuid":36,"project_id":6,"title":37,"type":9,"status":10,"public_enabled":10,"views":38,"sort":39,"created_at":40,"updated_at":41,"project_title":15,"project_slug":16},478,"tK3YUYq8","03. Vue3集成Element-Plus",9899,919,"2024-10-14 22:18:17","2026-05-07 15:33:28.189425+00",{"id":43,"uuid":44,"project_id":6,"title":45,"type":9,"status":10,"public_enabled":10,"views":46,"sort":47,"created_at":48,"updated_at":14,"project_title":15,"project_slug":16},488,"J2MV6UAG","04. Element-Plus组件使用速成",8093,938,"2024-10-11 16:49:40",{"id":50,"uuid":51,"project_id":6,"title":52,"type":9,"status":10,"public_enabled":10,"views":53,"sort":54,"created_at":55,"updated_at":14,"project_title":15,"project_slug":16},489,"zGi4XJsb","05. Vue3集成Vue-Router实现路由跳转",7443,939,"2024-10-12 15:44:41",{"id":57,"uuid":58,"project_id":6,"title":59,"type":9,"status":10,"public_enabled":10,"views":60,"sort":61,"created_at":62,"updated_at":14,"project_title":15,"project_slug":16},499,"cwsTdvo9","06. Vue3搭建后台管理系统",7421,964,"2024-10-14 16:02:49",{"id":64,"uuid":65,"project_id":6,"title":66,"type":9,"status":10,"public_enabled":10,"views":67,"sort":68,"created_at":69,"updated_at":14,"project_title":15,"project_slug":16},501,"JlXltpKA","07. Mysql语法简介（速成）",5208,973,"2024-10-15 16:52:18",{"id":71,"uuid":72,"project_id":6,"title":73,"type":9,"status":10,"public_enabled":10,"views":74,"sort":75,"created_at":76,"updated_at":14,"project_title":15,"project_slug":16},509,"c3XrOTcU","08. 从0带你搭建SpringBoot3工程",9397,992,"2024-10-16 15:43:13",{"id":78,"uuid":79,"project_id":6,"title":80,"type":9,"status":10,"public_enabled":10,"views":81,"sort":82,"created_at":83,"updated_at":14,"project_title":15,"project_slug":16},516,"QsKwbDX7","09. SpringBoot3集成Mybatis",10348,1004,"2024-10-17 16:36:15",{"id":85,"uuid":86,"project_id":6,"title":87,"type":9,"status":10,"public_enabled":10,"views":88,"sort":89,"created_at":90,"updated_at":14,"project_title":15,"project_slug":16},519,"qKG13ySo","10. SpringBoot3+Vue3实现基本的增删改查功能",9572,1013,"2024-10-18 16:34:55",{"id":92,"uuid":93,"project_id":6,"title":94,"type":9,"status":10,"public_enabled":10,"views":95,"sort":96,"created_at":97,"updated_at":14,"project_title":15,"project_slug":16},527,"7uED9n7e","11. Vue3开发登录注册页面",7106,1031,"2024-10-21 17:35:30",{"id":99,"uuid":100,"project_id":6,"title":101,"type":9,"status":10,"public_enabled":10,"views":102,"sort":103,"created_at":104,"updated_at":14,"project_title":15,"project_slug":16},535,"poUfWrWc","12. Vue3管理系统开发个人信息、修改密码页面",5777,1050,"2024-10-22 17:50:30",{"id":106,"uuid":107,"project_id":6,"title":108,"type":9,"status":10,"public_enabled":10,"views":109,"sort":110,"created_at":111,"updated_at":14,"project_title":15,"project_slug":16},542,"FxHR3hNR","13. SpringBoot3+Vue3实现文件上传下载功能",4489,1057,"2024-10-23 17:31:02",{"id":113,"uuid":114,"project_id":6,"title":115,"type":9,"status":10,"public_enabled":10,"views":116,"sort":117,"created_at":118,"updated_at":14,"project_title":15,"project_slug":16},548,"VPZNSTxr","14. SpringBoot3+Vue3实现富文本编辑器功能",3931,1072,"2024-10-24 17:38:21",{"id":4,"uuid":5,"project_id":6,"title":7,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},{"id":121,"uuid":122,"project_id":6,"title":123,"type":9,"status":10,"public_enabled":10,"views":124,"sort":125,"created_at":126,"updated_at":14,"project_title":15,"project_slug":16},564,"XxSPPFGi","16. SpringBoot3+Vue3实现数据统计图表功能",4236,1109,"2025-01-09 09:40:57"]