功能需求

在一个表格中按顺序显示图层名称,可以通过拖动表格行的形式改变图层的顺序,通过滑块改变图层透明度。

实现方法

ElementUI的表格不支持拖拽,查找之后决定使用Sortablejs这个JavaScript拖拽库实现。

安装

  • npm

    npm install sortablejs --save
  • script引入

    <script src="../../js/Sortable.min.js"></script>

引入

import Sortable from "sortablejs";

问题

在拖拽表格行后,表格立刻又恢复为原来的顺序。因为vue2.0后引入了虚拟DOM,Sortable拖拽后只改变了真实DOM,虚拟DOM没有发生变化,所以更新后又将真实DOM还原成原来的样子。根本原因是真实DOM和VNode不一致,所以可以通过把拖拽移动真实DOM的操作还原,把DOM的操作交还给Vue。也可以尝试用Vue.Draggable实现。

代码

<template>
  <div class="layer-manage-container">
    <div class="layer-manage-icon" @click="toggleLayerManage"></div>
    <div class="layer-manage-content" v-show="isManageShow">
      <div class="layer-manage-title">
        <span @click="toggleLayerManage"></span>
      </div>
      <el-table
        class="table-content"
        :data="layersData"
        :show-header="false"
        row-key="id"
        align="left"
      >
        <el-table-column
          prop="name"
          label="图层名称"
          align="left"
          header-align="center"
          width="100"
        ></el-table-column>
        <el-table-column
          label="透明度"
          align="left"
          header-align="center"
          width="100"
        >
          <template slot-scope="scope">
            <el-slider
              v-model="scope.row.alpha"
              :show-tooltip="false"
              :max="1"
              :step="0.01"
            ></el-slider>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script>
import Sortable from "sortablejs";
export default {
  props: {
    layers: {
      type: Array,
      default: () => []
    },
    layerChangeFlag: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      layersData: [],
      col: [
        {
          label: "名称",
          prop: "name"
        },
        {
          label: "透明度",
          prop: "alpha"
        }
      ],
      isManageShow: false
      //   dropCol: [
      //     {
      //       label: "名称",
      //       prop: "name"
      //     },
      //     {
      //       label: "透明度",
      //       prop: "alpha"
      //     }
      //   ]
    };
  },
  mounted() {
    // 初始化行拖拽
    this.rowDrop();
    // this.columnDrop();
  },
  watch: {
    /**
     * 监听图层改变
     */
    layerChangeFlag: function() {
      this.getAllowDropLayers();
    }
  },
  methods: {
    /**
     * 获取可以操作的图层
     */
    getAllowDropLayers() {
      this.layersData = [];
      // console.log("this.layers-->", this.layers);
      this.layers.map(layer => {
        if (layer.show == true && layer.inManage == true) {
          this.layersData.unshift(layer);
        }
      });
      // 返回图层管理中的新数据
      this.$emit("layersDataChange", this.layersData);
    },
    formatTooltip(val) {
      return val;
    },
    /**
     * 行拖拽
     */
    rowDrop() {
      const tbody = document.querySelector(".el-table__body-wrapper tbody");
      const _this = this;
      let a = Sortable.create(tbody, {
        onUpdate: function(event) {
          let newIndex = event.newIndex;
          let oldIndex = event.oldIndex;
          /**
           * 解决Vue2.0以后,引入Virtual DOM,导致Virtual DOM和真实DOM之间出现了不一致,使得列表显示与layersData不一致的问题
           * Vue2.0前: 拖拽移动真实DOM -> 操作数据数组 -> Patch算法再更新真实DOM
           * Vue2.0后:拖拽移动真实DOM -> 还原移动操作 -> 操作数据数组 -> Patch算法再更新真实DOM
           */
          let $tr = tbody.children[newIndex];
          let $oldTr = tbody.children[oldIndex];
          // 先删除移动的节点
          tbody.removeChild($tr);
          // 再插入移动的节点到原有节点,还原了移动的操作
          if (newIndex > oldIndex) {
            tbody.insertBefore($tr, $oldTr);
          } else {
            tbody.insertBefore($tr, $oldTr.nextSibling);
          }
          //----------------------------------------------------------------------------------------------------------------//

          // 更新layersData数组
          const currRow = _this.layersData.splice(oldIndex, 1);
          _this.layersData.splice(newIndex, 0, currRow[0]);

          // 翻转数组
          let newArray = [];
          for (let i = _this.layersData.length - 1; i >= 0; i--) {
            newArray.push(_this.layersData[i]);
          }
          _this.$emit("layerLevelChange", newArray);
        }
      });
    },
    //列拖拽
    // columnDrop() {
    //   const wrapperTr = document.querySelector(".el-table__header-wrapper tr");
    //   this.sortable = Sortable.create(wrapperTr, {
    //     animation: 180,
    //     delay: 0,
    //     onEnd: evt => {
    //       const oldItem = this.dropCol[evt.oldIndex];
    //       this.dropCol.splice(evt.oldIndex, 1);
    //       this.dropCol.splice(evt.newIndex, 0, oldItem);
    //     }
    //   });
    // }
    reverseArray(oldArray) {
      return newArray;
    },
    toggleLayerManage() {
      this.isManageShow = !this.isManageShow;
    }
  }
};
</script>
<style lang="scss" scoped>
.layer-manage-icon {
  width: 54px;
  height: 54px;
  background-image: url("~@/assets/imgs/layerManage/icon-hide.png");
  cursor: pointer;
}
.layer-manage-content {
  width: 250px;
  position: relative;
  top: -50px;
  padding: 16px 24px;
  background-color: #fff;
  box-shadow: 0px 0px 6px 0px rgba(102, 102, 102, 0.25);
  box-sizing: border-box;
  border-radius: 5px;

  .layer-manage-title {
    height: 13px;
    margin-bottom: 19px;
    & > span {
      display: block;
      width: 24px;
      height: 24px;
      background-image: url("~@/assets/imgs/layerManage/icon-show.png");
      cursor: pointer;
      float: right;
    }
  }
  ::v-deep .table-content {
    cursor: pointer;
    &::before {
      height: 0;
    }
    & tr.el-table__row {
      & td {
        padding: 3px 0;
        border: none;
      }
    }
  }
  ::v-deep .el-slider__button-wrapper {
    z-index: 3;
  }
}
</style>

参考

https://www.jianshu.com/p/d92b9efe3e6a

https://github.com/SortableJS/Vue.Draggable