vue实现多栏布局(拖拽改变盒子的宽度和高度),任意区域最大化/最小化,并且实现类似vscode,一键折叠展开相应区域

项目中要实现分栏布局效果的需求,各区域的宽高可以随意改变(拖拽改变盒子的宽度和高度),并且每个区域可以实现最大化/最小化,并且底部实现类似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

 如果帮助到您了,可以留下一个赞👍告诉我  

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿小野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值