Vue3.0组件开发の侧边菜单组件

1,463 阅读2分钟

Vue3.0组件开发の侧边菜单组件

1. 实现目标

  • (1)通过树形的菜单结构,生成菜单组件
  • (2)传入的菜单结构
export const ComponentList = [
    {
        type: "group",
        name: "通用",
        id: "1",
        children: [
            {
                type: "item",
                name: '透明输入框',
                path: '/docs/inputdoc',
                id: "1-1"
            },
            {
                type: "item",
                name: '菜单',
                path: '/docs/menudoc',
                id: "1-2"
            }
        ]
    },
    {
        type: "subItem",
        name: "测试",
        id: "2",
        children: [
            {
                type: "subItem",
                name: '测试',
                id: "2-1",
                children: [
                    {
                        type: "item",
                        name: '测试',
                        path: '/docs',
                        id: "2-1-1",
                        checked: true
                    },
                    {
                        type: "item",
                        name: '测试',
                        path: '/docs',
                        id: "2-1-2"
                    }
                ]
            }
        ]
    }
]
  • (3)通过以上结构生成
  • (4)默认项为:2-1-2

2. 组件设计

  • (1)新建Menu菜单,新建Item.vue, menu.vue, menuList.vue, subItem.vue四个组件
  • (2)Menu.vue: 组件入口,接收菜单列表,默认项数据。
	<template>
        <div class="tao_menu">
            <div class="menu_header">

            </div>
            <menu-list :list="menuList" :active="state.active"></menu-list>
        </div>
    </template>

    <style lang="less" scoped>
        .menu_header {
            height: 48px;
        }
    </style>

    <script>
    import { provide, reactive } from 'vue';
    import MenuList from './MenuList';

    export default {
        components: {
            'menu-list': MenuList
        },
        props: {
            menuList: {
                type: Array,
                default: () => []
            },
            defaultActive: {
                type: String,
                default: ""
            }
        },
        setup(props, context) {
            const state = reactive({
                active: props.defaultActive
            });
            const itemChange = (e) => {
                state.active = e;
            }

            provide('itemChange', itemChange);
            return {
                state
            }
        }
    }
    </script>
  • (3) MenuList.vue: 遍历工作,接收菜单列表,通过不同类型渲染不同组件,传递默认项数据。
    <template>
        <template v-for="item in list" :key="item.id">
            <div v-if="item.type === 'group'" class="group">
                <div class="group_title">
                    <div class="text">{{ item.name }}</div>
                </div>
                <div class="group_content">
                    <menu-list :active="active" :list='item.children'></menu-list>
                </div>
            </div>
            <template v-else-if="item.type === 'subItem'">
                <sub-item :active="active" :data='item'></sub-item>
            </template>
            <template v-else>
                <v-item :active="active" :data='item'></v-item>
            </template>
        </template>
    </template>

    <style lang="less" scoped>
        .group {
            .group_title {
                padding: 0 0 0 48px;
                color: #888;
                .text {
                    padding: 10px 0 10px 10px;
                    border-bottom: 1px solid #f0f0f0;
                }
            }
            .group_content {
                padding: 0 0 0 48px;
            }
        }

    </style>

    <script>
    import { onMounted, ref, reactive, inject, watchEffect } from 'vue';
    import Item from './Item';
    import SubItem from './SubItem';

    export default {
        name: "MenuList",
        components: {
            'v-item': Item,
            'sub-item': SubItem
        },
        props: {
            list: {
                type: Array,
                default: () => []
            },
            active: {
                type: String,
                default: ""
            }
        },
        setup(props, context) {
            onMounted(() => {
            });

            // watchEffect(() =>{
            //     console.log(props.active,"watch")
            // })
            /**
             * sub开关
             */

        }
    }
    </script>

