前文再续
前序:
碎碎念:
- 此文个人笔记,官网文档搬运
- x6 版本:2.9.7
- x6-vue-shape 版本:2.0.11
- x6-plugin-selection 版本:2.1.7
- vue 版本:2.6.11
- vue-template-compiler 版本:2.6.11
- Element-UI 版本:2.15.13
- 示例只展示单个节点的,多节点请下载demo自行查看源码
- demo均在易水GIT
一:步骤:
1 —— 创建vue2项目:详情请看 vue开发 —— CLI(开发环境)搭建
2 —— 引入开发组件【Element-UI、antv.x6】
npm i element-ui
npm install @antv/x6
npm install @antv/x6-vue-shape
npm install @antv/x6-plugin-selection
2.1 —— 生成的项目目录如下:
Demo
├─ node_modules
├─ public
├─ favicon.ico
└─ index.html
├─ src
├─ assets
└─ logo.png
├─ components
└─ HelloWorld.vue
├─ App.vue
└─ main.js
├─ .browserslistrc
├─ .eslintrc.js
├─ babel.config.js
├─ package.json
├─ package-lock.json
└─ README.md
2.2 —— 修改生成的项目【编辑main.js、编辑App.vue、新增Dag.vue、OneNode.vue】
// main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import '@/assets/base.scss';
Vue.use(ElementUI);
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
// App.vue
<template>
<div id="app">
<div id="nav">
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
:router="true"
>
<el-menu-item index="/">单个节点</el-menu-item>
<el-menu-item index="/two">两个节点</el-menu-item>
<el-menu-item index="/more">多节点</el-menu-item>
</el-menu>
</div>
<router-view />
</div>
</template>
<script>
export default {
data() {
return {
activeIndex: '/',
};
},
};
</script>
// Dag.vue
<template>
<div class="data-processing-dag-node">
<div
class="main-area"
@mouseenter="onMainMouseEnter"
@mouseleave="onMainMouseLeave"
>
<div class="main-info">
<!-- 节点类型icon -->
<i
class="node-logo"
:style="{
backgroundImage: 'url(' + global.NODE_TYPE_LOGO[type] + ')',
}"
/>
<el-tooltip :content="name" placement="top" :open-delay="800">
<div class="ellipsis-row node-name">{{ name }}</div>
</el-tooltip>
</div>
<!-- 节点状态信息 -->
<div class="status-action">
<el-tooltip
:content="statusMsg"
v-if="status === global.CellStatus.ERROR"
placement="top"
>
<i class="status-icon status-icon-error" />
</el-tooltip>
<i
class="status-icon status-icon-success"
v-if="status === global.CellStatus.SUCCESS"
/>
<!-- 节点操作菜单 -->
<div class="more-action-container">
<i class="more-action" />
</div>
</div>
</div>
<!-- +号菜单 -->
<div class="plus-dag" v-if="type !== global.NodeType.OUTPUT">
<el-dropdown trigger="click">
<i class="plus-action" />
<!-- <i class="el-icon-circle-plus-outline el-icon--right"></i> -->
<el-dropdown-menu
slot="dropdown"
placement="bottom"
class="processing-node-menu"
>
<el-dropdown-item
v-for="(item, index) in global.PROCESSING_TYPE_LIST"
:key="index"
>
<div
class="node-dropdown-item"
@click="clickPlusDragMenu(item.type)"
>
<i
class="node-mini-logo"
:style="{
backgroundImage: `url(${global.NODE_TYPE_LOGO[item.type]})`,
}"
/>
{{ item.name }}
</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import dagDictionary from './dagDictionary';
export default {
inject: ['getNode'],
data() {
return {
name: '',
status: '',
statusMsg: '',
type: '',
};
},
mounted() {
let node = this.getNode();
// 初始化数据绑定
this.mapper(node.data, this.$data);
// 节点数据变化监听,从而绑定数据
node.on('change:data', ({ current }) => this.mapper(current, this.$data));
},
methods: {
mapper(source, target) {
for (let key in target) {
target[key] = source[key] ?? target[key];
}
},
// 鼠标进入矩形主区域的时候显示连接桩
onMainMouseEnter() {
let node = this.getNode();
// 获取该节点下的所有连接桩
const ports = node.getPorts() || [];
ports.forEach((port) => {
node.setPortProp(port.id, 'attrs/circle', {
fill: '#fff',
stroke: '#85A5FF',
});
});
},
// 鼠标离开矩形主区域的时候隐藏连接桩
onMainMouseLeave() {
let node = this.getNode();
// 获取该节点下的所有连接桩
const ports = node.getPorts() || [];
ports.forEach((port) => {
node.setPortProp(port.id, 'attrs/circle', {
fill: 'transparent',
stroke: 'transparent',
});
});
},
// 点击添加下游+号
clickPlusDragMenu(type) {
this.createDownstream(type);
this.setState({
plusActionSelected: false,
});
},
// 创建下游的节点和边
createDownstream(type) {
let node = this.getNode();
const { graph } = node.model || {};
if (graph) {
// 获取下游节点的初始位置信息
const position = this.getDownstreamNodePosition(node, graph);
// 创建下游节点
const newNode = this.createNode(type, graph, position);
const source = node.id;
const target = newNode.id;
// 创建该节点出发到下游节点的边
this.createEdge(source, target, graph);
}
},
/**
* 创建边并添加到画布
* @param source
* @param target
* @param graph
*/
createEdge(source, target, graph) {
const edge = {
id: this.uuid(),
shape: 'data-processing-curve',
source: {
cell: source,
port: `${source}-out`,
},
target: {
cell: target,
port: `${target}-in`,
},
zIndex: -1,
data: {
source,
target,
},
};
if (graph) {
graph.addEdge(edge);
}
},
/**
* 创建节点并添加到画布
* @param type 节点类型
* @param graph
* @param position 节点位置
* @returns
*/
createNode(type, graph, position) {
if (!graph) {
return {};
}
let newNode = {};
const sameTypeNodes = graph
.getNodes()
.filter((item) => item.getData()?.type === type);
const typeName = this.global.PROCESSING_TYPE_LIST?.find(
(item) => item.type === type
)?.name;
const id = this.uuid();
const node = {
id,
shape: 'data-processing-dag-node',
x: position?.x,
y: position?.y,
ports: this.getPortsByType(type, id),
data: {
name: `${typeName}_${sameTypeNodes.length + 1}`,
type,
},
};
newNode = graph.addNode(node);
return newNode;
},
/**
* 根据起点初始下游节点的位置信息
* @param node 起始节点
* @param graph
* @returns
*/
getDownstreamNodePosition(node, graph, dx = 250, dy = 100) {
// 找出画布中以该起始节点为起点的相关边的终点id集合
const downstreamNodeIdList = [];
graph.getEdges().forEach((edge) => {
const originEdge = edge.toJSON()?.data;
if (originEdge.source === node.id) {
downstreamNodeIdList.push(originEdge.target);
}
});
// 获取起点的位置信息
const position = node.getPosition();
let minX = Infinity;
let maxY = -Infinity;
graph.getNodes().forEach((graphNode) => {
if (downstreamNodeIdList.indexOf(graphNode.id) > -1) {
const nodePosition = graphNode.getPosition();
// 找到所有节点中最左侧的节点的x坐标
if (nodePosition.x < minX) {
minX = nodePosition.x;
}
// 找到所有节点中最x下方的节点的y坐标
if (nodePosition.y > maxY) {
maxY = nodePosition.y;
}
}
});
return {
x: minX !== Infinity ? minX : position.x + dx,
y: maxY !== -Infinity ? maxY + dy : position.y,
};
},
// 根据节点的类型获取ports
getPortsByType(type, nodeId) {
let ports = [];
switch (type) {
case this.global.NodeType.INPUT:
ports = [
{
id: `${nodeId}-out`,
group: 'out',
},
];
break;
case this.global.NodeType.OUTPUT:
ports = [
{
id: `${nodeId}-in`,
group: 'in',
},
];
break;
default:
ports = [
{
id: `${nodeId}-in`,
group: 'in',
},
{
id: `${nodeId}-out`,
group: 'out',
},
];
break;
}
return ports;
},
uuid() {
var s = [];
var hexDigits = '0123456789abcdef';
for (var i = 0; i < 32; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23];
var uuid = s.join('');
return uuid;
},
},
computed: {
global: function () {
return dagDictionary;
},
},
};
</script>
// OneNode.vue
<template>
<div>
<el-alert
title="温馨提示"
type="success"
description="单个演示,不涉及链接,仅就描绘单个节点的场景"
>
</el-alert>
<div id="oneNode"></div>
</div>
</template>
<script>
import { Graph } from '@antv/x6';
import { register } from '@antv/x6-vue-shape';
import DagNode from '@/components/dag/Index';
import dagMap from '@/components/dag/dagMap.json';
export default {
data() {
return {
// 节点状态列表
nodeStatusList: [
{
id: 'node-0',
status: 'success',
},
{
id: 'node-1',
status: 'success',
},
{
id: 'node-2',
status: 'success',
},
{
id: 'node-3',
status: 'success',
},
{
id: 'node-4',
status: 'error',
statusMsg: '错误信息示例',
},
],
};
},
mounted() {
// 1. 注册节点
register({
shape: 'data-processing-dag-node',
width: 212,
height: 48,
component: DagNode,
});
// 2. 创建画布
const graph = new Graph({
container: document.getElementById('oneNode'),
width: 1000,
height: 1000,
});
// 3. 根据json数据创建节点,此处只取第一个
let map = {};
map.nodes = dagMap.nodes.slice(0, 1);
graph.fromJSON(map);
// 4. 设置节点状态
let { id, status, statusMsg } = this.nodeStatusList[0];
let node = graph.getCellById(id);
let data = node.getData();
node.setData({
...data,
status,
statusMsg,
});
},
};
</script>
2.3 —— 收工,翻归卖豉油
二:常见问题:
暂未发现