iview admin源码解读

  • 下载地址:

iView Admin 码云:https://gitee.com/icarusion/iview-admin

  • 下载后的目录结构

在这里插入图片描述

  • 登录页显示效果
    在这里插入图片描述
  • 首页显示效果:

在这里插入图片描述
在此介绍一下iview的框架代码如何阅读

  • main.js里引入了很多要用到的插件和组件
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import iView from 'iview'
import i18n from '@/locale'
import config from '@/config'
import importDirective from '@/directive'
import { directive as clickOutside } from 'v-click-outside-x'
import installPlugin from '@/plugin'
import './index.less'
import '@/assets/icons/iconfont.css'
import TreeTable from 'tree-table-vue'
import VOrgTree from 'v-org-tree'
import 'v-org-tree/dist/v-org-tree.css'

其中iview,router,store,App就是我们要用到的关键插件

  • 在文件的最后,把router路由传递进来:
new Vue({
  el: '#app',
  router,
  i18n,
  store,
  render: h => h(App)
})

由import router from './router’这行代码可以看到router所在的目录,跟main.js在同一级,

对应的文件就是router/routers.js

此文件导出一个对象,此文件里有大量的数据,就是框架左侧菜单的数据


```javascript
 {
    path: '/login',
    name: 'login',
    meta: {
      title: 'Login - 登录',
      hideInMenu: true
    },
    component: () => import('@/view/login/login.vue')
  },
  {
    path: '/',
    name: '_home',
    redirect: '/home',
    component: Main,
    meta: {
      hideInMenu: true,
      notCache: true
    },
    children: [
      {
        path: '/home',
        name: 'home',
        meta: {
          hideInMenu: true,
          title: '首页',
          notCache: true,
          icon: 'md-home'
        },
        component: () => import('@/view/single-page/home')
      }
    ]
  },
从下面这段代码可以知道,框架第一个加载的组件就是App.vue

```javascript
new Vue({
  el: '#app',
  router,
  i18n,
  store,
  render: h => h(App)
})

App.vue里是什么代码呢?

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

从此段代码看出,这里放了一个路由视图,可能会有点疑惑,这样放有什么好处呢?
好处就是当我点击某个页面进行切换时,浏览器会出现一个蓝色的加载条,如下图所示:
在这里插入图片描述

当从地址栏访问此工程时,输入任意地址,都会跳转到login.vue中。
为什么会跳到这个页面呢,那是因为router/index.js里有这么一段路由守卫的代码:

router.beforeEach((to, from, next) => {
  iView.LoadingBar.start()
  const token = getToken()
  if (!token && to.name !== LOGIN_PAGE_NAME) {
    // 未登录且要跳转的页面不是登录页
    next({
      name: LOGIN_PAGE_NAME // 跳转到登录页
    })
  } else if (!token && to.name === LOGIN_PAGE_NAME) {
    // 未登陆且要跳转的页面是登录页
    next() // 跳转
  } else if (token && to.name === LOGIN_PAGE_NAME) {
    // 已登录且要跳转的页面是登录页
    next({
      name: homeName // 跳转到homeName页
    })
  } else {
    if (store.state.user.hasGetInfo) {
      turnTo(to, store.state.user.access, next)
    } else {
      store.dispatch('getUserInfo').then(user => {
        // 拉取用户信息,通过用户权限和跳转的页面的name来判断是否有权限访问;access必须是一个数组,如:['super_admin'] ['super_admin', 'admin']
        turnTo(to, user.access, next)
      }).catch(() => {
        setToken('')
        next({
          name: 'login'
        })
      })
    }
  }
})

此代码会使用getToken()方法获取当前是否已经登录过,
那么是从哪里来的呢?
在顶部有一段代码:import { setToken, getToken, canTurnTo, setTitle } from ‘@/libs/util’
可以看到这个方法是从/libs/util.js里来的。

export const TOKEN_KEY = 'token'
export const getToken = () => {
  const token = Cookies.get(TOKEN_KEY)
  if (token) return token
  else return false
}

从代码可以看出,cookie里应该有一个叫token的cookie对象
在这里插入图片描述
从Chrome浏览器里可以看到这个token,它的值就是我们登录的用户名。
如果用户未登录,getToken方法就会返回一个false,否则就会返回super_admin这个用户名。

回到上面router/index.js文件里的代码可以看到,如果getToken返回false,就会使用路由对象的方法next,跳转到用户登录页

next({
      name: LOGIN_PAGE_NAME // 跳转到登录页
    })

如果getToken返回用户名,就会跳转到登录页

next({
      name: homeName // 跳转到homeName页
    })

