vue antv/x6流程图
自己设计的流程图 vue2加element-ui 有类似可以直接修改拿去用 每个流程都可以配置,只是流程线没加配置 有需要流程线配置可以看官文档 https://x6.antv.vision/zh/docs/tutorial/basic/graph 我用的antv/x6版本是"@antv/x6": “~1.16.0”,
<template>
<div>
<el-button
style="float: right; margin-top: -70px; margin-right: 180px"
size="mini"
type="primary"
@click="saveFlow"
>
保存
</el-button>
<el-button
style="float: right; margin-top: -70px; margin-right: 100px"
size="mini"
type="primary"
@click="$router.back(-1)"
>
返回
</el-button>
<el-container class="process-edit-container">
<!-- 顶部功能区域 -->
<el-header>
<el-form ref="workflow" :model="workflow" :rules="rules" label-width="80px" style="display: flex">
<el-form-item label="名称" prop="name" style="width: 300px">
<el-input v-model="workflow.name" size="mini" :disabled="disables" />
</el-form-item>
<el-form-item label="描述" prop="description" style="width: 300px">
<el-input v-model="workflow.description" size="mini" />
</el-form-item>
<el-form-item label="产品线" prop="productLine" style="width: 300px">
<el-select
v-model="workflow.productLine"
clearable
filterable
placeholder="请选择产品线"
size="mini"
style="width: 100%"
:disabled="disables"
@change="getJobName"
>
<el-option
v-for="(option, index) in [ { label: 'mall', value: 1 }, { label: 'store', value: 2 } ]"
:key="index"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</el-form>
</el-header>
<el-container>
<!-- 左侧组件区域 -->
<el-aside width="200px">
<div class="process-component-list">
<img
data-color="#FA8C16"
data-label="开始"
data-shape="circle"
data-size="72*72"
data-type="node"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0ic3RhcnRfbm9kZSIgd2lkdGg9IjgwcHgiIGhlaWdodD0iODBweCIgdmlld0JveD0iMCAwIDgwIDgwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCA0OS4xICg1MTE0NykgLSBodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2ggLS0+CiAgICA8dGl0bGU+R3JvdXAgMjwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxjaXJjbGUgaWQ9InBhdGgtMSIgY3g9IjM2IiBjeT0iMzYiIHI9IjM2Ij48L2NpcmNsZT4KICAgICAgICA8ZmlsdGVyIHg9Ii05LjclIiB5PSItNi45JSIgd2lkdGg9IjExOS40JSIgaGVpZ2h0PSIxMTkuNCUiIGZpbHRlclVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgaWQ9ImZpbHRlci0yIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IGR4PSIwIiBkeT0iMiIgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMSI+PC9mZU9mZnNldD4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMiIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIj48L2ZlR2F1c3NpYW5CbHVyPgogICAgICAgICAgICA8ZmVDb21wb3NpdGUgaW49InNoYWRvd0JsdXJPdXRlcjEiIGluMj0iU291cmNlQWxwaGEiIG9wZXJhdG9yPSJvdXQiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUNvbXBvc2l0ZT4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgICAwIDAgMCAwIDAgICAwIDAgMCAwIDAgIDAgMCAwIDAuMDQgMCIgdHlwZT0ibWF0cml4IiBpbj0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUNvbG9yTWF0cml4PgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IuWfuuehgOa1geeoi+Wbvi0wMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEwNi4wMDAwMDAsIC05My4wMDAwMDApIj4KICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExMC4wMDAwMDAsIDk1LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPGcgaWQ9Ik92YWwiPgogICAgICAgICAgICAgICAgICAgIDx1c2UgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMSIgZmlsdGVyPSJ1cmwoI2ZpbHRlci0yKSIgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgICAgICAgICAgPHVzZSBmaWxsLW9wYWNpdHk9IjAuOTIiIGZpbGw9IiNGRkYyRTgiIGZpbGwtcnVsZT0iZXZlbm9kZCIgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgICAgICAgICAgPGNpcmNsZSBzdHJva2U9IiNGRkMwNjkiIHN0cm9rZS13aWR0aD0iMSIgY3g9IjM2IiBjeT0iMzYiIHI9IjM1LjUiPjwvY2lyY2xlPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPHRleHQgaWQ9IuW8gOWni+iKgueCuSIgZm9udC1mYW1pbHk9IlBpbmdGYW5nU0MtUmVndWxhciwgUGluZ0ZhbmcgU0MiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSJub3JtYWwiIGxpbmUtc3BhY2luZz0iMTIiIGZpbGw9IiMwMDAwMDAiIGZpbGwtb3BhY2l0eT0iMC42NSI+CiAgICAgICAgICAgICAgICAgICAgPHRzcGFuIHg9IjEyIiB5PSI0MSI+5byA5aeL6IqC54K5PC90c3Bhbj4KICAgICAgICAgICAgICAgIDwvdGV4dD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+"
@mousedown="addNodeToGraph"
>
<img
draggable="false"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODhweCIgaGVpZ2h0PSI1NnB4IiB2aWV3Qm94PSIwIDAgODggNTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDQ5LjEgKDUxMTQ3KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Hcm91cDwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxyZWN0IGlkPSJwYXRoLTEiIHg9IjAiIHk9IjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI0OCIgcng9IjQiPjwvcmVjdD4KICAgICAgICA8ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXItMiI+CiAgICAgICAgICAgIDxmZU9mZnNldCBkeD0iMCIgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiPjwvZmVPZmZzZXQ+CiAgICAgICAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUdhdXNzaWFuQmx1cj4KICAgICAgICAgICAgPGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb21wb3NpdGU+CiAgICAgICAgICAgIDxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwICAgMCAwIDAgMCAwICAgMCAwIDAgMCAwICAwIDAgMCAwLjA0IDAiIHR5cGU9Im1hdHJpeCIgaW49InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD4KICAgICAgICA8L2ZpbHRlcj4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSLln7rnoYDmtYHnqIvlm74tMDEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02LjAwMDAwMCwgLTEwNS4wMDAwMDApIj4KICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMC4wMDAwMDAsIDEwNy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMTUtQ29weSI+CiAgICAgICAgICAgICAgICAgICAgPHVzZSBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIxIiBmaWx0ZXI9InVybCgjZmlsdGVyLTIpIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8dXNlIGZpbGwtb3BhY2l0eT0iMC45MiIgZmlsbD0iI0U2RjdGRiIgZmlsbC1ydWxlPSJldmVub2RkIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8cmVjdCBzdHJva2U9IiMxODkwRkYiIHN0cm9rZS13aWR0aD0iMSIgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSI4MiIgaGVpZ2h0PSI0NyIgcng9IjQiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDx0ZXh0IGlkPSLlrqHmibnoioLngrkiIGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0ibm9ybWFsIiBsaW5lLXNwYWNpbmc9IjEyIiBmaWxsPSIjMDAwMDAwIiBmaWxsLW9wYWNpdHk9IjAuNjUiPgogICAgICAgICAgICAgICAgICAgIDx0c3BhbiB4PSI2IiB5PSIyNiI+SmVua2luc19UYXNrPC90c3Bhbj4KICAgICAgICAgICAgICAgIDwvdGV4dD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+"
data-type="node"
data-shape="rect"
data-size="90*48"
data-color="#1890FF"
data-label="Jenkins_Task"
@mousedown="addNodeToGraph"
>
<img
draggable="false"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODhweCIgaGVpZ2h0PSI1NnB4IiB2aWV3Qm94PSIwIDAgODggNTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDQ5LjEgKDUxMTQ3KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Hcm91cDwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxyZWN0IGlkPSJwYXRoLTEiIHg9IjAiIHk9IjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI0OCIgcng9IjQiPjwvcmVjdD4KICAgICAgICA8ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXItMiI+CiAgICAgICAgICAgIDxmZU9mZnNldCBkeD0iMCIgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiPjwvZmVPZmZzZXQ+CiAgICAgICAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUdhdXNzaWFuQmx1cj4KICAgICAgICAgICAgPGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb21wb3NpdGU+CiAgICAgICAgICAgIDxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwICAgMCAwIDAgMCAwICAgMCAwIDAgMCAwICAwIDAgMCAwLjA0IDAiIHR5cGU9Im1hdHJpeCIgaW49InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD4KICAgICAgICA8L2ZpbHRlcj4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSLln7rnoYDmtYHnqIvlm74tMDEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02LjAwMDAwMCwgLTEwNS4wMDAwMDApIj4KICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMC4wMDAwMDAsIDEwNy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMTUtQ29weSI+CiAgICAgICAgICAgICAgICAgICAgPHVzZSBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIxIiBmaWx0ZXI9InVybCgjZmlsdGVyLTIpIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8dXNlIGZpbGwtb3BhY2l0eT0iMC45MiIgZmlsbD0iI0U2RkZGQiIgZmlsbC1ydWxlPSJldmVub2RkIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8cmVjdCBzdHJva2U9IiMxODkwRkYiIHN0cm9rZS13aWR0aD0iMSIgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSI3OSIgaGVpZ2h0PSI0NyIgcng9IjQiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDx0ZXh0IGlkPSLlrqHmibnoioLngrkiIGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0ibm9ybWFsIiBsaW5lLXNwYWNpbmc9IjEyIiBmaWxsPSIjMDAwMDAwIiBmaWxsLW9wYWNpdHk9IjAuNjUiPgogICAgICAgICAgICAgICAgICAgIDx0c3BhbiB4PSI2IiB5PSIyNiI+QWlyZmxvd19UYXNrPC90c3Bhbj4KICAgICAgICAgICAgICAgIDwvdGV4dD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+"
data-type="node"
data-shape="rect"
data-size="90*48"
data-color="#13C2C2"
data-label="Airflow_Task"
@mousedown="addNodeToGraph"
>
<img
draggable="false"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODhweCIgaGVpZ2h0PSI1NnB4IiB2aWV3Qm94PSIwIDAgODggNTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDQ5LjEgKDUxMTQ3KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Hcm91cDwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxyZWN0IGlkPSJwYXRoLTEiIHg9IjAiIHk9IjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI0OCIgcng9IjQiPjwvcmVjdD4KICAgICAgICA8ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXItMiI+CiAgICAgICAgICAgIDxmZU9mZnNldCBkeD0iMCIgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiPjwvZmVPZmZzZXQ+CiAgICAgICAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUdhdXNzaWFuQmx1cj4KICAgICAgICAgICAgPGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb21wb3NpdGU+CiAgICAgICAgICAgIDxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwICAgMCAwIDAgMCAwICAgMCAwIDAgMCAwICAwIDAgMCAwLjA0IDAiIHR5cGU9Im1hdHJpeCIgaW49InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD4KICAgICAgICA8L2ZpbHRlcj4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSLln7rnoYDmtYHnqIvlm74tMDEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02LjAwMDAwMCwgLTEwNS4wMDAwMDApIj4KICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMC4wMDAwMDAsIDEwNy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMTUtQ29weSI+CiAgICAgICAgICAgICAgICAgICAgPHVzZSBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIxIiBmaWx0ZXI9InVybCgjZmlsdGVyLTIpIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8dXNlIGZpbGwtb3BhY2l0eT0iMC45MiIgZmlsbD0iIzAwNzc5OSIgZmlsbC1ydWxlPSJldmVub2RkIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8cmVjdCBzdHJva2U9IiMxODkwRkYiIHN0cm9rZS13aWR0aD0iMSIgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSI3OSIgaGVpZ2h0PSI0NyIgcng9IjQiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDx0ZXh0IGlkPSLlrqHmibnoioLngrkiIGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0ibm9ybWFsIiBsaW5lLXNwYWNpbmc9IjEyIiBmaWxsPSIjMDAwMDAwIiBmaWxsLW9wYWNpdHk9IjAuNjUiPgogICAgICAgICAgICAgICAgICAgIDx0c3BhbiB4PSI5IiB5PSIyNiI+U2hlbGxfVGFzazwvdHNwYW4+CiAgICAgICAgICAgICAgICA8L3RleHQ+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=="
data-type="node"
data-shape="rect"
data-size="90*48"
data-color="#008080"
data-label="Shell_Task"
@mousedown="addNodeToGraph"
>
<img
draggable="false"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODhweCIgaGVpZ2h0PSI1NnB4IiB2aWV3Qm94PSIwIDAgODggNTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDQ5LjEgKDUxMTQ3KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Hcm91cDwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxyZWN0IGlkPSJwYXRoLTEiIHg9IjAiIHk9IjAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI0OCIgcng9IjQiPjwvcmVjdD4KICAgICAgICA8ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXItMiI+CiAgICAgICAgICAgIDxmZU9mZnNldCBkeD0iMCIgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiPjwvZmVPZmZzZXQ+CiAgICAgICAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUdhdXNzaWFuQmx1cj4KICAgICAgICAgICAgPGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb21wb3NpdGU+CiAgICAgICAgICAgIDxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwICAgMCAwIDAgMCAwICAgMCAwIDAgMCAwICAwIDAgMCAwLjA0IDAiIHR5cGU9Im1hdHJpeCIgaW49InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD4KICAgICAgICA8L2ZpbHRlcj4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSLln7rnoYDmtYHnqIvlm74tMDEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02LjAwMDAwMCwgLTEwNS4wMDAwMDApIj4KICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMC4wMDAwMDAsIDEwNy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMTUtQ29weSI+CiAgICAgICAgICAgICAgICAgICAgPHVzZSBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIxIiBmaWx0ZXI9InVybCgjZmlsdGVyLTIpIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8dXNlIGZpbGwtb3BhY2l0eT0iMC45MiIgZmlsbD0iI0ZGRkYwMCIgZmlsbC1ydWxlPSJldmVub2RkIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgICAgICAgICA8cmVjdCBzdHJva2U9IiMxODkwRkYiIHN0cm9rZS13aWR0aD0iMSIgeD0iMC41IiB5PSIwLjUiIHdpZHRoPSI3OSIgaGVpZ2h0PSI0NyIgcng9IjQiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDx0ZXh0IGlkPSLlrqHmibnoioLngrkiIGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0ibm9ybWFsIiBsaW5lLXNwYWNpbmc9IjEyIiBmaWxsPSIjMDAwMDAwIiBmaWxsLW9wYWNpdHk9IjAuNjUiPgogICAgICAgICAgICAgICAgICAgIDx0c3BhbiB4PSI3IiB5PSIyNiI+UHl0aG9uX1Rhc2s8L3RzcGFuPgogICAgICAgICAgICAgICAgPC90ZXh0PgogICAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4="
data-type="node"
data-shape="rect"
data-size="90*48"
data-color="#66FF00"
data-label="Python_Task"
@mousedown="addNodeToGraph"
>
<img
draggable="false"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODhweCIgaGVpZ2h0PSI1NnB4IiB2aWV3Qm94PSIwIDAgODggNTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDQ5LjEgKDUxMTQ3KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5Hcm91cCA0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+CiAgICAgICAgPHJlY3QgaWQ9InBhdGgtMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjQ4IiByeD0iMjQiPjwvcmVjdD4KICAgICAgICA8ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXItMiI+CiAgICAgICAgICAgIDxmZU9mZnNldCBkeD0iMCIgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiPjwvZmVPZmZzZXQ+CiAgICAgICAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSI+PC9mZUdhdXNzaWFuQmx1cj4KICAgICAgICAgICAgPGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb21wb3NpdGU+CiAgICAgICAgICAgIDxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwICAgMCAwIDAgMCAwICAgMCAwIDAgMCAwICAwIDAgMCAwLjA0IDAiIHR5cGU9Im1hdHJpeCIgaW49InNoYWRvd0JsdXJPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD4KICAgICAgICA8L2ZpbHRlcj4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSLln7rnoYDmtYHnqIvlm74tMDEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMDIuMDAwMDAwLCAtMTk1LjAwMDAwMCkiPgogICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTA2LjAwMDAwMCwgMTk3LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPGcgaWQ9IlJlY3RhbmdsZS0xNS1Db3B5LTM1Ij4KICAgICAgICAgICAgICAgICAgICA8dXNlIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjEiIGZpbHRlcj0idXJsKCNmaWx0ZXItMikiIHhsaW5rOmhyZWY9IiNwYXRoLTEiPjwvdXNlPgogICAgICAgICAgICAgICAgICAgIDx1c2UgZmlsbC1vcGFjaXR5PSIwLjkyIiBmaWxsPSIjRjlGMEZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHhsaW5rOmhyZWY9IiNwYXRoLTEiPjwvdXNlPgogICAgICAgICAgICAgICAgICAgIDxyZWN0IHN0cm9rZT0iI0IzN0ZFQiIgc3Ryb2tlLXdpZHRoPSIxIiB4PSIwLjUiIHk9IjAuNSIgd2lkdGg9Ijc5IiBoZWlnaHQ9IjQ3IiByeD0iMjMuNSI+PC9yZWN0PgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPHRleHQgaWQ9Iue7k+adn+iKgueCuSIgZm9udC1mYW1pbHk9IlBpbmdGYW5nU0MtUmVndWxhciwgUGluZ0ZhbmcgU0MiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSJub3JtYWwiIGxpbmUtc3BhY2luZz0iMTIiIGZpbGw9IiMwMDAwMDAiIGZpbGwtb3BhY2l0eT0iMC42NSI+CiAgICAgICAgICAgICAgICAgICAgPHRzcGFuIHg9IjE2IiB5PSIyOSI+57uT5p2f6IqC54K5PC90c3Bhbj4KICAgICAgICAgICAgICAgIDwvdGV4dD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+"
data-type="node"
data-shape="ellipse"
data-size="80*48"
data-color="#722ED1"
data-label="结束"
@mousedown="addNodeToGraph"
>
</div>
</el-aside>
<el-main>
<div ref="processCanvas" class="process-canvas" />
</el-main>
<!-- 右侧配置区域 -->
<el-aside style="width: 200px; height: 700px">
<!-- <div-->
<!-- style="display: inline-block;-->
<!-- width: 400px; height: 650px;-->
<!-- border: 1px solid #E4E7ED; padding-right: 4px;"-->
<!-- >-->
<!-- <el-tabs v-model="activeTab" style="height: 650px">-->
<!-- <el-tab-pane label="流程信息" name="processInfo">-->
<!-- </el-tab-pane>-->
<!-- <el-tab-pane label="配置信息" name="cellInfo">-->
<!-- <el-card shadow="never" style="height: 600px">-->
<el-drawer
title="节点配置信息"
:visible.sync="drawer"
:direction="direction"
>
<!-- <template #header>-->
<!-- <div class="card-header">-->
<!-- <span>配置信息</span>-->
<!-- </div>-->
<!-- </template>-->
<el-form
v-if="selectCell &&
selectCell.shape !== 'edge' &&
selectCell.shape !== 'circle' &&
selectCell.shape !== 'ellipse'"
ref="node"
:model="node"
:rules="rules"
label-width="88px"
class="father"
>
<el-form-item label="节点名称" prop="name" style="margin-right: 20px">
<el-input v-model="node.name" size="mini" placeholder="请输入节点名称" @input="updateNodeName" />
</el-form-item>
<el-form-item label="描述" prop="description" style="margin-top: -20px;margin-right: 20px">
<el-input v-model="node.description" type="textarea" size="mini" />
</el-form-item>
<el-form-item label="节点类型" prop="type" style="margin-top: -20px;margin-right: 20px">
<el-select
v-model.number="node.type"
filterable
placeholder="请选择处理人类型"
size="mini"
style="width: 100%"
disabled
@change="getJobName"
>
<el-option
v-for="item in nodeTypeList"
:key="item.type"
:label="item.name"
:value="item.type"
/>
</el-select>
</el-form-item>
<el-form-item label="Job组件" style="margin-top: -20px;margin-right: 20px">
<el-select
v-model.number="node.component_id"
filterable
placeholder="请选择job"
size="mini"
style="width: 100%"
@change="getJobParam"
>
<el-option
v-for="item in jobList"
:key="item.id"
:label="item.version > 0?`${item.name}(version:${item.version})`:`${item.name}`"
:value="item.id"
/>
</el-select>
</el-form-item>
<div style="margin-left: 5px">
输入参数:
</div>
<el-card class="process-box" shadow="never">
<template v-for="(item, index) of node.paramList">
<div :key="index">
<!-- <el-tooltip class="item" effect="dark" :content="item.description" placement="top-start"> -->
<span> {{ item.name }}:</span>
<!-- </el-tooltip> -->
<!-- <el-form-item :label="item.name" style="margin-top: -20px;"> -->
<el-form-item style="margin-left: -50px;">
<!-- 33.3%-->
<el-select
v-model="item.param"
filterable
placeholder="请选择"
size="mini"
style="width: 30%;"
@change="selectData"
>
<el-option
v-for="key in [{ label: '缺省值', value: 0 },
{ label: '定值', value: 1 }, { label: '节点输出值', value: 2 },
{ label: '全局参数', value: 3 } ]"
:key="key.value"
:label="key.label"
:value="key.value"
/>
</el-select>
<!-- 58.5%-->
<el-input
v-if="item.param === 0 || item.param === 3"
:value="String(item.defaultValue)"
size="mini"
style="width: 40%"
disabled
/>
<el-input
v-if="item.param === 1"
v-model="item.defaultValue"
size="mini"
style="width: 40%"
/>
<!-- 28%-->
<el-select
v-if="item.param === 2"
v-model="item.nodeName"
filterable
placeholder="请选择"
size="mini"
style="width: 32%"
@change="selectJobName"
>
<el-option
v-for="key in node.nodeViewList"
:key="key.id"
:label="key.name"
:value="key.id"
/>
</el-select>
<el-select
v-if="item.param === 2"
v-model="item.export"
filterable
placeholder="请选择"
size="mini"
style="width: 32%"
>
<el-option
v-for="key in Object.keys(node.nowNodeExportParam)"
:key="key"
:label="key"
:value="key"
/>
</el-select>
<br>
<span style="font-size: 12px; margin-top: -15px;">{{ item.description }}</span>
</el-form-item>
</div>
</template>
</el-card>
<div style="margin: 20px 0 0 5px">
输出参数:
</div>
<el-card class="export-box" shadow="never">
<span>{{ Object.keys(node.exportParam).join() }}</span>
</el-card><br>
</el-form>
<el-form
v-if="selectCell && selectCell.shape === 'edge'"
ref="edge"
:model="edge"
:rules="rules"
label-width="80px"
>
<el-form-item v-show="false" label="源节点" prop="sourceStatus" style="margin-top: -20px">
<el-input v-model.number="edge.sourceStatus" size="mini" placeholder="请输入源节点" readonly />
</el-form-item>
<el-form-item v-show="false" label="目标节点" prop="destinationStatus" style="margin-top: -20px;">
<el-input v-model.number="edge.destinationStatus" size="mini" placeholder="请输入目标节点" readonly />
</el-form-item>
</el-form>
</el-drawer>
<!-- </el-card>-->
<!-- </el-tabs>-->
</el-aside>
</el-container>
</el-container>
</div>
</template>
<script>
// import { getRegisterTestData, getRegisterJobName } from '@/api/integration_test';
import { Graph, Addon } from '@antv/x6';
import { deepCopy } from '../utils/utils';
const { Dnd } = Addon;
export default {
name: 'ProcessEdit',
props: {
labelType: {
type: Number,
default: 1,
},
data: {
type: Object,
default: () => ({
name: '',
projectId: null,
description: '',
icon: 'el-icon-user',
iconColor: 'blue',
tpl: [],
flow_chart: {
nodes: [],
edges: [],
workflow: {},
},
}),
},
},
data: () => ({
cells: [],
workflow: {},
internalChange: false,
graph: null,
dnd: null,
selectCell: null,
activeTab: 'processInfo',
projectListTmp: [],
templateList: [],
node: {
importParam: [{ param: '' }],
},
edge: {},
rules: {
name: [
{ required: true, message: ' ', trigger: 'change' },
],
icon: [
{ required: true, message: ' ', trigger: 'change' },
],
orderId: [
{ required: true, message: ' ', trigger: 'change' },
],
participant: [
{ required: true, message: ' ', trigger: 'change' },
],
type: [
{ required: true, message: ' ', trigger: 'change' },
],
distributeTypeId: [
{ required: true, message: ' ', trigger: 'change' },
],
attributeTypeId: [
{ required: true, message: ' ', trigger: 'change' },
],
sourceStatus: [
{ required: true, message: ' ', trigger: 'change' },
],
created_by: [
{ required: true, message: ' ', trigger: 'change' },
],
productLine: [
{ required: true, message: '请选择产品线', trigger: 'change' },
],
destinationStatus: [
{ required: true, message: ' ', trigger: 'change' },
],
},
nodeTypeList: [
{ type: 1, name: 'JenkinsJob' },
{ type: 2, name: 'AirflowJob' },
{ type: 3, name: 'ShellJob' },
{ type: 4, name: 'PythonJob' },
],
componentList: [
{ id: 0, name: 'mall' },
{ id: 1, name: 'store' },
{ id: 2, name: 'gate' },
{ id: 4, name: 'airport' },
],
participantOptions: [],
participantMap: {},
clickCellMap: {},
loadUserPromise: [],
jobInfo: {},
selectId: -1,
jobList: [],
nodeList: [],
jobNode: {},
jobCells: [],
nodeTmpId: -1,
exportParam: {},
disables: false,
dialogVisible: false,
drawer: false,
direction: 'rtl',
}),
computed: {
currentTemplateList() {
return this.templateList.filter((tpl) => {
if (!this.workflow.tpl || !Array.isArray(this.workflow.tpl) || this.workflow.tpl.length === 0) {
return false;
}
return this.workflow.tpl.indexOf(tpl.id) >= 0;
});
},
},
watch: {
labelType: {
handler(val) {
if (val === 2) {
this.disables = true;
}
},
immediate: true,
},
data: {
handler(val) {
if (this.internalChange) {
return;
}
this.workflow = {
name: val.name,
projectId: val.projectId,
description: val.description,
icon: val.icon,
iconColor: val.iconColor,
tpl: deepCopy(val.tpl),
productLine: val.product_line,
nodeConfigs: {},
edgeConfigs: {},
};
const cells = [];
if (val.flow_chart && val.flow_chart.nodes && val.flow_chart.nodes.length > 0) {
val.flow_chart.nodes.forEach((node) => {
const temp = deepCopy(node);
this.workflow.nodeConfigs[node.id] = temp.valueData;
delete temp.valueData;
cells.push(temp);
});
}
if (val.flow_chart && val.flow_chart.edges && val.flow_chart.edges.length > 0) {
val.flow_chart.edges.forEach((edge) => {
const temp = deepCopy(edge);
this.workflow.edgeConfigs[edge.id] = temp.valueData;
delete temp.valueData;
cells.push(temp);
});
}
if (this.graph) {
this.graph.fromJSON({ cells });
this.graph.centerContent();
} else {
this.cells = cells;
}
},
deep: true,
immediate: true,
},
},
created() {
this.workflow.name = '测试流程1';
this.workflow.productLine = 1;
},
mounted() {
this.initG6();
},
methods: {
getRegisterTestData() {
getRegisterTestData(1).then((res) => {
console.log(res, 666);
}, (err) => {
this.$message({
type: 'error',
message: err.errmsg,
});
console.error(err);
});
},
selectData(val) {
this.selectId = val;
},
getJobName() {
if (!this.workflow.productLine) {
return;
}
if (!this.node.type) {
return;
}
// getRegisterJobName(this.workflow.productLine, this.node.type).then((res) => {
// if (res) {
// this.jobList = res;
// }
// }, (err) => {
// this.$message({
// type: 'error',
// message: err.errmsg,
// });
// console.error(err);
// });
// });
},
getJobParam(val) {
this.jobList.forEach((item) => {
if (item.id === val) {
this.node.name = item.name;
this.selectCell.setAttrs({
label: {
text: this.node.name,
},
});
this.nodeList.forEach((keys) => {
if (this.nodeTmpId === keys.id) {
const ee = keys;
ee.exportParam = item.export_param;
}
});
this.workflow.nodeConfigs[this.nodeTmpId].paramList = item.import_param.map(val => Object.assign({}, deepCopy(val), { param: 0, nodeName: '', export: '' }));
if (item.export_param) {
this.workflow.nodeConfigs[this.nodeTmpId].exportParam = item.export_param;
}
}
});
this.getNodeNameList();
},
getNodeNameList() {
if (Object.keys(this.jobNode).length) {
Object.keys(this.jobNode).forEach((item) => {
if (this.nodeTmpId === item) {
this.workflow.nodeConfigs[this.nodeTmpId].nodeViewList = this.jobNode[item];
}
});
}
},
selectJobName(val) {
if (this.workflow.nodeConfigs[val].exportParam) {
this.workflow.nodeConfigs[this.nodeTmpId].nowNodeExportParam = this.workflow.nodeConfigs[val].exportParam;
}
},
handleCellClick(cell) {
console.log(cell, 5676);
if (cell.isNode() && cell.store.data.attrs.label.text !== '开始' && cell.store.data.attrs.label.text !== '结束') {
this.drawer = true;
}
this.activeTab = 'cellInfo';
this.selectCell = cell;
const { id } = cell;
const data = this.graph.toJSON();
const { cells } = data;
if (cell.isNode()) {
if (!this.workflow.nodeConfigs[id]) {
let type = 0;
if (cell.attrs.label.text === 'Jenkins_Task') {
type = 1;
} else if (cell.attrs.label.text === 'Airflow_Task') {
type = 2;
} else if (cell.attrs.label.text === 'Shell_Task') {
type = 3;
} else if (cell.attrs.label.text === 'Python_Task') {
type = 4;
} else if (cell.label === 'ellipse') {
type = 5;
}
this.workflow.nodeConfigs[id] = {
tmpId: id,
name: cell.attrs.label.text,
type,
participant: '',
description: '',
paramList: [],
component_id: '',
import_param: {},
exportParam: {},
nowNodeExportParam: {},
nodeViewList: [],
};
}
this.node = this.workflow.nodeConfigs[id];
const list1 = cells.filter(item => item.shape === 'rect' && item.attrs.body.stroke === '#a2d2ff');
const list2 = cells.filter(item => item.shape === 'rect' && item.attrs.body.stroke === '#a0e6e6');
const list3 = cells.filter(item => item.shape === 'rect' && item.attrs.body.stroke === '#99cccc');
const list4 = cells.filter(item => item.shape === 'rect' && item.attrs.body.stroke === '#c1ff99');
if (this.node.name === 'Jenkins_Task') {
this.node.name = `${this.node.name}${list1.length}`;
this.selectCell.setAttrs({
label: {
text: this.node.name,
},
});
}
if (this.node.name === 'Airflow_Task') {
this.node.name = `${this.node.name}${list2.length}`;
this.selectCell.setAttrs({
label: {
text: this.node.name,
},
});
}
if (this.node.name === 'Shell_Task') {
this.node.name = `${this.node.name}${list3.length}`;
this.selectCell.setAttrs({
label: {
text: this.node.name,
},
});
}
if (this.node.name === 'Python_Task') {
this.node.name = `${this.node.name}${list4.length}`;
this.selectCell.setAttrs({
label: {
text: this.node.name,
},
});
}
this.getJobName();
this.nodeTmpId = this.node.tmpId;
} else {
if (!this.workflow.edgeConfigs[id]) {
this.workflow.edgeConfigs[id] = {
tmpId: id,
// label: [],
// name: '',
sourceStatus: cell.source.cell,
destinationStatus: cell.target.cell,
// attributeTypeId: '',
};
}
this.edge = this.workflow.edgeConfigs[id];
}
this.nodeList = deepCopy(cells);
const edgeList = deepCopy(cells);
edgeList.forEach((item) => {
if (item.source && item.target) {
this.jobNode[item.target.cell] = [];
// let list = this.jobNode[item.target.cell];
let obj = {};
const nodeObj = this.nodeList.find(key => item.source.cell === key.id);
obj = {
name: nodeObj.attrs.label.text,
id: nodeObj.id,
};
if (obj.name !== '开始' && obj.name !== '结束') {
this.jobNode[item.target.cell].push(obj);
Object.keys(this.jobNode).forEach((key) => {
if (item.source.cell === key) {
this.jobNode[item.target.cell].push(...this.jobNode[key]);
}
});
}
}
});
this.getNodeNameList();
},
updateEdgeName(name) {
this.selectCell.setLabels(name);
},
updateNodeName(name) {
this.selectCell.setAttrs({
label: {
text: name,
},
});
},
addNodeToGraph(event) {
const target = event.currentTarget;
const type = target.getAttribute('data-type');
let node = null;
if (type === 'node') {
const shape = target.getAttribute('data-shape');
const size = target.getAttribute('data-size');
const width = Number(size.split('*')[0]);
const height = Number(size.split('*')[1]);
const label = target.getAttribute('data-label');
const color = target.getAttribute('data-color');
const rgbToHex = (r, g, b) => {
// eslint-disable-next-line no-bitwise
const hex = ((r << 16) | (g << 8) | b).toString(16);
return `#${new Array(Math.abs(hex.length - 7)).join('0')}${hex}`;
};
// hex to rgb
const hexToRgb = (hex) => {
const rgb = [];
for (let i = 1; i < 7; i += 2) {
// eslint-disable-next-line radix
rgb.push(parseInt(`0x${hex.slice(i, i + 2)}`));
}
return rgb;
};
// 计算渐变过渡色
const gradient = (startColor, endColor, step) => {
// 将 hex 转换为rgb
const sColor = hexToRgb(startColor);
const eColor = hexToRgb(endColor);
// 计算R\G\B每一步的差值
const rStep = (eColor[0] - sColor[0]) / step;
const gStep = (eColor[1] - sColor[1]) / step;
const bStep = (eColor[2] - sColor[2]) / step;
const gradientColorArr = [];
for (let i = 0; i < step; i += 1) {
// 计算每一步的hex值
gradientColorArr.push(rgbToHex(
// eslint-disable-next-line radix
parseInt(rStep * i + sColor[0]), parseInt(gStep * i + sColor[1]), parseInt(bStep * i + sColor[2]),
));
}
return gradientColorArr;
};
const colorList = gradient(color, '#ffffff', 10);
const halfColor = colorList[9];
const fillColor = colorList[6];
const ports = {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
left: {
position: 'left',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
right: {
position: 'right',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
},
items: [
{
id: 'top',
group: 'top',
},
{
id: 'bottom',
group: 'bottom',
},
{
id: 'left',
group: 'left',
},
{
id: 'right',
group: 'right',
},
],
};
const nodeInfo = {
shape,
width,
height,
attrs: {
label: {
'font-size': 12,
text: label,
fill: 'black',
},
body: {
stroke: fillColor,
fill: halfColor,
strokeWidth: 2,
},
},
ports,
};
if (shape === 'polygon') {
nodeInfo.points = '0,10 10,0 20,10 10,20';
}
node = this.graph.createNode(nodeInfo);
} else {
node = {};
}
this.dnd.start(node, event);
},
initG6() {
const that = this;
this.graph = new Graph({
container: that.$refs.processCanvas,
autoResize: true,
connecting: {
snap: true, // 自动吸附连接线
allowBlank: false, // 不允许只有一个节点的线
// allowMulti: false, // 不允许在两个节点间创建多个线
highlight: true, // 高亮可被连接的点
},
grid: true,
history: true,
snapline: {
enabled: true, // 对齐线
sharp: true,
},
scroller: {
enabled: true,
pageVisible: false,
pageBreak: false,
pannable: true,
},
// mousewheel: {
// enabled: true,
// modifiers: ['ctrl', 'meta'],
// },
});
this.dnd = new Dnd({
target: this.graph,
scaled: false,
animation: true,
});
this.graph.on('edge:connected', ({ isNew, edge }) => {
if (isNew) {
that.handleCellClick(edge);
}
});
this.graph.on('cell:mouseenter', ({ cell }) => {
if (this.clickCellMap[cell.id]) {
clearTimeout(this.clickCellMap[cell.id]);
delete this.clickCellMap[cell.id];
}
if (cell.isNode()) {
let offset = { x: 10, y: 10 };
if (cell.shape === 'polygon') {
offset = { x: 20, y: 20 };
}
cell.addTools([
{
name: 'boundary',
args: {
attrs: {
fill: '#7c68fc',
stroke: '#333',
'stroke-width': 1,
'fill-opacity': 0.2,
},
},
},
{
name: 'button-remove',
args: {
x: 0,
y: 0,
offset,
},
},
]);
} else {
cell.addTools(['button-remove']);
}
});
this.graph.on('cell:mouseleave', ({ cell }) => {
cell.removeTools();
});
this.graph.on('cell:click', ({ cell }) => {
that.handleCellClick(cell);
});
this.graph.on('node:added', ({ cell }) => {
that.handleCellClick(cell);
});
if (this.cells.length > 0) {
this.graph.fromJSON({ cells: this.cells });
this.$nextTick(() => {
this.graph.centerContent();
});
this.cells = [];
}
},
async saveFlow() {
let workflowValid;
await this.$refs.workflow.validate((valid) => {
workflowValid = valid;
});
if (!workflowValid) {
this.$message({
type: 'warning',
message: '请完成流程信息的配置',
});
return;
}
const data = this.graph.toJSON();
const { cells } = data;
const workflow = {
name: this.workflow.name,
product_line: this.workflow.productLine,
description: this.workflow.description,
created: this.workflow.created,
icon: this.workflow.icon,
iconColor: this.workflow.iconColor,
created_by: this.workflow.created_by,
tpl: deepCopy(this.workflow.tpl),
};
const flowchart = {};
flowchart.workflow = deepCopy(workflow);
flowchart.nodes = [];
flowchart.edges = [];
const that = this;
const cellValid = cells.every((cell) => {
// 线
if (cell.shape === 'edge') {
const { id } = cell;
if (!that.workflow.edgeConfigs[id]) {
const cellObj = that.graph.getCell(id);
that.handleCellClick(cellObj);
that.$nextTick(() => {
that.$refs.edge.validate();
});
return false;
}
const temp = deepCopy(cell);
temp.valueData = that.workflow.edgeConfigs[id];
flowchart.edges.push(temp);
return true;
}
const { id } = cell;
if (!that.workflow.nodeConfigs[id]) {
const cellObj = that.graph.getCell(id);
that.activeTab = 'cellInfo';
that.handleCellClick(cellObj);
that.$nextTick(() => {
that.$refs.node.validate();
});
return false;
}
const temp = deepCopy(cell);
temp.valueData = that.workflow.nodeConfigs[id];
flowchart.nodes.push(temp);
return true;
});
// workflow.flowchart = flowchart;
if (!cellValid) {
return;
}
this.internalChange = true;
this.$emit('update', flowchart);
this.$nextTick(() => {
this.internalChange = false;
});
},
},
};
</script>
<style scoped>
.process-edit-container {
margin-top: 20px;
height: calc(100% - 20px);
}
.process-canvas {
width: 100%;
height: 100%;
}
.process-component-list {
height: 100%;
background-color: #e6e6e6;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-around;
align-content: flex-start;
}
.father {
height: calc(100%);
}
.process-box {
width: calc(100% - 15px);
height: 280px;
overflow-y: auto;
margin-left: 10px;
}
.export-box {
height: 200px;
margin-left: 10px;
width: calc(100% - 15px);
}
</style>
下面是将上面的代码作为一个公共组件使用,通过props传入可进行编辑操作
<template>
<div>
<span class="staff-title">{{ activeTitle }}</span>
<process-edit
:data="processInfo"
:label-type="labelType"
@update="handleSave"
/>
</div>
</template>
<script>
//import { getRegisterTestProcess, postRegisterTestProcess } from '@/api/integration_test';
import { deepCopy } from '../../utils/util';
import ProcessEdit from './components/ProcessEdit.vue';
export default {
name: 'IntegrationTestProcess',
components: {
ProcessEdit,
},
data: () => ({
init: false,
realId: null,
processInfo: {
name: '',
projectId: null,
description: '',
icon: 'el-icon-user',
iconColor: 'blue',
tpl: [],
flow_chart: {
nodes: [],
edges: [],
workflow: {},
},
},
activeTitle: '',
processId: -1,
labelType: 1,
}),
watch: {
processInfo: {
handler(val) {
if (this.init) {
return;
}
const data = deepCopy(val);
const statusList = [];
const transformList = [];
const statusIDList = [];
const transformIDList = [];
// eslint-disable-next-line no-restricted-syntax
for (const arrayValue of data.nodes) {
if (statusIDList.indexOf(arrayValue.valueData.id) === -1) {
statusIDList.push(arrayValue.valueData.id);
} else {
delete arrayValue.valueData.id;
}
arrayValue.valueData.tmpId = arrayValue.id;
statusList.push(arrayValue.valueData);
arrayValue.label = arrayValue.valueData.name;
}
// eslint-disable-next-line no-restricted-syntax
for (const transformValue of data.edges) {
if (transformIDList.indexOf(transformValue.valueData.id) === -1) {
transformIDList.push(transformValue.valueData.id);
} else {
delete transformValue.valueData.id;
}
transformValue.valueData.tmpId = transformValue.id;
transformList.push(transformValue.valueData);
}
const workflowValue = Object.assign({}, data.workflow);
workflowValue.flowchart = data;
// const jsonData = {
// workflow: workflowValue,
// status: statusList,
// transform: transformList,
// };
},
},
},
created() {
this.processId = this.$route.params.process_id;
if (Number(this.processId) !== -1 && this.processId) {
this.init = true;
this.activeTitle = '编辑流程';
this.realId = this.processId;
this.labelType = 2;
getRegisterTestProcess(this.processId).then((res) => {
this.processInfo = res;
this.$nextTick(() => {
this.init = false;
});
}, (err) => {
this.$message({
type: 'error',
message: err.errmsg,
});
console.error(err);
});
} else {
this.activeTitle = '新建流程';
}
},
methods: {
handleSave(val) {
const data = deepCopy(val);
const statusList = [];
const transformList = [];
const statusIDList = [];
const transformIDList = [];
// eslint-disable-next-line no-restricted-syntax
for (const arrayValue of data.nodes) {
if (statusIDList.indexOf(arrayValue.valueData.id) === -1) {
statusIDList.push(arrayValue.valueData.id);
} else {
delete arrayValue.valueData.id;
}
arrayValue.valueData.tmpId = arrayValue.id;
statusList.push(arrayValue.valueData);
arrayValue.label = arrayValue.valueData.name;
}
// eslint-disable-next-line no-restricted-syntax
for (const transformValue of data.edges) {
if (transformIDList.indexOf(transformValue.valueData.id) === -1) {
transformIDList.push(transformValue.valueData.id);
} else {
delete transformValue.valueData.id;
}
transformValue.valueData.tmpId = transformValue.id;
transformList.push(transformValue.valueData);
}
const workflowValue = Object.assign({}, data.workflow);
workflowValue.flowchart = data;
const jsonData = {
workflow: workflowValue,
status: statusList,
transform: transformList,
};
const flows = [];
jsonData.transform.forEach((item) => {
let obj = {};
jsonData.status.forEach((key) => {
if (key.tmpId === item.sourceStatus) {
obj = {
from: key.name,
};
}
});
jsonData.status.forEach((key) => {
if (item.destinationStatus === key.tmpId) {
obj.to = key.name;
}
});
flows.push(obj);
});
const nodesList = jsonData.status.map((item) => {
const importParam = {};
item.paramList.forEach((key) => {
if (key.param === 0 || key.param === 1) {
importParam[key.name] = {
type: key.param,
value: key.defaultValue,
description: key.description,
};
} else if (key.param === 2) {
const nodeObj = jsonData.status.find(item => item.tmpId === key.nodeName);
importParam[key.name] = {
type: key.param,
value: nodeObj && nodeObj.name ? `${nodeObj.name}.${key.export}` : '',
description: key.description,
};
} else if (key.param === 3) {
importParam[key.name] = {
type: key.param,
value: key.defaultValue,
description: key.description,
};
}
});
const obj = {
name: item.name,
description: item.description,
type: item.type,
component_id: item.component_id,
import_param: importParam,
export_param: item.exportParam,
};
return obj;
});
const nodeList = nodesList.filter(item => item.name !== '开始' && item.name !== '结束');
const flowData = {
name: jsonData.workflow.name,
description: jsonData.workflow.description,
product_line: jsonData.workflow.product_line,
created_by: jsonData.workflow.created_by,
};
const resultData = {
...flowData,
nodes: nodeList,
flows,
flow_chart: jsonData.workflow.flowchart,
};
if (this.realId === null) {
postRegisterTestProcess(resultData).then((res) => {
if (res) {
this.$router.push({ name: 'IntegrationTestJobList' });
this.$message.success('流程新建成功!');
}
}, (err) => {
this.$message({
type: 'error',
message: err.errmsg,
});
console.error(err);
});
} else {
const jsonData = {
process_id: this.realId,
...resultData,
};
postRegisterTestProcess(jsonData).then((res) => {
if (res) {
this.$router.push({ name: 'IntegrationTestJobList' });
this.$message.success('流程修改成功!');
}
}, (err) => {
this.$message({
type: 'error',
message: err.errmsg,
});
console.error(err);
});
}
},
},
};
</script>
<style scoped>
.staff-title {
display: block;
width: 100%;
height: 50px;
padding: 13px 0 5px 30px;
font-size: 16px;
margin-top: 20px;
border-bottom: 1px solid #ccc;
margin-bottom: 30px;
background-color: skyblue;
}
</style>
// 深拷贝
export function deepCopy(obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, (c) => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach((key) => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}