JSFiddle - React, Tailwind, and code Playground

by chuangchen

HTML

<div id="app">

(后续可改成递归调用,目前只支持显示两级)
<hr>
  <div style="width: 400px;">
      <div :key="x.id"
           v-for="x in data">
        <drag-sort :id="x.id"
                   :has-child-node="x.children && x.children.length"
                   @move="moveTo">
          {{ x.name }}
        </drag-sort>

        <!--    后面可改成递归调用    -->
        <div class="ml-8"
             @move="moveTo"
             v-for="item of x.children" :key="item.id">
          <drag-sort :id="item.id"
                     :has-child-node="item.children && item.children.length"
                     @move="moveTo">
            {{ item.name }}
          </drag-sort>
        </div>

      </div>
    </div>

</div>

<template id="dragSort">
  <div draggable="true" @dragstart="dragstart" @dragleave="dragleave" @dragenter="dragenter" @dragend="dragend" @drop="drop" @dragover="dragover">
    <slot />
  </div>
</template>

CSS

.ml-8 {
  margin-left: 32px;
}

JavaScript

Vue.component('drag-sort', {
  template: '#dragSort',
  props: ['id', 'hasChildNode'],
  data: function() {
    return {
      styleMapping: {
        'below': {'data-origin-border-bottom': 'borderBottom'},
        'above': {'data-origin-border-top': 'borderTop'},
        'inner': {'data-origin-bg-color': 'backgroundColor'},
      },
      styleChangeMapping: {
        'below': {'borderBottom': '1px solid green'},
        'above': {'borderTop': '1px solid green'},
        'inner': {'backgroundColor': 'lightgray'},
      }
    }
  },
  methods: {
    recoverStyle(e, removeAttrData = true) {
      for (const value of Object.values(this.styleMapping)) {
        for (const dataKey of Object.keys(value)) {
          if (e.target.getAttribute(dataKey)) {
            e.target.style[value[dataKey]] = e.target.getAttribute(dataKey)
          }
          if (removeAttrData) {
            e.target.removeAttribute(dataKey)
          }
        }
      }
    },
    checkLevel(floatValue) {
      // 如果有子级:不支持直接拖拽到父级内部,请拖拽到子级的上下位置
      if (this.hasChildNode) {
        if (floatValue < 0.5) {
          return 'above'
        } else {
          return 'below'
        }
      }
      if (floatValue < 0.33) {
        return 'above'
      } else if (floatValue > 0.66) {
        return 'below'
      } else {
        return 'inner'
      }
    },
    dragstart(e) {
      // dataTransfer 似乎只能在 dragstart 设置
      e.dataTransfer.setData("text/plain", this.id + "")
      e.target.style.opacity = '0.4'
    },
    dragenter(e) {
      e.preventDefault()
      // 【记录原始属性值】
      for (const value of Object.values(this.styleMapping)) {
        for (const dataKey of Object.keys(value)) {
          if (!e.target.getAttribute(dataKey)) {
            e.target.setAttribute(dataKey, window.getComputedStyle(e.target)[value[dataKey]]);
          }
        }
      }
    },
    dragleave(e) {
      // [drop的时候,被drop的元素不触发 dragleave]
      this.recoverStyle(e)
    },
    dragend(e) {
     ...