vue模拟element menu折叠菜单

1,629 阅读1分钟

menu.gif

  1. 实现展开关闭过渡效果
  2. 选中项刷新页面后保持选中展开效果
  3. 子菜单动态缩进
  4. 使用组件递归实现

index.vue

<template>
    <div class="menu">
        <collapseMenu :list="$router.options.routes" :depth="1" :currentName="currentName"
                      @routeJump="routeJump"></collapseMenu>
    </div>
</template>

<script>
    import collapseMenu from './collapse-menu'

    export default {
        components: {
            collapseMenu
        },
        data() {
            return {
                currentName: null
            };
        },
        mounted() {
            this.currentName = this.$route.name
        },
        methods: {
            /**
             * 点击最后的节点进行路由跳转
             **/
            routeJump(item) {
                this.$router.push({name: item.name})
                this.currentName = this.$route.name
            }
        }
    }
</script>

<style lang="scss">
    .menu {
        height: 100%;
        width: 50%;
        overflow: auto;
        background: #000000;
    }
</style>

collapse-menu.vue

<template>
    <div :class="depth === 1 ? 'collapse-menu' :'menu-children'"
         :style="{display:depth === 1 ? '' : 'none'}">
        <template v-for="(item,index) in list">
            <div class="menu-group" v-if="item.children && item.children.length" :key="index"
                 @click.stop="openMenu($event.currentTarget.children[1],item.name)">
                <div class="menu-title" :style="{'padding-left': `${depth * 20}px`}">
                    <span>
                       <i v-show="item.meta.icon" v-html="item.meta.icon"></i>
                        {{item.meta.title}}
                    </span>
                    <span class="icon-top" :class="{active:activeName[item.name]}">&#9650;</span>
                </div>
                <collapse-menu :list="item.children" :depth="depth + 1" :current-name="currentName"
                               v-on="$listeners"></collapse-menu>
            </div>
            <div class="menu-title" :key="index" :style="{'padding-left': `${depth * 20}px`}"
                 :class="{'menu-active':currentName === item.name}"
                 v-else
                 @click.stop="routeJump(item,$event)">
                <span>
                     <i v-show="item.meta.icon" v-html="item.meta.icon"></i>
                    {{item.meta.title}}
                </span>
            </div>
        </template>
    </div>
</template>

<script>
    export default {
        name: "collapse-menu",
        props: {
            list: Array,
            depth: Number,
            currentName: String
        },
        data() {
            return {
                activeName: {},
                activeDelay: {}, // true表示过渡还在执行中,false表示过渡完毕
                doc: document,
            }
        },
        mounted() {
            // 默认展开的菜单
            for (let item of this.$route.matched) {
                for (let value of this.list) {
                    if (item.name === value.name) {
                        this.$el.opened = true
                        this.$el.style.display = ''
                        this.$set(this.activeName, value.name, !this.activeName[value.name])
                        return
                    }
                }
            }
        },
        methods: {
            /**
             * 展开菜单
             **/
            openMenu(dom, key) {
                // 节流
                // 为true表示过渡效果还没执行完,退出后面方法的执行
                if (this.activeDelay[key]) {
                    return
                }

                // 过渡结束触发
                dom.ontransitionend = (el) => {
                    if (el.target.classList.contains('menu-children')) {
                        // false关闭   true展开
                        if (dom.opened) {
                            dom.opened = false
                            dom.style.height = ``
                            dom.style.display = 'none'
                        } else {
                            dom.opened = true
                            dom.style.height = ``
                        }
                    }
                    this.$set(this.activeDelay, key, false)
                    el.stopPropagation()
                }

                if (!dom.opened) {
                    dom.style.display = 'block'
                    dom.style.height = `0`

                    this.$nextTick(() => {
                        dom.style.height = `${dom.scrollHeight}px`
                    })
                } else {
                    dom.style.height = `${dom.scrollHeight}px`
                    setTimeout(() => {
                        dom.style.height = '0'
                    })
                }

                this.$set(this.activeDelay, key, true)

                // 设置三角形的class
                this.$set(this.activeName, key, !this.activeName[key])
            },

            /**
             * 点击最后的子节点进行路由跳转
             **/
            routeJump(item) {
                // 避免重复选择
                if (this.$route.name !== item.name) {
                    this.$emit('routeJump', item)
                }
            }
        }
    }
</script>

<style lang="scss" scoped>
    .menu-children {
        transition: height .5s;
    }

    .menu-children, .collapse-menu {
        overflow: hidden;
        color: #cccccc;
    }

    .icon-top {
        margin-top: 3px;
        transition: transform 0.5s;
    }

    .active {
        transform: rotateZ(180deg);
    }

    .menu-title {
        padding-right:20px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        cursor: pointer;
        height: 45px;

        &:hover {
            background: #1E2088;

            span {
                color: #ffffff;
            }
        }

        span {
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
            i:first-child{
                margin-right: 10px;
            }
        }
    }

    .menu-active {
        background: #1E2088;
        color: #ffffff;
    }
</style>

测试router文件

export default [
    {
        path: '/test',
        name: 'test',
        meta: {
            title: 'XX配置',
            icon: '<i style="font-style: initial;">&#9728;</i>'
        },
        children: [
            {
                path: 'vcvc',
                name: 'vcvc',
                meta: {
                    title: 'vcvc'
                },
                children: [
                    {
                        path: 'aaa',
                        name: 'aaa',
                        meta: {
                            title: 'aaa'
                        },
                    }, {
                        path: 'nnn',
                        name: 'nnn',
                        meta: {
                            title: 'nnn'
                        },
                    }
                ]
            },
            {
                path: 'fdf',
                name: 'fdf',
                meta: {
                    title: 'fdf',
                    icon: '<i style="font-style: initial;">&#9729;</i>'
                },
                children: [
                    {
                        path: 'opop',
                        name: 'opop',
                        meta: {
                            title: 'opop'
                        },
                    },
                    {
                        path: 'jjj',
                        name: 'jjj',
                        meta: {
                            title: 'jjj'
                        },
                        children: [
                            {
                                path: 'pppp',
                                name: 'pppp',
                                meta: {
                                    title: 'pppp'
                                },
                            }, {
                                path: 'ilili',
                                name: 'ilili',
                                meta: {
                                    title: 'ilili'
                                },
                            },
                        ]
                    }
                ]
            },
            {
                path: 'test',
                name: 'test',
                meta: {
                    title: 'test',
                    icon: '<i style="font-style: initial;">&#9748;</i>'
                },
                children: [
                    {
                        path: 'nbnb',
                        name: 'nbnb',
                        meta: {
                            title: 'nbnb'
                        },
                    }
                ]
            },
        ]
    },
    {
        path: '/aaaa',
        name: 'aaaa',
        meta: {
            title: 'aas',
            icon: '<i style="font-style: initial;">&#9815;</i>'
        },
    },
]