美文网首页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