# springboot-vue-demo **Repository Path**: division-222/springboot-vue-demo ## Basic Information - **Project Name**: springboot-vue-demo - **Description**: SpringBoot+VUE 图书管理系统,练手项目,可以进行参考。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: https://gitee.com/division-222/springboot-vue-demo - **GVP Project**: No ## Statistics - **Stars**: 28 - **Forks**: 10 - **Created**: 2022-05-07 - **Last Updated**: 2025-05-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: 图书管理系统 ## README # [ springboot+vue练手小项目 ] - **技术栈:** springboot+vue3+element-plus +Mybaties-plus+hutool +mysql8 - **项目介绍** :最近刚学了springboot+vue,就想着做一个小的前后端分离的练手项目,简单的后台管理页面,有基本的登陆注册+增删改查,后面具体的模块等需要的时候的再进行完善,这只是一个练手项目,如果大家运行不出来或者有疑问,欢迎交流。 ### 1.环境搭建 1.安装node环境,npm,cli,这里不再赘述 2.在指定文件夹使用cmd指令创建项目:vue create springboot-vue-demo 3.选择Manually select features![在这里插入图片描述](https://img-blog.csdnimg.cn/20d8ad2bc333416fba8ec4dc4faa87fc.png) 4.选择路由和vuex,这里未选择了eslint语法检测[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![在这里插入图片描述](https://img-blog.csdnimg.cn/79eddac4c074404b9315137e339ac8f6.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_13,color_FFFFFF,t_70,g_se,x_16) 5.选择3.x版本[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-H4bjGfpT-1647170194103)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309152419903.png)\]](https://img-blog.csdnimg.cn/01313a7a07824b64820fb1c22a09aa4f.png) 6.输入y (路由信息为history,)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-K8XKVnNm-1647170194104)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309152546319.png)\]](https://img-blog.csdnimg.cn/616317c8beea4a0fa731a390d18c8dbc.png) 7.选择In package.json ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgXKp4wx-1647170194105)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309152708971.png)\]](https://img-blog.csdnimg.cn/b3c03f1357744e088c015e2323da7899.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 8.是否保存配置 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kToZAsdH-1647170194105)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309152756027.png)\]](https://img-blog.csdnimg.cn/2deb920094914424a8aae41c6f423642.png) 9.创建,启动项目 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8SbmAjbi-1647170194106)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309153156801.png)\]](https://img-blog.csdnimg.cn/91201394dc6a49e0a9b467017255790a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_17,color_FFFFFF,t_70,g_se,x_16) 10.启动成功,浏览器输入8080端口进行访问 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xyJSTE4P-1647170194107)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309153341418.png)\]](https://img-blog.csdnimg.cn/8abde87616874ac48298a5880085026c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_17,color_FFFFFF,t_70,g_se,x_16) 11.在idea打开项目,配置vue启动 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECaYZ44b-1647170194108)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309153839751.png)\]](https://img-blog.csdnimg.cn/6b310f9466d34ab0a0d8d8a3ff949aef.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 选择npm,在npm中Script选项中输入serve ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0yj8MHT-1647170194108)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309154020540.png)\]](https://img-blog.csdnimg.cn/263e42b481814c6e9f0e070d3b77f326.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 之后就可以点击启动键,快捷启动项目了 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ts8xxuQl-1647170194110)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309154109032.png)\]](https://img-blog.csdnimg.cn/72c58a966e5b4ae285f40302a87a27f1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_16,color_FFFFFF,t_70,g_se,x_16) 安装vue插件 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BoOodoxL-1647170194111)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309155231754.png)\]](https://img-blog.csdnimg.cn/9e17a4cf44b34c428b60afb16f499fa2.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) ### 2.项目基本布局 1.引入Element-plus(基于vue3.x版本) ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdfSdwaW-1647170194111)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309161033385.png)\]](https://img-blog.csdnimg.cn/384b85604a834436816073f0b93088f8.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_18,color_FFFFFF,t_70,g_se,x_16) 在idea终端输入指令引入Element-ui依赖 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpot5kCY-1647170194112)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309161155066.png)\]](https://img-blog.csdnimg.cn/f95ba643217d40ff80a75d55f1e19c0b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 项目中引入Element-ui,main.js文件中引入Element-plus ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJK0eYQE-1647170194112)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220309162320799.png)\]](https://img-blog.csdnimg.cn/54682a65f4ef4105b900375d1dae8c9d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 2.删除HelloWorld组件,删除App.vue的多余部分 在components中创建Header组件 在App.js中引入 创建css文件夹,创建global.css文件,在main.js中引入全局css样式 在components中创建Aside(侧边栏),app.vue引入组件 Aside中引入emement导航栏样式 Home中引入四个区域 功能 搜索 表格 分页作为主体区域 3.项目目录 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56llqBrZ-1647170194113)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310133522560.png)\]](https://img-blog.csdnimg.cn/003bcf4cc929489dad6020994a864d56.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_15,color_FFFFFF,t_70,g_se,x_16) 4.代码 global.css ``` *{ margin: 0; padding: 0; box-sizing: border-box; } ``` Aside.vue ``` ``` Header.vue ``` ``` router/index.js ``` import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const routes = [ { path: '/', name: 'home', component: HomeView }, ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router ``` store/index.js ``` import { createStore } from 'vuex' export default createStore({ state: { }, getters: { }, mutations: { }, actions: { }, modules: { } }) ``` HomeView.vue ``` ``` App.vue ``` ``` main.js ``` import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import '@/assets/css/global.css' import zhCn from 'element-plus/es/locale/lang/zh-cn' createApp(App).use(store).use(router).use(ElementPlus, {locale: zhCn,}).mount('#app') ``` ### 3.后台编写 在项目名称右键new一个Moudule为springboot ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eNMuRjoZ-1647170194114)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310092435766.png)\]](https://img-blog.csdnimg.cn/2575481669464961b6bf395825cfa1b5.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U17gkgNC-1647170194114)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310092604149.png)\]](https://img-blog.csdnimg.cn/12237b64ddc74a3e8340de228048602b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 在项目目录下新建vue文件夹 将原来vue的工程文件拷贝进去 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7HpFHg6-1647170194115)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310093145571.png)\]](https://img-blog.csdnimg.cn/b48e547574294d02b02f4b64f30f4d46.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_9,color_FFFFFF,t_70,g_se,x_16) 重新配置serve,因为项目结构改变,要重新选择vue的package.json ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dizv0dNW-1647170194115)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310093324415.png)\]](https://img-blog.csdnimg.cn/45dd187730234d32ab2c71b3f6110462.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_19,color_FFFFFF,t_70,g_se,x_16) ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVdVhpZM-1647170194116)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310093521214.png)\]](https://img-blog.csdnimg.cn/367268dc71a6430a9a4cb00ec446e74e.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 修改项目的maven为自己的本地maven仓库 pom文件 ``` 4.0.0 org.springframework.boot spring-boot-starter-parent 2.6.4 com.example lesson08 0.0.1-SNAPSHOT lesson08 Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-test test com.alibaba druid-spring-boot-starter 1.1.21 org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin ``` 检验是否配置成功 如果报错则将vue文件夹的node_modules文件夹删除 再vue项目执行命令npm install 新的目录结构 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZIhuuKvi-1647170194116)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310101019845.png)\]](https://img-blog.csdnimg.cn/4c9a5850212a4444bede24781851980d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_12,color_FFFFFF,t_70,g_se,x_16) springboot的resource目录下application.property文件修改为application.yml 输入配置(我这是mysql8版本 配置了druid数据库连接池) ``` server: port: 9090 spring: datasource: druid: url: jdbc:mysql://localhost:3306/springboot-vue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver initialSize: 10 maxActive: 20 maxWait: 60000 minIdle: 1 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 testWhileIdle: true testOnBorrow: true testOnReturn: false poolPreparedStatements: true maxOpenPreparedStatements: 20 validationQuery: SELECT 1 validation-query-timeout: 500 filters: stat,wall stat-view-servlet: enabled: true url-pattern: /druid/* reset-enable: true login-username: admin login-password: 10197538 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.syb.po ``` 启动mysql服务 再navicat里新建数据库 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n9CS2DG9-1647170194117)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310102239367.png)\]](https://img-blog.csdnimg.cn/34575df5493048d88a608ecac34ee8cd.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 新建user表 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ly41P8jg-1647170194117)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310102550784.png)\]](https://img-blog.csdnimg.cn/503bf15ee6c241c58923145435a42f97.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 点击启动,启动成功则配置完成 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsCJaAWA-1647170194118)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220310102912554.png)\]](https://img-blog.csdnimg.cn/0b8ee3347ca8418d8646fab95a271ca8.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 引入mybaties-plus pom文件添加依赖 ```xml com.baomidou mybatis-plus-boot-starter 3.4.3.1 ``` 导入分页插件包 com.example.spring目录下创建mapper包 com.example.springboot包创建common包,创建MybatiesPlusCongif类 ``` package com.example.springboot.common; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.example.springboot.mapper") public class MybatisPlusConfig { /** * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } ``` 创建Result包包装返回类型工具类,新建Result ``` package com.example.springboot.common; public class Result { private String code; private String msg; private T data; public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Result() { } public Result(T data) { this.data = data; } public static Result success(){ Result result = new Result<>(); result.setCode("0"); result.setMsg("成功"); return result; } public static Result success(T data){ Result result = new Result<>(data); result.setCode("0"); result.setMsg("成功"); return result; } public static Result error(String code,String msg){ Result result = new Result(); result.setCode(code); result.setMsg(msg); return result; } } ``` com.example.spring目录下新建controller包,新建UserController类 com.example.spring目录下新建entity包,新建User类 ``` package com.example.springboot.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; //数据库表名 @TableName("user") @Data public class User { // 设置ID自增长 @TableId(type = IdType.AUTO) private Integer id; private String username; private String password; private Integer age; private String sex; // private String address } ``` 定义UserMapper接口,继承BaseMapper,传入的泛型为User 再UserConroller中使用@Resource注解引入UserMapper(这里为了简化没有创建service) UserMapper: ``` package com.example.springboot.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.springboot.entity.User; public interface UserMapper extends BaseMapper { } ``` UserConroller: ``` package com.example.springboot.controller; import com.example.springboot.common.Result; import com.example.springboot.entity.User; import com.example.springboot.mapper.UserMapper; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; //定义是返回json的controller @RestController //定义统一的路由 @RequestMapping("/user") public class UserController { //引入UserMaooer @Resource UserMapper userMapper; // 定义post接口 @PostMapping // 新增 RequestBody注解把前台传来的数据转换成java对象实体 public Result save(@RequestBody User user) { //使用userMapper的insert方法新增user对象 userMapper.insert(user); return Result.success(); } } ``` ### 4.前台业务完善 1.在新增按钮中绑定add方法添加用户数据,点击后有弹窗提示 引入elementui Dialog 对话框组件,引入表单组件 点击新增按钮后弹出对话框,点击确认后使用axios将数据存入后台 ``` add() { //打开弹窗 this.dialogVisible = true; //清空 this.form = {} }, //点击确认后保存到后台 save() { //使用axios进行数据交互 //两个参数,url和请求参数 /* 注意这样写会有跨域问题 request.post("/user",this.form).then(res => { console.log(res) }) request.post("http://localhost:9090/user",this.form).then(res => { console.log(res) }) */ //如果id存在 执行更新操作 否则执行新增操作 if (this.form.id) {//更新 request.put("/user", this.form).then(res => { console.log(res) //根据状态码判断 0为成功 这里用了elmentui 的提示框 if (res.code === '0') { this.$message({ type: "success", message: "更新成功" }) } else { this.$message({ type: "error", message: res.msg }) } }) } }, ``` ### 5.前后台数据交互 1.封装axios接口 - 安装axios依赖,在vue目录下执行npm i axios -S - 在vue项目scr目录下新建utils文件夹,新建request.js文件,在request.js里引入axios,request.js用来请求数据 ``` //引入axios包 import axios from 'axios' //创建request对象 const request = axios.create({ baseURL: '/api', // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!! timeout: 5000 }) // request 拦截器 // 可以自请求发送前对请求做一些处理 // 比如统一加token,对请求参数统一加密 request.interceptors.request.use(config => { //设置请求头 config.headers['Content-Type'] = 'application/json;charset=utf-8'; // config.headers['token'] = user.token; // 设置请求头 return config }, error => { return Promise.reject(error) }); // response 拦截器 // 可以在接口响应后统一处理结果 request.interceptors.response.use( response => { //获取返回结果的data let res = response.data; // 如果是返回的文件 if (response.config.responseType === 'blob') { return res } // 兼容服务端返回的字符串数据 if (typeof res === 'string') { res = res ? JSON.parse(res) : res } return res; }, error => { console.log('err' + error) // for debug return Promise.reject(error) } ) export default request ``` - 解决跨域问题,vue文件夹下新建vue.config.js文件,重启项目(这里将端口设置为9876) ``` // 跨域配置 module.exports = { devServer: { //记住,别写错了devServer//设置本地默认端口 选填 port: 9876, proxy: { //设置代理,必须填 '/api': { //设置拦截器 拦截器格式 斜杠+拦截器名字,名字可以自己定 target: 'http://localhost:9090', //代理的目标地址 changeOrigin: true, //是否设置同源,输入是的 pathRewrite: { //路径重写 '^/api': '' //选择忽略拦截器里面的内容 把api解析为空字符串 } } } } } ``` - HomeView.vue中的save方法使用request.post方法访问后台的接口 ``` save(){ request.post("/user",this.form).then(res => { console.log(res) } ``` 2.实现了添加功能,前台点击确认,数据库有数据。接下来要实现前台的数据渲染 - 导入hutool工具类,引入依赖 ``` cn.hutool hutool-all 5.7.3 ``` - 在UserController中新增findpage方法 ``` // 定义get接口 @GetMapping /** * @pageNum 当前页 默认为1 * @pageSize 分页大小 默认为10 * @search 查询关键字 默认为空字符串 */ public Result findPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(defaultValue = "") String search) { //Page是MybatiesPlus提供的类 //分页对象 Wrappers 条件构造器, 通过搜索框绑定的search值,在用户列表中拆模糊查询 LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); //当search为空时查询全部 if (StrUtil.isNotBlank(search)){ wrapper.like(User::getNickName,search); } Page userPage = userMapper.selectPage(new Page<>(pageNum, pageSize), wrapper); return Result.success(userPage); } ``` - 将查到的数据在渲染到前端tableData变量中 HomeView.vue中定义load方法,加载后台数据。在生命周期created(页面加载完成后) 调用load方法,即可在前端拿到后台的数据。 在load().then()内将拿到对象的具体data赋值给tableData,即可将后台数据渲染至前端页面 HomeView.vue ``` //生命周期函数 created() { //调用load() this.load() }, methods: { load() { //传入三个参数 get请求不能传对象 request.get("/user", { params: { pageNum: this.currentPage, pageSize: this.pageSize, search: this.search } }).then(res => { this.tableData = res.data.records; //实现前端显示总条数 this.total = res.data.total; // console.log(res) }) }, ``` ### 6.crud 1.查询 给查询按钮点击事件绑定load方法,点击按钮即可查询 ``` 查询 //注意我这里遇到了el-input输入框无法实时刷新的问题,在网上寻找后发现这应该是element-ui的一个bug,解决方案是:在el-input标签绑定@input事件 @input="onInput" 定义事件 onInput(){ this.$forceUpdate(); } ``` 2.编辑功能 点击编辑按钮,出现弹窗,并且弹窗里有原本的数据 为了避免点击取消后数据依然会有变化,这里选择深拷贝 现在在弹窗点击确定时,后台会报错,因为之前post只有一个新增方法,数据的主键ID重复,所以会报错,这里我们需要改写之前的save方法,并在UserController新增一个update方法 编辑按钮绑定handleEdit方法 ``` 编辑 handleEdit(row) { this.form = JSON.parse(JSON.stringify(row)); this.dialogVisible = true }, ``` 改写save方法 ``` save() { //使用axios进行数据交互 //两个参数,url和请求参数 /* 注意这样写会有跨域问题 request.post("/user",this.form).then(res => { console.log(res) }) request.post("http://localhost:9090/user",this.form).then(res => { console.log(res) }) */ //如果id存在 执行更新操作 否则执行新增操作 if (this.form.id) {//更新 request.put("/user", this.form).then(res => { console.log(res) //根据状态码判断 0为成功 这里用了elmentui 的提示框 if (res.code === '0') { this.$message({ type: "success", message: "更新成功" }) } else { this.$message({ type: "error", message: res.msg }) } this.load()//刷新表格数据 this.dialogVisible = false //关闭弹窗 }) } else {//新增 request.post("/user", this.form).then(res => { console.log(res) if (res.code === '0'){ this.$message({ type: "success", message:"新增成功" }) }else { this.$message({ type: "error", message:res.msg }) } this.load()//刷新表格数据 this.dialogVisible = false //关闭弹窗 }) } }, ``` UserController添加update方法 ``` // 一般put进行更新 @PutMapping public Result update(@RequestBody User user) { userMapper.updateById(user); return Result.success(); } ``` 3.删除: 确认删除按钮,confirm事件绑定handleDelete方法,根据主键id删除 ``` ``` 拿到id后,在后台写一个删除接口 UserController ``` //占位符方式传入参数id 必须用@PathVariable接收 @DeleteMapping("/{id}") public Result delete(@PathVariable Long id) { userMapper.deleteById(id); return Result.success(); } ``` 分页功能: HomeView中有两个方法: handleSizeChange(),改变当前每页个数触发和handleCurrentChange(),改变当前页数触发 ``` handleSizeChange(pageSize) { this.pageSize = pageSize this.load(); }, handleCurrentChange(currentPage) { this.currentPage = currentPage this.load(); } ``` ### 7.登陆注册 因为之前实在App.vue作为页面的框架,所有的页面都是在App.vue里,App在main.js作为根节点,因此应该将App.vue作为访问所有界面的结点,根据具体的路由进行页面展示 重构App.vue ``` ``` 在src目录下新建目录Layout,新建Layout.vue作为框架页面 ``` ``` 修改HomeView.vue为Home.vue 配置路由 router/index.js ``` import { createRouter, createWebHistory } from 'vue-router' import Layout from '../Layout/Layout.vue' import Login from '../views/Login.vue' const routes = [ { //默认路由地址为Layout path: '/', name: 'Layout', component: Layout, //重定向设置路由跳转,当访问'/'时自动跳转到'/home' redirect:"/home", //嵌套子路由 主体区域展示 children:[ { path:"home", name:"Home", component:() => import("@/views/Home") } ] }, { path: '/login', name: 'Login', component: () => import("@/views/Login") }, ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router ``` 编写登陆页面 vies/Login.vue ``` ``` UserCOntroller添加登录接口 ``` //登录接口 @PostMapping("/login") public Result login(@RequestBody User user) { User res = userMapper.selectOne(Wrappers.lambdaQuery().eq(User::getUsername,user.getUsername()).eq(User::getPassword,user.getPassword())); //判断查询是否存在 if (res == null){ return Result.error("-1","用户名或密码错误"); } return Result.success(res); } ``` 登陆后点击退出系统,重新返回登录界面 在Header.vue的添加路由跳转 ``` 个人信息 退出系统 ``` 注册界面 在views目录下新建Register.vue ``` ``` router/index.js新增路由 ``` { path: '/register', name: 'Register', component: () => import("@/views/Register") }, ``` UserController新增注册接口 ``` //注册接口 @PostMapping("/register") public Result register(@RequestBody User user) { //注册之前先验证是否有重名 User res = userMapper.selectOne(Wrappers.lambdaQuery().eq(User::getUsername,user.getUsername())); //判断查询是否存在 if (res != null){ return Result.error("-1","用户名重复!"); } //默认密码 if(user.getPassword() == null){ user.setPassword("123456"); } userMapper.insert(user); return Result.success(); } ``` ### 8.项目路由 我们发现默认打开的是用户界面,因此Home.vue命名不太合适 更名为User.vue,同时将router/index.js中的home都改为user 在Aside.vue的el-menu标签里写入router,可以直接根据el-menu-item的index进行路由跳转 为了实现效果,在views下新建Book.vue,同时写入路由 ``` ``` ``` import { createRouter, createWebHistory } from 'vue-router' import Layout from '../Layout/Layout.vue' import Login from '../views/Login.vue' const routes = [ { //默认路由地址为Layout path: '/', name: 'Layout', component: Layout, //重定向设置路由跳转,当访问'/'时自动跳转到'/home' redirect:"/user", //嵌套子路由 主体区域展示 children:[ { path:"user", name:"User", component:() => import("@/views/User") }, { path:"book", name:"Book", component:() => import("@/views/Book") }, ] }, { path: '/login', name: 'Login', component: () => import("@/views/Login") }, { path: '/register', name: 'Register', component: () => import("@/views/Register") }, ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router ``` 点击菜单栏可以发现实现了路由的跳转 这里有一个bug,每次重新进入项目都会自动进去user,无论我们有没有登录。因此我们可以在request.js里设置拦截器,登陆成功则将用户信息存储在sessionStorage中命名为user变量,若sessionStoragem没有用户信息,则重启项目强制跳转到登陆界面 request.js ``` //引入axios包 import axios from 'axios' import router from "@/router"; //创建request对象 const request = axios.create({ baseURL: '/api', // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!! timeout: 5000 }) // request 拦截器 // 可以自请求发送前对请求做一些处理 // 比如统一加token,对请求参数统一加密 request.interceptors.request.use(config => { config.headers['Content-Type'] = 'application/json;charset=utf-8'; // config.headers['token'] = user.token; // 设置请求头 //取出sessionStorage里面的缓存的用户信息 let userJson = sessionStorage.getItem("user"); if (!userJson){ //如果没有该用户信息则跳转到登陆页面 router.push("login") } return config }, error => { return Promise.reject(error) }); // response 拦截器 // 可以在接口响应后统一处理结果 request.interceptors.response.use( response => { //获取返回结果的data let res = response.data; // 如果是返回的文件 if (response.config.responseType === 'blob') { return res } // 兼容服务端返回的字符串数据 if (typeof res === 'string') { res = res ? JSON.parse(res) : res } return res; }, error => { console.log('err' + error) // for debug return Promise.reject(error) } ) export default request ``` 可以看到,登陆成功后,sessionStorage存储了用户信息 ![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gIoagdBA-1647173109764)(C:\Users\erhang\AppData\Roaming\Typora\typora-user-images\image-20220311162146379.png)\]](https://img-blog.csdnimg.cn/dcab92fda35f4bb58388b49fa9f30e9d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) 有了sessionStorage对象,我们可以页面右上角的用户实时更新为用户名 在Header.vue添加数据user:JSON.parse(sessionStorage.getItem("user"))(这里是将JSON字符串转化为JSON对象), ``` ``` 我们新建Person.vue ## 运行项目 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5b4414502d1a48269c8731e6cca6ecaa.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) ![在这里插入图片描述](https://img-blog.csdnimg.cn/d105e1ee6d544f099b2056c6caa667a2.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_19,color_FFFFFF,t_70,g_se,x_16) ![在这里插入图片描述](https://img-blog.csdnimg.cn/dc1190bc05944a618a69b7738393f3e7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) ![在这里插入图片描述](https://img-blog.csdnimg.cn/49ebbe9d4fc84e3cb2e8dbdb4376f927.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) ![在这里插入图片描述](https://img-blog.csdnimg.cn/f199a327be7f4c409286c4416669b2bd.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16) ![在这里插入图片描述](https://img-blog.csdnimg.cn/1d5c11f16fc54bde824cacf94c952fa6.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMTHnnaHop4nnmoTni64=,size_20,color_FFFFFF,t_70,g_se,x_16)