目錄
- 系列文章
- 一、先說點什麼
- 二、從問題開始,側邊欄是幹啥的?
- 三、封裝元件之思路分析
- 四、封裝菜單部分
-
- 1. MenuItem
- 2. ModuleMenu
系列文章
- vue3.0+ts+element-plus多頁簽應用模闆:前言
- vue3.0+ts+element-plus多頁簽應用模闆:項目搭建
- vue3.0+ts+element-plus多頁簽應用模闆:vue-router與vuex
- vue3.0+ts+element-plus多頁簽應用模闆:element-plus按需引入與動态換膚
- vue3.0+ts+element-plus多頁簽應用模闆:如何優雅地使用Svg圖示
- vue3.0+ts+element-plus多頁簽應用模闆:側邊導航菜單(上)
- vue3.0+ts+element-plus多頁簽應用模闆:側邊導航菜單(下)
- vue3.0+ts+element-plus多頁簽應用模闆:多級路由緩存
- vue3.0+ts+element-plus多頁簽應用模闆:頭部工具欄(上)
- vue3.0+ts+element-plus多頁簽應用模闆:頭部工具欄(中)
- vue3.0+ts+element-plus多頁簽應用模闆:頭部工具欄(下)
一、先說點什麼
從這一章開始,文章描述的重點,将會從實際代碼轉移到思路講述,貼出的代碼隻是關鍵部分。俗話說授之以魚不如授之以漁,要想學會一樣東西,打通思路是最重要的。就和學會武功,要打通任督二脈是一個道理。好的,那麼我們就進入側邊欄的實作吧。
二、從問題開始,側邊欄是幹啥的?
這個問題問得好!我們在做一個東西之前,首先要了解這個東西有啥用,你光圖好看有逼格那屬于浪費精力。是以,我們必須要知道我們為什麼要去做側邊欄。那麼側邊欄是幹啥的呢?它的核心功能隻有一個,那就是路由跳轉。使用者可以通過這樣一個側邊導航,很輕松的進入一個又一個頁面,這就是它最本質的功能。
是以,有了這個思路,我們就知道,我們需要将路由配置與這個元件聯系起來,每當我們改動路由配置的時候,它就會自動生成菜單項。好的,既然說到了菜單項,那我們就先來研究一下,每個菜單項應該包括一些什麼東西:
- key:每個菜單項的唯一辨別,最好是其名稱的英文翻譯
- type:菜單項的類别(可包含子級菜單項的下拉菜單、可包含子級菜單項的分組菜單和普通子級菜單項)
- label:菜單項名稱
- icon:svg圖示名
- hidden:該路由是否顯示到菜單中
- roles:能夠通路該路由的賬号角色,可以是字元串或字元串數組
- disabled:是否已禁用狀态展示該菜單項
- children:其下包含的子級菜單項
- onClick:點選目前路由會發生什麼(一般情況下是在系統内跳轉路由,但有時候也需要幹點别的)
這樣就分析好了每個菜單項的屬性了,現在讓我們來與路由配置項對應一下:
{
key: name, // 菜單項的唯一辨別對應路由名
type: meta.type, // 除了children之外的都可以放進meta中
label: meta.label,
icon: meta.icon,
hidden: meta.hidden,
roles: meta.roles,
disabled: meta.disabled,
onClick: meta.onClick
children // 子級菜單項對應子路由
}
由此可見,二者可以非常完美的對應起來,那麼根據這個思路,我們就可以開始封裝元件了。
三、封裝元件之思路分析
初步分析一下,整個元件裡面都有些啥:
- 一個整體:AsideBar
- 頭部下面的菜單:ModuleMenu
- 菜單裡的菜單項:MenuItem
- 頭部Logo與标題:SystemLogo
總結一下:
- 建立一個
作為菜單項,将路由配置項轉換為菜單配置項,并且要分三種不同的類别,其中submenu和group兩類中需要遞歸調用自身MenuItem.vue
- 建立一個
作為所有菜單項的容器,負責将路由配置傳遞給ModuleMenu.vue
MenuItem.vue
- 建立一個
作為頭部的容器,展示圖示和系統名SystemLogo.vue
- 建立一個
作為一個整體的容器,讀取路由配置資訊并傳遞給AsideBar.vue
,同時需要接收外部傳來的collapse(控制自身的折疊與展開)、iconName(logo圖示名)、iconColor(logo顔色)、systemName(系統名)ModuleMenu.vue
好的,整體分析完成。下面我們來一個一個的去實作吧。
四、封裝菜單部分
1. MenuItem
我們先來看一下MenuItem的template部分:
<template>
<!-- menuItems是經過轉換的菜單項清單 -->
<template v-for="item in menuItems" :key="item.key">
<!-- 通過hidden和roles字段判斷是否顯示目前菜單項 -->
<template v-if="getItemShow(item)">
<!-- v-if submenu -->
<el-submenu v-if="item.type === 'submenu'" :index="item.key" :disabled="item.disabled">
<template #title>
<svg-icon v-if="item.icon" :name="item.icon" size="xl" class="item-icon" />
<span>{{ item.label }}</span>
</template>
<!-- 遞歸調用自身,渲染子級菜單項,下面的group也是一樣 -->
<template v-if="item.children">
<menu-item :route-list="item.children" />
</template>
</el-submenu>
<!-- v-if group -->
<el-menu-item-group v-if="item.type === 'group'">
<template #title>
<span>{{ item.label }}</span>
</template>
<template v-if="item.children">
<menu-item :route-list="item.children" />
</template>
</el-menu-item-group>
<!-- v-if item -->
<el-menu-item
v-if="item.type === 'item'"
:index="item.key"
@click="onMenuItemClick($event, item)"
:disabled="item.disabled"
>
<svg-icon v-if="item.icon" :name="item.icon" size="xl" class="item-icon" />
<span>{{ item.label }}</span>
</el-menu-item>
</template>
</template>
</template>
上面SvgIcon元件的實作請看同系列文章:如何優雅地使用Svg圖示,然後看一下script部分:
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { RouteRecordRaw, useRouter } from 'vue-router'
import { MenuItemProps } from '../../typings'
import Variables from '@/assets/styles/variables.scss'
export default defineComponent({
name: 'MenuItem',
props: {
routeList: {
type: Array as PropType<RouteRecordRaw[]>,
required: true
}
},
emits: ['itemClick'],
setup(props, context) {
const router = useRouter()
// 處理菜單項的點選事件
const onMenuItemClick = (event: any, item: MenuItemProps) => {
// 處理onClick函數
if (item.onClick) {
// 把MouseEvent和菜單項本身傳回去
item.onClick(event, item)
// 觸發一個點選事件給父元件,用于再不進行路由跳轉時,取消菜單項的激活狀态
context.emit('itemClick', event, item)
return
}
// 沒有onClick函數的時候,進行路由跳轉
router.push({ name: item.key })
}
// 将路由配置轉換成菜單配置
const menuItems = computed(() => {
return props.routeList.map((item) => {
let temp: MenuItemProps = { key: item.name as string }
if (item.meta) {
temp = { ...temp, ...item.meta }
}
if (item.children) {
temp.children = item.children
}
return temp
})
})
// 通過hidden和roles字段判斷是否顯示目前菜單項
const getItemShow = computed(() => (item: MenuItemProps) => {
/**
* 這裡暫時不寫關于使用者權限控制的邏輯了,因為我也不知道你們會怎麼存儲使用者的role字段
* 權限控制思路:
* 1. 假設你的role字段存在了store.userModule.role中
* 2. 使用instanceof判斷一下menuitem中的roles是數組還是字元串
* 3. 如果是字元串:return store.userModule.role === item.roles
* 4. 如果是數組:return item.roles.includes(store.userModule.role)
* 5. 但是要注意修改一下分支結構,因為item.hidden的優先級在item.roles前面
*/
return !item.hidden
})
return {
menuItems,
Variables,
onMenuItemClick,
getItemShow
}
}
})
</script>
2. ModuleMenu
template部分:
<template>
<el-menu
:collapse="collapse"
:collapse-transition="false"
:background-color="Variables.ASIDE_BAR_BG_COLOR"
:text-color="Variables.ASIDE_BAR_COLOR"
:active-text-color="Variables.ASIDE_BAR_ACTIVE_COLOR"
:default-active="$route.name"
:key="menuKey"
class="asideBar-menu"
>
<menu-item :route-list="routeList" @item-click="onItemClick" />
</el-menu>
</template>
這個元件裡隻是簡單的引用了一下ElMenu元件以及我們剛剛封裝好的MenuItem元件。然後看一下script部分:
<script lang="ts">
import { defineComponent, PropType, ref } from 'vue'
import { MenuItemProps } from '../../typings'
import Variables from '@/assets/styles/variables.scss'
import Utils from '@/utils'
import MenuItem from '../MenuItem'
export default defineComponent({
name: 'ModuleMenu',
components: {
MenuItem
},
props: {
collapse: {
type: Boolean,
required: true
},
routeList: {
type: Array as PropType<MenuItemProps[]>,
required: true
}
},
setup() {
/**
* 定義key,并監聽菜單項點選事件,也就是每當菜單項的onClick函數觸發的時候,
* 都會強制重新整理ElMenu元件,達到取消目标路由激活狀态的目的
*/
const menuKey = ref(Utils.uuid())
const onItemClick = () => (menuKey.value = Utils.uuid())
return {
Variables,
menuKey,
onItemClick
}
}
})
</script>
至此,前兩步的主要内容就講述完了,後面的内容且聽下回分解。
下一篇預告:vue3.0+ts+element-plus多頁簽應用模闆:側邊導航菜單(下)