【摸魚神器】vue + 路由 + 菜單 + tabs 能不能一次搞定佣盒?

做一個管理后臺挎袜,首先要設(shè)置路由,然后配置菜單(有時候還需要導(dǎo)航)肥惭,再來一個動態(tài)tabs盯仪,最后加上權(quán)限判斷。

這個是不是有點(diǎn)繁瑣蜜葱?尤其是路由的設(shè)置和菜單的配置全景,是不是很雷同?那么能不能簡單一點(diǎn)呢牵囤?如果可以實(shí)現(xiàn)設(shè)置一次就全部搞定的話爸黄,那么是不會很香呢?

我們可以簡單封裝一下揭鳞,實(shí)現(xiàn)這個愿望炕贵。

定義一個結(jié)構(gòu)

我們可以參考 vue-router 的設(shè)置 和 el-menu 的參數(shù),設(shè)置一個適合我們需求的結(jié)構(gòu):

  • ./router.js
import { createRouter } from '@naturefw/ui-elp'

import home from '../views/home.vue'

const router = {
  /**
   * 基礎(chǔ)路徑
   */
  baseUrl: baseUrl,

  /**
   * 首頁
   */
  home: home,

  menus: [
    {
      menuId: '1', // 相當(dāng)于路由的 name
      title: '全局狀態(tài)', // 瀏覽器的標(biāo)題
      naviId: '0', // 導(dǎo)航ID
      path: 'global', // 相當(dāng)于 路由 的path
      icon: FolderOpened, // 菜單里的圖標(biāo)
      childrens: [ // 子菜單野崇,不是子路由称开。
        {
          menuId: '1010', // 相當(dāng)于路由的 name
          title: '純state',
          path: 'state',
          icon: Document,
          // 加載的組件
          component: () => import('../views/state-global/10-state.vue')
          // 還可以有子菜單。 
        },
        {
          menuId: '1020',
          title: '一般的狀態(tài)',
          path: 'standard',
          icon: Document,
          component: () => import('../views/state-global/20-standard.vue')
        } 
      ]
    },
    {
      menuId: '2000',
      title: '局部狀態(tài)',
      naviId: '0',
      path: 'loacl',
      icon: FolderOpened,
      childrens: [
        {
          menuId: '2010',
          title: '父子組件',
          path: 'parent-son',
          icon: Document,
          component: () => import('../views/state-loacl/10-parent.vue')
        }
      ]
    } 
  ]
}

export default createRouter(router )

在 Router 的配置的基礎(chǔ)上乓梨,加上 title鳖轰、icon等菜單需要的屬性,基本就搞定了扶镀。

  • baseUrl:如果不能發(fā)布到根目錄的話蕴侣,需要設(shè)置一個基礎(chǔ)URL。
  • home:默認(rèn)顯示的組件臭觉,比如大屏睛蛛。
  • menus:路由鹦马、菜單集合。
    • naviId:導(dǎo)航ID忆肾。
    • menuId:相當(dāng)于路由的 name荸频。
    • path:相當(dāng)于 路由 的path。
    • title:瀏覽器的標(biāo)題客冈。
    • icon: 菜單里的圖標(biāo)旭从。
    • childrens:子菜單,不是子路由场仲。

main 里面加載和悦。

設(shè)置之后,我們在main里面掛載一下即可渠缕。

import { createApp } from 'vue'
import App from './App.vue'

// 簡易路由
import router from './router'

createApp(App)
  .use(router)
  .mount('#app')
  • 看看效果
路由和菜單

https://naturefw-code.gitee.io/nf-rollup-state/class/object

這樣就搞定了鸽素,是不是很簡單,因?yàn)槲覀儼哑渌a都封裝成了組件亦鳞。

封裝 n級菜單

我們可以基于 el-menu馍忽,封裝一個動態(tài)n級菜單組件(nf-menu)。

