zm-org-tree可拖拽的组织树,简易好上手

这篇具有很好参考价值的文章主要介绍了zm-org-tree可拖拽的组织树,简易好上手。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1.简介

2.安装及使用

下载包

main.js全局引用

页面使用   

数据要求

配合使用

3.基础使用

4.较深入使用

5.修改后的代码如下


1.简介

一个不算太简易的简易版组织架构图,组件依赖于vue-org-tree, 在此基础上将部分源代码进行优化修改。增加鼠标拖拽和鼠标滚轮缩放,并支持节点拖拽,以及节点编辑等功能。

优势:
1.支持整体拖拽、自定义展开组织树展开层级;
2.可进行节点搜索,显示搜索节点相关的组织树;
3.支持自定义节点样式,自定义新增、编辑、删除、节点是否拖拽、拖拽节点副本/节点;

做demo进行测试时发现一个缺点:当数据从1800条左右开始时,拖拽合并速度太快且频繁拖拽合并时,会报错数据找不到(感觉是上一轮拖拽数据还没有处理完,下一轮数据处理不了了,希望有大佬能解惑,能有好的解决办法)。

vue2的版本:zm-tree-org (gitee.io)

vue3的版本:Home | vue3-tree-org (sangtian152.github.io)

 git源码:GitHub - sangtian152/zm-tree-org: 一个简易版组织架构图,组件依赖于vue-org-tree, 在此基础上将部分源代码进行优化修改。支持鼠标拖动,鼠标滚轮缩放,节点拖拽。

 

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

2.安装及使用

以vue2的版本示例。

下载包

npm i zm-tree-org -S

main.js全局引用

import Vue from 'vue';
import ZmTreeOrg from 'zm-tree-org';
import "zm-tree-org/lib/zm-tree-org.css";
Vue.use(ZmTreeOrg);

页面使用  <zm-tree-org></zm-tree-org> 

//使用示例:修改背景色/文字颜色  
<zm-tree-org :label-style="style">
</zm-tree-org>

数据要求

最外层(公司级)是Object,其子级及以下为Array。

