vue3+ts 绘制流程图 vueflow 附代码及效果图

news2024/12/23 6:30:53

已完成渲染流程图,自定义模板内容(上下分级),自定义样式,新增节点addRandomNode,点击修改节点nodeClickHandler(从父组件传值)

官网:https://vueflow.dev/guide/node.html#node-template
文档比较复杂,很多想找的方法没法一下就找到需要注意

我装了三个,如果npm安装报错可以试试yarn add

    "@vue-flow/background": "^1.2.0",
    "@vue-flow/core": "^1.20.2",
    "vue3-flowchart": "^0.19.1"

<template>
  <div style="height: 500px;">
    <VueFlow v-model="elements" @nodeClick="nodeClickHandler"  fit-view-on-init @edgeClick="clickadd"
      @edge-update="onEdgeUpdate" :style="{ background: 'transparent' }" :default-zoom="1.5">

      <Background :pattern-color="'#aaa'" gap="8" />
      

      <template #node-custom="props">
        <CustomNode :node="props"  />
      </template>
<!-- 新增 @edgeClick="clickadd"  -->
    </VueFlow>
  </div>
  <dynamic-modal :visible="visible" top="15vh" width="500px" :title="title" :form="addForm" :items="items" @submit="submit"
    @reset="reset" @close="close" />

  <dynamic-modal :visible="visible_edit" top="15vh" width="500px" :title="'修改节点'" :form="editForm" :items="items" @submit="submit2"
    @reset="reset" @close="close" />
</template>

 
<script setup>
import { ref, reactive, computed, watch , markRaw } from 'vue'
import { Panel, PanelPosition, VueFlow, useVueFlow , isNode,applyChanges} from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { onMounted } from 'vue'
import useLoading from '@/hooks/loading'
import dynamicModal from '@/components/modal/dynamicModal.vue';
import { Message } from '@arco-design/web-vue'
import { queryPostList } from '@/api/data-assets/base-assets.ts'
import { useI18n } from 'vue-i18n';
import router from '@/router';
import CustomNode  from './CustomNode.vue'

const props = defineProps(['processData'])
const { loading, setLoading } = useLoading(true);

const { t } = useI18n()

const { nodes, addNodes, updateEdge, removeEdges, getElements, getNode, addEdges, onConnect, dimensions } = useVueFlow()
onConnect((params) => addEdges(params))
const visible = ref(false)
const visible_edit = ref(false)
const title = ref('选择节点操作')
const img_params = ref({})
// const nodeTypes = {
//   custom: markRaw(CustomNode),
// }
let addForm = ref({
  nodeOperations: '',
  postNames:[],
  ccNames:[],
})
let editForm = ref({
  nodeOperations: '',
})
const op2 = [
  { label: '审批', value: '审批' },
  { label: '确认', value: '确认' },
  { label: '处理', value: '处理' },
]
let optionPost = ref([])
// 新增、编辑弹框
const items = reactive([
  
  { field: 'nodeOperations',width:24, label: computed(()=> t('data.departmentCollaboration.columns.process.flow_1')),disabled:true, type: 'radio_comm',option: op2, rules: [{ required: true, message: computed(()=> t('data.departmentCollaboration.columns.process.select')) }], placeholder: computed(()=> t('data.dictory.form.columns.desc.placeholder'))},
  { field: 'postIds', type: 'select', label: computed(()=> t('data.departmentCollaboration.columns.process.flow_2')),multiple:true,fieldNames: { label: 'postName', value: 'postId' }, rules: [{ required: true, message: computed(()=> t('data.departmentCollaboration.columns.process.select')) }],option: optionPost, placeholder: computed(()=> t('data.departmentCollaboration.columns.process.select')) },
  { field: 'ccIds', type: 'select', label: computed(()=> t('data.departmentCollaboration.columns.process.flow_3')),multiple:true,fieldNames: { label: 'postName', value: 'postId' }, rules: [{ required: true, message: computed(()=> t('data.departmentCollaboration.columns.process.select')) }],option: optionPost, placeholder: computed(()=> t('data.departmentCollaboration.columns.process.select')) }

])
// 提交方法
const submit = async(params)=> {
  
  // console.log(params,img_params.value)
  // let ccNames = []
  // params.ccIds.forEach(id=>{
  //   ccNames.push(optionPost.value.filter(i=>i.postId==id)[0].postName)
  // })
  // params.ccNames = ccNames
  // let postNames = []
  // params.postIds.forEach(id=>{
  //   postNames.push(optionPost.value.filter(i=>i.postId==id)[0].postName)
  // })
  // params.postNames = postNames
  try {
    await addRandomNode(params)
    visible.value = false
    Message.success("添加成功")
  } catch (error) {
    console.log(error)
  }
}

