博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue-element-admin源码解读——项目组件布局及状态管理
阅读量:3898 次
发布时间:2019-05-23

本文共 12023 字,大约阅读时间需要 40 分钟。


vue-element-admin作为一款优秀的前端解决方案,整个框架的结构非常清晰,同时利用VuexVue Router来实现SPA(单页面应用)开发模式,只需要简单的编写组件和配置文件即可完成项目的初步搭建,这对某些需要简单的前端界面的团队无疑是个福利。不过对于需要高度自定义的公司可能还需要更加深入的了解一下,所以这篇文章将带你一步步的摸索整个项目的组件是如何加载的。

基础页面

要摸索当然首先就要找到入口,对于界面而言当然就是index.html的作为入口了。

public\index.html

      
<%= webpackConfig.name %>

这个界面很简单就是定义了一个#app的节点,而是谁在给该节点渲染子节点呢。

src\router\index.js

import Vue from 'vue'import Cookies from 'js-cookie'import 'normalize.css/normalize.css' // a modern alternative to CSS resetsimport Element from 'element-ui'import './styles/element-variables.scss'import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖import '@/styles/index.scss' // global cssimport App from './App'import store from './store'import router from './router'import './icons' // iconimport './permission' // permission controlimport './utils/error-log' // error logimport * as filters from './filters' // global filters//如果是开发模式,则启动本地mock服务器if (process.env.NODE_ENV === 'production') {
const {
mockXHR } = require('../mock') mockXHR()}//使用element-ui插件Vue.use(Element, {
size: Cookies.get('size') || 'medium', // set element-ui default size locale: enLang // 如果使用中文,无需设置,请删除})//指定组件过滤器Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])})Vue.config.productionTip = false//为index.html绑定基础组件App.vue,同时绑定路由和状态管理插件new Vue({
el: '#app', router, store, render: h => h(App)})// App.vue

到这里我们遇到了第一个路由组件<router-view />,接下来我们来看看路由配置信息加载了什么路由组件到这个位置。

src\main.js

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

路由配置信息

