diff --git a/src/X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx b/src/X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx
index ea8da39..eecf8d2 100644
--- a/src/X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx
+++ b/src/X1.WebUI/src/components/testcases/TestCaseDetailDrawer.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback } from 'react';
+import { useState, useEffect } from 'react';
import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle } from '@/components/ui/drawer';
import ReactFlow, {
Node,
@@ -13,23 +13,7 @@ import ReactFlow, {
} from 'reactflow';
import { testcaseService, TestCaseFlowDetail } from '@/services/testcaseService';
import 'reactflow/dist/style.css';
-import {
- Play,
- Square,
- GitBranch,
- Smartphone,
- Wifi,
- WifiOff,
- Phone,
- PhoneCall,
- PhoneOff,
- Network,
- Activity,
- Signal,
- SignalHigh,
- SignalLow,
- Settings
-} from 'lucide-react';
+import { Settings } from 'lucide-react';
interface TestCaseDetailDrawerProps {
testCaseId: string | null;
@@ -37,281 +21,45 @@ interface TestCaseDetailDrawerProps {
onOpenChange: (open: boolean) => void;
}
-// 自定义节点组件 - 与 ReactFlowDesigner 完全一致
+// 简化的自定义节点组件
const TestStepNode = ({ data, selected }: { data: any; selected?: boolean }) => {
- console.log('TestStepNode 接收到的数据:', data);
- console.log('TestStepNode selected:', selected);
-
- const getIconComponent = (iconName: string) => {
- // 根据图标名称返回对应的图标组件
- switch (iconName) {
- case 'play-circle': return ;
- case 'stop-circle': return ;
- case 'git-branch': return ;
- case 'settings': return ;
- case 'smartphone': return ;
- case 'wifi': return ;
- case 'wifi-off': return ;
- case 'phone': return ;
- case 'phone-call': return ;
- case 'phone-off': return ;
- case 'network': return ;
- case 'activity': return ;
- case 'signal': return ;
- case 'signal-high': return ;
- case 'signal-low': return ;
- default: return ;
- }
- };
-
const getNodeStyle = (stepType: number) => {
switch (stepType) {
- case 1: // 开始步骤 - 圆形
- return {
- shape: 'rounded-full',
- bgColor: 'bg-blue-50 dark:bg-blue-900/30',
- textColor: 'text-blue-600 dark:text-blue-400',
- borderColor: 'border-blue-200 dark:border-blue-600/50',
- hoverBorderColor: 'hover:border-blue-300 dark:hover:border-blue-500'
- };
- case 2: // 结束步骤 - 圆形
- return {
- shape: 'rounded-full',
- bgColor: 'bg-red-50 dark:bg-red-900/30',
- textColor: 'text-red-600 dark:text-red-400',
- borderColor: 'border-red-200 dark:border-red-600/50',
- hoverBorderColor: 'hover:border-red-300 dark:hover:border-red-500'
- };
- case 3: // 处理步骤 - 矩形
- return {
- shape: 'rounded-md',
- bgColor: 'bg-green-50 dark:bg-green-900/30',
- textColor: 'text-green-600 dark:text-green-400',
- borderColor: 'border-green-200 dark:border-green-600/50',
- hoverBorderColor: 'hover:border-green-300 dark:hover:border-green-500'
- };
- case 4: // 判断步骤 - 菱形
- return {
- shape: 'transform rotate-45',
- bgColor: 'bg-purple-50 dark:bg-purple-900/30',
- textColor: 'text-purple-600 dark:text-purple-400',
- borderColor: 'border-purple-200 dark:border-purple-600/50',
- hoverBorderColor: 'hover:border-purple-300 dark:hover:border-purple-500'
- };
- default:
- return {
- shape: 'rounded-md',
- bgColor: 'bg-gray-50 dark:bg-gray-800',
- textColor: 'text-gray-600 dark:text-gray-400',
- borderColor: 'border-gray-200 dark:border-gray-700',
- hoverBorderColor: 'hover:border-gray-300 dark:hover:border-gray-600'
- };
- }
- };
-
- const getIconBgColor = (iconName: string) => {
- // 设备相关图标 - 蓝色
- const deviceIcons = ['smartphone', 'phone', 'phone-call', 'phone-off', 'wifi', 'wifi-off', 'signal', 'signal-high', 'signal-low'];
- // 网络相关图标 - 绿色
- const networkIcons = ['network', 'activity'];
- // 控制相关图标 - 橙色
- const controlIcons = ['play-circle', 'stop-circle'];
- // 配置相关图标 - 紫色
- const configIcons = ['settings', 'git-branch'];
-
- if (deviceIcons.includes(iconName)) {
- return 'bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400';
- } else if (networkIcons.includes(iconName)) {
- return 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400';
- } else if (controlIcons.includes(iconName)) {
- return 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400';
- } else if (configIcons.includes(iconName)) {
- return 'bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400';
+ case 1: return { bgColor: 'bg-blue-50', borderColor: 'border-blue-200' };
+ case 2: return { bgColor: 'bg-red-50', borderColor: 'border-red-200' };
+ case 3: return { bgColor: 'bg-green-50', borderColor: 'border-green-200' };
+ case 4: return { bgColor: 'bg-purple-50', borderColor: 'border-purple-200' };
+ default: return { bgColor: 'bg-gray-50', borderColor: 'border-gray-200' };
}
-
- return 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400';
};
const nodeStyle = getNodeStyle(data.stepType);
return (
-
- {/* 开始和结束步骤使用圆形 */}
- {(data.stepType === 1 || data.stepType === 2) && (
-
-
-
- {getIconComponent(data.icon || 'settings')}
-
-
-
-
- )}
+
+
+
+ {data.stepName}
+
- {/* 处理步骤使用矩形 */}
- {data.stepType === 3 && (
-
-
-
- {getIconComponent(data.icon || 'settings')}
-
-
-
-
- )}
-
- {/* 判断步骤使用菱形 */}
- {data.stepType === 4 && (
-
-
-
- {getIconComponent(data.icon || 'settings')}
-
-
-
-
- )}
-
- {/* 连接点 - 根据节点类型显示不同的连接点 */}
- {/* 开始节点 (type=1) - 只有输出连接点 */}
- {data.stepType === 1 && (
- <>
-
-
- >
- )}
-
- {/* 结束节点 (type=2) - 只有输入连接点 */}
- {data.stepType === 2 && (
- <>
-
-
- >
- )}
-
- {/* 处理节点 (type=3) - 有输入和输出连接点 */}
- {data.stepType === 3 && (
- <>
-
-
-
-
- >
- )}
-
- {/* 判断节点 (type=4) - 有输入和输出连接点 */}
- {data.stepType === 4 && (
- <>
-
-
-
-
- >
- )}
+ {/* 连接点 */}
+
+
);
};
-// 节点类型定义
-const nodeTypes = {
- testStep: TestStepNode,
-};
+const nodeTypes = { testStep: TestStepNode };
function TestCaseDetailDrawerInner({ testCaseId, open, onOpenChange }: TestCaseDetailDrawerProps) {
const [selectedTestCase, setSelectedTestCase] = useState
(null);
@@ -325,7 +73,6 @@ function TestCaseDetailDrawerInner({ testCaseId, open, onOpenChange }: TestCaseD
if (open && testCaseId) {
loadTestCaseDetail(testCaseId);
} else {
- // 关闭抽屉时清理状态
setSelectedTestCase(null);
setError(null);
setNodes([]);
@@ -340,76 +87,40 @@ function TestCaseDetailDrawerInner({ testCaseId, open, onOpenChange }: TestCaseD
const result = await testcaseService.getTestCaseFlowById(id);
if (result.isSuccess && result.data) {
setSelectedTestCase(result.data.testCaseFlow);
- // 转换节点和连线为ReactFlow格式
const flowData = getReactFlowData(result.data.testCaseFlow);
setNodes(flowData.nodes);
setEdges(flowData.edges);
- // 延迟执行 fitView,确保节点已渲染
- setTimeout(() => {
- fitView({ padding: 0.1 });
- }, 100);
+ setTimeout(() => fitView({ padding: 0.1 }), 100);
} else {
setError('加载测试用例详情失败');
- console.error('加载测试用例详情失败:', result.errorMessages);
}
} catch (error) {
setError('加载测试用例详情出错');
- console.error('加载测试用例详情出错:', error);
} finally {
setFlowLoading(false);
}
};
- // 转换节点和连线为ReactFlow格式
const getReactFlowData = (testCase: TestCaseFlowDetail) => {
- console.log('原始节点数据:', testCase.nodes);
- console.log('原始连线数据:', testCase.edges);
-
- const flowNodes: Node[] = testCase.nodes.map(node => {
- console.log('处理节点:', node);
- console.log('节点数据:', node.data);
- console.log('节点位置:', node.position);
-
- // 检查数据字段是否存在,如果不存在则使用默认值
- const nodeData = {
+ const flowNodes: Node[] = testCase.nodes.map(node => ({
+ id: node.id,
+ type: 'testStep',
+ position: node.position,
+ data: {
stepId: node.data?.stepId || node.id,
stepName: node.data?.stepName || 'Unknown',
stepType: node.data?.stepType || 3,
- stepTypeName: node.data?.stepTypeName || '处理步骤',
- description: node.data?.description || '',
icon: node.data?.icon || 'settings'
- };
-
- console.log('处理后的节点数据:', nodeData);
-
- return {
- id: node.id,
- type: 'testStep', // 使用自定义节点类型
- position: node.position,
- data: nodeData,
- width: node.width || 150,
- height: node.height || 50,
- selected: node.selected || false,
- positionAbsolute: node.positionAbsolute,
- dragging: node.dragging || false
- };
- });
+ }
+ }));
- const flowEdges: Edge[] = testCase.edges.map(edge => {
- console.log('处理连线:', edge);
-
- return {
- id: edge.id,
- source: edge.source,
- sourceHandle: edge.sourceHandle || null,
- target: edge.target,
- targetHandle: edge.targetHandle || null,
- type: edge.type || 'smoothstep',
- animated: edge.animated || false,
- style: edge.style || { stroke: '#333', strokeWidth: 2 },
- data: edge.data || {}
- };
- });
+ const flowEdges: Edge[] = testCase.edges.map(edge => ({
+ id: edge.id,
+ source: edge.source,
+ target: edge.target,
+ type: 'smoothstep',
+ style: { stroke: '#3b82f6', strokeWidth: 2 }
+ }));
return { nodes: flowNodes, edges: flowEdges };
};
@@ -434,11 +145,6 @@ function TestCaseDetailDrawerInner({ testCaseId, open, onOpenChange }: TestCaseD
) : error ? (
-
{error}
) : selectedTestCase ? (
-
+
+
) : (
diff --git a/src/X1.WebUI/src/pages/users/UserForm.tsx b/src/X1.WebUI/src/pages/users/UserForm.tsx
index c242141..591506b 100644
--- a/src/X1.WebUI/src/pages/users/UserForm.tsx
+++ b/src/X1.WebUI/src/pages/users/UserForm.tsx
@@ -1,8 +1,10 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
+import { Checkbox } from '@/components/ui/checkbox';
import { CreateUserRequest } from '@/services/userService';
+import { roleService, Role } from '@/services/roleService';
interface UserFormProps {
onSubmit: (data: CreateUserRequest) => void;
@@ -18,11 +20,35 @@ export default function UserForm({ onSubmit, initialData }: UserFormProps) {
roles: initialData?.roles || []
});
+ const [roles, setRoles] = useState
([]);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const fetchRoles = async () => {
+ setLoading(true);
+ const result = await roleService.getAllRoles();
+ if (result.isSuccess && result.data) {
+ setRoles(result.data.roles || []);
+ }
+ setLoading(false);
+ };
+ fetchRoles();
+ }, []);
+
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(formData);
};
+ const handleRoleChange = (roleName: string, checked: boolean) => {
+ const currentRoles = formData.roles || [];
+ if (checked) {
+ setFormData({ ...formData, roles: [...currentRoles, roleName] });
+ } else {
+ setFormData({ ...formData, roles: currentRoles.filter(r => r !== roleName) });
+ }
+ };
+
return (