Browse Source

adb

feature/x1-web-request
root 4 months ago
parent
commit
9c282b3713
  1. 10
      src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs
  2. 8
      src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs
  3. 14
      src/X1.Domain/Entities/Terminal/AdbOperation.cs
  4. 347
      src/X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx
  5. 152
      src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx
  6. 3
      src/X1.WebUI/src/routes/AppRouter.tsx
  7. 3
      src/X1.WebUI/src/services/adbOperationsService.ts
  8. 113
      src/modify.md

10
src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommand.cs

@ -42,6 +42,11 @@ public class CreateAdbOperationCommand : IRequest<OperationResult<CreateAdbOpera
/// 操作截图数据(字节数组)
/// </summary>
public byte[]? ScreenshotData { get; set; }
/// <summary>
/// 设备ID
/// </summary>
public string DeviceId { get; set; } = string.Empty;
}
/// <summary>
@ -88,4 +93,9 @@ public class CreateAdbOperationResponse
/// 操作截图数据(字节数组)
/// </summary>
public byte[]? ScreenshotData { get; set; }
/// <summary>
/// 设备ID
/// </summary>
public string DeviceId { get; set; } = string.Empty;
}

8
src/X1.Application/Features/AdbOperations/Commands/CreateAdbOperation/CreateAdbOperationCommandHandler.cs

@ -30,8 +30,8 @@ public class CreateAdbOperationCommandHandler : IRequestHandler<CreateAdbOperati
CreateAdbOperationCommand request,
CancellationToken cancellationToken)
{
_logger.LogInformation("开始创建ADB操作,命令: {Command}, 路径: {Path}, 描述: {Description}",
request.Command, request.Path, request.Description);
_logger.LogInformation("开始创建ADB操作,命令: {Command}, 路径: {Path}, 描述: {Description}, 设备ID: {DeviceId}",
request.Command, request.Path, request.Description, request.DeviceId);
try
{
@ -45,6 +45,7 @@ public class CreateAdbOperationCommandHandler : IRequestHandler<CreateAdbOperati
request.Path,
request.UseAbsolutePath,
currentUser,
request.DeviceId,
request.IsEnabled,
request.WaitTimeMs,
request.ScreenshotData);
@ -62,7 +63,8 @@ public class CreateAdbOperationCommandHandler : IRequestHandler<CreateAdbOperati
UseAbsolutePath = savedOperation.UseAbsolutePath,
IsEnabled = savedOperation.IsEnabled,
WaitTimeMs = savedOperation.WaitTimeMs,
ScreenshotData = savedOperation.ScreenshotData
ScreenshotData = savedOperation.ScreenshotData,
DeviceId = savedOperation.DeviceId
};
_logger.LogInformation("ADB操作创建成功,ID: {Id}", savedOperation.Id);

14
src/X1.Domain/Entities/Terminal/AdbOperation.cs

@ -42,6 +42,11 @@ public class AdbOperation : AuditableEntity
/// </summary>
public byte[]? ScreenshotData { get; private set; }
/// <summary>
/// 设备ID
/// </summary>
public string DeviceId { get; private set; } = string.Empty;
/// <summary>
/// 私有构造函数,确保通过工厂方法创建实例
/// </summary>
@ -55,6 +60,7 @@ public class AdbOperation : AuditableEntity
/// <param name="path">命令执行时所依赖的路径</param>
/// <param name="useAbsolutePath">是否启用绝对路径</param>
/// <param name="createdBy">创建人</param>
/// <param name="deviceId">设备ID</param>
/// <param name="isEnabled">是否启用,默认true</param>
/// <param name="waitTimeMs">执行命令完需要等待的时间(毫秒),默认0</param>
/// <param name="screenshotData">操作截图数据,可选</param>
@ -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);
}
}

