【Vue3+Vite+TS】8.0 組件六:導航菜單

必備UI組件

將用到的組件:
Menu 菜單

組件設計

新建src\components\baseline\menu\src\index.vue

<template>
    <div>Menu</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { MenuItem } from './types'

const props = defineProps({
    //說明:
    data: {
        required: true,
        type: Array as PropType<MenuItem[]>,
    },
})
console.log('data:', props.data)
</script>
<style lang="scss" scoped></style>

新建src\components\baseline\menu\src\types.ts

export interface MenuItem {
    //導航菜單的圖標
    icon?: string
    //導航菜單的名字
    name: string
    //導航菜單的標識
    code: string
    //子菜單
    children?: MenuItem[]
}

新建src\components\baseline\menu\index.ts

import { App } from 'vue'
import Menu from './src/index.vue'

export { Menu }

//組件可通過use的形式使用
export default {
    Menu,
    install(app: App) {
        app.component('bs-menu', Menu)
    },
}

修改src\components\baseline\index.ts

import { App } from 'vue'
import ChooseArea from './chooseArea'
import ChooseIcon from './chooseIcon'
import Container from './container'
import Trend from './trend'
import Notification from './notification'
import List from './list'
import Menu from './menu'
const components = [
    ChooseArea,
    ChooseIcon,
    Container,
    Trend,
    Notification,
    List,
    Menu,
]
export { ChooseArea, ChooseIcon, Container, Trend, Notification, List, Menu }

//組件可通過use的形式使用
export default {
    install(app: App) {
        components.map(item => {
            app.use(item)
        })
    },
    ChooseArea,
    ChooseIcon,
    Container,
    Trend,
    Notification,
    List,
    Menu,
}

修改src\router\index.ts,新增路由參數(shù)

......
      {
                path: '/menu',
                component: () =>
                    import('../views/baseline/menu/index.vue'),
            },
......

新建src\views\baseline\menu\index.vue

<template>
    <div><bs-menu :data="data"></bs-menu></div>
</template>
<script lang="ts" setup>
let data = [
    { name: '首頁', code: '1', icon: 'el-icon-document' },
    { name: '圖標選擇器', code: '2', icon: 'el-icon-document' },
    {
        name: '省市區(qū)選擇組件',
        code: '3',
        icon: 'el-icon-document',
        children: [
            { name: '省市選擇組件', code: '3-1', icon: 'el-icon-document' },
            { name: '省市區(qū)村選擇組件', code: '3-2', icon: 'el-icon-document' },
        ],
    },
]
</script>
<style lang="scss" scoped></style>

如下,可見數(shù)據(jù)已經(jīng)傳達到基礎組件:

image.png

完善組件

首先實現(xiàn)一級菜單术裸。
修改src\components\baseline\menu\src\index.vue

<template>
    <div>
        <el-menu>
            <template v-for="(item, index) in data" :key="index">
                <div>
                    <el-menu-item
                        v-if="!item.children || !item.children.length"
                        :index="item.code"
                    >
                        <component v-if="item.icon" :is="item.icon"></component>
                        <span>{{ item.name }}</span>
                    </el-menu-item>
                </div>
            </template>
        </el-menu>
    </div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { MenuItem } from './types'

const props = defineProps({
    //說明:
    data: {
        required: true,
        type: Array as PropType<MenuItem[]>,
    },
})
console.log('data:', props.data)
</script>
<style lang="scss" scoped></style>

修改src\views\baseline\menu\index.vue

<template>
    <div style="width: 2rem"><bs-menu :data="data"></bs-menu></div>
</template>
<script lang="ts" setup>
let data = [
    { name: '首頁', code: '1', icon: 'el-icon-document' },
    { name: '圖標選擇器', code: '2', icon: 'el-icon-document' },
    {
        name: '省市區(qū)選擇組件',
        code: '3',
        icon: 'el-icon-document',
        children: [
            { name: '省市選擇組件', code: '3-1', icon: 'el-icon-document' },
            { name: '省市區(qū)村選擇組件', code: '3-2', icon: 'el-icon-document' },
        ],
    },
]
</script>
<style lang="scss" scoped></style>

效果如下:


image.png

接下來實現(xiàn)多級菜單
修改src\components\baseline\menu\src\types.ts

