• LogicFlow 学习笔记——10. LogicFlow 进阶 边


    我们可以基于 Vue 组件自定义边,可以在边上添加任何想要的 Vue 组件,甚至将原有的边通过样式隐藏,重新绘制。
    如 Example3 中所示:
    在这里插入图片描述

    锚点

    默认情况下,LogicFlow 只记录节点与节点的信息。但是在一些业务场景下,需要关注到锚点,比如在 UML 类图中的关联关系;或者锚点表示节点的入口和出口之类。这个时候需要重写连线的保存方法,将锚点信息也一起保存。

    class CustomEdgeModel2 extends LineEdgeModel {
      // 重写此方法,使保存数据是能带上锚点数据。
      getData() {
        const data = super.getData();
        data.sourceAnchorId = this.sourceAnchorId;
        data.targetAnchorId = this.targetAnchorId;
        return data;
      }
    }
    

    动画

    由于 LogicFlow 是基于 svg 的流程图编辑框架,所以我们可以给 svg 添加动画的方式来给流程图添加动画效果。为了方便使用,我们也内置了基础的动画效果。在定义边的时候,可以将属性isAnimation设置为 true 就可以让边动起来,也可以使用lf.openEdgeAnimation(edgeId)来开启边的默认动画。

    class CustomEdgeModel extends PolylineEdgeModel {
      setAttributes() {
        this.isAnimation = true;
      }
      getEdgeAnimationStyle() {
        const style = super.getEdgeAnimationStyle();
        style.strokeDasharray = "5 5";
        style.animationDuration = "10s";
        return style;
      }
    }
    

    下面我们对上面的内容写一个简单的样例:
    样例中使用了 JSX 所以需要进行配置,在项目中,运行pnpm install @vitejs/plugin-vue-jsx并在vite.config.js增加如下配置:

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';
    
    export default defineConfig({
      plugins: [vue(), vueJsx()]
    });
    

    新建src/views/Example/LogicFlowAdvance/Edge/Example01/CustomCard.vue代码如下:

    <script setup lang="tsx">
    import { ref } from 'vue'
    
    const props = defineProps({
      properties: {
        type: Object,
        required: true
      }
    })
    
    type Answer = {
      text: string
      id: string
    }
    
    type Properties = {
      title: string
      content: string
      answers: Answer[]
    }
    
    // Example props passed to the component
    const properties = ref(props.properties as Properties)
    script>
    <template>
      <div class="html-card">
        
        <div class="html-card-header">{{ properties.title }}div>
        <div class="html-card-body">{{ properties.content }}div>
        <div class="html-card-footer">
          <div v-for="answer in properties.answers" :key="answer.id" class="html-card-label">
            {{ answer.text }}
          div>
        div>
      div>
    template>
    <style scoped>
    .html-card {
      width: 240px;
      height: 100%;
      box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
      border-radius: 4px;
      border: 1px solid #ebeef5;
      background-color: #fff;
      overflow: hidden;
      color: #303133;
      transition: 0.3s;
      box-sizing: border-box;
      padding: 5px;
    }
    /* 定义节点不被允许连接的时候,节点样式 */
    .lf-node-not-allow .html-card {
      border-color: #f56c6c;
    }
    .lf-node-allow .html-card {
      border-color: #67c23a;
    }
    .html-card-header {
      font-size: 12px;
      line-height: 24px;
      margin-left: 14px;
    }
    .html-card-header:before {
      content: '';
      position: absolute;
      left: 5px;
      top: 13px;
      display: block;
      width: 7px;
      height: 7px;
      border: 1px solid #cbcef5;
      border-radius: 6px;
    }
    .html-card-body {
      font-size: 12px;
      color: #6f6a6f;
      margin-top: 5px;
    }
    
    .html-card-footer {
      display: flex;
      position: absolute;
      bottom: 5px;
    }
    .html-card-label {
      font-size: 12px;
      line-height: 16px;
      padding: 2px;
      background: #ebeef5;
      margin-right: 10px;
    }
    style>
    

    新建src/views/Example/LogicFlowAdvance/Edge/Example01/CustomCard.tsx代码如下:

    import { HtmlNode, HtmlNodeModel } from '@logicflow/core'
    import { createApp, h, App, VNode, render } from 'vue'
    import CustomCard from './CustomCard.vue'
    
    class HtmlCard extends HtmlNode {
      isMounted: boolean
      app: App<Element>
      r: VNode
    
      constructor(props: any) {
        super(props)
        this.isMounted = false
        this.r = h(CustomCard, {
          properties: props.model.getProperties(),
          text: props.model.inputData
        })
        this.app = createApp({
          render: () => this.r
        })
      }
    
      // 重写HtmlNode的setHtml,来控制html节点内容。
      setHtml(rootEl: HTMLElement) {
        if(!this.isMounted) {
            this.isMounted = true
            const node = this.getCardEl()
            render(node, rootEl)
        } else {
          if (this.r.component) {
            this.r.component.props.properties = this.props.model.getProperties();
          }
        }
      }
      getCardEl() {
        const { properties } = this.props.model
        return <><CustomCard properties={properties} /></>
      }
    }
    class HtmlCardModel extends HtmlNodeModel {
      initNodeData(data: any) {
        super.initNodeData(data)
        // 禁止节点文本可以编辑
        this.text.editable = false
        this.width = 240
        // 定义连接规则,只允许出口节点连接入口节点
        const rule = {
          message: '只允许出口节点连接入口节点',
          validate: (sourceNode: any, targetNode: any, sourceAnchor: any, targetAnchor: any) => {
            console.log(sourceNode, targetNode)
            console.log(sourceAnchor, targetAnchor)
            return sourceAnchor.type === 'sourceAnchor' && targetAnchor.type === 'targetAnchor'
          }
        }
        this.sourceRules.push(rule)
      }
      setAttributes() {
        const {
          properties: { content }
        } = this
        // 动态计算节点的高度
        const rowSize = Math.ceil(content.length / 20)
        this.height = 60 + rowSize * 18
      }
      /**
       * 计算每个锚点的位置
       */
      getDefaultAnchor() {
        const { height, x, y, id, properties } = this
        const anchorPositon = []
        anchorPositon.push({
          x,
          y: y - height / 2,
          type: 'targetAnchor',
          id: `${id}_targetAnchor`
        })
        if (properties.answers) {
          let preOffset = 5
          properties.answers.forEach((answer: any) => {
            const text = answer.text
            // 计算每个锚点的位置,锚点的位置一般相对节点中心点进行偏移
            const offsetX = preOffset + (this.getBytesLength(text) * 6 + 4) / 2 - this.width / 2
            preOffset += this.getBytesLength(text) * 6 + 4 + 10
            const offsetY = height / 2
            anchorPositon.push({
              x: x + offsetX,
              y: y + offsetY,
              type: 'sourceAnchor',
              id: answer.id
            })
          })
        }
        return anchorPositon
      }
      getBytesLength(word: any) {
        if (!word) {
          return 0
        }
        let totalLength = 0
        for (let i = 0; i < word.length; i++) {
          const c = word.charCodeAt(i)
          if (word.match(/[A-Z]/)) {
            totalLength += 1.5
          } else if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
            totalLength += 1.2
          } else {
            totalLength += 2
          }
        }
        return totalLength
      }
    }
    
    export default {
      type: 'html-card',
      view: HtmlCard,
      model: HtmlCardModel
    }
    

    新建src/views/Example/LogicFlowAdvance/Edge/Example01/CustomEdge.tsx代码如下:

    import { BezierEdge, BezierEdgeModel } from '@logicflow/core'
    
    class CustomEdge extends BezierEdge {}
    
    class CustomEdgeModel extends BezierEdgeModel {
      getEdgeStyle() {
        const style = super.getEdgeStyle()
        // svg属性
        style.strokeWidth = 1
        style.stroke = '#ababac'
        return style
      }
      /**
       * 重写此方法,使保存数据是能带上锚点数据。
       */
      getData() {
        const data: any = super.getData()
        data.sourceAnchorId = this.sourceAnchorId
        data.targetAnchorId = this.targetAnchorId
        return data
      }
    
      setAttributes() {
        this.isAnimation = true;
      }
    }
    
    export default {
      type: 'custom-edge',
      view: CustomEdge,
      model: CustomEdgeModel
    }
    

    新建src/views/Example/LogicFlowAdvance/Edge/Example01/data.ts,内容如下:

    const data = {
      nodes: [
        {
          id: 'node_id_1',
          type: 'html-card',
          x: 340,
          y: 100,
          properties: {
            title: '普通话术',
            content: '喂,您好,这里是XX装饰,专业的装修品牌。请问您最近有装修吗?',
            answers: [
              { id: '1', text: '装好了' },
              { id: '2', text: '肯定' },
              { id: '3', text: '拒绝' },
              { id: '4', text: '否定' },
              { id: '5', text: '默认' }
            ]
          }
        },
        {
          id: 'node_id_2',
          type: 'html-card',
          x: 160,
          y: 300,
          properties: {
            title: '推荐话术',
            content:
              '先生\\女士,您好!几年来,我们通过对各种性质的建筑空间进行设计和施工,使我们积累了丰富的管理、设计和施工经验,公司本着以绿色环保为主题,对家居住宅、办公、商铺等不同特点的室内装饰产品形成了独特的装饰理念。',
            answers: [
              { id: '1', text: '感兴趣' },
              { id: '2', text: '不感兴趣' },
              { id: '3', text: '拒绝' }
            ]
          }
        },
        {
          id: 'node_id_3',
          type: 'html-card',
          x: 480,
          y: 260,
          properties: { title: '结束话术', content: '抱歉!打扰您了!', answers: [] }
        },
        {
          id: 'node_id_4',
          type: 'html-card',
          x: 180,
          y: 500,
          properties: {
            title: '结束话术',
            content: '好的,我们将安排师傅与您联系!',
            answers: []
          }
        }
      ],
      edges: [
        {
          id: 'e54d545f-3381-4769-90ef-0ee469c43e9c',
          type: 'custom-edge',
          sourceNodeId: 'node_id_1',
          targetNodeId: 'node_id_2',
          startPoint: { x: 289, y: 148 },
          endPoint: { x: 160, y: 216 },
          properties: {},
          pointsList: [
            { x: 289, y: 148 },
            { x: 289, y: 248 },
            { x: 160, y: 116 },
            { x: 160, y: 216 }
          ],
          sourceAnchorId: '2',
          targetAnchorId: 'node_id_2_targetAnchor'
        },
        {
          id: 'ea4eb652-d5de-4a85-aae5-c38ecc013fe6',
          type: 'custom-edge',
          sourceNodeId: 'node_id_2',
          targetNodeId: 'node_id_4',
          startPoint: { x: 65, y: 384 },
          endPoint: { x: 180, y: 461 },
          properties: {},
          pointsList: [
            { x: 65, y: 384 },
            { x: 65, y: 484 },
            { x: 180, y: 361 },
            { x: 180, y: 461 }
          ],
          sourceAnchorId: '1',
          targetAnchorId: 'node_id_4_targetAnchor'
        },
        {
          id: 'da216c9e-6afe-4472-baca-67d98abb1d31',
          type: 'custom-edge',
          sourceNodeId: 'node_id_1',
          targetNodeId: 'node_id_3',
          startPoint: { x: 365, y: 148 },
          endPoint: { x: 480, y: 221 },
          properties: {},
          pointsList: [
            { x: 365, y: 148 },
            { x: 365, y: 248 },
            { x: 480, y: 121 },
            { x: 480, y: 221 }
          ],
          sourceAnchorId: '4',
          targetAnchorId: 'node_id_3_targetAnchor'
        },
        {
          id: '47e8aff3-1124-403b-8c64-78d94ec03298',
          type: 'custom-edge',
          sourceNodeId: 'node_id_1',
          targetNodeId: 'node_id_3',
          startPoint: { x: 327, y: 148 },
          endPoint: { x: 480, y: 221 },
          properties: {},
          pointsList: [
            { x: 327, y: 148 },
            { x: 327, y: 248 },
            { x: 476, y: 161 },
            { x: 480, y: 221 }
          ],
          sourceAnchorId: '3',
          targetAnchorId: 'node_id_3_targetAnchor'
        }
      ]
    }
    
    export default data
    

    最后新建src/views/Example/LogicFlowAdvance/Edge/Example01/Example01.vue内容如下:

    <script setup lang="ts">
    import LogicFlow from '@logicflow/core'
    import '@logicflow/core/dist/style/index.css'
    import { onMounted } from 'vue'
    import data from './data'
    import CustomCard from './CustomCard'
    import CustomEdge from './CustomEdge'
    import CustomEdge2 from './CustomEdge2'
    
    // 在组件挂载时执行
    onMounted(() => {
      // 创建 LogicFlow 实例
      const lf = new LogicFlow({
        container: document.getElementById('container')!, // 指定容器元素
        grid: true // 启用网格
      })
      lf.register(CustomCard)
      lf.register(CustomEdge)
      lf.register(CustomEdge2)
      lf.setDefaultEdgeType('custom-edge')
      lf.render(data)
    })
    script>
    
    <template>
      <h3>Example01h3>
      <div id="container">div>
      
    template>
    
    <style>
    #container {
      /* 容器宽度 */
      width: 100%;
      /* 容器高度 */
      height: 600px;
    }
    style>
    

    样例运行如下:
    在这里插入图片描述

  • 相关阅读:
    JAVA毕业设计高校考务管理计算机源码+lw文档+系统+调试部署+数据库
    Hadoop大数据系统架构(深入浅出)
    分库分表
    3 分钟掌握 Node.js 版本的区别
    电力智能运维管理平台:提升电力行业运营效率与安全
    【PAT甲级】1153 Decode Registration Card of PAT
    Linux下修改可执行程序或者库的动态链接库的路径
    redis集群节点间的内部通信机制
    Packet Tracer的使用介绍
    Kettle【实践 08】全国地铁线路信息及线路站点座标数据获取脚本及技巧说明(云资源分享:完整ktr脚本及20221008最新数据SQL)
  • 原文地址:https://blog.csdn.net/lt5227/article/details/139735469