347
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<CommandItem[]>(parseInitialCommands());
const [formData, setFormData] = React.useState<CreateAdbOperationRequest>({
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<Record<string, string>>({});
const [devices, setDevices] = React.useState<TerminalDevice[]>([]);
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<string, string> = {};
// 验证设备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 (
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-6">
{/* 设备选择 */}
<div className="space-y-2">
<Label htmlFor="command">ADB命令</Label>
<Input
id="command"
value={formData.command}
onChange={e => setFormData({ ...formData, command: e.target.value })}
placeholder="例如: adb devices"
required
disabled={isSubmitting}
/>
<Label className="text-sm font-medium">
<span className="text-red-500">*</span>
</Label>
<Select
value={formData.deviceId}
onValueChange={(value) => handleInputChange('deviceId', value)}
disabled={isSubmitting || loadingDevices}
>
<SelectTrigger className={errors.deviceId ? 'border-red-500 focus:border-red-500' : ''}>
<SelectValue placeholder="请选择设备" />
</SelectTrigger>
<SelectContent>
{devices.map((device) => (
<SelectItem key={device.serial} value={device.serial}>
{device.alias || device.name} - {device.brand} ({device.serial})
</SelectItem>
))}
</SelectContent>
</Select>
{errors.deviceId && (
<p className="text-sm text-red-500">{errors.deviceId}</p>
)}
<p className="text-xs text-muted-foreground">
ADB操作的设备ID将与TerminalDevice的Serial字段关联
</p>
</div>
{/* 命令列表 */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<Label className="text-sm font-medium">
ADB命令列表 <span className="text-red-500">*</span>
</Label>
<Button
type="button"
variant="outline"
size="sm"
onClick={addCommand}
disabled={isSubmitting}
className="flex items-center gap-1"
>
<Plus className="h-4 w-4" />
</Button>
</div>
{errors.commands && (
<p className="text-sm text-red-500">{errors.commands}</p>
)}
<div className="space-y-3">
{commands.map((cmd, index) => (
<div key={index} className="flex gap-3 items-start p-3 border rounded-lg">
<div className="flex-1 space-y-3">
<div>
<Label className="text-xs text-muted-foreground">
{index + 1}
</Label>
<Input
value={cmd.command}
onChange={e => 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}`] && (
<p className="text-sm text-red-500">{errors[`command_${index}`]}</p>
)}
</div>
<div>
<Label className="text-xs text-muted-foreground">
()
</Label>
<Input
type="number"
min="0"
step="100"
value={cmd.waitTimeMs}
onChange={e => 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}`] && (
<p className="text-sm text-red-500">{errors[`waitTime_${index}`]}</p>
)}
</div>
</div>
{commands.length > 1 && (
<Button
type="button"
variant="outline"
size="sm"
onClick={() => removeCommand(index)}
disabled={isSubmitting}
className="text-red-500 hover:text-red-700"
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
))}
</div>
<p className="text-xs text-muted-foreground">
ADB命令
</p>
</div>
<div className="space-y-2">
<Label htmlFor="description"></Label>
<Label htmlFor="description" className="text-sm font-medium">
</Label>
<Textarea
id="description"
value={formData.description}
onChange={e => setFormData({ ...formData, description: e.target.value })}
placeholder="请输入操作描述"
onChange={e => handleInputChange('description', e.target.value)}
placeholder="请描述此ADB操作的用途和功能(可选)"
rows={3}
disabled={isSubmitting}
/>
<p className="text-xs text-muted-foreground">
便
</p>
</div>
<div className="space-y-2">
<Label htmlFor="path"></Label>
<Label htmlFor="path" className="text-sm font-medium">
ADB路径
</Label>
<Input
id="path"
value={formData.path}
onChange={e => setFormData({ ...formData, path: e.target.value })}
placeholder="例如: /usr/local/bin/adb"
required
onChange={e => handleInputChange('path', e.target.value)}
placeholder="例如: /usr/local/bin/adb, C:\adb\adb.exe(可选)"
disabled={isSubmitting}
/>
<p className="text-xs text-muted-foreground">
ADB可执行文件的完整路径使
</p>
</div>
<div className="space-y-2">
<Label htmlFor="waitTimeMs">()</Label>
<Input
id="waitTimeMs"
type="number"
min="0"
value={formData.waitTimeMs}
onChange={e => setFormData({ ...formData, waitTimeMs: parseInt(e.target.value) || 0 })}
placeholder="例如: 1000"
required
disabled={isSubmitting}
/>
</div>
<div className="space-y-4">
<div className="flex items-center space-x-2">
<Checkbox
id="useAbsolutePath"
checked={formData.useAbsolutePath}
onCheckedChange={(checked) =>
handleInputChange('useAbsolutePath', checked as boolean)
}
disabled={isSubmitting}
/>
<Label htmlFor="useAbsolutePath" className="text-sm font-medium">
使
</Label>
</div>
<p className="text-xs text-muted-foreground ml-6">
使使
</p>
<div className="flex items-center space-x-2">
<Checkbox
id="useAbsolutePath"
checked={formData.useAbsolutePath}
onCheckedChange={(checked) =>
setFormData({ ...formData, useAbsolutePath: checked as boolean })
}
disabled={isSubmitting}
/>
<Label htmlFor="useAbsolutePath">使</Label>
<div className="flex items-center space-x-2">
<Checkbox
id="isEnabled"
checked={formData.isEnabled}
onCheckedChange={(checked) =>
handleInputChange('isEnabled', checked as boolean)
}
disabled={isSubmitting}
/>
<Label htmlFor="isEnabled" className="text-sm font-medium">
ADB操作
</Label>
</div>
<p className="text-xs text-muted-foreground ml-6">
</p>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="isEnabled"
checked={formData.isEnabled}
onCheckedChange={(checked) =>
setFormData({ ...formData, isEnabled: checked as boolean })
}
<div className="flex gap-3 pt-4">
<Button
type="submit"
className="flex-1"
disabled={isSubmitting}
/>
<Label htmlFor="isEnabled">ADB操作</Label>
>
{isSubmitting ? '提交中...' : (isEdit ? '更新ADB操作' : '创建ADB操作')}
</Button>
</div>
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting ? '提交中...' : (isEdit ? '更新ADB操作' : '创建ADB操作')}
</Button>
</form>
);
}