export interface MenuItem {
    //導航菜單的圖標
    icon?: string
    //導航菜單的名字
    name: string
    //導航菜單的標識
    index: string
    //子菜單
    children?: MenuItem[]
}

修改src\components\baseline\menu\src\index.vue

<template>
    <div>
        <el-menu
            :default-active="defaultActive"
            :router="router"
            v-bind="$attrs"
        >
            <template v-for="(item, index) in data" :key="index">
                <div>
                    <!-- 一級無二級菜單的菜單欄 -->
                    <el-menu-item
                        v-if="!item.children || !item.children.length"
                        :index="item.index"
                    >
                        <component v-if="item.icon" :is="item.icon"></component>
                        <span>{{ item.name }}</span>
                    </el-menu-item>
                    <el-sub-menu
                        v-if="item.children && item.children.length"
                        :index="item.index"
                    >
                        <template #title>
                            <component
                                v-if="item.icon"
                                :is="item.icon"
                            ></component>
                            <span>{{ item.name }}</span>
                        </template>
                        <!-- 二級菜單欄 -->
                        <el-menu-item
                            v-for="(item2, index2) in item.children"
                            :index="item2.index"
                        >
                            <component
                                v-if="item2.icon"
                                :is="item2.icon"
                            ></component>
                            <span>{{ item2.name }}</span>
                        </el-menu-item>
                    </el-sub-menu>
                </div>
            </template>
        </el-menu>
    </div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { MenuItem } from './types'

const props = defineProps({
    //說明:
    data: {
        required: true,
        type: Array as PropType<MenuItem[]>,
    },
    // 默認選中的菜單
    defaultActive: {
        type: String,
        default: '',
    },
    //是否是路由模式 router,
    //是否啟用 vue-router 模式
    //啟用該模式會在激活導航時以 index 作為 path 進行路由跳轉
    router: {
        type: Boolean,
        default: false,
    },
})
// console.log('data:', props.data)
</script>
<style lang="scss" scoped>
svg {
    margin-right: 0.04rem;
}
</style>

修改src\views\baseline\menu\index.vue

<template>
    <div style="width: 2rem">
        <bs-menu :data="data" defaultActive="3-2"></bs-menu>
    </div>
</template>
<script lang="ts" setup>
let data = [
    { name: '首頁', index: '1', icon: 'el-icon-document' },
    { name: '圖標選擇器', index: '2', icon: 'el-icon-document' },
    {
        name: '行政區(qū)域選擇組件',
        index: '3',
        icon: 'el-icon-document',
        children: [
            { name: '省市選擇組件', index: '3-1', icon: 'el-icon-document' },
            { name: '省市區(qū)選擇組件', index: '3-2', icon: 'el-icon-document' },
            {
                name: '省市區(qū)村選擇組件',
                index: '3-3',
                icon: 'el-icon-document',
            },
        ],
    },
    0,
]
</script>
<style lang="scss" scoped></style>

實現(xiàn)效果如下:


image.png

v-bind="$attrs":接受父組件傳入的數(shù)據(jù)和方法额嘿,并排除在組件的props中響應的參數(shù)铃在。
具體可以參考: vue中使用v-bind="$attrs"和v-on="$listeners"進行多層組件監(jiān)聽

TSX實現(xiàn)無限層級的導航菜單

首先需要安裝插件儿咱。

npm i -D @vitejs/plugin-vue-jsx

修改vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
//******************* Element自動導入  *******************
//目前語言包存在報錯颁独,無法自動導出打包翘单,暫時注釋
// import AutoImport from 'unplugin-auto-import/vite'
// import Components from 'unplugin-vue-components/vite'
// import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
//******************* Element自動導入  *******************

//*******************  rollup 打包體積分析插件可視化工具  *******************
import { visualizer } from 'rollup-plugin-visualizer'
//*******************  rollup 打包體積分析插件可視化工具  *******************
// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        vueJsx(),
        //******************* Element自動導入  *******************
        // AutoImport({
        //     resolvers: [ElementPlusResolver()],
        // }),
        // Components({
        //     resolvers: [ElementPlusResolver()],
        // }),
        //******************* Element自動導入  *******************

        //******************* 打包插件可視化工具  *******************
        visualizer(),
        //******************* 打包插件可視化工具  *******************
    ],
    resolve: {
        alias: {
            '@': '/src',
            '@style': '/src/style',
            '@com': '/src/components',
            '@baseline': '/src/components/baseline',
            '@business': '/src/components/business',
        },
    },
    server: {
        port: 8080,
    },
})

