Bootstrap

vue Ant Design Vue 树形控件增删改拖拽操作

<template>
  <page-header-wrapper>
    <a-card class="container">
      <div class="tree-container">
        <div class="left">
          <div class="left-input">
            <a-input placeholder="请输入" style="width: 200px;margin-right: 10px"/>
            <a-button @click="newlyAdded" class="add">新增</a-button>
          </div>
          <!-- 树形组织架构树 -->
          <Tree :tree-data="treeData" :defaultExpandAll="false" draggable @dragenter="onDragEnter" @drop="onDrop">
            <!-- 每个父级前的图标 -->
            <span slot="switcherIcon" class="icon-plus"></span>
            <template slot="custom" slot-scope="item">
              <div>
                <!-- 名称 -->
                <span class="node-title">{{ item.title }} </span>
                <span style="margin-left: 20px">
                  <!-- 新增 -->
                  <span style="margin-right: 10px;">
                    <a-icon type="plus-circle" @click="subordinateItem(item)"/>
                  </span>
                  <!-- 编辑 -->
                  <span style="margin-right: 10px;">
                    <a-icon type="edit" @click="modifyItem(item)"/>
                  </span>
                  <!-- 删除 -->
                  <span style="margin-right: 10px;">
                    <a-popconfirm title="是否要删除此行?" @confirm="deleteItem(item)">
                      <a-icon type="delete"/>
                    </a-popconfirm>
                  </span>
                </span>
              </div>
            </template>
          </Tree>
        </div>
        <div class="right">
          <div class="right-btn">
            <a-button type="primary"><a-icon type="plus" />添加</a-button>
          </div>
          <!-- 表格开始 -->
          <div class="container-table">
            <a-table
              :columns="columns"
              :data-source="data"
            >
              <span slot="action" slot-scope="text, record">
                <template>
                  <a @click="handleEnable(record)">启用</a>
                </template>
                <a-divider type="vertical" />
              </span>
            </a-table>
          </div>
          <!-- 表格结束 -->
        </div>
      </div>

    </a-card>
    <!-- 弹窗开始 -->
    <tree-lower-modules ref="treeLowerModules" @ok="onOk"/>
    <!-- 弹窗结束 -->
  </page-header-wrapper>
</template>

<script>
import treeLowerModules from './components/treeLowerModules.vue'
import { Tree } from 'ant-design-vue'
import { projectList } from '@/views/authority/project/table-header'

