vue封装左右菜单布局盒子,支持拖动和隐藏

367 阅读1分钟

vue封装左右菜单布局盒子,支持拖动和隐藏

实现原理:

  1. 在左右两个盒子中间位置添加一条辅助线dom
  2. 通过辅助线dom的mousedown和mousemove事件获取辅助线的坐标
  3. 根据获取的坐标动态计算左侧盒子的宽度,右侧盒子自适应
<template>
    <div class="box">
        <div class="left-box" :style="leftStyle">
            <!-- 左侧div内容 -->
            <div class="menu">
                <slot name="customMenu"></slot>
                <!-- 菜单底部插槽 -->
                <div class="menu-footer" :style="iconType ? {display: 'none'} : ''">
                    <slot name="footer"></slot>
                </div>
            </div>
            <!-- 盒子拖动与收起 -->
            <div class="x-resizer" @mousedown="mouseDown"></div>
            <span class="cate-switch" :title="iconType ? '展开' : '收起'" @click="changeShowHidden">
                 <h-icon :name="iconType ? 'ios-arrow-forward' : 'ios-arrow-back'" size=14 color="#cacfd4"></h-icon>
            </span>
        </div>
        <div class="right-box">
            <!-- 右侧box内容 -->
            <slot name="right"></slot>
        </div>
    </div>
</template>
<script>
export default {
    name: 'ApmBox',
    props: {
        menuTitle: {
            type: String,
            default: undefined
        },
        // 收起时左侧盒子最小宽度
        menuMinWidth: {
            type: Number,
            default: 0
        },
        // 菜单列表
        menuList: {
            type: Array,
            default: 0
        }
    },
    data() {
        return {
            lastLeftX: 224,
            leftBarWidth: 224,
            iconType: false, // 0:收起, 1:展开
            startX: 0
        };
    },
    methods: {

        // 左侧box显示与隐藏
        changeShowHidden() {
            this.iconType = !this.iconType;
            this.lastLeftX = this.iconType ? this.menuMinWidth : this.leftBarWidth;
        },

        // 左侧宽度拖动
        mouseDown(e) {
            this.startX = e.clientX;
            this.mouseMove(e);
            this.mouseUp();
            document.addEventListener('mousemove', this.mouseMove);
            document.addEventListener('mouseup', this.mouseUp);
        },
        mouseUp() {
            this.leftBarWidth = this.lastLeftX;
            document.removeEventListener('mousemove', this.mouseMove);
            document.removeEventListener('mouseup', this.mouseUp);
        },
        mouseMove(e) {
            e.preventDefault();
            e.stopPropagation();
            if (e.clientX < 200) return;
            if (this.iconType || e.clientX < 224) return;
            const offset = e.clientX - this.startX;
            if (offset) {
                this.lastLeftX = offset + this.leftBarWidth;
            }
        },
    },
    computed: {
        leftStyle() {
            return { width: this.lastLeftX + 'px', 'min-width': this.menuMinWidth + 'px' };
        }
    }
};
</script>

<style lang="less" scoped>
.apm-box {
    display: flex;
    width: 100%;
    height: calc(100% - 63px);
    margin-top: 15px;
    border-radius: var(--border-radius);
    background: var(--wrapper-color);

    .left-box {
        height: 100%;
        background: var(--wrapper-color);
        border-right: 1px solid var(--box-color);
        will-change: transform;
        transition: width 0.2s;
        z-index: 6;

        .menu {
            position: relative;
            width: 100%;
            height: 100%;
            border-right: 1px solid var(--wrapper-color);

                .menu-selected {
                    background: var(--link-opacity-color);

                    &::after {
                        position: absolute;
                        left: 0;
                        top: 0;
                        content: "";
                        width: 4px;
                        height: 33px;
                        background: var(--link-color);
                    }
                }

            }

            .menu-footer {
                position: absolute;
                bottom: 5px;
                left: 8px;
                width: 90%;
            }
        }

        .x-resizer {
            position: absolute;
            top: 0;
            bottom: 0;
            right: -2px;
            width: 2px;
            user-select: none;
            cursor: col-resize;
            z-index: 9;

            &:hover {
                background-color: var(--base-color);
            }
        }

        .cate-switch {
            position: absolute;
            top: 50%;
            right: -13px;
            height: 44px;
            width: 12px;
            margin-top: -22px;
            line-height: 44px;
            text-align: center;
            background: rgba(209, 216, 229, 0.2);
            border-radius: 0 4px 4px 0;
            cursor: pointer;
            transform: perspective(0.5em) rotateY(10deg);
            z-index: 10;

            &:hover {
                background: rgba(209, 216, 229, 0.4);
            }
        }
    }

    .right-box {
        flex: 1;
        position: relative;
        background: var(--wrapper-color);
        min-width: 75%;
    }
}
</style>