最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3?封裝?Element?Plus?Menu?無限級(jí)菜單組件功能的詳細(xì)代碼

 更新時(shí)間:2022年09月17日 07:51:09   作者:程序員優(yōu)雅哥(\/同)  
本文分別使用?SFC(模板方式)和?tsx?方式對(duì)?Element?Plus?*el-menu*?組件進(jìn)行二次封裝,實(shí)現(xiàn)配置化的菜單,有了配置化的菜單,后續(xù)便可以根據(jù)路由動(dòng)態(tài)渲染菜單,對(duì)Vue3?無限級(jí)菜單組件相關(guān)知識(shí)感興趣的朋友一起看看吧

本文分別使用 SFC(模板方式)和 tsx 方式對(duì) Element Plus el-menu 組件進(jìn)行二次封裝,實(shí)現(xiàn)配置化的菜單,有了配置化的菜單,后續(xù)便可以根據(jù)路由動(dòng)態(tài)渲染菜單。

1 數(shù)據(jù)結(jié)構(gòu)定義

1.1 菜單項(xiàng)數(shù)據(jù)結(jié)構(gòu)

使用 element-plus el-menu 組件實(shí)現(xiàn)菜單,主要包括三個(gè)組件:

el-menu:整個(gè)菜單;
el-sub-menu:含有子菜單的菜單項(xiàng);
el-sub-menu:沒有子菜單的菜單項(xiàng)(最末級(jí));

結(jié)合菜單的屬性和展示效果,可以得到每個(gè)菜單項(xiàng)包括:菜單名稱、菜單圖標(biāo)、菜單唯一標(biāo)識(shí)、子菜單列表四個(gè)屬性。于是可得到菜單項(xiàng)結(jié)構(gòu)定義如下:

/**
 * 菜單項(xiàng)
 */
export interface MenuItem {
  /**
   * 菜單名稱
   */
  title: string;
  /**
   * 菜單編碼(對(duì)應(yīng) el-menu-item / el-sub-menu 組件的唯一標(biāo)識(shí) index 字段)
   */
  code: string;
  /**
   * 菜單的圖標(biāo)
   */
  icon?: string;
  /**
   * 子菜單
   */
  children?: MenuItem[]
}

傳入 MenuItem 數(shù)組,使用該數(shù)組渲染出菜單。但有時(shí)候數(shù)據(jù)字段不一定為上面結(jié)構(gòu)定義的屬性名,如 菜單名稱 字段,上面定義的屬性為 title,但實(shí)際開發(fā)過程中后端返回的是 name,此時(shí)字段名不匹配。一種處理方式是前端開發(fā)獲取到后臺(tái)返回的數(shù)據(jù)后,遍歷構(gòu)造上述結(jié)構(gòu),由于是樹形結(jié)構(gòu),遍歷起來麻煩。另一種方式是由用戶指定字段的屬性名,分別指定菜單名稱、菜單編碼等分別對(duì)應(yīng)用戶傳遞數(shù)據(jù)的什么字段。所以需要再定義一個(gè)結(jié)構(gòu),由用戶來配置字段名稱。

1.2 菜單配置數(shù)據(jù)結(jié)構(gòu)

首先定義菜單項(xiàng)字段配置的結(jié)構(gòu):

/**
 * 菜單項(xiàng)字段配置結(jié)構(gòu)
 */
export interface MenuOptions {
  title?: string;
  code?: string;
  icon?: string;
  children?: string;
}

再定義菜單項(xiàng)結(jié)構(gòu)默認(rèn)字段名:

/**
 * 菜單項(xiàng)默認(rèn)字段名稱
 */
export const defaultMenuOptions: MenuOptions = {
  title: 'title',
  code: 'code',
  icon: 'icon',
  children: 'children'
}

2 使用 tsx 實(shí)現(xiàn)封裝

2.1 tsx 基本結(jié)構(gòu)

通常使用 tsx 封裝組件的結(jié)構(gòu)如下:

import { defineComponent } from 'vue'

export default defineComponent({
  name: 'yyg-menu',

  // 屬性定義
  props: {
  },

  setup (props, context) {
    console.log(props, context)

    return () => (
      <div>yyg-menu</div>
    )
  }
})

2.2 定義 prop