這里也順便提一下tsconfig.json的配置:

{
    // 指定要編譯的路徑列表
    "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
    "compilerOptions": {
        // target用于指定編譯之后的版本目錄
        "target": "esnext",
        "useDefineForClassFields": true,
        //module用來指定要使用的模板標準
        "module": "esnext",
        // 用于選擇模塊解析策略梯皿,有"node"和"classic"兩種類型
        "moduleResolution": "node",
        "strict": true,
        // 指定jsx代碼用于的開發(fā)環(huán)境:'preserve','react-native',or 'react
        "jsx": "preserve",
        // 指定是否將map文件內(nèi)容和js文件編譯在一個同一個js文件中
        // 如果設為true,則map的內(nèi)容會以//#soureMappingURL=開頭,然后接base64字符串的形式插入在js文件底部
        "sourceMap": true,
        "skipLibCheck": true,
        "resolveJsonModule": true,
        //通過導入內(nèi)容創(chuàng)建命名空間县恕,實現(xiàn)CommonJS和ES模塊之間的互操作性
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        // 指定是否引入tslib里的復制工具函數(shù)东羹,默認為false
        "importHelpers": true,
        // 指定是否將每個文件作為單獨的模塊,默認為true忠烛,他不可以和declaration同時設定
        "isolatedModules": true,
        // removeComments用于指定是否將編譯后的文件注釋刪掉属提,設為true的話即刪除注釋,默認為false
        "removeComments": true,
        // lib用于指定要包含在編譯中的庫文件
        "lib": ["esnext", "dom"],
        //指定全局組件類型:element:"element-plus/global"
        "types": ["vite/client", "element-plus/global"],
        // ++ 這里加上baseUrl 和 path即可 ++
        // 用于設置解析非相對模塊名稱的基本目錄美尸,相對模塊不會受到baseUrl的影響
        "baseUrl": "./",
        //用于設置模塊名到基于baseUrl的路徑映射
        "paths": {
            // 根據(jù)別名配置相關路徑
            "@/*": ["./src/*"],
            "@style/*": ["./src/style/*"],
            "@com/*": ["./src/components/*"],
            "@baseline/*": ["./src/components/baseline/*"],
            "@business/*": ["./src/components/business/*"]
        }
        //****************** 未配置選項 ******************
        // 用來指定是否允許編譯JS文件冤议,默認false,即不編譯JS文件
        // "allowJs": false,
        // 用來指定是否在編譯的時候生成相的d.ts聲明文件
        // 如果設為true,編譯每個ts文件之后會生成一個js文件和一個聲明文件,但是declaration和allowJs不能同時設為true
        // "declaration": true,
        // 用來指定編譯時是否生成.map文件
        // "declarationMap": true,
        // 用來指定輸出文件夾师坎,值為一個文件夾路徑字符串恕酸,輸出的文件都將放置在這個文件夾
        // "outDir": "./",
        // 用來指定輸出文件夾,值為一個文件夾路徑字符串胯陋,輸出的文件都將放置在這個文件夾
        // "outDir": "./",
        // 用于指定輸出文件合并為一個文件
        //只有設置module的值為amd和system模塊時才支持這個配置
        // "outFile": "./",
        // 是否編譯構建引用項目
        // "composite": true,
        // 不生成編譯文件
        // "noEmit": true,
        // 當target為"ES5"或"ES3"時蕊温,為"for-of" "spread"和"destructuring"中的迭代器提供完全支持
        // "downlevelIteration": true,
        // 用于指定是否啟動所有類型檢查袱箱,如果設為true這回同時開啟下面這幾個嚴格檢查,默認為false
        // "strict": true,
        // 如果我們沒有一些值設置明確類型义矛,編譯器會默認認為這個值為any類型发笔,如果將noImplicitAny設為true,則如果沒有設置明確的類型會報錯,默認值為false
        // "noImplicitAny": true,
        // 當設為true時凉翻,null和undefined值不能賦值給非這兩種類型的值了讨,別的類型的值也不能賦給他們,除了any類型制轰,還有個例外就是undefined可以賦值給void類型
        // "strictNullChecks": true,
        // 用來指定是否使用函數(shù)參數(shù)雙向協(xié)變檢查
        // "strictFunctionTypes": true,
        // 設為true后對bind前计、call和apply綁定的方法的參數(shù)的檢測是嚴格檢測
        // "strictBindCallApply": true,
        // 設為true后會檢查類的非undefined屬性是否已經(jīng)在構造函數(shù)里初始化,如果要開啟這項垃杖,需要同時開啟strictNullChecks,默認為false
        // "strictPropertyInitialization": true,
        // 當this表達式的值為any類型的時候残炮,生成一個錯誤
        // "noImplicitThis": true,
        // alwaysStrict指定始終以嚴格模式檢查每個模塊,并且在編譯之后的JS文件中加入"use strict"字符串缩滨,用來告訴瀏覽器該JS為嚴格模式
        // "alwaysStrict": true,
        // 用于檢查是否有定義了但是沒有使用變量势就,對于這一點的檢測,使用ESLint可以在你書寫代碼的時候做提示脉漏,你可以配合使用苞冯,他的默認值為false
        // "noUnusedLocals": true,
        // 用于檢測是否在函數(shù)中沒有使用的參數(shù)
        // "noUnusedParameters": true,
        // 用于檢查函數(shù)是否有返回值,設為true后侧巨,如果函數(shù)沒有返回值則會提示舅锄,默認為false
        // "noImplicitReturns": true,
        // 用于檢查switch中是否有case沒有使用break跳出switch,默認為false
        // "noFallthroughCasesInSwitch": true,
        // 可以指定一個路徑列表,在構建時編譯器會將這個路徑中的內(nèi)容都放到一個文件夾中
        // "rootDirs": [],
        // 用來指定聲明文件或文件夾的路徑列表司忱,如果指定了此項皇忿,則只有在這里列出的聲明文件才會被加載
        // "typeRoots": [],
        // types用于指定需要包含的模塊,只有在這里列出的模塊的聲明文件才會被加載
        // "types": [],
        // 用來指定允許從沒有默認導出的模塊中默認導入
        // "allowSyntheticDefaultImports": true,
        // 不把符號鏈接解析為真實路徑坦仍,具體可以了解下webpack和node.js的symlink相關知識
        // "preserveSymlinks": true,
        // 用于指定調(diào)試器應該找到TypeScript文件而不是源文件的位置鳍烁,這個值會被寫進.map文件里
        // "sourceRoot": "",
        // 用于指定調(diào)試器找到映射文件而非生成文件的位置,指定map文件的根路徑
        // 該選項會影響.map文件中的sources屬性
        // "mapRoot": "",
        // s用于指定是否進一步將ts文件的內(nèi)容也包含到輸出文件中
        // "inlineSources": true,
        // 用于指定是否啟用實驗性的裝飾器特性
        // "experimentalDecorators": true,
        // 用于指定是否為裝上去提供元數(shù)據(jù)支持
        // 關于元數(shù)據(jù)繁扎,也是ES6的新標準幔荒,可以通過Reflect提供的靜態(tài)方法獲取元數(shù)據(jù)
        // 如果需要使用Reflect的一些方法,需要引用ES2015.Reflect這個庫
        // "emitDecoratorMetadata": true,
        // 可以配置一個數(shù)組列表
        // "files":[],
        // exclude表示要排除的梳玫,不編譯的文件
        // "exclude":[]
        // include也可以指定要編譯的路徑列表
        // "include":[],
        //它也可以指定一個列表爹梁,規(guī)則和include一樣,可以是文件可以是文件夾提澎,可以是相對路徑或絕對路徑姚垃,可以使用通配符
        // "exclude":[]
        // 可以通過指定一個其他的tsconfig.json文件路徑,來繼承這個配置文件里的配置盼忌,繼承來的文件的配置會覆蓋當前文件定義的配置
        // "extends":""
        // 如果設為true,在我們編輯了項目文件保存的時候积糯,編輯器會根據(jù)tsconfig.json的配置更新重新生成文本掂墓,不過這個編輯器支持
        // "compileOnSave":true
        // 一個對象數(shù)組,指定要引用的項目
        // "references":[]
        //****************** 未配置選項 ******************
    }
}

