项目中要实现分栏布局效果的需求,各区域的宽高可以随意改变(拖拽改变盒子的宽度和高度),并且每个区域可以实现最大化/最小化,并且底部实现类似vscode中的分栏图标按钮,点击随意折叠/展开每个区域。
直接看效果
在CSDN中查找到,vue-split-pane组件,由于我有定制化的需求,就没有直接npm install vue-splitpane使用,而是重新写了一下。如果要了解vue-split-pane组件,源码链接:
GitHub - PanJiaChen/vue-split-pane: Split-Pane component built with vue2.0, can be split vertically or horizontally. http://panjiachen.github.io/split-pane/demo/index.html
demo的github地址:GitHub - MonkeyKingYY/vue-split-pane-demo
下面直接放代码:
APP.vue
<template>
<div id="app" class="app-main-contain">
<div class="center-contain" style="width: 100%; height: calc(100% - 25px)">
<SplitPane @resize="leftResize" :default-percent="leftPercent" split="vertical">
<template slot="paneL">
<div class="title-contain" v-show="leftPercent > 0">
<p>资源导航</p>
<i class="iconfont icon-zuidahua" v-show="leftPercent > 0 && leftPercent < 100" @click="leftMaxFunc()"></i>
<i class="iconfont icon-zuixiaohua" v-show="leftPercent >= 100" @click="leftMinFunc()"></i>
</div>
1111111
</template>
<template slot="paneR">
<SplitPane split="vertical" @resize="rightResize" :default-percent="rightPercent">
<template slot="paneL">
<SplitPane split="horizontal" @resize="topBottomResize" :default-percent="topPercent">
<template slot="paneL">
<div class="title-contain" v-show="topPercent > 0 && rightPercent > 0">
<p>查询编辑器</p>
<i class="iconfont icon-zuidahua" v-show="leftPercent !== 100 && rightPercent !== 100" @click="topMaxFunc()"></i>
<i class="iconfont icon-zuixiaohua" v-show="leftPercent == 0 && topPercent == 100 && rightPercent == 100" @click="topMinFunc()"></i>
</div>
<div style="height: calc(100% - 25px)">2222222</div>
</template>
<template slot="paneR">
<div class="title-contain" v-show="topPercent < 100 && rightPercent > 0">
<p>结果区</p>
<i class="iconfont icon-zuidahua" v-show="leftPercent !== 100 && topPercent !== 0 && rightPercent !== 0" @click="bottomMaxFunc()"></i>
<i class="iconfont icon-zuixiaohua" v-show="leftPercent == 0 && topPercent == 0 && rightPercent >= 100" @click="bottomMinFunc()"></i>
</div>
<div v-show="topPercent < 100 && rightPercent > 0" style="height: calc(100% - 25px)">3333333</div>
</template>
</SplitPane>
</template>
<template slot="paneR">
<div class="title-contain" v-show="rightPercent < 100">
<p>辅助区</p>
<i class="iconfont icon-zuidahua" v-show="leftPercent !== 100 && rightPercent !== 0" @click="rightMaxFunc()"></i>
<i class="iconfont icon-zuixiaohua" v-show="leftPercent == 0 && rightPercent == 0" @click="rightMinFunc()"></i>
</div>
<div v-show="rightPercent < 100" style="height: calc(100% - 25px)">444444</div>
</template>
</SplitPane>
</template>
</SplitPane>
</div>
<div class="buttom-contain">
<BottomTaskBar :rightPercent="rightPercent" :leftPercent="leftPercent" :topPercent="topPercent" @leftClose="leftClose" @rightClose="rightClose" @bottomClose="bottomClose" @openBottom="openBottom" @openLeft="openLeft" @openRight="openRight"></BottomTaskBar>
</div>
</div>
</template>
<script>
import SplitPane from './components/SplitPane.vue'
export default {
name: 'index',
components: {
SplitPane,
BottomTaskBar: () => import('./components/BottomTaskBar.vue'),
},
created() {
this.leftPercent = parseInt(localStorage.getItem('leftPercent') || 20)
this.rightPercent = parseInt(localStorage.getItem('rightPercent') || 70)
this.topPercent = parseInt(localStorage.getItem('topPercent') || 70)
},
data() {
return {
GLayoutRoot: null,
leftPercent: 20,
rightPercent: 70,
topPercent: 70,
oldLeftPercent: 20,
oldRightPercent: 70,
oldTopPercent: 70,
}
},
watch: {
leftPercent: {
handler(percent) {
console.log('watchleftPercent', percent)
localStorage.setItem('leftPercent', percent)
},
deep: true,
},
rightPercent: {
handler(percent) {
console.log('watchrightPercent', percent)
localStorage.setItem('rightPercent', percent)
},
deep: true,
},
topPercent: {
handler(percent) {
console.log('watchtopPercent', percent)
localStorage.setItem('topPercent', percent)
},
deep: true,
},
},
methods: {
leftResize(percent) {
this.oldLeftPercent = percent
this.leftPercent = percent
},
rightResize(percent) {
this.oldRightPercent = percent
this.rightPercent = percent
},
topBottomResize(percent) {
this.oldTopPercent = percent
this.topPercent = percent
},
leftClose() {
this.leftPercent = 0
},
rightClose() {
this.rightPercent = 100
},
bottomClose() {
this.topPercent = 100
},
openBottom() {
this.topPercent = this.oldTopPercent
},
openLeft() {
this.leftPercent = this.oldLeftPercent
},
openRight() {
this.rightPercent = this.oldRightPercent
},
leftMaxFunc() {
this.leftPercent = 100
},
leftMinFunc() {
this.leftPercent = this.oldLeftPercent
},
topMaxFunc() {
this.topPercent = 100
this.leftPercent = 0
this.rightPercent = 100
},
topMinFunc() {
this.topPercent = this.oldTopPercent
this.leftPercent = this.oldLeftPercent
this.rightPercent = this.oldRightPercent
},
bottomMaxFunc() {
this.topPercent = 0
this.leftPercent = 0
this.rightPercent = 100
},
bottomMinFunc() {
this.topPercent = this.oldTopPercent
this.leftPercent = this.oldLeftPercent
this.rightPercent = this.oldRightPercent
},
rightMaxFunc() {
this.leftPercent = 0
this.rightPercent = 0
},
rightMinFunc() {
this.topPercent = this.oldTopPercent
this.leftPercent = this.oldLeftPercent
this.rightPercent = this.oldRightPercent
},
},
}
</script>
<style scoped>
.app-main-contain {
height: 100%;
display: flex;
flex-direction: column;
}
.center-contain .title-contain {
display: flex;
align-items: center;
padding: 0 10px 0 10px;
justify-content: space-between;
background-color: #eee;
}
.center-contain .title-contain p {
margin: 0;
height: 25px;
line-height: 25px;
font-size: 12px;
}
.center-contain .title-contain i {
font-size: 14px;
cursor: pointer;
}
.app-main-contain .buttom-contain {
position: fixed;
bottom: 0;
height: 25px;
width: 100%;
background-color: #0074c8;
z-index: 999999;
}
</style>
SplitPane.vue:
<template>
<div :style="{ cursor, userSelect }" class="vue-splitter-container clearfix" @mouseup="onMouseUp" @mousemove="onMouseMove">
<Pane class="splitter-pane splitter-paneL" :split="split" :style="{ [type]: percent + '%' }">
<slot name="paneL"></slot>
</Pane>
<Resizer
:className="className"
:style="{ [resizeType]: percent + '%' }"
:split="split"
@mousedown.native="onMouseDown"
@click.native="onClick"
></Resizer>
<Pane class="splitter-pane splitter-paneR" :split="split" :style="{ [type]: 100 - percent + '%' }">
<slot name="paneR"></slot>
</Pane>
<div class="vue-splitter-container-mask" v-if="active"></div>
</div>
</template>
<script>
import Resizer from './Resizer.vue'
import Pane from './Pane.vue'
export default {
name: 'splitPane',
components: { Resizer, Pane },
props: {
minPercent: {
type: Number,
default: 0,
},
defaultPercent: {
type: Number,
default: 50,
},
split: {
validator(value) {
return ['vertical', 'horizontal'].indexOf(value) >= 0
},
required: true,
},
className: String,
},
computed: {
userSelect() {
return this.active ? 'none' : ''
},
cursor() {
return this.active ? (this.split === 'vertical' ? 'col-resize' : 'row-resize') : ''
},
},
watch: {
defaultPercent(newValue, oldValue) {
console.log(oldValue)
this.percent = newValue
},
},
data() {
return {
active: false,
hasMoved: false,
height: null,
percent: this.defaultPercent,
type: this.split === 'vertical' ? 'width' : 'height',
resizeType: this.split === 'vertical' ? 'left' : 'top',
firstTime: 0,
lastTime: 0,
key: false,
}
},
methods: {
onClick() {
console.log(111)
if (this.key) {
this.percent = 50
this.$emit('resize', this.percent)
this.key = false
}
},
onMouseDown() {
console.log('mouseDown')
this.firstTime = new Date().getTime()
this.active = true
this.hasMoved = false
},
onMouseUp() {
console.log('mouseUp')
this.active = false
this.lastTime = new Date().getTime()
if (this.lastTime - this.firstTime < 500) {
this.key = true
console.log(this.key)
}
},
onMouseMove(e) {
if (e.buttons === 0 || e.which === 0) {
this.active = false
}
if (this.active) {
let offset = 0
let target = e.currentTarget
if (this.split === 'vertical') {
while (target) {
offset += target.offsetLeft
target = target.offsetParent
}
} else {
while (target) {
offset += target.offsetTop
target = target.offsetParent
}
}
const currentPage = this.split === 'vertical' ? e.pageX : e.pageY
const targetOffset = this.split === 'vertical' ? e.currentTarget.offsetWidth : e.currentTarget.offsetHeight
const percent = Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100
if (percent > this.minPercent && percent < 100 - this.minPercent) {
this.percent = percent
}
this.$emit('resize', this.percent)
this.hasMoved = true
}
},
},
}
</script>
<style scoped>
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
.vue-splitter-container {
height: 100%;
position: relative;
}
.vue-splitter-container-mask {
z-index: 9999;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
</style>
Resizer.vue:
<template>
<div :class="classes"></div>
</template>
<script>
export default {
props: {
split: {
validator(value) {
return ['vertical', 'horizontal'].indexOf(value) >= 0
},
required: true
},
className: String
},
computed: {
classes() {
const classes = ['splitter-pane-resizer', this.split, this.className]
return classes.join(' ')
}
}
}
</script>
<style scoped>
.splitter-pane-resizer {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
background: #000;
position: absolute;
opacity: .2;
z-index: 1;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding-box;
}
.splitter-pane-resizer.horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
}
.splitter-pane-resizer.vertical {
width: 11px;
height: 100%;
margin-left: -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
}
</style>
Pane.vue:
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'Pane',
props: {
className: String
},
data() {
const classes = [this.$parent.split, this.className]
return {
classes: classes.join(' '),
percent: 50
}
}
}
</script>
<style scoped>
.splitter-pane.vertical.splitter-paneL {
position: absolute;
left: 0px;
height: 100%;
padding-right: 3px;
}
.splitter-pane.vertical.splitter-paneR {
position: absolute;
right: 0px;
height: 100%;
padding-left: 3px;
}
.splitter-pane.horizontal.splitter-paneL {
position: absolute;
top: 0px;
width: 100%;
}
.splitter-pane.horizontal.splitter-paneR {
position: absolute;
bottom: 0px;
width: 100%;
padding-top: 3px;
}
</style>
BottomTaskBar.vue:
<template>
<div id="BottomTaskBar">
<div class="img-contain">
<img @click="leftClose" v-if="leftPercent !== 0" src="../assets/bottomTask/left_show.png" />
<img @click="openLeft" v-if="leftPercent == 0" src="../assets/bottomTask/left_hidden.png" />
<img @click="bottomClose" v-if="topPercent !== 100" src="../assets/bottomTask/bottom_show.png" />
<img @click="openBottom" v-if="topPercent == 100" src="../assets/bottomTask/bottom_hidden.png" />
<img @click="rightClose" v-if="rightPercent !== 100" src="../assets/bottomTask/right_show.png" style="margin-bottom: 1px" />
<img @click="openRight" v-if="rightPercent == 100" src="../assets/bottomTask/right_hidden.png" />
</div>
</div>
</template>
<script>
export default {
props: {
leftPercent: {
type: Number,
},
rightPercent: {
type: Number,
},
topPercent: {
type: Number,
},
},
data() {
return {}
},
methods: {
leftClose() {
this.$emit('leftClose')
},
rightClose() {
this.$emit('rightClose')
},
bottomClose() {
this.$emit('bottomClose')
},
openBottom() {
this.$emit('openBottom')
},
openLeft() {
this.$emit('openLeft')
},
openRight() {
this.$emit('openRight')
},
},
}
</script>
<style scoped>
#BottomTaskBar {
color: #fff;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: flex-end;
}
#BottomTaskBar p {
margin: 0;
height: 25px;
line-height: 25px;
}
#BottomTaskBar .img-contain img {
width: 20px;
height: 20px;
cursor: pointer;
margin-right: 10px;
}
</style>
注意⚠️:静态资源包括图标和图标就不上传了,如果需要运行代码,可以直接从github上clone下来运行即可。关于这种类似vscode的分栏布局,曾经是让我很头疼的需求,解决了之后很开心。如果能帮助大家解决自己的问题,我会更开心😊。最后再次感谢这个插件:GitHub - PanJiaChen/vue-split-pane: Split-Pane component built with vue2.0, can be split vertically or horizontally. http://panjiachen.github.io/split-pane/demo/index.html
如果帮助到您了,可以留下一个赞👍告诉我