el-tree树节点同层级移动,节点上移、下移

1,051 阅读1分钟

封装的组件:

<template>
  <div class="tree-move-search">
    <!-- 搜索框 -->
    <div v-if="searchInputVisible" class="search-ctx">
      <el-input
        v-model="searchValue"
        size="small"
        clearable
        :placeholder="placeholder"
        suffix-icon="el-icon-search"
      />
    </div>
    <!-- 中间操作按钮 -->
    <div class="btnWrap">
      <slot name="btnWrap" />
    </div>
    <div v-show="!searchValue" class="tree-wrapper">
      <el-tree
        ref="moveTree"
        :props="defaultProps"
        check-on-click-node
        :expand-on-click-node="false"
        :data="treeData"
        highlight-current
        node-key="id"
        draggable
        :lazy="lazy"
        :load="loadNode"
        :allow-drop="allowDrop"
        :filter-node-method="filterNode"
        :default-expanded-keys="defaultExpandedKeys"
        @node-click="nodeClick"
        @node-drop="handleDrop"
      >
        <div slot-scope="{ node, data }" class="custom-tree-node text-overflow">
          <!-- 自定义节点内容 -->
          <slot name="nodeText" v-bind="data">
            <div>
              <span class="node-text ml5">{{ node.label }}</span>
            </div>
          </slot>
          <!-- 节点右侧操作列表 -->
          <div @click.stop="()=>{}">
            <el-dropdown
              class="operator"
              trigger="click"
              @command="(command)=>{
                handleCommand(command,node)
              }">
              <span class="el-dropdown-link">
                <i class="el-icon-more" />
              </span>
              <el-dropdown-menu slot="dropdown">
                <slot name="operator" v-bind="node" />
              </el-dropdown-menu>
            </el-dropdown>
          </div>
        </div>
      </el-tree>
    </div>
    <!-- 搜索内容展示 -->
    <div v-show="searchValue" class="search-content">
      <slot name="searchContent" />
    </div>
  </div>
</template>

<script>
export default {
  components: {},
  props: {
    searchVal: {
      type: String,
      require: true,
    },
    searchInputVisible: {
      type: Boolean,
      default: true,
    },
    placeholder: {
      type: String,
      default: '请输入',
    },
    treeData: {
      type: Array,
      require: true,
    },
    defaultProps: {
      type: Object,
      default: () => ({
        label: 'name',
        children: 'children',
        isLeaf: 'leaf',
      }),
    },
    lazy: {
      type: Boolean,
      default: false,
    },
    loadNode: {
      type: Function,
    },
    defaultExpandedKeys: {
      type: Array,
      default: () => [],
    },
  },
  computed: {
    searchValue: {
      get() {
        return this.searchVal
      },
      set(val) {
        this.$emit('update:searchVal', val)
      },
    },
    nameIndex() {
      return (name) => {
        return name.indexOf(this.searchValue)
      }
    },

  },
  watch: {
    searchValue(val) {
      this.$refs.moveTree?.filter(val)
    },
  },
  created() {

  },
  mounted() {
    this.$emit('TreeInstance', this.$refs.moveTree)
  },
  methods: {
    filterNode(value, data) {
      if (!value) return true
      return data.name.indexOf(value) !== -1
    },
    nodeClick(...args) {
      this.$emit('nodeClick', ...args)
    },
    // 搜索父节点
    searchNodeById(nodes, targetId) {
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i]

        if (node.id === targetId) {
          return node
        }

        if (node.children) {
          const result = this.searchNodeById(node.children, targetId)
          if (result) {
            return result
          }
        }
      }

      return null
    },

    move(node, type) {
      const data = node.data
      const parent = node.parent
      const childNodes = parent.childNodes
      const cIndex = childNodes.findIndex((n) => n.data.id === data.id)
      // 被移动的节点
      const passiveNode = childNodes[cIndex]
      // 移动完成后的上一个节点
      let beforeNode = null
      // 上移
      if (type === 'up') {
        // 参照节点
        const referenceNode = childNodes[cIndex - 1]
        beforeNode = childNodes[cIndex - 2]

        this.$refs.moveTree.remove(passiveNode)
        this.$refs.moveTree.insertBefore(passiveNode.data, referenceNode)
      }
      // 下移
      else {
        // 参照节点
        const referenceNode = childNodes[cIndex + 1]
        beforeNode = referenceNode

        this.$refs.moveTree.remove(passiveNode)
        this.$refs.moveTree.insertAfter(passiveNode.data, referenceNode)
      }
      console.log(passiveNode, 'passiveNode')
      return {
        orgId: passiveNode.data.id,
        // 移动到第一个时,beforeNode是不存在的,所以这里要进行判断
        targetBeforeOrgId: beforeNode ? beforeNode.data.id : '',
        targetParentOrgId: passiveNode.data.parent,
      }
    },
    handleCommand(command, node) {
      let moveInfo = null
      switch (command) {
      case 'up':
        moveInfo = this.move(node, 'up')
        break
      case 'down':
        moveInfo = this.move(node, 'down')
        break
      }
      this.$emit('triggerCommand', { command, node, moveInfo })
    },
    // tree拖拽成功完成时触发的事件
    handleDrop(draggingNode, dropNode, dropType) {
      console.log(draggingNode, dropNode, dropType)
      const info = {
        id: draggingNode.data.id,
        targetBeforeId: dropNode.data.id,
        targetParentId: dropNode.parent.data.id,
      }
      // 如果是before说明是拖到了第一个位置
      if (dropType === 'before') {
        info.targetBeforeOrgId = ''
      }
      console.log(info)
      this.$emit('drop', info)
    },
    // 拖拽时判定目标节点能否被放置
    // 'prev'、'inner' 和 'next',分前、插入、后
    allowDrop(draggingNode, dropNode, type) {
      console.log(draggingNode, dropNode, type)
      if (draggingNode.level === dropNode.level) {
        // 父节点相同
        if (dropNode.parent.childNodes.find((item)=>item.data.id === draggingNode.data.id)) {
          /* 
              这里或的顺序很重要 
              此时的写法表示:首先判断是否可以移动到某个元素的下方,然后再判断是否可以移动到某个元素的上方
           */
          return type === 'next' || type === 'prev'
        }
        return false
      }
      // 不同级进行处理
      return false
    },
  },
}
</script>
<style scoped lang="scss">
.tree-move-search{
  padding: 20px;
  .tree-wrapper{
    margin-top: 20px;
    .custom-tree-node{
      display: flex;
      justify-content: space-between;
      width: 100%;
      &:hover{
        color: #4a60ec;
      }

      .node-text{
        .search-value{
          color: #4a60ec;
        }
      }
      .operator:hover{
        background: #f0f1f4;
      }
    }
  }
  .search-content{
    margin-top: 20px;
  }
}
</style>