orgData:{ id: 1,     //组织id,必须
  label: "xxx科技有限公司",  //组织名称,必须
  disabled: true,   //是否可编辑
  children: [      //子级
     {
      id: 2,    //子级组织id,必须
      pid: 1,   //父级组织id,必须
      label: "产品研发部",   //组织名称,必须
      expand: false,  //当前节点下的节点是否默认展开
      noDragging: true,  //当前节点是够允许拖拽
      children: []
     }
  }

配合使用

页面部分功能需结合elementUI使用。

下载

npm i element-ui -S

 main.js全局引用

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

3.基础使用

(1)树形结构横向/纵向展开;

(2)树形结构展开/收起;

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

(3)组织树默认展开层级 or 单一某节点的子节点树展开/收起;

单一某节点的详细说明:

需对数据做处理,单一节点的子节点树不填写默认跟随:default-expand-level。

示例:{id: 6,pid: 2,label: "展开当前节点的组织树",expand: true}。

如组件绑定 :default-expand-level="num"  

且 num+1 < 当前设置的层级 上述设置不生效;

num+1 >= 当前设置的层级 上述设置生效。

num+1举例:expand:false,即使 num+1 >= 当前设置的层级,当前节点的子节点树也会收起。

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

文中最开始的示例图全部展开后共为 2级子节点(num) + 1 级根节点(所以num需要加1)。:default-expand-level="1"时,代表展开到第一级子节点,即“产品研发部”及它的同层级。org-tree,vue3,vue2,vue.js,javascript,elementui,前端

(4)组织树  可编辑/不可编辑 or 单一某节点  可编辑/不可编辑;

单一某节点的详细说明:

需对数据做处理,子节点不填写默认为false(可编辑)。

子节点示例:{id: 6,pid: 2,label: "禁止编辑节点",disabled: true}。

如组件绑定 :disabled="true"  上述设置被覆盖;:disabled="false",上述设置生效。

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

(5)节点树整体 可拖拽/不可拖拽 or 单一某节点 可拖拽/不可拖拽

单一某节点的详细说明:

需对数据做处理,子组件不填写默认为false(可拖拽)。

示例:{id: 6,pid: 2,label: "禁止拖拽节点",noDragging: true}。

如组件绑定 :node-draggable="true" 上述设置也生效; :node-draggable="false",上述设置被覆盖。

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

(6)拖拽节点副本、当前位置保留节点本身 / 拖拽节点本身;

(7)仅拖动当前节点、子节点添加到当前节点 / 当前节点及子节点一起拖动;

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

(8)在线调整组织结构任意背景色;

(9)在线调整文字任意颜色;

//color为string类型  :label-style="style"
  <el-color-picker v-model="style.color" size="small"></el-color-picker>

(10)搜索组织,同时显示当前组织的上级所有组织;

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

<input type="text" v-model="keyword" placeholder="请输入搜索内容" @keydown.enter="filter" />

filter(){ this.$refs.tree.filter(this.keyword)  }

4.较深入使用

(1)自定义节点右键点击事件修改;

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

如仅修改name,则仅修改显示的文字,插件自带的【编辑】样式依然显示;

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

如修改command值,则绑定右键菜单弹出和【复制】【新增】【编辑】【删除】4个可自定义的绑定事件失效,需重写写入事件。

例如:仅将command:'edit'修改为command:'edit1',如果某子节点为不可编辑状态,点击右键,依然会显示菜单【复制】【编辑】,但点击【编辑】事件无反应。如果不想【编辑】功能显示,可以绑定vue的@contextmenu.prevent,对menus进行动态赋值。

// data值
menus: [{ name: '复制文本', command: 'copy' }, { name: '新增节点', command: 'add' }, { name: '编辑节点', command: 'edit1' }, { name: '删除节点', command: 'delete' }],
disaled: false,

//自定义节点绑定方法
<div class="tree-org-node__text node-label node" @contextmenu.prevent="terFun(node)"></div>

//动态赋值
terFun(node) {
  console.log(node)
  if (node.disabled || this.disaled) { 
    this.menus = [{ name: '复制文本', command: 'copy' }] 
  } else {
    this.menus = [
                  { name: '复制文本', command: 'copy' }, 
                  { name: '新增节点', command: 'add' }, 
                  { name: '编辑节点', command: 'edit1' }, 
                  { name: '删除节点', command: 'delete' }
                 ]
  }
},

(2)自定义节点及自定义编辑节点。

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

在4-(1)的基础上去做,需要修改自定义点击右键事件的edit值,否则插件自带的【编辑节点】样式依然会闪烁显示。

————————  Html说明  ————————

<template slot-scope="{node}">     // 插件的自定义节点插槽
   // 此处可修改组织树的节点样式,当前为默认
  <div class="tree-org-node__text node-label node"> 
    {{ node.label }}        // 组织名称
      // 此处的class为late的 div为自定义的【编辑节点】card
    <div v-if="treeScope && !node.disabled && node.open" class="late">
        <el-input type="textarea" placeholder="请输入内容" v-model="cardOne.label" maxlength="30" show-word-limit></el-input>
        <div @click="close(node)">确定</div>
    </div>
  </div>
</template>

————————  data说明 除node.disabled外,可自定义————————

treeScope: false, //是否显示【自定义编辑】card
node.disabled:  查找当前节点可编辑/不可编辑的属性
cardOne: {}, //【自定义编辑】card组件信息
node.open:  //自定义的节点【编辑属性值】

————————  function说明 ————————

@on-contextmenu="onMenus" //右键菜单点击事件
如修改【4、自定义节点右键点击菜单修改的command:'edit'值,则插件自带编辑事件失效,需自定义,自定义如下】


onMenus({ node, command }) {
  // 自定义编辑
  if (command === 'edit1' && !node.disabled) {
      //显示【自定义编辑】card,将node赋值给cardOne
      //自定义编辑--card
  }
},

 组件自带的默认编辑节点样式:

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

修改后的实现效果:
弹窗自定义编辑节点

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

标签自定义编辑节点

org-tree,vue3,vue2,vue.js,javascript,elementui,前端

5.修改后的代码如下

<template>
  <div class="all" @click="closeO">
    <div style="display: flex; padding: 10px 0">
      <div style="margin-right: 10px">
        <el-switch v-model="horizontal"></el-switch> {{ horizontal? "横向": "纵向" }}
      </div>
      <div style="margin-right: 10px">
        <el-switch v-model="collapsable"></el-switch> {{ collapsable? "可收起": "仅展开" }}
      </div>
      <div style="margin-right: 10px">
        <el-switch v-model="disaled"></el-switch> {{ disaled? "禁止编辑": "可编辑" }}
      </div>
      <div style="margin-right: 10px">
        <el-switch v-model="onlyOneNode"></el-switch> {{ onlyOneNode? "仅拖动当前节点": "拖动当前节点树" }}
      </div>
      <div style="margin-right: 10px">
        <el-switch v-model="cloneNodeDrag"></el-switch> {{ cloneNodeDrag? "拖动节点副本": "拖动节点本身" }}
      </div>
      <div style="margin-right: 10px">
        <el-switch v-model="pop"></el-switch> {{ pop? "弹窗修改节点": "标签修改节点" }}
      </div>
    </div>
    <div style="padding-bottom: 10px" class="pickers">
      背景色:
      <el-color-picker v-model="style.background" size="small"></el-color-picker>&nbsp; 文字颜色:
      <el-color-picker v-model="style.color" size="small"></el-color-picker>&nbsp;
      搜索:
      <input type="text" v-model="keyword" placeholder="请输入搜索内容" @keydown.enter="filter" />
    </div>
    <div class="lll">
      <div ref="nodeOne" class="nodeOne">
        <zm-tree-org ref="tree" :data="data" :disabled="disaled" :horizontal="horizontal" :collapsable="collapsable"
          :label-style="style" :node-draggable="true" :default-expand-level="1" :only-one-node="onlyOneNode"
          :clone-node-drag="cloneNodeDrag" :node-draging="nodeDragMove" :node-drag-end="nodeDragEnd" :toolBar="toolBar"
          :filterNodeMethod="filterNodeMethod" @on-contextmenu="onMenus" @on-expand="onExpand"
          @on-node-click="onNodeClick" @on-node-dblclick="onNodeDblclick" @on-node-copy="onNodeCopy"
          :define-menus="menus">
          <!-- 自定义节点内容 -->
          <template slot-scope="{node}">
            <div class="tree-org-node__text node-label node" @contextmenu.prevent="terFun(node)">
              {{ node.label }}
              <div v-if="treeScope && !node.disabled && node.open" class="late" id="lateId">
                <el-input type="textarea" placeholder="请输入内容" v-model="cardOne.label" maxlength="30" show-word-limit>
                </el-input>
                <div @click="close(cardOne)" class="onCloseCss">确定</div>
              </div>
            </div>
          </template>
          <!-- 自定义展开按钮 -->
          <template v-slot:expand="{ node }">
            <div>{{ node.children.length }}</div>
          </template>
        </zm-tree-org>
      </div>
    </div>
  </div>
</template>
<script>
import allList from '../../public/orgTree.json'
export default {
  data() {
    return {
      toolBar: {
        scale: false,
      },
      keyword: "",
      menus: [{ name: '复制文本', command: 'copy' }, { name: '新增节点', command: 'add' }, { name: '编辑节点', command: 'edit1' }, { name: '删除节点', command: 'delete' }],
      data: {},
      horizontal: false,
      collapsable: true,
      onlyOneNode: false,//仅拖动当前节点
      cloneNodeDrag: false,//是否拷贝节点拖拽 false仅拖拽  true拷贝+保留原位
      expandAll: true,
      disaled: false,
      style: { color: "#fff", background: "#108ffe" },
      treeScope: false,
      //弹窗
      pop: false,
      //card组件信息
      cardOne: {},
    };
  },
  created() {
    this.data = allList.orgAll
  },
  methods: {
    //动态赋值
    terFun(node) {
      console.log(node)
      if (node.disabled || this.disaled) { this.menus = [{ name: '复制文本', command: 'copy' }] } else {
        this.menus = [{ name: '复制文本', command: 'copy' }, { name: '新增节点', command: 'add' }, { name: '编辑节点', command: 'edit1' }, { name: '删除节点', command: 'delete' }]
      }
    },
    //右侧菜单点击事件
    onMenus({ node, command }) {
      //自定义编辑--弹窗编辑
      let labelOld = node.label
      if (command === 'edit1' && this.pop && !node.disabled) {
        this.$prompt('修改当前信息', '编辑', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          inputValue: labelOld
        }).then((val) => {
          this.$message({
            type: 'success',
            message: '修改成功'
          });
          // this.editFun(this.data.children, node, val.value)
          this.$set(node, 'label', val.value)
        }).catch(action => {
          if (action === 'cancel') {
            this.$message({
              type: 'info',
              message: '取消输入'
            });
          }
        });
        //当前不允许编辑
      } else if (node.disabled && command === 'edit1') {
        this.$message({
          type: 'info',
          message: '当前内容不可编辑'
        });
        //自定义编辑--card
      } else if (command === 'edit1' && !this.pop && !node.disabled) {
        this.$set(node, 'open', true)
        this.cardOne = node
        this.treeScope = true
      }
    },
    //自定义修改属性值
    editFun(data, node, val) {
      if (!data || !data.length) {
        return;
      }
      for (let i = 0; i < data.length; i++) {
        if (data[i].id === node.id) {
          data[i].label = val;
          break;
        }
        this.editFun(data[i].children, node, val);
      }
    },
    //确定 关闭car界面
    close(val) {
      this.$delete(val, 'open')
      this.treeScope = false
    },
    //颠倒编辑外区域,关闭
    closeO(event) {
      var currentCli = document.getElementById("lateId");
      if (currentCli) {
        if (!currentCli.contains(event.target)) {
          this.treeScope = false;
        }
      }
    },
    //自定义card显示
    filter() {
      this.$refs.tree.filter(this.keyword);
    },
    filterNodeMethod(value, data) {
      if (!value) return true;
      return data.label.indexOf(value) !== -1;
    },
    //展开事件
    onExpand(e, data) {
    },
    nodeDragMove(data) {
    },
    nodeDragEnd(data, isSelf) {
      // isSelf && this.$Message.info("移动到自身");
    },
    //点击节点
    onNodeClick(e, data) {
      // this.$Message.info(data.label);
    },
    onNodeDblclick() {
      // this.$Message.info("双击节点");
    },
    onNodeCopy() {
      // this.$Message.success("复制成功");
    },
    handleNodeAdd(node) {
      // this.$Message.info("新增节点");
    },
    expandChange() {
      this.toggleExpand(this.data, this.expandAll);
    },
    //递归创建节点树
    toggleExpand(data, val) {
      if (Array.isArray(data)) {
        data.forEach((item) => {
          this.$set(item, "expand", val);
          if (item.children) {
            this.toggleExpand(item.children, val);
          }
        });
      } else {
        this.$set(data, "expand", val);
        if (data.children) {
          this.toggleExpand(data.children, val);
        }
      }
    },
  },
};
</script>
<style scoped>
.pickers {
  display: flex;
}

