Vue实现流程图,借鉴vue-tree-color 实现流程框架技术

Vue实现流程图,借鉴vue-tree-color 实现流程框架技术

借鉴鸣谢

实现组织架构图(vue-org-tree)
如果向使用原来的依赖可以使用这个人的,因为我也是根据这个博客大佬仿照Vue-org-tree实现的方案
对此有几点不惑,问了大佬,大佬也没有回复我

  • className 貌似不起作用,看了文章底部,她也意识到这个问题,但是没有给出详细的解决方案
  • node.js中虽然做了充分的注释,但是她把子节点渲染和主节点渲染给单独分开了,指定了render函数去渲染

演示效果

在这里插入图片描述
由于还在启动项目中,还没有完善,目前已经可以支持动态创建节点了
在这里插入图片描述
这是通过点击实现抽屉展示
在这里插入图片描述
这是通过hover鼠标悬停高亮,由于我并不是一个正儿八经的前端,准确的说我是来搞笑的。所以对于每个节点面板渲染是很简陋的。

引入依赖

# 这里是安装大佬提供的依赖
npm install vue-tree-color

检查less和less-loader,因为该组件使用到了less,可以看看项目工程是否已经安装

npm install --save-dev less less-loader

注意这里可能会在项目启动的时候出现以下问题

报错信息:Syntax Error: TypeError: this.getOptions is not a function

解决方案-需要在安装less和lessloader时,指定版本号,否则就会导致兼容问题。

npm i less@3.9.0 less-loader@4.1.0 -D

添加全局

找到main.js中追加一下内容

import Vue2OrgTree from 'vue-tree-color'
Vue.use(Vue2OrgTree)

组件的二次封装

在公共层面@components 目录下,我们需要进行二次封装,为什么要二次封装,上面已经说到原因了,因为定制化内容不同, 肯定会涉及更改组件渲染方式以及样式,因为二次封装是非常有必要的
在这里插入图片描述

步骤1 创建组件目录

我就随便给取了一个名字 SimpleTree 目录

Vue

组件页面,我没做更改,直接拿来用

<template>
  <div class="org-tree-container">
    <div class="org-tree" :class="{horizontal, collapsable}">
      <org-tree-node
        :data="data"
        :props="props"
        :judge="judge"
        :NodeClass="NodeClass"
        :horizontal="horizontal"
        :label-width="labelWidth"
        :collapsable="collapsable"
        :render-content="renderContent"
        :label-class-name="labelClassName"
        @on-expand="(e, data) => $emit('on-expand', e, data)"
        @on-node-focus="(e, data) => $emit('on-node-focus', e, data)"
        @on-node-click="(e, data) => $emit('on-node-click', e, data)"
        @on-node-mouseover="(e, data) => $emit('on-node-mouseover', e, data)"
        @on-node-mouseout="(e, data) => $emit('on-node-mouseout', e, data)"
      />
    </div>
  </div>
</template>
<!--
data:就是数据格式,时一个对象,具体格式下面有示例
horizontal:默认是false,即纵向展示
label-class-name:可以给节点添加的类名,不过我这边使用了没有效果,下面有其他方法来处理节点样式
collapsable:是否折叠,有这个属性,则表示默认折叠,有其他方法可以在存在此属性时,也保证是展开状态
on-expand:点击折叠点,点击可以展开,再次点击可以折叠,是个方法
on-node-click:顾名思义,就是点击节点,触发的事件
on-node-mouseover:鼠标移入节点触发的事件,可以触发一个弹层用于展示详情
on-node-mouseout:鼠标移出节点触发的事件,可以控制详情弹层的隐藏
-->
<script>
import render from './node'

export default {
  name: "SimpleTree",
  components: {
    OrgTreeNode: {
      render,
      functional: true
    }
  },
  props: {
    data: {
      type: Object,
      required: true
    },
    props: {
      type: Object,
      default: () => ({
        id: 'id',
        logo: 'logo',
        key: 'key',
        parentKey: 'parentKey',
        label: 'label',
        expand: 'expand',
        children: 'children',
        type: 'type',
        status: 'status',
        controller: 'controller',
        sys: 'sys',
        remark: 'remark',
        description: 'description',
        registration: 'registration'
      })
    },
    judge: {
      type: Object,
      required: false
    },
    NodeClass: {
      type: Array,
      required: false
    },
    horizontal: Boolean,
    selectedKey: String,
    collapsable: Boolean,
    renderContent: Function,
    labelWidth: [String, Number],
    labelClassName: [Function, String],
    selectedClassName: [Function, String]
  }
}
</script>

<style lang="less">
@import './tree';
</style>

node.js

