需求描述:
右边标签栏中的每个标签都可以被拖动,是目标元素。左边每个矩形框是容器,要做到三点。
一:标签拖到对应的容器范围内部后要在容器内部添加一个固定样式的元素
二:容器(矩形框)的数量不固定,可以任意删除,也可以从尾部增加
三:已被拖动到容器的元素可被删除,也可以拖动到其他容器里面(移动)
代码分析:
一:拖动的实现
二:拖动结束,元素位置的确定
三:数据的处理
首先是拖动的实现:
1.为需要拖动的目标元素添加属性 draggable = "true"
<div class="tagSet">
<span v-for="item in tagSet" :key="item.id" :draggable="true"
@dragend.stop="dragEnd($event, item)">
{{item.name}}
</span>
</div>
其中dragend是拖拽结束时候触发的事件,stop为修饰符,也就是拖拽鼠标松开时触发的事件,里面的方法一会详解
2.鼠标禁止样式的取消
在拖动的时候发现鼠标的样式是禁止样式,就是一个黑圆圈内部一个斜杠,很不好看。可以通过对容器添加@dragenter.prevent @dragover.prevent属性来解决。
<div class="bigrule" @dragenter.prevent @dragover.prevent>
这里的容器不仅仅指的是那些矩形框,我是给整个页面的那个大盒子加了这个属性,这样拖动的时候在整个大盒子里面鼠标的样式都是我想要的,如果仅仅给那三个框加这个属性,那么在鼠标还没到框的时候就还会出现禁止图标,这可以根据个人的需求自己选择。
至此,拖动功能已经实现,接下来是放置功能
上代码
结构部分
<el-card class="box-card secondCard" v-for="(item, index) in rule" :key="index">
<icon name="times" class="cardclose" @click="closeCard(index)"></icon><!-- 矩形框的删除按钮 -->
<ul class="select"> <!-- 矩形框右上方的交并差按钮 -->
<li v-for="(item1, index1) in selector" :key="index1" @click="clickSelector(index,
index1)" :class="{active:index1 === current[index]}">{{item1}}</li>
</ul>
<span v-for="(item2, name2) in item" :key="name2" class="ruleBox" :draggable="true"
@dragend.stop="exitTagdragEnd($event, index, name2, item2)" @mouseover="showdel(item2)"
@mouseout="hidedel(item2)"> <!-- 拖动后出现的元素 -->
<i class="el-icon-circle-close" v-show="item2.show" @click="delMoveTag(index,
name2)"></i> <!-- 鼠标移入标签后出现的删除 -->
<i class="el-icon-edit-outline" v-show="item2.show" @click="editMoveTagTime(index,
name2, item2)"></i> <!-- 鼠标移除后出现的编辑 -->
<div class="cardRuleText">{{item2.name.name}}</div> <!-- 标签正文 -->
<div class="cardRuleTime"> <!-- 正文下面的时间 -->
<img src="../assets/img/u318.png" alt="" :draggable="false">时间段:{{item2.tim}}
</div>
</span>
</el-card>
效果如下图,第二个标签为鼠标移入样式(矩形框为动态的,故v-for,rule为[],每个矩形框为一个子元素,也是[],里面的子元素span是通过v-for rule的子元素得到的即rule:[ [ {}, {}, {}], [], [] ] )
标签拖动结束后事件,也是核心事件
dragEnd (e, item) {
this.setX()
if (e.pageX < this.screenSize.min || e.pageX > this.screenSize.max) return // 水平方向的位置限制
const y = e.pageY - 140 // 矩形框到页面顶部的距离固定为140px
for (let i = 0; i < this.rule.length; i++) {
if (y > 144 * i + 30 * i && y < 144 * (i + 1) + 30 * i) {
if (this.rule[i].length === 3) return this.$message.warning('一个规则框最多拖入三个标签')
this.which = i // 保存此刻拖进的框
this.dialogTag = true // 让时间弹框出现
this.ruleMiddle = item // 用来临时保存数据
return
}
}
}
setX () {
this.screenSize.min = window.innerWidth / 4.71
this.screenSize.max = window.innerWidth / 1.48
}
根据结束事件触发时,确定鼠标相对于文档的位置,pageX,pageY。
首先,是对拖动位置的限定,当x轴方向的位置过大或者过小时,直接结束。但是我的布局是垂直方向的高度是固定的,水平方向用的是element-ui的栅格,类似于百分比。所以当屏幕变化时,pageY是不会变化的,pageX会随着屏幕宽度的变化而变化。在这里,我用了window.innerWidth来监控浏览器窗口的宽度,通过一个比例来确定不同窗口下的X轴方向的范围。
具体的操作是,把标签拖动矩形框的左右两边,打印出pageX的值,然后拿到window.innerWidth的值,一除就是这个比例(这样的准确度不严谨,应该是通过对页面布局元素宽度的计算得出的,但是对于拖动的横向来说,粗略一点没啥影响)。
X轴方向的限定只需要一个左右的极限范围,接下来是Y轴纵向的限定。
确定拖动到了哪个元素框里面,这里做了一个每个框元素个数的限定,最多三个、
其实拖动结束后,这里还有一个日期弹框的出现,需要额外绑定数据,也就是因为这个时间,我在代码里面加了一个ruleMiddle先把标签的数据保存,然后等日期信息ok了,再一起保存进对象里面。
下面是日期弹框关闭后,把标签以及日期的数据完整的保存
// 标签弹出框关闭事件,把标签的数据与日期数据一同保存在数组里面
dialogTagClose () {
this.dialogTag = false
var time1 = new Date(this.valueTime[0])
var time2 = new Date(this.valueTime[1])
const timeNew1 = time1.getTime() / 1000 >> 0 // 转为参数需要的时间戳
const timeNew2 = time2.getTime() / 1000 >> 0
// console.log(time1, timeNew1, time2, timeNew2)
var time = getDate(time1, time2) // 将两个时间转为可视化格式
for (let i = 0; i < this.rule.length; i++) { // 数据的完整保存
if (this.which === i) {
this.rule[i].push({ name: this.ruleMiddle, tim: time, start: timeNew1, end:
timeNew2, show: false }) // show:false 是为了鼠标移入事件准备的
}
}
this.valueTime = '' // 把日期控件绑定数据清空
this.ruleMiddle = '' // 把暂存区清空
}
以上是标签的拖动生成,那么剩下的是矩形框的增删,矩形框元素的删除以及跨矩形框的拖动
在结构部分我给矩形框加了删除按钮,给span加了删除与编辑按钮,并且开启了拖动。(再放一遍)
<el-card class="box-card secondCard" v-for="(item, index) in rule" :key="index">
<icon name="times" class="cardclose" @click="closeCard(index)"></icon><!-- 矩形框的删除按钮 -->
<ul class="select"> <!-- 矩形框右上方的交并差按钮 -->
<li v-for="(item1, index1) in selector" :key="index1" @click="clickSelector(index,
index1)" :class="{active:index1 === current[index]}">{{item1}}</li>
</ul>
<span v-for="(item2, name2) in item" :key="name2" class="ruleBox" :draggable="true"
@dragend.stop="exitTagdragEnd($event, index, name2, item2)" @mouseover="showdel(item2)"
@mouseout="hidedel(item2)"> <!-- 拖动后出现的元素 -->
<i class="el-icon-circle-close" v-show="item2.show" @click="delMoveTag(index,
name2)"></i> <!-- 鼠标移入标签后出现的删除 -->
<i class="el-icon-edit-outline" v-show="item2.show" @click="editMoveTagTime(index,
name2, item2)"></i> <!-- 鼠标移除后出现的编辑 -->
<div class="cardRuleText">{{item2.name.name}}</div> <!-- 标签正文 -->
<div class="cardRuleTime"> <!-- 正文下面的时间 -->
<img src="../assets/img/u318.png" alt="" :draggable="false">时间段:{{item2.tim}}
</div>
</span>
</el-card>
矩形框的增删
// 删除矩形框
closeCard (index) {
this.rule.splice(index, 1)
this.current.splice(index, 1)
},
// 新增矩形框
addCard () {
this.rule.push([])
this.current.push(1)
}
}
矩形框元素的拖动
exitTagdragEnd (e, index, index1, item) {
this.setX()
if (e.pageX < this.screenSize.min || e.pageX > this.screenSize.max) return
const y = e.pageY - 140
for (let i = 0; i < this.rule.length; i++) {
if (y > 144 * i + 30 * i && y < 144 * (i + 1) + 30 * i) {
this.which = i
if (this.rule[i].length === 3) return this.$message.warning('一个规则框最多拖入三个标签')
this.rule[i].push(item)
this.rule[index].splice(index1, 1) // 把原来矩形框的数据删除
return
}
}
}
拖动其实就多了一个把原来矩形框数据的删除,这个和原来的拖动事件应该可以合并,加一个判断。
矩形框元素的删除
在上文的日期弹框关闭保存数据里面,我给每一个元素加了一个show:false的属性,然后把移入显现绑定它,这样我可以控制每个元素的按钮的鼠标移入显现与移除消失
// 拖动的标签,点击×删除
delMoveTag (index, index1) {
this.rule[index].splice(index1, 1)
}