利用SpringBoot和Vue实现前后端分离(附源码)
引言:
本文主要分享了SpringBoot和Vue整合实现前后端分离,实现了简单的增删查改;包括:项目的搭建、后端的实现、前台的实现;(附源码)
1. 相关知识
本文使用SpringBoot和Vue技术实现前后端的分离;
2. 实现思路
前台利用Vue编写页面,使用ElementUI,后台基于SpringBoot;当访问http://localhost:8080/#/时到主页面,点击登录进入登录界面,输入admin跳到main界面,首先展示欢迎页面,之后点击学生信息加载全查页面,完成相应的增删改;
后台搭建SpringBoot的环境,编写业务代码,完成增删改查操作(端口号8088)
3. 后台实现
搭建项目
3.1 创建数据库
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50) NOT NULL,
sex VARCHAR(10) DEFAULT 'man',
age VARCHAR(5)
) CHARSET = utf8;
3.2 修改application.properties
# 应用服务 WEB 访问端口
server.port=8088
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db0711?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:mapping/*Mapper.xml
mybatis.type-aliases-package=com.sx.kak.po
3.3 编写实体类
在po包下编写实体类
package com.sx.kak.po;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Created by Kak on 2020/9/4.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String name;
private String sex;
private String age;
}
3.4 编写StudentMapper.java
在mapper中编写StudentMapper
package com.sx.kak.mapper;
import com.sx.kak.po.Student;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* Created by Kak on 2020/9/4.
*/
@Mapper
public interface StudentMapper {
public List<Student> findAllStudent();
public void updateStudent (Student student);
public void addStudent(Student student);
public void deleteStudent(int id);
}
3.5 编写StudentMapper.xml
在resources/mapping下编写
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sx.kak.mapper.StudentMapper">
<select id="findAllStudent" resultType="com.sx.kak.po.Student">
select * from student
</select>
<update id="updateStudent" parameterType="com.sx.kak.po.Student">
update student set name=#{name},sex=#{sex},age=#{age} where id=#{id}
</update>
<insert id="addStudent" parameterType="com.sx.kak.po.Student" useGeneratedKeys="true" keyProperty="id">
insert into student (id,name,sex,age) VALUES (#{id},#{name},#{sex},#{age})
</insert>
<delete id="deleteStudent" parameterType="int">
DELETE FROM student where id=#{id}
</delete>
</mapper>
3.6 编写StudentService
在service下
package com.sx.kak.service;
import com.sx.kak.po.Student;
import java.util.List;
/**
* Created by Kak on 2020/9/4.
*/
public interface StudentService {
public List<Student> findAllService();
public void updateStudentService(Student student);
public Student addStudentService(Student student);
public void deleteStudentService(int id);
}
3.7 编写StudentServiceImpl
在service/impl下
package com.sx.kak.service.impl;
import com.sx.kak.mapper.StudentMapper;
import com.sx.kak.po.Student;
import com.sx.kak.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by Kak on 2020/9/4.
*/
@Service
@Slf4j
public class StudentServiceImpl implements StudentService{
@Autowired(required = false)
private StudentMapper studentMapper;
@Override
public List<Student> findAllService() {
try {
List<Student> allStudent = studentMapper.findAllStudent();
return allStudent;
}catch (Exception ex) {
log.info(ex.getMessage());
}
return null;
}
@Override
public void updateStudentService(Student student) {
try {
studentMapper.updateStudent(student);
}catch (Exception ex){
log.info(ex.getMessage());
}
}
@Override
public Student addStudentService(Student student) {
try{
studentMapper.addStudent(student);
return student;
}catch (Exception ex){
log.info(ex.getMessage());
}
return null;
}
@Override
public void deleteStudentService(int id) {
try{
studentMapper.deleteStudent(id);
}catch (Exception ex){
log.info(ex.getMessage());
}
}
}
3.8 编写ActionResult
用于响应状态
package com.sx.kak.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 封装统一的前端响应对象
* Created by Kak on 2020/9/4.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ActionResult {
private int statusCode;//响应状态码
private String msg;//响应的短消息
private Object data;//响应携带的数据
}
3.9 编写StudentController
业务控制层
- 加入@CrossOrigin :标识接受跨域处理
package com.sx.kak.controller;
import com.sx.kak.po.Student;
import com.sx.kak.service.StudentService;
import com.sx.kak.vo.ActionResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Created by Kak on 2020/9/4.
*/
@RestController
@RequestMapping("/api")
public class StudentController {
@Autowired(required = false)
private StudentService studentService;
@CrossOrigin //标识接受跨域处理
@RequestMapping(value="/studentList" ,method= RequestMethod.GET)
public ActionResult findAllStu(){
ActionResult actionResult = new ActionResult();
List<Student> allService = studentService.findAllService();
actionResult.setStatusCode(200);
actionResult.setData(allService);
return actionResult;
}
//修改学生信息
@CrossOrigin
@RequestMapping(value = "/updateStudent",method = RequestMethod.PUT)
public ActionResult updateStu(@RequestBody Student student){
studentService.updateStudentService(student);
ActionResult actionResult = new ActionResult();
actionResult.setStatusCode(200);
actionResult.setMsg("Update Success!!!");
return actionResult;
}
//添加学生信息
@CrossOrigin
@RequestMapping(value="/addStudent" ,method= RequestMethod.POST)
public ActionResult addStu(@RequestBody Student student){
Student student1 = studentService.addStudentService(student);
ActionResult result = new ActionResult();
result.setStatusCode(200);
result.setMsg("add Success!!!");
result.setData(student1);
return result;
}
//删除学生信息
@CrossOrigin
@RequestMapping(value = "/deleteStudent",method = RequestMethod.GET)
public ActionResult deleteStu(@RequestParam("id") int id){
studentService.deleteStudentService(id);
ActionResult actionResult = new ActionResult();
actionResult.setStatusCode(200);
actionResult.setMsg("Update Success!!!");
return actionResult;
}
}
4. 前台实现
环境搭建:
Vue基础:https://blog.csdn.net/weixin_42601136/article/details/108297010
添加axios
cnpm install axios --save
4.1 main.js
引用ElementUI的组件,引用axios并设置基础的url
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
//引用axios,设置基础的url
var axios = require('axios')
axios.defaults.baseURL = 'http://localhost:8088/api'
//绑定到全局
Vue.prototype.$axios = axios
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: {
App },
template: '<App/>'
})
4.2 index.js
添加路由
import Vue from 'vue'
import Router from 'vue-router'
import IndexView from '@/view/index'
import MainView from '@/view/admin/main'
import LoginView from '@/view/login'
import WelcomeView from '@/view/admin/welcome'
import StudentView from '@/view/admin/student'
Vue.use(Router)
export default new Router({
routes: [
{
//前台首页
path: '/',
name: 'IndexView',
component: IndexView
},
{
//后台主页面
path:"/main",
component:MainView,
children:[
{
path:"/",component:WelcomeView},
{
path:"/showStudent",component:StudentView}
]
},
{
//登录页
path:"/login",
component:LoginView,
}
]
})
4.3 index.vue
访问的默认页面
<template>
<div>
<el-container>
<el-header>
<div class="sousuo">
<i class="el-icon-edit"></i>
<i class="el-icon-share"></i>
<i class="el-icon-delete"></i>
<el-button type="primary" icon="el-icon-search">搜索</el-button>
</div>
<div class="Lr">
<router-link to="/login">登录</router-link>
</div>
<div class="fonts">
<b>学生管理系统</b>
</div>
</el-header>
<el-main>
<div class="block">
<el-carousel height="545px">
<el-carousel-item v-for="item in items" :key="item">
<img :src="item" class="imgSize"/>
</el-carousel-item>
</el-carousel>
</div>
</el-main>
<el-footer>
<div id="footer">
<p>©kak.com 京ICP证XXXXX号,本网站所列数据,除特殊说明,所有数据均出自我司实验室测试</p>
</div>
</el-footer>
</el-container>
</div>
</template>
<script>
export default {
name:'IndexView',
data(){
return{
items:['https://img-blog.csdnimg.cn/20200828150734825.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center',
'https://img-blog.csdnimg.cn/20200828150734958.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center',
'https://img-blog.csdnimg.cn/20200828150810950.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center',
'https://img-blog.csdnimg.cn/2020082815084381.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center'
]
}
}
}
</script>
<style>
.imgSize{
height: 545px;
width: 1500;
}
.sousuo{
float: right;
}
.Lr{
float: left;
}
.fonts{
float: left;
margin-left: 500px;
font-family: fantasy;
font-size: 30px;
font-style: normal;
font-weight: lighter;
letter-spacing:20px;
color: brown;
}
.el-header,.el-footer {
background-color: #B3C0D1;
color: #333;
text-align: center;
line-height: 60px;
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
line-height: 545px;
}
body > .el-container {
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
line-height: 320px;
}
/* 轮播图 */
.el-carousel__item h3 {
color: #475669;
font-size: 14px;
opacity: 0.75;
line-height: 150px;
margin: 0;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n+1) {
background-color: #d3dce6;
}
</style>
4.4 login.vue
点击登录触发的页面
<template>
<!-- 视图显示部分-->
<div>
<h2>登录页面</h2>
<div id="inputStyle" class="inputClass">
<el-form :model="ruleForm2" status-icon :rules="rules2" ref="ruleForm2" label-width="100px" class="demo-ruleForm">
<el-form-item label="用户名" prop="uname">
<el-input type="text" v-model="ruleForm2.uname" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm2.pass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-input v-model.number="ruleForm2.code"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm2')">提交</el-button>
<el-button @click="resetForm('ruleForm2')">重置</el-button>
<el-button type="text"><router-link to="/">回到首页</router-link></el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
// 编写js行为
export default {
name:'LoginView',
data(){
return {
ruleForm2: {
uname: '',
pass: '',
code: ''
}
}
},
methods: {
submitForm(formName) {
this.$message(this.ruleForm2.uname+this.ruleForm2.code);
//
if(this.ruleForm2.uname=='admin'){
//登录成功
// 导航到后台主页
this.$router.push("/main");//使用编程式动态路由
}else{
this.$router.push("/");
}
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
<style scoped>
.inputClass{
padding: 20px;
width: 300px;
height: 250px;
margin: 0 auto;
}
.Codename{
margin-right: 227px;
padding: 10px;
font-family: PingFang SC
}
.name{
margin-left: -237px;
padding: 10px;
font-style: inherit
}
</style>
4.5 main.vue
登录成功后的页面,与欢迎页面一起展示
<template>
<el-container style="height: 600px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu :default-openeds="['1', '3']">
<el-submenu index="1">
<template slot="title"><i class="el-icon-message"></i>导航一</template>
<el-menu-item index="1-1"><router-link :to="{path:'showStudent'}">学生信息</router-link></el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
<el-menu-item index="1-3">选项3</el-menu-item>
<el-submenu index="1-4">
<template slot="title">选项4</template>
<el-menu-item index="1-4-1">选项4-1</el-menu-item>
</el-submenu>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="el-icon-menu"></i>导航二</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
<el-submenu index="2-4">
<template slot="title">选项4</template>
<el-menu-item index="2-4-1">选项4-1</el-menu-item>
</el-submenu>
</el-submenu>
<el-submenu index="3">
<template slot="title"><i class="el-icon-setting"></i>导航三</template>
<el-menu-item index="3-1">选项1</el-menu-item>
<el-menu-item index="3-2">选项2</el-menu-item>
<el-menu-item index="3-3">选项3</el-menu-item>
<el-submenu index="3-4">
<template slot="title">选项4</template>
<el-menu-item index="3-4-1">选项4-1</el-menu-item>
</el-submenu>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>kak</span>
</el-header>
<el-main>
<!-- 子路由视图,显示后台业务数据 -->
<router-view/>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
data() {
return {
}
}
}
</script>
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #D3DCE6;
text-align: center;
line-height: 200px;
}
body > .el-container {
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
line-height: 320px;
}
</style>
4.6 welcome.vue
欢迎页面,加载主页面时默认的页面
<template>
<div>
<el-container>
<el-main>
<div class="block">
<el-carousel height="500px">
<el-carousel-item v-for="item in items" :key="item">
<img :src="item" class="imgSize"/>
</el-carousel-item>
</el-carousel>
</div>
</el-main>
</el-container>
</div>
</template>
<script>
export default {
name:'FirstPage',
data(){
return{
items:['https://img-blog.csdnimg.cn/20200828150734825.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center',
'https://img-blog.csdnimg.cn/20200828150734958.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center',
'https://img-blog.csdnimg.cn/20200828150810950.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center',
'https://img-blog.csdnimg.cn/2020082815084381.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjYwMTEzNg==,size_16,color_FFFFFF,t_70#pic_center'
]
}
}
}
</script>
<style>
.imgSize{
height: 545px;
width: 1500;
}
/* 轮播图 */
.el-carousel__item h3 {
color: #475669;
font-size: 14px;
opacity: 0.75;
line-height: 150px;
margin: 0;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n+1) {
background-color: #d3dce6;
}
</style>
4.7 student.vue
与后台进行跨域访问,实现增删改查
<template>
<div>
<el-table :data="students" stripestyle="width: 100%">
<el-table-column prop="id" label="学生ID" width="180"></el-table-column>
<el-table-column prop="name" label="学生姓名" width="180"></el-table-column>
<el-table-column prop="sex" label="学生性别"></el-table-column>
<el-table-column prop="age" label="学生年龄"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">修改</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- Form -->
<el-dialog title="修改学生信息" :visible.sync="dialogFormVisible">
<el-form :model="student">
<el-form-item label="学生ID" :label-width="formLabelWidth">
<el-input v-model="student.id" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="学生姓名" :label-width="formLabelWidth">
<el-input v-model="student.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="学生性别" :label-width="formLabelWidth">
<el-input v-model="student.sex" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="学生年龄" :label-width="formLabelWidth">
<el-input v-model="student.age" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="updateStudent()">确 定</el-button>
</div>
</el-dialog>
<!-- Form -->
<el-button type="primary" @click="addView()">添加学生信息</el-button>
<el-dialog title="添加学生信息" :visible.sync="dialogFormVisible1">
<el-form :model="student1">
<el-form-item label="学生ID" :label-width="formLabelWidth">
<el-input v-model="student1.id" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="学生姓名" :label-width="formLabelWidth">
<el-input v-model="student1.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="学生性别" :label-width="formLabelWidth">
<el-input v-model="student1.sex" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="学生年龄" :label-width="formLabelWidth">
<el-input v-model="student1.age" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible1 = false">取 消</el-button>
<el-button type="primary" @click="addStudent()">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
dialogFormVisible: false,
dialogFormVisible1: false,
formLabelWidth: "120px",
student: {
id: 0,
name: "",
sex: "",
age: "",
},
student1: {
id: " ",
name: "",
sex: "",
age: "",
},
students: [],
search: "",
};
},
// 页面加载时触发
created() {
alert("跳转学生信息界面!!");
//替换students
//get请求,url是studentList
this.$axios
.get("studentList", {
})
//写成功
.then((response) => {
//打印到控制台
console.log(response);
//是否是200
if (response.data.statusCode == 200) {
//接受数据
this.students = response.data.data;
}
})
.catch((error) => {
alert(error);
});
},
methods: {
//修改
updateStudent: function () {
// 发出修改请求
this.$axios
.put("updateStudent", {
id: this.student.id,
name: this.student.name,
sex: this.student.sex,
age: this.student.age,
})
.then((response) => {
if (response.data.statusCode == 200) {
//更新前端页面
for (var i = 0; i < this.students.length; i++) {
if (this.students[i].id == this.student.id) {
this.students[i].id = this.student.id;
this.students[i].name = this.student.name;
this.students[i].sex = this.student.sex;
this.students[i].age = this.student.age;
}
}
alert("修改成功");
this.dialogFormVisible = false;
}
})
.catch((error) => {
this.$message(error);
});
},
addView: function () {
// 重置对象数据
this.student1.id = 0;
this.student1.name = "";
this.student1.sex = "";
this.student1.age = "";
// 显示对话框
this.dialogFormVisible1 = true;
},
handleEdit(index, row) {
// console.log(index,row);
// console.log(row.id);
// 给弹出框绑定的对象填值
this.student.id = row.id;
this.student.name = row.name;
this.student.sex = row.sex;
this.student.age = row.age;
// 显示对话框
this.dialogFormVisible = true;
},
//删除
handleDelete(index, row) {
this.$axios
.get("deleteStudent?id=" + row.id)
.then((response) => {
console.log(response);
if (response.data.statusCode == 200) {
for (var i = 0; i < this.students.length; i++) {
if (this.students[i].id == row.id) {
this.students.splice(i, 1);
alert("Delete Success!!!");
}
}
}
})
.catch((error) => {
alert(error);
});
},
//添加学生
addStudent() {
this.$axios
.post("addStudent", {
id: this.student1.id,
name: this.student1.name,
sex: this.student1.sex,
age: this.student1.age,
})
.then((response) => {
if (response.data.statusCode == 200) {
this.$message("add successful");
var stu = response.data.data;
this.students.push({
id: stu.id,
name: stu.name,
sex: stu.sex,
age: stu.age,
});
// this.students.push(this.student1);
this.dialogFormVisible1 = false;
}
})
.catch((error) => {
this.$message(error);
});
},
},
};
</script>