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

1 篇文章 0 订阅

目录

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, 在此基础上将部分源代码进行优化修改。支持鼠标拖动,鼠标滚轮缩放,节点拖拽。

 

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)树形结构展开/收起;

(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 >= 当前设置的层级,当前节点的子节点树也会收起。

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

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

单一某节点的详细说明:

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

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

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

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

单一某节点的详细说明:

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

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

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

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

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

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

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

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

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

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

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

4.较深入使用

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

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

如修改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)自定义节点及自定义编辑节点。

在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
  }
},

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

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

标签自定义编辑节点

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数据

{
    "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": "人力资源中心"
          }
        ]
    }
  }
  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 27
    评论
引用中提到,zm-tree-org是一个简易版的组织架构图组件,它依赖于vue-org-tree,并在此基础上进行了一些优化和修改。zm-tree-org组件支持鼠标动和功能,可以通过动节点来改变的结构。同时,它还支持鼠标滚轮缩放功能。所以,使用zm-tree-org组件可以实现功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [zm-org-tree组织简易上手](https://blog.csdn.net/weixin_53582738/article/details/129037965)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [zm-tree-org:一个简易组织架构图,组件依赖于vue-org-tree, 在此基础上将部分源代码进行优化修改。...](https://download.csdn.net/download/weixin_42133969/19256646)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [vue-drag-tree:基于Vue的组织](https://download.csdn.net/download/weixin_42117082/19124918)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值