export default {
  name: 'OrganizateTree',
  components: { treeLowerModules, Tree },
  props: {},
  data () {
    return {
      // 组织树数据
      treeData: [],
      selectKeys: [],
      // 表格数据
      data: [
        {
          key: '1',
          name: 'John Brown',
          age: 32,
          address: 'New York No. 1 Lake Park',
          tags: ['nice', 'developer'],
          state: 1
        },
        {
          key: '2',
          name: 'Jim Green',
          age: 42,
          address: 'London No. 1 Lake Park',
          tags: ['loser'],
          state: 1
        },
        {
          key: '3',
          name: 'Joe Black',
          age: 32,
          address: 'Sidney No. 1 Lake Park',
          tags: ['cool', 'teacher'],
          state: 1
        }
      ],
      // 表格行
      columns: projectList
    }
  },
  filters: {},
  computed: {},
  watch: {},
  created () {
    this.getTreeData()
  },
  mounted () {
  },
  beforeDestroy () {
  },
  methods: {

    /**
     * 组织树的数据
     */
    getTreeData () {
      this.treeData = [
        {
          title: '0',
          key: '0',
          scopedSlots: { title: 'custom' },
          children: [
            {
              title: '0-0',
              key: '0-0',
              scopedSlots: { title: 'custom' },
              children: [
                {
                  title: '0-0-0',
                  key: '0-0-0',
                  scopedSlots: { title: 'custom' }
                },
                {
                  title: '0-0-1',
                  key: '0-0-1',
                  scopedSlots: { title: 'custom' }
                },
                {
                  title: '0-0-2',
                  key: '0-0-2',
                  scopedSlots: { title: 'custom' }
                }
              ]
            },
            {
              title: '0-1',
              key: '0-1',
              scopedSlots: { title: 'custom' },
              children: [
                {
                  title: '0-1-0',
                  key: '0-1-0',
                  scopedSlots: { title: 'custom' }
                },
                {
                  title: '0-1-1',
                  key: '0-1-1',
                  scopedSlots: { title: 'custom' }
                },
                {
                  title: '0-1-2',
                  key: '0-1-2',
                  scopedSlots: { title: 'custom' }
                }
              ]
            },
            {
              title: '0-2',
              key: '0-2',
              scopedSlots: { title: 'custom' }
            }
          ]
        },
        {
          title: '1',
          key: '1',
          scopedSlots: { title: 'custom' },
          children: [
            {
              title: '1-1',
              key: '1-1',
              scopedSlots: { title: 'custom' }
            },
            {
              title: '1-2',
              key: '1-2',
              scopedSlots: { title: 'custom' }
            },
            {
              title: '1-3',
              key: '1-3',
              scopedSlots: { title: 'custom' }
            }
          ]
        },
        {
          title: '2',
          key: '2',
          scopedSlots: { title: 'custom' }
        }
      ]

    // 请求接口
    // getTreeList().then(res => {
    //    console.log(res.result)
    //    const result = res.result
        // 不一样字段的时候替换下
    //    this.treeData = JSON.parse(JSON.stringify(result).replace(/"parent_id"/g,         
    //    '"key"'))
    //    this.treeData = JSON.parse(JSON.stringify(result).replace(/"group_name"/g, 
    //    '"title"'))
    // 每一项都加 scopedSlots: { title: 'custom' }
    //  this.handleData(this.treeData)
    // https://www.cnblogs.com/xianglian/p/15469956.html
    // https://blog.csdn.net/qq_41579104/article/details/115617070
    //  })
    },

    // 递归每一项都加 scopedSlots: { title: 'custom' }
    // handleData (tree) {
    //   for (const item of tree) {
    //     item['scopedSlots'] = { title: 'custom' }
    //     if (item.children && item.children.length) {
    //       this.handleData(item.children)
    //     }
    //   }
    // },
    /**
     * 组织树新增按钮
     */
    newlyAdded () {
      const item = { key: this.treeData[0].key, operation: 1 }
      this.$refs.treeLowerModules.add(item)
    },

    /**
     * 添加下級
     * @param item
     */
    subordinateItem (item) {
      item.operation = 2
      this.$refs.treeLowerModules.add(item)
    },

    /**
     * 修改
     * @param item
     */
    modifyItem (item) {
      this.$refs.treeLowerModules.edit(item)
    },

    /**
     * 刪除
     * @param item
     */
    deleteItem (item) {
      this.selectKeys = [item.key]
      this.dataDriveDelete()
    },

    /**
     * 確定按鈕
     * @param val
     */
    onOk (val) {
      // 1:一级新增, 3:编辑,  2:二级新增
      if (val.operation === 1) {
        this.selectKeys = [val.key]
        this.dataDriveAddSame(val.title)
      } else if (val.operation === 2) {
        this.selectKeys = [val.key]
        this.dataDriveAddSub(val.title)
      } else if (val.operation === 3) {
        this.selectKeys = [val.key]
        this.dataDriveModify(val.title)
      }
    },

    /**
     * 公共父级修改方法
     * @param childs: 组织树数据
     * @param findKey 目标key
     */
    getTreeDataByKey (childs = [], findKey) {
      let finditem = null
      for (let i = 0, len = childs.length; i < len; i++) {
        const item = childs[i]
        if (item.key !== findKey && item.children && item.children.length > 0) {
          finditem = this.getTreeDataByKey(item.children, findKey)
        }
        if (item.key === findKey) {
          finditem = item
        }
        if (finditem != null) {
          break
        }
      }
      return finditem
    },

    /**
     * 公共父级方法
     * @param childs: 组织树数据
     * @param findKey 目标key
     */
    getTreeParentChilds (childs = [], findKey) {
      let parentChilds = []
      for (let i = 0, len = childs.length; i < len; i++) {
        const item = childs[i]
        if (item.key !== findKey && item.children && item.children.length > 0) {
          parentChilds = this.getTreeParentChilds(item.children, findKey)
        }
        if (item.key === findKey) {
          parentChilds = childs
        }
        if (parentChilds.length > 0) {
          break
        }
      }
      return parentChilds
    },

    /**
     * 添加同级
     * @param title
     */
    dataDriveAddSame (title) {
      const parentChilds = this.getTreeParentChilds(
        this.treeData,
        this.selectKeys[0]
      )
      // 校验 相同的不能不可以添加
      const existence = parentChilds.find(item => { return item.key === title })
      if (!existence) {
        parentChilds.push({
          title: title,
          key: new Date().getTime(),
          scopedSlots: { title: 'custom' }
        })
      } else {
        this.$message.success('此数据已存在')
        return false
      }
    },

    /**
     * 添加下级
     * @param title
     */
    dataDriveAddSub (title) {
      const selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
      if (!selectItem.children) {
        this.$set(selectItem, 'children', [])
      }
      // 校验 相同的不能不可以添加
      const existence = selectItem.children.find(item => { return item.title === title })
      if (!existence) {
        selectItem.children.push({
          title: title,
          key: new Date().getTime(),
          scopedSlots: { title: 'custom' }
        })
      } else {
        this.$message.success('此数据已存在')
        return false
      }
      this.$forceUpdate()
    },

    /**
     * 一级修改
     * @param title
     */
    dataDriveModify (title) {
      const selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
      selectItem.title = title
    },

    /**
     * 删除方法
     */
    dataDriveDelete () {
      const parentChilds = this.getTreeParentChilds(
        this.treeData,
        this.selectKeys[0]
      )
      // 对删除的数据若下面有子级就给与提示不允许删除
      parentChilds.map(item => {
        console.log(item.children, '000')
        return item.children
      })
      // console.log(noeDel, 'shanchu')
      const delIndex = parentChilds.findIndex(
        (item) => item.key === this.selectKeys[0]
      )
      parentChilds.splice(delIndex, 1)
    },

    /**
     * 拖拽
     * @param info
     */
    onDragEnter (info) {
      console.log(info, '12222')
      // expandedKeys 需要受控时设置
      // this.expandedKeys = info.expandedKeys
    },

    /**
     * 拖拽
     * @param info
     */
    onDrop (info) {
      console.log(info)
      const dropKey = info.node.eventKey
      const dragKey = info.dragNode.eventKey
      const dropPos = info.node.pos.split('-')
      const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
      const loop = (data, key, callback) => {
        data.forEach((item, index, arr) => {
          if (item.key === key) {
            return callback(item, index, arr)
          }
          if (item.children) {
            return loop(item.children, key, callback)
          }
        })
      }
      const data = [...this.treeData]

      // Find dragObject
      let dragObj
      loop(data, dragKey, (item, index, arr) => {
        arr.splice(index, 1)
        dragObj = item
      })
      if (!info.dropToGap) {
        // Drop on the content
        loop(data, dropKey, item => {
          item.children = item.children || []
          // where to insert 示例添加到尾部,可以是随意位置
          item.children.push(dragObj)
        })
      } else if (
        (info.node.children || []).length > 0 && // Has children
        info.node.expanded && // Is expanded
        dropPosition === 1 // On the bottom gap
      ) {
        loop(data, dropKey, item => {
          item.children = item.children || []
          // where to insert 示例添加到尾部,可以是随意位置
          item.children.unshift(dragObj)
        })
      } else {
        let ar
        let i
        loop(data, dropKey, (item, index, arr) => {
          ar = arr
          i = index
        })
        if (dropPosition === -1) {
          ar.splice(i, 0, dragObj)
        } else {
          ar.splice(i + 1, 0, dragObj)
        }
      }
      this.treeData = data
    },

    handleEnable () {

    }
  }
}
</script>

