前后端分離
前端只需要獨立編寫客戶端代碼
后端只需獨立編寫服務端代碼提供數(shù)據(jù)接口
前端通過Ajax請求來訪問后端的數(shù)據(jù)接口蔬墩,將Model展示到View中即可
前端HTML — Ajax — RESTful 后端數(shù)據(jù)接口
1.創(chuàng)建vue工程
下載Node.js
$ npm install -g @vue/cli //全局安裝vue-cli
下載太慢的話
可以修改npm映射:$npm config set registry https://registry.npm.taobao.org/
改回來:npm config set registry https://registry.npmjs.org/
cmd中:
>vue ui //vue 3.0以上支持的圖形界面創(chuàng)建Vue
進入視圖創(chuàng)建界面 localhost:8000
選擇手動配置
勾選Router畏妖、Vuex拳话,去掉Linter/Formatter
$npm run serve //啟動vue工程
2.新建SpringBoot應用
組件選擇
- Lombok
- Spring web
- Spring Data JPA
- MySQL Driver
resources中application.properties文件改為application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jpa:
show-sql: true # 打印SQL
properties:
hibernate:
format_sql: true # 格式化SQL
server:
port: 8181
不設置端口默認也是8080和vue工程端口沖突
3.前端展示數(shù)據(jù)
可以先使用假數(shù)據(jù)完成頁面
創(chuàng)建Book.vue
<template>
<div>
<table>
<th>{{msg}}</th>
<tr>
<td>編號</td>
<td>書名</td>
<td>作者</td>
</tr>
<tr v-for="book in books" :key="book.id">
<td>{{book.id}}</td>
<td>{{book.name}}</td>
<td>{{book.author}}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name:"Book",
data(){
return{
msg:'圖書展示',
books:[
{id:1,name:'Java學習',author:'Java老師'},
{id:2,name:'Vue學習',author:'Vue老師'},
{id:3,name:'Spring學習',author:'Java老師'}
]
}
}
}
</script>
在路由(router>index.js)添加 Book.vue
const routes = [
{ path: '/',name: 'Home',component: Home},
{ path: '/about',name: 'About',component: () => import('../views/About.vue')},
{ path:'/book',name:'Book',component:()=>import('../views/Book.vue')}
]
4.繼續(xù)完成后端
綁定數(shù)據(jù)表信息,測試端口
4.1 在entity包福荸,創(chuàng)建Book類
package com.xin.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
@Data //Lombok
public class Book {
@Id //主鍵
@GeneratedValue(strategy = GenerationType.IDENTITY) //自增
private Integer id;
private String name;
private String author;
}
4.2 在repository包,創(chuàng)建BookRepository 類
JpaRepository<實體類類型,主鍵類型> 中 集成了許多方法 省的自己寫了
package com.xin.repository;
import com.xin.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book,Integer>{
}
在Test中雀费,測試repository接口
@SpringBootTest
class SpringbootApplicationTests {
@Autowired
private BookRepository bookRepository;
@Test
void findAll() {
System.out.println(bookRepository.findAll());
}
}
4.3 在controller包比肄,創(chuàng)建BookController 類
package com.xin.controller;
import com.xin.entity.Book;
import com.xin.repository.BookRepository;
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
private BookRepository bookRepository;
@GetMapping("/findAll")
public List<Book> findAll(){
return bookRepository.findAll();
}
}
測試controller接口
5.前后端對接
axios實現(xiàn)前端調(diào)用后端接口
vue中安裝axios
> vue add axios
前端調(diào)用,在book.vue中添加
created(){
const _this =this
axios.get('http://localhost:8181/book/findAll')
.then(function(res) {
_this.books=res.data
})
}
※跨域問題的解決
在config包另患,創(chuàng)建CrosConfig類
package com.xin.config;
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 implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET","HEAD","POST","PUT","DELETE","POTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
6.使用ElementUI
6.1 Vue集成ElementUI
ElementUI https://element.faas.ele.me/#/zh-CN/component/installation
> vue add element-ui
6.1.1 運行時出現(xiàn)了問題
不能解析sass-loader纽乱,需要引入
npm install sass-loader -D
npm install node-sass -D
6.1.2 但又出現(xiàn)了新的問題
經(jīng)查 node版本和 node-sass版本 不匹配
參考網(wǎng)址
可能用到的命令:
查看 node 版本命令:node -v
查看 node-sass 等版本信息:npm list
卸載 sass-loader 命令:npm uninstall --save sass-loader
卸載 ode-sass 命令:npm uninstall --save node-sass
安裝低版本 sass-loader 的命令:npm i -D sass-loader@10.x
安裝低版本 node-sass 的命令:npm i node-sass@4.14.1
運行 Vue 命令:npm run serve
6.1.3 或者一開始安裝elementui不要使用scss也可以解決
6.2 使用ElementUI布局
任選一個組件布局 | Element
改寫App.vue
主要標簽
el-container 構(gòu)建整個頁面框架 類似div
el-aside 構(gòu)建左側(cè)菜單
el-menu 構(gòu)建左側(cè)菜單內(nèi)容,常用屬性:
:default-openeds="['1']" 默認展開的菜單昆箕,通過菜單index值來關聯(lián)
:default-active="'1-1'" 默認選中的菜單鸦列,通過菜單index值來關聯(lián)
el-submenu 可展開的菜單欄,常用屬性:
index="1" 菜單的下標鹏倘,文本類型薯嗤,不能是數(shù)值類型
template 對應el-submenu 菜單名
i 設置菜單圖標,通過class屬性實現(xiàn)
"el-icon-message"
"el-icon-menu"
"el-icon-setting"
el-menu-item-group 設置分組纤泵,不常用
el-menu-item 設置菜單子節(jié)點骆姐,不可再展開,常用屬性:
index="1-1" 菜單的下標
6.3 通過Vue router動態(tài)構(gòu)建左側(cè)菜單
6.3.1 創(chuàng)建 Page1.vue ~ Page4.vue
6.3.2 改寫 App.vue頁面
<!-- 側(cè)邊欄 -->
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu>
<el-submenu v-for="(item, index) in $router.options.routes" :key="index" :index="index + ''">
<template slot="title"><i class="el-icon-menu"></i>{{ item.name }}</template>
<el-menu-item v-for="(item2, index2) in item.children" :key="index2" :index="index + '-' + index2">
{{ item2.name }}</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 展示 -->
<el-main>
<router-view></router-view>
</el-main>
6.3.3 改寫 router > index.js路由
const routes = [
{ path: '/',
name: '導航一',
component: App,
children:[
{path:'/page1',name:'頁面一',component:page1},
{path:'/page2',name:'頁面二',component:page2}
]
},
{ path: '/navigation',
name: '導航二',
component:App,
children:[
{path:'/page3',name:'頁面三',component:page3},
{path:'/page4',name:'頁面四',component:page4}
]
}
]
6.3.4 按上述寫法發(fā)現(xiàn)頁面會發(fā)生嵌套,遷移App.vue中代碼至新建的index.vue玻褪,并修改路由
把路由 中 component:App 改為 component:Index
6.3.5 menu與router的綁定
-
<el-menu>標簽添加router屬性
<el-menu router>
頁面中添加<router-view></router-view>肉渴,動態(tài)渲染選擇的router
-
<el-menu-item>標簽index的值就是要跳轉(zhuǎn)的router
<el-menu router> <el-submenu v-for="(item, index) in $router.options.routes" :key="index" :index="index + ''"> <template slot="title"><i class="el-icon-menu"></i>{{ item.name }}</template> <el-menu-item v-for="(item2, index2) in item.children" :key="index2" :index="item2.path"> {{ item2.name }}</el-menu-item> </el-submenu> </el-menu>
6.3.6 初始化時默認加載Page1
路由加一個redirect屬性
const routes = [
{ path: '/',
name: '導航一',
component: Index,
redirect:"/page1",
children:[
{path:'/page1',name:'頁面一',component:()=>import('../views/Page1.vue')},
{path:'/page2',name:'頁面二',component:()=>import('../views/Page2.vue')}
]
},
左側(cè)樣式菜單綁定 $route.path==item2.path?'is-active':' '
<el-menu router :default-openeds="['0','1']">
<el-submenu v-for="(item, index) in $router.options.routes"
:key="index" :index="index + ''">
<template slot="title"><i class="el-icon-menu"></i>{{ item.name }}</template>
<el-menu-item v-for="(item2, index2) in item.children"
:key="index2"
:index="item2.path"
:class="$route.path==item2.path?'is-active':''">
{{ item2.name }}</el-menu-item>
</el-submenu>
</el-menu>
6.4 實現(xiàn)分頁
使用 Vue ElementUI Table組件
修改后端findAll方法
package com.xin.controller;
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired
private BookRepository bookRepository;
@GetMapping("/findAll/{page}/{size}")//獲取前端傳來的數(shù)據(jù)
public Page<Book> findAll(@PathVariable("page") Integer page,@PathVariable("size") Integer size){
Pageable pageable= PageRequest.of(page-1,size);
return bookRepository.findAll(pageable);
}
}
修改page頁面
<template>
<div>
<el-table :data="book" border style="width: 100%">
<el-table-column fixed prop="id" label="編號" width="150"></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 fixed="right" label="操作" width="100">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
<el-button 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>
export default {
methods: {
handleClick(row) {
console.log(row);
},
page(currentPage){
const _this=this
axios.get('http://localhost:8181/book/findAll/'+currentPage+'/'+_this.pageSize)
.then(function(res){
_this.book=res.data.content
_this.total=res.data.totalElements
})
}
},
created(){
const _this=this
axios.get('http://localhost:8181/book/findAll/1/'+_this.pageSize)
.then(function(res){
_this.book=res.data.content
_this.total=res.data.totalElements
})
},
data() {
return {
total:null,
pageSize:5,
book: null
}
}
}
7. 增
Vue ElementUI 提供表單校驗功能
Form組件 | Element
定義rules對象,在rules對象中設置表單各個選項的校驗規(guī)則
- :model="ruleForm" 數(shù)據(jù)綁定
- :rules="rules" 校驗綁定
AddBook.vue中
<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>
<el-button type="primary" @click="submitForm('ruleForm')">立即創(chuàng)建</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
export default {
data() {
return {
ruleForm: {
name: '',
author: ''
},
rules: {
name: [
{ required: true, message: '請輸入書名', trigger: 'blur' }
],
author: [
{ required: true, message: '請輸入作者', trigger: 'blur' },
{ min: 2, max: 8, message: '長度在 2 到 8 個字符', trigger: 'blur' }
]
}
};
}
}
- required: true 是否為必填項
- message: "提示信息"
- trigger: "blur" 觸發(fā)事件
修改后臺代碼
在bookController中添加
@PostMapping("/save")
public String save(@RequestBody Book book){
Book result = bookRepository.save(book);
if (result!=null){
return "success";
}else {
return "error";
}
}
前后對接
methods: {
submitForm(formName) {
const _this=this;
this.$refs[formName].validate((valid) => {
if (valid) {
axios.post('http://localhost:8181/book/save',_this.ruleForm).then(function(res){
if(res.data=="success"){
_this.$alert('《'+_this.ruleForm.name+'》'+'添加成功带射!','消息',{
confirmButtonText:'確定',
callback:action=>{
_this.$router.push('/bookManage')
}
});
}else{
_this.$message.error();('添加失敗');
}
})
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
8. 刪
后端編寫接口
// delete
@DeleteMapping("/deleteById/{id}")
public void deleteById(@PathVariable("id") Integer id){
bookRepository.deleteById(id);
}
為刪除按鈕綁定方法
<!-- 刪除 -->
<el-button @click="deleteBook(scope.row)" type="text" size="small">刪除</el-button>
// delete
deleteBook(row){
const _this=this;
axios.delete('http://localhost:8181/book/deleteById/'+row.id).then(function(res){
_this.$alert('《'+row.name+'》'+'刪除成功同规!','消息',{
confirmButtonText:'確定',
callback:action=>{
window.location.reload();
}
});
})
},
9. 改
后端編寫接口
// findById
@GetMapping("/findById/{id}")
public Book findById(@PathVariable("id") Integer id){
return bookRepository.findById(id).get();
}
// update
@PutMapping("/update")
public String update(@RequestBody Book book){
Book result=bookRepository.save(book);
if (result!=null){
return "success";
}else {
return "error";
}
}
新建update頁面
<template>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm" style="width:50%">
<el-form-item label="編號">
<el-input v-model="ruleForm.id" readonly></el-input>
</el-form-item>
<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>
<el-button type="primary" @click="submitForm('ruleForm')">修改</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
ruleForm: {
id:'',
name: '',
author: ''
},
rules: {
name: [
{ required: true, message: '請輸入書名', trigger: 'blur' }
],
author: [
{ required: true, message: '請輸入作者', trigger: 'blur' },
{ min: 2, max: 8, message: '長度在 2 到 8 個字符', trigger: 'blur' }
]
}
};
},
methods: {
submitForm(formName) {
const _this=this;
this.$refs[formName].validate((valid) => {
if (valid) {
axios.put('http://localhost:8181/book/update',_this.ruleForm).then(function(res){
if(res.data=="success"){
_this.$alert('《'+_this.ruleForm.name+'》'+'修改成功!','消息',{
confirmButtonText:'確定',
callback:action=>{
_this.$router.push('/bookManage')
}
});
}else{
_this.$message.error();('添加失敗');
}
})
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
created(){
const _this=this;
axios.get('http://localhost:8181/book/findById/'+this.$route.query.id).then(function(res){
_this.ruleForm=res.data;
})
}
}
</script>
頁面間交互:
發(fā)送
this.$router.push({
path:'/update',
query:{
id:row.id
}
})
接收
this.$route.query.id
將新建的update頁面添加進路由
{ path: '/',
name: '圖書管理',
component: Index,
redirect:"/bookManage",
children:[
{path:'/bookManage',name:'查詢圖書',component:()=>import('../views/BookManage.vue')},
{path:'/addBook',name:'添加圖書',component:()=>import('../views/AddBook.vue')}
]
},
{path:'/update',component:UpdateBook}
為修改按鈕綁定方法
<!-- 修改 -->
<el-button @click="edit(scope.row)" type="text" size="small">修改</el-button>
// edit
edit(row) {
this.$router.push({
path:'/update',
query:{
id:row.id
}
})
},
※將新建的update頁面添加進路由后左側(cè)導航顯示會多出一個空白項
給路由添加一個show屬性
{ path: '/',
name: '圖書管理',
component: Index,
show:true,
redirect:"/bookManage",
children:[
{path:'/bookManage',name:'查詢圖書',component:()=>import('../views/BookManage.vue')},
{path:'/addBook',name:'添加圖書',component:()=>import('../views/AddBook.vue')}
]
},
{path:'/update',component:UpdateBook,show:false}
改左側(cè)導航欄導航代碼窟社,通過v-show隱藏多出來的導航項
<el-submenu v-for="(item, index) in $router.options.routes"
:key="index" :index="index + ''"
v-show="item.show">
<template slot="title"><i class="el-icon-menu"></i>{{ item.name }}</template>
<el-menu-item v-for="(item2, index2) in item.children"
:key="index2"
:index="item2.path"
:class="$route.path==item2.path?'is-active':''">
{{ item2.name }}</el-menu-item>
</el-submenu>