前言

这个 demo 的衍生也是来自于 朋友的问题

需求是类似于 有一个资源池本身是一个组, 另外有 N 个队伍, 然后每一个队伍 有一个领导, 一组成员, 然后 各个元素互斥

可以从 资源池中拖拽元素, 来配置 各个队伍的人员信息

然后 这里也仅仅是一个 简单的需求的实现, 另外增加了一些 特性

  1. 领导这边的拖拽是替换式的, 一个队伍已经有一个领导的情况下, 从其他地方拖拽过来的元素会替换掉已有的领导
  2. 队伍的领导, 成员有最小人员限定, 少于这个人员数量, 则不能再拖拽人员出去

此 demo 基于 vue-draggable, 仅仅用于交流学习 

包含两个文件, 一个 HelloDraggable, 一个 DraggableItem, 前者是业务组件, 后者是基础组件 

HelloDraggable.vue

<template>
  <div class="testParent">
    <span style="position: absolute; top:250px; left: 430px; color: green;"> resource pool </span>
    <span style="position: absolute; top:250px; left: 1380px; color: red;"> leader </span>
    <span style="position: absolute; top:520px; left: 1380px; color: blue;"> members </span>
    <DraggableItem group-name="role" ref="resourcePool" drag-item-id="resourcePool" :init-item-list="resourcePool" :min-retain-item-count="0"
                   @onDragItemAdd="onDragItemAdd" @onDragItemUpdate="onDragItemUpdate"
                   style="position:absolute; top:300px;left:300px;width: 400px;height: 600px; border: green 1px solid;"
    ></DraggableItem>
    <DraggableItem group-name="role" ref="leaderGroup" drag-item-id="leaderGroup" :init-item-list="leaderGroup" :min-retain-item-count="1"
                   @onDragItemAdd="onDragItemAdd" @onDragItemUpdate="onDragItemUpdate"
                   style="position:absolute; top:300px;left:1200px;width: 400px;height: 200px; border: red 1px solid;"
    ></DraggableItem>
    <DraggableItem group-name="role" ref="membersGroup" drag-item-id="membersGroup" :init-item-list="membersGroup" :min-retain-item-count="2"
                   @onDragItemAdd="onDragItemAdd" @onDragItemUpdate="onDragItemUpdate"
                   style="position:absolute; top:550px;left:1200px;width: 400px;height: 300px; border: blue 1px solid;"
    ></DraggableItem>
  </div>
</template>

<script>
  import draggable from 'vuedraggable'
  import DraggableItem from './DraggableItem'

  export default {
    name: "HelloDraggable",
    components: {
      draggable,
      DraggableItem,
    },
    data() {
      return {
        resourcePool: [
          {id: "01", name: "sheldon"},
          {id: "02", name: "leonard"},
          {id: "03", name: "howard"},
          {id: "04", name: "raj"},
          {id: "05", name: "penny"},
        ],
        leaderGroup: [
          {id: "06", name: "alpha"}
        ],
        membersGroup: [
          {id: "07", name: "beta"},
          {id: "08", name: "gamma"},
        ]
      }
    },
    computed: {},
    created() {
    },
    methods: {
      onDragItemAdd(dragContext, itemList) {
        console.log("onDragItemAdd - ", dragContext)
        let toDragItemId = dragContext.toDragItemId
        // 如果是 leaderGroup, 直接替换, 将已有的 item 返回 resourcePool
        if (toDragItemId === "leaderGroup") {
          let newItemId = itemList[dragContext.toDragIndex].id
          let oldItemId = itemList[1-dragContext.toDragIndex].id
          let oldItem = this.findItemById(oldItemId)
          this.$refs.leaderGroup.removeItem(oldItemId)
          this.$refs.resourcePool.addItem(oldItem)
          // 如果是 membersGroup, 则限定最多放入 3 个成员, 默认将最后的成员返回 资源池
        } else if (toDragItemId === "membersGroup") {
          let membersCountMax = 3
          if (itemList.length <= membersCountMax) {
            return
          }

          let toEvictItem = itemList[membersCountMax]
          this.$refs.membersGroup.removeItem(toEvictItem.id)
          this.$refs.resourcePool.addItem(toEvictItem)
        }
      },
      onDragItemUpdate(dragContext, itemList) {
        console.log("onDragItemUpdate - ", dragContext)
      },
      findItemById(itemId) {
        let item = this.findItemByIdInList(this.$data.resourcePool, itemId)
        if(item == null) {
          item = this.findItemByIdInList(this.$data.leaderGroup, itemId)
        }
        if(item == null) {
          item = this.findItemByIdInList(this.$data.membersGroup, itemId)
        }
        return item
      },
      findItemByIdInList(list, itemId) {
        for(let i in list) {
          if(list[i].id === itemId) {
            return list[i]
          }
        }
        return null
      }
    },
  }
