Springboot+Vue前後端分離實現博客系統(前端Vue篇)

1、前言

接下來,我們來完成vueblog前端的部分功能。可能會使用的到技術如下:

  • vue
  • element-ui
  • axios
  • mavon-editor
  • markdown-it
  • github-markdown-css

直接使用 npm install element-ui 、npm install github-markdown-css ......

2、環境準備

1)首先要先安裝node、下載node地址:

Node.js官方安裝包及源碼下載地址:http://nodejs.org/download/

安裝node完成之後,查看版本信息(如果提示出錯,配置一下環境變量,將nodejs的路徑加入path中)

 

2)安裝vue的環境

# 安裝淘寶npm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# vue-cli 安裝依賴包
cnpm install --g vue-cli

3、新建項目

進入指定的文件夾下,執行:vue init webpack vue-blog-front      ;這裏的vue-blog-front是項目名稱。

這裏創建項目需要等待一會兒0.0,我這邊使用的vscode,導入剛剛創建的項目

1.先執行npm install,下載該項目需要的依賴。

目錄結構大致解釋

├── README.md            項目介紹
├── index.html           入口頁面
├── build              構建腳本目錄
│  ├── build-server.js         運行本地構建服務器,可以訪問構建後的頁面
│  ├── build.js            生產環境構建腳本
│  ├── dev-client.js          開發服務器熱重載腳本,主要用來實現開發階段的頁面自動刷新
│  ├── dev-server.js          運行本地開發服務器
│  ├── utils.js            構建相關工具方法
│  ├── webpack.base.conf.js      wabpack基礎配置
│  ├── webpack.dev.conf.js       wabpack開發環境配置
│  └── webpack.prod.conf.js      wabpack生產環境配置
├── config             項目配置
│  ├── dev.env.js           開發環境變量
│  ├── index.js            項目配置文件
│  ├── prod.env.js           生產環境變量
│  └── test.env.js           測試環境變量
├── mock              mock數據目錄
│  └── hello.js
├── package.json          npm包配置文件,裏面定義了項目的npm腳本,依賴包等信息
├── src               源碼目錄 
│  ├── main.js             入口js文件
│  ├── app.vue             根組件
│  ├── components           公共組件目錄
│  │  └── title.vue
│  ├── assets             資源目錄,這裏的資源會被wabpack構建
│  │  └── images
│  │    └── logo.png
│  ├── routes             前端路由
│  │  └── index.js
│  ├── store              應用級數據(state)狀態管理
│  │  └── index.js
│  └── views              頁面目錄
│    ├── hello.vue
│    └── notfound.vue
├── static             純靜態資源,不會被wabpack構建。
└── test              測試文件目錄(unit&e2e)
  └── unit              單元測試
    ├── index.js            入口腳本
    ├── karma.conf.js          karma配置文件
    └── specs              單測case目錄
      └── Hello.spec.js

2.使用npm安裝Router、Vuex、element-ui

3.在src目錄下的main.js,引入element-ui依賴。

import Element from 'element-ui'
import "element-ui/lib/theme-chalk/index.css"

// 引入element
Vue.use(Element)

接下來就可以在頁面上使用element-ui組件了。

4.npm安裝axios

npm install axios --save

然後同樣我們在main.js中全局引入axios。

import axios from 'axios'

// 全局引用axios
Vue.prototype.$axios = axios 

組件中,我們就可以通過this.$axios.get()來發起我們的請求了哈。

5.頁面路由

我們在views文件夾下定義幾個頁面:

  • BlogDetail.vue(博客詳情頁)
  • BlogEdit.vue(編輯博客)
  • Blogs.vue(博客列表)
  • Login.vue(登錄頁面)

這裏只說明BlogDetail.vue(博客詳情頁),其他頁面都類似(當然你得會些許前端的知識)

頁面的結構是:

<template>

   <div>

           這裏就是實現頁面的內容

   </div>

</template>

<script>

       這裏面就是js了,一些定義的變量,引入的js、模塊、一些訪問後端的方法

</script>

<style scoped>

      /* scoped 代表該css只在該頁面有效 */

       這裏放該頁面的樣式css

