From 9c282b3713b92d551725c05b20beb88032fc048a Mon Sep 17 00:00:00 2001 From: root <295172551@qq.com> Date: Mon, 18 Aug 2025 23:25:29 +0800 Subject: [PATCH] adb --- .../CreateAdbOperationCommand.cs | 10 + .../CreateAdbOperationCommandHandler.cs | 8 +- .../Entities/Terminal/AdbOperation.cs | 14 +- .../pages/adb-operations/AdbOperationForm.tsx | 347 +++++++++++++++--- .../src/pages/testcases/TestCasesListView.tsx | 152 ++++++++ src/X1.WebUI/src/routes/AppRouter.tsx | 3 +- .../src/services/adbOperationsService.ts | 3 + src/modify.md | 113 +++++- 8 files changed, 589 insertions(+), 61 deletions(-) create mode 100644 src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx diff --git a/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs b/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs index 0201d59..42f5001 100644 --- a/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs +++ b/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs @@ -42,6 +42,11 @@ public class CreateAdbOperationCommand : IRequest public byte[]? ScreenshotData { get; set; } + + /// + /// 设备ID + /// + public string DeviceId { get; set; } = string.Empty; } /// @@ -88,4 +93,9 @@ public class CreateAdbOperationResponse /// 操作截图数据(字节数组) /// public byte[]? ScreenshotData { get; set; } + + /// + /// 设备ID + /// + public string DeviceId { get; set; } = string.Empty; } diff --git a/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs b/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs index 083e292..bdf0e33 100644 --- a/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs +++ b/src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs @@ -30,8 +30,8 @@ public class CreateAdbOperationCommandHandler : IRequestHandler public byte[]? ScreenshotData { get; private set; } + /// + /// 设备ID + /// + public string DeviceId { get; private set; } = string.Empty; + /// /// 私有构造函数,确保通过工厂方法创建实例 /// @@ -55,6 +60,7 @@ public class AdbOperation : AuditableEntity /// 命令执行时所依赖的路径 /// 是否启用绝对路径 /// 创建人 + /// 设备ID /// 是否启用,默认true /// 执行命令完需要等待的时间(毫秒),默认0 /// 操作截图数据,可选 @@ -65,6 +71,7 @@ public class AdbOperation : AuditableEntity string path, bool useAbsolutePath, string createdBy, + string deviceId, bool isEnabled = true, int waitTimeMs = 0, byte[]? screenshotData = null) @@ -81,6 +88,9 @@ public class AdbOperation : AuditableEntity if (string.IsNullOrWhiteSpace(createdBy)) throw new ArgumentException("创建人不能为空", nameof(createdBy)); + if (string.IsNullOrWhiteSpace(deviceId)) + throw new ArgumentException("设备ID不能为空", nameof(deviceId)); + if (waitTimeMs < 0) throw new ArgumentException("等待时间不能为负数", nameof(waitTimeMs)); @@ -92,7 +102,8 @@ public class AdbOperation : AuditableEntity UseAbsolutePath = useAbsolutePath, IsEnabled = isEnabled, WaitTimeMs = waitTimeMs, - ScreenshotData = screenshotData + ScreenshotData = screenshotData, + DeviceId = deviceId.Trim() }; // 设置审计信息 @@ -203,4 +214,5 @@ public class AdbOperation : AuditableEntity ScreenshotData = screenshotData; SetUpdated(updatedBy); } + } diff --git a/src/X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx b/src/X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx index 60bed45..8052e4b 100644 --- a/src/X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx +++ b/src/X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx @@ -5,6 +5,14 @@ import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Checkbox } from '@/components/ui/checkbox'; import { CreateAdbOperationRequest, UpdateAdbOperationRequest } from '@/services/adbOperationsService'; +import { getTerminalDevices, TerminalDevice } from '@/services/terminalDeviceService'; +import { Plus, Trash2 } from 'lucide-react'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; + +interface CommandItem { + command: string; + waitTimeMs: number; +} interface AdbOperationFormProps { onSubmit: (data: CreateAdbOperationRequest | UpdateAdbOperationRequest) => void; @@ -19,100 +27,329 @@ export default function AdbOperationForm({ isEdit = false, isSubmitting = false }: AdbOperationFormProps) { + // 解析初始命令数据 + const parseInitialCommands = (): CommandItem[] => { + if (initialData?.command) { + try { + return JSON.parse(initialData.command); + } catch { + // 如果不是JSON格式,当作单条命令处理 + return [{ command: initialData.command, waitTimeMs: initialData.waitTimeMs || 0 }]; + } + } + return [{ command: '', waitTimeMs: 0 }]; + }; + + const [commands, setCommands] = React.useState(parseInitialCommands()); const [formData, setFormData] = React.useState({ command: initialData?.command || '', description: initialData?.description || '', path: initialData?.path || '', useAbsolutePath: initialData?.useAbsolutePath ?? false, isEnabled: initialData?.isEnabled ?? true, - waitTimeMs: initialData?.waitTimeMs || 1000 + waitTimeMs: initialData?.waitTimeMs || 0, + deviceId: initialData?.deviceId || '' }); + const [errors, setErrors] = React.useState>({}); + const [devices, setDevices] = React.useState([]); + const [loadingDevices, setLoadingDevices] = React.useState(false); + + // 加载设备列表 + React.useEffect(() => { + const loadDevices = async () => { + setLoadingDevices(true); + try { + const result = await getTerminalDevices({ pageSize: 100 }); // 获取所有设备 + if (result.isSuccess && result.data) { + setDevices(result.data.terminalDevices || []); + } + } catch (error) { + console.error('加载设备列表失败:', error); + } finally { + setLoadingDevices(false); + } + }; + + loadDevices(); + }, []); + + // 更新命令列表 + const updateCommands = (newCommands: CommandItem[]) => { + setCommands(newCommands); + // 将命令列表转换为JSON字符串 + const commandJson = JSON.stringify(newCommands); + setFormData(prev => ({ ...prev, command: commandJson })); + }; + + // 添加新命令 + const addCommand = () => { + updateCommands([...commands, { command: '', waitTimeMs: 0 }]); + }; + + // 删除命令 + const removeCommand = (index: number) => { + if (commands.length > 1) { + const newCommands = commands.filter((_, i) => i !== index); + updateCommands(newCommands); + } + }; + + // 更新单个命令 + const updateCommand = (index: number, field: keyof CommandItem, value: string | number) => { + const newCommands = [...commands]; + newCommands[index] = { ...newCommands[index], [field]: value }; + updateCommands(newCommands); + }; + + // 表单验证 + const validateForm = (): boolean => { + const newErrors: Record = {}; + + // 验证设备ID + if (!formData.deviceId.trim()) { + newErrors.deviceId = '请选择设备'; + } + + // 验证命令列表 + if (commands.length === 0) { + newErrors.commands = '至少需要一条ADB命令'; + } else { + for (let i = 0; i < commands.length; i++) { + if (!commands[i].command.trim()) { + newErrors[`command_${i}`] = `第${i + 1}条命令不能为空`; + } + if (commands[i].waitTimeMs < 0) { + newErrors[`waitTime_${i}`] = `第${i + 1}条命令的等待时间不能为负数`; + } + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (isSubmitting) return; // 防止重复提交 - onSubmit(formData); + + if (!validateForm()) { + return; + } + + // 提交前清理数据 + const submitData = { + ...formData, + command: formData.command, // 已经是JSON字符串 + description: formData.description.trim(), + path: formData.path.trim() + }; + + onSubmit(submitData); + }; + + const handleInputChange = (field: keyof CreateAdbOperationRequest, value: string | number | boolean) => { + setFormData(prev => ({ ...prev, [field]: value })); + + // 清除对应字段的错误 + if (errors[field]) { + setErrors(prev => ({ ...prev, [field]: '' })); + } }; return ( -
+ + {/* 设备选择 */}
- - setFormData({ ...formData, command: e.target.value })} - placeholder="例如: adb devices" - required - disabled={isSubmitting} - /> + + + {errors.deviceId && ( +

{errors.deviceId}

+ )} +

+ 选择要执行ADB操作的设备,设备ID将与TerminalDevice的Serial字段关联 +

+
+ + {/* 命令列表 */} +
+
+ + +
+ + {errors.commands && ( +

{errors.commands}

+ )} + +
+ {commands.map((cmd, index) => ( +
+
+
+ + updateCommand(index, 'command', e.target.value)} + placeholder="例如: adb devices, adb shell ls, adb install app.apk" + disabled={isSubmitting} + className={errors[`command_${index}`] ? 'border-red-500 focus:border-red-500' : ''} + /> + {errors[`command_${index}`] && ( +

{errors[`command_${index}`]}

+ )} +
+ +
+ + updateCommand(index, 'waitTimeMs', parseInt(e.target.value) || 0)} + placeholder="例如: 1000" + disabled={isSubmitting} + className={errors[`waitTime_${index}`] ? 'border-red-500 focus:border-red-500' : ''} + /> + {errors[`waitTime_${index}`] && ( +

{errors[`waitTime_${index}`]}

+ )} +
+
+ + {commands.length > 1 && ( + + )} +
+ ))} +
+ +

+ 可以添加多条ADB命令,系统将按顺序执行。每条命令可以设置独立的等待时间。 +

- +