vue項目功能菜單配置自動生成路由及代碼切割

功能菜單配置自動生成路由及代碼切割

菜單即路由,共享配置

約定

views目錄中存放業務組件;業務模塊分文件夾存放,路由主組件使用index命名存放在以路由名的目錄下。

views                         **業務組件
└── base                        業務分組目錄,對應router目錄中的菜單組
    ├── about                   業務功能
    │   ├── index.vue           業務路由頁面主組件
    │   └── ...others.vue       相關的分塊組件
    ├── welcome
    │   └── index.vue
    └── index.vue               子路由組件,在菜單開啓子路由時可用

配置

每一個配置文件對應一組菜單,文件名對應views下面的子目錄,配置結構如下:

// base.ts
const menu: IMenu = {
  name: 'base',             // 菜單名稱,對應views目錄下的子目錄
  title: '基本信息',         // 菜單標題
  isPath: true,             // 是否爲訪問父路徑,默認情況下菜單下的目錄爲一級路由,開啓此項將讀取目錄下index.vue作爲子路由組件,文件不存在時自動生成組件。
  routes: [                 // 菜單目錄
    {
      name: 'about',        // 菜單名稱,對應路由名稱
      title: '關於',        // 菜單標題,對應路由meta信息
      filename: 'about',    // 組件文件名,默認爲name
      path: 'about',        // 訪問路徑,配置方式同vue-router,默認爲name
      ...RouterConfig       // 其它vue-router的路由配置
    },
    {
      name: 'welcome',
      title: '歡迎'
    }
  ]
}

export default menu

實現

菜單轉路由

menuToRoute 方法將菜單配置轉換爲路由配置

參數

  • menu:單個菜單配置對象或多個配置對象數組
  • isRoot:是否爲根路由;路由配置中根路由path需要以/開頭
// menuToRoute.ts
export default function formatRoute(menu: IMenu | IMenu[], isRoot?: boolean) {
  let _routes: RouteConfig[] = []
  if (Array.isArray(menu)) {
    for (const item of menu) {
      _routes = _routes.concat(formatRoute(item, isRoot))
    }
    return _routes
  }

  const { name: menuName, isPath, routes } = menu
  const children = routes.map(route => {
    const { name, filename = name, path, title, meta, ...config } = route
    return {
      name,
      path: (!isPath && isRoot ? '/' : '') + (path || name),
      component: () =>
        // 使用chunks方法對代碼按照菜單分塊打包,詳見後面說明
        chunks(menuName, filename).then(e => {
          // 在使用keep-alive動態緩存include屬性時需要組件name
          // 加上'v-'前綴避免“Do not use built-in or reserved HTML elements as component id”
          ;(e.default.options || e.default).name = 'v-' + name
          return e
        }),
      meta: title ? { title, ...meta } : meta
      ...config,
    }
  })
  _routes = isPath
    ? [
        {
          path: (isRoot ? '/' : '') + menuName,
          // 如果使用二級路由,讀取菜單目錄下的index爲子路由組件,沒有找到使用默認組件
          component: () => chunks(menuName, 'index').catch(() => subView),
          children
        }
      ]
    : children

  return _routes
}

與其它路由配置結合使用:

// @/router/routes.ts
import Home from '@/views/Home.vue'
/* 導入所有菜單配置數組 */
import menu from './menu/'
import menuToRoute from './menuToRoute'

const ISROOT = true
const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  ...menuToRoute(menu, ISROOT)
]

export default routes

代碼切割

路由對應的組件使用 webpack的import() 方法(查看官方文檔)懶加載,可根據需求修改menuToRoute.ts文件中的chunk方法進行代碼切割打包。

一般小的項目中,可以直接使用import( `@/views/${name}/index` )就可以了,此方法將每個目錄下的index默認導出類型文件爲入口將相關引用的文件進行打包;結合前面約定路由主組件以index命名的規範,可以避免將其它沒有引用到的文件打包。
/**
 * menuToRoute.ts
 * 代碼切割打包
 * @param type 菜單組(文件夾)名稱
 * @param name 路由名稱或路由對應文件名
 */
function chunks(type: string, name: string) {
  switch (type) {
    case 'guide':
      // guide菜單下的路由如果沒有指定filename,將默認導入md文件,由於md非默認導入類型,所以需要加上擴展名
      if (!/(^index)|(\.\w+$)/.test(name)) {
        name += '.md'
      }
      return import(/* webpackChunkName: 'guide' */ `@/views/guide/${name}`)

    case 'example':
      // 將views/example/下的所有vue和tsx文件打成一個包
      return import(
        /* webpackChunkName: 'example',webpackMode: "lazy-once",webpackInclude: /\.(vue|tsx)$/ */
        `@/views/example/${name}`
      )
    default:
      // 將views目錄下(包括子目錄)所有index文件的默認導入類型分塊打包
      return import( `@/views/${name}/index` )
  }
}

菜單應用及權限控制

應用到elment-ui的NavMenu菜單導航組件示例:

  <el-menu :default-active="$route.name" @select="$router.push({ name:$event })">
    <el-menu-item index="home">
      <template slot="title">首頁</template>
    </el-menu-item>
    <el-submenu :index="menu.name" v-for="menu of menuList" :key="menu.name">
      <template slot="title">{{ menu.title }}</template>
      <el-menu-item :index="item.name" v-for="item of menu.routes" :key="item.name">
        {{ item.title }}
      </el-menu-item>
    </el-submenu>
  </el-menu>

權限過濾

  • 後臺按菜單結構名稱建立多級功能列表,如:菜單->目錄->tab->按鈕
  • 將不同的功能權限分配到角色
  • 每個用戶可以指定多個角色
  • 用戶登錄返回用戶角色組,進行權限合併
/**
 * @param menuList 後臺配置的所有權限信息列表
 * @param roleList 用戶多個角色信息
 **/
function buildUserRule (menuList, roleList) {
  // 合併角色權限列表
  const menuIdList = [].concat(roleList.map(role=>role.menuIdList))
  // 過濾生成用戶權限
  const rules = menuList.filter(item => menuIdList.includes(item.id))
  // 通過權限信息中的id和parentId對應關係生成目錄樹結構
  return formatTree(rules)
}

將生成的權限樹結構存放在store中,推薦格式如下:

  {
    菜單名:{              // 對應菜單組
      目錄名:{            // 對應路由
        tab名:{           // 路由頁面中多個頁籤
          edit: true      // 操作按鈕
          delete: true
        }
      }
    }
  }

通過這種格式在菜單顯示的時候方便進行權限過濾,進入到某個路由時也方便取到當前路由下的權限進行權限適配

原創文章,轉載請聲明,歡迎一起討論! @nicefan.cn

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章