.node {
  position: relative;
}

.nodeOne {
  height: 800px;
}

.late {
  position: absolute;
  top: 20px;
  right: -100px;
  width: 200px;
  min-height: 100px;
  z-index: 999;
  background: #F2E2BE;
  padding: 10px;
}

.onCloseCss {
  width: 50px;
  height: 30px;
  text-align: center;
  line-height: 30px;
  background: #108FFE;
  margin: 10px auto;
  z-index: 999;
}

::v-deep .is-edit {
  background: palegoldenrod !important;
}
</style>

.json数据文章来源地址https://www.toymoban.com/news/detail-651453.html

{
    "orgAll":{
        "id": 1,
        "label": "xxx科技有限公司",
        "disabled": true,
        "children": [
          {
            "id": 2,
            "pid": 1,
            "label": "产品研发部",
            "expand": false,
            "children": [
              {
                "id": 6,
                "pid": 2,
                "label": "禁止编辑节点",
                "disabled": true
              },
              {
                "id": 8,
                "pid": 2,
                "label": "禁止拖拽节点",
                "noDragging": true
              },
              {
                "id": 7,
                "pid": 2,
                "label": "研发-后端",
                "children": [
                    {
                        "id": 14,
                        "pid": 7,
                        "label": "后端1组"
                      },
                      {
                        "id": 15,
                        "pid": 7,
                        "label": "后端2组"
                      }
                ]
              },
              {
                "id": 13,
                "pid": 2,
                "label": "研发-前端"
              },
              {
                "id": 9,
                "pid": 2,
                "label": "产品经理"
              },
              {
                "id": 10,
                "pid": 2,
                "label": "测试"
              }
            ]
          },
          {
            "id": 3,
            "pid": 1,
            "label": "客服部",
            "children": [
              {
                "id": 11,
                "pid": 3,
                "label": "客服一部"
              },
              {
                "id": 12,
                "pid": 3,
                "label": "客服二部"
              }
            ]
          },
          {
            "id": 4,
            "pid": 1,
            "label": "业务部"
          },
          {
            "id": 5,
            "pid": 1,
            "label": "人力资源中心"
          }
        ]
    }
  }

