【vue3进阶】vue3项目引入流程图LogicFlow

106 阅读3分钟

项目经常会使用到流程图,现在流程图的样式多种多样,目前我用到的比较好的,全面的就是 LogicFlow ,这个流程图的种类比较多,适合二次开发。展示一下示例:

审批流

审批流.png

比赛图

比赛图.png

编排器

编排器.png

树状流程图

树状流程.png

1. 安装插件

npm install @logicflow/core --save

# 插件包(不使用插件时不需要引入)
npm install @logicflow/extension --save

2. 简单案例

<template>
  <el-card header="Graph">
    <div class="flex-wrapper">
      <el-button key="arrow1" type="primary" @click="() => setLine('bezier')">连接线-贝塞尔曲线</el-button>
      <el-button key="arrow2" type="primary" @click="() => setLine('polyline')">连接线-多段线</el-button>
      <el-button key="arrow2" type="primary" @click="() => setLine('line')">连接线-直线</el-button>
      <el-button key="focusOn" type="primary" @click="focusOn"> 定位到node-1 </el-button>
      <el-button key="undo" type="primary" @click="() => lfRef.undo()">上一步</el-button>
      <el-button key="redo" type="primary" @click="() => lfRef.redo()">下一步</el-button>
      <el-button key="clearData" type="primary" @click="() => lfRef.clearData()">清空数据</el-button>
      <el-button key="changeType" type="primary" @click="changeNodeType">切换节点为圆形</el-button>
      <el-button key="cancelEdit" type="primary" @click="cancelEdit">禁止编辑</el-button>
      <el-button key="cancelEdit" type="primary" @click="canEdit">允许编辑</el-button>
      <el-button key="getData" type="primary" @click="() => getGraphData()">获取选中节点数据</el-button>
      <el-button key="setZoom" type="primary" @click="() => lfRef.zoom(0.6, [400, 400])">设置大小</el-button>
      <el-button key="selectElement" type="primary" @click="() => checkNode()">选中指定节点</el-button>
      <el-button key=" translateCenter" type="primary" @click="() => lfRef.translateCenter()">居中</el-button>
      <el-button key="fitView" type="primary" @click="() => lfRef.fitView()">适应屏幕</el-button>
      <el-button key="deleteNode" type="primary" @click="() => delNode()">删除节点</el-button>
    </div>
    <el-divider content-position="left">节点面板</el-divider>
    <div class="flex-wrapper">
      <div className="dnd-item wrapper" @mousedown="handleDragRect">矩形</div>
      <div className="dnd-item wrapper" @mousedown="handleDragCircle">圆形</div>
      <div className="dnd-item wrapper" @mousedown="handleDragDiamond">菱形</div>
      <div className="dnd-item wrapper" @mousedown="handleDragEllipse">椭圆</div>
      <div className="dnd-item wrapper" @mousedown="handleDragPolygon">多边形</div>
      <div className="dnd-item wrapper" @mousedown="handleDragText">文本</div>
    </div>
    <el-divider />
    <div ref="containerRef" id="graph" class="viewport"></div>
  </el-card>
</template>

<script setup>
import LogicFlow from '@logicflow/core'
import '@logicflow/core/es/index.css'

const data = {
  nodes: [
    {
      id: 'custom-node-1',
      text: 'node-1',
      type: 'polygon',
      x: 90,
      y: 94,
    },
  ]
}

const lfRef = ref(null)
const containerRef = ref(null)
const flowId = ref('')

onMounted(() => {
  if (containerRef.value) {
    const lf = new LogicFlow({
      container: containerRef.value,
      height: 400,
      multipleSelectKey: 'ctrl',
      disabledTools: ['multipleSelect'],
      autoExpand: true,
      adjustEdgeStartAndEnd: true,
      allowRotate: true,
      edgeTextEdit: true,
      keyboard: {
        enabled: true
      },
      partial: true,
      background: {
        color: '#FFFFFF'
      },
      grid: true,
      edgeTextDraggable: true,
      edgeType: 'bezier',
      style: {
        inputText: {
          background: 'black',
          color: 'white'
        }
      },
      idGenerator(type) {
        return type + '_' + Math.random()
      }
    })

    lf.on('graph:rendered', ({ graphModel }) => {
      flowId.value = graphModel?.flowId || ''
    })

    // 渲染数据
    lf.render(data)

    lfRef.value = lf
  }
})

