天天看點

vue3.0+ts+element-plus多頁簽應用模闆:側邊導航菜單(上)系列文章一、先說點什麼二、從問題開始,側邊欄是幹啥的?三、封裝元件之思路分析四、封裝菜單部分

目錄

  • 系列文章
  • 一、先說點什麼
  • 二、從問題開始,側邊欄是幹啥的?
  • 三、封裝元件之思路分析
  • 四、封裝菜單部分
    • 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

總結一下:

  1. 建立一個

    MenuItem.vue

    作為菜單項,将路由配置項轉換為菜單配置項,并且要分三種不同的類别,其中submenu和group兩類中需要遞歸調用自身
  2. 建立一個

    ModuleMenu.vue

    作為所有菜單項的容器,負責将路由配置傳遞給

    MenuItem.vue

  3. 建立一個

    SystemLogo.vue

    作為頭部的容器,展示圖示和系統名
  4. 建立一個

    AsideBar.vue

    作為一個整體的容器,讀取路由配置資訊并傳遞給

    ModuleMenu.vue

    ,同時需要接收外部傳來的collapse(控制自身的折疊與展開)、iconName(logo圖示名)、iconColor(logo顔色)、systemName(系統名)

好的,整體分析完成。下面我們來一個一個的去實作吧。

四、封裝菜單部分

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多頁簽應用模闆:側邊導航菜單(下)