【Spring Boot + Vue 前后端分离 - 图书管理Demo】——【一】环境准备
GitHub源码地址
<template>
<div id="app">
<el-container style="height: 500px; 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-group>
<template slot="title">分组一</template>
<el-menu-item index="1-1">选项1</el-menu-item>
<el-menu-item index="1-2">选项2</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</div>
</template>
<style>
.el-header {
background-color: #B3C0D1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
<script>
export default {
data() {
const item = {
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
};
return {
tableData: Array(20).fill(item)
}
}
};
</script>
<template>
<router-view></router-view>
</template>
<script>
export default {
name: "Index"
}
</script>
<style scoped>
</style>
App.Vue
实现整体框架(侧边栏),每次页面跳转的时候这个框架的样式是不变的.App.vue
中的router-view
套index
,index
的router-view
套其他界面,这样才能保证侧边栏一直存在。此后的每次页面跳转都只更改index的router-view
中的内容在views中新建
BookManager.vue
(查询图书页面)、BookUpdate.vue
(图书信息修改页面)、AddBook.vue
(添加图书页面)`index.js
)import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from "../views/Index";
import BookManager from "../views/BookManager";
import AddBook from "../views/AddBook";
import BookUpdate from "../views/BookUpdate";
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: '图书管理',
show: true,
component: Index,
/* 首页地址直接跳转到/BookManager */
redirect: "/BookManager",
children: [
{
path: '/BookManager',
name: '查询图书',
component: BookManager
},
{
path: '/AddBook',
name: '添加图书',
component: AddBook
},
]
},
{
path: '/BookUpdate',
// name: '修改图书',
component: BookUpdate,
show: false
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
App.vue
, 实现menu 与 router的绑定(页面跳转)<template>
<div id="app">
<el-container style="height: 700px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu router :default-openeds="['0', '1']">
<!--循环遍历routes中的对象,有几个对象就有几个item-->
<!--添加index属性标识不同的submenu-->
<!--默认的index是字符串类型,而不是整数类型,多以需要转化为字符类型-->
<!--v-if=item.show 表示根据路由配置中的show:ture/false决定是否显示该菜单 -->
<el-submenu v-for="(item,index) in $router.options.routes" :index="index+''" v-if="item.show">
<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>
application.yml
spring:
datasource:
# 连接数据库library
url: jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
# 打印sql
show-sql: true
properties:
hibernate:
# 格式化sql
format_sql: true
server:
port: 8181
vue已经默认占用了8080端口,修改springboot的端口为8181防止冲突
在spring boot项目中新建实体类 entity/Book.java
/**
* 使用Entity将该类与数据库表绑定,根据类名和表名经行绑定(默认类名小写就是表名);
*
* Data是lombok插件的方法,自动帮我们生成各种setter和getter方法
*/
@Entity
@Data
public class Book {
// 和数据库表中的id绑定,表示这是主键
@Id
// 设置主键Id自增
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String author;
private String publish;
private Integer pages;
private Integer price;
}
新建 repository/ BookRepository.java
Spring Data 提供了统一的 repository 接口,实现了数据库的相关基本操作
继承 JpaRepository
来完成对数据库的操作
public interface BookRepository extends JpaRepository<Book, Integer> {
}
controller/ BookHandler.java
/**
* RestController = @Controller + @ResponseBody
* Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目跑起来的过程中,这个类就被实例化。
* 当然也有语义化的作用,即代表该类是充当Controller的作用
* ResponseBody 该类中所有的API接口返回的数据都会以Json字符串的形式返回给客户端
*
* RequestMapping 提供路由信息,负责URL到Controller中的具体函数的映射。服务器发送 /book 请求时执行该类
*
* 通过Handler数据才能调给前端使用
*/
@RestController
@RequestMapping("/book")
public class BookHandler {
@Autowired
private BookRepository bookRepository;
/**
* 分页显示
*
* GetMapping是一个组合注解 是@RequestMapping(method = RequestMethod.GET)的缩写
* page/size 表示从第几页开始,每页几个
*/
@GetMapping("/findAll/{page}/{size}")
public Page<Book> findAll(@PathVariable("page") Integer page, @PathVariable("size") Integer size){
PageRequest request = PageRequest.of(page,size);
return bookRepository.findAll(request);
}
}
运行该项目
http://localhost:8181/book/findAll/0/6
表示从第0页开始,显示6个数据
通过 axios 组件请求 Ajax
axios 是目前应用最为广泛的 Ajax 封装库
<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="deleteBook(scope.row)" type="text" size="small">删除</el-button>
<!--delete是关键字,不能自定义-->
</template>
</el-table-column>
<el-pagination
background
layout="prev, pager, next"
:page-size="pageSize"
:total="total"
@current-change="page">
<!--点击页数跳转-->
</el-pagination>
<script>
export default {
methods: {
page(currentPage){
const _this = this
<!--通过axios发送ajax 请求后端数据-->
axios.get('http://localhost:8181/book/findAll/'+(currentPage-1)+'/10').then(function(resp){
console.log(resp)
_this.tableData = resp.data.content
_this.pageSize = resp.data.size
_this.total = resp.data.totalElements
})
}
},
data() {
return {
pageSize: '',
total: '',
tableData: [
]
}
},
created(){
const _this = this
axios.get('http://localhost:8181/book/findAll/0/10').then(function(resp){
console.log(resp)
_this.tableData = resp.data.content
_this.pageSize = resp.data.size
_this.total = resp.data.totalElements
})
}
}
</script>
访问 http://localhost:8080/BookManager
(刷新浏览器)
查看控制台,出现跨域问题,前端无法获取后端数据
在后端解决跨域问题:
新建配置类 config/ CrosConfig.java
/**
* 解决跨域问题
*/
@Configuration
public class CrosConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}