前后端分離Test SpringBoot+Vue

前后端分離
前端只需要獨立編寫客戶端代碼
后端只需獨立編寫服務端代碼提供數(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接口

http://localhost:8181/book/findAll

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的綁定

  1. <el-menu>標簽添加router屬性

    <el-menu router>
    
  2. 頁面中添加<router-view></router-view>肉渴,動態(tài)渲染選擇的router

  3. <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組件

分頁組件 | Element

修改后端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>

至此券勺,一個基于SpringBoot、Vue 的 前后端分離 練習 就完成了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桥爽,一起剝皮案震驚了整個濱河市朱灿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钠四,老刑警劉巖盗扒,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缀去,居然都是意外死亡侣灶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門缕碎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褥影,“玉大人,你說我怎么就攤上這事咏雌》苍酰” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵赊抖,是天一觀的道長统倒。 經(jīng)常有香客問我,道長氛雪,這世上最難降的妖魔是什么房匆? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮报亩,結(jié)果婚禮上浴鸿,老公的妹妹穿的比我還像新娘。我一直安慰自己弦追,他們只是感情好岳链,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著劲件,像睡著了一般掸哑。 火紅的嫁衣襯著肌膚如雪左胞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天举户,我揣著相機與錄音烤宙,去河邊找鬼。 笑死俭嘁,一個胖子當著我的面吹牛躺枕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播供填,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼拐云,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了近她?” 一聲冷哼從身側(cè)響起叉瘩,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粘捎,沒想到半個月后薇缅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡攒磨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年泳桦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娩缰。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡灸撰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拼坎,到底是詐尸還是另有隱情浮毯,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布泰鸡,位于F島的核電站债蓝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鸟顺。R本人自食惡果不足惜惦蚊,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一器虾、第九天 我趴在偏房一處隱蔽的房頂上張望讯嫂。 院中可真熱鬧,春花似錦兆沙、人聲如沸欧芽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽千扔。三九已至憎妙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間曲楚,已是汗流浹背厘唾。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留龙誊,地道東北人抚垃。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像趟大,于是被迫代替她去往敵國和親鹤树。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容