</style>
<template>
  <div>
    <Header></Header>

    <div class="mblog">
      <h2> {{ blog.title }}</h2>
      <el-link icon="el-icon-edit" v-if="ownBlog">
        <router-link :to="{name: 'BlogEdit', params: {blogId: blog.id}}" >
        編輯
        </router-link>
      </el-link>
      <el-divider></el-divider>
      <div class="markdown-body" v-html="blog.content"></div>

    </div>

  </div>
</template>

<script>
  import 'github-markdown-css'
  import Header from "../components/Header";

  export default {
    name: "BlogDetail.vue",
    components: {Header},
    data() {
      return {
        blog: {
          id: "",
          title: "",
          content: ""
        },
        ownBlog: false
      }
    },
    created() {
      const blogId = this.$route.params.blogId
      console.log(blogId)
      const _this = this
      this.$axios.get('/blog/' + blogId).then(res => {
        const blog = res.data.data
        _this.blog.id = blog.id
        _this.blog.title = blog.title

        var MardownIt = require("markdown-it")
        var md = new MardownIt()

        var result = md.render(blog.content)
        _this.blog.content = result
        _this.ownBlog = (blog.userId === _this.$store.getters.getUser.id)

      })
    }
  }
</script>

<style scoped>
  .mblog {
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    width: 100%;
    min-height: 700px;
    padding: 20px 15px;
  }

</style>

頁面中引入了一個公共的組件,頁面的頭部內容,這樣抽取成一個組件,需要用到該頭部內容的頁面直接引入該組件即可。

然後再路由中心配置:

  • router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import Blogs from '../views/Blogs.vue'
import BlogEdit from '../views/BlogEdit.vue'
import BlogDetail from '../views/BlogDetail.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Index',
    redirect: {name: "Blogs"}
  },
  {
    path: '/blogs',
    name: 'Blogs',
    component: Blogs
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/blog/add',
    name: 'BlogAdd',
    component: BlogEdit,
    meta: {
      requireAuth: true
    }
  },
  {
    path: '/blog/:blogId',
    name: 'BlogDetail',
    component: BlogDetail
  },
  {
    path: '/blog/:blogId/edit',
    name: 'BlogEdit',
    component: BlogEdit,
    meta: {
      requireAuth: true
    }
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

接下來我們去開發我們的頁面。其中,帶有meta:requireAuth: true說明是需要登錄字後才能訪問的受限資源,後面我們路由權限攔截時候會用到。

登錄頁面

Login.vue

<template>
  <div>

    <el-container>
      <el-header>
        <img class="mlogo" src="https://www.markerhub.com/dist/images/logo/markerhub-logo.png" alt="">
      </el-header>
      <el-main>
        <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
          <el-form-item label="用戶名" prop="username">
            <el-input v-model="ruleForm.username"></el-input>
          </el-form-item>
          <el-form-item label="密碼" prop="password">
            <el-input type="password" v-model="ruleForm.password"></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>

      </el-main>
    </el-container>

  </div>
</template>

<script>
  export default {
    name: "Login",
    data() {
      return {
        ruleForm: {
          username: 'frank',
          password: '111111'
        },
        rules: {
          username: [
            { required: true, message: '請輸入用戶名', trigger: 'blur' },
            { min: 3, max: 15, message: '長度在 3 到 15 個字符', trigger: 'blur' }
          ],
          password: [
            { required: true, message: '請選擇密碼', trigger: 'change' }
          ]
        }
      };
    },
    methods: {
      submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            const _this = this
            this.$axios.post('/login', this.ruleForm).then(res => {

              console.log(res.data)
              const jwt = res.headers['authorization']
              const userInfo = res.data.data

              // 把數據共享出去
              _this.$store.commit("SET_TOKEN", jwt)
              _this.$store.commit("SET_USERINFO", userInfo)

              // 獲取
              console.log(_this.$store.getters.getUser)

              _this.$router.push("/blogs")
            })

          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      }
    }
  }
</script>

