[ 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

4.选择路由和vuex,这里未选择了eslint语法检测[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.选择3.x版本[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-H4bjGfpT-1647170194103)(C:UserserhangAppDataRoamingTypora ypora-user-imagesimage-20220309152419903.png)]](https://img-blog.csdnimg.cn/01313a7a07824b64820fb1c22a09aa4f.png)

6.输入y (路由信息为history,)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-K8XKVnNm-1647170194104)(C:UserserhangAppDataRoamingTypora ypora-user-imagesimage-20220309152546319.png)]](https://img-blog.csdnimg.cn/616317c8beea4a0fa731a390d18c8dbc.png)

7.选择In package.json

8.是否保存配置

9.创建,启动项目

10.启动成功,浏览器输入8080端口进行访问

11.在idea打开项目,配置vue启动

选择npm,在npm中Script选项中输入serve

之后就可以点击启动键,快捷启动项目了

安装vue插件

2.项目基本布局

1.引入Element-plus(基于vue3.x版本)

在idea终端输入指令引入Element-ui依赖

项目中引入Element-ui,main.js文件中引入Element-plus

2.删除HelloWorld组件,删除App.vue的多余部分

在components中创建Header组件 在App.js中引入

创建css文件夹,创建global.css文件,在main.js中引入全局css样式

在components中创建Aside(侧边栏),app.vue引入组件

Aside中引入emement导航栏样式

Home中引入四个区域 功能 搜索 表格 分页作为主体区域

3.项目目录

4.代码

global.css

*{margin: 0;padding: 0;box-sizing: border-box;
}

Aside.vue

<template><el-row class="tac"><el-col :span="24"><el-menuactive-text-color="#ffd04b"background-color="#545c64"class="el-menu-vertical-demo"default-active="2"style="width: 200px;min-height:100vh"text-color="#fff"><el-sub-menu index="1"><template #title><el-icon><location /></el-icon><span>选项1</span></template><el-menu-item index="1-1"><el-icon><setting /></el-icon><span>选项1-1</span></el-menu-item></el-sub-menu><el-sub-menu index="2"><template #title><el-icon><location /></el-icon><span>选项2</span></template><el-menu-item index="2-1"><el-icon><setting /></el-icon><span>选项2-1</span></el-menu-item></el-sub-menu><el-menu-item index="3"><el-icon><icon-menu /></el-icon><span>3</span></el-menu-item></el-menu></el-col></el-row>
</template><script>
export default {name: "Aside"
}
</script><style scoped></style>

Header.vue

<template><div style="height: 50px;line-height: 50px;border-bottom: 1px solid #ccc;display: flex"><div style="width: 200px;font-weight: bold;padding-left: 20px;color: blue">后天管理</div><div style="flex: 1"></div><div style="width: 100px;margin-right: 20px;margin-top: 20px"><el-dropdown><span class="el-dropdown-link">张三<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></div></div>
</template><script>
export default {name: "Header"
}
</script><style scoped></style>

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

<template><div class="home" 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" style="width: 20%;" placeholder="请输入关键字"></el-input><el-button type="primary" style="margin-left: 5px">查询</el-button></div><!--    表格区域--><el-table :data="tableData" border stripe style="width: 100%"><el-table-column prop="id" label="ID" sortable/><el-table-column prop="username" label="用户名"/><el-table-column prop="nickName" label="昵称"/><el-table-column prop="age" label="年龄"/><el-table-column prop="sex" label="性别"/><el-table-column label="操作"><template #default="scope"><el-button size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button><el-popconfirm title="确定删除吗?"><template #reference><el-buttonsize="small"type="danger"@click="handleDelete(scope.$index, scope.row)">删除</el-button></template></el-popconfirm></template></el-table-column></el-table><!--    分页--><div style="margin: 10px 0"><el-pagination:currentPage="currentPage":page-sizes="[5,10,20]":page-size="pageSize":small="small":disabled="disabled":background="background"layout="total, sizes, prev, pager, next, jumper":total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"></el-pagination></div></div>
</template><script>export default {name: 'HomeView',components: {},data() {return {form:{},dialogVisible: false,serch: '',currentPage: 1,pageSize:"10",total:  0,tableData: [{id: '1',name: 'Tom',nickName: '189号',age: 18,sex: "男",},{id: '1',name: 'Tom',nickName: '189号',age: 18,sex: "男",},{id: '1',name: 'Tom',nickName: '189号',age: 18,sex: "男",},{id: '1',name: 'Tom',nickName: '189号',age: 18,sex: "男",},]}},methods: {handleEdit() {},handleDelete() {},handleSizeChange(){},handleCurrentChange(){}}
}
</script>

App.vue

<template><nav><!--头部--><Header></Header><!--主题--><div style="display: flex"><!-- 侧边栏--><Aside></Aside><!--内容区域--><router-view style="flex: 1;"></router-view></div></nav>1</template><script>
import Header from "@/components/Header";
import Aside from "@/components/Aside";export default {name: "App",components: {Header,Aside}
}
</script>

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

在项目目录下新建vue文件夹 将原来vue的工程文件拷贝进去

重新配置serve,因为项目结构改变,要重新选择vue的package.json

修改项目的maven为自己的本地maven仓库

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.4</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>lesson08</artifactId><version>0.0.1-SNAPSHOT</version><name>lesson08</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.21</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

检验是否配置成功

如果报错则将vue文件夹的node_modules文件夹删除 再vue项目执行命令npm install

新的目录结构

springboot的resource目录下application.property文件修改为application.yml

输入配置(我这是mysql8版本 配置了druid数据库连接池)

server:port: 9090spring:datasource:druid:url: jdbc:mysql://localhost:3306/springboot-vueusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.DriverinitialSize: 10maxActive: 20maxWait: 60000minIdle: 1timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000testWhileIdle: truetestOnBorrow: truetestOnReturn: falsepoolPreparedStatements: truemaxOpenPreparedStatements: 20validationQuery: SELECT 1validation-query-timeout: 500filters: stat,wallstat-view-servlet:enabled: trueurl-pattern: /druid/*reset-enable: truelogin-username: adminlogin-password: 10197538mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.syb.po

启动mysql服务

再navicat里新建数据库

新建user表

点击启动,启动成功则配置完成

引入mybaties-plus pom文件添加依赖

    <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3.1</version></dependency>

导入分页插件包

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 避免缓存出现问题(该属性会在旧插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}}

