原文首發(fā)于我的博客,歡迎點(diǎn)擊查看獲得更好的閱讀體驗(yàn)~
更新內(nèi)容
-
最新版本
-
請(qǐng)關(guān)注G6官方的github倉(cāng)庫(kù)https://github.com/antvis/g6
2.x版本后,可以通過(guò)
npm install直接安裝使用了
-
-
相關(guān)資源下載
最近我司項(xiàng)目中需要加入流程圖制作功能,于是乎百度各種找可視化繪制拓?fù)鋱D的輪子,大部分都是國(guó)外的,看文檔太吃力,不過(guò)好在最終讓我發(fā)現(xiàn)了AntV G6流程圖圖表庫(kù),最新版為2.0,不過(guò)編輯器在2.0版本還沒(méi)有進(jìn)行開(kāi)源,所以只能退而求其次,使用了1.2.8版本。希望2.0版本的編輯器盡早開(kāi)源,在交互方面1.2.8版本還是差了一些。
該組件并
非開(kāi)箱即食,需要根據(jù)自己的業(yè)務(wù)進(jìn)行修改,右側(cè)屬性表單部分如果有時(shí)間考慮改為插槽形式,方便以后復(fù)用~
技術(shù)棧
-
Vue
v3.0.1 -
Element-ui
v2.4.5 -
antV G6
v1.2.8 - Sass
效果圖

