一、前言
【第一次在掘金上写文章,还不太熟悉,如有错误的地方,欢迎各位大佬指导~】
那是一个阳谷明媚的早上,我接了一杯滚烫的热水缓缓走进会议室。
在人员到齐后产品开始讲解这一期的需求~~~
我需要做的地方有很多,但总体来说难度so easy,心里窃喜脸上没表现出来,淡定的开完会后开始动工,然后折磨的旅途就此展开。。。
二、分析需求
其中的一个需求是要做一个横排,多个元素,可以拖拽排序的功能。
嘿,这个功能以前做过,内心窃喜,这不分分钟搞定。
立马翻开以前的代码,用的是vue-smooth-dnd插件,两三下就初步完成了一个可以拖拽的列表。哎嘿,可以提前摸鱼了。
不出意外的第一个坎就来了,vue-smooth-dnd只支持竖列的排序,不支持横着的排序。在不挣扎想换个插件是一天以后的事,期间查了api,找了度娘,甚至问了GPT大爹,最后悬着的心终于是死了。。。
最后的最后甚至动起了歪脑筋,功能不支持我就通过样式去处理。在一番操作后能实现了一排之间互相拖动的功能,又一次沾沾自喜的时候发现如果一排装不下,换到了第二排拖动的时候,非本行进行拖拽的时候排序是乱的。。。
很明显,产品这关是过不了的,在实在没办法的情况下,我换了插件Sortable
三、sortablejs的使用
在打开Sortable官网介绍的时候,脑袋里面的想法是这玩意能救我狗命,里面各种操作和控制都有,充分满足我现在的需求,边看边干。
【用的vue2,以下代码均以vue2来完成】
1.npm安装
npm install sortablejs --save
2.vue导入
import Sortable from 'sortablejs';
3.开始使用
【html部分】
<div ref="sortRef" class="cList">
<div v-for="(item,index) in list" :key="index" class="grid__item">
<div class="listTitle">{{ item.name }}</div>
<div class="isSwitch">
<div class="isTis">是否必填</div>
<el-switch
v-model="item.headerNotNull"
active-color="#13ce66"
inactive-color="#ff4949"
active-value="1"
inactive-value="0">
</el-switch>
</div>
</div>
</div>
【JS部分】
data() {
return {
list:[
{
id:'100',
name:'姓名',
headerNotNull:'1',
},
{
id:'101',
name:'年龄',
headerNotNull:'0',
},
{
id:'102',
name:'性别',
headerNotNull:'0',
},
{
id:'103',
name:'地址',
headerNotNull:'0',
},
],
}
}
mounted() {
this.sortDrag()},methods: {
sortDrag(){
let that = this
this.$nextTick(() => {
let el = this.$refs.sortRef
new Sortable(el, {
animation: 100,
onEnd: (evt) => {
that.list.splice(evt.newIndex, 0, that.list.splice(evt.oldIndex, 1)[0])}
})
})
},
}
拖动是拖动了,但数据和视图完全各是各的,没有毛关系。。。
还能怎么了,接着改呗。。。仔细观察后发现,数据的顺序是对的,视图是乱的,抽象!真抽1象!
直接说出解决办法:在onEnd内手动赋一次值,解决视图更新乱的问题。
onEnd: (evt) => {
that.addWorkArray.splice(evt.newIndex, 0, that.addWorkArray.splice(evt.oldIndex, 1)[0])
let newArray = that.addWorkArray.slice(0)
that.addWorkArray = [] that.$nextTick(()=>{ that.addWorkArray = newArray })
}
完结 撒花~~~
四、功能迭代以及完整代码
怎么可能,在我沾沾自喜的时候,产品过来了,我们的需求有一点小小的变动!
我们需要在设置模块,每个模块下是可以拖动的小字段。模块可以新增,也可以删除,但不能删除第一个模块,模块后能需要增加一个开关,模块可以自己命名。怎么样,很简单吧!
王德发??听到“很简单”这几个字就头皮发麻!
事实上确实,这玩意咋看简单,实际开发中插件自带的一些特性和本次的需求组合成了强力地雷,给我炸的体无完肤!
先贴出修复后的完整代码和实例图,在说说这次遇到的坑
【html部分】
<div class="listN" v-for="(item, index) in listArray" :key="index" :data-list="index" ref="sortableLists">
<!-- 输入框区域 -->
<div class="listNTop">
<i class="el-icon-circle-close delIcon" v-if="index != 0" @click="delList(index)"></i>
<el-input style="width: 300px;" v-model="item.groupName" placeholder="请输入模块名称,无内容则不显示"></el-input>
<el-select style="width: 100px; margin-left: 20px;" v-model="item.groupType" placeholder="请选择">
<el-option v-for="item in textOrSwitch" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<div class="switchTip" v-if="item.groupType == '1'">(在模板中开启状态才能填写当前板块下内容)</div>
</div>
<!-- 字段区域 -->
<div v-for="(it, ind) in item.templateList" :key="ind" :data-id="it.id" class="item">
<div class="listTitle">{{ it.name }}</div>
<div class="isSwitch">
<div class="isTis">是否必填</div>
<el-switch v-model="it.headerNotNull" active-color="#13ce66" inactive-color="#ff4949" active-value="1"
inactive-value="0">
</el-switch>
</div>
</div>
</div>
【JS部分】
data() {
return {
open:false,
id:'',
listArray:[
{
id:'0',
groupName:'基础信息',
groupType:0,
templateList:[
{
id:'100',
name:'姓名',
headerNotNull:'1',
},
{
id:'101',
name:'年龄',
headerNotNull:'0',
},
{
id:'102',
name:'性别',
headerNotNull:'0',
},
{
id:'103',
name:'地址',
headerNotNull:'0',
},
]
}
],
// 文本或开关
textOrSwitch:[
{
label:'文本',
value:0,
},
{
label:'开关',
value:1,
},
],
};
},
mounted() {
this.newSortable()
},
methods: {
// 初始化拖拽
newSortable(){
const options = {
group: 'shared',
preventOnFilter: false, //阻止事件穿透,否则输入框无法输入
filter:'.listNTop',
animation: 100,
onEnd: (evt) => {
this.updateListOrder(evt);
}
};
this.$nextTick(() => {
this.$refs.sortableLists.forEach(list => {
Sortable.create(list, { ...options, dataIdAttr: 'data-list' });
});
});
},
updateListOrder(evt) {
// 获取拖动前和拖动后的索引及列表索引
// 原数组内索引
const fromIndex = evt.oldIndex -1;
// 新数组内索引
const toIndex = evt.newIndex -1;
// 原列表索引
const fromListIndex = parseInt(evt.from.getAttribute('data-list'));
// 新列表索引
const toListIndex = parseInt(evt.to.getAttribute('data-list'));
// 获取拖动前和拖动后的列表
const fromList = this.listArray[fromListIndex].templateList;
const toList = this.listArray[toListIndex].templateList;
console.log('原数组内索引', fromIndex, '新数组内索引', toIndex,'原列表索引',fromListIndex,'新列表索引',toListIndex,'拖动前列表','拖动后列表')
if (toIndex == -1) {
// 不允许拖动
if (fromListIndex === toListIndex) {
// 拖动到同一列表内
this.listArray[fromListIndex].templateList.splice(toIndex, 0, this.listArray[fromListIndex].templateList.splice(fromIndex, 1)[0])
let newData = this.listArray[fromListIndex].templateList.slice(0)
this.listArray[fromListIndex].templateList = []
this.$nextTick(() => {
this.listArray[fromListIndex].templateList = newData
});
} else {
let newData1 = this.listArray[fromListIndex].templateList.splice(fromIndex, 1)[0]
this.listArray[toListIndex].templateList.splice(toIndex, 0, newData1)
let newData = this.listArray[fromListIndex].templateList.slice(0)
this.listArray[fromListIndex].templateList = []
this.$nextTick(() => {
this.listArray[fromListIndex].templateList = newData
});
}
} else {
if (fromListIndex === toListIndex) {
// 拖动到同一列表内
this.listArray[fromListIndex].templateList.splice(toIndex, 0, this.listArray[fromListIndex].templateList.splice(fromIndex, 1)[0])
let newData = this.listArray[fromListIndex].templateList.slice(0)
this.listArray[fromListIndex].templateList = []
this.$nextTick(() => {
this.listArray[fromListIndex].templateList = newData
});
} else {
let newData1 = this.listArray[fromListIndex].templateList.splice(fromIndex, 1)[0]
this.listArray[toListIndex].templateList.splice(toIndex, 0, newData1)
let newData = this.listArray[fromListIndex].templateList.slice(0)
this.listArray[fromListIndex].templateList = []
this.$nextTick(() => {
this.listArray[fromListIndex].templateList = newData
});
}
}
},
// 新增内容
listArrayData(){
this.listArray.push({
groupName:'',
groupType:0,
id:null,
templateList:[]
})
this.$nextTick(() => {
this.newSortable()
});
},
// 删除内容
delList(i){
if(this.listArray[i].templateList.length > 0){
this.$modal.msgError('当前板模块存在内容,需将内容移除后才能删除该模块!')
return
}
if(this.listArray[i].id != null){
this.form.deleteGroupIdList.push(this.listArray[i].id)
}
this.listArray.splice(i,1)
},
}
完成的效果就是这样:
5、细说遇到的问题以及解决思路:
1.新增后不能拖动:是因为拖动模块在mounted的时候就初始化了,所以新增的数据也需要初始化。
this.$nextTick(() => { this.newSortable() });
2.新增后的输入框这个区域是不能被拖动,或者被改变位置。
为了解决这个问题,前面滋生出N个问题,这个插件是根据代码的层级去决定是否可以拖动。
在最开始的时候我在字段区域包裹了一层div方便写样式。结果是字段区域只能整块拖动。
不能拖动用的是api里面的方法:.listNTop是输入框区域的class名
filter:'.listNTop',
然而不让拖动的问题只是视图上得到了解决! 在拖动后虽然拖不上去但数据的结构发生了变化!具体表现为:拖动后回到原来的位置,但报错了,因为他把输入框区域认为是数组内的一个元素,实际上不是,所以报错了。
好心累!
我做了如下的两个操作
a.拿到的索引需要-1,为了处理后面的数据,否则对不上。
// 原数组内索引 const fromIndex = evt.oldIndex -1;
// 新数组内索引 const toIndex = evt.newIndex -1;
b.对索引进行判断,如果索引是-1,那就对数据进行处理
拖动的视图与数据都没问题,正准备长舒一口气的时候,嘿 问题又来了,输入框没办法输东西!
3.输入框无法输入
在仔细观察后发现输入框不是无法输入,是没有获取到被点击选中的焦点。出现这个问题的原因是点击的时候焦点被拖动截取了。真是不让人省心啊,又接着看api,看了半天没看出啥有用的结果,只能问gpt大哥了,大哥给我说用这一句魔法就能解决我当前的困境。
preventOnFilter: false;
尝试以后解决了问题。在回顾api的时候发现,真怪人家,人家写出来了
只能说是急火攻心了,api没仔细看。。。
自此本期使用到sortablejs插件遇到的几个值得记录问题记录完毕,完结撒花~~