vue-workflow-editor
Version:
在线工作流流程图编辑器,vue2+ts,基于G6
280 lines (248 loc) • 11.9 kB
HTML
<html>
<head>
<meta charset="utf-8">
<title>vue-flow-editor demo</title>
<link type="text/css" rel="stylesheet" href="http://martsforever-pot.gitee.io/vue-flow-editor/dist/vue-flow-editor.css">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
html, body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<div id="app">
</div>
<script src="./lib/g6.umd.min.js"></script>
<script src="http://martsforever-pot.gitee.io/vue-flow-editor/lib/vue.min.js"></script>
<script src="https://unpkg.com/@vue/composition-api/dist/vue-composition-api.umd.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="http://martsforever-pot.gitee.io/vue-flow-editor/dist/vue-flow-editor.js"></script>
<script>
console.log(window)
Vue.use(window.vueCompositionApi.default)
Vue.use(window.VueFlowEditor)
const Ele = window.ELEMENT
const G6 = window.G6.default
const reactive = window.vueCompositionApi.reactive
function delay(time) {
return new Promise((resolve) => setTimeout(resolve, time))
}
const AppData = {
"nodes": [{"data": {}, "id": "start-node", "label": "开始", "shape": "ellipse", "size": [120, 40], "x": 380, "y": 100}, {"data": {}, "id": "1588848310120", "label": "主管审批", "shape": "rect", "size": [120, 40], "x": 380, "y": 180}, {"data": {}, "id": "1588848322179", "label": "经理审批", "shape": "rect", "size": [120, 40], "x": 380, "y": 260}, {
"data": {},
"id": "1588848338211",
"label": "金额>2万",
"shape": "diamond",
"size": [120, 40],
"x": 380,
"y": 340
}, {"data": {}, "id": "1588848351476", "label": "财务打款", "shape": "rect", "size": [120, 40], "x": 380, "y": 460}, {"data": {}, "id": "end-node", "label": "结束", "shape": "ellipse", "size": [120, 40], "x": 380, "y": 540}, {"data": {}, "id": "1588848397511", "label": "VIP审批", "shape": "rect", "size": [120, 40], "x": 620, "y": 340}, {
"data": {},
"id": "1588848436694",
"label": "金额>10万",
"shape": "diamond",
"size": [120, 40],
"x": 780,
"y": 340
}, {"data": {}, "id": "1588848449431", "label": "CEO审批", "shape": "rect", "size": [120, 40], "x": 980, "y": 460}],
"edges": [{"shape": "flow-polyline-round", "source": "start-node", "sourceAnchor": 2, "target": "1588848310120", "targetAnchor": 0}, {"shape": "flow-polyline-round", "source": "1588848310120", "sourceAnchor": 2, "target": "1588848322179", "targetAnchor": 0}, {
"shape": "flow-polyline-round",
"source": "1588848322179",
"sourceAnchor": 2,
"target": "1588848338211",
"targetAnchor": 0
}, {"shape": "flow-polyline-round", "source": "1588848338211", "sourceAnchor": 2, "target": "1588848351476", "targetAnchor": 0, "label": "否"}, {"shape": "flow-polyline-round", "source": "1588848351476", "sourceAnchor": 2, "target": "end-node", "targetAnchor": 0}, {
"shape": "flow-polyline-round",
"source": "1588848338211",
"sourceAnchor": 3,
"target": "1588848397511",
"targetAnchor": 1,
"label": "是"
}, {"shape": "flow-polyline-round", "source": "1588848436694", "sourceAnchor": 3, "target": "1588848449431", "targetAnchor": 0, "label": "是"}, {"shape": "flow-polyline-round", "source": "1588848449431", "sourceAnchor": 1, "target": "1588848351476", "targetAnchor": 3}, {
"shape": "flow-polyline-round",
"source": "1588848436694",
"sourceAnchor": 2,
"target": "1588848351476",
"targetAnchor": 3,
"label": "否"
}, {"shape": "flow-polyline-round", "source": "1588848397511", "sourceAnchor": 3, "target": "1588848436694", "targetAnchor": 1}]
}
new Vue({
el: '#app',
template: `
<div class="app" style="height: 100vh">
<vue-flow-editor
ref="editor"
:data="state.data"
:grid="showGrid"
:miniMap="showMiniMap"
:onRef="onRef"
:multipleSelect="true"
:loading="state.editorLoading"
:disabledUndo="false"
:disabledDragEdge="true"
:beforeDelete="handleBeforeDelete"
:afterDelete="handleAfterDelete"
:beforeAdd="handleBeforeAdd"
:afterAdd="handleAfterAdd"
@dblclick-node="onDblclickNode"
@dblclick-edge="onDblClickEdge"
>
<div slot="menu">
<vue-flow-edit-menu-group v-for="(group,groupIndex) in state.menuData" :label="group.label" :key="groupIndex">
<vue-flow-edit-menu v-for="(menu,menuIndex) in group.menus" :key="menuIndex" :model="menu"/>
</vue-flow-edit-menu-group>
</div>
<div slot="model" style="padding: 16px">
<el-form v-if="!!state.detailModel" ref="form" :model="state.detailModel" label-width="100px">
<el-form-item label="节点名称" prop="label">
<el-input v-model="state.detailModel.label"/>
</el-form-item>
<template v-if="state.detailModel.type !== 'edge'">
<el-form-item label="节点背景色" prop="style.fill">
<el-color-picker v-model="state.detailModel.style.fill"/>
</el-form-item>
<el-form-item label="节点边框色" prop="style.stroke">
<el-color-picker v-model="state.detailModel.style.stroke"/>
</el-form-item>
<el-form-item label="节点文字色" prop="labelCfg.style.stroke">
<el-color-picker v-model="state.detailModel.labelCfg.style.fill"/>
</el-form-item>
</template>
</el-form>
</div>
<template slot="toolbar">
<el-tooltip content="测试工具栏插槽">
<div class="vue-flow-editor-toolbar-item">
<i class="el-icon-search"/>
</div>
</el-tooltip>
</template>
<template slot="foot">
<el-button type="primary" @click="save">保存</el-button>
<el-button @click="cancel">取消</el-button>
</template>
</vue-flow-editor>
</div>
`,
setup() {
const state = reactive({
data: AppData,
detailModel: null,
editorLoading: false,
selectOptions: [
{label: '待确认', value: '0'},
{label: '填写表单', value: '1'},
{label: '部门负责人审批', value: '2'},
{label: '总经理审批', value: '3'},
],
menuData: [
{
label: '流程节点',
menus: [
{label: '开始', shape: 'ellipse', id: 'start-node'},
{label: '结束', shape: 'ellipse', id: 'end-node'},
{label: '审批节点', busType: '123'},
{label: '判断节点'},
],
},
{
label: '其他形状节点',
menus: [
{label: '矩形节点', shape: 'rect'},
{label: '圆形节点', shape: 'circle'},
{label: '椭圆节点', shape: 'ellipse'},
{label: '菱形节点', shape: 'diamond'},
{label: '三角形节点', shape: 'triangle'},
{label: '星形节点', shape: 'star'},
]
}
]
})
let editor;
function onDblclickNode(e) {
const model = G6.Util.clone(e.item.get('model'))
model.style = model.style || {}
model.labelCfg = model.labelCfg || {style: {}}
state.detailModel = model
editor.openModel()
}
function onDblClickEdge(e) {
const {source, target, style, labelCfg, label} = e.item.get('model')
const model = {label, source, target, style: style || {}, labelCfg: labelCfg || {style: {}}, type: null, id: null}
model.type = e.item.get('type')
model.id = e.item.get('id')
state.detailModel = model
editor.openModel()
}
function cancel() {
editor.closeModel()
}
function save() {
editor.updateModel(state.detailModel)
editor.closeModel()
}
async function handleBeforeDelete(model, type) {
if (type === 'node') {
if (model.label === '开始') {
state.editorLoading = true
await delay(1000)
state.editorLoading = false
Ele.Notification.error('不可以删除【开始】节点')
return Promise.reject('reject')
}
}
}
function handleAfterDelete(model, type) {
if (type === 'edge') {
console.log('delete edge')
} else {
console.log('after delete', model.label, {...model})
}
}
function handleBeforeAdd(model, type) {
if (type === 'edge') {
if (model.source === 'end-node') {
Ele.Notification.error('结束节点不能输出连线其他节点')
return Promise.reject('reject')
}
}
if (type === 'node') {
if (model.id === 'start-node' || model.id === 'end-node') {
const data = editor.editorState.graph.save()
for (let i = 0; i < data.nodes.length; i++) {
const node = data.nodes[i];
if (node.id === model.id) {
Ele.Notification.error(`只能有一个${model.id === 'start-node' ? '开始' : '结束'}节点`)
return Promise.reject('reject')
}
}
}
}
}
function handleAfterAdd(model, type) {
if (type === 'edge') {
console.log(`新增连接线`)
}
}
return {
state,
showGrid: true,
showMiniMap: false,
onDblclickNode,
onDblClickEdge,
cancel,
save,
handleBeforeDelete,
handleAfterDelete,
handleBeforeAdd,
handleAfterAdd,
onRef: e => editor = e,
}
},
}).$mount()
</script>
</body>
</html>