工作流圖.gif
引入
在index.html中進(jìn)行了全局引用
<script src="./static/plugin/g6.min.js"></script>
實(shí)例代碼
仿照2.0版本的編輯器將G6作為了一個(gè)組件使用,代碼:
<template>
<div id="flowChart">
<div class="operating">
<div class="btn-group">
<div class="btn" @click="addCircle" title="起始節(jié)點(diǎn)">
<i class="iconfont icon-circle-oeps"></i>
</div>
<div class="btn" @click="addRect" title="常規(guī)節(jié)點(diǎn)">
<i class="iconfont icon-square-oeps"></i>
</div>
<div class="btn" @click="addRhombus" title="條件節(jié)點(diǎn)">
<i class="iconfont icon-square-ling"></i>
</div>
</div>
<div class="btn-group">
<div class="btn" @click="addLine" title="直線">
<i class="iconfont icon-zhixian"></i>
</div>
<div class="btn" @click="addSmooth" title="曲線">
<i class="iconfont icon-quxian"></i>
</div>
<div class="btn" @click="addArrowLine" title="箭頭直線">
<i class="iconfont icon-jiantouzhixian"></i>
</div>
<div class="btn" @click="addArrowSmooth" title="箭頭曲線">
<i class="iconfont icon-jiantouquxian"></i>
</div>
</div>
<div class="btn-group">
<div class="btn" @click="changeMode('edit')" title="選擇模式">
<i class="iconfont icon-chose"></i>
</div>
<div class="btn" @click="changeMode('drag')" title="拖拽模式">
<i class="iconfont icon-move"></i>
</div>
</div>
<div class="btn-group">
<div class="btn" @click="del" style="margin-top: 5px;" title="刪除">
<i class="el-icon-delete"></i>
</div>
<div class="btn" @click="save" title="保存">
<i class="iconfont icon-baocun"></i>
</div>
</div>
<div class="btn-group">
<el-input size="mini" v-model="workflowName" placeholder="請(qǐng)輸入流圖名稱(chēng)..."></el-input>
</div>
</div>
<div class="info">
<div class="title">
<span>{{infoTitle}}屬性</span>
</div>
<div class="content">
<el-checkbox v-if="isBlank === true" v-model="checked">網(wǎng)格對(duì)齊</el-checkbox>
<el-form v-else label-position="left" label-width="60px">
<el-form-item v-if="isNode !== true" label="動(dòng)作">
<el-select v-model="action" size="mini" filterable placeholder="綁定動(dòng)作" value="">
<el-option
v-for="item in actionList"
:key="item.id"
:label="item.label"
:value="item.id">
</el-option>
</el-select>
</el-form-item> <!-- 線-->
<el-form-item v-if="isNode === true" label="名稱(chēng)">
<el-input size="mini" v-model="name"></el-input>
</el-form-item>
<el-form-item v-if="isNode === true" label="功能">
<el-select v-model="func" size="mini" filterable placeholder="綁定功能" value="">
<el-option
v-for="item in funcList"
:key="item.id"
:label="item.label"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="isNode === true" label="賬號(hào)">
<el-select v-model="account" size="mini" filterable multiple
collapse-tags placeholder="綁定賬號(hào)" value="">
<el-option
v-for="item in accountList"
:key="item.id"
:label="item.label"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="isNode === true" label="流圖">
<el-select v-model="workflow" size="mini" filterable clearable placeholder="綁定流圖" value="">
<el-option
v-for="item in workflowList"
:key="item.id"
:label="item.label"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="isNode === true" label="類(lèi)型">
<el-select v-model="nodeType" size="mini" filterable placeholder="請(qǐng)選擇類(lèi)型" value="">
<el-option
v-for="item in nodeTypeList"
:key="item.id"
:label="item.label"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="顏色">
<el-color-picker v-model="color"></el-color-picker>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default {
name: "index",
components: {},
mounted() {
this.initG6();
},
props: {
actionList: {
type: Array, default: []
},
funcList: {
type: Array, default: []
},
accountList: {
type: Array, default: []
},
workflowList: {
type: Array, default: []
},
nodeTypeList: {
type: Array, default: () => {
return [
{id: 0, label: '普通節(jié)點(diǎn)'},
{id: 1, label: '入口節(jié)點(diǎn)'},
{id: 2, label: '出口節(jié)點(diǎn)'}
]
}
}
},
data() {
return {
action: '',
name: '',
func: '',
account: '',
workflow: '',
nodeType: 0,
color: '',
net: '',
Util: '',
workflowName: '',
activation: '', //當(dāng)前激活的節(jié)點(diǎn)
isNode: false, //當(dāng)前是節(jié)點(diǎn)
isBlank: true, //當(dāng)前是空白區(qū)
checked: true, //網(wǎng)格對(duì)齊
infoTitle: '畫(huà)布',//屬性標(biāo)題
oldColor: '', //獲取節(jié)點(diǎn)本身顏色
type: '', //有值為編輯狀態(tài)
}
},
methods: {
initG6() {
let self = this;
self.Util = G6.Util;
let grid;
if (self.checked) {
grid = {
forceAlign: true, // 是否支持網(wǎng)格對(duì)齊
cell: 25, // 網(wǎng)格大小
};
} else {
grid = null;
}
self.net = new G6.Net({
id: 'flowChart', // 容器ID
mode: 'edit',
grid: grid,
/*width: 500, // 畫(huà)布寬*/
height: 800 // 畫(huà)布高
});
/*self.net.tooltip({
title: '信息', // @type {String} 標(biāo)題
split: ':', // @type {String} 分割符號(hào)
dx: 0, // @type {Number} 水平偏移
dy: 0 // @type {Number} 豎直偏移
});*/
/**
*點(diǎn)擊空白處
*/
self.net.on('click', (ev) => {
if (!self.Util.isNull(ev.item)) {
self.isBlank = false
} else {
self.isBlank = true;
self.infoTitle = '畫(huà)布'
}
});
/**
*點(diǎn)擊節(jié)點(diǎn)
*/
self.net.on('itemclick', function (ev) {
self.isNode = self.Util.isNode(ev.item); //是否為Node
self.activation = ev.item;
if (self.isNode) {
/* 激活節(jié)點(diǎn)后節(jié)點(diǎn)名稱(chēng)input聚焦*/
self.$nextTick(()=>{
self.$refs.inputFocus.$el.querySelector('input').focus();
});
self.infoTitle = '節(jié)點(diǎn)';
self.name = ev.item.get('model').label;
self.func = ev.item.get('model').func;
self.account = ev.item.get('model').account || [];
self.workflow = ev.item.get('model').workflow;
self.nodeType = ev.item.get('model').nodeType;
} else {
self.infoTitle = '邊';
self.action = ev.item.get('model').action;
}
self.color = self.oldColor;
});
/**
* 鼠標(biāo)移入移出事件改變顏色
*/
self.net.on('itemmouseenter', ev => {
const item = ev.item;
self.oldColor = item.get('model').color; //獲取節(jié)點(diǎn)顏色
self.net.update(item, {
color: '#108EE9',
});
self.net.refresh();
});
self.net.on('itemmouseleave', ev => {
const item = ev.item;
self.net.update(item, {
color: self.oldColor
});
self.net.refresh();
});
/**
* 提示信息
*/
/* self.net.node().tooltip(['label', 'func', 'role', 'color']);
self.net.edge().tooltip(['label', 'color']);*/
/**
* 渲染
*/
/*self.net.source(self.nodes, self.edges);*/ //加載資源數(shù)據(jù)
self.net.render();
},
addCircle() {
this.net.beginAdd('node', {
shape: 'circle',
nodeType: 0
})
},//添加起始節(jié)點(diǎn)
addRect() {
this.net.beginAdd('node', {
shape: 'rect',
nodeType: 0
})
},//添加常規(guī)節(jié)點(diǎn)
addRhombus() {
this.net.beginAdd('node', {
shape: 'rhombus',
nodeType: 0
})
}, //添加條件節(jié)點(diǎn)
addLine() {
this.net.beginAdd('edge', {
shape: 'line'
});
}, //添加直線
addSmooth() {
this.net.beginAdd('edge', {
shape: 'smooth'
})
}, //添加曲線
addArrowSmooth() {
this.net.beginAdd('edge', {
shape: 'smoothArrow'
})
}, //添加箭頭曲線
addArrowLine() {
this.net.beginAdd('edge', {
shape: 'arrow'
});
}, //添加箭頭直線
addPolyLine() {
this.net.beginAdd('edge', {
shape: 'polyLineFlow'
});
}, //添加折線
changeMode(mode) {
this.net.changeMode(mode)
}, //拖拽與編輯模式的切換
del() {
this.net.del()
},//刪除
save() {
/* 驗(yàn)證流圖名稱(chēng)*/
if (this.workflowName !== '') {
let data = this.net.save();
if (data.source.nodes.length === 0) {
this.$message({type: 'error', message: '流圖內(nèi)容不能為空'});
return false
}
/* 驗(yàn)證節(jié)點(diǎn)名稱(chēng)*/
for (let item of data.source.nodes) {
if (item.label === '' || item.label === null || item.label === undefined) {
this.$message({type: 'error', message: '節(jié)點(diǎn)名稱(chēng)不能為空'});
return false
}
}
data.source['name'] = this.workflowName;
/*let json = JSON.stringify(data, null, 2);*/
this.$emit('saveData', data.source, this.type);
} else {
this.$message({type: 'error', message: '流圖名稱(chēng)不能為空'})
}
/*console.log(saveData, json);*/
},//保存
update() {
if (this.activation.get('type') === 'node') {
this.net.update(this.activation, {
label: this.name,
func: this.func,
account: this.account,
workflow: this.workflow,
nodeType: this.nodeType,
color: this.color
});
} else {
/* 根據(jù)ID取出label*/
let label = this.actionList.map(item => {
if (item.id === this.action) {
return item.label
}
}).join('');
this.net.update(this.activation, {
label: label,
color: this.color,
action: this.action
});
}
}, //更新節(jié)點(diǎn)
clearView() {
this.type = '';
this.workflowName = '';
this.net.changeData()
}, //清空視圖
source(nodes, edges, name, type) {
this.type = type;
this.workflowName = name;
this.net.changeData(nodes, edges)
}, //更新數(shù)據(jù)
},
watch: {
/**
* 監(jiān)聽(tīng)輸入框
*/
action: function () {
this.update()
},
name: function () {
this.update()
},
func: function () {
this.update()
},
account: function () {
this.update()
},
workflow: function () {
this.update()
},
nodeType: function () {
this.update()
},
color: function () {
this.update()
},
/**
* 網(wǎng)格切換
*/
checked: function () {
let _saveData = this.net.save();
this.net.destroy(); //銷(xiāo)毀畫(huà)布
this.initG6();
this.net.read(_saveData);
this.net.render()
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
#flowChart {
border: 1px solid #ebeef5;
position: relative;
overflow: hidden;
}
.operating {
position: absolute;
z-index: 99;
background-color: #ffffff;
padding: 20px 10px;
box-shadow: 1px 1px 4px 0 #0a0a0a2e;
}
.info {
position: absolute;
right: 0;
z-index: 99;
box-shadow: 1px 1px 4px 0 #0a0a0a2e;
.title {
height: 40px;
padding-left: 10px;
border-top: 1px solid #DCE3E8;
border-bottom: 1px solid #DCE3E8;
border-left: 1px solid #DCE3E8;
background: rgb(235, 238, 242);
line-height: 40px;
span {
font-size: 14px;
}
}
.content {
background: rgba(247, 249, 251, 0.45);
width: 200px;
height: 800px;
border-left: 1px solid #E6E9ED;
padding: 10px;
}
}
.btn-group {
border-right: 1px solid #efefef;
display: inline-block;
padding-left: 10px;
padding-right: 14px;
&:last-of-type {
border-right: 0;
}
.btn {
display: inline-block;
margin: 2px;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
cursor: pointer;
border: 1px solid rgba(233, 233, 233, 0);
i {
font-size: 20px;
}
&:hover {
border: 1px solid #E9E9E9;
color: #767A85;
border-radius: 2px;
background: #FAFAFE;
}
}
.el-form-item {
margin-bottom: 0 !important;
}
}
</style>
流圖屬性
| 參數(shù) | 說(shuō)明 | 類(lèi)型 | 可選值 | 默認(rèn)值 |
|---|---|---|---|---|
| actionList | 動(dòng)作數(shù)據(jù) | Array | —— | [] |
| funcList | 功能數(shù)據(jù) | Array | —— | [] |
| accountList | 賬號(hào)數(shù)據(jù) | Array | —— | [] |
| workflowList | 流圖數(shù)據(jù) | Array | —— | [] |
| nodeTypeList | 節(jié)點(diǎn)類(lèi)型數(shù)據(jù) | Array | —— | [{id: 0, label: '普通節(jié)點(diǎn)'},{id: 1, label: '入口節(jié)點(diǎn)'},{id: 2, label: '出口節(jié)點(diǎn)'}] |
所有屬性接收的數(shù)據(jù)格式需要與
nodeTypeList的默認(rèn)值相同
流圖事件
| 事件名 | 說(shuō)明 | 參數(shù) |
|---|---|---|
| saveData | 當(dāng)用戶手動(dòng)點(diǎn)擊保存觸發(fā)事件 | source,type |
參數(shù)
type可為空,在此項(xiàng)目中主要用來(lái)區(qū)分新建與編輯
流圖方法
| 方法名 | 說(shuō)明 | 參數(shù) |
|---|---|---|
| clearView | 清空當(dāng)前視圖 | —— |
| source | 渲染數(shù)據(jù) | nodes, edges, name, type |
參數(shù)
type與事件中相同,參數(shù)name的作用是用來(lái)取流圖名