首先定義兩個(gè)屬性:菜單的數(shù)據(jù)、菜單數(shù)據(jù)的字段名。

// 屬性定義
props: {
  data: {
    type: Array as PropType<MenuItem[]>,
    required: true
  },
  menuOptions: {
    type: Object as PropType<MenuOptions>,
    required: false,
    default: () => ({})
  }
},

除了上面定義的兩個(gè)屬性,el-menu 中的屬性我們也希望能夠暴露出去使用:

但 el-menu 的屬性太多,一個(gè)個(gè)定義不太現(xiàn)實(shí),在 tsx 中可以使用 context.attrs 來獲取。

context.attrs 會(huì)返回當(dāng)前組件定義的屬性之外、用戶傳入的其他屬性,也就是返回沒有在 props 中定義的屬性。

2.3 遞歸實(shí)現(xiàn)組件

在 setup 中 遞歸 實(shí)現(xiàn)菜單的無限級(jí)渲染。封裝函數(shù) renderMenu,該函數(shù)接收一個(gè)數(shù)組,遍歷數(shù)組:

  • 如果沒有子節(jié)點(diǎn),則使用 el-menu-item 渲染
  • 如果有子節(jié)點(diǎn),先使用 el-sub-menu 渲染,el-sub-menu 中的內(nèi)容又繼續(xù)調(diào)用 renderMenu 函數(shù)繼續(xù)渲染。

整個(gè)組件實(shí)現(xiàn)如下 infinite-menu.tsx:

import { DefineComponent, defineComponent, PropType } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { defaultMenuOptions, MenuItem, MenuOptions } from './types'

export default defineComponent({
  name: 'yyg-menu-tsx',

  // 屬性定義
  props: {
    data: {
      type: Array as PropType<MenuItem[]>,
      required: true
    },
    menuOptions: {
      type: Object as PropType<MenuOptions>,
      required: false,
      default: () => ({})
    }
  },

  setup (props, context) {
    console.log(props, context)

    // 合并默認(rèn)的字段配置和用戶傳入的字段配置
    const options = {
      ...defaultMenuOptions,
      ...props.menuOptions
    }

    // 渲染圖標(biāo)
    const renderIcon = (icon?: string) => {
      if (!icon) {
        return null
      }
      const IconComp = (ElementPlusIconsVue as { [key: string]: DefineComponent })[icon]
      return (
        <el-icon>
          <IconComp/>
        </el-icon>
      )
    }

    // 遞歸渲染菜單
    const renderMenu = (list: any[]) => {
      return list.map(item => {
        // 如果沒有子菜單,使用 el-menu-item 渲染菜單項(xiàng)
        if (!item[options.children!] || !item[options.children!].length) {
          return (
            <el-menu-item index={item[options.code!]}>
              {renderIcon(item[options.icon!])}
              <span>{item[options.title!]}</span>
            </el-menu-item>
          )
        }

        // 有子菜單,使用 el-sub-menu 渲染子菜單
        // el-sub-menu 的插槽(title 和 default)
        const slots = {
          title: () => (
            <>
              {renderIcon(item[options.icon!])}
              <span>{item[options.title!]}</span>
            </>
          ),
          default: () => renderMenu(item[options.children!])
        }

        return <el-sub-menu index={item[options.code!]} v-slots={slots} />
      })
    }

    return () => (
      <el-menu {...context.attrs}>
        {renderMenu(props.data)}
      </el-menu>
    )
  }
})

3 使用 SFC 實(shí)現(xiàn)菜單封裝

SFC 即 Single File Component,可以理解為 .vue 文件編寫的組件。上面使用 tsx 可以很方便使用遞歸,模板的方式就不太方便使用遞歸了,需要使用兩個(gè)組件來實(shí)現(xiàn)。

3.1 封裝菜單項(xiàng)的渲染

infinite-menu-item.vue:

