vue2自定义可添加、编辑、删除的树形组件

依赖的库 Element

创建组件

创建文件 cus-tree-node.vue , 这是一个节点组件

 <template>
  <div class="cus-tree-node">
    <div class="cus-tree-entry">
      <div class="cus-tree-main" @click="nodeHandle(node, $event)">
        <div>
          <i
            v-if="(node.child && node.child.length > 0) || node.pid == 0"
            class="cus-tree-icon"
            :class="{ 'el-icon-folder': !nodeOpenTag, 'el-icon-folder-opened': nodeOpenTag }"
          ></i>
          <i v-if="(!node.child || node.child.length <= 0) && node.pid != 0" class="cus-tree-icon el-icon-document"></i>
        </div>
        <div>
          <div v-show="nodeEditTag" @click.stop>
            <el-input size="mini" v-model="node.title"></el-input>
          </div>
          <div v-show="!nodeEditTag" class="cus-tree-txt">
            <div v-if="node.child && node.child.length > 0" style="font-weight: bolder">
              {{ node.title }}
            </div>
            <div v-else-if="!node.child || node.child.length <= 0">
              {{ node.title }}
            </div>
          </div>
        </div>
      </div>
      <!-- 编辑栏 -->
      <div class="tree-handle-tool">
        <i v-show="nodeEditTag && node.title.trim().length > 0" class="el-icon-check" @click="submitHandle"></i>
        <i v-show="nodeEditTag" class="el-icon-close" @click="closeHandle"></i>

        <i v-show="!nodeEditTag" class="el-icon-plus" @click="nodeCreate"></i>
        <i v-show="!nodeEditTag" class="el-icon-edit-outline" @click="nodeEdit"></i>
        <i v-show="!nodeEditTag" class="el-icon-delete" @click="nodeDelete"></i>
      </div>
    </div>
    <!-- 递归轮询出下一级的树节点 -->
    <el-collapse-transition>
      <div v-show="nodeOpenTag" class="cus-child-container" v-if="node.child && node.child.length > 0">
        <el-collapse-transition v-for="child in node.child" :key="child.id">
          <div class="cus-child-node" v-show="nodeOpenTag">
            <cusTreeNode :node="child" :treeData="treeData" :addHanlde="addHanlde" :delHandle="delHandle" :editHandle="editHandle"></cusTreeNode>
          </div>
        </el-collapse-transition>
      </div>
    </el-collapse-transition>
  </div>
</template>

<script>
/**
 * @description 树形节点
 * @param {Object}  node	        节点的数据
 * @param {Boolean}  allOpen	    子节点全部打开
 * @param {Array}  treeData       树型组件的数据
 * @param {Boolean}  noLine		    开启树形的线条
 *
 * @event {Function()} addHanlde  添加的操作
 * @event {Function()} delHandle  删除的操作
 * @event {Function()} editHandle 修改的操作
 */
export default {
  name: 'cusTreeNode',
  props: {
    node: {
      type: Object,
      default: {},
    },
    allOpen: {
      type: Boolean,
      default: true,
    },
    treeData: {
      type: Array,
      default: [],
    },
    noLine: {
      type: Boolean,
      default: false,
    },
    addHanlde: {
      type: Function,
      default: () => {},
    },
    delHandle: {
      type: Function,
      default: () => {},
    },
    editHandle: {
      type: Function,
      default: () => {},
    },
  },
  data: () => {
    return {
      nodeOpenTag: false, // 节点打开标志
      nodeEditTag: false, // 节点编辑标志
      oldNodeTitle: '',
    }
  },
  methods: {
    inputHandle() {},
    // 子级节点的展开操作
    nodeHandle(node, e) {
      this.nodeOpenTag = !this.nodeOpenTag
    },
    // 节点创建
    nodeCreate() {
      let treeArr = this.treeToArray(this.treeData)
      let addNode = treeArr.filter((t) => t.id == '')
      if (addNode && addNode[0]) {
        return  this.$message('已有未确定的添加')
      }
      this.recQueryHandle(this.treeData, 'create')
    },
    // 节点删除
    nodeDelete() {
      this.delHandle(this.node)
    },
    // 节点编辑
    nodeEdit() {
      this.nodeEditTag = true
      this.oldNodeTitle = this.node.title
    },
    // 提交操作
    submitHandle() {
      this.recQueryHandle(this.treeData, 'submit')
    },
    // 关闭操作
    closeHandle() {
      this.nodeEditTag = false
      this.recQueryHandle(this.treeData, 'close')
    },
    // 对自己节点进行递归操作
    recQueryHandle(treeData, tag) {
      // 当前操作的节点
      let curNode = this.node
      treeData.forEach((item) => {
        if (item.id === curNode.id) {
          // 添加元素
          if (tag === 'create') {
            // 先查询是否已有追加的元素
            if (!curNode.child) {
              this.$set(item, 'child', [])
            }
            // 查询当前同层新增且未提交的节点
            let addTag = item.child.filter((t) => t.id == '')
            if (addTag.length > 0) {
              this.$message('已有未确定的添加')
              return
            }
            // 添加节点
            item.child.push({ title: '新建子节点', id: '', edit: true, pid: item.id })
          }
          // 删除元素
          else if (tag === 'del') {
            let index = treeData.findIndex((t) => t.id == curNode.id)
            treeData.splice(index, 1)
          }
          // 编辑元素
          else if (tag === 'edit') {
            this.oldNodeTitle = this.node.title
          }
          // 节点的提交事件
          else if (tag == 'submit') {
            // id为空为添加数据
            if (curNode.id == '') {
              var existIndex = item.title.indexOf('/')
              if (existIndex != -1) return  this.$message('不允许存在 / 字符存在')
              this.addHanlde(curNode)
            } else {
              // id 不为空为编辑数据
              if (item.title === this.oldNodeTitle) return (this.nodeEditTag = false)
              // 特殊字符查询
              var existIndex = item.title.indexOf('/')
              if (existIndex != -1) return  this.$message('不允许存在 / 字符存在')
              this.editHandle(curNode)
            }
          }
          // 节点的修改或添加状态的关闭状态
          else if (tag == 'close') {
            // id 若为空则删除
            if (curNode.id == '') {
              treeData.pop()
            } else {
              item.title = this.oldNodeTitle
            }
          }
          return
        }
        if (item.child) {
          this.recQueryHandle(item.child, tag)
        }
      })
    },
    // 获取扁平化的数据
    treeToArray(tree) {
      var res = []
      for (const item of tree) {
        const { child, ...i } = item
        if (child && child.length) {
          res = res.concat(this.treeToArray(child))
        }
        res.push(i)
      }
      return res
    },
    layerTips(title) {},
  },
  watch: {
    node() {
      this.nodeEditTag = this.node.edit
    },
  },
  created() {
    this.nodeOpenTag = this.allOpen
    this.nodeEditTag = this.node.edit
  },
  mounted() {
    this.oldNodeTitle = this.node.title
  },
}
</script>

