实现拖曳排序功能
- 前言
- 一、功能描述
- 二、代码实现
- 三、使用 draggable 组件小结
- 四、完整代码
前言
在前面一节 《实现添加和删除常用应用功能》 讲到了删除和添加常用应用功能,今天给大家分享一下拖曳排序功能。
一、功能描述
如下图实现拖动图标置换顺序。
我最开始本来想自己实现的,将需要拖动的元素的 draggable 属性设置为 true。结果拖了半天没有反应,我以为是不是在 Vue 中不支持拖曳功能,后来才发现在模拟器中使用的是手机,也就是移动端,所以当然无法拖曳了。
再后面找了下相关的组件 vuedraggable ,是一个专门用来实现拖曳的组件,还挺好用的。有了组件,就只需要按照文档操作就可以了。
二、代码实现
- 首先下载 vuedraggable 模块。
npm install vuedraggable
- 引用模块
import draggable from 'vuedraggable';
export default {
components: {draggable},
updated() {
//在这里可以获取调整顺序之后的list
console.log(this.appList[0].list);
},
}
- 编写界面(这里只贴核心代码)
需要用到的组件是 draggable 和 transition-group。然后把迭代放在这两个组件里面就可以了。
<draggable class="wrapper" v-model="itemList.list">
<transition-group class="cm-flex flex-wrap">
<div :class="[isShow == 1 ? 'box-style-bg': 'box-style']"
v-for="(item,index) in itemList.list"
:key="item.appName"
>
<div class="modify-empty"></div>
<div class="cm-tx-c">
<div><img class="img-size"
:src="item.appIcon"
alt=""></div>
<div class="label-font cm-mt-03">{{item.appName}}</div>
</div>
<div class="modify-empty" v-if="itemList.id == 1">
<div class="operate-style" v-if="isShow == 1 && itemList.id == 1"
@click="changeSort(index1,index,'reduce',item.appName)">-
</div>
</div>
<div class="modify-empty" v-if="itemList.id != 1">
<div class="operate-style-add" :ref="item.appName" v-if="isShow == 1 && itemList.id != 1"
@click="changeSort(index1,index,'add',item.appName)">+
</div>
</div>
</div>
</transition-group>
</draggable>
三、使用 draggable 组件小结
使用 draggable 组件小结:
- 使用 draggable 组件三步走;第一,draggable 组件放在最外层;第二,transition-group 组件放在第二层;第三,自己写的迭代组件放在最里面。
<draggable class="wrapper" v-model="itemList.list">
<transition-group class="cm-flex flex-wrap">
<div v-for = "item in [1,2,3,4]" :key="item">{{item}}</div>
</transition-group>
</draggable>
- 在写迭代元素时,:key 一定要设置,而且最好是数值或者字符串,也不要用 index,会报错。最理想的是使用不会重复的属性值。
四、完整代码
《实战 Vue 第6天:实现添加和删除常用应用功能》 和 《实战 Vue 第7天:实现拖曳排序功能》 完整代码如下:
<template>
<div class="bg-color">
<div v-for="(itemList,index1) in appList" class="cm-bg-white" v-if="itemList.id == 1 && !isDrag">
<div class="cm-flex cm-jc-sb cm-mrl-percent1 cm-pt-10 cm-pt-10">
<div class="cm-fw-bold">{{itemList.typeName}}<span class="drag-style"> ( 按住拖动调整排序 )</span></div>
<div class="edit-color" @click="displayOperate('edit')">编辑
</div>
</div>
<div class="cm-flex flex-wrap cm-mt-10 cm-mb-10 cm-mrl-percent1 cm-pb-10" >
<div :class="[isShow == 1 ? 'box-style-bg': 'box-style']"
v-for="(item,index) in itemList.list"
:key="item.appName"
draggable="false"
@dragstart="drag(event)"
@dragover="allowDrop(event)"
@drop="drop(event)"
>
<div class="modify-empty"></div>
<div class="cm-tx-c">
<div><img class="img-size"
:src="item.appIcon"
alt=""></div>
<div class="label-font cm-mt-03">{{item.appName}}</div>
</div>
<div class="modify-empty" v-if="itemList.id == 1">
<div class="operate-style" v-if="isShow == 1 && itemList.id == 1"
@click="changeSort(index1,index,'reduce',item.appName)">-
</div>
</div>
<div class="modify-empty" v-if="itemList.id != 1">
<div class="operate-style-add" :ref="item.appName" v-if="isShow == 1 && itemList.id != 1"
@click="changeSort(index1,index,'add',item.appName)">+
</div>
</div>
</div>
</div>
</div>
<div v-for="(itemList,index1) in appList" class="cm-bg-white" v-if="itemList.id == 1 && isDrag">
<div class="cm-flex cm-jc-sb cm-mrl-percent1 cm-pt-10 cm-pt-10">
<div class="cm-fw-bold">{{itemList.typeName}}<span class="drag-style"> ( 按住拖动调整排序 )</span></div>
<div class="edit-color" @click="displayOperate('done')">完成
</div>
</div>
<div class="cm-mt-10 cm-mb-10 cm-mrl-percent1 cm-pb-10" >
<draggable class="wrapper" v-model="itemList.list">
<transition-group class="cm-flex flex-wrap">
<div :class="[isShow == 1 ? 'box-style-bg': 'box-style']"
v-for="(item,index) in itemList.list"
:key="item.appName"
>
<div class="modify-empty"></div>
<div class="cm-tx-c">
<div><img class="img-size"
:src="item.appIcon"
alt=""></div>
<div class="label-font cm-mt-03">{{item.appName}}</div>
</div>
<div class="modify-empty" v-if="itemList.id == 1">
<div class="operate-style" v-if="isShow == 1 && itemList.id == 1"
@click="changeSort(index1,index,'reduce',item.appName)">-
</div>
</div>
<div class="modify-empty" v-if="itemList.id != 1">
<div class="operate-style-add" :ref="item.appName" v-if="isShow == 1 && itemList.id != 1"
@click="changeSort(index1,index,'add',item.appName)">+
</div>
</div>
</div>
</transition-group>
</draggable>
</div>
</div>
<div v-for="(itemList,index1) in appList" class="cm-bg-white" v-if="itemList.id > 1">
<div class="cm-flex cm-jc-sb cm-mrl-percent1 cm-pt-10 cm-pt-10">
<div class="cm-fw-bold">{{itemList.typeName}}</div>
</div>
<div class="cm-mt-10 cm-flex flex-wrap cm-mb-10 cm-mrl-percent1 cm-pb-10" >
<div :class="[isShow == 1 ? 'box-style-bg': 'box-style']"
v-for="(item,index) in itemList.list"
>
<div class="modify-empty"></div>
<div class="cm-tx-c">
<div><img class="img-size"
:src="item.appIcon"
alt=""></div>
<div class="label-font cm-mt-03">{{item.appName}}</div>
</div>
<div class="modify-empty" v-if="itemList.id == 1">
<div class="operate-style" v-if="isShow == 1 && itemList.id == 1"
@click="changeSort(index1,index,'reduce',item.appName)">-
</div>
</div>
<div class="modify-empty" v-if="itemList.id != 1">
<div class="operate-style-add" :ref="item.appName" v-if="isShow == 1 && itemList.id != 1"
@click="changeSort(index1,index,'add',item.appName)">+
</div>
</div>
</div>
</div>
</div>
<div><img class="img-bottom" :src="bottomObj.bgImg" alt=""></div>
</div>
</template>
<script>
import '../../../css/common.css';
import alreadyAdd from '../assets/alreadyAdd.png';
import footerImg from '../assets/footer.png';
import draggable from 'vuedraggable';
import {getApi} from '../../../utils/common';
import Config from '../../../envConfig';
let self;
export default {
components: {draggable},
data(){
return {
appList: [{
list: [
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用2',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用3',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用4',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用5',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用6',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用7',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用8',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用9',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用10',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用11',
homepageLink: ''
},
], typeName: '常用的应用', id: 1
}, {
list: [
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用111',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用112',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用113',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用114',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用115',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用116',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用117',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用118',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用119',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用120',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用121',
homepageLink: ''
},
], typeName: '娱乐', id: 2
}
, {
list: [
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1110',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1120',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1130',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1140',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1150',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1160',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1170',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1180',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1190',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1200',
homepageLink: ''
},
{
appIcon: "https://static.dingtalk.com/media/lALPDeC2t_t1SR3NAZDNAZA_400_400.png_450x10000q90.jpg",
appName: '应用1210',
homepageLink: ''
},
], typeName: '生活', id: 3
}
],
bottomObj: {
bgImg: footerImg
},
typeList: [
{id: 1, typeName: '我的应用', list: []},
{id: 2, typeName: 'OA协同', list: []},
{id: 3, typeName: '信息中心', list: []},
{id: 4, typeName: '钉钉办公', list: []},
{id: 5, typeName: '微创生活', list: []},
],
isShow: 0,
minNum:6,
maxNum:11,
isDrag:false
}
},
updated() {
console.log(this.appList[0].list);
},
created(){
self = this;
self.getAppList();
},
methods: {
drag:(ev)=>{
console.log(ev);
},
allowDrop:(ev)=>{
ev.preventDefault();
},
drop:(ev)=>{
console.log(ev);
},
changeSort: (index1, index2, str,appName) => {
var arr = self.appList[index1].list;
//如果点击的是删除按钮
if (str == 'reduce') {
if (arr.length > self.minNum) {
//常用应用个数大于4才允许继续添加
for (let j = 1; j < self.appList.length; j++) {
for (let i = 0; i < self.appList[j].list.length; i++) {
if (self.appList[0].list[index2].appName == self.appList[j].list[i].appName) {
//判断当删除的对象是从非常用应用中添加上来的,点击删除时还需要将对象恢复原样
self.$refs[appName][0].removeChild(self.$refs[appName][0].firstChild);
self.$refs[appName][0].innerHTML = "+";
self.$refs[appName][0].className = "operate-style-add";
self.$refs[appName][0].style.background = "red";
break;
}
}
}
//通过splice方法将当前对象从数组中删除。
self.appList[index1].list = arr.splice(0, index2).concat(arr.splice(1, arr.length - 1));
} else {
//常用应用个数小于或等于4则弹出提示框
self.$alert('自建应用不能少于'+self.minNum+'个', '警告', {
confirmButtonText: '确定',
});
}
} else {
//如果点击的是添加按钮
if (self.appList[0].list.length >= self.maxNum) {
//添加时如果大于或等于11个应用时,则弹出提示框
self.$alert('自建应用不能多于'+self.maxNum+'个', '警告', {
confirmButtonText: '确定',
});
} else {
for (let k = 0; k < self.appList.length; k++) {
if(index1 == k){
//当前对象没有图片元素时,则需要添加图片元素,消除掉加号
if (self.$refs[appName][0].getElementsByTagName('img').length == 0) {
self.$refs[appName][0].style.background = "#eee";
let img = document.createElement('img');
img.src = alreadyAdd;
img.style.width = '0.8rem';
img.className = "alreadyAdd-size";
self.$refs[appName][0].removeChild(self.$refs[appName][0].firstChild);
self.$refs[appName][0].appendChild(img);
self.appList[0].list.push(self.appList[index1].list[index2]);
break;
}
}
}
}
}
},
displayOperate: (operate) => {
//判断是否显示编辑按钮,编辑按钮在第一行显示
self.isShow = (self.isShow == 0) ? 1 : 0;
self.isDrag = !self.isDrag;
if(operate == 'done'){
getApi('',{},function (res) {
})
}
},
getAppList: () => {
getApi(Config.serverUrl + "/appInfo/getappList", {
pageCount: 1,
pageSize: 20
},function (res) {
if (res.data.isSuc) {
var list = res.data.result;
for (var i = 0; i < self.typeList.length; i++) {
var item = self.typeList[i];
for (var j = 0; j < list.length; j++) {
if (item.id == list[j].typeId) {
self.typeList[i].list.push(list[j]);
}
}
}
self.appList = self.typeList;
}
})
},
}
}
</script>
<style scoped>
.img-size {
width: 3.5rem;
height: 3.5rem;
border-radius: 50%;
}
.edit-color {
color: #5dbdfd
}
.bg-color {
background: #f2f9ff
}
.flex-wrap {
flex-wrap: wrap;
}
.label-font {
color: #363636;
font-size: 0.8rem;
}
.img-bottom {
width: 100%;
height: 130px;
}
.operate-style {
color: white;
background: red;
width: 0.8rem;
height: 0.8rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.operate-style-add {
font-size: 1rem;
color: white;
background: red;
width: 0.8rem;
height: 0.8rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.drag-style {
color: #999;
font-size: 0.8rem;
}
.box-style-bg{
display: flex;
justify-content: center;
padding: 0.3rem 0;
width: 23%;
margin-left: 1%;
margin-right: 1%;
margin-bottom: 0.3rem;
background: #f2f9ff;
}
.box-style{
display: flex;
justify-content: center;
padding: 0.3rem 0;
margin-left: 1%;
margin-right: 1%;
margin-bottom: 0.3rem;
width: 23%;
}
.modify-empty{
min-width: 0.8rem;
}
</style>