springboot+vue前后端分离项目整合
1 创建新项目
打开视图操作
vue ui
一直点击往下,直到创建成功项目为止
启动项目:
npm run serve
2 构建Book表格Demo
2.1 创建vue文件
首先在views下创建vue文件,并创建表格,填写好假数据
注意:template下只能有一个div包裹,否则元素会溢出,显示不了且报错
views/Book.vue
<template>
<div>
<tr>
<td>编号</td>
<td>图书名称</td>
<td>作者</td>
<td>出版社</td>
<td>页数</td>
<td>价格</td>
</tr>
<tr v-for="book in books">
<td>{{book.id}}</td>
<td>{{book.name}}</td>
<td>{{book.author}}</td>
<td>{{book.publish}}</td>
<td>{{book.pages}}</td>
<td>{{book.price}}</td>
</tr>
</div>
</template>
<script>
export default {
name: "Book",
data(){
return{
msg:'Hello Vue',
books:[
{
id:1,
name:"追风筝的人",
author:'战三',
publish:'问心出版社',
pages:123,
price:44.3,
},
{
id:2,
name:"解忧杂货铺",
author:'李四',
publish:'问心出版社',
pages:113,
price:33.3,
},
{
id:3,
name:"暖暖心绘本",
author:'王五',
publish:'问心出版社',
pages:133,
price:23.1,
},
]
}
}
}
</script>
2.2 注册router
在router下引入Book,并注册
routes
下的数组的意思是当走/book
的请求的时候,会返回Book组件
import Book from "../views/Book";
const routes = [
{
path: '/book',
component: Book
},
]
2.3 前端测试
成功显示假数据,此时可将前端搁置一遍不管,等后端传输数据!
2.4 后端查找数据
2.4.1 配置yaml
application.yaml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?&useSSL=false&serverTimezone=UTC&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
# 整合mybatis
mybatis:
type-aliases-package: com.cycyong.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
server:
port: 8181
2.4.2 pojo
Book.java
package com.cycyong.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Integer id;// id
private String name; // 书名
private String author; //作者
private String publish; // 出版社
private int pages; // 页数
private float price; // 价格
}
2.4.3 mapper
@Mapper
表示这是一个Mybatis的Mapper类
BookMapper.java
package com.cycyong.Mapper;
import com.cycyong.pojo.Book;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
// 表示这是一个Mybatis的Mapper类
@Mapper
@Repository
public interface BookMapper {
public List<Book> getBookList();
}
BookMapper.xml
<?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.cycyong.Mapper.BookMapper">
<select id="getBookList" resultType="Book">
select * from library.book
</select>
</mapper>
2.4.4 service
BookService.java
package com.cycyong.service;
import com.cycyong.pojo.Book;
import org.springframework.stereotype.Service;
import java.util.List;
public interface BookService {
public List<Book> getBookList();
}
BookServiceImpl.java
package com.cycyong.service;
import com.cycyong.Mapper.BookMapper;
import com.cycyong.pojo.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
BookMapper bookMapper;
@Override
public List<Book> getBookList() {
return bookMapper.getBookList();
}
}
2.4.5 controller
BookController.java
package com.cycyong.controller;
import com.cycyong.pojo.Book;
import com.cycyong.service.BookServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired
BookServiceImpl bookService;
@GetMapping("/getBookList")
public List<Book>getBookList(){
return bookService.getBookList();
}
}
2.5 跨域传输数据
由于vue前端的端口是8080,而springBoot后端的端口为8181,两者传输数据会报传输数据问题,因此得配置参数文件CrosConfig
下面这段代码复制即可用!
config/CrosConfig.java
package com.cycyong.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CrosConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(false)
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowedOrigins("*");
}
};
}
}
2.6 anxios前后端交互
2.6.1 安装axios
cnpm install axios --save
2.6.2 引入axios
在需要用到的界面引入axios
const axios = require('axios');
2.6.3 编写请求
created()
是页面刚刚初始化的时候的函数,注意不要放错在methods方法中
即要做的就是在刚刚初始化时就把books数据加到table表格中!
注意:
- 设置一个_this,把vue的this拿过来即可
- 这里使用的this是指回调的this,不是上面的vue对象的
// 页面加载就初始化
created() {
//这里设置一个_this,把vue的this拿过来即可
const _this = this
axios.get('http://localhost:8181/book/getBookList').then(function (response) {
// 这里使用的this是指回调的this,不是上面的vue对象
_this.books = response.data
})
}
成功实现跨域请求数据!
3 简单使用Element UI
3.1 认识el-table
- el-container:构建整个页面框架
- el-aside:构建左侧菜单
- el-menu:左侧菜单内容
- default-openeds:默认展开菜单,通过菜单index值关联
- default-active:默认选中的菜单,通过菜单的index值关联
- el-submenu:可展开的菜单
- index:菜单下标,文本类型,不能是数值类型
- template:el-submenu的带单名,可设置图标i
- i:用class属性设置,例如el-icon-message
- el-menu-item:菜单的子节点,不可再展开
- index:菜单下标,文本类型,不能是数值类型
3.2 构建左侧菜单
构建目标:
- 导航1
- 页面1
- 页面2
- 导航2
- 页面3
- 页面4
<template>
<div>
<el-container style="height: 500px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu router :default-openeds="['0','1']">
<el-submenu v-for="(item,index) in this.$router.options.routes" :index="index+''">
<template slot="title"><i class="el-icon-setting"></i>{{item.name}}</template>
<el-menu-item v-for="(item2,index2) in item.children" :index="item2.path" :class="$route.path===item2.path?'is-active':''">{{item2.name}}</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</div>
</template>
代码讲解:
default-openeds="['0','1']"
:默认展开1,2两个菜单router
:menu和菜单绑定的条件- 在el-submenu配置
index
是为了展开,否则点击两个菜单项同时展示 - 在el-menu-item总配置
:index
就是它的跳转路径 - 在el-menu-item总配置
:class
是为了选中时发光是否正确,与地址栏的route.path比较即可
3.3 menu和router绑定
<el-menu>
标签添加router属性- 在页面中添加
<router-view>
标签,它是一个容器,动态渲染你选择的router <el-menu-item>
标签的index值就是要跳转的router
3.4 表单数据校验
定义rules对象,在rules对象中设置各个选项的校验规则
rules: {
name: [
{ required: true, message: '请输入活动名称', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
]
}
- require:是否为必填项
- message:提示信息
- trigger:blur 失去焦点触发事件
4 完善Book的CRUD
4.1 增加图书
4.1.1 前端逻辑
template
<template>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="书名" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="作者" prop="author">
<el-input v-model="ruleForm.author"></el-input>
</el-form-item>
<el-form-item label="出版社" prop="publish">
<el-input v-model="ruleForm.publish"></el-input>
</el-form-item>
<el-form-item label="页数" prop="pages">
<el-input v-model.number="ruleForm.pages" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model.number="ruleForm.price" type="number"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">立即添加</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</template>
代码解析:
:model="ruleForm"
是vue的双向绑定,即与data中return数据绑定rules="rules"
绑定检验规则prop
绑定具体的检验规则
script
<script>
const axios = require('axios')
export default {
data() {
return {
// 绑定数据
ruleForm: {
name: '',
author: '',
publish: '',
pages: null,
price: null,
},
// 绑定校验规则
rules: {
name: [
{ required: true, message: '图书名不能为空', trigger: 'blur' },
],
author: [
{ required: true, message: '作者不能为空', trigger: 'blur' },
],
publish: [
{ required: true, message: '出版社不能为空', trigger: 'blur' },
],
pages: [
{ required: true, message: '页数不能为空', trigger: 'blur' },
{ type:'number',message: '请输入数字'}
],
price: [
{ required: true, message: '价格不能为空', trigger: 'blur' },
{ type:'float',message: '请输入浮点数'},
],
},
};
},
methods: {
submitForm(formName) {
const _this = this
this.$refs[formName].validate((valid) => {
if (valid) {
axios.post("http://localhost:8181/book/addBook",this.ruleForm).then(function (response) {
if (response.data==='success'){
_this.$alert('添加成功',{
confirmButtonText:'确定',
callback: action => {
_this.$router.push('/BookManager')
}
})
}
})
} else {
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
代码讲解:
ruleForm
代表与上方进行数据绑定rules
检验规则methods
一些函数
axios提交请求
submitForm(formName) {
const _this = this
this.$refs[formName].validate((valid) => {
if (valid) {
axios.post("http://localhost:8181/book/addBook",this.ruleForm).then(function (response) {
if (response.data==='success'){
_this.$alert('添加成功',{
confirmButtonText:'确定',
callback: action => {
_this.$router.push('/BookManager')
}
})
}
})
} else {
return false;
}
});
},
代码讲解:
- 这是一个提交事件
- 注意添加成功回调跳转到BookManager页面,否则不刷新
_this.$router.push('/BookManager')
这个是跳转
4.1.2 后端逻辑
就讲一点就是将前端数据给到后端需要添加@RequesstBody
@RequestBody
:把JSON对象转换为Java对象
@PostMapping("/addBook")
// 通过@RequestBody 把JSON对象转换为Java对象
public String addBook(@RequestBody Book book){
System.out.println(book);
int flag = bookService.addBook(book);
if(flag>0){
return "success";
}
else{
return "error";
}
}
4.2 分页
4.2.1 前端模块
template
<template>
<div>
<el-table
:data="tableData"
border
style="width: 60%">
<el-table-column
fixed
prop="id"
label="编号"
width="100">
</el-table-column>
<el-table-column
prop="name"
label="书名"
width="120">
</el-table-column>
<el-table-column
prop="author"
label="作者"
width="120">
</el-table-column>
<el-table-column
prop="publish"
label="出版社"
width="120">
</el-table-column>
<el-table-column
prop="pages"
label="页数"
width="50">
</el-table-column>
<el-table-column
prop="price"
label="价格"
width="100">
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button @click="edit(scope.row)" type="text" size="small">修改</el-button>
<el-button @click="dele(scope.row)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
background
layout="prev, pager, next"
:page-size=pageSize
:total=total
@current-change="page"
>
</el-pagination>
</div>
</template>
代码讲解:
prop
属性可以作为传参数的桥梁(双向绑定)@click="edit(scope.row)
点击事件,并将当行数据作为参数传递el-pagination
为分页控件,需要传递两个参数:pageSize和totalSize@current-change="page"
为点击页数时的点击切换页码函数
script
data() {
return {
total:null,
pageSize:null,
tableData: null,
}
}
page(currentPage) {
const _this = this
axios.get("http://localhost:8181/book/getBookList/"+currentPage+"/5").then(function (response) {
_this.tableData = response.data.books
_this.total = response.data.count
_this.pageSize = response.data.pageSize
})
}
},
created() {
const _this = this
axios.get("http://localhost:8181/book/getBookList/1/5").then(function (response) {
_this.tableData = response.data.books
_this.total = response.data.count
_this.pageSize = response.data.pageSize
})
},
currentPage
获得当前选的的页码,5是pageSize
- 后端需给出三个参数:
tableData
、count
、pageSize
,接着赋给分页控件即可
4.2.2 后端模块
4.2.2.1 mapper
public List<Book> getBookList(Map<String,Object> data);
<!--结果集映射-->
<resultMap id="BookMap" type="com.cycyong.pojo.Book">
<!--column为数据库字段,property为实体类的属性-->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="author" property="author"/>
<result column="publish" property="publish"/>
<result column="pages" property="pages"/>
<result column="price" property="price"/>
</resultMap>
<select id="getBookList" parameterType="map" resultMap="BookMap">
select * from library.book limit #{currPage},#{pageSize}
</select>
4.2.2.2 service
public List<Book> getBookList(int currPage, int pageSize);
@Override
public List<Book> getBookList(int currPage, int pageSize) {
Map <String,Object> data = new HashMap<String,Object>();
/*
1 3 | 0 1 2
2 3 | 3 4 5
3 3 | 6 7 8
*/
data.put("currPage", (currPage - 1) * pageSize);
data.put("pageSize", pageSize);
return bookMapper.getBookList(data);
}
4.2.2.3 controller
@GetMapping("/getBookList/{page}/{size}")
public Map<String, Object>getBookList(@PathVariable("page") int page,@PathVariable("size") int size){
Map<String, Object> map = new HashMap<>();
map.put("books",bookService.getBookList(page,size));
map.put("count",bookService.getBookCount());
map.put("pageSize",size);
return map;
}
4.3 删除图书
4.3.1 前端模块
<template slot-scope="scope">
<el-button @click="edit(scope.row)" type="text" size="small">修改</el-button>
<el-button @click="dele(scope.row)" type="text" size="small">删除</el-button>
</template>
dele(row){
const _this = this
this.$confirm('此操作将永久删除该条记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
axios.delete("http://localhost:8181/book/deleteBook/"+row.id).then(function (response) {
if(response.data==='success'){
_this.$message({
type: 'success',
message: '删除成功!',
});
window.location.reload()
}
})
}).catch(() => {
_this.$message({
type: 'info',
message: '已取消删除'
});
});
},
代码讲解:
window.location.reload()
刷新界面
4.3.2 后端模块
@DeleteMapping:删除用DeleteMapping
@DeleteMapping("/deleteBook/{id}")
public String deleteBook(@PathVariable ("id") int id){
int flag = bookService.deleteBook(id);
if(flag>0){
return "success";
}else{
return "error";
}
}
4.4 修改图书
4.4.1前端模块
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button @click="edit(scope.row)" type="text" size="small">修改</el-button>
<el-button @click="dele(scope.row)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
edit(row) {
//row得到当前行数据
// console.log(row);
this.$router.push({
path:"/updateBook",
query:{
id:row.id
}
})
},
4.4.2后端模块
@PutMapping:修改用putMapping
//更新信息用PutMapping
@PutMapping("/updateBook")
public String updateBook(@RequestBody Book book){
int flag = bookService.updateBook(book);
if(flag>0){
return "success";
}else{
return "error";
}
}