这个是自己写着玩的,利用@antv/g6自定义绘制流程图,然后保存到localstorage中,在左侧表格展示,还可以通过表格操作来查看对应的流程图以及删除;
这里特别注意一下,@antv/g6版本是1.2.8,vue版本是2.5.x;
下面我会把实现代码全部粘贴出来,不需要需改,开袋即食,放到你的项目中直接可以展示;
代码中我也会加上注释,不懂的可以看注释;
flow.vue
<template>
<div id="flowChart">
<!-- 头部工具栏 -->
<div class="operating">
<div class="btn-group">
<div class="btn" @click="addCircle" title="开始节点">
<i class="iconfont icon-weixuanzhongyuanquan"></i>
</div>
<div class="btn" @click="addRect" title="普通节点">
<i class="iconfont icon-gl-square"></i>
</div>
<div class="btn" @click="addRhombus" title="条件节点">
<i class="iconfont icon-tubiao"></i>
</div>
</div>
<div class="btn-group">
<div class="btn" @click="addLine" title="直线">
<i class="iconfont icon-line"></i>
</div>
<div class="btn" @click="addSmooth" title="曲线">
<i class="iconfont icon-byangtiaoquxian"></i>
</div>
<div class="btn" @click="addArrowLine" title="箭头直线">
<i class="iconfont icon-gl-arrowRd"></i>
</div>
<div class="btn" @click="addArrowSmooth" title="箭头曲线">
<i class="iconfont icon-a-18"></i>
</div>
</div>
<div class="btn-group">
<div class="btn" @click="changeMode('edit')" title="选择模式">
<i class="iconfont icon-xuanze"></i>
</div>
<div class="btn" @click="changeMode('drag')" title="拖拽模式">
<i class="iconfont icon-tuozhuai"></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="请输入流图名称..."></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">网格对齐</el-checkbox>
<el-form v-else label-position="left" label-width="60px">
<el-form-item v-if="isNode !== true" label="动作">
<el-select v-model="action" size="mini" filterable placeholder="绑定动作" 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="名称">
<el-input size="mini" v-model="name"></el-input>
</el-form-item>
<!-- <el-form-item v-if="isNode === true" label="类型">
<el-select v-model="nodeType" size="mini" filterable placeholder="请选择类型" 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>
import G6 from '@antv/g6';
export default {
mounted() {
this.initG6();
},
props: {
actionList: {
type: Array, default: []
},
nodeTypeList: {
type: Array, default: () => {
return [
{id: '001', label: '普通节点'},
{id: '002', label: '开始节点'},
]
}
}
},
data() {
return {
action: '',
name: '',
nodeType: 0,
color: '',
net: '',
Util: '',
workflowName: '',
activation: '', //当前激活的节点
isNode: false, //当前是节点
isBlank: true, //当前是空白区
checked: true, //网格对齐
infoTitle: '画布',//属性标题
oldColor: '', //获取节点本身颜色
type: '', //有值为编辑状态
}
},
methods: {
//初始化
initG6() {
let self = this;
self.Util = G6.Util;
let grid;
if (self.checked) {
grid = {
forceAlign: true, // 是否支持网格对齐
cell: 25, // 网格大小
};
} else {
grid = null;
}
self.net = new G6.Net({
id: 'flowChart', // 容器ID
mode: 'edit',
grid: grid,
/*width: 500, // 画布宽*/
height: 800 // 画布高
});
/**
*点击空白处
*/
self.net.on('click', (ev) => {
if (!self.Util.isNull(ev.item)) {
self.isBlank = false
} else {
self.isBlank = true;
self.infoTitle = '画布'
}
});
/**
*点击节点
*/
self.net.on('itemclick', function (ev) {
self.isNode = self.Util.isNode(ev.item); //是否为Node
self.activation = ev.item;
if (self.isNode) {
/* 激活节点后节点名称input聚焦*/
self.$nextTick(()=>{
self.$refs.inputFocus.$el.querySelector('input').focus();
});
self.infoTitle = '节点';
self.name = ev.item.get('model').label;
self.nodeType = ev.item.get('model').nodeType;
} else {
self.infoTitle = '边';
self.action = ev.item.get('model').action;
}
self.color = self.oldColor;
});
/**
* 鼠标移入移出事件改变颜色
*/
self.net.on('itemmouseenter', ev => {
const item = ev.item;
self.oldColor = item.get('model').color; //获取节点颜色
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.source(self.nodes, self.edges);*/ //加载资源数据
self.net.render();
},
//添加起始节点
addCircle() {
this.net.beginAdd('node', {
shape: 'circle',
nodeType: 0
})
},
//添加常规节点
addRect() {
this.net.beginAdd('node', {
shape: 'rect',
nodeType: 0
})
},
//添加条件节点
addRhombus() {
this.net.beginAdd('node', {
shape: 'rhombus',
nodeType: 0
})
},
//添加直线
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() {
/* 验证流图名称*/
if (this.workflowName !== '') {
let data = this.net.save();
if (data.source.nodes.length === 0) {
this.$message({type: 'error', message: '流图内容不能为空'});
return false
}
/* 验证节点名称*/
for (let item of data.source.nodes) {
if (item.label === '' || item.label === null || item.label === undefined) {
this.$message({type: 'error', message: '节点名称不能为空'});
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: '流图名称不能为空'})
}
/*console.log(saveData, json);*/
},
//更新节点
update() {
if (this.activation.get('type') === 'node') {
this.net.update(this.activation, {
label: this.name,
nodeType: this.nodeType,
color: this.color
});
} else {
/* 根据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
});
}
},
//清空视图,重置画布
clearView() {
this.type = '';
this.workflowName = '';
this.net.changeData()
},
//渲染流程数据
source(nodes, edges, name, type) {
this.type = type;
this.workflowName = name;
this.net.changeData(nodes, edges)
},
},
watch: {
/**
* 监听输入框
*/
action: function () {
this.update()
},
name: function () {
this.update()
},
nodeType: function () {
this.update()
},
color: function () {
this.update()
},
/**
* 网格切换
*/
checked: function () {
let _saveData = this.net.save();
this.net.destroy(); //销毁画布
this.initG6();
this.net.read(_saveData);
this.net.render()
}
}
}
</script>
<style lang="less" scoped>
#flowChart {
border: 1px solid #cdcdcd;
border-radius: 5px;
position: relative;
overflow: hidden;
width: 80%;
box-sizing: border-box;
height: 100%;
}
.operating {
position: absolute;
z-index: 99;
background-color: #ffffff;
padding: 20px 10px;
box-shadow: 1px 1px 4px 0 #0a0a0a2e;
}
.info {
position: absolute;
height: 100%;
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: 220px;
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>
<template>
<div class="flow_content">
<div class="table_content">
<el-button size="small" type="primary" style="margin: 10px 0;" @click="newAdd">新建流程</el-button>
. <el-table
:data="tableData"
border
highlight-current-row=true
style="width: 100%">
<el-table-column
prop="name"
align="center"
label="名称"
>
</el-table-column>
<el-table-column
label="操作"
align="center"
width="100">
<template slot-scope="scope">
<el-button type="text" size="small"
@click.native.prevent="viewFlow(scope.row)">
查看
</el-button>
<el-button type="text" size="small"
@click.native.prevent="deleteRow(scope.$index, scope.row)">
移除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<flowChart
ref="flow"
:actionList="actionList"
@saveData="saveData"
></flowChart>
</div>
</template>
<script>
import flowChart from './components/flow.vue'
import { v4 as uuidv4 } from "uuid";
export default {
data(){
return {
actionList:[
{id:'001',label:'拒绝'},
{id:'002',label:'通过'},
{id:'003',label:'下发'}
],
tableData:[],
clickName:"",//当前点击渲染的流程图
}
},
components:{
flowChart
},
mounted(){
//初始化获取列表数据,如果有数据,画布就展示第一个流程,如果没有数据就为空集合
var tables = localStorage.getItem('flowTable')
if(tables){
this.tableData = JSON.parse(tables)
const {nodes, edges, name, type} = this.tableData[0]
this.$refs.flow.source(nodes, edges, name, type)
}else{
this.tableData = []
}
},
methods:{
saveData(source,type){
var isHave = false
var indexNum
var filterTableData = this.tableData.filter( item => {
return item.type !== type
})
filterTableData.forEach( (item,index) => {
if(item.name == source.name){
isHave = true
indexNum = index
}
})
if(type){
//type有值,编辑
if(isHave){
//编辑的名称已存在
this.$message({
message: '该名称已存在!',
type: 'warning'
});
}else{
var obj = source
obj.type = type
this.tableData.splice(indexNum, 1,obj);
this.$message({
message: '保存成功!',
type: 'success'
});
}
}else{
if(!isHave){
//type无值,新建
let uid = uuidv4()
var obj = source
obj.type = uid
this.tableData.push(source)
this.$message({
message: '保存成功!',
type: 'success'
});
}else{
this.$message({
message: '该名称已存在!',
type: 'warning'
});
}
}
var tables = JSON.stringify(this.tableData)
localStorage.setItem('flowTable',tables)
},
//点击查看流程图
viewFlow(row){
this.clickName = row.name
this.$refs.flow.clearView()
const {nodes, edges, name, type} = row
this.$refs.flow.source(nodes, edges, name, type)
},
//新建流程图,清空画布
newAdd(){
this.$refs.flow.clearView()
},
//删除单个列表并清空当前删除的画布
deleteRow(index, row) {
var leng = this.tableData.length
if(this.clickName == row.name || index == leng -1 || leng == 0){
this.$refs.flow.clearView()
}
this.tableData.splice(index, 1);
var tables = JSON.stringify(this.tableData)
localStorage.setItem('flowTable',tables)
}
}
}
</script>
<style lang="less" scoped>
.flow_content{
height: 100%;
display: flex;
justify-content: space-around;
.table_content{
width: 19%;
box-sizing: border-box;
border: 1px solid #cdcdcd;
border-radius: 5px;
padding: 0 10px;
}
}
</style>
以上就是上面效果图实现的全部代码,没有什么技术含量,就是简单的记录下来,分享给有需要的人;