<style scoped>
  .el-header, .el-footer {
    background-color: #B3C0D1;
    color: #333;
    text-align: center;
    line-height: 60px;
  }

  .el-aside {
    background-color: #D3DCE6;
    color: #333;
    text-align: center;
    line-height: 200px;
  }

  .el-main {
    /*background-color: #E9EEF3;*/
    color: #333;
    text-align: center;
    line-height: 160px;
  }

  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;
  }

  .mlogo {
    height: 60%;
    margin-top: 10px;
  }

  .demo-ruleForm {
    max-width: 500px;
    margin: 0 auto;
  }

</style>

從返回的結果請求頭中獲取到token的信息,然後使用store提交token和用戶信息的狀態。完成操作之後,我們調整到了/blogs路由,即博客列表頁面。

const token = res.headers['authorization']
_this.$store.commit('SET_TOKEN', token)
_this.$store.commit('SET_USERINFO', res.data.data)
_this.$router.push("/blogs")

token的狀態同步

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    token: '',
    userInfo: JSON.parse(sessionStorage.getItem("userInfo"))
  },
  mutations: {
    // set
    SET_TOKEN: (state, token) => {
      state.token = token
      localStorage.setItem("token", token)
    },
    SET_USERINFO: (state, userInfo) => {
      state.userInfo = userInfo
      sessionStorage.setItem("userInfo", JSON.stringify(userInfo))
    },
    REMOVE_INFO: (state) => {
      state.token = ''
      state.userInfo = {}
      localStorage.setItem("token", '')
      sessionStorage.setItem("userInfo", JSON.stringify(''))
    }

  },
  getters: {
    // get
    getUser: state => {
      return state.userInfo
    }

  },
  actions: {
  },
  modules: {
  }
})

存儲token,我們用的是localStorage,存儲用戶信息,我們用的是sessionStorage。畢竟用戶信息我們不需要長久保存,保存了token信息,我們隨時都可以初始化用戶信息。

定義全局axios攔截器

點擊登錄按鈕發起登錄請求,成功時候返回了數據,如果是密碼錯誤,我們是不是也應該彈窗消息提示。爲了讓這個錯誤彈窗能運用到所有的地方,所以我對axios做了個後置攔截器,就是返回數據時候,如果結果的code或者status不正常,那麼我對應彈窗提示。

import axios from 'axios'
import Element from 'element-ui'
import router from './router'
import store from './store'


axios.defaults.baseURL = "http://localhost:8088"

// 前置攔截
axios.interceptors.request.use(config => {
  return config
})

axios.interceptors.response.use(response => {
    let res = response.data;

    console.log("=================")
    console.log(res)
    console.log("=================")

    if (res.code === 200) {
      return response
    } else {

      Element.Message.error('錯了哦,這是一條錯誤消息', {duration: 3 * 1000})

      return Promise.reject(response.data.msg)
    }
  },
  error => {
    console.log(error)
    if(error.response.data) {
      error.message = error.response.data.msg
    }

    if(error.response.status === 401) {
      store.commit("REMOVE_INFO")
      router.push("/login")
    }

    Element.Message.error(error.message, {duration: 3 * 1000})
    return Promise.reject(error)
  }
)

路由權限攔截

permission.js

import router from "./router";

// 路由判斷登錄 根據路由配置文件的參數
router.beforeEach((to, from, next) => {

  if (to.matched.some(record => record.meta.requireAuth)) { // 判斷該路由是否需要登錄權限

    const token = localStorage.getItem("token")
    console.log("------------" + token)

    if (token) { // 判斷當前的token是否存在 ; 登錄存入的token
      if (to.path === '/login') {

      } else {
        next()
      }
    } else {
      next({
        path: '/login'
      })
    }
  } else {
    next()
  }
})

通過之前我們再定義頁面路由時候的的meta信息,指定requireAuth: true,需要登錄才能訪問,因此這裏我們在每次路由之前(router.beforeEach)判斷token的狀態,覺得是否需要跳轉到登錄頁面。

{
  path: '/blog/add', // 注意放在 path: '/blog/:blogId'之前
  name: 'BlogAdd',
  meta: {
    requireAuth: true
  },
  component: BlogEdit
}

然後我們再main.js中import我們的permission.js

import './permission.js'  // 路由攔截

最後啓動項目測試:

1.npm install

2.npm run serve

啓動成功。

自此vue的前端開發完成。前端代碼

文章參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章