// 重置方法
const reset = ()=> {
  addForm.value = {
    remark:undefined
  }
  editForm.value = {
    remark:undefined
  }
}
const close = ()=> {
  visible.value = false
  visible_edit.value = false
  addForm.value = {
    remark:undefined
  }
  editForm.value = {
    remark:undefined
  }
}
function clickadd  (params){
  Message.info("暂无法新增")
  // img_params.value=params
  // console.log("点击",params)
  // visible.value = true
}
const handleEdgeMouseEnter = (edge) => {
  console.log('Mouse entered edge:', edge);
};
// let elements = ref([])
// elements = computed(() => {
//     return JSON.parse(JSON.stringify(props.processData))
//   })
const elements = ref(
  [
  {
    id: '1', type: 'input', position: { x: 400, y: 5 }, label: "发起人",
     data: {
      title: '发起人',
      content: '这是发起人的内容',
      ccIds:[],//抄送人IDs 实际为岗位数组
      postIds:[],//操作人IDs 实际为岗位数组
      postNames:[],
      ccNames:[],
      nodeOperations:"确认",//操作节点
    },
  },
 
  {
    id: '2', type: 'custom',  position: { x: 400, y: 100 }, label: "审批",
    // template: markRaw(OverwriteCustomNode),
    data: {
      ccIds:[],//抄送人IDs 实际为岗位数组
      postIds:[],//操作人IDs 实际为岗位数组
      postNames:[],
      ccNames:[],
      nodeOperations:"审批",//操作节点
    },
  },
  {
    id: '3', type: 'custom',  position: { x: 400, y: 250 }, label: "处理",
    data: {
      ccIds:[],//抄送人IDs 实际为岗位数组
      postIds:[],//操作人IDs 实际为岗位数组
      postNames:[],
      ccNames:[],
      nodeOperations:"处理",//操作节点
    },
  },
  {
    id: '4',type: 'custom',   position: { x: 400, y: 400 }, label: "确认",
    data: {
      ccIds:[],//抄送人IDs 实际为岗位数组
      postIds:[],//操作人IDs 实际为岗位数组
      postNames:[],
      ccNames:[],
      nodeOperations:"确认",//操作节点
    },
  },
  {
    id: '5', type: 'output', position: { x: 400, y: 550 }, label: "完成",
    data: {
      title: '流程结束',
      content: '这是流程结束的内容',
    },
  },

  { id: 'e1-2', source: '1', type: 'smoothstep', label: '+', target: '2',
    labelBgPadding: [8, 4],
    labelStyle: { fill: '#fff', fontWeight: 700 },
    labelBgBorderRadius: 4,
    labelBgStyle: { fill: '#10b981', color: '#fff', fillOpacity: 1 },},

  { id: 'e2-3', source: '2', type: 'smoothstep', label: '+', target: '3',
    labelBgPadding: [8, 4],
    labelStyle: { fill: '#fff', fontWeight: 700 },
    labelBgBorderRadius: 4,
    labelBgStyle: { fill: '#10b981', color: '#fff', fillOpacity: 1 },},

  { id: 'e3-4', source: '3', type: 'smoothstep', label: '+', target: '4',
    labelBgPadding: [8, 4],
    labelStyle: { fill: '#fff', fontWeight: 700 },
    labelBgBorderRadius: 4,
    labelBgStyle: { fill: '#10b981', color: '#fff', fillOpacity: 1 },},

  { id: 'e4-5', source: '4', type: 'smoothstep', label: '+', target: '5',
    labelBgPadding: [8, 4],
    labelStyle: { fill: '#fff', fontWeight: 700 },
    labelBgBorderRadius: 4,
    labelBgStyle: { fill: '#10b981', color: '#fff', fillOpacity: 1 },},
])
//取父组件的值
//elements.value = props.processData
// console.log(props.processData)
// 修改方法
const submit2 = async(params)=> {
  console.log("params",JSON.stringify(params))
  let ccNames = []
  JSON.parse(JSON.stringify(params.ccIds)).forEach(id=>{
    ccNames.push(optionPost.value.filter(i=>i.postId==id)[0].postName)
  })
  params.ccNames = ccNames
  let postNames = []
  params.postIds.forEach(id=>{
    postNames.push(optionPost.value.filter(i=>i.postId==id)[0].postName)
  })
  params.postNames = postNames

  console.log("params",JSON.stringify(params))
  console.log(elements.value,elements)
  elements.value.forEach(e => {
    if(e.id == img_params.value.node.id){
      // console.log("匹配到了")
      e.data = JSON.parse(JSON.stringify(params))
      e.label = e.data.nodeOperations +"<br/>"+ e.data.postNames
      // console.log(e)
    }
  });
  console.log(elements.value)
  visible_edit.value=false
  // try {
  //   await addRandomNode(params)
  //   visible.value = false
  //   Message.success("添加成功")
  // } catch (error) {
    
  // }
}
const nodeClickHandler = (params) => {
  console.log()
  console.log(JSON.stringify(params.node.data));
  // 如果是发起工单和流程结束,跳过
  if(params.node.label=="发起人" || params.node.label=="完成" ) return
  img_params.value=params
  console.log("点击",params)
  visible_edit.value = true
  editForm.value = params.node.data
};
// addNodes(node1)
// addEdges(edge1)

