feat(flow): 实现事件节点参数配置与WebSocket通信

master
钟良源 4 months ago
parent 56919c70c1
commit 9dec93bf0d

@ -0,0 +1,20 @@
import axios from 'axios';
import { apiResData } from '@/api/interface/index';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 获取应用事件
export function getAppEventData(id: any) {
return axios.get<apiResData>(`${urlPrefix}/appEvent/${id}`);
}
// 获取工程下的所有应用事件
export function getAppEventList(id: any) {
return axios.get<apiResData>(`${urlPrefix}/appEvent/${id}/list`);
}
// 更新事件
export function updateAppEvent(id: any, data: any) {
return axios.post<apiResData>(`${urlPrefix}/appEvent/${id}/update`, data);
}

@ -5,6 +5,7 @@ import { IconUnorderedList } from '@arco-design/web-react/icon';
import EventSelect from './EventSelect';
import { useDispatch, useSelector } from 'react-redux';
import { queryEventItemBySceneId } from '@/api/event';
import ParamsTable from '@/components/FlowEditor/nodeEditors/components/ParamsTable';
const EventListenEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }) => {
const [eventList, setEventList] = useState<any[]>();
@ -31,7 +32,17 @@ const EventListenEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData
updateNodeData('component', {
...data
});
}}></EventSelect>
}} />
<Typography.Title heading={5}><IconUnorderedList style={{ marginRight: 5 }} /></Typography.Title>
<ParamsTable
initialData={nodeData.parameters.dataIns || []}
onUpdateData={(data) => {
updateNodeData('parameters', {
...nodeData.parameters,
dataIns: data
});
}}
/>
</>
);
};

@ -24,6 +24,7 @@ const typeMap = {
const EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, onRefresh, onUpdateData }) => {
const [options, setOptions] = useState<any[]>([]);
const [specialOptions, setSpecialOptions] = useState<any>({});
const [form] = Form.useForm();
const [showModal, setShowModal] = useState(false);
const [currentEvent, setCurrentEvent] = useState<any>(null);
@ -31,7 +32,8 @@ const EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, on
useEffect(() => {
if (eventList && eventList.length > 0) {
setOptions(eventList);
setSpecialOptions(eventList.find(item => item.topic.includes('**empty**')));
setOptions(eventList.filter(item => !item.topic.includes('**empty**')));
try {
setCurrentEvent(eventList.find(item => JSON.parse(nodeData.component?.customDef).eventId === item.id));
} catch (e) {
@ -50,8 +52,7 @@ const EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, on
const formData = form.getFields();
const params = {
...formData,
sceneId: currentAppData.sceneId,
topic: formData.topic += `/${currentAppData.identify}`
sceneId: currentAppData.sceneId
};
const res: any = await addEventItem(params as AddEventParams);
@ -76,7 +77,6 @@ const EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, on
topic: e.topic
}
};
console.log('data:', data);
onUpdateData(data);
};

@ -5,6 +5,7 @@ import { IconUnorderedList } from '@arco-design/web-react/icon';
import { useDispatch, useSelector } from 'react-redux';
import EventSelect from '@/components/FlowEditor/nodeEditors/components/EventSelect';
import { queryEventItemBySceneId } from '@/api/event';
import ParamsTable from '@/components/FlowEditor/nodeEditors/components/ParamsTable';
const EventSendEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }) => {
const [eventList, setEventList] = useState<any[]>();
@ -31,7 +32,17 @@ const EventSendEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }
updateNodeData('component', {
...data
});
}}></EventSelect>
}} />
<Typography.Title heading={5}><IconUnorderedList style={{ marginRight: 5 }} /></Typography.Title>
<ParamsTable
initialData={nodeData.parameters.dataOuts || []}
onUpdateData={(data) => {
updateNodeData('parameters', {
...nodeData.parameters,
dataOuts: data
});
}}
/>
</>
);
};

