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 EventSelect from './EventSelect';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { queryEventItemBySceneId } from '@/api/event'; import { queryEventItemBySceneId } from '@/api/event';
import ParamsTable from '@/components/FlowEditor/nodeEditors/components/ParamsTable';
const EventListenEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }) => { const EventListenEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }) => {
const [eventList, setEventList] = useState<any[]>(); const [eventList, setEventList] = useState<any[]>();
@ -31,7 +32,17 @@ const EventListenEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData
updateNodeData('component', { updateNodeData('component', {
...data ...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 EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, onRefresh, onUpdateData }) => {
const [options, setOptions] = useState<any[]>([]); const [options, setOptions] = useState<any[]>([]);
const [specialOptions, setSpecialOptions] = useState<any>({});
const [form] = Form.useForm(); const [form] = Form.useForm();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [currentEvent, setCurrentEvent] = useState<any>(null); const [currentEvent, setCurrentEvent] = useState<any>(null);
@ -31,7 +32,8 @@ const EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, on
useEffect(() => { useEffect(() => {
if (eventList && eventList.length > 0) { if (eventList && eventList.length > 0) {
setOptions(eventList); setSpecialOptions(eventList.find(item => item.topic.includes('**empty**')));
setOptions(eventList.filter(item => !item.topic.includes('**empty**')));
try { try {
setCurrentEvent(eventList.find(item => JSON.parse(nodeData.component?.customDef).eventId === item.id)); setCurrentEvent(eventList.find(item => JSON.parse(nodeData.component?.customDef).eventId === item.id));
} catch (e) { } catch (e) {
@ -50,8 +52,7 @@ const EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, on
const formData = form.getFields(); const formData = form.getFields();
const params = { const params = {
...formData, ...formData,
sceneId: currentAppData.sceneId, sceneId: currentAppData.sceneId
topic: formData.topic += `/${currentAppData.identify}`
}; };
const res: any = await addEventItem(params as AddEventParams); const res: any = await addEventItem(params as AddEventParams);
@ -76,7 +77,6 @@ const EventSelect: React.FC<EventSelectProps> = ({ nodeData, eventList, type, on
topic: e.topic topic: e.topic
} }
}; };
console.log('data:', data);
onUpdateData(data); onUpdateData(data);
}; };

@ -5,6 +5,7 @@ import { IconUnorderedList } from '@arco-design/web-react/icon';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import EventSelect from '@/components/FlowEditor/nodeEditors/components/EventSelect'; import EventSelect from '@/components/FlowEditor/nodeEditors/components/EventSelect';
import { queryEventItemBySceneId } from '@/api/event'; import { queryEventItemBySceneId } from '@/api/event';
import ParamsTable from '@/components/FlowEditor/nodeEditors/components/ParamsTable';
const EventSendEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }) => { const EventSendEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }) => {
const [eventList, setEventList] = useState<any[]>(); const [eventList, setEventList] = useState<any[]>();
@ -31,7 +32,17 @@ const EventSendEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }
updateNodeData('component', { updateNodeData('component', {
...data ...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': case 'LOOP_END':
errors.push(...validateLoopNode(nodeData, nodeType)); errors.push(...validateLoopNode(nodeData, nodeType));
break; break;
case 'EVENTSEND': // case 'EVENTSEND':
case 'EVENTLISTENE': // case 'EVENTLISTENE':
errors.push(...validateEventNode(nodeData)); // errors.push(...validateEventNode(nodeData));
break; // break;
case 'WAIT': case 'WAIT':
errors.push(...validateWaitNode(nodeData)); errors.push(...validateWaitNode(nodeData));
break; break;

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

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

@ -6,7 +6,13 @@ import LogBar from './logBar';
import RightSideBar from './rightSideBar'; import RightSideBar from './rightSideBar';
import NavBar, { NavBarRef } from './navBar'; import NavBar, { NavBarRef } from './navBar';
import { Selected } from '@/pages/ideContainer/types'; 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 { useDispatch, useSelector } from 'react-redux';
import { getAppListBySceneId } from '@/api/apps'; import { getAppListBySceneId } from '@/api/apps';
import { getProjectComp } from '@/api/scene'; import { getProjectComp } from '@/api/scene';
@ -20,6 +26,10 @@ import ComponentList from '@/pages/componentDevelopment/componentList';
import ComponentCoding from '@/pages/componentDevelopment/componentCoding'; import ComponentCoding from '@/pages/componentDevelopment/componentCoding';
import ComponentDeployment from '@/pages/componentDevelopment/componentDeployment'; import ComponentDeployment from '@/pages/componentDevelopment/componentDeployment';
import ComponentTest from '@/pages/componentDevelopment/componentTest'; 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 = { type UrlParamsOptions = {
@ -52,6 +62,38 @@ function IDEContainer() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navBarRef = useRef<NavBarRef>(null); 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 getAppList = async () => {
const res: any = await getAppListBySceneId({ const res: any = await getAppListBySceneId({
pageSize: 999, 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(() => { useEffect(() => {
setUrlParams(getUrlParams(window.location.href) as UrlParamsOptions); setUrlParams(getUrlParams(window.location.href) as UrlParamsOptions);
dispatch(updateInfo(getUrlParams(window.location.href) as UrlParamsOptions)); dispatch(updateInfo(getUrlParams(window.location.href) as UrlParamsOptions));
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -81,6 +127,13 @@ function IDEContainer() {
} }
}, [urlParams.id]); }, [urlParams.id]);
useEffect(() => {
if (urlParams.identity && urlParams.identity === 'scene') {
connectWS();
getEvent();
}
}, [urlParams.identity]);
// 当selected.path变化时添加到已打开的tab集合中 // 当selected.path变化时添加到已打开的tab集合中
useEffect(() => { useEffect(() => {
if (selected.key) { if (selected.key) {

@ -29,7 +29,7 @@ const HandleModal = ({ visible, onChangeVisible, onRefresh }) => {
const params = { const params = {
name: formData.name, name: formData.name,
topic: `${formData.topic}/${info.identity}`, topic: `${formData.topic}`,
description: formData.description, description: formData.description,
sceneId: info.id sceneId: info.id
}; };
@ -183,7 +183,7 @@ const EventContainer = () => {
const fetchEventData = async () => { const fetchEventData = async () => {
const res: any = await queryEventItemBySceneId(info.id); 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(() => { useEffect(() => {

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

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

Loading…
Cancel
Save