Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
vue-flow-editor
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
hookehuyr
2023-11-15 17:07:44 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
479fd5da850df53c010a809bb06a29ad086d7f06
479fd5da
1 parent
93881aa1
控制结束节点不能有连接线,控制一个节点不能同时连接同样的节点
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
218 additions
and
186 deletions
doc/App.vue
doc/data.js
src/behavior/drag-edge.ts
doc/App.vue
View file @
479fd5d
...
...
@@ -222,36 +222,39 @@
</template>
<script lang="ts">
import { reactive, onMounted, watch, nextTick } from
"vue";
import { AppData } from
"./data.js";
import { staticPath } from
"./utils";
import { ElNotification } from
"element-plus";
import axios from
"./axios";
import $ from
"jquery";
import { Calendar, Search } from
"@element-plus/icons-vue";
import SelectUserView from
"./selectUserView.vue";
import { Function } from 'lodash'
;
import { extend } from '@vue/shared'
;
const G6 = (window as any).G6.default as any
;
import { reactive, onMounted, watch, nextTick } from
'vue'
import { AppData } from
'./data.js'
import { staticPath } from
'./utils'
import { ElNotification } from
'element-plus'
import axios from
'./axios'
import $ from
'jquery'
import { Calendar, Search } from
'@element-plus/icons-vue'
import SelectUserView from
'./selectUserView.vue'
import { Function } from 'lodash'
import { extend } from '@vue/shared'
const G6 = (window as any).G6.default as any
function delay(time: number) {
return new Promise(
resolve => setTimeout(resolve, time));
return new Promise(
(resolve) => setTimeout(resolve, time))
}
interface myObj {
source: string;
id: string;
label: string;
control: string;
source: string
id: string
label: string
control: string
target: string
}
interface myEvent {
item: {
get(T: string): {
source: any,
target: any,
style: any,
get(
T: string,
): {
source: any
target: any
style: any
labelCfg: any
label: any
}
...
...
@@ -262,7 +265,7 @@ export default {
components: {
Calendar,
Search,
SelectUserView
SelectUserView
,
},
setup(props, context) {
const state = reactive({
...
...
@@ -270,32 +273,32 @@ export default {
detailModel: null,
editorLoading: false, // 开始编辑器的loading状态
selectOptions: [
{ label:
"待确认", value: "0"
},
{ label:
"填写表单", value: "1"
},
{ label:
"部门负责人审批", value: "2"
},
{ label:
"总经理审批", value: "3" }
{ label:
'待确认', value: '0'
},
{ label:
'填写表单', value: '1'
},
{ label:
'部门负责人审批', value: '2'
},
{ label:
'总经理审批', value: '3' },
],
menuData: [
{
label:
"流程节点"
,
label:
'流程节点'
,
menus: [
{ label:
"开始", shape: "ellipse", id: "start-node"
},
{ label:
"结束", shape: "ellipse", id: "end-node"
},
{ label:
"审批节点", busType: "123"
},
{ label:
"判断节点", shape: "diamond" }
]
{ label:
'开始', shape: 'ellipse', id: 'start-node'
},
{ label:
'结束', shape: 'ellipse', id: 'end-node'
},
{ label:
'审批节点', busType: '123'
},
{ label:
'判断节点', shape: 'diamond' },
]
,
},
{
label:
"其他形状节点"
,
label:
'其他形状节点'
,
menus: [
{ label:
"矩形节点", shape: "rect"
},
{ label:
"圆形节点", shape: "circle"
},
{ label:
"椭圆节点", shape: "ellipse"
},
{ label:
"菱形节点", shape: "diamond"
},
{ label:
"三角形节点", shape: "triangle"
},
{ label:
"星形节点", shape: "star" }
]
}
{ label:
'矩形节点', shape: 'rect'
},
{ label:
'圆形节点', shape: 'circle'
},
{ label:
'椭圆节点', shape: 'ellipse'
},
{ label:
'菱形节点', shape: 'diamond'
},
{ label:
'三角形节点', shape: 'triangle'
},
{ label:
'星形节点', shape: 'star' },
]
,
}
,
],
// activityConfig: {
// advertisement: {
...
...
@@ -319,130 +322,142 @@ export default {
// },
controlConfig: {
start: {
id:
"start-node"
,
text:
"开始"
,
desc:
"开始"
,
color:
"#9283ed"
,
img:
"https://cdn.ipadbiz.cn/oa/advertisement-node.svg"
id:
'start-node'
,
text:
'开始'
,
desc:
'开始'
,
color:
'#9283ed'
,
img:
'https://cdn.ipadbiz.cn/oa/advertisement-node.svg',
},
flow: {
text:
"流程节点"
,
desc:
"流程节点"
,
color:
"#ed8383"
,
img:
"https://cdn.ipadbiz.cn/oa/coupon-node.svg"
text:
'流程节点'
,
desc:
'流程节点'
,
color:
'#ed8383'
,
img:
'https://cdn.ipadbiz.cn/oa/coupon-node.svg',
},
end: {
id:
"end-node"
,
text:
"结束"
,
desc:
"结束"
,
color:
"#92dba8"
,
img:
"https://cdn.ipadbiz.cn/oa/crowd-node.svg"
}
id:
'end-node'
,
text:
'结束'
,
desc:
'结束'
,
color:
'#92dba8'
,
img:
'https://cdn.ipadbiz.cn/oa/crowd-node.svg',
}
,
},
dialogUserFormVisible: false,
activeName:
"node"
,
attr_radio:
"基础属性"
,
activeName:
'node'
,
attr_radio:
'基础属性'
,
auth_all_checked: false,
auth_all_edit: false,
field_auths: [
{
name:
"字段1"
,
name:
'字段1'
,
visible: {
checked: true,
disabled: false
disabled: false
,
},
editable: {
checked: false,
disabled: true
}
disabled: true
,
}
,
},
{
name:
"字段2"
,
name:
'字段2'
,
visible: {
checked: true,
disabled: false
disabled: false
,
},
editable: {
checked: false,
disabled: false
}
disabled: false
,
}
,
},
{
name:
"字段3"
,
name:
'字段3'
,
visible: {
checked: true,
disabled: false
disabled: false
,
},
editable: {
checked: false,
disabled: false
}
}
disabled: false
,
}
,
}
,
],
})
;
})
onMounted(() => {
document.title = '可视化流程设计器'
;
// // 显示提示框的标志位
// var showConfirmation = true;
// // 监听 beforeunload 事件
// window.addEventListener('beforeunload', function (event) {
// if (showConfirmation) {
// // 取消事件的默认行为(弹出确认对话框)
// event.preventDefault();
// // Chrome 和 Firefox 需要返回一个值以显示确认对话框
// event.returnValue = '';
// // 显示自定义的提示信息
// var confirmationMessage = '确定要离开此页面吗?';
// (event || window.event).returnValue = confirmationMessage; // 兼容旧版浏览器
// return confirmationMessage;
// }
// });
// // 监听 unload 事件
// window.addEventListener('unload', function () {
// // 设置标志位为 false,避免在刷新页面时再次显示提示框
// showConfirmation = false;
// });
})
;
document.title = '可视化流程设计器'
// // 显示提示框的标志位
// var showConfirmation = true;
// // 监听 beforeunload 事件
// window.addEventListener('beforeunload', function (event) {
// if (showConfirmation) {
// // 取消事件的默认行为(弹出确认对话框)
// event.preventDefault();
// // Chrome 和 Firefox 需要返回一个值以显示确认对话框
// event.returnValue = '';
// // 显示自定义的提示信息
// var confirmationMessage = '确定要离开此页面吗?';
// (event || window.event).returnValue = confirmationMessage; // 兼容旧版浏览器
// return confirmationMessage;
// }
// });
// // 监听 unload 事件
// window.addEventListener('unload', function () {
// // 设置标志位为 false,避免在刷新页面时再次显示提示框
// showConfirmation = false;
// });
})
function handleActiveChange(name) {
console.warn(name)
;
console.warn(name)
}
const onAuthAllChange = (val: any) => { // 全选可见按钮回调
const onAuthAllChange = (val: any) => {
// 全选可见按钮回调
if (val) {
// 全部选中
state.field_auths.forEach(
ele
=> {
ele.visible.checked = true
;
})
;
state.field_auths.forEach(
(ele)
=> {
ele.visible.checked = true
})
} else {
// 全部取消选中
state.field_auths.forEach(
ele
=> {
ele.visible.checked = false
;
})
;
state.field_auths.forEach(
(ele)
=> {
ele.visible.checked = false
})
}
};
const onAuthAllEditChange = (val: any) => { // 全选可编辑按钮回调
console.warn(val);
};
}
const onAuthAllEditChange = (val: any) => {
// 全选可编辑按钮回调
console.warn(val)
}
/****** 用户选择控件弹框 ******/
const openUserForm = () => { // 打开设置用户弹框
state.dialogUserFormVisible = true;
};
const openUserForm = () => {
// 打开设置用户弹框
state.dialogUserFormVisible = true
}
const onCloseUserView = (status: boolean) => {
state.dialogUserFormVisible = status
;
state.dialogUserFormVisible = status
}
const onConfirmUserView = (data: any) => {
console.log(data)
;
console.log(data)
}
/******************* END *******************/
/********** 流程图功能函数 **********/
let editor: { openModel: () => void; closeModel: () => void; updateModel: (arg0: any) => void; editorState: { graph: { save: () => { nodes: any; edges: any; }; }; }; };
let editor: {
openModel: () => void
closeModel: () => void
updateModel: (arg0: any) => void
editorState: {
graph: {
removeItem: any
save: () => { nodes: any; edges: any }
}
}
}
/**
* 双击节点回调
...
...
@@ -450,23 +465,23 @@ export default {
* @param {Object} e - The event object
*/
function onDblClickNode(e: myEvent) {
const model = G6.Util.clone(e.item.get(
"model"));
model.style = model.style || {}
;
model.labelCfg = model.labelCfg || { style: {} }
;
const model = G6.Util.clone(e.item.get(
'model'))
model.style = model.style || {}
model.labelCfg = model.labelCfg || { style: {} }
model.data = model.data ? model.data : {}
;
state.detailModel = model
;
editor.openModel()
;
model.data = model.data ? model.data : {}
state.detailModel = model
editor.openModel()
}
function onClickNode(e: myEvent) {
const model = G6.Util.clone(e.item.get(
"model"));
model.style = model.style || {}
;
model.labelCfg = model.labelCfg || { style: {} }
;
const model = G6.Util.clone(e.item.get(
'model'))
model.style = model.style || {}
model.labelCfg = model.labelCfg || { style: {} }
model.data = model.data ? model.data : {}
;
state.detailModel = model
;
editor.openModel()
;
model.data = model.data ? model.data : {}
state.detailModel = model
editor.openModel()
}
/**
...
...
@@ -475,7 +490,7 @@ export default {
* @param {Event} e - The event object representing the double click event.
*/
function onDblClickEdge(e: myEvent) {
const { source, target, style, labelCfg, label } = e.item.get(
"model");
const { source, target, style, labelCfg, label } = e.item.get(
'model')
const model = {
label,
source,
...
...
@@ -483,13 +498,13 @@ export default {
style: style || {},
labelCfg: labelCfg || { style: {} },
type: null,
id: null
}
;
model.type = e.item.get(
"type");
model.id = e.item.get(
"id");
id: null
,
}
model.type = e.item.get(
'type')
model.id = e.item.get(
'id')
state.detailModel = model
;
editor.openModel()
;
state.detailModel = model
editor.openModel()
}
/**
...
...
@@ -497,7 +512,7 @@ export default {
*
*/
function cancel() {
editor.closeModel()
;
editor.closeModel()
}
/**
...
...
@@ -505,8 +520,8 @@ export default {
*
*/
function save() {
editor.updateModel(state.detailModel)
;
editor.closeModel()
;
editor.updateModel(state.detailModel)
editor.closeModel()
}
/**
...
...
@@ -516,14 +531,17 @@ export default {
* @param {string} type - The type of the model.
* @return {Promise} A promise that resolves when the event is handled.
*/
async function handleBeforeDelete(model: myObj, type: string): Promise<any> {
if (type === "node") {
if (model.label === "开始") {
state.editorLoading = true;
await delay(1000);
state.editorLoading = false;
ElNotification.error("不可以删除【开始】节点");
return Promise.reject("reject");
async function handleBeforeDelete(
model: myObj,
type: string,
): Promise<any> {
if (type === 'node') {
if (model.label === '开始') {
state.editorLoading = true
await delay(1000)
state.editorLoading = false
ElNotification.error('不可以删除【开始】节点')
return Promise.reject('reject')
}
}
}
...
...
@@ -535,10 +553,10 @@ export default {
* @param {string} type - The type of the model being deleted.
*/
function handleAfterDelete(model: myObj, type: string) {
if (type ===
"edge"
) {
console.log(
"delete edge");
if (type ===
'edge'
) {
console.log(
'delete edge')
} else {
console.log(
"after delete", model.label, { ...model });
console.log(
'after delete', model.label, { ...model })
}
}
...
...
@@ -550,22 +568,33 @@ export default {
* @return {Promise} A promise that resolves to a result or rejects with an error.
*/
function handleBeforeAdd(model: myObj, type: string): Promise<any> {
if (type === "edge") {
if (model.source === "end-node") {
ElNotification.error("结束节点不能输出连线其他节点");
return Promise.reject("reject");
const source = model.source;
const target = model.target;
let { nodes, edges } = editor.editorState.graph.save();
if (type === 'edge') {
if (model.source === 'end-node') {
ElNotification.error('结束节点不能输出连线其他节点')
return Promise.reject('reject')
}
for (let index = 0; index < edges.length; index++) {
const element = edges[index]
if (element.source === source && element.target === target) {
ElNotification.error('不可以重复添加连线')
return Promise.reject('reject')
}
}
}
if (type ===
"node"
) {
if (model.control ===
"start" || model.control === "end"
) {
const data = editor.editorState.graph.save()
;
if (type ===
'node'
) {
if (model.control ===
'start' || model.control === 'end'
) {
const data = editor.editorState.graph.save()
for (let i = 0; i < data.nodes.length; i++) {
const node = data.nodes[i]
;
const node = data.nodes[i]
if (node.control === model.control) {
ElNotification.error(
`只能有一个${model.control ===
"start" ? "开始" : "结束"}节点`
)
;
return Promise.reject(
"reject");
`只能有一个${model.control ===
'start' ? '开始' : '结束'}节点`,
)
return Promise.reject(
'reject')
}
}
}
...
...
@@ -579,15 +608,18 @@ export default {
* @param {type} type - The type of the event.
*/
function handleAfterAdd(model: myObj, type: string) {
console.log('handleAfterAdd', model);
//
console.log('handleAfterAdd', model);
// TODO: 因为resize会重新绘制,所以可能需要保存操作
if (type === 'node') {
console.log(`新增节点`)
;
console.log(`新增节点`)
}
if (type ===
"edge"
) {
console.log(`新增连接线`)
;
if (type ===
'edge'
) {
console.log(`新增连接线`)
}
state.data.nodes = editor.editorState.graph.save().nodes
state.data.edges = editor.editorState.graph.save().edges
}
/**
...
...
@@ -597,8 +629,8 @@ export default {
*/
function onDragEndNode(e: myEvent) {
// TODO:可能需要接口保存相应位置,避免拖动窗口时数据丢失
const model = e.item.get(
"model");
console.log(
"onDragEndNode", model);
const model = e.item.get(
'model')
console.log(
'onDragEndNode', model)
}
/**
...
...
@@ -607,18 +639,18 @@ export default {
* @return {void} No return value.
*/
function logData(): void {
let { nodes, edges } = editor.editorState.graph.save()
;
let { nodes, edges } = editor.editorState.graph.save()
// console.log("nodes", nodes);
// console.log("edges", edges);
// 使用时需要把自定义节点的类型带过去 activity/control
nodes.forEach((node: { [x: string]: string; shape: string
;
}) => {
if (node.shape ===
"activity"
) {
node['shape'] = 'activity_' + node['activity']
;
nodes.forEach((node: { [x: string]: string; shape: string }) => {
if (node.shape ===
'activity'
) {
node['shape'] = 'activity_' + node['activity']
}
if (node.shape ===
"control"
) {
node['shape'] = 'control_' + node['control']
;
if (node.shape ===
'control'
) {
node['shape'] = 'control_' + node['control']
}
})
;
})
nodes = nodes.map(
({ data, id, label, shape, x, y, text, desc, img }) => ({
...
...
@@ -630,18 +662,18 @@ export default {
y,
text,
desc,
img
})
)
;
img
,
})
,
)
edges = edges.map(({ source, sourceAnchor, target, targetAnchor }) => ({
source,
sourceAnchor,
target,
targetAnchor
}))
;
targetAnchor
,
}))
// console.log(JSON.stringify({ nodes, edges }, null, 2));
console.log(nodes)
;
console.log(edges)
;
console.log(nodes)
console.log(edges)
}
/**
...
...
@@ -652,8 +684,8 @@ export default {
*/
function toolbarButtonHandler(buttons: any[]): Array<any> {
// TAG:测试隐藏缩略图
let map = buttons.filter(
item => item.key !== "miniMapSwitcher");
return map
;
let map = buttons.filter(
(item) => item.key !== 'miniMapSwitcher')
return map
}
return {
...
...
@@ -686,10 +718,10 @@ export default {
toolbarButtonHandler,
onRef: (e: any) => (editor = e),
staticPath
}
;
}
}
;
staticPath
,
}
}
,
}
</script>
<style lang="scss">
...
...
doc/data.js
View file @
479fd5d
/*
* @Date: 2023-10-27 09:29:48
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-11-15 1
5:56:03
* @LastEditTime: 2023-11-15 1
6:16:04
* @FilePath: /vue-flow-editor/doc/data.js
* @Description: 初始化结构,数据都是固定的
*/
...
...
src/behavior/drag-edge.ts
View file @
479fd5d
...
...
@@ -130,7 +130,7 @@ export function dragEdge(G6, option: OptionType) {
async
_addEdge
()
{
if
(
this
.
origin
.
targetNode
)
{
const
addModel
=
{
cla
zz
:
'flow'
,
cla
ss
:
'flow'
,
source
:
this
.
origin
.
sourceNode
.
get
(
'id'
),
target
:
this
.
origin
.
targetNode
.
get
(
'id'
),
sourceAnchor
:
this
.
origin
.
sourceAnchor
,
...
...
@@ -159,4 +159,4 @@ export function dragEdge(G6, option: OptionType) {
}
}
});
}
\ No newline at end of file
}
...
...
Please
register
or
login
to post a comment