bpmn.js鼠标右键菜单选择节点

1,935 阅读2分钟

因公司需求节点可以自定义且节点非常多,如果将所有节点放在左侧菜单中会非常难以定位需要的task

故设计右键幕布空白处弹出节点选择菜单

菜单定位

image.png 首先我们给canvas的父级节点加一个id

document.getElementById('content').oncontextmenu=function(ev){
  _this.actionName = ''
  let clintX = ev.clientX;  //ev获取的只是屏幕可视范围的x,y值
  let clintY = ev.clientY;
  // let scollTop = document.documentElement.scrollTop|| document.body.scrollTop;   //当有下拉条的时候必须加上当前屏幕不可视范围的left,和top值
  // let scollLeft = document.documentElement.scrollLeft || document.body.scrollLeft

  _this.mouse.x = clintX
  _this.mouse.y = clintY

  document.getElementById('actionMenu').style.left=(clintX - 200)+'px';
  document.getElementById('actionMenu').style.top=(clintY - 68)+'px';
  document.getElementById('actionMenu').style.display='block';
  return false;    //屏蔽右键菜单
}

然后监听幕布右键菜单事件弹出节点列表 这里通过右键时的鼠标坐标来定位菜单位置 然后将鼠标坐标保存起来用于点击task节点后的定位

菜单内容

image.png

管理节点的页面,task可以自定义task的图标和颜色 image.png

菜单样式

image.png

点击task

addAction(item,event){
  //先隐藏task菜单
  document.getElementById('actionMenu').style.display='none';
  this.$nextTick(e =>{
    let elementRegistry = this.bpmnModeler.get('elementRegistry')
    const elementFactory = this.bpmnModeler.get("elementFactory")
    const modeling = this.bpmnModeler.get("modeling")
    const canvas = this.bpmnModeler.get("canvas")
    const rootElement =canvas.getRootElement()
    let x_canvas
    let y_canvas
    if (canvas._cachedViewbox){
      this.cachedViewbox = canvas._cachedViewbox
      x_canvas = canvas._cachedViewbox.x
      y_canvas = canvas._cachedViewbox.y
    } else {
      x_canvas = this.cachedViewbox.x
      y_canvas = this.cachedViewbox.y
    }
    //此处考虑到如果图有缩放 那么鼠标在中id为content的div中的坐标就和鼠标在幕布中的坐标不同了 所以通过缩放值计算缩放后的坐标
    let x = x_canvas + (this.mouse.x / canvas.zoom())
    let y = y_canvas + (this.mouse.y / canvas.zoom())
    //创建节点
    let branchShape = elementFactory.createShape({
      type: "bpmn:Task",
      id: 'element_id_'+ new Date().getTime()
    })
    branchShape.businessObject.name = item.actionName
    branchShape.businessObject.actionId = item.id
    modeling.createShape(
      branchShape,
      {
        x: x,
        y: y
      },
      rootElement
    )
  })
},

显示效果

foky1-p6spt.gif

渲染

通过 businessObject 中 自定义字段判断task需要显示什么图标什么颜色

drawShape(parentNode, element) {
  const shape = this.bpmnRenderer.drawShape(parentNode, element);
  const type = element.type; // 获取到类型
  // 所有节点都会走这个函数,所以此时只限制,需要自定义的才去自定义,否则仍显示bpmn默认图标
  const businessObject = getBusinessObject(element);
  const { actionId } = businessObject;
  if (customElements.includes(type) && actionId) {

    let tasks = JSON.parse(sessionStorage.getItem('tasks'))
    let task = tasks.find(e =>{
      return e.id === actionId
    })

    const {url, attr} = customConfig[task.icon];
    const customIcon = svgCreate('image', {...attr, href: url});
    element['width'] = 56;
    element['height'] = 56;
    svgAppend(parentNode, customIcon);

    let textNode = parentNode.childNodes[1]
    console.log(textNode)

    textNode.childNodes.forEach(e =>{
      // e.setAttribute('y',parseInt(e.getAttribute('y')) + 52)
      e.style.display = 'none'
    })

    // 下方为更改节点名称位置
    if (element.businessObject.name) {
      const text = svgCreate('text', {
        x: attr.x + 26,
        y: attr.y + attr.height + 20, //位置可以随意调,我理解此时的attr.y 是此时元素的左上角纵坐标
        'font-size': '12',
        'text-anchor': 'middle',
        'dominant-baseline':'middle'
      });
      text.innerHTML = element.businessObject.name;
      svgAppend(parentNode, text);
    }

    if (!element.businessObject.name){
      element.businessObject.name = task.actionName
    }

    //修改边框颜色
    if(task.color){
      shape.style.setProperty('fill', task.color)
    }
    return customIcon;
  }
  return shape;
}

这里参考 # LinDaiDai_霖呆呆 的文章 juejin.cn/post/684490…