类型group为分组,只在最外层存在,类型subItem为可展开项,类型item为基本菜单项,最外层直接开始vfor遍历数组,当type为group时,通过name的定义重新遍历自己,当type为subItem时,调用另一个组件SubItem,当type为item时则调用Item组件,这个过程最外层group遍历完毕(组件不支持内部group),接下来的遍历交给SubItem

  • (4)subItem.vue: 可展开菜单项,接收变量data和active,data为当前项的对象,包含名称,children等字段,变量active为传递过来的默认选中项
    <template>
        <div class="sub_item">
            <div class="subitem_title" @click="itemSwitch">
                <span class="text">{{ data.name }}</span>
                <span class="iconfont icon-arrow-right arrow" :class="{'on_bottom': state.itemShow}"></span>
            </div>
            <transition name="slide">
                <div class="sub_content" v-show="state.itemShow">
                    <template v-for="item in data.children" :key="item.id">
                        <template v-if="item.type === 'subItem'">
                            <sub-item :active="active" :data='item'></sub-item>
                        </template>
                        <template v-else>
                            <v-item :active="active" :data='item'></v-item>
                        </template>
                    </template>
                </div>
            </transition>
        </div>
    </template>

    <style lang="less" scoped>
        .sub_item {
            .subitem_title {
                padding: 10px 10px 10px 48px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                cursor: pointer;
                .text {
                    padding-left: 10px;
                }
                .arrow {
                    transition: all 0.5s ease;
                }
                .on_bottom {
                    transform: rotate(90deg);
                }
            }
            .subitem_title:hover {
                color: #008dfe;
                transition: color 0.5s ease;
            }
            .sub_content {
                padding: 0 0 0 48px;
            }
        }
    </style>

    <script>
    import { onMounted, ref, reactive, inject, watchEffect } from 'vue';
    import Item from './Item';
    import MenuList from './MenuList';

    export default {
        name: "SubItem",
        components: {
            'v-item': Item,
            'menu-list': MenuList
        },
        props: {
            data: {
                type: Object,
                default: () => {}
            },
            active: {
                type: String,
                default: ""
            }
        },
        setup(props, context) {
            const state = reactive({
                itemShow: false // 是否展开list
            });

            onMounted(() => {
                if (props.active.indexOf(props.data.id) === 0) {
                    state.itemShow = true;
                }
            })

            const change = inject("itemChange");

            function itemSwitch() {
                state.itemShow = !state.itemShow;
            }

            return {
                itemSwitch,
                state
            }
        }
    }
    </script>

该组件由标题和子列表两部分组成,子列表根据选中项判断是否显示使用vshow,子列表由transition包裹,vue的动画组件,当标题被选中后执行动画显示子列表,遍历子列表时依然要判断是否是type为subItem,若true继续调用自身(name定义组件名称);

  • (5)Item.vue: 基本子菜单,接收变量为本身描述对象data和选中项active
    <template>
        <div class="item" @click="checkItem">
            <router-link :class="{action: data.id === active}" :to="data.path" v-if="data.path">{{ data.name }}</router-link>
            <a href="javascript:void()" v-else>地址未传</a>
        </div>
    </template>

    <style lang="less" scoped>
        .item {
            a {
                padding: 10px 0 10px 10px;
                color: #222;
                text-decoration: none;
                width: 100%;
                height: 100%;
                display: block;
                box-sizing: border-box;
                transition: all 0.5s ease;
            }
            a:hover {
                color: #008dfe;
            }
            .action {
                background: rgb(230, 247, 255);
                color: #008dfe;
            }
        }

    </style>

    <script>
    import { onMounted, inject } from 'vue';

    export default {
        props: {
            data: {
                type: Object,
                default: () => {}
            },
            active: {
                type: String,
                default: null
            }
        },
        setup(props, context) {
            onMounted(() => {
            });
            /**
             * 菜单选择
             */
            const change = inject("itemChange");
            function checkItem() {
                change(props.data.id);
            }
            return {
                checkItem: checkItem
            }
        }
    }
    </script>

被单机后更改选项,重新传递更新

github 地址: github.com/z1429170900…