新建src\components\baseline\menu\src\menu.tsx

import { defineComponent, PropType } from 'vue'
import { MenuItem } from './types'

export default defineComponent({
    name: 'infiniteMenu',
    props: {
        //說明:
        data: {
            required: true,
            type: Array as PropType<MenuItem[]>,
        },
        // 默認選中的菜單
        defaultActive: {
            type: String,
            default: '',
        },
        //是否是路由模式 router,
        //是否啟用 vue-router 模式
        //啟用該模式會在激活導航時以 index 作為 path 進行路由跳轉
        router: {
            type: Boolean,
            default: false,
        },
    },
    setup(props, ctx) {
        return () => {
            return <div>menus</div>
        }
    },
})

修改src\views\baseline\menu\index.vue

<template>
    <div style="width: 2rem">
        <!-- <bs-menu :data="data" defaultActive="3-2"></bs-menu> -->
        <bs-infinite-menu :data="data2"></bs-infinite-menu>
    </div>
</template>
<script lang="ts" setup>
let data = [
    { name: '首頁', index: '1', icon: 'el-icon-document' },
    { name: '圖標選擇器', index: '2', icon: 'el-icon-document' },
    {
        name: '行政區(qū)域選擇組件',
        index: '3',
        icon: 'el-icon-document',
        children: [
            { name: '省市選擇組件', index: '3-1', icon: 'el-icon-document' },
            { name: '省市區(qū)選擇組件', index: '3-2', icon: 'el-icon-document' },
            {
                name: '省市區(qū)村選擇組件',
                index: '3-3',
                icon: 'el-icon-document',
            },
        ],
    },
    0,
]
let data2 = [
    { name: '首頁', index: '1', icon: 'el-icon-document' },
    { name: '圖標選擇器', index: '2', icon: 'el-icon-document' },
    {
        name: '行政區(qū)域選擇組件',
        index: '3',
        icon: 'el-icon-document',
        children: [
            {
                name: '省市選擇組件',
                index: '3-1',
                icon: 'el-icon-document',
                children: [
                    {
                        name: '組件演示',
                        index: '3-1-1',
                        icon: 'el-icon-document',
                    },
                ],
            },
            { name: '省市區(qū)選擇組件', index: '3-2', icon: 'el-icon-document' },
            {
                name: '省市區(qū)村選擇組件',
                index: '3-3',
                icon: 'el-icon-document',
            },
        ],
    },
    0,
]
</script>
<style lang="scss" scoped></style>
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末絮宁,一起剝皮案震驚了整個濱河市梆暮,隨后出現(xiàn)的幾起案子服协,更是在濱河造成了極大的恐慌绍昂,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偿荷,死亡現(xiàn)場離奇詭異窘游,居然都是意外死亡,警方通過查閱死者的電腦和手機跳纳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門忍饰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寺庄,你說我怎么就攤上這事艾蓝。” “怎么了斗塘?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵赢织,是天一觀的道長。 經(jīng)常有香客問我馍盟,道長于置,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任贞岭,我火速辦了婚禮八毯,結果婚禮上,老公的妹妹穿的比我還像新娘瞄桨。我一直安慰自己话速,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布芯侥。 她就那樣靜靜地躺著尿孔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筹麸。 梳的紋絲不亂的頭發(fā)上活合,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音物赶,去河邊找鬼白指。 笑死,一個胖子當著我的面吹牛酵紫,可吹牛的內(nèi)容都是我干的告嘲。 我是一名探鬼主播错维,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼橄唬!你這毒婦竟也來了赋焕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤仰楚,失蹤者是張志新(化名)和其女友劉穎隆判,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僧界,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡侨嘀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捂襟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咬腕。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖葬荷,靈堂內(nèi)的尸體忽然破棺而出涨共,到底是詐尸還是另有隱情,我是刑警寧澤宠漩,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布举反,位于F島的核電站,受9級特大地震影響哄孤,放射性物質發(fā)生泄漏照筑。R本人自食惡果不足惜喳魏,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一弛秋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧阔涉,春花似錦晨逝、人聲如沸蛾默。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽支鸡。三九已至,卻和暖如春趁窃,著一層夾襖步出監(jiān)牢的瞬間牧挣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工醒陆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瀑构,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓刨摩,卻偏偏與公主長得像寺晌,于是被迫代替她去往敵國和親世吨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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