技术栈: 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版本[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
6.输入y (路由信息为history,)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
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
选项1 选项1-1 选项2 选项2-1 3
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
1
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文件
4.0.0 org.springframework.boot spring-boot-starter-parent2.6.4 com.example lesson080.0.1-SNAPSHOT lesson08 Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-weborg.mybatis.spring.boot mybatis-spring-boot-starter2.2.2 mysql mysql-connector-javaruntime org.springframework.boot spring-boot-starter-testtest com.alibaba druid-spring-boot-starter1.1.21 org.springframework.boot spring-boot-starter-actuatororg.springframework.boot spring-boot-maven-plugin
检验是否配置成功
如果报错则将vue文件夹的node_modules文件夹删除 再vue项目执行命令npm install
新的目录结构
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
@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和请求参数
//如果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-all5.7.3
在UserController中新增findpage方法
// 定义get接口
@GetMapping
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和请求参数
//如果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/i事件绑定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,同时写入路由
我是book
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存储了用户信息
有了sessionStorage对象,我们可以页面右上角的用户实时更新为用户名
在Header.vue添加数据user:JSON.parse(sessionStorage.getItem(“user”))(这里是将JSON字符串转化为JSON对象),
后台管理
管理员:{{ user.username }}
个人信息
退出系统
我们新建Person.vue
运行项目


 springboot+vue练手小项目:[前台搭建+后台编写](非常详细)](http://www.mshxw.com/aiimages/31/763401.png)