<template>
  <!-- 沒有子節(jié)點(diǎn),使用 el-menu-item 渲染 -->
  <el-menu-item v-if="!item[menuOptions.children] || !item[menuOptions.children].length"
                :index="item[menuOptions.code]">
    <el-icon v-if="item[menuOptions.icon]">
      <Component :is="ElementPlusIconsVue[item[menuOptions.icon]]"/>
    </el-icon>
    <span>{{ item[menuOptions.title] }}</span>
  </el-menu-item>

  <!-- 有子節(jié)點(diǎn),使用 el-sub-menu 渲染 -->
  <el-sub-menu v-else
               :index="item[menuOptions.code]">
    <template #title>
      <el-icon v-if="item[menuOptions.icon]">
        <Component :is="ElementPlusIconsVue[item[menuOptions.icon]]"/>
      </el-icon>
      <span>{{ item[menuOptions.title] }}</span>
    </template>
    <!-- 循環(huán)渲染 -->
    <infinite-menu-item v-for="sub in item[menuOptions.children]"
                        :key="sub[menuOptions.code]"
                        :item="sub"
                        :menu-options="menuOptions"/>
  </el-sub-menu>
</template>

<script lang="ts" setup>
import { defineProps, PropType } from 'vue'
import { MenuOptions } from './types'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

defineProps({
  item: {
    type: Object,
    required: true
  },
  menuOptions: {
    type: Object as PropType<MenuOptions>,
    required: true
  }
})
</script>

<style scoped lang="scss">
</style>

3.2 封裝菜單組件

infinite-menu-sfc.vue

<template>
  <el-menu v-bind="$attrs">
    <infinite-menu-item v-for="(item, index) in data"
                        :key="index"
                        :item="item"
                        :menu-options="options"/>
  </el-menu>
</template>

<script lang="ts" setup>
import InfiniteMenuItem from './infinite-menu-item.vue'
import { defineProps, onMounted, PropType, ref } from 'vue'
import { defaultMenuOptions, MenuItem, MenuOptions } from './types'

const props = defineProps({
  data: {
    type: Array as PropType<MenuItem[]>,
    required: true
  },
  menuOptions: {
    type: Object as PropType<MenuOptions>,
    required: false,
    default: () => ({})
  }
})

const options = ref({})

onMounted(() => {
  options.value = {
    ...defaultMenuOptions,
    ...props.menuOptions
  }
})
</script>

<style scoped lang="scss">
</style>

4 測(cè)試組件

4.1 菜單測(cè)試數(shù)據(jù)

menu-mock-data.ts

export const mockData = [{
  title: '系統(tǒng)管理',
  id: 'sys',
  logo: 'Menu',
  children: [{
    title: '權(quán)限管理',
    id: 'permission',
    logo: 'User',
    children: [
      { title: '角色管理', id: 'role', logo: 'User' },
      { title: '資源管理', id: 'res', logo: 'User' }
    ]
  }, {
    title: '字典管理', id: 'dict', logo: 'User'
  }]
}, {
  title: '營(yíng)銷管理', id: '2', logo: 'Menu'
}, {
  title: '測(cè)試',
  id: 'test',
  logo: 'Menu',
  children: [{
    title: '測(cè)試-1',
    id: 'test-1',
    logo: 'Document',
    children: [{ title: '測(cè)試-1-1', id: 'test-1-1', logo: 'Document', children: [{ title: '測(cè)試-1-1-1', id: 'test-1-1-1', logo: 'Document' }]}, { title: '測(cè)試-1-2', id: 'test-1-2', logo: 'Document' }]
  }]
}]

4.2 測(cè)試頁(yè)面

<template>
  <div class="menu-demo">
    <div>
      <h3>tsx</h3>
      <yyg-infinite-menu-tsx
        :data="mockData"
        active-text-color="red"
        default-active="1"
        :menu-options="menuOptions"/>
    </div>

    <div>
      <h3>sfc</h3>
      <yyg-infinite-menu-sfc
        :data="mockData"
        active-text-color="red"
        default-active="1"
        :menu-options="menuOptions"/>
    </div>
  </div>
</template>

<script lang="ts" setup>
import YygInfiniteMenuTsx from '@/components/infinite-menu'
import YygInfiniteMenuSfc from '@/components/infinite-menu-sfc.vue'
import { mockData } from '@/views/data/menu-mock-data'

const menuOptions = { title: 'title', code: 'id', icon: 'logo' }
</script>

<style scoped lang="scss">
.menu-demo {
  display: flex;

  > div {
    width: 250px;
    margin-right: 30px;
  }
}
</style>

4.3 運(yùn)行效果

