vue从零搭建后台管理系统(二)ElementUI 、Vuex、 axios、Mock.js及其他一些配置
本章主要演示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
今天就进行到这里,本教程可能注重过程,忽略了很多概念的东西和细节,一些地方都会标注使用到的功能依赖的路径跳转,如需深入了解可以先去读读文档大概了解一些概念再来也无妨
以上
如有问题,欢迎批评指正
后续会在此基础上继续更新,感谢欢迎关注支持
发表评论 (审核通过后显示评论):