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

news2024/12/26 22:37:35

我们可以基于 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">
    <!-- <ElButton οnclick="alert(123)" type="primary" style="margin-left: 15px">Title</ElButton> -->
    <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>Example01</h3>
  <div id="container"></div>
  <!-- 用于显示 LogicFlow 图表的容器 -->
</template>

<style>
#container {
  /* 容器宽度 */
  width: 100%;
  /* 容器高度 */
  height: 600px;
}
</style>

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1843041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

易兆微电子_嵌入式软件工程师笔试题

易先电子 嵌入式软件工程师笔试题(十七) 1.关键字 extern是什么含义, 请举例说明。 修饰符extern用在变量或者函数的声明前&#xff0c;用来说明 “ 此变量 / 函数是在别处定义的&#xff0c;要在此处引用 ”。 //main.c #include <stdio.h>int main() {extern int num…

HTML播放flv

页面效果&#xff1a; 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" …

Object类hashCode方法和equals方法源码

hashCode方法 顶级类Object里面的方法&#xff0c;所有类都是继承Object的&#xff0c;返回值int类型 根据一定的hash规则&#xff08;存储地址、字段、或者长度等&#xff09;&#xff0c;映射成一个数值&#xff0c;即散列值 public static int hashCode(Object a[]) {if (a…

windows系统下安装redis,并进行密码配置

一、windows系统下安装redis Redis&#xff08;Remote Dictionary Server &#xff0c;远程字典服务&#xff09; 是一个高性能的key-value数据格式的内存数据库&#xff0c;是NoSQL数据库。redis的出现主要是为了替代早起的Memcache缓存系统的。 内存型(数据存放在内存中)的非…

MPI并行计算关键点讲解及使用入门

MPI&#xff08;Message Passing Interface&#xff09;是并行计算领域的一个关键标准&#xff0c;它定义了一套用于在多个计算节点间进行高效消息传递和数据交换的通信协议和库。在高性能计算&#xff08;HPC&#xff09;领域&#xff0c;MPI尤为重要&#xff0c;特别是在处理…

Nuxt3 实战 (十一):添加路由 Transition 过渡效果和 Loading 动画

页面过渡效果 Nuxt3 利用 Vue 的 组件 在页面和布局之间应用过渡效果。 nuxt.config.ts 文件配置&#xff1a; export default defineNuxtConfig({app: {pageTransition: { name: page, mode: out-in }}, })在页面之间添加过渡效果&#xff0c;在 app.vue 文件中添加以下 CS…

opencv 打开图片后,cv::mat存入共享内存的代码,实现消费者与生产者模型。XSI信号量和POSIX 信号量

文章目录 基于 sys 系统信号量(XSI信号量)常用api参考 基于 POSIX 信号量有名信号量常用 api 无名信号量常用 api 参考 实践-基于POSIX有名信号量生产者消费者模型任务说明同步关系互斥关系 设置一个互斥信号量&#xff0c;实现对共享内存的互斥访问设置两个信号量&#xff0c;…

ESP32 矩阵键盘 4*3状态机

简洁高效的ESP32处理矩阵键盘代码… /**********矩阵键盘IO映射***************3(9) 1(8) 5(4)2(13)7(12)6(18)4(19)*************************************/ uint8_t Trg0,Cont0; void Key_Task(void) {uint8_t ReadData,ColumnData,RowData;pinMode(9,INPUT_PULLUP);pin…

[面试题]RabbitMQ

[面试题]Java【基础】[面试题]Java【虚拟机】[面试题]Java【并发】[面试题]Java【集合】[面试题]MySQL[面试题]Maven[面试题]Spring Boot[面试题]Spring Cloud[面试题]Spring MVC[面试题]Spring[面试题]MyBatis[面试题]Nginx[面试题]缓存[面试题]Redis[面试题]消息队列[面试题]…

候选键的确定方法-如何判断属性集U的子集K是否为候选键、如何找到关系模式的候选键

一、候选键的定义 在关系模式R(U,F)中&#xff0c;若&#xff0c;且K满足&#xff0c;则K为关系模式R的候选键 关系模式R的候选键必须满足以下两个条件&#xff1a; &#xff08;1&#xff09;必须是属性集U的子集 &#xff08;2&#xff09;完全函数决定属性集U 二、如何…

使用opencv合并两个图像

本节的目的 linear blending&#xff08;线性混合&#xff09;使用**addWeighted()**来添加两个图像 原理 (其实我也没太懂&#xff0c;留个坑&#xff0c;感觉本科的时候线代没学好。不对&#xff0c;我本科就没学线代。) 源码分析 源码链接 #include "opencv2/imgc…

工控 UI 风格美轮美奂

工控 UI 风格美轮美奂

Docker 部署项目,真的太雅了~

大家好&#xff0c;我是南城余&#xff01; 最近在找工作&#xff0c;正好手里有台服务器&#xff0c;之前项目上线用的宝塔部署项目上线&#xff0c;在公司实习了一年后&#xff0c;发现如今项目部署都使用的是容器化部署方案&#xff0c;也就是类似于和 Docker 一样的部署方案…

PFC 离散元数值模拟仿真技术与应用

近几年&#xff0c;随着计算能力的提高和算法的优化&#xff0c;离散元仿真技术得到了快速发展&#xff0c;并在学术界产生了大量研究成果。在 PFC 离散元计算中无需给定材料的宏观本构关系和对应的参数&#xff0c;这些传统的参数和力学特性在程序中可以自动得到。据调查&…

【绝对有用】刚刚开通的GPT-4o计算这种数学题目出现问题了

欢迎关注如何解决以上问题的方法&#xff1a;查看个人简介中的链接的具体解决方案

Matlab数学建模实战应用:案例2 - 传染病传播

目录 前言 一、问题分析 二、模型建立 三、Matlab代码实现 四、模型验证 灵敏度分析 五、模型应用 实例总结 总结 前言 传染病传播模型是公共卫生和流行病学的重要研究内容&#xff0c;通过数学建模可以帮助我们理解传染病的传播规律和趋势&#xff0c;以便制定有效的…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 机器人搬砖(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

全网最易懂,开源时序数据库influxDB,实际应用评测

前言&#xff1a; 当今是信息爆炸的时代&#xff0c;在处理高频数据时&#xff0c;关系型数据库oracle/mysql明显表现出乏力&#xff0c;因秒级、毫秒级高频数据&#xff0c;分分钟可以把关系型数据库的表塞爆。在日常生活工作中&#xff0c;我们经常会遇到哪些需要高频分析的场…

令人震撼的人类智慧的科学领域-AI技术

AI&#xff0c;全称为人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;是一门致力于让机器模仿人类智慧的科学领域。其核心技术涵盖了机器学习、自然语言处理、计算机视觉及专家系统等多个方面。AI旨在开发能够感知环境、进行逻辑推理、自主学习并做出决策…

Leetcode 力扣124. 二叉树中的最大路径和 (抖音号:708231408)

二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root &#xff0c…