美文网首页Web前端之路
vue从零搭建后台管理系统(二)ElementUI 、Vuex、

vue从零搭建后台管理系统(二)ElementUI 、Vuex、

作者: 史密斯_Du | 来源:发表于2020-03-23 15:43 被阅读0次

    本章主要演示VUE项目引入ElementUI 、Vuex、 axios、Mock.js及一些公共变量的配置
    本章需要在上一章的基础上安装以下组件,绿色为新增组件

    image.png

    也可异步github至项目自行对照package.json对比缺少哪些依赖
    https://github.com/a307420929/Vue-management-system

    dependencies下安装命令:
    以axios为例
    npm i axios --save (最新发布版本)
    npm i axios@^7.8.7 --save (安装指定版本)
    devDependencies下安装命令:
    以babel-cli为例
    npm i babel-cli --save-dev (最新发布版本)
    npm i babel-cli@^6.26.0 --save-dev (安装指定版本)
    后续不再过多赘述安装依赖方法

    1.ElementUI

    因为是后台项目,视图组件库采用目前支持比较好的并且用的比较多的ElementUI>https://element.eleme.cn/#/zh-CN

    在/src/main.js中添加代码

    import ElementUI from 'element-ui'  //引入ElementUI
    import 'element-ui/lib/theme-chalk/index.css' //引入ElementUI样式相关
    Vue.use(ElementUI) // Vue中使用
    

    安装成功后,便可在项目中使用elment相关视图组件,类似下面当中的el-***开头的标签都是element的组件,未来项目中会大面积使用,先截取/src/views/login/index.vue 部分代码熟悉一下


    image.png

    2.Vuex的引用

    详细了解移步官网地址:https://vuex.vuejs.org/zh/

    image.png

    我们将项目目录中的store优化成以下结构,方便后期分功能模块存取状态值


    image.png

    /store/modules/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    import getters from './getters'
    import app from './modules/app'
    import settings from './modules/settings'
    import user from './modules/user'
    
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
      modules: {
        app,
        settings,
        user
      },
      getters
    })
    
    export default store
    

    /store/modules/getters.js

    const getters = {
      sidebar: state => state.app.sidebar,
      device: state => state.app.device,
      token: state => state.user.token,
      avatar: state => state.user.avatar,
      name: state => state.user.name
    }
    export default getters
    

    /store/modules/user.js

    import {
      login,
      logout,
      getInfo
    } from '@/api/user'
    import {
      getToken,
      setToken,
      removeToken
    } from '@/utils/auth'
    import {
      resetRouter
    } from '@/router'
    
    const getDefaultState = () => {
      return {
        token: getToken(),
        name: '',
        avatar: ''
      }
    }
    
    const state = getDefaultState()
    
    const mutations = {
      RESET_STATE: (state) => {
        Object.assign(state, getDefaultState())
      },
      SET_TOKEN: (state, token) => {
        state.token = token
      },
      SET_NAME: (state, name) => {
        state.name = name
      },
      SET_AVATAR: (state, avatar) => {
        state.avatar = avatar
      }
    }
    
    const actions = {
      // user login
      login({
        commit
      }, userInfo) {
        const {
          username,
          password
        } = userInfo
        return new Promise((resolve, reject) => {
          login({
            username: username.trim(),
            password: password
          }).then(response => {
            const {
              data
            } = response
       
            commit('SET_TOKEN', data.token)
            setToken(data.token)
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // get user info
      getInfo({
        commit,
        state
      }) {
        return new Promise((resolve, reject) => {
          getInfo(state.token).then(response => {
            const {
              data
            } = response
    
            if (!data) {
              reject('Verification failed, please Login again.')
            }
    
            const {
              name,
              avatar
            } = data
    
            commit('SET_NAME', name)
            commit('SET_AVATAR', avatar)
            resolve(data)
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // user logout
      logout({
        commit,
        state
      }) {
        return new Promise((resolve, reject) => {
          logout(state.token).then(() => {
            removeToken() // must remove  token  first
            resetRouter()
            commit('RESET_STATE')
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // remove token
      resetToken({
        commit
      }) {
        return new Promise(resolve => {
          removeToken() // must remove  token  first
          commit('RESET_STATE')
          resolve()
        })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    

    同样需要在/src/main.js中添加代码,因为我们通过vue-cli的时候选择了vuex,所以main里面默认就已经引用了store

    import store from './store'
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#app')
    

    此时,就可以在正常业务过程中通过vuex去存取状态了,截取以下登录逻辑片段方便理解


    image.png

    3.axios的引用

    Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
    详细使用示例及API 移步 https://www.kancloud.cn/yunye/axios/234845

    封装axios
    /src/utils/request.js

    import axios from 'axios'
    import { MessageBox, Message } from 'element-ui'
    import store from '@/store'
    import { getToken } from '@/utils/auth'
    
    // 新建axios服务
    const service = axios.create({
      baseURL: process.env.VUE_APP_BASE_API,  // 这里取环境变量VUE_APP_BASE_API
      timeout: 5000 // request timeout
    })
    
    // 请求拦截器
    service.interceptors.request.use(
      config => {
        // do something before request is sent
        if (store.getters.token) {
          // let each request carry token
          // ['X-Token'] is a custom headers key
          // please modify it according to the actual situation
          config.headers['X-Token'] = getToken()
        }
        return config
      },
      error => {
        // do something with request error
        console.log(error) // for debug
        return Promise.reject(error)
      }
    )
    
    // 响应拦截器
    service.interceptors.response.use(
      response => {
        const res = response.data
        if (res.code !== 20000) {
          Message({
            message: res.message || 'Error',
            type: 'error',
            duration: 5 * 1000
          })
    
          // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
          if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
            // to re-login
            MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
              confirmButtonText: 'Re-Login',
              cancelButtonText: 'Cancel',
              type: 'warning'
            }).then(() => {
              store.dispatch('user/resetToken').then(() => {
                location.reload()
              })
            })
          }
          return Promise.reject(new Error(res.message || 'Error'))
        } else {
          return res
        }
      },
      error => {
        console.log('err' + error) // for debug
        Message({
          message: error.message,
          type: 'error',
          duration: 5 * 1000
        })
        return Promise.reject(error)
      }
    )
    
    export default service
    

    此时我们就可以在定义API的时候用到封装的axios了
    示例代码 /src/api/user.js

    import request from '@/utils/request'
    export function login(data) {
      return request({
        url: '/vue-management-system/user/login',
        method: 'post',
        data
      })
    }
    export function getInfo(token) {
      return request({
        url: '/vue-management-system/user/info',
        method: 'get',
        params: { token }
      })
    }
    export function logout() {
      return request({
        url: '/vue-management-system/user/logout',
        method: 'post'
      })
    }
    

    定义好的API在业务代码中的使用参考以下代码片段
    如需使用接口别忘记在代码最上方引入对应接口方法名
    import {getInfo } from '@/api/user'

    image.png

    4.Mock.js的引用

    mock的具体使用请移步 https://github.com/nuysoft/Mock/wiki/Getting-Started

    新建mock目录,在mock目录下新建index.js和mock-server.js文件
    index.js

    import Mock from 'mockjs'
    import { param2Obj } from '../src/utils'
    
    import user from './user'
    
    const mocks = [
      ...user,
    ]
    
    // for front mock
    // please use it cautiously, it will redefine XMLHttpRequest,
    // which will cause many of your third-party libraries to be invalidated(like progress event).
    export function mockXHR() {
      // mock patch
      // https://github.com/nuysoft/Mock/issues/300
      Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
      Mock.XHR.prototype.send = function() {
        if (this.custom.xhr) {
          this.custom.xhr.withCredentials = this.withCredentials || false
    
          if (this.responseType) {
            this.custom.xhr.responseType = this.responseType
          }
        }
        this.proxy_send(...arguments)
      }
    
      function XHR2ExpressReqWrap(respond) {
        return function(options) {
          let result = null
          if (respond instanceof Function) {
            const { body, type, url } = options
            // https://expressjs.com/en/4x/api.html#req
            result = respond({
              method: type,
              body: JSON.parse(body),
              query: param2Obj(url)
            })
          } else {
            result = respond
          }
          return Mock.mock(result)
        }
      }
    
      for (const i of mocks) {
        Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
      }
    }
    
    // for mock server
    const responseFake = (url, type, respond) => {
      return {
        url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
        type: type || 'get',
        response(req, res) {
          console.log('request invoke:' + req.path)
          res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
        }
      }
    }
    
    export default mocks.map(route => {
      return responseFake(route.url, route.type, route.response)
    })
    
    

    mock-server.js

    const chokidar = require('chokidar')
    const bodyParser = require('body-parser')
    const chalk = require('chalk')
    const path = require('path')
    
    const mockDir = path.join(process.cwd(), 'mock')
    
    function registerRoutes(app) {
      let mockLastIndex
      const { default: mocks } = require('./index.js')
      for (const mock of mocks) {
        app[mock.type](mock.url, mock.response)
        mockLastIndex = app._router.stack.length
      }
      const mockRoutesLength = Object.keys(mocks).length
      return {
        mockRoutesLength: mockRoutesLength,
        mockStartIndex: mockLastIndex - mockRoutesLength
      }
    }
    
    function unregisterRoutes() {
      Object.keys(require.cache).forEach(i => {
        if (i.includes(mockDir)) {
          delete require.cache[require.resolve(i)]
        }
      })
    }
    
    module.exports = app => {
      // es6 polyfill
      require('@babel/register')
    
      // parse app.body
      // https://expressjs.com/en/4x/api.html#req.body
      app.use(bodyParser.json())
      app.use(bodyParser.urlencoded({
        extended: true
      }))
    
      const mockRoutes = registerRoutes(app)
      var mockRoutesLength = mockRoutes.mockRoutesLength
      var mockStartIndex = mockRoutes.mockStartIndex
    
      // watch files, hot reload mock server
      chokidar.watch(mockDir, {
        ignored: /mock-server/,
        ignoreInitial: true
      }).on('all', (event, path) => {
        if (event === 'change' || event === 'add') {
          try {
            // remove mock routes stack
            app._router.stack.splice(mockStartIndex, mockRoutesLength)
    
            // clear routes cache
            unregisterRoutes()
    
            const mockRoutes = registerRoutes(app)
            mockRoutesLength = mockRoutes.mockRoutesLength
            mockStartIndex = mockRoutes.mockStartIndex
    
            console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
          } catch (error) {
            console.log(chalk.redBright(error))
          }
        }
      })
    }
    

    user.js 为相应返回mock数据的定义

    
    const tokens = {
      admin: {
        token: 'admin-token'
      },
      editor: {
        token: 'editor-token'
      }
    }
    
    const users = {
      'admin-token': {
        roles: ['admin'],
        introduction: 'I am a super administrator',
        avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
        name: 'Super Admin'
      },
      'editor-token': {
        roles: ['editor'],
        introduction: 'I am an editor',
        avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
        name: 'Normal Editor'
      }
    }
    
    export default [
      // user login
      {
        url: '/vue-management-system/user/login',
        type: 'post',
        response: config => {
          const { username } = config.body
          const token = tokens[username]
    
          // mock error
          if (!token) {
            return {
              code: 60204,
              message: 'Account and password are incorrect.'
            }
          }
    
          return {
            code: 20000,
            data: token
          }
        }
      },
    
      // get user info
      {
        url: '/vue-management-system/user/info\.*',
        type: 'get',
        response: config => {
          const { token } = config.query
          const info = users[token]
    
          // mock error
          if (!info) {
            return {
              code: 50008,
              message: 'Login failed, unable to get user details.'
            }
          }
    
          return {
            code: 20000,
            data: info
          }
        }
      },
    
      // user logout
      {
        url: '/vue-management-system/user/logout',
        type: 'post',
        response: _ => {
          return {
            code: 20000,
            data: 'success'
          }
        }
      }
    ]
    

    相关定义文件写好,此时需要去vue.config.js的devserver配置一下拦截


    image.png

    5.其他配置项

    到此步骤有一个需要注意的地方分享给大家
    因为此项目我们使用的是vue-cli3
    在vue-cli2中打包时可以修改 “build” 和 “config”中的文件来区分不同的线上环境
    而vue-cli3号称0配置,无法直接修改打包文件

    其实可以通过为 .env 文件增加后缀来设置某个模式下特有的环境变量。比如,如果你在项目根目录创建一个名为 .env.development 的文件,那么在这个文件里声明过的变量就只会在 development 模式下被载入。
    所以我们项目中会有以下两个文件


    image.png

    .env.development 内容定义如下

    # just a flag
    ENV = 'development'
    
    # base api
    VUE_APP_BASE_API = '/dev-api'
    

    此时我们就可以通过process.env.VUE_APP_BASE_API获取到对应的值了
    可参考src\utils\request.js 中baseURL的定义

    image.png

    今天就进行到这里,本教程可能注重过程,忽略了很多概念的东西和细节,一些地方都会标注使用到的功能依赖的路径跳转,如需深入了解可以先去读读文档大概了解一些概念再来也无妨
    以上
    如有问题,欢迎批评指正
    后续会在此基础上继续更新,感谢欢迎关注支持

    相关文章

      网友评论

        本文标题:vue从零搭建后台管理系统(二)ElementUI 、Vuex、

        本文链接:https://www.haomeiwen.com/subject/rzjjyhtx.html