import Vue from 'vue'import Router from 'vue-router'Vue.use(Router)/* Layout */import Layout from '@/layout'/* Router Modules */import componentsRouter from './modules/components'import chartsRouter from './modules/charts'import tableRouter from './modules/table'import nestedRouter from './modules/nested'export const constantRoutes = [  {
path: '/redirect', component: Layout, hidden: true, children: [ {
path: '/redirect/:path(.*)', component: () => import('@/views/redirect/index') } ] }, {
path: '/login', component: () => import('@/views/login/index'), hidden: true }, {
path: '/auth-redirect', component: () => import('@/views/login/auth-redirect'), hidden: true }, {
path: '/404', component: () => import('@/views/error-page/404'), hidden: true }, {
path: '/401', component: () => import('@/views/error-page/401'), hidden: true }, {
path: '/', component: Layout, redirect: '/dashboard', children: [ {
path: 'dashboard', component: () => import('@/views/dashboard/index'), name: 'Dashboard', meta: {
title: 'Dashboard', icon: 'dashboard', affix: true } } ] }, ...]/** * asyncRoutes * the routes that need to be dynamically loaded based on user roles */export const asyncRoutes = [ {
path: '/permission', component: Layout, redirect: '/permission/page', alwaysShow: true, // will always show the root menu name: 'Permission', meta: {
title: 'Permission', icon: 'lock', roles: ['admin', 'editor'] // you can set roles in root nav }, children: [ {
path: 'page', component: () => import('@/views/permission/page'), name: 'PagePermission', meta: {
title: 'Page Permission', roles: ['admin'] // or you can only set roles in sub nav } }, {
path: 'directive', component: () => import('@/views/permission/directive'), name: 'DirectivePermission', meta: {
title: 'Directive Permission' // if do not set roles, means: this page does not require permission } }, {
path: 'role', component: () => import('@/views/permission/role'), name: 'RolePermission', meta: {
title: 'Role Permission', roles: ['admin'] } } ] }, /** when your routing map is too long, you can split it into small modules **/ componentsRouter, chartsRouter, nestedRouter, tableRouter, ..... {
path: '*', redirect: '/404', hidden: true }]const createRouter = () => new Router({
//切换界面后自动滚动到顶部 scrollBehavior: () => ({
y: 0 }), routes: constantRoutes})const router = createRouter()export function resetRouter() {
const newRouter = createRouter() router.matcher = newRouter.matcher // reset router}export default router

vue-element-admin框架将路由分为静态路由(constantRoutes)动态路由(asyncRoutes)两部分,静态路由指定的是这些访问路径不需要权限就可以访问,比如404/401/login这些界面。而动态路由是指需要获取相应权限的才可以访问的路径,例如:文章编辑界面权限管理界面等等都是需要有权限才能访问的。

这里存在静态路由的原因是本项目是作为一个示例Demo运行的,而实际项目大部分都是利用当前用户的权限查询之后,然后动态添加到Vue Router中的(后面会谈到)

静态路由

我们以/dashboard为例,该路径配置配置信息如下:

src\router\index.js

{
path: '/', component: Layout, redirect: '/dashboard', children: [ {
path: 'dashboard', component: () => import('@/views/dashboard/index'), name: 'Dashboard', meta: {
title: 'Dashboard', icon: 'dashboard', affix: true } } ]},

根据Vue Router的文档可知,当浏览器访问到http://localhost:9527/#/dashboard路径时,App.vue文件中的<router-view />将会被组件Layout给替换。这里多了个#是因为本项目默认采用的是 hashHistory,还有一种browserHistory

两者的区别简单来说是对路由方式的处理不一样,hashHistory 是以 # 后面的路径进行处理,通过HTML 5 History 进行前端路由管理,而 browserHistory 则是类似我们通常的页面访问路径,并没有 #,但要通过服务端的配置,能够访问指定的 url 都定向到当前页面,从而能够进行前端的路由管理。

src\layout\index.vue

一看这个模板就能看出整个项目的布局结构了,大致分布如下:

图片来源于官方文档
其中sidecartabs-viewbreadcrumd(面包屑)navbar等等结构,框架都已经自动完成填充,只需要在路由配置中增添相应的内容,对应内容就会显示在指定位置,我们需要定制的就是AppMain这一块的内容。

src\layout\components\AppMain.vue

打开AppMain组件之后你会发现他就定义一个简单的布局,并留有一个<router-view :key="key" />(key主要是用于区分不同路由下的相同组件的)子节点,用于路由时的填充。还是之前的那个示例:

{
path: '/', component: Layout, redirect: '/dashboard', children: [ {
path: 'dashboard', component: () => import('@/views/dashboard/index'), name: 'Dashboard', meta: {
title: 'Dashboard', icon: 'dashboard', affix: true } } ]},

可以看出在该路由下,AppMain中的<router-view>将被填入@/views/dashboard/index这个组件。

views/dashboard/index

这里呢,index.vue做了一下权限判断处理,来决定加载哪一个数据面板界面,我们假设我们是以admin角色登入,那么adminDashboard组件就将被渲染在<component :is="currentRole" />的位置。所以我们再进一步看一下adminDashboard组件。

src\views\dashboard\admin\index.vue

对比着之前的截图,这个组件中的节点就按照代码中组织的那样排列整齐。

动态路由

之前描述都是静态路由的组件渲染加载工程,动态路由也是一样的过程,只不过动态路由的配置信息是后面用户登录过后动态添加到Vue Router而已。

由于动态路由是在用户登录之后被添加的,所以我们需要从登录的最初始的地方开发探索。当然首先不能放过,作者配置的导航守卫,也就是拦截器。

src/permission.js

const whiteList = ['/login', '/auth-redirect']router.beforeEach(async(to, from, next) => {
//启动导航进度条 NProgress.start() //设置页面标题 document.title = getPageTitle(to.meta.title) //获取从cookie中获取token const hasToken = getToken() if (hasToken) {
if (to.path === '/login') {
//如果有token的情况下进入login界面,则直接默认登录跳转到Dashboard界面 next({
path: '/' }) NProgress.done() } else {
//如果是其他界面,则查询该用户的角色信息 const hasRoles = store.getters.roles && store.getters.roles.length > 0 if (hasRoles) {
//有角色信息则允许继续路由 next() } else {
try {
//尝试查询角色信息 const {
roles } = await store.dispatch('user/getInfo') //这里就是根据角色信息添加动态路由,这里是异步的 const accessRoutes = await store.dispatch('permission/generateRoutes', roles) router.addRoutes(accessRoutes) next({
...to, replace: true }) } catch (error) {
//如果出错则说明token有问题需要重新获取 await store.dispatch('user/resetToken') Message.error(error || 'Has Error') next(`/login?redirect=${
to.path}`) NProgress.done() } } } } else {
// 没有获取token的说明第一次登录,重定向到登录界面 // 如果是login、auth-redirect则允许继续路由 if (whiteList.indexOf(to.path) !== -1) {
next() } else {
next(`/login?redirect=${
to.path}`) NProgress.done() } }})

从上面可以看出动态的路由信息是利用Vuex的异步action获取的,即调用permission/generateRoutes.

src\store\modules\permission.js

const actions = {
generateRoutes({
commit }, roles) {
return new Promise(resolve => {
let accessedRoutes if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || [] } else {
//这里是根据每个路由配置信息中的meta元信息中的rules过滤 accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } //同步调用下面的mutations方法 commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) }}const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes //合并静态路由和动态路由 state.routes = constantRoutes.concat(routes) }}

到这里整个路由的构成和功能就介绍完毕了。

状态管理

vue-element-admin中使用了很多Vuex提供的getteractionmutations方法,所以掌握这些信息的管理方式也是很有必要的。当然,我们首先要找到状态管理插件信息的传递入口:

src\main.js

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

src\store\index.js

import Vue from 'vue'import Vuex from 'vuex'import getters from './getters'//使用Vuex插件Vue.use(Vuex)//获取store目录的下的modules目录的所有文件const modulesFiles = require.context('./modules', true, /\.js$/)//遍历上述文件const modules = modulesFiles.keys().reduce((modules, modulePath) => {
//将每个文件注册为文件名对应的模块 const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') const value = modulesFiles(modulePath) modules[moduleName] = value.default return modules}, {
})const store = new Vuex.Store({
modules, getters})export default store

src\store目录的结构如下:

在这里插入图片描述

我们查看其中一个模块文件

src\store\module\app.js

import Cookies from 'js-cookie'const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, withoutAnimation: false }, device: 'desktop', size: Cookies.get('size') || 'medium'}const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened state.sidebar.withoutAnimation = false if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1) } else {
Cookies.set('sidebarStatus', 0) } }, ......}const actions = {
toggleSideBar({
commit }) {
commit('TOGGLE_SIDEBAR') }, ......}export default {
namespaced: true, state, mutations, actions}

这个模块中定义了mutationsactions等,可以通过commit()dispatch()等方法来调用,就像.这些各个模块通过在中遍历汇总,最终一并传入Vue中。而各个模块中的state较为散乱,作者还特意将各个state汇总到如下文件中,提供一个统一的访问接口。

src\store\getter.js

const getters = {
sidebar: state => state.app.sidebar, size: state => state.app.size, device: state => state.app.device, visitedViews: state => state.tagsView.visitedViews, cachedViews: state => state.tagsView.cachedViews, token: state => state.user.token, avatar: state => state.user.avatar, name: state => state.user.name, introduction: state => state.user.introduction, roles: state => state.user.roles, permission_routes: state => state.permission.routes, errorLogs: state => state.errorLog.logs}export default getters

通过这种方式,我们只需要通过mapGetters辅助函数就可以将store 中的对应 getter 映射到局部计算属性中,或者通过如下方式访问到:

import {
mapGetters } from 'vuex'export default {
// ... computed: {
...mapGetters([ //如果计算属性和getter中命名相同, // 则可以直接写为'sidebar', sidebar:'sidebar', // ... ]) }}//上述方法等效于如下:export default {
// ... computed: {
sidebar(){
return this.$store.getters.sidebar; } }}

转载地址:http://mqfen.baihongyu.com/

你可能感兴趣的文章
object c 非常强大的类的属性复制kcv键值码赋值
查看>>
Java中普通代码块,构造代码块,静态代码块区别及代码示例
查看>>
iOS 第4课 UILabel
查看>>
[已解决]junit.framework.AssertionFailedError: No tests found in
查看>>
“服务器端跳转”和“客户端跳转”的区别
查看>>
Datatables基本初始化——jQuery表格插件
查看>>
Servlet监听器——实现在线登录人数统计小例子
查看>>
Oracle笔记——简单查询语句 Oracle入门
查看>>
基于Hibernate和Struts2的用户管理系统小案例
查看>>
打开.class文件的方法
查看>>
基于windows平台Git+GitHub+Hexo搭建个人博客(一)
查看>>
基于windows平台Git+GitHub+Hexo搭建个人博客(二)
查看>>
Windows平台下SVN安装配置及使用
查看>>
python简便的编辑工具:jupyter notebook
查看>>
使用pip安装的时候出现 ModuleNotFoundError: No module named ‘pip‘
查看>>
Selenium自动化测试(八)之上传文件
查看>>
Selenium UI自动化(Java篇)
查看>>
使用Fiddler模拟弱网进行测试
查看>>
使用POI读取Excel测试用例
查看>>
记一次数据推送的异常解决端口解决
查看>>