创建Result包包装返回类型工具类,新建Result

package com.example.springboot.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;}
}

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<User>
{
}

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@ResourceUserMapper 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<User> wrapper = Wrappers.<User>lambdaQuery();//当search为空时查询全部if (StrUtil.isNotBlank(search)){wrapper.like(User::getNickName,search);}Page<User> 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 v-model="search" @input="onInput" style="width: 20%;" clearable placeholder="请输入关键字"  />
<el-button type="primary" @click="load" style="margin-left: 5px">查询</el-button>//注意我这里遇到了el-input输入框无法实时刷新的问题,在网上寻找后发现这应该是element-ui的一个bug,解决方案是:在el-input标签绑定@input事件  @input="onInput"定义事件
onInput(){this.$forceUpdate();}

2.编辑功能

点击编辑按钮,出现弹窗,并且弹窗里有原本的数据

为了避免点击取消后数据依然会有变化,这里选择深拷贝

现在在弹窗点击确定时,后台会报错,因为之前post只有一个新增方法,数据的主键ID重复,所以会报错,这里我们需要改写之前的save方法,并在UserController新增一个update方法

编辑按钮绑定handleEdit方法

 <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>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删除

          <el-popconfirm title="确定删除吗?" @confirm="handleDelete(scope.row.id)"><template #reference><el-buttonsize="small"type="danger">删除</el-button></template></el-popconfirm>

拿到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 = pageSizethis.load();
},
handleCurrentChange(currentPage) {this.currentPage = currentPagethis.load();
}

7.登陆注册

因为之前实在App.vue作为页面的框架,所有的页面都是在App.vue里,App在main.js作为根节点,因此应该将App.vue作为访问所有界面的结点,根据具体的路由进行页面展示

重构App.vue

<template><div><router-view></router-view></div>
</template><script>export default {name: "App",components: {}
}
</script>

在src目录下新建目录Layout,新建Layout.vue作为框架页面

<template><div><!--头部--><Header></Header><!--主题--><div style="display: flex"><!-- 侧边栏--><Aside></Aside><!--内容区域--><router-view style="flex: 1;"></router-view></div></div>
</template><script>
import Header from "@/components/Header";
import Aside from "@/components/Aside";export default {name: "Layout",components:{Header,Aside}
}
</script><style scoped></style>