//新增一个节点
function addRandomNode(params) {
  console.log(img_params)
  //1.新建node,节点位置为(img_params.value.edge.events.sourceX+img_params.value.edge.events.targetX)/2-30,(img_params.value.edge.events.sourceY+img_params.value.edge.events.targetY)/2-15,
  //2.将当前edge线段的target改为新建的nodeid
  //3.新建一个edge线段,source为新建的nodeid,target为原有target,即img_params.value里面的
  //4.将当前新建的node节点以及往后的所有节点的y都新增150
  const nodeId = (nodes.value.length + 1).toString()+'add'
  // console.log(getNode,img_params.value.edge.source)
  // console.log(getNode,img_params.value.edge.source)
  // const source = getNode(img_params.value.edge.source)
  // console.log(source)
  const newNode = {
    id: nodeId,
    label: JSON.parse(JSON.stringify(params.nodeOperations)),
    type:'custom',
    // label: `新增Node: ${nodeId}`,
    position: { x: img_params.value.edge.sourceX-75, y: (img_params.value.edge.sourceY + img_params.value.edge.targetY) / 2 - 15 },
    // position: { x: (img_params.value.edge.sourceX + img_params.value.edge.targetX) / 2 - 30, y: (img_params.value.edge.sourceY + img_params.value.edge.targetY) / 2 - 15 },
    data:JSON.parse(JSON.stringify(params))
    // position: { x: Math.random() * dimensions.value.width, y: Math.random() * dimensions.value.height },
  }
  removeEdges(img_params.value.edge.id)
  const newEdge = {
    id: img_params.value.edge.id,
    source: img_params.value.edge.source,
    type: img_params.value.edge.type,
    label: img_params.value.edge.label,
    target: nodeId,
    updatable: img_params.value.edge.updatable,
    // events: { click: clickadd },
    labelBgPadding: [8, 4],
    labelStyle: { fill: '#fff', fontWeight: 700 },
    labelBgBorderRadius: 4,
    labelBgStyle: { fill: '#10b981', color: '#fff', fillOpacity: 1 },

  }
  const newEdge2 = {
    id: nodeId + 'add',
    source: nodeId,
    type: img_params.value.edge.type,
    label: img_params.value.edge.label,
    target: img_params.value.edge.target,
    updatable: img_params.value.edge.updatable,
    // events: { click: clickadd },
    labelBgPadding: [8, 4],
    labelStyle: { fill: '#fff', fontWeight: 700 },
    labelBgBorderRadius: 4,
    labelBgStyle: { fill: '#10b981', color: '#fff', fillOpacity: 1 },
  }
//   //找到当前指向的下一个节点
//   let index = elements.value.findIndex(item => item.id == img_params.value.edge.target)
//   // console.log(elements.value,img_params.value.edge.target)
//   // console.log(elements.value.findIndex(item => item.id == img_params.value.edge.target))
//   //将节点的y设置
//   for (let i = 0; i > index-1; i++) {
//     let node = elements.value[i]
//     if(isNode(node)){
//       console.log("是node")
//       node.position.y = node.position.y +153
//       // applyChanges(elements.value,{nodes:[node]})
//     }    
//   }
// console.log(elements.value)
  addNodes([newNode])
  addEdges([newEdge])
  addEdges([newEdge2])
}

