1. 程式人生 > >讀vue-element-admin原始碼二三事(一)

讀vue-element-admin原始碼二三事(一)

因為本人是前端純菜鳥,除了上週大概看了vue.js較簡單的部分以及簡單的ajax和js基礎框架什麼接觸的少,其他沒接觸過,以下沒接觸過的框架用單行程式碼表示,如123

文章目錄

基礎

登入

  • 路由
    看著路由帶#還是很不舒適,於是router/index.js內的
export default new Router({
  mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

mode:histroy部分放開去#號

  • URL過濾

    main.js內匯入了permission.js,程式碼如下:
router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  if (getToken()) { // determine if there has token
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
} else { if (store.getters.roles.length === 0) { // 判斷當前使用者是否已拉取完user_info資訊 store.dispatch('GetUserInfo').then(res => { // 拉取user_info const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop'] store.dispatch('GenerateRoutes', { roles }
).then(() => { // 根據roles許可權生成可訪問的路由表 router.addRoutes(store.getters.addRouters) // 動態新增可訪問路由表 next({ ...to, replace: true }) // hack方法 確保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record }) }).catch((err) => { store.dispatch('FedLogOut').then(() => { Message.error(err || 'Verification failed, please login again') next({ path: '/' }) }) }) } else { // 沒有動態改變許可權的需求可直接next() 刪除下方許可權判斷 ↓ if (hasPermission(store.getters.roles, to.meta.roles)) { next() } else { next({ path: '/401', replace: true, query: { noGoBack: true }}) } // 可刪 ↑ } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // 在免登入白名單,直接進入 next() } else { next(`/login?redirect=${to.path}`) // 否則全部重定向到登入頁 NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it } } })

此處用到框架:NProgress.js大概看了一下,是個進度條。
最外層虛擬碼:

如果(有token【已登入】){
。。。
}否則{
如果(當前跳轉地址在白名單【此地址不需要登入允許匿名訪問】){
	直接進入
}否則{
	回登入頁
}
}

由此攔截未登入的使用者到登入頁

  • 角色管理
    繼續看beforeEach部分,會發現登入後,有一個拉去使用者資料,然後生成路由的特殊部分,用了vuex的store,內部根據asyncRouterMap和當前角色(目前有admin和editor[editor需過濾])進行路由篩選,實際是檢查路由的meta->roles欄位是否包含當前角色,包含就放入路由中。主要通過下列兩段程式碼完成:
    src/store/modules/permission.js
/**
 * 通過meta.role判斷是否與當前使用者許可權匹配
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

/**
 * 遞迴過濾非同步路由表,返回符合使用者角色許可權的路由表
 * @param routes asyncRouterMap
 * @param roles
 */
function filterAsyncRouter(routes, roles) {
  const res = []

  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRouter(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}
  • 多語言
    用到框架:vue-i18n
    具體用法見src/components/LangSelect/index.vue
<template>
  <el-dropdown trigger="click" class="international" @command="handleSetLanguage">
    <div>
      <svg-icon class-name="international-icon" icon-class="language" />
    </div>
    <el-dropdown-menu slot="dropdown">
      <el-dropdown-item :disabled="language==='zh'" command="zh">中文</el-dropdown-item>
      <el-dropdown-item :disabled="language==='en'" command="en">English</el-dropdown-item>
    </el-dropdown-menu>
  </el-dropdown>
</template>

<script>
export default {
  computed: {
    language() {
      return this.$store.getters.language
    }
  },
  methods: {
    handleSetLanguage(lang) {
      this.$i18n.locale = lang
      this.$store.dispatch('setLanguage', lang)
      this.$message({
        message: 'Switch Language Success',
        type: 'success'
      })
    }
  }
}
</script>

主頁

程式碼:

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

看路由是Layout裡面嵌套了一個<router-view/>裡面放了主面板,看程式碼是這樣:

<template>
  <div :class="classObj" class="app-wrapper">
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
    <sidebar class="sidebar-container"/>
    <div class="main-container">
      <navbar/>
      <tags-view/>
      <app-main/>
    </div>
  </div>
</template>

就是側邊欄+主欄,主欄包括導航欄,標籤欄和內容。

  • 側邊欄
<template>
  <el-scrollbar wrap-class="scrollbar-wrapper">
    <el-menu
      :show-timeout="200"
      :default-active="$route.path"
      :collapse="isCollapse"
      mode="vertical"
      background-color="#304156"
      text-color="#bfcbd9"
      active-text-color="#409EFF"
    >
      <sidebar-item v-for="route in permission_routers" :key="route.name" :item="route" :base-path="route.path"/>
    </el-menu>
  </el-scrollbar>
</template>

這裡如果沒有看路由過濾是會有點費解的。通過路由過濾動態設定路由,然後再把路由傳到這裡,然後看sidebar-item內部:

<template>
  <div v-if="!item.hidden&&item.children" class="menu-wrapper">
    <!--單根直接連結,多個下拉列表-->
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <a :href="onlyOneChild.path" target="_blank" @click="clickLink(onlyOneChild.path,$event)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon||item.meta.icon" :title="generateTitle(onlyOneChild.meta.title)" />
        </el-menu-item>
      </a>
    </template>

    <el-submenu v-else :index="item.name||item.path">
      <template slot="title">
        <item v-if="item.meta" :icon="item.meta.icon" :title="generateTitle(item.meta.title)" />
      </template>

      <template v-for="child in item.children" v-if="!child.hidden">
        <sidebar-item
          v-if="child.children&&child.children.length>0"
          :is-nest="true"
          :item="child"
          :key="child.path"
          :base-path="resolvePath(child.path)"
          class="nest-menu" />

        <a v-else :href="child.path" :key="child.name" target="_blank" @click="clickLink(child.path,$event)">
          <el-menu-item :index="resolvePath(child.path)">
            <item v-if="child.meta" :icon="child.meta.icon" :title="generateTitle(child.meta.title)" />
          </el-menu-item>
        </a>
      </template>
    </el-submenu>

  </div>
</template>

其實不是很複雜,就是如果當前根路由沒有hidden,並且有子路由,分兩種情況處理:

1.單個子路由或除了一個子路由其他全部隱藏

例如:

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

就是首頁,那麼點選標題直接進行連結。

2.多個子路由開啟下拉列表,下拉列表再進行連結

例如:

{
    path: '/permission',
    component: Layout,
    redirect: '/permission/index',
    alwaysShow: true, // will always show the root menu
    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: 'pagePermission',
          roles: ['admin'] // or you can only set roles in sub nav
        }
      },
      {
        path: 'directive',
        component: () => import('@/views/permission/directive'),
        name: 'DirectivePermission',
        meta: {
          title: 'directivePermission'
          // if do not set roles, means: this page does not require permission
        }
      }
    ]
  }

今天到這裡了,剩下時間打算看看上述的幾個框架vuex store,element uivue-i18n,還不太會用。