登录页面是哪个呢?要从路由中找出来

  {
    path: '/login',
    name: 'login',
    meta: {
      title: 'Login - 登录',
      hideInMenu: true
    },
    component: () => import('@/view/login/login.vue')
  },

找到/view/login/login.vue这个页面

<script>
import LoginForm from '_c/login-form'
import { mapActions } from 'vuex'
export default {
  components: {
    LoginForm
  },
  methods: {
    ...mapActions([
      'handleLogin',
      'getUserInfo'
    ]),
    handleSubmit ({ userName, password }) {
      this.handleLogin({ userName, password }).then(res => {
        this.getUserInfo().then(res => {
          this.$router.push({
            name: this.$config.homeName
          })
        })
      })
    }
  }
}
</script>

当用户点击登录时,会执行handleSubmit 这个方法
比方法又调用了this.handleLogin和getUserInfo这两个方法,这两个方法就是vuex这个插件定义的。
这个文件导入了import { mapActions } from ‘vuex’
vuex的代码及配置是在下面这个目录/store/index.js
在这里插入图片描述
其中module子文件夹中的app.js和user.js是vuex的两个子模块module
在index.js里这样定义和引入了两个子模块:

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

import user from './module/user'
import app from './module/app'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    //
  },
  mutations: {
    //
  },
  actions: {
    //
  },
  modules: {
    user,
    app
  }
})

所以代码都在两个子模块的js里。在user.js里有handleLogin方法

 // 登录
    handleLogin ({ commit }, { userName, password }) {
      userName = userName.trim()
      return new Promise((resolve, reject) => {
        login({
          userName,
          password
        }).then(res => {
          const data = res.data
          commit('setToken', data.token)
          resolve()
        }).catch(err => {
          reject(err)
        })
      })
    },

此方法接收userName, password参数。
先对useName这个参数去除空格,接着执行login方法,login方法是从哪里来的呢?

import {
  login,
  logout,
  getUserInfo,
  getMessage,
  getContentByMsgId,
  hasRead,
  removeReaded,
  restoreTrash,
  getUnreadCount
} from '@/api/user'

从顶部的import中可以看出是来自api/user.js文件

import axios from '@/libs/api.request'

export const login = ({ userName, password }) => {
  const data = {
    userName,
    password
  }
  return axios.request({
    url: 'login',
    data,
    method: 'post'
  })
}

上面定义login方法的代码段,可以看出,要用axios发起一个请示,还是post方式发起。目标文件就是login这个地址。而这个地址就是在路由里定义的src\view\login\login.vuelogin.vue。真的发起请求了吗?真的发起了,但是有没有做处理呢,还真是什么都没有干,执行完了这个请求,代码还要往下跑(此得不确定到底有没有做什么,需要再次确认)。

在then方法里执行 commit(‘setToken’, data.token),这就是要调用vuex框架里的setToken方法,此方法在use.js中引入了util.js的方法

export const TOKEN_KEY = 'token'
export const setToken = (token) => {
  Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 })
}

这里看出,在浏览器中设置了一个cookie,cookie的名字是token,值是,并且有效期为1天。
此处的token变量其实就是用户名,那么这个用户名是哪里来的呢?其实是在login.vue里传递过来的

   handleSubmit ({ userName, password }) {
      this.handleLogin({ userName, password }).then(res => {
        this.getUserInfo().then(res => {
          this.$router.push({
            name: this.$config.homeName
          })
        })
      })
    }

这段代码的userName参数,传递给了handleLogin方法。

这时进入到this.getUserInfo方法里这个方法也在user.js里。

  // 获取用户相关信息
    getUserInfo ({ state, commit }) {
      return new Promise((resolve, reject) => {
        try {
          getUserInfo(state.token).then(res => {
            const data = res.data
            commit('setAvatar', data.avatar)
            commit('setUserName', data.name)
            commit('setUserId', data.user_id)
            commit('setAccess', data.access)
            commit('setHasGetInfo', true)
            resolve(data)
          }).catch(err => {
            reject(err)
          })
        } catch (error) {
          reject(error)
        }
      })
    },

在getUserInfo 里又返回了一个promise对象,此对象里的代码又使用了getUserInfo方法,有一个state.token的参数,这个参数其实又是一个方法token: getToken(),
这个方法又是从import { setToken, getToken } from ‘@/libs/util’
这段代码可以看出,来自libs/util.js文件

export const getToken = () => {
  const token = Cookies.get(TOKEN_KEY)
  if (token) return token
  else return false
}

完成了登录,

   handleSubmit ({ userName, password }) {
      this.handleLogin({ userName, password }).then(res => {
        this.getUserInfo().then(res => {
          this.$router.push({
            name: this.$config.homeName
          })
        })
      })
    }

