ingenious-flow-designer
Version:
[演示地址](http://antd-vben5-pro.madong.tech/)
514 lines (491 loc) • 15 kB
text/typescript
import LogicFlow from '@logicflow/core';
import { DndPanel, Control, Menu, MiniMap, Snapshot } from '@logicflow/extension';
import StartSvg from '../assets/images/panel-start.svg';
import EndSvg from '../assets/images/panel-end.svg';
import TaskSvg from '../assets/images/panel-task.svg';
import DecisionSvg from '../assets/images/panel-decision.svg';
import CustomSvg from '../assets/images/panel-custom.svg';
import ForkSvg from '../assets/images/panel-fork.svg';
import JoinSvg from '../assets/images/panel-join.svg';
import SubProcessSvg from '../assets/images/panel-subprocess.svg';
LogicFlow.use(DndPanel)
LogicFlow.use(Control)
LogicFlow.use(Menu)
LogicFlow.use(MiniMap, {
width: 200,
height: 150,
showEdge: true,
isShowHeader: false,
isShowCloseIcon: true,
})
LogicFlow.use(Snapshot)
const nodes = import.meta.glob('../nodes/*.ts',{
eager: true
})
const edges = import.meta.glob('../edges/*.ts',{
eager: true
})
class Flow {
dndPanel: any[] = []
miniMapVisible: boolean = false
static pluginName = 'flow'
static defaultEdgeType = 'transition'
defaultDndPanel: any[] = [
{
type: 'start',
text: '开始',
icon: StartSvg,
className: 'dnd-start',
sort: 10
},
{
type: 'task',
text: '用户任务',
icon: TaskSvg,
className: 'dnd-task',
sort: 20
},
{
type: 'decision',
text: '条件判断',
icon: DecisionSvg,
className: 'dnd-decision',
sort: 30
},
{
type: 'fork',
text: '分支',
icon: ForkSvg,
className: 'dnd-fork',
sort: 40
},
{
type: 'join',
text: '合并',
icon: JoinSvg,
className: 'dnd-join',
sort: 50
},
{
type: 'subProcess',
text: '子流程',
icon: SubProcessSvg,
className: 'dnd-subprocess',
sort: 60
},
{
type: 'custom',
text: '自定义',
icon: CustomSvg,
className: 'dnd-custom',
sort: 70
},
{
type: 'end',
text: '结束',
icon: EndSvg,
className: 'dnd-end',
sort: 80
}
]
props: any;
defaultControl: any[] = [
{
key: 'zoom-out',
iconClass: 'lf-control-zoomOut',
title: '缩小流程图',
text: '缩小',
sort: 10,
onClick: () => {
this.lf.zoom(false);
},
},
{
key: 'zoom-in',
iconClass: 'lf-control-zoomIn',
title: '放大流程图',
sort: 20,
text: '放大',
onClick: () => {
this.lf.zoom(true);
},
},
{
key: 'reset',
iconClass: 'lf-control-fit',
title: '恢复流程原有尺寸',
text: '适应',
sort: 30,
onClick: () => {
this.lf.resetZoom();
},
},
{
key: 'undo',
iconClass: 'lf-control-undo',
title: '回到上一步',
text: '上一步',
sort: 40,
onClick: () => {
this.lf.undo();
},
},
{
key: 'redo',
iconClass: 'lf-control-redo',
title: '移到下一步',
sort: 50,
text: '下一步',
onClick: () => {
this.lf.redo();
},
},
{
key: 'clear',
iconClass: 'lf-control-clear',
title: '清空画布',
sort: 60,
text: '清空',
onClick: () => {
this.lf.clearData();
}
},
{
key: 'see',
iconClass: 'lf-control-see',
title: '查看流程数据',
sort: 70,
text: '查看',
onClick: () => {
this.props.modalRef?.value?.show({
type: 'see',
graphData: this.lf.getGraphData(),
lf: this.lf
})
}
},
{
key: 'import',
iconClass: 'lf-control-import',
title: '导入流程数据',
sort: 80,
text: '导入',
onClick: () => {
this.props.modalRef?.value?.show({
type: 'import',
lf: this.lf
})
}
},
{
key: 'highlight',
iconClass: 'lf-control-highlight',
title: '设置高亮数据',
sort: 85,
text: '高亮',
onClick: () => {
this.props.modalRef?.value?.show({
type: 'highlight',
lf: this.lf
})
}
},
{
key: 'save',
iconClass: 'lf-control-save',
title: '保存流程数据',
sort: 90,
text: '保存',
onClick: () => {
const { eventCenter } = this.lf.graphModel;
eventCenter.emit("custom:save", this.lf.getGraphData());
}
},
{
key: 'minimap',
iconClass: 'lf-control-minimap',
title: '切换小地图',
sort: 100,
text: '小地图',
onClick: () => {
const miniMap = this.lf.extension.miniMap
if (miniMap) {
if (this.miniMapVisible) {
miniMap.hide()
this.miniMapVisible = false
} else {
miniMap.show()
miniMap.updatePosition({ right: 15, top: 70 })
this.miniMapVisible = true
}
}
}
}
]
lf: any;
constructor(data: any){
this.lf = data.lf;
const props = data.props
this.props = props
// 注册节点和边,并添加类型前缀
Object.keys(nodes).forEach((key) => {
const node = (nodes[key] as any).default
if (node && node.type) {
if (props.typePrefix && !node.type.startsWith(props.typePrefix)) {
node.type = `${props.typePrefix}${node.type}`
}
this.lf.register(node)
console.log(`Registered node: ${node.type}`)
} else {
console.warn(`Node ${key} is invalid:`, node)
}
})
Object.keys(edges).forEach((key) => {
const edge = (edges[key] as any).default
if (edge && edge.type) {
if (props.typePrefix && !edge.type.startsWith(props.typePrefix)) {
edge.type = `${props.typePrefix}${edge.type}`
}
this.lf.register(edge)
} else {
console.warn(`Edge ${key} is invalid:`, edge)
}
})
// 设置默认边类型
let defaultEdgeType = props.defaultEdgeType || Flow.defaultEdgeType
if (props.typePrefix && !defaultEdgeType.startsWith(props.typePrefix)) {
defaultEdgeType = `${props.typePrefix}${defaultEdgeType}`
}
this.lf.setDefaultEdgeType(defaultEdgeType)
// 初始化拖拽面板
if (props.initDndPanel !== false && props.viewer !== true) {
// 拖拽面板追加类型前辍
const defaultDndPanelWithPrefix = this.defaultDndPanel.map((item: any) => {
if (props.typePrefix && item.type && !item.type.startsWith(props.typePrefix)) {
return {
...item,
type: `${props.typePrefix}${item.type}`
}
}
return item
})
const newDndPanel: any[] = [...defaultDndPanelWithPrefix]
if(props.dndPanel && props.dndPanel.length > 0) {
props.dndPanel.forEach((item: any) => {
const index = newDndPanel.findIndex((i) => i.type === item.type)
if(index > -1) {
if(item.hide === true) {
newDndPanel.splice(index, 1)
} else {
newDndPanel[index] = {
...newDndPanel[index],
...item,
}
}
} else {
newDndPanel.push(item)
}
})
}
newDndPanel.sort((a: any, b: any) => {
return (a.sort === undefined ? 99: a.sort) - (b.sort === undefined ? 99: b.sort)
})
this.initDndPanel(newDndPanel)
}
// 初始化控制面板
if (props.initControl !== false) {
const newControl: any[] = [...this.defaultControl]
if(props.control && props.control.length > 0) {
props.control.forEach((item: any) => {
const index = newControl.findIndex((i) => i.key === item.key)
if(index > -1) {
if(item.hide === true) {
newControl.splice(index, 1)
} else {
newControl[index] = {
...newControl[index],
...item,
}
}
} else {
newControl.push(item)
}
})
}
newControl.sort((a: any, b: any) => {
return (a.sort === undefined ? 99: a.sort) - (b.sort === undefined ? 99: b.sort)
})
this.initControl(newControl.filter(item=>{
if(props.viewer === true) {
return !['undo','redo','clear','save','see','import','highlight'].includes(item.key)
}
return true
}))
} else {
this.lf.extension.control.controlItems = []
}
if(props.viewer) {
this.lf.extension.menu.setMenuConfig({})
} else {
this.lf.on('blank:contextmenu', (e: any) => {
if (e.preventDefault) {
e.preventDefault();
}
// 优先调用用户传入的blankContextmenu方法
if (props.blankContextmenu && typeof props.blankContextmenu === 'function') {
const processData: any = {};
const flowFields = ['name', 'display_name', 'key', 'version', 'expire_time', 'instance_url', 'instance_no_class', 'pre_interceptors', 'post_interceptors', 'formType', 'customFormConfig'];
flowFields.forEach(field => {
if (this.lf.graphModel[field] !== undefined) {
processData[field] = this.lf.graphModel[field];
}
});
// 调用用户传入的blankContextmenu方法
props.blankContextmenu({
data: {
type: 'process',
lf: this.lf,
properties: processData
}
});
} else {
// 如果用户没有传入blankContextmenu方法,检查dndPanel中是否有type为'process'的配置
const processData: any = {};
const flowFields = ['name', 'display_name', 'key', 'version', 'expire_time', 'instance_url', 'instance_no_class', 'pre_interceptors', 'post_interceptors', 'formType', 'customFormConfig'];
flowFields.forEach(field => {
if (this.lf.graphModel[field] !== undefined) {
processData[field] = this.lf.graphModel[field];
}
});
// 检查dndPanel中是否有type为'process'的配置
const dndPanel = props.dndPanel;
const processConfig = dndPanel?.find((item: any) => item.type === 'process');
if (processConfig) {
// 调用process配置的nodeClick方法
if (processConfig.nodeClick) {
processConfig.nodeClick({
data: {
type: 'process',
lf: this.lf,
properties: processData
}
});
}
} else if (this.props.drawerRef?.value?.show) {
// 使用内置的流程设置面板
this.props.drawerRef.value.show({
type: 'process',
lf: this.lf,
properties: processData
});
}
}
});
this.lf.extension.menu.setMenuConfig({
nodeMenu: [
{
text: '删除',
callback: (node: any) => {
this.lf.deleteNode(node.id)
}
}
],
graphMenu: []
})
}
const lf = this.lf;
lf.adapterIn = (userData: any) => {
const flowFields = ['name', 'display_name', 'key', 'version', 'expire_time', 'instance_url', 'instance_no_class', 'pre_interceptors', 'post_interceptors', 'formType', 'customFormConfig'];
flowFields.forEach(field => {
if (userData[field] !== undefined) {
lf.graphModel[field] = userData[field];
}
});
return userData;
};
lf.adapterOut = (userData: any) => {
const processData: any = {};
const flowFields = ['name', 'display_name', 'key', 'version', 'expire_time', 'instance_url', 'instance_no_class', 'pre_interceptors', 'post_interceptors', 'formType', 'customFormConfig'];
flowFields.forEach(field => {
if (lf.graphModel[field] !== undefined) {
processData[field] = lf.graphModel[field];
}
});
return {
...userData,
...processData
};
};
lf.graphModel.props = props;
const { eventCenter } = lf.graphModel;
eventCenter.on('node:click', (event: any) => {
if(props.viewer === true) {
return
}
const shapeList = this.dndPanel
const index = shapeList.findIndex((item: any)=>item.type === event.data.type)
let nodeClick = props.nodeClick
if(index>=-1) {
nodeClick = shapeList[index]?.nodeClick || props.nodeClick
}
if(nodeClick && typeof nodeClick === 'function') {
nodeClick(event)
} else {
const nodeData = {
id: event.data.id,
type: event.data.type,
label: event.data.text?.value || event.data.text || '',
properties: {
...event.data.properties
}
};
const nodeConfig = props.nodeConfig?.find((config: any) => config.type === event.data.type);
let customPanel = props.customPanel;
let useExternalPanel = false;
if (nodeConfig) {
if (nodeConfig.customPanel) {
customPanel = nodeConfig.customPanel;
}
}
const patternItem = shapeList[index];
if (patternItem && patternItem.form) {
customPanel = patternItem.form;
useExternalPanel = true;
}
if (this.props.drawerRef.value?.show) {
this.props.drawerRef.value.show({
...nodeData,
patternItem: patternItem,
lf
}, customPanel, useExternalPanel ? 'external' : 'build_in');
}
}
})
eventCenter.on('edge:click', (event: any) => {
if(props.viewer === true) {
return
}
let edgeClick = props.edgeClick
if(edgeClick && typeof edgeClick === 'function') {
edgeClick(event)
} else {
this.props.drawerRef.value?.show({
...event,
patternItem: {},
lf
})
}
})
}
initDndPanel(data: any){
this.lf.extension.dndPanel.setPatternItems(data)
this.dndPanel = data || []
}
initControl(data: any){
const control = this.lf?.extension?.control
if (control && control.controlItems) {
control.controlItems = data || []
}
}
}
export default Flow