修改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 = [{//默认路由地址为Layoutpath: '/',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

<template><div style="width: 100%;height: 100vh;overflow:hidden ;background-color: darkcyan;"><div style="width: 400px;margin: 150px auto"><div style="color:#cccccc; padding: 30px 0; font-size: 30px;text-align: center">欢迎登陆</div><el-form ref="formRef" :model="form" size="normal" :rules="rules"><el-form-item prop="username"><el-input placeholder="请输入用户名" v-model="form.username"></el-input></el-form-item><el-form-item prop="password"><el-input placeholder="请输入密码" v-model="form.password" style=""></el-input></el-form-item><el-form-item><el-button style="width: 100%;" @click="login" type="primary">登录</el-button></el-form-item><el-form-item><el-button style="width: 100%;" @click="toRegister" type="primary">注册</el-button></el-form-item></el-form></div></div>
</template><script>
import request from "@/utils/request";export default {name: "Login",components: {},data() {return {form: {},rules: {username: [{required: true,message: '请输入用户名',trigger: 'blur',},],password: [{required: true,message: '请输入密码',trigger: 'blur',},],}}},methods: {toRegister(){this.$router.push("/login")},login() {//满足校验规则才传给后台数据this.$refs['formRef'].validate((valid) => {if (valid) {//将登录信息传给后台request.post("/user/login", this.form).then(res => {if (res.code === '0') {this.$message({type: "success",message: "登陆成功"})//登陆成功后将用户信息存到sessionStoragesessionStorage.setItem("user",JSON.stringify(res.data))this.$router.push("/") //登录成功后进行页面跳转 跳转到主页} else {this.$message({type: "error",message: res.msg})}})}})}}
}</script><style scoped></style>

UserCOntroller添加登录接口

//登录接口
@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(res);
}

登陆后点击退出系统,重新返回登录界面

在Header.vue的添加路由跳转

<el-dropdown-menu><el-dropdown-item>个人信息</el-dropdown-item><el-dropdown-item @click="$router.push('/login')">退出系统</el-dropdown-item>
</el-dropdown-menu>

注册界面

在views目录下新建Register.vue

<template><div style="width: 100%;height: 100vh;overflow:hidden ;background-color: darkcyan;"><div style="width: 400px;margin: 150px auto"><div style="color:#cccccc; padding: 30px 0; font-size: 30px;text-align: center">欢迎注册</div><el-form ref="formRef" :model="form" size="normal" :rules="rules"><el-form-item prop="username"><el-input placeholder="请输入用户名" v-model="form.username"></el-input></el-form-item><el-form-item prop="password"><el-input placeholder="请输入密码" v-model="form.password" style=""></el-input></el-form-item><el-form-item prop="confirm"><el-input placeholder="确认密码" v-model="form.confirm" style=""></el-input></el-form-item><el-form-item><el-button style="width: 100%;" @click="register" type="primary">注册</el-button></el-form-item><el-form-item><el-button style="width: 100%;" @click="toLogin" type="primary">已有帐号</el-button></el-form-item></el-form></div></div>
</template><script>
import request from "@/utils/request";export default {name: "Register",components: {},data() {return {form: {},//表单校验规则rules: {username: [{required: true,message: '请输入用户名',trigger: 'blur',},],password: [{required: true,message: '请输入密码',trigger: 'blur',},],confirm: [{required: true,message: '请确认密码',trigger: 'blur',},],}}},methods: {toLogin(){this.$router.push("/login")},register() {//判断两次密码是否一致if (this.form.password !== this.form.confirm) {this.$message({type: "error",message: "2次密码输入不一致!"})return}//满足校验规则才传给后台数据this.$refs['formRef'].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") //Register成功后进行页面跳转 跳转到登录} else {this.$message({type: "error",message: res.msg})}})}})}}
}
</script><style scoped></style>

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.<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();
}

8.项目路由

我们发现默认打开的是用户界面,因此Home.vue命名不太合适

更名为User.vue,同时将router/index.js中的home都改为user

在Aside.vue的el-menu标签里写入router,可以直接根据el-menu-item的index进行路由跳转

为了实现效果,在views下新建Book.vue,同时写入路由

<template><div>我是book</div>
</template><script>
export default {name: "book"
}
</script><style scoped></style>import { createRouter, createWebHistory } from 'vue-router'
import Layout from '../Layout/Layout.vue'
import Login from '../views/Login.vue'const routes = [{//默认路由地址为Layoutpath: '/',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 => {//获取返回结果的datalet 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 debugreturn Promise.reject(error)}
)export default request

可以看到,登陆成功后,sessionStorage存储了用户信息

有了sessionStorage对象,我们可以页面右上角的用户实时更新为用户名

在Header.vue添加数据user:JSON.parse(sessionStorage.getItem(“user”))(这里是将JSON字符串转化为JSON对象),

<template><div style="height: 50px;line-height: 50px;border-bottom: 1px solid #ccc;display: flex"><div style="width: 200px;font-weight: bold;padding-left: 20px;color: blue">后台管理</div><div style="flex: 1"></div><div style="width: 100px;margin-right: 20px;margin-top: 20px"><el-dropdown><span class="el-dropdown-link"><!--绑定user的username属性-->管理员:{{ user.username }}<el-icon class="el-icon--right"><arrow-down/></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item @click="$router.push('/person')">个人信息</el-dropdown-item><el-dropdown-item @click="$router.push('/login')">退出系统</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></div>
</template><script>
export default {name: "Header",data() {return {//注意这里是将JSON字符串转化为JSON对象 muser: sessionStorage.getItem("user")?JSON.parse(sessionStorage.getItem("user")):""}}
}
</script><style scoped></style>

我们新建Person.vue

运行项目




springboot+vue练手小项目[前台搭建+后台编写](非常详细)相关推荐

  1. springboot+vue练手级项目,真实的在线博客系统

    文章目录 spring boot 练手实战项目说明 基础知识 面试准备 1. 工程搭建 1.1 新建maven工程 1.1.2遇到的bug 1.2 配置 1.3 启动类 2. 首页-文章列表 2.1 ...

  2. 一个springboot的练手小项目

    文章目录 前言 一.项目需求分析 二.具体实现 三. 技术支持 4.程序功能图 前言   这几天完成了一个基于springboot.mybatis.thymeleaf的ems小项目,主要实现后端的登陆 ...

  3. vue练手小项目--眼镜在线试戴

    最近看到了一个眼镜在线试戴小项目使用纯js手写的,本人刚学习vue.js没多久,便试试用vue做做看了,还没完善. 其中包括初始图片加载,使用keywords查找,父子组件之间传递信息,子组件之间传递 ...

  4. ssm练手小项目_20 个 JavaScript+Html+CSS 练手的小项目

    前言: 最近在 GitHub 上发现了一个 vanillawebprojects[1] 开源仓库,里面收集了 20 个 JavaScript+Html+CSS的练手项目,没有使用任何框架,可以让你从基 ...

  5. 台式小风扇(HTML+CSS+JS练手小项目)

    台式小风扇(HTML+CSS+JS练手小项目) 功能介绍 外观展示 HTML代码 CSS代码 JS代码 总结 功能介绍 前段时间看到这样的风扇特效,感觉还挺好玩,就自己也写一个练练手. 风扇有四个档位 ...

  6. 爬虫练手小项目:豆瓣高分图书TOP100

    爬虫练手小项目:豆瓣高分图书TOP100 import requests import re from requests.exceptions import RequestException impo ...

  7. 数据结构练手小项目(AVL树、哈希表、循环链表、MySQL数据库)

    文章目录 前言 正文(无删减) 我的想法(删减修改版) 数据导入与数据存储 功能实现 数据结构 用户结构 SIM卡结构 AVL树数据结构 哈希表结构 数据表 用户表 SIM卡表 时间安排 前言 本月主 ...

  8. html+css+js之20个练手小项目(一)

    html+css+js之20个练手小项目(一)--Hangman 前言 一.HTML 二.CSS 三.JS 前言 前端新手练习,记录不迷失. 主要练习html和CSS布局以及JS. 来源github, ...

  9. 练手小项目,爬取3DM图片

    博客原文:https://weweweha.com 1. 概述 ​ 爬取3DM指定网页的游戏壁纸,并且通过多线程来加速爬取图片的速度. 2.使用库 ​ request库用来1解析指定网页,re库用来搜 ...

最新文章

  1. 关于JVM,你需要掌握这些!!
  2. 微软、海思、任天堂等50多家知名公司源代码泄露,人人均可公开访问
  3. 李开复:非常看好移动互联网的未来 --记CSDNCTO俱乐部李开复交流会
  4. 谷歌大脑Wasserstein自编码器:新一代生成模型算法
  5. Visual Studio Code 保存代码时报Applying code action Organize Imports
  6. 在Angular的index.html里插入script,拦截所有动态创建标签页的场景
  7. 模板:回文自动机(PAM)
  8. 信息学奥赛一本通(1194:移动路线)
  9. 程序员面试题之从字节截断谈起
  10. html一行显示四个图片,css一行显示之:实现多个图片一行显示的方法
  11. distcp用于集群中数据传输解读
  12. shell中 if else以及大于、小于、等于逻辑表达式介绍
  13. extern小结(转)
  14. Oracle 数据库认证考试
  15. MATLAB算法实战应用案例精讲-【图像处理】缺陷检测(补充篇)(附matlab实现代码)
  16. WEB前端 VS 后端,学哪个就业前景更好
  17. xcoj1226-捕鼠
  18. STM32F103定时器详解
  19. 对Shortcut使用的理解
  20. Python利用PIL将png图像转jpg图像

热门文章

  1. 依维莫司/Everolimus——一款非常厉害的mTOR抑制剂
  2. Redis 管理工具:Another Redis DeskTop Manager
  3. 机器学习 自学书本推荐(中文书) ---入门与基础
  4. 《MATLAB神经网络案例分析》学习(一)——BP神经网络基本理论
  5. MySQL数据库还原
  6. 滴滴打车每天自动领券
  7. 小说app搭建—小说app运营选择付费模式还是免费模式?
  8. linux 删除行 脚本呢,Linux SED脚本找到匹配模式的第一行并删除它
  9. 6-2 求子串*分数 20
  10. java语言sql接口_java.sql包中的类和接口及其使用