//新增一个分支
function onEdgeUpdate({ edge, connection }) {
  console.log(edge, connection)
  return updateEdge(edge, connection)
}
const dosomething = ()=>{
  // return getElements.value
  return elements.value
}
onMounted(async() => {
  const a = { page: 1, pageSize: 1000 }
  try {
    const { data } = await queryPostList(a)
    optionPost.value = data.records
  } catch (err) {
    Message.error(err.message)
  }
})
watch(visible_edit, (newValue) => {
  // console.log(elements.value)
  if (!newValue) {
  // console.log("修改??",newValue)
    // 可以在这里执行重新渲染操作
    elements.value = [...elements.value];
  // console.log("修elements改??",elements.value)
  }
});
defineExpose({
  dosomething
})
</script>
 
<style>
/* these are necessary styles for vue flow */
@import "@vue-flow/core/dist/style.css";

/* this contains the default theme, these are optional styles */
@import "@vue-flow/core/dist/theme-default.css";

.custom-node {
  background-color: #f2f2f2;
  border: 1px solid #ccc;
  padding: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.node-title {
  font-weight: bold;
}

.node-content {
  margin-top: 4px;
}</style>

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

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

相关文章

nohup命令解决SpringBoot/java -jar命令启动项目运行一段时间自动停止问题

问题描述&#xff1a; 在centos7上部署多个springcloud项目。出现了服务莫名其妙会挂掉一两个的问题&#xff0c;重新启动挂掉的服务之后又会出现其他服务挂掉的情况&#xff0c;查看启动日志也并没有发现有异常抛出。令人费解的是所有的服务都是通过nohup java -jar xxx.jar …

2020年国赛高教杯数学建模C题中小微企业的信贷决策解题全过程文档及程序

2020年国赛高教杯数学建模 C题 中小微企业的信贷决策 原题再现 在实际中&#xff0c;由于中小微企业规模相对较小&#xff0c;也缺少抵押资产&#xff0c;因此银行通常是依据信贷政策、企业的交易票据信息和上下游企业的影响力&#xff0c;向实力强、供求关系稳定的企业提供贷…

MAYA过山车动画

创建骨骼 把小车模型放入到控制器里 有点问题&#xff0c;先建立一个组在试&#xff0c;没问题了

docker框架02docker的安装

01.这次的docker是在centos版本下的Linux系统中安装的。 02.输入命令 01.先去卸载就得版本 02.安装工具包&#xff0c;和设置镜像仓库 03.由于网络的问题&#xff0c;访问国内的阿里云镜像 修改&#xff1a; 04.更新索引和安装社区版的docker 05.启动docker 06.用命令d…

nginx开启http2导致的服务验证码不可用问题

问题描述: 新搭建了一套开源的系统。通过nginx做了https反向代理后无法显示验证码。 具体报错&#xff1a; Uncaught TypeError: Cannot read properties of null (reading ‘property’) 点击报错后跳转到方法&#xff1a; xhr.getAllResponseHeaders() 问题就出在这个方法&…

leetcode 206.反转链表

⭐️ 往期相关文章 ✨链接&#xff1a;数据结构-手撕单链表代码详解。 ⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;反转链表 1️⃣ 代码&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* }…

html中input属性设置(合集)

html中input标签是常见的一个标签&#xff0c;下面由学习啦小编为大家整理了html中的input属性设置的相关知识&#xff0c;希望对大家有帮助! html中input属性设置总结 1、value 属性 value 属性规定输入字段的初始值&#xff1a; 实例 <form action""> F…

Day9操作系统基础——linux

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 linuxSSH黑客常用命令重难点 linux SSH 黑客常用命令 重难点 linux的相关操作指令

即时抽奖,即时开奖,技术上如何实现?(原来抽奖类的运营活动背后都是这么搞的)...

回答知识星球水友“self”提问。 需求抽象一下&#xff1a; &#xff08;1&#xff09;前100个人有资格&#xff0c;假设抽出N5个奖品&#xff1b; &#xff08;2&#xff09;报名就出奖&#xff0c;不用等100人到齐&#xff1b; &#xff08;3&#xff09;每个人概率一样&…

window debug ios webview

业务需求 在window上想要debug在ios的应用中的webview页面&#xff0c;毕竟页面是在安卓端和ios端都有webview的。安卓的页面使用edge的edge://inspect/#devices&#xff0c;手机开启调试模式就可以了。对于ios就没有办法&#xff0c;页面中已经使用了vconsole可以看到部分的信…

融云观察:社交大佬发家史,模仿才是终极成功学密码?

上周&#xff0c;多所高校因微信支付将收取手续费宣布暂停使用&#xff0c;微信致歉并称在校园非盈利场景将持续保持零费率。关注【融云全球互联网通信云】了解更多 微信的回应非常迅速&#xff0c;但还是多次冲上了热搜榜&#xff0c;可见对一个用户量超过 12 亿的社交巨头来…

fiddler弱网测试 和 Chrome浏览器弱网设置

文章目录 前言 一、fiddler弱网测试 二、Chrome浏览器弱网测试 步骤1&#xff1a;在Fiddler中启动弱网 步骤2&#xff1a;设置网络参数 步骤3&#xff1a;设置完成后&#xff0c;保存 三、弱网测试关注点 总结 前言 测试APP、web经常需要用到弱网测试&#xff0c;也就是…

2023年5月PETS5(WSK)考试经验分享

由于本人明年打算出国联培的缘故&#xff0c;CSC国家留学基金委需要申请人的语言成绩达到一定的要求 英语&#xff08;PETS5&#xff09;&#xff1a;笔试总分55分&#xff08;含&#xff09;以上&#xff0c;其中听力部分18分&#xff08;含&#xff09;以上&#xff0c;口试…

PyQt中资源文件的使用(详细步骤介绍)

新建文件&#xff1a; 在 Qt Creator&#xff0c;选中菜单 File->New File...&#xff0c;选择新建 Qt Resource File。 命名为res.qrc 在项目文件目录树中&#xff0c;会自动出现 Resources 文件组和 res.qrc 文件 添加资源记录&#xff1a; 在文件 res.qrc 上点击右键&a…

靶场DVWA未授权访问导致的RCE

1漏洞地址&#xff1a; http://xxxx.vom/vulnerabilities/exec/source/low.php 2漏洞原因&#xff1a; 命令执行直接拼接 3漏洞验证 linux写入phpinfo(); 到hackable/uploads/目录&#xff08;也可以直接写在当前目录&#xff09; 明文内容&#xff1a; 1&echo “<…

LeViT-UNet:transformer 编码器和CNN解码器的有效整合

levi - unet[2]是一种新的医学图像分割架构&#xff0c;它使用transformer 作为编码器&#xff0c;这使得它能够更有效地学习远程依赖关系。levi - unet[2]比传统的U-Nets更快&#xff0c;同时仍然实现了最先进的分割性能。 levi - unet[2]在几个具有挑战性的医学图像分割基准…

如何删除Linux下乱码文件

第一、使用 ls -i 命令获取文件的节点&#xff0c;如下图所示 第二、执行 find -inum 节点号 -delete 命令 删除成功。红色得就是节点号。

stm32烧录hal库固件后keil检测不到芯片,无法下载,但是按着复位键能下载和检测到芯片

keil检测不到芯片的原因有很多&#xff0c;我的原因是没有在stm32cubemx中配置下载方式 请检查stm32cubemx是否配置下载方式&#xff0c;我这里使用jlink的SWD模式进行下载&#xff0c;所以配置如下&#xff1a; 配置好后重新下载代码就可以检测到芯片了

浅析JS中变量前面的加号 + 的含义

javascript 中经常会看到在变量前面有个加号 &#xff0c;它有什么用处呢&#xff1f;其实很简单&#xff0c;就是把变量转换成 number 类型&#xff08;另外&#xff0c;变量 - 0 也是把变量的值转为数值的一种写法&#xff09;。话不多说&#xff0c;我们先看下面的几个例子…

MySQL数据库汇总

MySQL数据库必须掌握的知识点汇总 文章目录 1、三大范式2、DML 语句和 DDL 语句区别3、主键和外键的区别4、drop、delete、truncate 区别5、基础架构6、MyISAM 和 InnoDB 有什么区别&#xff1f;7、推荐自增id作为主键问题8、为什么 MySQL 的自增主键不连续9、redo log 是做什么…