这个js就是组件渲染的函数,可以稍微研究一下

// 判断是否叶子节点
const isLeaf = (data, prop) => {
  return !(Array.isArray(data[prop]) && data[prop].length > 0)
}
// 创建 node 节点
export const renderNode = (h, data, context) => {
  const { props } = context
  const cls = ['org-tree-node']
  const childNodes = []
  const children = data[props.props.children]

  if (isLeaf(data, props.props.children)) {
    cls.push('is-leaf')
  } else if (props.collapsable && !data[props.props.expand]) {
    cls.push('collapsed')
  }

  if (data) {
    childNodes.push(renderLabel(h, data, context))
  }

  if ((!props.collapsable || data[props.props.expand]) && children.length > 0) {
    childNodes.push(renderChildren(h, children, context))
  }

  return h('div', {
    domProps: {
      className: cls.join(' ')
    }
  }, childNodes)
}

// 创建展开折叠按钮
export const renderBtn = (h, data, { props, listeners }) => {
  const expandHandler = listeners['on-expand']

  let cls = ['org-tree-node-btn']

  if (data[props.props.expand]) {
    cls.push('expanded')
  }

  return h('span', {
    domProps: {
      className: cls.join(' ')
    },
    on: {
      click: e => expandHandler && expandHandler(e,data)
    }
  })
}

// 创建 label 节点
export const renderLabel = (h, data, context) => {
  const { props, listeners } = context
  const label = data[props.props.label]
  const renderContent = props.renderContent

  // event handlers
  const clickHandler = listeners['on-node-click']
  const mouseOverHandler = listeners['on-node-mouseover']
  const mouseOutHandler = listeners['on-node-mouseout']

  const childNodes = []
  if (typeof renderContent === 'function') {
    let vnode = renderContent(h, data)

    vnode && childNodes.push(vnode)
  } else {
    childNodes.push(label)
  }

  if (props.collapsable && !isLeaf(data, props.props.children)) {
    childNodes.push(renderBtn(h, data, context))
  }

  const cls = ['org-tree-node-label-inner']
  let { labelWidth, labelClassName, selectedClassName, selectedKey ,judge,NodeClass} = props

  if (typeof labelWidth === 'number') {
    labelWidth += 'px'
  }

  if (typeof labelClassName === 'function') {
    labelClassName = labelClassName(data)
  }

  labelClassName && cls.push(labelClassName)

  // add selected class and key from props
  if (typeof selectedClassName === 'function') {
    selectedClassName = selectedClassName(data)
  }

  selectedClassName && selectedKey && data[selectedKey] && cls.push(selectedClassName)

  return h('div', {
    domProps: {
      className: 'org-tree-node-label'
    }
  }, [h('div', {
    domProps: {
      className:ChangeTheColor(data,judge,NodeClass) + " org-tree-node-label-inner"
    },
    style: { width: labelWidth },
    on: {
      'click': e => clickHandler && clickHandler(e, data),
      'mouseover': e => mouseOverHandler && mouseOverHandler(e, data),
      'mouseout': e => mouseOutHandler && mouseOutHandler(e, data)
    }
  }, childNodes)])
}

function ChangeTheColor(e,judge,NodeClass){
  if(judge !== "" && judge !== undefined && judge !== null && judge.swtich !== false){
    for(var k in judge) {
      var a = (eval("e."+k))
      if(NodeClass){
        for(let c =0 ;c<NodeClass.length;c++){
          if( a === NodeClass[c])
            return  NodeClass[c]
          else if(NodeClass.length-1==c)
            return ""
        }
      }else{
        return ""
      }
    }
  }else{
    return ""
  }
}
// 创建 node 子节点
export const renderChildren = (h, list, context) => {
  if (Array.isArray(list) && list.length > 0) {
    const children = list.map(item => {
      return renderNode(h, item, context)
    })

    return h('div', {
      domProps: {
        className: 'org-tree-node-children'
      }
    }, children)
  }
  return ''
}

export const render = (h, context) => {
  const {props} = context
  if (props.data.id) {
    return renderNode(h, props.data, context)
  } else {
    return ''
  }

}

export default render

tree.less
.org-tree-container {
  display: inline-block;
  padding: 15px;
  background-color: #fff;
}

.org-tree {
  // display: inline-block;
  display: table;
  text-align: center;

  &:before, &:after {
    content: '';
    display: table;
  }

  &:after {
    clear: both;
  }
}