總結(jié):

  • 在之前的文章中有讀者問我為什么要使用 tsx,從這個(gè)例子可以看出,如果控制流程復(fù)雜或有遞歸等操作時(shí),tsx 會(huì)比 sfc 更容易實(shí)現(xiàn);
  • tsx 和 sfc 中動(dòng)態(tài)組件的使用;
  • tsx 中的 context.attrs 和 sfc 中的 v-bind="$attrs" 的使用。

到此這篇關(guān)于Vue3 封裝 Element Plus Menu 無限級(jí)菜單組件的文章就介紹到這了,更多相關(guān)Vue3 無限級(jí)菜單組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 自定義vue全局組件use使用、vuex的使用詳解

    自定義vue全局組件use使用、vuex的使用詳解

    本篇文章主要介紹了自定義vue全局組件use使用、vuex的使用詳解,本文主要來講解一下怎么樣定義一個(gè)全局組件,并解釋vue.use()的原理
    2017-06-06
  • 2分鐘實(shí)現(xiàn)一個(gè)Vue實(shí)時(shí)直播系統(tǒng)的示例代碼

    2分鐘實(shí)現(xiàn)一個(gè)Vue實(shí)時(shí)直播系統(tǒng)的示例代碼

    這篇文章主要介紹了2分鐘實(shí)現(xiàn)一個(gè)Vue實(shí)時(shí)直播系統(tǒng)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • vue調(diào)用原生方法交互解讀

    vue調(diào)用原生方法交互解讀

    這篇文章主要介紹了vue調(diào)用原生方法交互,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • vue-router 基于后端permissions動(dòng)態(tài)生成導(dǎo)航菜單的示例代碼

    vue-router 基于后端permissions動(dòng)態(tài)生成導(dǎo)航菜單的示例代碼

    本文主要介紹了vue-router 基于后端permissions動(dòng)態(tài)生成導(dǎo)航菜單的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • vue引入iconfont圖標(biāo)庫(kù)的優(yōu)雅實(shí)戰(zhàn)記錄

    vue引入iconfont圖標(biāo)庫(kù)的優(yōu)雅實(shí)戰(zhàn)記錄

    使用組件庫(kù)時(shí),圖標(biāo)往往不能滿足需求,所以我們常常需要用到第三方圖標(biāo)庫(kù),這篇文章主要給大家介紹了關(guān)于vue引入iconfont的相關(guān)資料,需要的朋友可以參考下
    2021-06-06
  • Vue項(xiàng)目history模式下微信分享爬坑總結(jié)

    Vue項(xiàng)目history模式下微信分享爬坑總結(jié)

    這篇文章主要介紹了Vue項(xiàng)目history模式下微信分享爬坑總結(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-03-03
  • Vue Cli與BootStrap結(jié)合實(shí)現(xiàn)表格分頁(yè)功能

    Vue Cli與BootStrap結(jié)合實(shí)現(xiàn)表格分頁(yè)功能

    這篇文章主要介紹了Vue Cli與BootStrap結(jié)合實(shí)現(xiàn)表格分頁(yè)功能,需要的朋友可以參考下
    2017-08-08
  • el-table渲染慢卡頓問題最優(yōu)解決方案

    el-table渲染慢卡頓問題最優(yōu)解決方案

    本文主要介紹了el-table渲染慢卡頓問題最優(yōu)解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • vue3+ts使用Echarts的實(shí)例詳解

    vue3+ts使用Echarts的實(shí)例詳解

    這篇文章主要介紹了vue3+ts使用Echarts的實(shí)例詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • vue中defineProperty和Proxy的區(qū)別詳解

    vue中defineProperty和Proxy的區(qū)別詳解

    這篇文章主要介紹了vue中defineProperty和Proxy的區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11

最新評(píng)論

什邡市| 辰溪县| 增城市| 怀柔区| 濮阳市| 南郑县| 金山区| 彩票| 榕江县| 九江县| 井陉县| 泸水县| 措美县| 阿瓦提县| 休宁县| 秦皇岛市| 阜康市| 田林县| 合水县| 百色市| 高尔夫| 扎囊县| 循化| 甘谷县| 江都市| 莎车县| 永新县| 泽库县| 开封县| 田阳县| 浪卡子县| 建阳市| 无极县| 区。| 南投市| 陆丰市| 封丘县| 白水县| 商河县| 嘉黎县| 台北市|