<template>
  <table class="table drag-table">
    <colgroup>
      <col v-if="dragEnable" width="20" />
      <col v-for="column in columns" :key="column.prop" :width="column.width" />
    </colgroup>
    <thead>
      <tr>
        <th v-if="dragEnable"></th>
        <th v-for="column in columns" :key="column.prop">
          {{ column.label }}
        </th>
      </tr>
    </thead>
    <tbody
      class="drag-table-tbody"
      @drop.prevent
      @dragover.prevent="dragOver($event)"
      @dragleave.prevent
    >
      <tr
        v-for="row in tableData"
        :key="row.$key"
        :id="row.$key"
        :class="[
          'drag-table-row',
          {
            'after-drop': row.$key === curDropRow.$key && isDragDown,
            'before-drop': row.$key === curDropRow.$key && !isDragDown,
          },
        ]"
        draggable="false"
        @dragstart="($event) => dragStart($event, row)"
        @dragend.prevent="dragEnd"
      >
        <td
          v-if="dragEnable"
          class="drag-handle"
          @mousedown.stop="dragHandleMousedown"
          @mouseup.stop="dragHandleMouseup"
        >
          <i class="el-icon-more"></i>
        </td>
        <td
          v-for="column in columns"
          :key="column.prop"
          :class="column.className"
        >
          <slot :name="column.prop" v-bind:row="row">
            {{ row[column.prop] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
import { randomCode } from "@/plugins/utils";

export default {
  name: "drag-table",
  props: {
    data: {
      type: Array,
      default() {
        return [];
      },
    },
    columns: {
      type: Array,
      default() {
        // {label,prop,width?}[]
        return [];
      },
    },
    sortField: {
      type: String,
      default: "sortNum",
    },
    dragEnable: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      tableData: [],
      curDragRow: {},
      curDropRow: {},
      dragStartPageY: null,
      isDragDown: false,
    };
  },
  methods: {
    buildData(val) {
      if (!val) {
        this.tableData = [];
        return;
      }

      this.tableData = val.map((item, index) => {
        return {
          ...item,
          $index: index + 1,
          $key: randomCode(),
        };
      });
    },
    getRelateElement(dom) {
      let element = null;
      let parentNode = dom;
      while (!element && !parentNode.className.includes("drag-table-tbody")) {
        if (!element && parentNode["id"]) {
          element = parentNode;
        } else {
          parentNode = parentNode.parentNode;
        }
      }

      return element;
    },
    getSiblingRow(targetKey, offset) {
      const pos = this.tableData.findIndex((row) => row.$key === targetKey);
      return this.tableData[pos + offset] || null;
    },
    dragStart(e, row) {
      if (!this.dragEnable) return;
      this.dragStartPageY = e.pageY;
      this.curDragRow = row;
    },
    dragOver(e) {
      if (!this.dragEnable) return;

      // console.log(e.target);
      this.isDragDown = e.pageY > this.dragStartPageY;
      if (e.target.className.includes("drag-table-tbody")) {
        this.curDropRow = this.isDragDown
          ? this.tableData.slice(-1)[0]
          : this.tableData[0];
        return;
      }

      const elementDom = this.getRelateElement(e.target);
      if (!elementDom) return;

      const targetKey = elementDom.id;
      this.curDropRow = this.getSiblingRow(targetKey, 0);
    },
    dragEnd(e) {
      if (!this.dragEnable) return;
      if (this.curDragRow.id === this.curDropRow.id || !this.curDropRow) return;
      // 往下：target上一个位置
      // 往上：target下一个位置
      this.moveElementToElement({
        curRow: this.curDragRow,
        toRow: this.curDropRow,
        isDragDown: this.isDragDown,
      });

      e.target.setAttribute("draggable", false);

      this.curDragRow = {};
      this.curDropRow = {};
      this.dragStartPageY = null;
      this.isDragDown = false;
    },
    moveElementToElement({ curRow, toRow, isDragDown }) {
      const curPos = this.tableData.findIndex(
        (row) => row.$key === curRow.$key
      );
      this.tableData.splice(curPos, 1);

      const toPos = this.tableData.findIndex((row) => row.$key === toRow.$key);
      const offset = isDragDown ? 1 : 0;
      this.tableData.splice(toPos + offset, 0, curRow);

      this.tableData.forEach((row, index) => {
        row[this.sortField] = index + 1;
        row.$index = index + 1;
      });
      this.$emit("sort-change", this.tableData);
    },
    dragHandleMousedown(e) {
      const elementDom = this.getRelateElement(e.target);
      elementDom.setAttribute("draggable", true);
    },
    dragHandleMouseup(e) {
      const elementDom = this.getRelateElement(e.target);
      elementDom.setAttribute("draggable", false);
    },
  },
  watch: {
    data: {
      handler(val) {
        this.buildData(val);
      },
      immediate: true,
    },
  },
};
</script>