到了这里,关于zm-org-tree可拖拽的组织树,简易好上手的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 封装React组件DragLine,鼠标拖拽的边框改变元素宽度

    原文合集地址如下,有需要的朋友可以关注 本文地址 合集地址 在项目中,设计说想做个面板,其宽度随鼠标拖拽而变化,有最大最小值。基于这个小功能封装一个可拖拽组件,在需要的地方引入即可。 这里只是实现x方向的拖拽,y轴拖拽思路差不多。 既然是鼠标操作,那肯

    2024年02月16日
    浏览(30)
  • Vue:可拖拽组件

            在实际开发中,很可能会遇到开发可拖拽组件的需求,目的是应对某些弹框组件会遮盖某些重要信息/可操作面板,通过可拖拽的形式可以将上层的弹框组件移动到其他位置,从而不影响整个系统的操作。下面,我们分两步走,开发一个可拖拽的弹框组件,最终效果如

    2024年02月12日
    浏览(28)
  • LVGL可拖拽窗口实现

    一、 LVGL拖拽功能     LVGL obj提供了使能拖拽功能的函数:     不过在多元素界面,例如下边在 bg win上创建了若干个 sub win,bg win大部分面积被覆盖的情况下,如果单纯将 bg win 设置为可拖拽,则只有点击到  没有被覆盖的区域才能成功,效果不满足要求;如果简单的把 sub

    2023年04月25日
    浏览(27)
  • 实现ElementUI tab标签可拖拽

    通过sortablejs实现 参考:https://blog.csdn.net/wangjiecsdn/article/details/121995534

    2024年01月22日
    浏览(59)
  • 前端弹窗可拖拽功能实现

           前端弹窗可拖拽功能主要实现思路就是监听鼠标移动事件,根据鼠标位置实时修改弹窗距离父级窗口(或者屏幕,根据需求设置)的left和right,但是考虑到鼠标拖拽一般都是在div的标题栏处发生,鼠标按下的位置不可能是弹窗的左上角位置,为此需要计算 鼠标按下的

    2024年02月19日
    浏览(35)
  • vue 进度条组件(可拖拽可点击)

    在日常的开发当中,随着项目的需求复杂化,自定义组件也越来越常见,而且扩展性也比一些组件库要更加全面,比如视频播放器的进度条。 可自定义设置以下属性: 当前进度value,默认50 是否可拖拽isDrag,默认true 设置最小值min,默认0 设置最大值max,默认100 进度条颜色

    2024年02月16日
    浏览(26)
  • 可拖拽编辑的流程图X6

     先上图

    2024年02月11日
    浏览(31)
  • vue3实现组件可拖拽 vuedraggable

    npm i -S vuedraggable@next 中文文档,里面有完整代码案例,值得一看 vue.draggable vue3 版本在工作台中的应用场景 - itxst.com

    2024年02月13日
    浏览(26)
  • Unity之XR Interaction Toolkit如何在VR中实现一个可以拖拽的UI

    普通的VR项目中,我们常见的UI都是一个3D的UI,放置在场景中的某个位置,方便我们使用射线点击。但是为了更好的体验,我们可能会有跟随头显的UI,或者可拖拽的UI,这样更方便用户去操作。 所以我们今天的需求就是:如何基于XR Interaction Toolkit 插件 在VR中使用手柄射线来

    2024年02月19日
    浏览(27)
  • 基于vue的可拖拽设计的报表看板设计器

    gitee上的不错项目,基于vue实现的可拖拽的看板设计器可以自由搭配颜色和图标,开发者可以只关注业务数据接口,前端不擅长的人员可以直接轻松上手。 1.可支持的元素 文字,边框,常见图表,柱形图,折线饼图等等,还有一些列表数据,当然还支持iframe嵌套,当组件满足

    2024年02月11日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包