菜單組件可以基于 el-menu 封裝燕差,也可以基于其他組件封裝遭笋,或者自己寫一個,這里以el-menu為例徒探,介紹一下封裝方式:

  • 父級菜單
  <el-menu
    ref="domMenu"
    class="el-menu-vertical-demo"
    @select="select"
    background-color="#6c747c"
    text-color="#fff"
    active-text-color="#ffd04b"
  >
    <sub-menu1
      :subMenu="menus"
    />
  </el-menu>

父級菜單比較簡單瓦呼,設(shè)置 el-menu 需要的屬性,然后加載子菜單組件测暗。

  • n級子菜單
  <template v-for="(item, index) in subMenu">
    <!--樹枝-->
    <template v-if="item.childrens && item.childrens.length > 0">
      <el-sub-menu
        :key="item.menuId + '_' + index"
        :index="item.menuId"
        style="vertical-align: middle;"
        
      >
        <template #title>
          <component
            :is="item.icon"
            style="width: 1.5em; height: 1.5em; margin-right: 8px;vertical-align: middle;"
          >
          </component>
          <span>{{item.title}}</span>
        </template>
        <!--遞歸子菜單-->
        <my-sub-menu2
          :subMenu="item.childrens"
        />
      </el-sub-menu>
    </template>
    <!--樹葉-->
    <el-menu-item v-else
      :index="item.menuId"
      :key="item.menuId + 'son_' + index"
      
    >
      <template #title>
        <span style="float: left;">
          <component
            :is="item.icon"
            style="width: 1.5em; height: 1.5em; margin-right: 8px;vertical-align: middle;"
          >
          </component>
          <span >{{item.title}}</span>
        </span>
      </template>
    </el-menu-item>
  </template>
  • 樹枝:含有子菜單的菜單央串,使用 el-sub-menu 實(shí)現(xiàn),不加載組件碗啄。
  • 樹葉:沒有子菜單质和,使用 el-menu-item 實(shí)現(xiàn),加載組件的菜單挫掏。
  • 圖標(biāo):使用 component 加載圖標(biāo)組件侦另。

然后設(shè)置屬性即可,這樣一個n級菜單就搞定了尉共。

封裝一個動態(tài)tabs

菜單有了褒傅,下一步就是tabs,為了滿足不同的需求袄友,這里封裝兩個組件殿托,一個單tab的,一個是動態(tài)多tabs的剧蚣。

  • 單 tab
    參考 Router 的 router-view 封裝一個組件 nf-router-view:
  <component :is="$router.getComponent()">
  </component>

直接使用 component 加載組件即可支竹。

  • 動態(tài)多tabs
    基于 el-tabs 封裝一個動態(tài)多tabs組件 nf-router-view-tabs:
  <el-tabs
    v-model="$router.currentRoute.key"
    type="border-card"
  >
    <el-tab-pane label="桌面" name="home">
      <component :is="$router.home">
      </component>
    </el-tab-pane>
    <el-tab-pane
      v-for="key in $router.tabs"
      :key="key"
      :label="$router.menuList[key].title"
      :name="key"
    >
     <template #label>
        <span>{{$router.menuList[key].title}} &nbsp; 
          <circle-close-filled
            style="width: 1.0em; height: 1.0em; margin-top: 8px;"
            @click.stop="$router.removeTab(key)" />
        </span>
      </template>
      <component :is="$router.menuList[key].component">
      </component>
    </el-tab-pane>
  </el-tabs>

為了保持狀態(tài)旋廷,這里采用了一個笨辦法,點(diǎn)擊菜單加載的組件都放在 el-tab-pane 里面礼搁,通過切換 tab 的方式顯示組件褪秀。

源碼:https://gitee.com/naturefw-code/nf-rollup-ui-controller

做一個簡單的路由

看了半天牵现,你有沒有發(fā)現(xiàn),似乎缺少了一個重要環(huán)節(jié)?

你猜對了出吹,路由的封裝還沒有介紹综膀。

這里并不想設(shè)計一個像 vue-router那樣的全能路由呻征,而是設(shè)計一個適合管理后臺的簡易路由谆级。