// 设置箭头
const setLine = (arrowName) => {
  const lf = lfRef?.value
  if (lf) {
    const { edges } = lf.getSelectElements()
    edges.forEach(({ id, properties }) => {
      lf.changeEdgeType(id, arrowName)
    })
  }
}
// 定位到指定节点
const focusOn = () => {
  lfRef?.value?.focusOn({
    id: 'custom-node-1'
  })
}
// 切换节点类型
const changeNodeType = () => {
  const lf = lfRef?.value
  if (lf) {
    const { nodes } = lf.getSelectElements()
    nodes.forEach(({ id, type }) => {
      lf.changeNodeType(id, type === 'rect' ? 'circle' : 'rect')
    })
  }
}

// 取消编辑
const cancelEdit = () => {
  const lf = lfRef?.value
  if (lf) {
    const { editConfigModel } = lf.graphModel
    editConfigModel.updateEditConfig({
      isSilentMode: true, // 是否为静默模式
      stopZoomGraph: true, // 禁止缩放画布
      stopScrollGraph: true, // 禁止鼠标滚动移动画布
      stopMoveGraph: true // 禁止拖动画布
    });
  }
}

const canEdit = () => {
  const lf = lfRef?.value
  if (lf) {
    const { editConfigModel } = lf.graphModel
    editConfigModel.updateEditConfig({
      isSilentMode: false,
      stopZoomGraph: false,
      stopScrollGraph: false,
      stopMoveGraph: false
    });
  }
}

// 获取选中节点数据
const getGraphData = () => {
  const lf = lfRef?.value
  if (lf) {
    const { nodes } = lf.getSelectElements()
    console.log(nodes);
  }
}

// 选中指定节点
const checkNode = () => {
  const lf = lfRef?.value
  if (lf) {
    lf.selectElementById('custom-node-1')
  }
}

// 删除节点
const delNode = () => {
  const lf = lfRef?.value
  if (lf) {
    const { nodes } = lf.getSelectElements()
    nodes.forEach(({ id }) => {
      lf.deleteNode(id)
    })
  }
}

const handleDragRect = () => {
  lfRef?.value?.dnd.startDrag({
    type: 'rect',
    text: '矩形'
  })
}
const handleDragCircle = () => {
  lfRef?.value?.dnd.startDrag({
    type: 'circle',
    text: '圆形',
    r: 25
  })
}

const handleDragDiamond = () => {
  lfRef?.value?.dnd.startDrag({
    type: 'diamond',
    text: '菱形',
  })
}

const handleDragEllipse = () => {
  lfRef?.value?.dnd.startDrag({
    type: 'ellipse',
    text: '椭圆',
    properties: {
      rx: 40,
      ry: 80,
    },
  })
}

const handleDragPolygon = () => {
  let x = 50, y = 50
  lfRef?.value?.dnd.startDrag({
    type: 'polygon',
    text: '多边形',
    properties: {
      points: [
        [x - 0.205 * 100, y - 0.5 * 100],
        [x + 0.205 * 100, y - 0.5 * 100],
        [x + 0.5 * 100, y - 0.205 * 100],
        [x + 0.5 * 100, y + 0.205 * 100],
        [x + 0.205 * 100, y + 0.5 * 100],
        [x - 0.205 * 100, y + 0.5 * 100],
        [x - 0.5 * 100, y + 0.205 * 100],
        [x - 0.5 * 100, y - 0.205 * 100],
      ],
    }
  })
}

const handleDragText = () => {
  lfRef?.value?.dnd.startDrag({
    type: 'text',
    text: '文本'
  })
}

const handleDragStar = () => {
  lfRef?.value?.dnd.startDrag({
    type: 'star',
    text: '五角星'
  })
}
</script>
<style scoped lang="scss">
.flex-wrapper {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

*:focus {
  outline: none;
}

.rect {
  width: 50px;
  height: 50px;
  background: #fff;
  border: 2px solid #000;
}

.circle {
  width: 50px;
  height: 50px;
  background: #fff;
  border: 2px solid #000;
  border-radius: 50%;
}

.dnd-item {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  user-select: none;
}

.wrapper {
  width: 80px;
  height: 50px;
  background: #fff;
  border: 2px solid #000;
}
</style>

以下为效果图

QQ截图20241224165132.png

大部分功能都能实现,根据自己的需求去选择。