树形可拖拽排序配置组件

效果

树形可拖拽排序配置组件

使用场景

vue2下自定义表格表头配置: 列排序,显示/隐藏等。确保表头以配置项的形式加载,这样表格才能对修改后的配置作响应

思路

1、表格使用render函数加载(如有疑问可私信),通过类似如下的columns配置表头

columns: [
          { label: '姓名', prop: 'name', width: '160', fixed: true },
          { label: '性别', prop: 'sex', align: 'center', width: '160', fixed: false },
          { label: '证件类别', prop: 'cardType', align: 'center', width: '160', fixed: false },
          { label: '证件号', prop: 'idCard', align: 'center', width: '260', fixed: false },
          { label: '手机号', prop: 'mobile', align: 'center', width: '160', fixed: false },
          { label: '标签', prop: 'tag', align: 'center', width: '160', fixed: false },
          { label: '操作', prop: 'manage', align: 'center', disControl: true },
]

2、通过element-ui 的 el-dropdown 组件完成配置项的展示动作

<el-dropdown trigger="click" :hide-on-click="false">
      <span class="el-icon-setting" style="font-size: 25px"></span>
      <template slot="dropdown">
        <el-dropdown-menu>
          <JSelectionItem :arr.sync="rows" @item-click="itemClickHandle">
            <template v-slot:default="{item,level}">
              <div class="item-custom">
                {{item.id}}
              </div>
            </template>
            <template v-slot:tool="{item,level}">
              <span class="el-icon-check"
                    v-if="item.visible"
                    style="size: 20px;margin-top: 5px">
              </span>
            </template>
          </JSelectionItem>
        </el-dropdown-menu>
      </template>
    </el-dropdown>

3、使用vue.draggable 组件完成拖拽效果,具体应用可参考draggable
4、树形结构往往需要递归加载,我选择使用组件递归而非在render中通过js递归去完成配置组件的加载,因为使用render过程中碰到拖拽动画失效的问题,没有思路去解决。

代码

使用树形配置项的组件

<template>
  <div>
    <el-dropdown trigger="click" :hide-on-click="false">
      <span class="el-icon-setting" style="font-size: 25px"></span>
      <template slot="dropdown">
        <el-dropdown-menu>
          <JSelectionItem :arr.sync="rows" @item-click="itemClickHandle">
            <template v-slot:default="{item,level}">
              <div class="item-custom">
                {{item.id}}
              </div>
            </template>
            <template v-slot:tool="{item,level}">
              <span class="el-icon-check"
                    v-if="item.visible"
                    style="size: 20px;margin-top: 5px">
              </span>
            </template>
          </JSelectionItem>
        </el-dropdown-menu>
      </template>
    </el-dropdown>

  </div>
</template>

<script>
import JSelectionItem from "./JSelectionItem";

export default {
  name: "JHeadManage",
  components: {  JSelectionItem },
  data() {
    return {
      rows: []
    }
  },
  created() {
    this.getResource()
  },
  methods: {
    itemClickHandle(item) {
      item.visible = !item.visible
      console.log(item)
    },
    getResource() {
      const vm = this
      //测试用数据只有一个根节点 "0" 方便构建测试用数据树
      const data = [
        { id: '1', parentId: '0' },
        { id: '2', parentId: '0' },
        { id: '2-0', parentId: '2' },
        { id: '1-0', parentId: '1' },
        { id: '1-1', parentId: '1' },
        { id: '1-1-0', parentId: '1-1' },
        { id: '1-1-0-0', parentId: '1-1-0' },
        { id: '1-1-0-1', parentId: '1-1-0' },
        { id: '1-2', parentId: '1' },
        { id: '1-2-0', parentId: '1-2' },
        { id: '1-2-1', parentId: '1-2' },
      ]
      //1) 简单处理数据用于自定义渲染; checked:  indeterminate:
      data.forEach((d, index) => {
        d.visible = true //是否选中
        d.isIndeterminate = false//是否是半选状态
        d.a = 'a' + index
      })
      vm.rows = vm.makeTree(data, 'id', 'parentId', '0')
      // vm.rows = data
    },
    /**
     * 构建树,与复选逻辑无关
     * @param data
     * @param idMark
     * @param pIdMark
     * @param rootId
     * @returns {*}
     */
    makeTree(data, idMark, pIdMark, rootId) {
      //转化为字典,id为键值,并添加根节点对象
      let nodeDict = {};
      (nodeDict[rootId] = { children: [] })[idMark] = rootId;
      data.forEach(n => (nodeDict[n[idMark]] = n).children = []);
      data.forEach(d => nodeDict[d[pIdMark]]?.children?.push(d))
      return nodeDict[rootId].children;
    }
  }
}
</script>

<style scoped>

  .item-custom {
    display: inline-flex;
    padding: 10px 0;
    margin-right: auto;
  }

</style>

树型配置项组件

定义了两个插槽,一个用来显示主要内容一个用来放置对选项的操作

<template>
  <ul class="list" :style="{paddingLeft:level==0?'10px':'0'}">
    <draggable v-model="locArr"
               :animation="100"
               handle=".tip"
               @start="onStart"
               @end="onEnd">
      <transition-group>
        <li class="item" v-for="(c,i) in locArr" :key="i">
          <div class="item-inner" :style="{paddingLeft:level*20+'px'}" @click.stop="itemClick(c)">
            <span class="tip el-icon-more-outline"></span>
            <slot v-bind:item="c" v-bind:level="level"></slot>
            <div class="item-tool">
              <slot name="tool" v-bind:item="c" v-bind:level="level"></slot>
            </div>
          </div>
          <template v-if="c.children&&c.children.length>0">
            <JSelectionItem :arr.sync="c.children" :level="level+1" @item-click="itemClick">
              <template v-slot:default="{item,level}">
                <slot v-bind:item="item" v-bind:level="level"></slot>
              </template>
              <template v-slot:tool="{item,level}">
                <slot name="tool" v-bind:item="item" v-bind:level="level"></slot>
              </template>
            </JSelectionItem>
          </template>
        </li>
      </transition-group>
    </draggable>
  </ul>
</template>

<script>
import draggable from 'vuedraggable'
import JSelectionItem from "@/views/demo/JSelectionItem";

export default {
  name: "JSelectionItem",
  components: {
    JSelectionItem, draggable
  },
  data() {
    return {}
  },
  computed: {
    locArr: {
      get() {
        return this.arr
      },
      set(val) {
        this.$emit('update:arr', val)
      }
    },
  },
  props: {
    arr: {
      type: Array,
      default: () => []
    },
    level: {
      type: Number,
      default: 0,
    }
  },
  methods: {
    onStart() {

    },
    onEnd() {

    },
    itemClick(item) {
      this.$emit('item-click', item)
    }
  }
}
</script>

<style scoped>
  .list {
    width: 100%;
    margin: 0;
    padding: 0;
  }

  .item {
    padding: 0;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
  }

  .item-inner {
    display: flex;
    width: 100%;
    position: relative;
    justify-content: space-between;
    border-bottom: 1px solid #e5e5e5;
    user-select: none;
  }

  .item-inner:hover {
    /*font-weight: bold;*/
  }

  .item-tool {
    display: flex;
    position: absolute;
    align-items: center;
    right: 10px;
  }

  .tip {
    transform: rotate(-90deg);
    cursor: move;
    font-size: 20px;
    user-select: none;
  }

</style>

上一篇:CSS动画


下一篇:Flex布局