<style scoped lang="less">
::v-deep .ant-tree-switcher.ant-tree-switcher_open {
  .icon-plus {
    background-image: url("../../../assets/icons/minus.png"); // 展开节点时的icon
    background-size: 24px;
    width: 24px;
    height: 24px;
  }
}

::v-deep .ant-tree-switcher.ant-tree-switcher_close {
  .icon-plus {
    background-image: url("../../../assets/icons/add.png"); // 收起节点时的icon
    background-size: 24px;
    width: 24px;
    height: 24px;
  }
}

.icon-wrap {
  margin: 0 6px;
}

.node-title {
  padding-right: 15px;
}

.tree-cancle_icon {
  margin: 0 6px;
}

.org-input {
  display: flex;
  flex-direction: row;
}

.add {
  background: #1890ff;
  color: #FFFFFF;
}

/deep/ .ant-card-body {
  padding: 50px 60px;
}

.tree-container {
  display: flex;
  flex-direction: row;
  .left-input{
    display: flex;
    flex-direction: row;
    margin-bottom: 15px;
  }
  .right{
    margin-left: 50px;
    width: 100%;
    &-btn{
      float: right;
      margin-bottom: 20px;
    }
  }
}
</style>

 操作弹窗:treeModules.vue

<template>
  <a-modal :title="title" v-model="visible" :confirmLoading="confirmLoading" @ok="handleSubmit">
    <a-form :form="form">
      <a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" label="名称">
        <a-input :disabled="disabled" placeholder="请输入名称" v-decorator="['title', {rules: [{ required: true, message: '请输入名称' }]}]" />
      </a-form-item>
    </a-form>
  </a-modal>
