做一個管理后臺挎袜,首先要設(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}}
<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'
}
}