菜單是多級的,url 也是多級的和菜單對應(yīng)扯罐,但是路由是單級的负拟,不嵌套。

也就是說歹河,點(diǎn)擊任意一級的(樹葉)菜單掩浙,加載的都是同級的組件。

另外暫時不考慮加載組件后的路由的設(shè)置启泣。我覺得涣脚,這個可以交給加載的組件自行實(shí)現(xiàn)示辈。

import { defineAsyncComponent, reactive, watch, inject } from 'vue'

const flag = Symbol('nf-router-menu___')

/**
 * 一個簡單的路由
 * @param { string } baseUrl 基礎(chǔ)路徑
 * @param { components } home 基礎(chǔ)路徑
 * @param { array } menus 路由設(shè)置寥茫,數(shù)組,多級
 * * [{
 * * *  menuId: '菜單ID'
 * * *  naviId: '0', // 導(dǎo)航ID矾麻,可以不設(shè)置
 * * *  title: '標(biāo)題',
 * * *  path: '路徑',
 * * *  icon: Edit, // 圖標(biāo)組件
 * * *  component: () => import('./views/xxx.vue') // 要加載的組件纱耻,可以不設(shè)置
 * * *  childrens: [ // 子菜單,可以多級
 * * * *  {
 * * * * *  menuId: '菜單ID' 
 * * * * *  title: '標(biāo)題',
 * * * * *  path: '路徑',
 * * * * *  icon: Edit, // 圖標(biāo)組件
 * * * * *  component: () => import('./views/xxx.vue') // 要加載的組件
 * * * * }
 * * * ]
 * * },
 * * 其他菜單
 * * ]
 * @returns 
 */
class Router {
  constructor (info) {
    // 設(shè)置當(dāng)前選擇的路由
    this.currentRoute = reactive({
      key: 'home', // 默認(rèn)的首頁
      paths: [] // 記錄打開的多級菜單的信息 
    })
    this.baseUrl = info.baseUrl // 基礎(chǔ)路徑险耀,應(yīng)對網(wǎng)站的二級目錄
    this.baseTitle = document.title // 初始的標(biāo)題
    this.isRefresh = false // 是否刷新進(jìn)入
    this.home = info.home // 默認(rèn)的首頁
    this.menus = reactive(info.menus) // 菜單集合弄喘,數(shù)組形式,支持多級甩牺,可以設(shè)置導(dǎo)航ID
    this.menuList = {} // 變成單層的樹蘑志,便于用key查找。
    this.tabs = reactive(new Set([])) // 點(diǎn)擊過且沒有關(guān)閉的二級菜單贬派,做成動態(tài)tab標(biāo)簽
   
    this.setup()
  }

  /**
   * 初始化設(shè)置
   */
  setup = () => {
    // 監(jiān)聽當(dāng)前路由急但,設(shè)置 tabs 和標(biāo)題、url
    watch(() => this.currentRoute.key, (key) => {
      略
    })
  }

   /**
   * 添加新路由搞乏,主要是實(shí)現(xiàn)根據(jù)用戶權(quán)限加載對應(yīng)的菜單波桩。
   */
  addRoute = (newMenus, props = {}) => {
      略
  }

  /**
   * 刪除路由
   * @param { array } path 菜單的路徑,[] 表示根菜單
   * @param { string | number } id 要刪除的菜單ID
   */
  removeRoute = (path = [], id = '') => {
      略
  }

  /**
   * 刷新時依據(jù)url加載組件
   */
  refresh = () => {
     略
  }

  /**
   * 加載路由指定的組件
   * @returns 
   */
  getComponent = () => {
    if (this.currentRoute.key === '' || this.currentRoute.key === 'home') {
      return this.home
    } else {
      return this.menuList[this.currentRoute.key].component
    }
  }

  /**
   * 刪除tab
   * @param { string } key 
   * @returns 
   */
  removeTab = (key) => {
    略
  }

  /**
   * 安裝插件
   * @param {*} app 
   */
  install = (app) => {
    // 便于模板獲取
    app.config.globalProperties.$router = this
    // 便于代碼獲取
    app.provide(flag, this)
  }
}