组件的使用:


<template>
  <div id="app">
    <TreeMoveSearch :treeData="treeData" :defaultProps="defineProps">
      <template #operator="node">
        <el-dropdown-item
          v-for="item in filterDropdownList(node)"
          :key="item.label"
          :command="item.type"
        >
          {{ item.label }}
        </el-dropdown-item>
      </template>
    </TreeMoveSearch>
  </div>
</template>

<script>
import TreeMoveSearch from '@/components/tree-move-search/index.vue'
export default{
  components:{
    TreeMoveSearch
  },
  data() {
    return {
      treeData: [{
          id: 1,
          label: '一级 1',
          children: [{
            id: 4,
            label: '二级 1-1',
            children: [{
              id: 9,
              label: '三级 1-1-1'
            }, {
              id: 10,
              label: '三级 1-1-2'
            }]
          }]
        }, {
          id: 2,
          label: '一级 2',
          children: [{
            id: 5,
            label: '二级 2-1'
          }, {
            id: 6,
            label: '二级 2-2'
          }]
        }, {
          id: 3,
          label: '一级 3',
          children: [{
            id: 7,
            label: '二级 3-1'
          }, {
            id: 8,
            label: '二级 3-2'
          }]
        }],
      defineProps:{
        label:'label',
        children:'children'
      }
    }
  },
  methods:{
      // 过滤操作按钮
      filterDropdownList(node) {
        console.log(node);
      const data = node.data
      const parent = node.parent
      const childNodes = parent.childNodes
      const cLen = childNodes.length
      const cIndex = childNodes.findIndex((n) => n.data.id === data.id)

      // 根节点
      if (cIndex === 0 && cIndex === cLen - 1) {
        return [
          { label: '添加子组织', type: 'add' },
          { label: '修改组织', type: 'update' },
        ]
      } else if (cIndex === 0) {
        //第一个
        return [
          { label: '添加子组织', type: 'add' },
          { label: '修改组织', type: 'update' },
          { label: '下移', type: 'down' },
          { label: '删除', type: 'delete' },
        ]
      } else if (cIndex === cLen - 1 ) {
        // 最后一个
        return [
          { label: '添加子组织', type: 'add' },
          { label: '修改组织', type: 'update' },
          { label: '上移', type: 'up' },
          { label: '删除', type: 'delete' },
        ]
      }
      return [
        { label: '添加子组织', type: 'add' },
        { label: '修改组织', type: 'update' },
        { label: '上移', type: 'up' },
        { label: '下移', type: 'down' },
        { label: '删除', type: 'delete' },
      ]
    },
  }
}

</script>

<style scoped>

</style>