<style lang="scss" scoped>
/* 树节点 */
.cus-tree-node {
  line-height: 30px;
  padding-left: 23px;
  position: relative;
}

.cus-tree-node:not(:last-child) > .cus-child-container::after {
  content: '';
  border-left: 1px dashed #ccc;
  position: absolute;
  left: 30.5px;
  top: 29.5px;
  height: calc(100% - 30px);
}

/* 子节点的连线 */
.cus-child-node:not(:last-child) > ::after {
  content: '';
  border-left: 1px dashed #ccc;
  position: absolute;
  left: 7.5px;
  top: 0;
  height: 100%;
}

.cus-child-node:last-child > ::after {
  content: '';
  border-left: 1px dashed #ccc;
  position: absolute;
  left: 7.5px;
  top: -1px;
  height: 16px;
}

.cus-child-node .cus-tree-icon::after {
  content: '';
  border-top: 1px dashed #ccc;
  width: 60%;
  position: absolute;
  top: 7.2px;
  left: -14px;
}

/* 单个书节点的显示主体 */
.cus-tree-entry {
  height: 30px;
  display: flex;
  line-height: 30px;
  cursor: pointer;
}

.cus-tree-entry:hover .tree-handle-tool {
  line-height: 30px;
  height: 30px;
  display: block;
}

.cus-tree-main {
  display: flex;
  font-size: 15px;

  div:first-child {
    padding-right: 8px;
  }
}

/* 树的操作栏工具 */
.tree-handle-tool {
  display: none;
  padding-left: 10px;

  i {
    padding: 0 5px;
    font-weight: 9;
  }

  i:hover {
    opacity: 0.5;
  }
}

.cus-tree-icon {
  background-color: #ffffff;
  color: #ffb800;
  z-index: 2;
  position: relative;
}

/* 树节点的标题 */
.cus-tree-txt:hover {
  text-decoration: underline;
  color: #a8a8a8;
}
</style>

创建一个节点的容器组件 cus-tree.vue

<template>
  <div class="cus-tree">
    <template v-for="(node, index) in tree_data">
      <cus-tree-node
        :node="node"
        :noLine="false"
        :treeData="tree_data"
        :allOpen="allOpen"
        :addHanlde="addHanlde"
        :delHandle="delHandle"
        :editHandle="editHandle"
        :key="index"
      >
      </cus-tree-node>
    </template>
  </div>
</template>

<script>
/**
 * @description 自定义树形组件
 * @param {Array}  treeData       树型组件的数据
 * @param {Boolean}  allOpen      子节点全部打开
 *
 * @event {Function()} addHanlde  添加的操作
 * @event {Function()} delHandle  删除的操作
 * @event {Function()} editHandle 修改的操作
 */
import cusTreeNode from './cus-tree-node.vue'

