前文再續(xù)
前序:
碎碎念:
- 此文個(gè)人筆記鸽疾,官網(wǎng)文檔搬運(yùn)
- 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
- 示例只展示單個(gè)節(jié)點(diǎn)的,多節(jié)點(diǎn)請(qǐng)下載demo自行查看源碼
- demo均在易水GIT
一:步驟:
1 —— 創(chuàng)建vue2項(xiàng)目:詳情請(qǐng)看 vue開(kāi)發(fā) —— CLI(開(kāi)發(fā)環(huán)境)搭建
2 —— 引入開(kāi)發(fā)組件【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 —— 生成的項(xiàng)目目錄如下:
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 —— 修改生成的項(xiàng)目【編輯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="/">單個(gè)節(jié)點(diǎn)</el-menu-item>
<el-menu-item index="/two">兩個(gè)節(jié)點(diǎn)</el-menu-item>
<el-menu-item index="/more">多節(jié)點(diǎn)</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">
<!-- 節(jié)點(diǎn)類(lèi)型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>
<!-- 節(jié)點(diǎn)狀態(tài)信息 -->
<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"
/>
<!-- 節(jié)點(diǎn)操作菜單 -->
<div class="more-action-container">
<i class="more-action" />
</div>
</div>
</div>
<!-- +號(hào)菜單 -->
<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();
// 初始化數(shù)據(jù)綁定
this.mapper(node.data, this.$data);
// 節(jié)點(diǎn)數(shù)據(jù)變化監(jiān)聽(tīng)吼和,從而綁定數(shù)據(jù)
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];
}
},
// 鼠標(biāo)進(jìn)入矩形主區(qū)域的時(shí)候顯示連接樁
onMainMouseEnter() {
let node = this.getNode();
// 獲取該節(jié)點(diǎn)下的所有連接樁
const ports = node.getPorts() || [];
ports.forEach((port) => {
node.setPortProp(port.id, 'attrs/circle', {
fill: '#fff',
stroke: '#85A5FF',
});
});
},
// 鼠標(biāo)離開(kāi)矩形主區(qū)域的時(shí)候隱藏連接樁
onMainMouseLeave() {
let node = this.getNode();
// 獲取該節(jié)點(diǎn)下的所有連接樁
const ports = node.getPorts() || [];
ports.forEach((port) => {
node.setPortProp(port.id, 'attrs/circle', {
fill: 'transparent',
stroke: 'transparent',
});
});
},
// 點(diǎn)擊添加下游+號(hào)
clickPlusDragMenu(type) {
this.createDownstream(type);
this.setState({
plusActionSelected: false,
});
},
// 創(chuàng)建下游的節(jié)點(diǎn)和邊
createDownstream(type) {
let node = this.getNode();
const { graph } = node.model || {};
if (graph) {
// 獲取下游節(jié)點(diǎn)的初始位置信息
const position = this.getDownstreamNodePosition(node, graph);
// 創(chuàng)建下游節(jié)點(diǎn)
const newNode = this.createNode(type, graph, position);
const source = node.id;
const target = newNode.id;
// 創(chuàng)建該節(jié)點(diǎn)出發(fā)到下游節(jié)點(diǎn)的邊
this.createEdge(source, target, graph);
}
},
/**
* 創(chuàng)建邊并添加到畫(huà)布
* @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);
}
},
/**
* 創(chuàng)建節(jié)點(diǎn)并添加到畫(huà)布
* @param type 節(jié)點(diǎn)類(lèi)型
* @param graph
* @param position 節(jié)點(diǎn)位置
* @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;
},
/**
* 根據(jù)起點(diǎn)初始下游節(jié)點(diǎn)的位置信息
* @param node 起始節(jié)點(diǎn)
* @param graph
* @returns
*/
getDownstreamNodePosition(node, graph, dx = 250, dy = 100) {
// 找出畫(huà)布中以該起始節(jié)點(diǎn)為起點(diǎn)的相關(guān)邊的終點(diǎn)id集合
const downstreamNodeIdList = [];
graph.getEdges().forEach((edge) => {
const originEdge = edge.toJSON()?.data;
if (originEdge.source === node.id) {
downstreamNodeIdList.push(originEdge.target);
}
});
// 獲取起點(diǎn)的位置信息
const position = node.getPosition();
let minX = Infinity;
let maxY = -Infinity;
graph.getNodes().forEach((graphNode) => {
if (downstreamNodeIdList.indexOf(graphNode.id) > -1) {
const nodePosition = graphNode.getPosition();
// 找到所有節(jié)點(diǎn)中最左側(cè)的節(jié)點(diǎn)的x坐標(biāo)
if (nodePosition.x < minX) {
minX = nodePosition.x;
}
// 找到所有節(jié)點(diǎn)中最x下方的節(jié)點(diǎn)的y坐標(biāo)
if (nodePosition.y > maxY) {
maxY = nodePosition.y;
}
}
});
return {
x: minX !== Infinity ? minX : position.x + dx,
y: maxY !== -Infinity ? maxY + dy : position.y,
};
},
// 根據(jù)節(jié)點(diǎn)的類(lèi)型獲取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="單個(gè)演示一屋,不涉及鏈接窘疮,僅就描繪單個(gè)節(jié)點(diǎn)的場(chǎng)景"
>
</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 {
// 節(jié)點(diǎn)狀態(tài)列表
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: '錯(cuò)誤信息示例',
},
],
};
},
mounted() {
// 1. 注冊(cè)節(jié)點(diǎn)
register({
shape: 'data-processing-dag-node',
width: 212,
height: 48,
component: DagNode,
});
// 2. 創(chuàng)建畫(huà)布
const graph = new Graph({
container: document.getElementById('oneNode'),
width: 1000,
height: 1000,
});
// 3. 根據(jù)json數(shù)據(jù)創(chuàng)建節(jié)點(diǎn),此處只取第一個(gè)
let map = {};
map.nodes = dagMap.nodes.slice(0, 1);
graph.fromJSON(map);
// 4. 設(shè)置節(jié)點(diǎn)狀態(tài)
let { id, status, statusMsg } = this.nodeStatusList[0];
let node = graph.getCellById(id);
let data = node.getData();
node.setData({
...data,
status,
statusMsg,
});
},
};
</script>
單節(jié)點(diǎn)
兩節(jié)點(diǎn)
多節(jié)點(diǎn)
2.3 —— 收工冀墨,翻歸賣(mài)豉油
二:常見(jiàn)問(wèn)題:
暫未發(fā)現(xiàn)