.org-tree-node,
.org-tree-node-children {
  position: relative;
  margin: 0;
  padding: 0;
  list-style-type: none;

  &:before, &:after {
    transition: all .35s;
  }
}
.org-tree-node-label {
  position: relative;
  display: inline-block;

  .org-tree-node-label-inner {
    padding: 10px 15px;
    text-align: center;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
    -webkit-transition-duration: 0.3s;
    transition-duration: 0.3s;
    cursor: pointer;
  }
  .org-tree-node-label-inner:hover {
    background-color: #c6e2ff;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)
  }

}

.render-node-panel-header {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: center;
  margin: 10px 0;
}
.node-box {
  min-width: 200px;
}

.render-node-panel-middle {
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;
}

.org-tree-node-btn {
  position: absolute;
  top: 100%;
  left: 50%;
  width: 20px;
  height: 20px;
  z-index: 10;
  margin-left: -11px;
  margin-top: 9px;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 50%;
  box-shadow: 0 0 2px rgba(0, 0, 0, .15);
  cursor: pointer;
  transition: all .35s ease;

  &:hover {
    background-color: #e7e8e9;
    transform: scale(1.15);
  }

  &:before, &:after {
    content: '';
    position: absolute;
  }

  &:before {
    top: 50%;
    left: 4px;
    right: 4px;
    height: 0;
    border-top: 1px solid #ccc;
  }

  &:after {
    top: 4px;
    left: 50%;
    bottom: 4px;
    width: 0;
    border-left: 1px solid #ccc;
  }

  &.expanded:after {
    border: none;
  }
}
.org-tree-node {
  padding-top: 20px;
  display: table-cell;
  vertical-align: top;

  &.is-leaf, &.collapsed {
    padding-left: 10px;
    padding-right: 10px;
  }

  &:before, &:after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 50%;
    height: 19px;
  }

  &:after {
    left: 50%;
    border-left: 1px solid #ddd;
  }

  &:not(:first-child):before,
  &:not(:last-child):after {
    border-top: 1px solid #ddd;
  }

}
.collapsable .org-tree-node.collapsed {
  padding-bottom: 30px;

  .org-tree-node-label:after {
    content: '';
    position: absolute;
    top: 100%;
    left: 0;
    width: 50%;
    height: 20px;
    border-right: 1px solid #ddd;
  }
}
.org-tree > .org-tree-node {
  padding-top: 0;

  &:after {
    border-left: 0;
  }
}
.org-tree-node-children {
  padding-top: 20px;
  display: table;

  &:before {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    width: 0;
    height: 20px;
    border-left: 1px solid #ddd;
  }

  &:after {
    content: '';
    display: table;
    clear: both;
  }
}

.horizontal {
  .org-tree-node {
    // display: flex;
    // flex-direction: row;
    // justify-content: flex-start;
    // align-items: center;
    display: table-cell;
    float: none;
    padding-top: 0;
    padding-left: 20px;

    &.is-leaf, &.collapsed {
      padding-top: 10px;
      padding-bottom: 10px;
    }

    &:before, &:after {
      width: 19px;
      height: 50%;
    }

    &:after {
      top: 50%;
      left: 0;
      border-left: 0;
    }

    &:only-child:before {
      top: 1px;
      border-bottom: 1px solid #ddd;
    }

    &:not(:first-child):before,
    &:not(:last-child):after {
      border-top: 0;
      border-left: 1px solid #ddd;
    }

    &:not(:only-child):after {
      border-top: 1px solid #ddd;
    }

    .org-tree-node-inner {
      display: table;
    }

  }

  .org-tree-node-label {
    display: table-cell;
    vertical-align: middle;
  }

  &.collapsable .org-tree-node.collapsed {
    padding-right: 30px;

    .org-tree-node-label:after {
      top: 0;
      left: 100%;
      width: 20px;
      height: 50%;
      border-right: 0;
      border-bottom: 1px solid #ddd;
    }
  }

  .org-tree-node-btn {
    top: 50%;
    left: 100%;
    margin-top: -11px;
    margin-left: 9px;
  }

  & > .org-tree-node:only-child:before {
    border-bottom: 0;
  }

  .org-tree-node-children {
    // display: flex;
    // flex-direction: column;
    // justify-content: center;
    // align-items: flex-start;
    display: table-cell;
    padding-top: 0;
    padding-left: 20px;

    &:before {
      top: 50%;
      left: 0;
      width: 20px;
      height: 0;
      border-left: 0;
      border-top: 1px solid #ddd;
    }

    &:after {
      display: none;
    }

    & > .org-tree-node {
      display: block;
    }
  }
}

使用

组件引入使用

 <simple-tree
        :data="orgData"
        :horizontal="horizontalRadio"
        :label-class-name="labelClassName"
        :collapsable="collapsableRadio"
        @on-expand="onExpand"
        @on-node-click="NodeClick"
        @on-node-mouseover="onMouseover"
        @on-node-mouseout="onMouseout"
        :renderContent="renderContent"/>
  • 参数说明:
data: 就是树形结构,下面会有案例
horizontal:排列形式
collapsable: 是否展开
  • 函数说明:
on-expand: 展开、闭合节点
on-node-click: 节点点击事件
on-node-mouseover:鼠标悬停
on-node-mouseout:鼠标悬出
renderContent:渲染函数

数据结构案例

data(){
	return{
	  labelClassName: "bg-color-orange", // 看到node.js 中这个实际没用的
      basicInfo: { id: null, label: null },
      basicSwitch: false,
      data: {
        id: 0,
        label: "XXX科技有限公司",
        className: 'nxnnxnxn', // 切记HTML的class 标签属性不能以数字开头
        children: [
          {
            id: 2,
            label: "产品研发部",
            className: 'nxnnxnxn-1',
            children: [
              {
                id: 5,
                label: "研发-前端",
                children: [
                  {
                    id: 55,
                    className: 'nxnnxnxn-55',
                    label: "前端1"
                  },
                  {
                    id: 56,
                     className: 'nxnnxnxn-56',
                    label: "前端2"
                  },
                  {
                    id: 57,
                    className: 'nxnnxnxn-57',
                    label: "前端3"
                  },
                  {
                    id: 58,
                     className: 'nxnnxnxn-58',
                    label: "前端4"
                  }
                ]
              }
            ]
          }
        ]
      },
	}
},
methods:{
	//渲染节点
	renderContent(h, data) {
	// 通过data中的className属性来对div元素进行注入class
	// 每个节点渲染必然会走这个函数
	//这里对应的不同的className 需要在上面的tree.less中写入样式
      return (
        <div class = {data.className}>
          <div>
            <i class="el-icon-user-solid"></i>
            <span>{data.label}</span>
            <span></span>
          </div>
          <div style="font-size:12px;line-height:20px;">测试人员</div>
        </div>
      );
    },
    //鼠标移出
    onMouseout(e, data) {
       console.log("onMouseout", data)
    },
    //鼠标移入
    onMouseover(e, data) {
     console.log("onMouseover", data)
    },
    //点击节点
    NodeClick(e, data) {
      console.log(e, data);
    },
    //默认展开
    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);
        }
      }
    },
    collapse(list) {
      list.forEach(child => {
        if (child.expand) {
          child.expand = false;
        }
        child.children && this.collapse(child.children);
      });
    },
    //展开
    onExpand(e, data) {
      if ("expand" in data) {
        data.expand = !data.expand;
        if (!data.expand && data.children) {
          this.collapse(data.children);
        }
      } else {
        this.$set(data, "expand", true);
      }
    },
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Vue.js是一种流行的JavaScript框架,可以用于构建用户界面。要实现流程图组件,可以使用Vue.js结合其他库或插件来实现。以下是一个使用Vue.js和GoJS库来实现流程图组件的示例: 首先,确保你已经安装了Vue.js和GoJS库。可以通过以下命令来安装它们: ```shell npm install vue npm install gojs ``` 然后,在Vue组件中引入GoJS库,并在`mounted`生命周期钩子中初始化流程图。在模板中,可以使用`div`元素作为容器来显示流程图。 ```javascript <template> <div id="flowchart"></div> </template> <script> import go from 'gojs'; export default { mounted() { const $ = go.GraphObject.make; const myDiagram = $(go.Diagram, 'flowchart'); // 在这里添加流程图的定义和布局 // 示例:添加一个节点 myDiagram.nodeTemplate = $(go.Node, 'Auto', $(go.Shape, 'RoundedRectangle', { fill: 'lightblue' }), $(go.TextBlock, { margin: 8 }, new go.Binding('text', 'key')) ); // 示例:添加一个连接线 myDiagram.linkTemplate = $(go.Link, $(go.Shape), $(go.Shape, { toArrow: 'Standard' }) ); // 示例:添加一些节点和连接线 myDiagram.model = new go.GraphLinksModel( [ { key: 'Node1' }, { key: 'Node2' }, { key: 'Node3' } ], [ { from: 'Node1', to: 'Node2' }, { from: 'Node2', to: 'Node3' } ] ); } } </script> ``` 在上述示例中,我们使用了GoJS的一些基本概念,如节点模板和连接线模板。你可以根据自己的需求自定义节点和连接线的样式。 请注意,上述示例只是一个简单的示例,你可以根据自己的需求进行更复杂的定制。同时,你还可以使用其他流程图库或自己手写一个组件来实现流程图功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的钱包空指针了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值