/**
 * 創(chuàng)建簡易路由
 */
const createRouter = (info) => {
  // 創(chuàng)建路由请敦,
  const router = new Router(info)
  // 判斷url镐躲,是否需要加載組件
  setTimeout(() => {
    router.refresh()
  }, 300)
  // 使用vue的插件储玫,設(shè)置全局路由
  return router
}

/**
 * 獲取路由
 * @returns 
 */
const useRouter = () => {
  return inject(flag)
}

export {
  createRouter,
  useRouter
}

篇幅有限,這里只介紹了路由的整體結(jié)構(gòu)萤皂,具體實(shí)現(xiàn)方式可以看源碼:

源碼:https://gitee.com/naturefw-code/nf-rollup-ui-controller

菜單與權(quán)限

上面是靜態(tài)的路由和導(dǎo)航的設(shè)置方式撒穷,對于管理后臺,必備的一個需求就是裆熙,根據(jù)用戶的權(quán)限來加載路由和菜單桥滨。

所以我們提供了一個 addRoute 方法,實(shí)現(xiàn)動態(tài)添加路由的功能弛车,這樣可以等用戶登錄之后齐媒,得到用戶的權(quán)限,然后按照權(quán)限加載路由和菜單纷跛。

  const router = useRouter()

   router.addRoute([
      {
        menuId: 'dt-100',
        title: '添加根菜單',
        naviId: '0',
        path: 'new-router',
        icon: FolderOpened,
        childrens: [
          {
            menuId: '100-10',
            title: '動態(tài)菜單',
            path: 'ui',
            icon: Document,
            component: () => import('../ui/base/c-01html.vue')
          }
        ]
      }
    ], { index: 1 })

同時也可以加上權(quán)限判斷喻括。菜單是基于 el-menu 實(shí)現(xiàn)的,可以加上 select 事件贫奠,然后在事件里面判斷權(quán)限唬血,如果沒有權(quán)限可以跳轉(zhuǎn)到登錄組件。


const router = useRouter()

const myselect = (index, indexPath) => {
  // 驗(yàn)證權(quán)限唤崭,如果沒有權(quán)限拷恨,加載登錄組件
  if (沒有權(quán)限) {
    router.currentRoute.paths = ''
    router.currentRoute.key = '登錄組件的key'
  }
}

示例項(xiàng)目

https://gitee.com/naturefw-code/nf-rollup-state

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谢肾,隨后出現(xiàn)的幾起案子腕侄,更是在濱河造成了極大的恐慌,老刑警劉巖芦疏,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冕杠,死亡現(xiàn)場離奇詭異,居然都是意外死亡酸茴,警方通過查閱死者的電腦和手機(jī)分预,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薪捍,“玉大人笼痹,你說我怎么就攤上這事±掖” “怎么了凳干?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昆稿。 經(jīng)常有香客問我纺座,道長,這世上最難降的妖魔是什么溉潭? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任净响,我火速辦了婚禮少欺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘馋贤。我一直安慰自己赞别,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布配乓。 她就那樣靜靜地躺著仿滔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪犹芹。 梳的紋絲不亂的頭發(fā)上崎页,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音腰埂,去河邊找鬼飒焦。 笑死,一個胖子當(dāng)著我的面吹牛屿笼,可吹牛的內(nèi)容都是我干的牺荠。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼驴一,長吁一口氣:“原來是場噩夢啊……” “哼休雌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肝断,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤杈曲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后孝情,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鱼蝉,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洒嗤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年箫荡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渔隶。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡羔挡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出间唉,到底是詐尸還是另有隱情绞灼,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布呈野,位于F島的核電站低矮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏被冒。R本人自食惡果不足惜军掂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一轮蜕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝗锥,春花似錦跃洛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至穴张,卻和暖如春细燎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背皂甘。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工找颓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叮贩。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓击狮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親益老。 傳聞我的和親對象是個殘疾皇子彪蓬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容