</script>

<style>

</style>

DraggableItem.vue

<template>
  <div class="draggableItem">
    <draggable v-model="itemList" :group="groupName" class="dragGroupClass" animation="200"
               @add="onAdd" @update="onUpdate" :move="onMove" >
      <transition-group :id="dragItemId" style="display: block; height: 100%" >
        <div v-for="entity in itemList" :key="entity.id" class="list-element app-container">
          <span class="defaultBlock">{{entity.name}}</span><!---->
          <p></p>
          <div class="xl-tooltip">
            <div class="el-tooltip dv-tooltip-more" aria-describedby="el-tooltip-3283" tabindex="0"
                 style="overflow: hidden;">
                    <span style="box-shadow: transparent 0px 0px; color:red; ">
                        <span :aria-label="entity.name">{{entity.name}}</span>
                    </span>
            </div>
          </div>
        </div>
      </transition-group>
    </draggable>
  </div>
</template>

<script>
  import draggable from 'vuedraggable'

  export default {
    name: "DraggableItem",
    components: {
      draggable
    },
    props: {
      dragItemId: {
        type: String,
      },
      minRetainItemCount: {
        type: Number,
      },
      initItemList: {
        type: Array,
        default: function () {
          return []
        }
      },
      groupName: {
        type: String,
        default: function () {
          return "defGroup"
        }
      }
    },
    data() {
      return {
        itemList: []
      }
    },
    created() {

    },
    mounted() {
      this.initItemList.forEach(item => this.itemList.push(item))
    },
    methods: {
      onAdd(evt) {
        let dragContext = {
          fromDragItemId : evt.from.id,
          toDragItemId : evt.to.id,
          fromDragIndex : evt.oldDraggableIndex,
          toDragIndex : evt.newDraggableIndex
        }
        this.$emit("onDragItemAdd", dragContext, this.itemList)
      },
      onUpdate(evt) {
        let dragContext = {
          fromDragItemId : evt.from.id,
          toDragItemId : evt.to.id,
          fromDragIndex : evt.oldDraggableIndex,
          toDragIndex : evt.newDraggableIndex
        }
        this.$emit("onDragItemUpdate", dragContext, this.itemList)
      },
      onMove(evt, originalEvt) {
        let dragContext = {
          fromDragItemId : evt.from.id,
          toDragItemId : evt.to.id,
          fromDragIndex : evt.oldDraggableIndex,
          toDragIndex : evt.newDraggableIndex
        }
        if(dragContext.fromDragItemId === dragContext.toDragItemId) {
          return true
        }
        if(this.itemList.length <= this.minRetainItemCount) {
          return false
        }
        return true
      },
      addItem(item) {
        this.itemList.push(item)
      },
      removeItem(itemId) {
        for (let i in this.itemList) {
          if (this.itemList[i].id === itemId) {
            this.itemList.splice(i, 1)
          }
        }
      }
    },
  }
</script>

<style>
  .dragGroupClass {
    width: 100%;
    height: 100%;
  }

  .list-element {
    display: inline-block;
    background: #eee;
    /*float:left;*/
    width: 128px;
    height: 128px;
  }

  .app-container {
    width: 90px;
    padding: 17.0666px 8.5334px;
    text-align: center;
    color: #fff;
    font-size: 14.9334px;
    height: 90px;
    position: relative;
  }

  .app-container .defaultBlock {
    background: rgb(70, 176, 190);
    border-radius: 20%;
    width: 60px;
    height: 60px;
    display: block;
    line-height: 65px;
    font-size: 22.9333px;
    margin: 0 auto;
  }

  .app-container:hover {
    background-color: rgba(0, 0, 0, .2);
    cursor: pointer
  }

  .xl-tooltip {
    width: 100%;
    word-break: break-all
  }

  .xl-tooltip .ellipsis {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    outline: none
  }

  .xl-tooltip .ellipsis .show-more-ellipsis {
    display: -webkit-box;
    line-clamp: 2;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    white-space: normal;
    overflow: hidden
  }

</style>

演示效果

36 Vue不同的组相互拖拽小组件的实现_javascript

完