vue封装左右菜单布局盒子,支持拖动和隐藏
实现原理:
- 在左右两个盒子中间位置添加一条辅助线dom
- 通过辅助线dom的mousedown和mousemove事件获取辅助线的坐标
- 根据获取的坐标动态计算左侧盒子的宽度,右侧盒子自适应
<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>