实现拖曳排序功能

  • 前言
  • 一、功能描述
  • 二、代码实现
  • 三、使用 draggable 组件小结
  • 四、完整代码


前言

在前面一节 《实现添加和删除常用应用功能》 讲到了删除和添加常用应用功能,今天给大家分享一下拖曳排序功能。

一、功能描述

如下图实现拖动图标置换顺序。

vue组件递归实现 vuedraggable拖拽排序 vuedraggable的例子Functional third part vue实现元素拖拽排序_Vue


我最开始本来想自己实现的,将需要拖动的元素的 draggable 属性设置为 true。结果拖了半天没有反应,我以为是不是在 Vue 中不支持拖曳功能,后来才发现在模拟器中使用的是手机,也就是移动端,所以当然无法拖曳了。

再后面找了下相关的组件 vuedraggable ,是一个专门用来实现拖曳的组件,还挺好用的。有了组件,就只需要按照文档操作就可以了。

二、代码实现

  1. 首先下载 vuedraggable 模块。
npm install vuedraggable
  1. 引用模块
import draggable from 'vuedraggable';
 export default {
    components: {draggable}, 
     updated() {
     //在这里可以获取调整顺序之后的list
      console.log(this.appList[0].list);
    },
}
  1. 编写界面(这里只贴核心代码)
    需要用到的组件是 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 组件小结:

  1. 使用 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>
  1. 在写迭代元素时,: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>