@ -35,10 +35,10 @@ export const validateNodeData = (nodeData: any, nodeType: string): ValidationRes
case 'LOOP_END':
errors.push(...validateLoopNode(nodeData, nodeType));
break;
case 'EVENTSEND':
case 'EVENTLISTENE':
errors.push(...validateEventNode(nodeData));
break;
// case 'EVENTSEND':
// case 'EVENTLISTENE':
// errors.push(...validateEventNode(nodeData));
// break;
case 'WAIT':
errors.push(...validateWaitNode(nodeData));
break;

@ -31,6 +31,9 @@ import {
} from '@/components/FlowEditor/nodeEditors/validators/nodeValidators';
import { Dispatch } from 'redux';
import { runMainFlow } from '@/api/apps';
import store from '@/store';
import { updateAppEvent } from '@/api/appEvent';
// 根据节点类型获取对应的节点组件
const getNodeComponent = (nodeType: string) => {
@ -774,6 +777,14 @@ export const useFlowCallbacks = (
})
};
}
else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
const { eventList } = store.getState().ideContainer;
const emptyEvent = eventList.find(item => item.topic.includes('**empty**'));
newNode.data.component = {
type: nodeType,
customDef: { eventId: emptyEvent.id, name: emptyEvent.name, topic: emptyEvent.topic }
};
}
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
@ -863,7 +874,6 @@ export const useFlowCallbacks = (
return;
}
// 创建新节点
const newNode = {
id: `${nodeType}-${Date.now()}`,
@ -885,6 +895,14 @@ export const useFlowCallbacks = (
})
};
}
else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
const { eventList } = store.getState().ideContainer;
const emptyEvent = eventList.find(item => item.topic.includes('**empty**'));
newNode.data.component = {
type: nodeType,
customDef: { eventId: emptyEvent.id, name: emptyEvent.name, topic: emptyEvent.topic }
};
}
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
@ -945,8 +963,9 @@ export const useFlowCallbacks = (
// 转换会原始数据类型
const revertedData = revertFlowData(nodes, edges);
console.log('revertedData(中断):', revertedData);
// return;
updateEvent(revertedData.nodeConfigs);
const res: any = await setMainFlow(revertedData, initialData.appId);
if (res.code === 200) {
Message.success('保存成功');
@ -960,49 +979,78 @@ export const useFlowCallbacks = (
}
}, [nodes, edges, initialData?.appId]);
// 初始化WebSocket hook
const ws = useWebSocket({
onOpen: () => {
console.log('WebSocket连接已建立');
Message.success('运行已启动');
},
onClose: () => {
console.log('WebSocket连接已关闭');
setIsRunning(false);
Message.info('运行已停止');
},
onError: (event) => {
console.error('WebSocket错误:', event);
setIsRunning(false);
Message.error('运行连接出错');
},
onMessage: (event) => {
console.log('收到WebSocket消息:', event.data);
// 这里可以处理从后端收到的消息,例如日志更新等
const updateEvent = (revertedData) => {
// 初始化参数对象
const params: any = {
eventListenes: [],
eventSends: []
};
// 遍历所有节点,查找事件相关节点
Object.entries(revertedData).forEach(([nodeId, nodeConfig]: [string, any]) => {
// 检查节点是否有component属性和type定义
if (nodeConfig.component?.type) {
const nodeType = nodeConfig.component.type;
const customDef = nodeConfig.component.customDef;
// 解析customDef可能是字符串也可能是对象
let eventConfig;
if (typeof customDef === 'string') {
try {
eventConfig = JSON.parse(customDef);
} catch (e) {
console.error('Failed to parse customDef:', e);
return;
}
}
else {
eventConfig = customDef;
}
// 根据节点类型处理事件节点
if (nodeType === 'EVENTLISTENE') {
// 事件接收节点
params.eventListenes.push({
eventId: eventConfig?.eventId || '',
topic: eventConfig?.topic || '',
eventName: eventConfig?.name || '',
dataOuts: nodeConfig.dataOuts || []
});
}
else if (nodeType === 'EVENTSEND') {
// 事件发送节点
params.eventSends.push({
eventId: eventConfig?.eventId || '',
topic: eventConfig?.topic || '',
eventName: eventConfig?.name || '',
dataIns: nodeConfig.dataIns || []
});
}
}
});
// 调用更新事件的API
if (initialData?.appId) {
updateAppEvent(initialData.appId, params);
}
});
};
// 运行处理函数
const handleRun = useCallback(async (running: boolean) => {
if (running) {
const { currentAppData, socketId } = store.getState().ideContainer;
// 启动运行
const res = await getUserToken();
const token = res.data;
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
let wsApi = `${protocol}://${window.location.host}/ws/v1/bpms-runtime`;
if (window.location.host.includes('localhost')) {
wsApi = `ws://api.myserver.com:4121/ws/v1/bpms-runtime`;
}
const uri = `${wsApi}?x-auth0-token=${token}`;
ws.connect(uri);
setIsRunning(true);
const params = {
appId: currentAppData.id,
socketId
};
runMainFlow(params);
}
else {
// 停止运行
ws.disconnect();
setIsRunning(false);
}
}, [initialData?.appId, ws]);
}, [initialData?.appId]);
return {
// Event handlers

@ -180,7 +180,7 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
const formatFooter = (data: any) => {
try {
switch (data.type) {
switch (data?.type) {
case 'WAIT':
const { duration } = deserializeValue(data.customDef);
const hours = Math.floor(duration / 3600);
@ -192,10 +192,11 @@ const formatFooter = (data: any) => {
return cronstrue.toString(intervalSeconds, { locale: 'zh_CN' });
case 'EVENTSEND':
case 'EVENTLISTENE':
const { name } = isJSON(data.customDef) ? JSON.parse(data.customDef) : data.customDef;
const { name, topic } = isJSON(data.customDef) ? JSON.parse(data.customDef) : data.customDef;
if (topic.includes('**empty**')) return '';
return `事件: ${name}`;
default:
return '这个类型还没开发';
return '';
}
} catch (e) {
console.log(e);
@ -207,7 +208,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
const apiOuts = data.parameters?.apiOuts || [];
const dataIns = data.parameters?.dataIns || [];
const dataOuts = data.parameters?.dataOuts || [];
const showFooter = data?.component?.customDef || false;
const showFooter = formatFooter(data.component) || false;
const footerData = (showFooter && data.component) || {};
// console.log(apiIns, apiOuts, dataIns, dataOuts);

@ -6,7 +6,13 @@ import LogBar from './logBar';
import RightSideBar from './rightSideBar';
import NavBar, { NavBarRef } from './navBar';
import { Selected } from '@/pages/ideContainer/types';
import { updateCurrentAppData, updateInfo, updateProjectComponentData } from '@/store/ideContainer';
import {
updateCurrentAppData,
updateInfo,
updateProjectComponentData,
updateSocketId,
updateEventList
} from '@/store/ideContainer';
import { useDispatch, useSelector } from 'react-redux';
import { getAppListBySceneId } from '@/api/apps';
import { getProjectComp } from '@/api/scene';
@ -20,6 +26,10 @@ import ComponentList from '@/pages/componentDevelopment/componentList';
import ComponentCoding from '@/pages/componentDevelopment/componentCoding';
import ComponentDeployment from '@/pages/componentDevelopment/componentDeployment';
import ComponentTest from '@/pages/componentDevelopment/componentTest';
import { getUserToken } from '@/api/user';
import useWebSocket from '@/hooks/useWebSocket';
import { Message } from '@arco-design/web-react';
import { queryEventItemBySceneId } from '@/api/event';
type UrlParamsOptions = {
@ -52,6 +62,38 @@ function IDEContainer() {
const dispatch = useDispatch();
const navBarRef = useRef<NavBarRef>(null);
// 初始化WebSocket hook
const ws = useWebSocket({
onOpen: () => {
console.log('WebSocket连接已建立');
},
onClose: () => {
console.log('WebSocket连接已关闭');
},
onError: (event) => {
console.error('WebSocket错误:', event);
},
onMessage: (event) => {
console.log('收到WebSocket消息:', event.data);
// 这里可以处理从后端收到的消息,例如日志更新等
const socketMessage = JSON.parse(event.data);
if (socketMessage?.socketId) dispatch(updateSocketId(socketMessage.socketId));
}
});
const connectWS = async () => {
const res = await getUserToken();
const token = res.data;
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
let wsApi = `${protocol}://${window.location.host}/ws/v1/bpms-runtime`;
// if (window.location.host.includes('localhost')) {
wsApi = `ws://192.168.5.119/ws/v1/bpms-runtime`;
// }
const uri = `${wsApi}?x-auth0-token=${token}`;
ws.connect(uri);
};
const getAppList = async () => {
const res: any = await getAppListBySceneId({
pageSize: 999,
@ -68,10 +110,14 @@ function IDEContainer() {
}
};
const getEvent = async () => {
const res: any = await queryEventItemBySceneId(urlParams.id);
if (res.code === 200) dispatch(updateEventList(res.data));
};
useEffect(() => {
setUrlParams(getUrlParams(window.location.href) as UrlParamsOptions);
dispatch(updateInfo(getUrlParams(window.location.href) as UrlParamsOptions));
}, []);
useEffect(() => {
@ -81,6 +127,13 @@ function IDEContainer() {
}
}, [urlParams.id]);
useEffect(() => {
if (urlParams.identity && urlParams.identity === 'scene') {
connectWS();
getEvent();
}
}, [urlParams.identity]);
// 当selected.path变化时添加到已打开的tab集合中
useEffect(() => {
if (selected.key) {

@ -29,7 +29,7 @@ const HandleModal = ({ visible, onChangeVisible, onRefresh }) => {
const params = {
name: formData.name,
topic: `${formData.topic}/${info.identity}`,
topic: `${formData.topic}`,
description: formData.description,
sceneId: info.id
};
@ -183,7 +183,7 @@ const EventContainer = () => {
const fetchEventData = async () => {
const res: any = await queryEventItemBySceneId(info.id);
if (res && res.code === 200) setEventData(res.data);
if (res && res.code === 200) setEventData(res.data.filter(item => !item.topic.includes('**empty**')));
};
useEffect(() => {

@ -7,7 +7,9 @@ interface IDEContainerState {
canvasDataMap: any;
projectComponentData: any;
currentAppData: any;
eventList: any;
logBarStatus?: boolean;
socketId: string;
}
const initialState: IDEContainerState = {
@ -17,7 +19,9 @@ const initialState: IDEContainerState = {
canvasDataMap: {}, // 每个画布的缓存信息
projectComponentData: {}, // 工程下的组件列表
currentAppData: {}, // 当前选中的应用数据
logBarStatus: false
eventList:[], // 工程下的事件列表
logBarStatus: false,
socketId: '' // 工程的socketId
};
const ideContainerSlice = createSlice({
@ -42,8 +46,14 @@ const ideContainerSlice = createSlice({
updateCurrentAppData(state, action) {
state.currentAppData = action.payload;
},
updateEventList(state, action) {
state.eventList = action.payload;
},
updateLogBarStatus(state, action) {
state.logBarStatus = action.payload;
},
updateSocketId(state, action) {
state.socketId = action.payload;
}
}
});
@ -55,7 +65,9 @@ export const {
updateCanvasDataMap,
updateProjectComponentData,
updateCurrentAppData,
updateLogBarStatus
updateEventList,
updateLogBarStatus,
updateSocketId
} = ideContainerSlice.actions;
export default ideContainerSlice.reducer;

@ -579,7 +579,6 @@ const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData:
// 获取节点的API输出句柄名称
const getNodeApiOutHandle = (nodeId: string, nodeConfig: any) => {
console.log('nodeConfig:', nodeConfig);
if (nodeConfig.component?.type === 'LOOP_START') {
return 'done';
}

Loading…
Cancel
Save