拖拽导航卡
<transition-group></transition-group>做动画 加 h5的拖拽特性
<template>
<transition-group class="container clearFixed" tag="ul" ref="ul" :style="{height:height + 'px',lineHeight:height+'px'}">
<li class="drag-nav-li" v-for="(item,index) in items" :key="item[id]|| (index+1)"
draggable="true" ref="li"
@mousedown="liClickEvent($event,item,index)"
@dragstart="handleDragStart($event, item,index)"
@dragover.prevent="handleDragOver($event)"
@dragenter="handleDragEnter($event, item)"
@dragend="handleDragEnd($event, item)"
@contextmenu.prevent="contextMenuEvent($event,item,index)"
:class="{current:currentIndex === index}">{{item[label]}}
<ul class="rightDialogUl ft_14" :style="{left:leftWidth + 'px',top:topHeight + 'px',display:display}">
<li @mousedown.stop="closeThisEvent">关闭当前</li>
<li @mousedown.stop="closeALLEvent">关闭所有</li>
<li @mousedown.stop="closeLeftAllEvent">关闭左边所有</li>
<li @mousedown.stop="closeRightALLEvent">关闭右边所有</li>
</ul>
<span @mousedown.stop="closeNavEvent(item,index)"></span>
</li>
</transition-group>
</template>
<script>
export default {
props:{
object:Object,// 传入的对象
label:{type:String,default:'navName'},// 要展示的字段名
id:{type:String,default:'id'},// 主键id,默认为id,如果object的主键为id则不传,否则必传
height:{type:String,default:''},// 选项卡高度
defaultObject:{type:Object,default:{path:'/',name:''}},// 默认对象,当导航卡全部关闭时要访问的路径,此项很重要,如果不传则默认为/,根路径
},
data () {
return {
items: [],
dragElement: null,
currentIndex:-1,
oldCurrentIndex:-1,// 记录上一次的选项卡下标值
leftWidth:'',
topHeight:'',
display:'none',
}
},
watch:{
object(data){
// 如果点击首页时直接返回,不添加到导航切换卡数组中,当前选中下标置为-1
if(data.path === (this.defaultObject && this.defaultObject.path)){this.currentIndex = -1;return;}
/*如果在已有数组中找不到当前要新增的对象则返回*/
if(!this.items.some(a=>a[this.id] === data[this.id])){
this.init();
}else {
this.currentIndex = this.items.findIndex(a=>a[this.id] === data[this.id]);
}
},
items(data){
sessionStorage.setItem('navList',JSON.stringify(data));
},
currentIndex(data){
sessionStorage.setItem('navCurrentIndex',JSON.stringify(data));
}
},
mounted(){
let _this = this;
/** 浏览器窗口变化监听事件,
* 根据浏览器窗口的大小变化获取父级元素的宽度,
* 当父级元素宽度小于当前所有子级元素宽度总和删除最后一个子元素 */
window.addEventListener('resize',function (){
if(_this.$refs.li && _this.$refs.ul){
let parentWidth = _this.$refs.ul.$el.offsetWidth;
let liList = _this.$refs.li;
let widths = 0;
for(let i = 0;i < liList.length; i++){
widths+= liList[i].offsetWidth;
if(parentWidth - widths < 0){
_this.$emit('deleteComponent',_this.items[i]);// 把删除的元素的传出
_this.items = _this.items.slice(0,i);
// 如果当前访问的路径 所对应的选中的下标值等于 i 也就是等于要删除的路径的下标 ,
// 则选中的下标值 = 删除的下标 - 1;传递出点击事件,切换到新路径下,访问新页面
if(_this.currentIndex === i){
_this.currentIndex = i - 1;
_this.$emit('click',_this.items[_this.currentIndex],_this.currentIndex);
}
}
}
}
});
window.addEventListener('click',function () {
_this.display = 'none';
});
},
created(){
// 获取本地缓存值
if(sessionStorage.getItem('navList'))this.items = JSON.parse(sessionStorage.getItem('navList'));
if(sessionStorage.getItem('navCurrentIndex'))this.currentIndex = JSON.parse(sessionStorage.getItem('navCurrentIndex'))
},
methods:{
init(){
this.items.push(this.object);
this.$nextTick(()=>{
let parentWidth = this.$refs.ul.$el.offsetWidth;
let liList = this.$refs.li;
let widths = 0;
let lastChildrenWidth = 0;
for(let i = 0;i< liList.length;i++){
if(i === liList.length -1){
lastChildrenWidth = liList[i].offsetWidth;
continue;
}
widths+= liList[i].offsetWidth;
}
/** 如果当前选项卡父类宽度 - 除去最后一个孩子的宽度总和 小于 最后一个元素的宽度 就删除倒数第二个孩子,把第一个提前*/
if(parentWidth - widths < lastChildrenWidth){
/** 如果删除的元素的宽度 + (当前选项卡父类宽度 - 除去最后一个孩子的宽度总和) 小于 最后一个元素的宽度 就删除最后一个孩子 */
if( liList[this.items.length - 2].offsetWidth + (parentWidth - widths) < lastChildrenWidth ){
this.$emit('deleteComponent',this.items[this.items.length - 3]);// 把删除的元素的传出
this.$emit('deleteComponent',this.items[this.items.length - 2]);// 把删除的元素的传出
this.items.splice(this.items.length - 3,2);
}else {
this.$emit('deleteComponent',this.items[this.items.length - 2]);// 把删除的元素的传出
this.items.splice(this.items.length - 2,1);
}
this.currentIndex = this.items.findIndex(a=>a[this.id] === this.object[this.id]);
}else {
this.currentIndex = this.items.findIndex(a=>a[this.id] === this.object[this.id]);
}
})
},
handleDragStart(e,item,index){// 鼠标按下选中元素开始拖动时
this.dragElement = item;
},
handleDragEnd(e,item){// 元素停止拖动时执行此事件,标志着拖动结束
this.dragElement = null
},
/*参考dataTransfer API herf="https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer"*/
handleDragOver(e) {// 元素拖动的过程会一直执行的事件,直到元素停止拖动不再执行
e.dataTransfer.dropEffect = 'move'; //在dragenter中针对放置目标来设置! 给一个元素设置move值代表元素将移动到新位置
},
handleDragEnter(e,item){// 当拖动的元素进入某一个元素的范围内时执行,包括刚开始拖动在自己本身的范围时也会执行
e.dataTransfer.effectAllowed = "move"; //为需要移动的元素设置dragstart事件 ,move代表一个项目可能被移动到新位置。
if(item === this.dragging){
return
}
let items = this.items;
let dragElIndex = items.indexOf(this.dragElement);
let dropElIndex = items.indexOf(item);
items.splice(dropElIndex,0,...items.splice(dragElIndex,1));
this.items = items;
this.currentIndex = items.indexOf(this.dragElement);
},
liClickEvent(el,data,index){// 鼠标按下事件
// 按下的不是右键才会执行
if (event.button !== 2) {// 鼠标按键值 0---左键、 2---右键 、1---滚轮
this.currentIndex = index;
this.$emit('click',data,index);
this.display = 'none';
}
},
closeNavEvent(item,index){// 删除选项卡事件
if(item.path === this.fixedPath && this.items.length === 1)return;
this.$refs.li[index].style.transition = 'none';
this.items.splice(index,1);
if(this.currentIndex >= index){// 要删除的选项卡下标等于选中的选项卡下标时,删除后选中的下标-1
this.currentIndex = this.currentIndex - 1;
}
this.$emit('deleteComponent',item);// 把删除的元素的传出
if(this.currentIndex >= 0){// 只有在选中下标大于0时才会执行点击事件
this.$emit('click',this.items[this.currentIndex],this.currentIndex);
}else {
this.$emit('click',this.defaultObject,-1);
}
this.display = 'none';
},
contextMenuEvent(event,item,index){// 右键点击事件
let width = event.clientX,height = event.clientY;
this.leftWidth = width;
this.topHeight = height;
this.display = 'block';
this.rightColseItem = item;
this.rightColseIndex = index;
},
closeThisEvent(){// 关闭当前
this.closeNavEvent(this.rightColseItem,this.rightColseIndex)
},
closeALLEvent(){// 关闭所有
for(let i = 0 ;i<this.items.length;i++){
this.$emit('deleteComponent',this.items[i]);// 把删除的元素的传出
}
this.items = [];
this.$emit('click',this.defaultObject,-1);
},
closeLeftAllEvent(){// 关闭左边所有
if(this.rightColseIndex === 0)return;
for(let i = 0 ;i<this.items.length;i++){
if(i >= this.rightColseIndex)break;
this.$emit('deleteComponent',this.items[i]);// 把删除的元素的传出
}
this.items = this.items.slice(this.rightColseIndex,this.items.length);
},
closeRightALLEvent(){// 关闭右边所有
if(this.rightColseIndex === this.items.length - 1)return;
for(let i = 0 ;i<this.items.length;i++){
if(i <= this.rightColseIndex)continue;
this.$emit('deleteComponent',this.items[i]);// 把删除的元素的传出
}
this.items = this.items.slice(0,this.rightColseIndex + 1);
}
}
}
</script>
<style scoped>
.container{
width: 100%;
flex-direction: column;
height: 100%;
background:#65758a ;
overflow: hidden;
}
.current{
background: rgba(133,133,133,0.4);
}
.drag-nav-li {
transition: all .3s;
min-width: 80px;
height: 100%;
display: inline-block;
padding: 0 10px;
text-align: center;
font-size: 16px;
color: #409EFF;
margin: 0 auto;
border-right: 1px solid #908f8f;
/*position: relative;*/
}
.drag-nav-li span::before{
content: "×";
color: #CCCCCC;
}
.drag-nav-li span:hover{
color: #f43838;
}
.rightDialogUl{
position: absolute;
z-index: 99999;
background: #ffffff;
border-radius: 2px;
border: 1px solid #dedede;
}
.rightDialogUl li{
padding: 0 10px;
line-height: 32px;
color: #333333;
}
.rightDialogUl li:hover{
background: #f0f0f0;
}
</style>
注册为组件,然后直接调用组件
调用示例:
<drag-drop-nav :object="object" :defaultObject="{path:'propertyHome',name:'首页'}" id="path" label="name" @click="navEvent" @deleteComponent="deleteEvent"/>
navEvent(data){// 选项卡点击事件
this.routerFlag = true;
this.$router.push(data.path)
},
deleteEvent(data){// 选项卡删除事件
// 此处excludeData数组存储的是 vue缓存组件keep-alive 的不缓存对象 exclude
// 删除的元素加入到不缓存对象中,
this.excludeData.push(data.path);
},