export default {
  name: 'cusTree',
  props: {
    treeData: {
      type: Array,
      default: [],
    },
    allOpen: {
      type: Boolean,
      default: true,
    },
  },
  components: {
    cusTreeNode,
  },
  data: () => {
    return {
      tree_data: [],
    }
  },
  methods: {
    addHanlde(node) {
      this.$emit('addHanlde', node)
    },
    delHandle(node) {
      this.$emit('delHandle', node)
    },
    editHandle(node) {
      this.$emit('editHandle', node)
    },
    // 强制刷新(解决数据添加的时候无法更新的问题) 
	//(这里我也很纳闷,监听不起作用,只能出此下策)
    forceFlush() {
      this.tree_data = JSON.parse(JSON.stringify(this.treeData))
    },
  },
  watch: {
    treeData: {
      handler() {
        this.tree_data = JSON.parse(JSON.stringify(this.treeData))
        console.log('节点修改监听')
      },
      deep: true,
    },
  },
  created() {
    console.log(this.treeData)
    this.tree_data = JSON.parse(JSON.stringify(this.treeData))
  },
}
</script>

<style lang="scss" scoped>
.cus-tree {
  box-sizing: border-box;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
</style>

组件的引用案例

<template>
  <div class="index">
  
	<cusTree ref="cusTree"  :treeData="treeData"
			@addHanlde="addHanlde"
			@editHandle="editHandle"
			@delHandle="delHandle">
	</cusTree>

  </div>
</template>

<script>
 
import cusTree from '@/components/cus-tree/cus-tree.vue'
export default {
  data() {
    return {
      isShowTag: 'cusTree',
	  treeData:[{
			"id": 1,
			"title": "测试库(系统测试专用库)",
			"pid": 0,
			"child": [
				{
					"id": 11,
					"title": "新建子节热帖点111",
					"pid": 1,
				}
			]
		},
			{
						"id": 2,
						"title": "测试库(系统测试专用库)",
						"pid": 0,
						"child": [{
							"id": 21,
							"title": "新建子节热帖点111",
							"pid": 2,
						}]
					}]
    }
  },
  components: {
    cusTree,
  },
  methods:{
	  // 添加确认的回调
	  addHanlde(node){
		// 这里是后端传递过来的id  
		node.id =  new Date().getSeconds()
		console.log(node);
		this.recQueryHandle(this.treeData,node,"add") 
		this.$refs.cusTree.forceFlush();
	  },
	  // 编辑的回调
	  editHandle(node){
		  // .. 发送后端请求
		  this.recQueryHandle(this.treeData,node,"edit")
		  this.$refs.cusTree.forceFlush();
	  },
	  // 删除
	  delHandle(node){
		  this.recQueryHandle(this.treeData,node,"del")
		  this.$refs.cusTree.forceFlush();
	  },
	  recQueryHandle (treeData, curNode, tag) {
	      // 当前操作的节点
	      treeData.forEach((item) => {
	          if (tag == 'add' && item.id == curNode.pid) {
	              console.log('item.child', item)
	              if (!item.child) {
	                  item.child = []
	                  //this.$set(item, 'child', [])
	              }
	              curNode.edit = false
	              item.child.push(curNode)
	          }
	  
	          if (item.id === curNode.id) {
	              if (tag == 'edit') {
	                  item.edit = false;
	                  item.title = curNode.title
	              } else if (tag === 'del') {
	                  let index = treeData.findIndex((t) => t.id == curNode.id)
	                  treeData.splice(index, 1)
	              }
	          }
	  
	          if (item.child && item.child.length > 0) {
	              this.recQueryHandle(item.child, curNode, tag)
	          }
	      })
	  }

  }
}
</script>

<style>
.cus-component {
  margin-top: 50px;
  margin-bottom: 50px;
}

.cus-component span {
  margin: 0 5px;
  cursor: pointer;
  font-weight: 600;
}

.check-item {
  color: blueviolet;
}
</style>

最终效果图

本人非专业前端人员,样式可能有点陋,有需求的兄弟,还是自己修改 cus-tree-node 文件中的样式
在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue树形组件可以用来实现组织架构的展示和管理。在Vue中,我们可以利用组件的嵌套和数据的响应式特性来构建一个树形组织架构。 首先,我们可以定义一个组织节点的组件,包括节点的名称、展开状态和子节点等属性。通过使用Vue的v-model指令,可以轻松地实现节点展开和收缩的功能。当节点展开状态改变时,通过监听v-model的变化,可以重新渲染节点的子节点。 接下来,我们可以利用递归组件的概念来定义一个树形组件。在树形组件中,我们可以使用v-for指令遍历组织节点,并将每个节点作为子组件进行递归渲染。通过递归的方式,树形组件可以无限层级地展示组织架构。 为了方便管理和操作组织节点,我们可以添加一些其他功能,比如节点的新增、删除编辑等。通过在组织节点组件添加相应的方法,并在树形组件中调用,可以实现对组织架构的增删改操作。 最后,在Vue树形组件中还可以利用一些其他特性,比如插槽和过渡效果,来增强用户体验。通过插槽,可以实现自定义节点的内容和样式。通过过渡效果,可以为组织节点的展开和收缩添加动画效果,使界面更加美观和生动。 总之,Vue树形组件是一个非常灵活和强大的工具,可以帮助我们快速、高效地实现组织架构的展示和管理。无论是大型企业的组织架构还是小型项目的分类列表,Vue树形组件都能够满足我们的需求,并提供丰富的功能和扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值