美文网首页前端开发那些事儿
【Vue3+Vite+TS】8.0 组件六:导航菜单

【Vue3+Vite+TS】8.0 组件六:导航菜单

作者: bobokaka | 来源:发表于2021-12-31 15:57 被阅读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,新增路由参数

    ......
          {
                    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: '省市区选择组件',
            code: '3',
            icon: 'el-icon-document',
            children: [
                { name: '省市选择组件', code: '3-1', icon: 'el-icon-document' },
                { name: '省市区村选择组件', code: '3-2', icon: 'el-icon-document' },
            ],
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    
    

    如下,可见数据已经传达到基础组件:

    image.png

    完善组件

    首先实现一级菜单。
    修改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: '省市区选择组件',
            code: '3',
            icon: 'el-icon-document',
            children: [
                { name: '省市选择组件', code: '3-1', icon: 'el-icon-document' },
                { name: '省市区村选择组件', code: '3-2', icon: 'el-icon-document' },
            ],
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    

    效果如下:


    image.png

    接下来实现多级菜单
    修改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: '行政区域选择组件',
            index: '3',
            icon: 'el-icon-document',
            children: [
                { name: '省市选择组件', index: '3-1', icon: 'el-icon-document' },
                { name: '省市区选择组件', index: '3-2', icon: 'el-icon-document' },
                {
                    name: '省市区村选择组件',
                    index: '3-3',
                    icon: 'el-icon-document',
                },
            ],
        },
        0,
    ]
    </script>
    <style lang="scss" scoped></style>
    

    实现效果如下:


    image.png

    v-bind="$attrs":接受父组件传入的数据和方法,并排除在组件的props中响应的参数。
    具体可以参考: vue中使用v-bind="$attrs"和v-on="$listeners"进行多层组件监听

    TSX实现无限层级的导航菜单

    首先需要安装插件。

    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代码用于的开发环境:'preserve','react-native',or 'react
            "jsx": "preserve",
            // 指定是否将map文件内容和js文件编译在一个同一个js文件中
            // 如果设为true,则map的内容会以//#soureMappingURL=开头,然后接base64字符串的形式插入在js文件底部
            "sourceMap": true,
            "skipLibCheck": true,
            "resolveJsonModule": true,
            //通过导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性
            "esModuleInterop": true,
            "forceConsistentCasingInFileNames": true,
            // 指定是否引入tslib里的复制工具函数,默认为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": {
                // 根据别名配置相关路径
                "@/*": ["./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,
            // 用来指定是否使用函数参数双向协变检查
            // "strictFunctionTypes": true,
            // 设为true后对bind、call和apply绑定的方法的参数的检测是严格检测
            // "strictBindCallApply": true,
            // 设为true后会检查类的非undefined属性是否已经在构造函数里初始化,如果要开启这项,需要同时开启strictNullChecks,默认为false
            // "strictPropertyInitialization": true,
            // 当this表达式的值为any类型的时候,生成一个错误
            // "noImplicitThis": true,
            // alwaysStrict指定始终以严格模式检查每个模块,并且在编译之后的JS文件中加入"use strict"字符串,用来告诉浏览器该JS为严格模式
            // "alwaysStrict": true,
            // 用于检查是否有定义了但是没有使用变量,对于这一点的检测,使用ESLint可以在你书写代码的时候做提示,你可以配合使用,他的默认值为false
            // "noUnusedLocals": true,
            // 用于检测是否在函数中没有使用的参数
            // "noUnusedParameters": true,
            // 用于检查函数是否有返回值,设为true后,如果函数没有返回值则会提示,默认为false
            // "noImplicitReturns": true,
            // 用于检查switch中是否有case没有使用break跳出switch,默认为false
            // "noFallthroughCasesInSwitch": true,
            // 可以指定一个路径列表,在构建时编译器会将这个路径中的内容都放到一个文件夹中
            // "rootDirs": [],
            // 用来指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载
            // "typeRoots": [],
            // types用于指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载
            // "types": [],
            // 用来指定允许从没有默认导出的模块中默认导入
            // "allowSyntheticDefaultImports": true,
            // 不把符号链接解析为真实路径,具体可以了解下webpack和node.js的symlink相关知识
            // "preserveSymlinks": true,
            // 用于指定调试器应该找到TypeScript文件而不是源文件的位置,这个值会被写进.map文件里
            // "sourceRoot": "",
            // 用于指定调试器找到映射文件而非生成文件的位置,指定map文件的根路径
            // 该选项会影响.map文件中的sources属性
            // "mapRoot": "",
            // s用于指定是否进一步将ts文件的内容也包含到输出文件中
            // "inlineSources": true,
            // 用于指定是否启用实验性的装饰器特性
            // "experimentalDecorators": true,
            // 用于指定是否为装上去提供元数据支持
            // 关于元数据,也是ES6的新标准,可以通过Reflect提供的静态方法获取元数据
            // 如果需要使用Reflect的一些方法,需要引用ES2015.Reflect这个库
            // "emitDecoratorMetadata": true,
            // 可以配置一个数组列表
            // "files":[],
            // exclude表示要排除的,不编译的文件
            // "exclude":[]
            // include也可以指定要编译的路径列表
            // "include":[],
            //它也可以指定一个列表,规则和include一样,可以是文件可以是文件夹,可以是相对路径或绝对路径,可以使用通配符
            // "exclude":[]
            // 可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置,继承来的文件的配置会覆盖当前文件定义的配置
            // "extends":""
            // 如果设为true,在我们编辑了项目文件保存的时候,编辑器会根据tsconfig.json的配置更新重新生成文本,不过这个编辑器支持
            // "compileOnSave":true
            // 一个对象数组,指定要引用的项目
            // "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: '行政区域选择组件',
            index: '3',
            icon: 'el-icon-document',
            children: [
                { name: '省市选择组件', index: '3-1', icon: 'el-icon-document' },
                { name: '省市区选择组件', index: '3-2', icon: 'el-icon-document' },
                {
                    name: '省市区村选择组件',
                    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: '行政区域选择组件',
            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: '省市区选择组件', index: '3-2', icon: 'el-icon-document' },
                {
                    name: '省市区村选择组件',
                    index: '3-3',
                    icon: 'el-icon-document',
                },
            ],
        },
        0,
    ]
    </script>
    <style lang="scss" scoped></style>
    

    相关文章

      网友评论

        本文标题:【Vue3+Vite+TS】8.0 组件六:导航菜单

        本文链接:https://www.haomeiwen.com/subject/snncqrtx.html