152
src/X1.WebUI/src/pages/testcases/TestCasesListView.tsx

@ -0,0 +1,152 @@
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Plus, Search, Edit, Trash2, Eye } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
interface TestCase {
id: string;
name: string;
description: string;
status: 'active' | 'inactive' | 'draft';
priority: 'high' | 'medium' | 'low';
category: string;
createdBy: string;
createdAt: string;
stepsCount: number;
}
const mockTestCases: TestCase[] = [
{
id: '1',
name: '用户登录测试',
description: '测试用户登录功能的正常流程和异常情况',
status: 'active',
priority: 'high',
category: '认证测试',
createdBy: '张三',
createdAt: '2024-01-15',
stepsCount: 5
},
{
id: '2',
name: '设备连接测试',
description: '测试设备连接和断开连接的稳定性',
status: 'active',
priority: 'medium',
category: '设备测试',
createdBy: '李四',
createdAt: '2024-01-10',
stepsCount: 8
}
];
export default function TestCasesListView() {
const navigate = useNavigate();
const [testCases, setTestCases] = useState<TestCase[]>(mockTestCases);
const [searchTerm, setSearchTerm] = useState('');
const filteredTestCases = testCases.filter(testCase =>
testCase.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
testCase.description.toLowerCase().includes(searchTerm.toLowerCase())
);
const getStatusBadge = (status: string) => {
const statusConfig = {
active: { color: 'bg-green-100 text-green-800', text: '激活' },
inactive: { color: 'bg-gray-100 text-gray-800', text: '停用' },
draft: { color: 'bg-yellow-100 text-yellow-800', text: '草稿' }
};
const config = statusConfig[status as keyof typeof statusConfig];
return <Badge className={config.color}>{config.text}</Badge>;
};
const getPriorityBadge = (priority: string) => {
const priorityConfig = {
high: { color: 'bg-red-100 text-red-800', text: '高' },
medium: { color: 'bg-orange-100 text-orange-800', text: '中' },
low: { color: 'bg-blue-100 text-blue-800', text: '低' }
};
const config = priorityConfig[priority as keyof typeof priorityConfig];
return <Badge className={config.color}>{config.text}</Badge>;
};
return (
<div className="p-6 space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white"></h1>
<p className="text-gray-600 dark:text-gray-400 mt-1"></p>
</div>
<Button onClick={() => navigate('/dashboard/testcases/create')} className="flex items-center gap-2">
<Plus className="w-4 h-4" />
</Button>
</div>
<Card className="p-4">
<div className="flex gap-4 items-center">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="搜索测试用例名称或描述..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
</Card>
<Card>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredTestCases.map((testCase) => (
<TableRow key={testCase.id}>
<TableCell className="font-medium">{testCase.name}</TableCell>
<TableCell className="max-w-xs truncate">{testCase.description}</TableCell>
<TableCell>{getStatusBadge(testCase.status)}</TableCell>
<TableCell>{getPriorityBadge(testCase.priority)}</TableCell>
<TableCell>{testCase.category}</TableCell>
<TableCell>{testCase.stepsCount}</TableCell>
<TableCell>{testCase.createdBy}</TableCell>
<TableCell>{testCase.createdAt}</TableCell>
<TableCell>
<div className="flex gap-2">
<Button variant="ghost" size="sm" className="p-1">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="sm" className="p-1">
<Edit className="w-4 h-4" />
</Button>
<Button variant="ghost" size="sm" className="p-1 text-red-600 hover:text-red-700">
<Trash2 className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</Card>
</div>
);
}

3
src/X1.WebUI/src/routes/AppRouter.tsx

@ -16,6 +16,7 @@ const UsersView = lazy(() => import('@/pages/users/UsersView'));
// 场景管理页面
const ScenariosView = lazy(() => import('@/pages/scenarios/ScenariosView'));
const TestCasesView = lazy(() => import('@/pages/testcases/TestCasesView'));
const TestCasesListView = lazy(() => import('@/pages/testcases/TestCasesListView'));
const TestStepsView = lazy(() => import('@/pages/teststeps/TestStepsView'));
// 任务管理页面
@ -133,7 +134,7 @@ export function AppRouter() {
<Route path="list" element={
<ProtectedRoute requiredPermission="testcases.view">
<AnimatedContainer>
<TestCasesView />
<TestCasesListView />
</AnimatedContainer>
</ProtectedRoute>
} />

3
src/X1.WebUI/src/services/adbOperationsService.ts

@ -10,6 +10,7 @@ export interface AdbOperation {
useAbsolutePath: boolean;
isEnabled: boolean;
waitTimeMs: number;
deviceId: string;
screenshotData?: Uint8Array;
}
@ -37,6 +38,7 @@ export interface CreateAdbOperationRequest {
useAbsolutePath: boolean;
isEnabled: boolean;
waitTimeMs: number;
deviceId: string;
screenshotData?: Uint8Array;
}
@ -49,6 +51,7 @@ export interface CreateAdbOperationResponse {
useAbsolutePath: boolean;
isEnabled: boolean;
waitTimeMs: number;
deviceId: string;
screenshotData?: Uint8Array;
}

113
src/modify.md

@ -2,6 +2,59 @@
## 2025年修改记录
### 2025-01-16 测试用例列表组件创建
#### 修改文件:
1. `X1.WebUI/src/pages/testcases/TestCasesListView.tsx` - 创建测试用例列表组件
2. `X1.WebUI/src/routes/AppRouter.tsx` - 更新路由配置
#### 修改内容:
1. **问题描述**
- `testcases/list``testcases/create` 路由都使用了同一个组件 `TestCasesView`
- `TestCasesView` 是一个流程设计器,用于创建测试用例
- `testcases/list` 应该显示一个测试用例列表的表格
2. **解决方案**
- 创建了新的 `TestCasesListView` 组件,专门用于显示测试用例列表
- 更新路由配置,让 `testcases/list` 使用新的列表组件
- 保持 `testcases/create` 继续使用原有的流程设计器组件
3. **TestCasesListView组件特性**
- **表格显示**:使用表格组件显示测试用例列表
- **搜索功能**:支持按测试用例名称或描述搜索
- **状态管理**:显示测试用例的状态(激活、停用、草稿)
- **优先级管理**:显示测试用例的优先级(高、中、低)
- **操作按钮**:提供查看、编辑、删除操作
- **创建按钮**:提供创建新测试用例的快捷入口
4. **路由配置更新**
- 添加了 `TestCasesListView` 组件的lazy加载导入
- 更新 `testcases/list` 路由使用新的列表组件
- 保持 `testcases/create` 路由使用原有的流程设计器组件
5. **技术特性**
- 使用React + TypeScript开发
- 采用shadcn/ui组件库
- 支持响应式设计
- 完整的错误处理和用户反馈
- 模拟数据展示功能
6. **UI设计**
- 现代化的表格设计
- 状态徽章和优先级徽章
- 搜索工具栏
- 操作按钮图标化
- 响应式布局
#### 修改时间:
2025-01-16
#### 修改原因:
解决测试用例列表和创建页面显示相同内容的问题,为测试用例管理提供正确的列表视图和创建视图分离。
---
### 2025-01-16 StartTerminalServiceCommandHandler 代码优化
#### 修改文件:
@ -3513,4 +3566,62 @@ if (webSocketResponse?.Success == true)
2025-01-16
#### 修改原因:
修复 `TerminalMessageEventHandler` 中的依赖注入问题,解决 MediatR 单例处理器无法直接注入作用域服务的问题,确保终端消息事件能够正常处理。
修复 `TerminalMessageEventHandler` 中的依赖注入问题,解决 MediatR 单例处理器无法直接注入作用域服务的问题,确保终端消息事件能够正常处理。
## 2025-01-15 - AdbOperation 实体添加 DeviceId 字段
### 修改文件
1. **X1.Domain/Entities/Terminal/AdbOperation.cs**
### 修改内容
1. **新增字段**
- 添加了 `DeviceId` 属性,类型为 `string`,默认值为空字符串
- 该字段与 `TerminalDevice``Serial` 字段关联,用于标识ADB操作所属的设备
2. **更新 Create 方法**
- 添加了 `deviceId` 参数
- 添加了设备ID的必填验证
- 在创建实例时设置 `DeviceId` 属性
3. **更新 Update 方法**
- 添加了 `deviceId` 参数
- 添加了设备ID的必填验证
- 在更新时设置 `DeviceId` 属性
4. **新增 SetDeviceId 方法**
- 提供了专门用于设置设备ID的方法
- 包含参数验证和审计信息更新
### 技术特性
- **必填验证**:设备ID为必填项,不能为空
- **数据关联**:与 TerminalDevice 的 Serial 字段建立关联关系
- **审计支持**:所有修改都会更新审计信息
- **参数验证**:确保数据完整性和一致性
### 数据关联说明
- **ADB 操作的设备ID** 取的是 `terminal-devices` 表中的 `serial` 字段
- **关联关系**:`AdbOperation.DeviceId` = `TerminalDevice.Serial`
- **前端显示**:设备选择下拉框显示 `{设备别名或名称} - {设备品牌} ({序列号})` 格式
- **数据存储**:后端将选中的序列号存储到 `AdbOperation.DeviceId` 字段
### 影响范围
- 需要更新相关的 Command 和 Query 类以支持 DeviceId 参数
- 需要更新前端表单以包含设备选择功能
- 需要更新数据库迁移脚本
- 需要更新相关的 DTO 和响应类
### 后续更新
1. **CreateAdbOperationCommand 和 Response**
- 添加了 `DeviceId` 字段
- 更新了 CommandHandler 以支持设备ID参数
2. **前端服务接口**
- 更新了 `AdbOperation` 接口,添加 `deviceId` 字段
- 更新了 `CreateAdbOperationRequest``CreateAdbOperationResponse` 接口
3. **前端表单组件**
- 添加了设备选择功能
- 集成了终端设备服务
- 添加了设备ID的必填验证
- 支持多条命令输入,每条命令带时间
- 路径设为可选,描述不做限制
Loading…
Cancel
Save