</template>

<script>
import pick from 'lodash.pick'

export default {
  address: 'TreeModules',
  props: {},
  components: {},
  data () {
    return {
      visible: false,
      confirmLoading: false,
      form: this.$form.createForm(this),
      labelCol: {
        xs: { span: 24 },
        sm: { span: 5 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      },
      title: '',
      mdl: {},
      disabled: false,

      operation: 1,
      record: {}
    }
  },
  beforeCreate () {},
  created () {},
  computed: {},
  methods: {

    /**
     * 新增彈窗
     * @param record
     */
    add (record) {
      this.operation = record.operation
      if (record.operation === 1) {
        this.title = '新增'
      } else {
        this.title = '添加下级'
      }
      this.form.resetFields()
      this.visible = true
      this.disabled = false
      this.record = record
    },

    /**
     * 編輯彈窗
     * @param record
     */
    edit (record) {
      this.record = record
      this.operation = 3
      this.disabled = false
      this.title = '编辑名称'
      this.mdl = Object.assign({}, record)
      this.visible = true
      this.$nextTick(() => {
        this.form.setFieldsValue(pick(this.mdl,
          'title'
        ))
      })
    },

    /**
     * 保存
     * @param e
     */
    handleSubmit (e) {
      e.preventDefault()
      this.form.validateFields((err, values) => {
        if (!err) {
          values.operation = this.operation
          values.key = this.record.key
          this.confirmLoading = true
          this.$emit('ok', values)
          this.form.resetFields()
          this.$message.success('保存成功')
          this.visible = false
          this.confirmLoading = false
        }
      })
    }
  }
}
</script>

<style scoped lang="less">
</style>

vue Ant Design Vue 树形控件增删改操作_发根强劲的博客-CSDN博客

;