name: this. c o n f i g . h o m e N a m e , 这 段 代 码 就 要 进 行 路 由 跳 转 , t h i s . config.homeName,这段代码就要进行路由跳转,this. config.homeNamethis.config.homeName是来自哪里呢?
这时要回到main.js里查看代码

import config from '@/config'
/**
 * @description 全局注册应用配置
 */
Vue.prototype.$config = config

正是来自config/index.js文件

  /**
   * @description 默认打开的首页的路由name值,默认为home
   */
  homeName: 'home',

此home可不是home.vue这个文件,还要在路由文件routers.js里确认

 {
    path: '/',
    name: '_home',
    redirect: '/home',
    component: Main,
    meta: {
      hideInMenu: true,
      notCache: true
    },
    children: [
      {
        path: '/home',
        name: 'home',
        meta: {
          hideInMenu: true,
          title: '首页',
          notCache: true,
          icon: 'md-home'
        },
        component: () => import('@/view/single-page/home')
      }
    ]
  },

这个home其实就是Main.vue,Main.vue里又有一个子路由,子路由的页面才是home.vue
这时我们要先看Main.vue

<template>
  <Layout style="height: 100%" class="main">
    <Sider hide-trigger collapsible :width="256" :collapsed-width="64" v-model="collapsed" class="left-sider" :style="{overflow: 'hidden'}">
      <side-menu accordion ref="sideMenu" :active-name="$route.name" :collapsed="collapsed" @on-select="turnToPage" :menu-list="menuList">
        <!-- 需要放在菜单上面的内容,如Logo,写在side-menu标签内部,如下 -->
        <div class="logo-con">
          <img v-show="!collapsed" :src="maxLogo" key="max-logo" />
          <img v-show="collapsed" :src="minLogo" key="min-logo" />
        </div>
      </side-menu>
    </Sider>
    <Layout>
      <Header class="header-con">
        <header-bar :collapsed="collapsed" @on-coll-change="handleCollapsedChange">
          <user :message-unread-count="unreadCount" :user-avatar="userAvatar"/>
          <language v-if="$config.useI18n" @on-lang-change="setLocal" style="margin-right: 10px;" :lang="local"/>
          <error-store v-if="$config.plugin['error-store'] && $config.plugin['error-store'].showInHeader" :has-read="hasReadErrorPage" :count="errorCount"></error-store>
          <fullscreen v-model="isFullscreen" style="margin-right: 10px;"/>
        </header-bar>
      </Header>
      <Content class="main-content-con">
        <Layout class="main-layout-con">
          <div class="tag-nav-wrapper">
            <tags-nav :value="$route" @input="handleClick" :list="tagNavList" @on-close="handleCloseTag"/>
          </div>
          <Content class="content-wrapper">
            <keep-alive :include="cacheList">
              <router-view/>
            </keep-alive>
            <ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop>
          </Content>
        </Layout>
      </Content>
    </Layout>
  </Layout>
</template>

main.vue里有一个Sider组件,传递的:menu-list="menuList"参数是menuList,这就是我们看到的左侧的导航。

computed: {
    menuList () {
      return this.$store.getters.menuList
    }

这正是vuex的用法,在计算方法里有一个menuList ,而它来自app.js里的代码:

  getters: {
    menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access),
    errorCount: state => state.errorList.length
  },

参数routers是从import routers from '@/router/routers’导入进来的。
而routers就是整个路由对象。
getMenuByRouter方法又从libs/util.js里引入。

/**
 * @param {Array} list 通过路由列表得到菜单列表
 * @returns {Array}
 */
export const getMenuByRouter = (list, access) => {
  let res = []
  forEach(list, item => {
    if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
      let obj = {
        icon: (item.meta && item.meta.icon) || '',
        name: item.name,
        meta: item.meta
      }
      if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) {
        obj.children = getMenuByRouter(item.children, access)
      }
      if (item.meta && item.meta.href) obj.href = item.meta.href
      if (showThisMenuEle(item, access)) res.push(obj)
    }
  })
  return res
}

list其实就是我们传递进来的路由对象,路由对象里有每多元素,都如下代码所示,有path,name,meta,component,children。跟上面代码可以对应起来。

  {
    path: '/join',
    name: 'join',
    component: Main,
    meta: {
      hideInBread: true
    },
    children: [
      {
        path: 'join_page',
        name: 'join_page',
        meta: {
          icon: '_qq',
          title: 'QQ群'
        },
        component: () => import('@/view/join-page.vue')
      }
    ]
  },

总结:iview admin的源码还是比较复杂的。一眼看下去,有点不好理解。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值