import React, { useEffect, useState, useRef } from 'react'; import { Button, Space, Tree, Collapse, Divider, Message, Modal } from '@arco-design/web-react'; import { IconLeft, IconPlus, IconEdit, IconDelete, IconLink, IconSend } from '@arco-design/web-react/icon'; import styles from './style/testInstance.module.less'; import { getComponentDesign } from '@/api/componentDevelopProcess'; import { getComponentTestCaseList, submitTestCase, deleteTestCase, exportTemplate, exportTestCases, importTestCases, startTestCase, sendTestCase, generateTestCase } from '@/api/componentTestCase'; import TestCaseModal from './testCaseModal'; import useWebSocket from '@/hooks/useWebSocket'; import { getToken } from '@/utils/auth'; const CollapseItem = Collapse.Item; const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId: string; onBack: () => void }) => { const [activeTab, setActiveTab] = useState('link'); const [testCaseList, setTestCaseList] = useState([]); const [design, setDesign] = useState(null); const [modalVisible, setModalVisible] = useState(false); const [selectedOperationIdent, setSelectedOperationIdent] = useState(''); const [activeOperationIdent, setActiveOperationIdent] = useState(''); const [editingTestCase, setEditingTestCase] = useState(null); const [logs, setLogs] = useState([]); const [isSocketConnected, setIsSocketConnected] = useState(false); const fileInputRef = useRef(null); // WebSocket hook const { connect, disconnect, isConnected, sendMessage } = useWebSocket({ onOpen: () => { console.log('链接成功'); setIsSocketConnected(true); addLog('连接信息:连接成功!'); }, onClose: () => { console.log('链接关闭'); setIsSocketConnected(false); addLog('连接信息:连接已断开'); }, onError: () => { console.log('链接错误'); addLog('连接错误:连接失败'); }, onMessage: (event) => { try { const data = JSON.parse(event.data); addLog(`收到消息: ${JSON.stringify(data)}`); } catch (e) { addLog(`收到消息: ${event.data}`); } } }); // 添加日志 const addLog = (message: string) => { const timestamp = new Date().toLocaleTimeString(); setLogs(prev => [...prev, `[${timestamp}] ${message}`]); }; // 清空日志 const clearLogs = () => { setLogs([]); }; const getDesign = async () => { const res: any = await getComponentDesign(parentId); if (res.code === 200) { setDesign(res.data); // 默认激活第一个 operation if (res.data?.operates?.length > 0) { setActiveOperationIdent(res.data.operates[0].ident); } } }; const getTestCaseList = async () => { const res: any = await getComponentTestCaseList({ componentBaseId: parentId, identifier: instance.identifier }); if (res.code === 200) setTestCaseList(res.data); }; useEffect(() => { getTestCaseList(); getDesign(); }, [parentId, instance]); // 组件卸载时断开连接 useEffect(() => { return () => { disconnect(); }; }, [disconnect]); const cryptoRandom = () => { return new Date().getTime().toString(16) + Math.random().toString(16).substring(2); }; // 链接实例 const handleLinkInstance = async () => { if (isSocketConnected) { disconnect(); Message.info('已断开连接'); } else { // WebSocket连接前置 const res: any = await startTestCase(instance.id); console.log('res:', res); // 构建WebSocket URL,根据你的实际后端配置调整 const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const host = window.location.host; let wsUrl = ''; if (window.location.host.includes('localhost')) { wsUrl = `${process.env.NEXT_PUBLIC_DEV_SOCKET_HOST}/ws/v1/bpms-workbench/test-case/${instance.id}/${cryptoRandom()}?Authorization=Bearer ${getToken()}`; } else { wsUrl = `${protocol}//${host}/ws/v1/bpms-workbench/test-case/${instance.id}/${cryptoRandom()}?Authorization=Bearer ${getToken()}`; } connect(wsUrl); addLog('正在连接测试实例...'); } }; const handleSubmitTestCase = async (testCase: any, operationIdent: string) => { const res: any = await sendTestCase(testCase); if (res.code === 200) { Message.success('测试用例发送成功'); } else { Message.error(res.msg || '测试用例发送失败'); } }; const handleAddTestCase = (operationIdent: string) => { setSelectedOperationIdent(operationIdent); setEditingTestCase(null); setModalVisible(true); }; const handleEditTestCase = (testCase: any, operationIdent: string) => { setSelectedOperationIdent(operationIdent); setEditingTestCase(testCase); setModalVisible(true); }; const handleDeleteTestCase = (testCase: any) => { Modal.confirm({ title: '确认删除', content: `确定要删除测试用例"${testCase.testCaseName}"吗?`, okText: '确定', cancelText: '取消', onOk: async () => { try { const res: any = await deleteTestCase(testCase.id); if (res.code === 200) { Message.success('删除成功'); getTestCaseList(); } else { Message.error(res.msg || '删除失败'); } } catch (error) { Message.error('删除失败'); } } }); }; // 导出测试用例模板 const handleExportTemplate = async () => { try { Message.loading('正在生成模板文件...'); const res: any = await exportTemplate(parentId); // 创建下载链接 const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; // 设置文件名 const fileName = `测试用例模板_${instance.identifier}_${new Date().getTime()}.xlsx`; link.setAttribute('download', fileName); // 触发下载 document.body.appendChild(link); link.click(); // 清理 document.body.removeChild(link); window.URL.revokeObjectURL(url); Message.success('模板下载成功'); } catch (error) { console.error('导出模板失败:', error); Message.error('导出模板失败'); } }; // 导出当前测试用例 const handleExportTestCases = async () => { try { // 检查是否有测试用例 if (!testCaseList || testCaseList.length === 0) { Message.warning('当前没有测试用例可导出'); return; } Message.loading('正在生成测试用例文件...'); const res: any = await exportTestCases(parentId); // 创建下载链接 const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; // 设置文件名 const fileName = `测试用例_${instance.identifier}_${new Date().getTime()}.xlsx`; link.setAttribute('download', fileName); // 触发下载 document.body.appendChild(link); link.click(); // 清理 document.body.removeChild(link); window.URL.revokeObjectURL(url); Message.success('测试用例导出成功'); } catch (error) { console.error('导出测试用例失败:', error); Message.error('导出测试用例失败'); } }; // 触发文件选择 const handleImportClick = () => { fileInputRef.current?.click(); }; // 处理文件上传 const handleFileChange = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; // 验证文件类型 const allowedTypes = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx 'application/vnd.ms-excel' // .xls ]; if (!allowedTypes.includes(file.type) && !file.name.match(/\.(xlsx|xls)$/i)) { Message.error('请选择Excel文件(.xlsx 或 .xls)'); event.target.value = ''; // 清空input return; } try { Message.loading('正在导入测试用例...'); const res: any = await importTestCases({ componentBaseId: parentId, file: file }); if (res.code === 200) { Message.success(`导入成功!`); getTestCaseList(); // 刷新列表 } else { Message.error(res.msg || '导入失败'); } } catch (error) { console.error('导入测试用例失败:', error); Message.error('导入测试用例失败'); } finally { // 清空input,允许重复选择同一文件 event.target.value = ''; } }; const handleModalOk = async (values: any) => { const params = { ...values, componentBaseId: parentId, identifier: instance.identifier }; const res: any = await submitTestCase(params); if (res.code === 200) { Message.success('添加测试用例成功'); getTestCaseList(); } else Message.error(res.msg); setModalVisible(false); }; const handleModalCancel = () => { setModalVisible(false); setEditingTestCase(null); }; const handleGenerateTestCases = async () => { const res: any = await generateTestCase({ id: design?.baseInfo.id, identifier: instance.identifier }); if (res.code === 200) { Message.success('生成测试用例成功'); getTestCaseList(); } else { Message.error(res.msg); } }; return (
{/* 隐藏的文件input */} {/* 顶部标签栏 */}
{/* 左侧测试用例列表 */}
测试用例
{testCaseList.map((item: any, index: number) => ( setActiveOperationIdent(item.operationIdent)} > {item.operationIdent}
} name={index.toString()} key={index} extra={ { e.stopPropagation(); handleAddTestCase(item.operationIdent); }} style={{ cursor: 'pointer' }} /> } >
{item.children.map((child: any, index1: number) => (
setActiveOperationIdent(item.operationIdent)} > {child.testCaseName}
{ e.stopPropagation(); isSocketConnected && handleSubmitTestCase(child, item.operationIdent); }} /> { e.stopPropagation(); handleEditTestCase(child, item.operationIdent); }} /> { e.stopPropagation(); handleDeleteTestCase(child); }} />
))}
))}
{/* 中间流程图区域 */}
{/* 开始节点 */}
开始
流程协作接口
{/* 组件节点 - 单个节点展示所有接口 */}
{instance.identifier}
{/* 显示所有接口函数 - 固定在上方 */} {design?.operates?.map((operation: any, index: number) => (
{operation.ident}
))} {/* 分割线 */} {/* 显示激活接口的参数 */} {design?.operates?.map((operation: any, index: number) => ( activeOperationIdent === operation.ident && (
{/* Input 参数 - 显示 parameters */} {operation.parameters?.map((param: any, paramIndex: number) => (
{param.ident} {param.type}
))}
{/* Output 参数 - 显示 responses */} {operation.responses?.map((response: any, respIndex: number) => (
{response.ident} {response.type}
))}
) ))}
{/* 结束节点 */}
结束
流程协作接口
{/* 右侧运行日志 */}
运行日志
{logs.length === 0 ? (

暂无日志信息

请点击"链接实例"按钮连接测试实例

) : (
{logs.map((log, index) => (
{log}
))}
)}
{modalVisible && } ); }; export default TestInstance;