视频链接
代码链接
一、前端环境搭建
1.创建一个文件夹,在文件加内输入cmd打开命令行窗口,然后检查node.js和npm的版本用命令:node -v
npm -v
2.全局安装一个vue cli插件用命令
npm install -g @vue/cli
3.然后创建一个名字为springboot-vue-demo的项目
(在创建过程中遇到一个代理相关的问题,最后通过更换镜像源,切换代理最终解决问题)
vue create springboot-vue-demo
二、在idea中启动
1.把创建好的项目拖进idea中,设置启动自动跳转页面在如下位置
然后出现以下问题:
网址为http://0.0.0.0:8080/ 的页面可能存在问题,或者已永久移动到新的网址
通过在编辑里边全局查找0.0.0.0 然后修改为localhost,最后成功在idea里边启动,如下图;
三、前端布局以及页面的制作
1.删除不要的组件
1.删除掉多余的Helloworld组件,以及相关的配置,然后自己创建一个Header(头部)组件,然后在HomeView.vue中引入Header组件,代码如下;
<template><!-- 引入Header组件--><div> <Header/> </div> <router-view/></template><style></style><!--导入Header组件--><script >import Header from "@/components/Header";export default{ name:"Layout", components:{ //光标对着Header然后alt+enter,进行引入 Header }}</script>
在Header.vue中创建一个大盒子然后再在其中创建三个小盒子的代码如下;
<template > <div style="height:50px; line-height:50px; border-bottom: 1px solid #ccc; display: flex"> <div style="width: 200px">后台管理</div> <div style="flex:1">时间</div> <div style="width: 100px">下拉框</div> </div></template><script>export default { name: "Header"}</script><style scoped></style>
2.设置全局样式
2.再创建一个全局的css样式管理命名为global.css,代码如下
/*设置全局样式*/*{ margin: 0; padding: 0; box-sizing:border-box ;}
运行之后结果图为
3.在main.js中引入elementPlus组件
以下实在elementplus中的源码
// main.tsimport { createApp } from 'vue'import ElementPlus from 'element-plus'import 'element-plus/dist/index.css'import App from './App.vue'const app = createApp(App)app.use(ElementPlus)app.mount('#app')
需要引入的内容(蓝色部分和我标红的)
3.引入elementplus组件
1.引入下拉框
把文字部分改为自己需要的
<el-dropdown> <span class="el-dropdown-link" style="padding: 18px"> 用户 <el-icon class="el-icon--right"> <arrow-down /> </el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item>个人信息</el-dropdown-item> <el-dropdown-item>退出系统</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown>
2.引入侧边栏
1.再定义一个vue组件Aside.vue,然后再App.vue中引入Aside.vue组件
App.vue中的代码为:
<template><!-- 引入Header组件--><div><!-- 头部--> <Header/><!-- 主体 侧边栏再定义一个div flex布局--> <div style="display: flex"><!-- 侧边栏--> <Aside/><!-- 内容区域--> <router-view style="flex: 1"/> </div></div></template><style></style><!--导入Header组件--><script >import Header from "@/components/Header";import Aside from "@/components/Aside";export default{ name:"Layout", components:{ //光标对着Header然后alt+enter,进行引入 Header, //引入侧边栏vue组件 Aside }}</script>
2.然后引入侧边栏导航,为App.vue设置样式
<template><div> <el-menu default-active="2" class="el-menu-vertical-demo" style="width: 200px; min-height: calc(100vh - 50px)" > <el-sub-menu index="1"> <template #title> <el-icon><location /></el-icon> <span>导航一</span> </template> <el-menu-item index="1-3">选项一</el-menu-item> <el-menu-item index="1-3">选项二</el-menu-item> <el-menu-item index="1-3">选项三</el-menu-item> </el-sub-menu> <el-sub-menu index="2"> <template #title> <el-icon><location /></el-icon> <span>导航二</span> </template> <el-menu-item index="2-3">选项一</el-menu-item> <el-menu-item index="2-3">选项二</el-menu-item> <el-menu-item index="2-3">选项三</el-menu-item> </el-sub-menu> <el-sub-menu index="3"> <template #title> <el-icon><location /></el-icon> <span>导航三</span> </template> <el-menu-item index="3-3">选项一</el-menu-item> <el-menu-item index="3-3">选项二</el-menu-item> <el-menu-item index="3-3">选项三</el-menu-item> </el-sub-menu> </el-menu></div></template><script>export default { name: "Aside"}</script><style scoped></style>
宽度长度
3.引入主体表格等内容
在Home.vue主体组件中引入主体table以及新增、导入、导出、搜索等按钮
基本都是从elementplus上边复制过来的,只是做了一些加外边距、改点样式的工作
<template> <div style="padding: 10px"><!-- 功能区--> <div style="margin: 10px 0"> <el-button type="primary">新增</el-button> <el-button type="primary">导入</el-button> <el-button type="primary">导出</el-button> </div><!-- 搜索区--> <div style="margin:10px 0"> <el-input v-model="search" placeholder="请输入..." style="width: 20%"/> <el-button type="primary" style="margin-left: 5px">查询</el-button> </div><!-- 表格--> <el-table :data="tableData" border stripe style="width: 100%"><!-- sortable是为时间加上排序--> <el-table-column prop="date" label="日期" sortable/> <el-table-column prop="name" label="姓名" /> <el-table-column prop="address" label="地址" /> <el-table-column fixed="right" label="操作" width="120"><!-- 操作部分的按钮--> <template #default> <el-button link type="primary" @click="handleEdit" >编辑</el-button> <el-popconfirm title="确认删除吗?"> <template #reference> <el-button type="text">删除</el-button> </template> </el-popconfirm> </template> </el-table-column> </el-table><!-- 分页组件--> <div style="margin: 10px 0"> <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[5, 10, 20]" :small="small" :disabled="disabled" :background="background" layout="total, sizes, prev, pager, next, jumper" :total="10" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </div></template><script>// @ is an alias to /srcexport default { name: 'HomeView', components: { }, //先为表格填入假数据 data(){ return{ search:'', pageSize:10, currentPage:1, tableData:[{ date: '2016-05-03', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', }, { date: '2016-05-02', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', }, { date: '2016-05-04', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', }, { date: '2016-05-01', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', },] } }, methods:{ handleEdit(){ }, handleSizeChange(){ }, handleCurrentChange(){} },}</script>
做完以上工作得到如下界面:
问题:放大缩小界面后下边会有一个不能自适应屏幕大小的问题
以上问题解决方案:
<template #default="scope"> <el-button link type="primary" size="mini" @click="handleEdit(scope.$index,scope.row)" >编辑</el-button> <el-popconfirm title="确认删除吗?"> <template #reference> <el-button size="mini" type="danger" @click="handleDelete(scope.$index,scope.row)">删除</el-button> </template> </el-popconfirm> </template>
4.细节完善
按钮太大看着别扭,设置小号,可以让它看起来更舒服
如下如所示;
5.加新增弹出框
点击新增的时候弹出添加数据的显示框,用户可以在其中输入数据,需要为新增按钮绑定一个方法:
方法区:
methods:{ add(){ //表单其实一直都在,只是把它设置成为不可见得了 this.dialogVisible=true // 新增的时候一定要清空一下表单域,不会影响下一次操作 this.form={} }, handleEdit(){ }, handleSizeChange(){ }, handleCurrentChange(){} }
再添加elementPlus组件(对话框设置了一个dialogVisible的模型,在数据区设置为默认不可见,在add方法内写入逻辑,当点击确认按钮触发add方法后,对话框就显示出来了):
<!-- 弹出对话框 --> <el-dialog v-model="dialogVisible" title="提示" width="30%"><!-- 表单组件 --> <el-form :model="form" label-width="120px"> <el-form-item label="用户名"> <el-input v-model="form.username" style="width:80%"/> </el-form-item> <el-form-item label="昵称"> <el-input v-model="form.nickName" style="width:80%"/> </el-form-item> <el-form-item label="性别"> <el-radio v-model="form.sex" label="1" size="large">男</el-radio> <el-radio v-model="form.sex" label="2" size="large">女</el-radio> </el-form-item> <el-form-item label="年龄"> <el-input v-model="form.age" style="width:80%"/> </el-form-item> <el-form-item label="地址"> <el-input v-model="form.address" style="width:80%"/> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="dialogVisible = false"> 确认 </el-button> </span> </template> </el-dialog>
四、后端架子以及新增功能
1.创建一个springboot工程
很简单直接在idea里边创建,记得要添加web、mybatis、mysql、lombok(用来简化开发,少写一些东西)
2.配置application.properties文件
1.这是用properties写的比较老的东西了
server.port=9090spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url= jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=truespring.datasource.username= rootspring.datasource.password= root
2.pom文件引入mybatisPlus依赖,目的是为了能够使用mybatisPlus提供的分页插件
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency>
3.引入一些通用类
通用返回类
package com.example.common;public class Result<T> { 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 <T> Result<T> success(T data) { Result<T> 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; }}
分页插件
package com.example.common;import com.baomidou.mybatisplus.annotation.DbType;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;/** * mybatis-plus 分页插件 */@Configuration@MapperScan("com.example.mapper")public class MybatisPlusConfig { /** * 分页插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }}
实体类:
package com.example.entity;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.Data;//引入lombok之后,用这个注解可以将这个实体类和数据库名字为user1的表关联起来@TableName("user1")//使用这个注解可以不用写get set方法@Datapublic class User {// 设置主键自增,下边的id其实可以不用写,如果主键名字不是id的话,就需要写value ="id" @TableId(value ="id", type = IdType.AUTO) private Integer id; private String username; private String password; private Integer age; private String sex; private String address;}
4.写接口
因为这个项目比较简单,所以就没有写service层,只写了mapper和controller
mapper层:
package com.example.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.example.entity.User;import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface UserMapper extends BaseMapper<User> {}
controller层:
package com.example.controller;import com.example.common.Result;import com.example.entity.User;import com.example.mapper.UserMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.PostMapping;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;@RestController@Slf4j@RequestMapping("/user")public class UserController { @Resource UserMapper userMapper;// @RequestBody 的作用就是从前台拿过来的数据把它给封装成User实体类型的数据 @PostMapping public Result<?> save(@RequestBody User user){ userMapper.insert(user); return Result.success(); }}
5.实现前后端交互
在新增表单输入数据,点击确认后前端发送axios请求(要引入一个js文件),写上请求路径(这里涉及一个跨域限制的问题针对本项目实际就是前端的8080端口不能访问后端的9090端口,可以在查到跨域问题的解决方案,就是在前端文件夹里引入一个vue.config.js文件),能够在后端数据库中看到新增的数据
如图;
1.引入axios相关的配置文件
代码:
import axios from 'axios'import router from "@/router";const request = axios.create({ baseURL: "/api", timeout: 5000})// 请求白名单,如果请求在白名单里面,将不会被拦截校验权限const whiteUrls = ["/user/login", '/user/register']// request 拦截器// 可以自请求发送前对请求做一些处理// 比如统一加token,对请求参数统一加密request.interceptors.request.use(config => { config.headers['Content-Type'] = 'application/json;charset=utf-8'; // 取出sessionStorage里面缓存的用户信息 let userJson = sessionStorage.getItem("user") if (!whiteUrls.includes(config.url)) { // 校验请求白名单 if(!userJson) { router.push("/login") } else { let user = JSON.parse(userJson); config.headers['token'] = user.token; // 设置请求头 } } return config}, error => { return Promise.reject(error)});// response 拦截器// 可以在接口响应后统一处理结果request.interceptors.response.use( response => { let res = response.data; // 如果是返回的文件 if (response.config.responseType === 'blob') { return res } // 兼容服务端返回的字符串数据 if (typeof res === 'string') { res = res ? JSON.parse(res) : res } // 验证token if (res.code === '401') { console.error("token过期,重新登录") router.push("/login") } return res; }, error => { console.log('err' + error) // for debug return Promise.reject(error) })export default request
2.设置访问的请求路径
确认按钮添加上save方法
在save方法内设置请求路径
3.涉及一个跨域限制的问题针对本项目实际就是前端的8080端口不能访问后端的9090端口,可以在查到跨域问题的解决方案,就是在前端文件夹里引入一个vue.config.js文件(注意要在前端的工程的那个文件夹下)
代码:
// 跨域配置module.exports = { devServer: { //记住,别写错了devServer//设置本地默认端口 选填 port: 9876, proxy: { //设置代理,必须填 '/api': { //设置拦截器 拦截器格式 斜杠+拦截器名字,名字可以自己定 target: 'http://localhost:9090', //代理的目标地址 changeOrigin: true, //是否设置同源,输入是的 pathRewrite: { //路径重写 '/api': '' //选择忽略拦截器里面的单词 } } } }}
6.测试是否能从前端接收到数据
在UserController里边打一个断点,用debug模式启动
在输入框输入数据
此时idea响应并且成功接收到数据
放开断点,让程序启动
在数据库看到新添加的数据
7.至此完成自己第一个前后台交互的功能
以前自己也偷偷学过相关的内容,但是一遇到一点问题自己就坚持不下去,然后就放弃了,这一次真的不错,虽然还是遇到许多问题,但是都通过自己的努力都得到了解决,感谢自己一点一滴的坚持,加油!
对每一位正在学习的朋友们说一句,问题肯定会有的,但是只要你肯坚持,那么就一定可以战胜,最难不过坚持!
五、将数据在页面显示
前端
分页组件:
<!-- 分页组件--> <div style="margin: 10px 0"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[5, 10, 20]" :page-size="pageSize" :small="small" :disabled="disabled" :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total" />
后端
hutool的工具类:官网上搜索,然后引入依赖,可以用来判断某个字段是否为空
// TODO 差点死在这,草 要长记性呀,对于这种查询某个字段一定要看他是不是为空,要对他进行一个判断,不然会出问题的 if (StrUtil.isNotBlank(search)){ queryWrapper.like(User::getNickName,search); } Page<User> userPage= userMapper.selectPage(new Page<>(pageNum,pageSize),queryWrapper); return Result.success(userPage);
分页插件:
package com.example.common;import com.baomidou.mybatisplus.annotation.DbType;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;/** * mybatis-plus 分页插件 */@Configuration@MapperScan("com.example.mapper")public class MybatisPlusConfig { /** * 分页插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }}
注意:对某一个字段进行查询的时候一定要判断它是否为空,不然可能查不出来
/** * 查询的方法 * @param pageNum * @param pageSize * @param search * @return */ @GetMapping// defaultValue就是设置默认访问值, public Result<?> findPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "5") Integer pageSize, @RequestParam(defaultValue = "") String search){ LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();// TODO 差点死在这,草 要长记性呀,对于这种查询某个字段一定要看他是不是为空,要对他进行一个判断,不然会出问题的 if (StrUtil.isNotBlank(search)){ queryWrapper.like(User::getNickName,search); } Page<User> userPage= userMapper.selectPage(new Page<>(pageNum,pageSize),queryWrapper); return Result.success(userPage); }
六、查询功能
前端
查询按钮处:
<!-- 搜索区--> <div style="margin:10px 0"> <el-input v-model="search" placeholder="请输入..." style="width: 20%" clearable ></el-input> <el-button type="primary" style="margin-left: 5px" @click="load" >查询</el-button> </div>
对应的方法:
// 加载的方法,在加载的时候调用 created() { this.load() }, methods:{ load(){ request.get("/user",{params:{ pageNum:this.currentPage, pageSize:this.pageSize, search:this.search, }}).then(res=>{ console.log(res) this.tableData = res.data.records this.total=res.data.total }) }
后端
七、编辑功能
前端
点击编辑的时候弹出一个可以编辑的对话框(和新增时候弹出来的是一个,不同的是它会有数据的回显)
代码:
<!-- 操作框--> <el-table-column fixed="right" label="操作" > <template #default="scope"> <el-popconfirm title="确认删除吗?"> <template #reference> <el-button size="small" type="danger" @click="handleDelete(scope.$index,scope.row)">删除</el-button> </template> </el-popconfirm> <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button> </template> </el-table-column>
方法内:
handleEdit(row){ //弹出编辑框,和新增框一样的请求路径,深克隆 this.form=JSON.parse(JSON.stringify(row)) //弹出对话框 this.dialogVisible=true },
在前端页面点击编辑按钮,弹出对话框,编辑后点击确认按钮,执行save方法
save方法内需要判断执行的是新增还是编辑,通过id是否为空进行判断
// 点击确认按钮后触发的添加保存方法 save(){ if (this.form.id){ //更新 request.put("/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 }) }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 }) } }) } }
后端
在UserController中,新增一个方法
/** * 实现user对象的更新,修改功能 * @param user * @return */@PutMappingpublic Result<?> update(@RequestBody User user){ userMapper.updateById(user); return Result.success();}
过程中遇到的一个bug:
解决方案
自己重新把那里的代码敲了一遍,觉得最有可能的原因就是当初选择类型的时候form没有选正确,导致的错误,唉服了,
生气!
八、分页功能
前端
<!-- 分页组件--> <div style="margin: 10px 0"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[5, 10, 20]" :page-size="pageSize" :small="small" :disabled="disabled" :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total" />
对方法的实现
//改变每页的条数 handleSizeChange(pageSize){ this.pageSize=pageSize this.load() }, //改变当前页 handleCurrentChange(pageNum){ this.currentPage=pageNum this.load() }
后端
后端内容没有添加
遇到的bug
无论怎么切换每页的条数,下方控制台给出的响应都是pageNum=1&pageSize=10,故问题出在这里
查看elementPlus分页组件的使用方法后发现需要给分页对应的方法传递参数
运行效果图:
九、删除功能
需要注意的就是在后端controller中它的路径,如下图所示,因为它前端的
如果不写id
那么就会报如下错误
前端
注意 request.delete(“/user/”+ id ) 这个里边的路径写法和连接方式
handleDelete(id){ console.log(id) //删除 + id request.delete("/user/"+ id ).then(res => { if(res.code === '0'){ this.$message({ type:"success", message:"删除成功" }) }else { this.message({ type:"error", message:res.msg }) } this.load() //重新加载 }) },
后端
/** * 删除 * @param id * @return */ @DeleteMapping("/{id}") public Result<?> delete( @PathVariable Long id){ userMapper.deleteById(id); return Result.success(); }
运行结果图
十、路由设置
原理
在router文件夹下的index.js文件中也把App.vue设置为根路由,因为布局相关的代码都在里边,所以以后访问登录页面的时候也会有布局信息出来,如下图
把App.vue中的布局代码拷贝到一个心新的vue中命名为Layout.vue
<template> <div> <!-- 头部--> <Header/> <!-- 主体 侧边栏再定义一个div flex布局--> <div style="display: flex"> <!-- 侧边栏--> <Aside/> <!-- 内容区域--> <router-view style="flex: 1"/> </div> </div></template><script>import Header from "@/components/Header";import Aside from "@/components/Aside";export default { name: "Layout", components:{ //光标对着Header然后alt+enter,进行引入 Header, //引入侧边栏vue组件 Aside }}</script><style scoped></style>
解决方案:
在router文件夹下的index.js文件中的路由改成Layout
再次输入http://lcoalhost:9876/home时会有如下界面
登录功能
前端
在创建一个login.vue,里边写登录页面
<template><!-- 100vh表示高度整个屏幕 overflow:hidden表示隐藏空白处--><div style="width: 100%; height:100vh; background-color: darkslateblue; overflow:hidden "> <div style="width: 400px; margin: 150px auto"> <div style="color: #cccccc; text-align: center; font-size:30px; padding: 30px 0">欢迎登陆</div><!-- 对应的数据变量如果没有变紫色,表示没有用成功--> <el-form ref="form" :model="form" size="normal" :rules="rules"> <el-form-item prop="username"> <el-input v-model="form.username"> </el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="form.password" show-password> </el-input> </el-form-item> <el-form-item> <el-button style="width: 100% " type="primary" @click="login">登录</el-button> </el-form-item> </el-form> </div></div></template><script>import request from "@/utils/request";export default { name: "Login", data(){ return { form: {}, rules:{ username: [ {required: true, message: '请输入用户名', trigger: 'blur'}, ], password: [ {required: true, message: '请输入密码', trigger: 'blur'}, ] }, } }, methods:{ //登录 login(){ //表单验证内容,满足一下验证需求,他才会执行下面的请求 this.$refs['form'].validate((valid) => { if (valid) { request.post("/user/login",this.form).then(res => { if (res.code ==='0'){ this.$message({ type:"success", message:"登陆成功" }) // 登陆成功后页面跳转 this.$router.push("/") }else { this.$message({ type:"error", message:res.msg }) } }) } }) }, },}</script><style scoped></style>
在登录方法里把请求路径写好
在前端router文件夹里边找到index.js这个文件,设置路由
import { createRouter, createWebHistory } from 'vue-router'import Layout from '../layout/Layout.vue'const routes = [ { path: '/', name: 'Layout', component: Layout, //输入/的时候页面会自动跳转到/home页面 redirect:"/home", children: [ { path:'home', name:'Home', component: () => import("@/views/HomeView"), } ] }, { //写登录页面路由 path:'/login', name:'Login', //导入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
后端
在controller中写的PostMapping
@PostMapping("/login") public Result<?> login(@RequestBody User user){// 查询用户名,密码是否存在 User res= userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUsername,user.getUsername()).eq(User::getPassword,user.getPassword())); if(res == null){ return Result.error("-1","用户名密码错误"); } return Result.success(); }
注册功能
前端
<template><!-- 100vh表示高度整个屏幕 overflow:hidden表示隐藏空白处--><div style="width: 100%; height:100vh; background-color: darkslateblue; overflow:hidden "> <div style="width: 400px; margin: 150px auto"> <div style="color: #cccccc; text-align: center; font-size:30px; padding: 30px 0">欢迎注册</div><!-- 对应的数据变量如果没有变紫色,表示没有用成功--> <el-form ref="form" :model="form" size="normal" :rules="rules"> <el-form-item prop="username"> <el-input v-model="form.username"> </el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="form.password" show-password> </el-input> </el-form-item><!-- 确认密码--> <el-form-item prop="confirm"> <el-input v-model="form.confirm" show-password> </el-input> </el-form-item> <el-form-item> <el-button style="width: 100% " type="primary" @click="register">注册</el-button> </el-form-item> </el-form> </div></div></template><script>import request from "@/utils/request";export default { name: "register", data(){ return { form:{}, rules:{ username: [ {required: true, message: '请输入用户名', trigger: 'blur'}, ], password: [ {required: true, message: '请输入密码', trigger: 'blur'}, ], confirm: [ {required: true, message: '请确认密码', trigger: 'blur'}, ] } } }, methods:{ //表单验证 register(){ if (this.form.password != this.form.confirm){ this.$message({ type:"error", message:"两次密码输入不一致!" }) return } //表单验证 this.$refs[form].validate((valid) => { if (valid) { request.post("/user/register",this.form).then(res => { if (res.code ==='0'){ this.$message({ type:"success", message:"注册成功" }) // 成功后页面跳转 this.$router.push("/login") }else { this.$message({ type:"error", message:res.msg }) } }) }}) }, },}</script><style scoped></style>
同登录功能一样,设置路由
后端
1.查询用户名是否存在
2.如果没有设置密码,默认为******
@PostMapping("/register") public Result<?> register(@RequestBody User user){// 查询用户名,密码是否存在 User res= userMapper.selectOne(Wrappers.<User>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(); }
表单验证功能
效果图:
怎样实现:
1.在表单位置引入验证
3.验证表单内容,只有满足这个条件才执行下面的请求路径
bug:
解决方案:这种错误一般都是这个单词对应的代码格式写的不正确,导致没有识别出来
个人信息查看
1.写一个Person.vue
<template> <div> <el-card style="width: 40%; margin: 10px"> <el-form ref="form" :model="form" label-width="80px"> <el-form-item style="text-align: center" label-width="0"> <el-upload class="avatar-uploader" action="http://localhost:9090/files/upload" :show-file-list="false" :on-success="handleAvatarSuccess" > <img v-if="form.avatar" :src="form.avatar" class="avatar"> <i v-else class="el-icon-plus avatar-uploader-icon"></i> </el-upload> </el-form-item> <el-form-item label="用户名"> <el-input v-model="form.username" disabled></el-input> </el-form-item> <el-form-item label="昵称"> <el-input v-model="form.nickName"></el-input> </el-form-item> <el-form-item label="年龄"> <el-input v-model="form.age"></el-input> </el-form-item> <el-form-item label="性别"> <el-input v-model="form.sex"></el-input> </el-form-item> <el-form-item label="地址"> <el-input v-model="form.address"></el-input> </el-form-item><!-- <el-form-item label="密码">--><!-- <el-input v-model="form.password" show-password></el-input>--><!-- </el-form-item>--> <el-form-item label="余额(¥)"> <el-input v-model="form.account" show-password></el-input> </el-form-item> </el-form> <div style="text-align: center"> <el-button type="primary" @click="update">保存</el-button> </div> </el-card> </div></template><script>import request from "@/utils/request";export default { name: "Person", data() { return { form: {} } }, created() { let str = sessionStorage.getItem("user") || "{}" this.form = JSON.parse(str) }, methods: { handleAvatarSuccess(res) { this.form.avatar = res.data this.$message.success("上传成功") // this.update() }, update() { request.put("/user", this.form).then(res => { console.log(res) if (res.code === '0') { this.$message({ type: "success", message: "更新成功" }) sessionStorage.setItem("user", JSON.stringify(this.form)) // 触发Layout更新用户信息 this.$emit("userInfo") } else { this.$message({ type: "error", message: res.msg }) } }) } }}</script><style>.avatar-uploader .el-upload { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden;}.avatar-uploader .el-upload:hover { border-color: #409EFF;}.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 120px; height: 120px; line-height: 120px; text-align: center;}.avatar { width: 178px; height: 178px; display: block;}</style>
2.然后在Layout路由下设置一个子路由
import { createRouter, createWebHistory } from 'vue-router'import Layout from '../layout/Layout.vue'const routes = [ { path: '/', name: 'Layout', component: Layout, //输入/的时候页面会自动跳转到/home页面 redirect:"/home", children: [ { path:'home', name:'Home', component: () => import("@/views/HomeView"), }, // 个人信息界面 { path:'person', name:'Person', component: () => import("@/views/Person"), } ] }, { path:'/login', name:'Login', //导入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
3.给“个人信息”绑定点击事件,实现跳转
鸡肋拦截器
没有写(不会写,哈哈)
页面跳转
点击书籍管理的时候,它会跳转到新的一个功能页
在侧边栏vue中这样写
然后添加一个Book.vue(到这里其实就可以直接把User.vue复制过来,然后改一下table和新增表单里边的关键词,还有一些路径)
<template> <div style="padding: 10px"> <!-- 功能区--> <div style="margin: 10px 0"> <el-button type="primary" @click="add">新增</el-button> <el-button type="primary">导入</el-button> <el-button type="primary">导出</el-button> </div> <!-- 搜索区--> <div style="margin:10px 0"> <el-input v-model="search" placeholder="请输入..." style="width: 20%" clearable ></el-input> <el-button type="primary" style="margin-left: 5px" @click="load" >查询</el-button> </div>…… <!-- 表格--> <el-table :data="tableData" border stripe style="width: 100%"> <el-table-column prop="id" label="ID" width="150" /> <el-table-column prop="name" label="名称" /> <el-table-column prop="price" label="价格" /> <el-table-column prop="author" label="作者"/> <el-table-column prop="createTime" label="出版时间" /> <!-- 操作框--> <el-table-column fixed="right" label="操作" > <template #default="scope"> <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button> <!--elementPlus提供的方法 因为要根据id进行删除,索引就需要传入id--> <el-popconfirm title="确认删除吗?" @confirm="handleDelete(scope.row.id)"> <template #reference> <el-button size="mini" type="danger" >删除</el-button> </template> </el-popconfirm> </template> </el-table-column> </el-table> <!-- 分页组件--> <div style="margin: 10px 0"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[5, 10, 20]" :page-size="pageSize" :small="small" :disabled="disabled" :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total" /> <!-- 弹出对话框 --> <el-dialog v-model="dialogVisible" title="提示" width="30%"> <!-- 表单组件 --> <el-form :model="form" label-width="120px"> <el-form-item label="名称"> <el-input v-model="form.name" style="width:80%"/> </el-form-item> <el-form-item label="价格"> <el-input v-model="form.price" style="width:80%"/> </el-form-item> <el-form-item label="作者"> <el-input v-model="form.author" style="width:80%"/> </el-form-item> <el-form-item label="出版时间"> <el-date-picker v-model="form.createTime" value-format="YYYY-MM-DD" type="date" style="width:80%" clearable></el-date-picker> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="save">确认</el-button> </span> </template> </el-dialog> </div> </div></template><script>// @ is an alias to /srcimport request from "@/utils/request";export default { name: 'HomeView', components: { }, //先为表格填入假数据 data(){ return{ form:{}, dialogVisible:false, search:'', pageSize:10, currentPage:1, tableData:[], total:0, } }, // 加载的方法,在加载的时候调用 created() { this.load() }, methods:{ //刷新表格的数据 load(){ request.get("/book",{ params:{ pageNum:this.currentPage, pageSize:this.pageSize, search:this.search, }}).then(res => { console.log(res) this.tableData = res.data.records this.total=res.data.total }) }, add(){ //表单其实一直都在,只是把它设置成为不可见得了 this.dialogVisible=true // 新增的时候一定要清空一下表单域,不会影响下一次操作 this.form={} }, // 点击确认按钮后触发的添加保存方法 save(){ if (this.form.id){ //更新 request.put("/book",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 }) }else { //新增 request.post("/book",this.form).then(res => { console.log(res) if(res.code === '0'){ this.$message({ type:"success", message:"新增成功" }) this.load() this.dialogVisible=false }else { this.message({ type:"error", message:res.msg }) } }) } }, handleDelete(id){ console.log(id) //删除 + id request.delete("/book/"+ id ).then(res => { if(res.code === '0'){ this.$message({ type:"success", message:"删除成功" }) }else { this.message({ type:"error", message:res.msg }) } this.load() //重新加载 }) }, handleEdit(row){ //弹出编辑框,和新增框一样的请求路径,深克隆 this.form=JSON.parse(JSON.stringify(row)) //弹出对话框 this.dialogVisible=true }, //改变每页的条数 handleSizeChange(pageSize){ this.pageSize=pageSize this.load() }, //改变当前页 handleCurrentChange(pageNum){ this.currentPage=pageNum this.load() } },}</script>
改的路径
然后就是设置一下路由 (在Layout下设置的子路由,以Layout为背景)
日期格式化:
问题
怎么做:
后端实体类数据上加 @JsonFormat
效果:
高亮显示不正确
基本功能就这么多啦!