20 changed files with 1285 additions and 35 deletions
@ -0,0 +1,146 @@ |
|||
import React, { useState, useEffect } from 'react'; |
|||
import { Button } from '@/components/ui/button'; |
|||
import { Input } from '@/components/ui/input'; |
|||
import { Label } from '@/components/ui/label'; |
|||
import { Textarea } from '@/components/ui/textarea'; |
|||
import { Switch } from '@/components/ui/switch'; |
|||
import { AdbOperation, CreateAdbOperationRequest, UpdateAdbOperationRequest } from '@/services/adbOperationsService'; |
|||
|
|||
interface AdbOperationFormProps { |
|||
onSubmit: (data: CreateAdbOperationRequest | UpdateAdbOperationRequest) => void; |
|||
isSubmitting: boolean; |
|||
initialData?: AdbOperation; |
|||
} |
|||
|
|||
export default function AdbOperationForm({ onSubmit, isSubmitting, initialData }: AdbOperationFormProps) { |
|||
const [formData, setFormData] = useState({ |
|||
command: '', |
|||
description: '', |
|||
path: '', |
|||
useAbsolutePath: false, |
|||
isEnabled: true, |
|||
waitTimeMs: 0, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
if (initialData) { |
|||
setFormData({ |
|||
command: initialData.command, |
|||
description: initialData.description, |
|||
path: initialData.path, |
|||
useAbsolutePath: initialData.useAbsolutePath, |
|||
isEnabled: initialData.isEnabled, |
|||
waitTimeMs: initialData.waitTimeMs, |
|||
}); |
|||
} |
|||
}, [initialData]); |
|||
|
|||
const handleSubmit = (e: React.FormEvent) => { |
|||
e.preventDefault(); |
|||
|
|||
if (initialData) { |
|||
// 更新操作
|
|||
const updateData: UpdateAdbOperationRequest = { |
|||
id: initialData.id, |
|||
...formData, |
|||
}; |
|||
onSubmit(updateData); |
|||
} else { |
|||
// 创建操作
|
|||
const createData: CreateAdbOperationRequest = { |
|||
...formData, |
|||
}; |
|||
onSubmit(createData); |
|||
} |
|||
}; |
|||
|
|||
const handleInputChange = (field: string, value: string | number | boolean) => { |
|||
setFormData(prev => ({ |
|||
...prev, |
|||
[field]: value, |
|||
})); |
|||
}; |
|||
|
|||
return ( |
|||
<form onSubmit={handleSubmit} className="space-y-6"> |
|||
<div className="space-y-4"> |
|||
<div> |
|||
<Label htmlFor="command">ADB命令 *</Label> |
|||
<Input |
|||
id="command" |
|||
value={formData.command} |
|||
onChange={(e) => handleInputChange('command', e.target.value)} |
|||
placeholder="例如: adb devices, adb shell pm list packages" |
|||
required |
|||
/> |
|||
<p className="text-sm text-muted-foreground mt-1"> |
|||
输入要执行的ADB命令,支持参数 |
|||
</p> |
|||
</div> |
|||
|
|||
<div> |
|||
<Label htmlFor="description">描述 *</Label> |
|||
<Textarea |
|||
id="description" |
|||
value={formData.description} |
|||
onChange={(e) => handleInputChange('description', e.target.value)} |
|||
placeholder="描述此ADB操作的用途" |
|||
required |
|||
/> |
|||
</div> |
|||
|
|||
<div> |
|||
<Label htmlFor="path">执行路径</Label> |
|||
<Input |
|||
id="path" |
|||
value={formData.path} |
|||
onChange={(e) => handleInputChange('path', e.target.value)} |
|||
placeholder="例如: /usr/local/bin, C:\Android\platform-tools" |
|||
/> |
|||
<p className="text-sm text-muted-foreground mt-1"> |
|||
指定ADB命令执行的工作目录,留空使用系统默认路径 |
|||
</p> |
|||
</div> |
|||
|
|||
<div className="flex items-center space-x-2"> |
|||
<Switch |
|||
id="useAbsolutePath" |
|||
checked={formData.useAbsolutePath} |
|||
onCheckedChange={(checked) => handleInputChange('useAbsolutePath', checked)} |
|||
/> |
|||
<Label htmlFor="useAbsolutePath">使用绝对路径</Label> |
|||
</div> |
|||
|
|||
<div className="flex items-center space-x-2"> |
|||
<Switch |
|||
id="isEnabled" |
|||
checked={formData.isEnabled} |
|||
onCheckedChange={(checked) => handleInputChange('isEnabled', checked)} |
|||
/> |
|||
<Label htmlFor="isEnabled">启用此操作</Label> |
|||
</div> |
|||
|
|||
<div> |
|||
<Label htmlFor="waitTimeMs">等待时间 (毫秒)</Label> |
|||
<Input |
|||
id="waitTimeMs" |
|||
type="number" |
|||
min="0" |
|||
value={formData.waitTimeMs} |
|||
onChange={(e) => handleInputChange('waitTimeMs', parseInt(e.target.value) || 0)} |
|||
placeholder="0" |
|||
/> |
|||
<p className="text-sm text-muted-foreground mt-1"> |
|||
命令执行后的等待时间,用于确保命令完成 |
|||
</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div className="flex justify-end space-x-2"> |
|||
<Button type="submit" disabled={isSubmitting}> |
|||
{isSubmitting ? '保存中...' : (initialData ? '更新' : '创建')} |
|||
</Button> |
|||
</div> |
|||
</form> |
|||
); |
|||
} |
|||
@ -0,0 +1,183 @@ |
|||
import React from 'react'; |
|||
import { Button } from '@/components/ui/button'; |
|||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; |
|||
import { AdbOperation } from '@/services/adbOperationsService'; |
|||
import { Badge } from '@/components/ui/badge'; |
|||
import { Pencil1Icon, TrashIcon } from '@radix-ui/react-icons'; |
|||
import { DensityType } from '@/components/ui/TableToolbar'; |
|||
|
|||
interface AdbOperationsTableProps { |
|||
adbOperations: AdbOperation[]; |
|||
loading: boolean; |
|||
onEdit: (operation: AdbOperation) => void; |
|||
onDelete: (operation: AdbOperation) => void; |
|||
page: number; |
|||
pageSize: number; |
|||
total: number; |
|||
onPageChange: (page: number) => void; |
|||
hideCard?: boolean; |
|||
density?: DensityType; |
|||
columns?: { key: string; title: string; visible: boolean }[]; |
|||
} |
|||
|
|||
// ADB操作状态徽章组件
|
|||
function AdbOperationStatusBadge({ isEnabled }: { isEnabled: boolean }) { |
|||
return ( |
|||
<Badge variant={isEnabled ? "default" : "secondary"}> |
|||
{isEnabled ? "启用" : "禁用"} |
|||
</Badge> |
|||
); |
|||
} |
|||
|
|||
// 绝对路径徽章组件
|
|||
function AbsolutePathBadge({ useAbsolutePath }: { useAbsolutePath: boolean }) { |
|||
return ( |
|||
<Badge variant={useAbsolutePath ? "default" : "outline"}> |
|||
{useAbsolutePath ? "是" : "否"} |
|||
</Badge> |
|||
); |
|||
} |
|||
|
|||
export default function AdbOperationsTable({ |
|||
adbOperations, |
|||
loading, |
|||
onEdit, |
|||
onDelete, |
|||
page, |
|||
pageSize, |
|||
total, |
|||
onPageChange, |
|||
hideCard = false, |
|||
density = 'default', |
|||
columns = [] |
|||
}: AdbOperationsTableProps) { |
|||
const getDensityClass = () => { |
|||
switch (density) { |
|||
case 'compact': |
|||
return 'py-1'; |
|||
case 'comfortable': |
|||
return 'py-3'; |
|||
default: |
|||
return 'py-2'; |
|||
} |
|||
}; |
|||
|
|||
const visibleColumns = columns.filter(col => col.visible); |
|||
|
|||
if (loading) { |
|||
return ( |
|||
<div className="flex items-center justify-center py-8"> |
|||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
if (adbOperations.length === 0) { |
|||
return ( |
|||
<div className="text-center py-8 text-muted-foreground"> |
|||
暂无ADB操作数据 |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
const tableContent = ( |
|||
<Table> |
|||
<TableHeader> |
|||
<TableRow> |
|||
{visibleColumns.map((column) => ( |
|||
<TableHead key={column.key} className={getDensityClass()}> |
|||
{column.title} |
|||
</TableHead> |
|||
))} |
|||
</TableRow> |
|||
</TableHeader> |
|||
<TableBody> |
|||
{adbOperations.map((operation) => ( |
|||
<TableRow key={operation.id}> |
|||
{visibleColumns.map((column) => { |
|||
switch (column.key) { |
|||
case 'command': |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
<code className="bg-muted px-2 py-1 rounded text-sm"> |
|||
{operation.command} |
|||
</code> |
|||
</TableCell> |
|||
); |
|||
case 'description': |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
{operation.description} |
|||
</TableCell> |
|||
); |
|||
case 'path': |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
<span className="font-mono text-sm">{operation.path}</span> |
|||
</TableCell> |
|||
); |
|||
case 'useAbsolutePath': |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
<AbsolutePathBadge useAbsolutePath={operation.useAbsolutePath} /> |
|||
</TableCell> |
|||
); |
|||
case 'isEnabled': |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
<AdbOperationStatusBadge isEnabled={operation.isEnabled} /> |
|||
</TableCell> |
|||
); |
|||
case 'waitTimeMs': |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
{operation.waitTimeMs}ms |
|||
</TableCell> |
|||
); |
|||
case 'actions': |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
<div className="flex items-center space-x-2"> |
|||
<Button |
|||
variant="outline" |
|||
size="sm" |
|||
onClick={() => onEdit(operation)} |
|||
> |
|||
<Pencil1Icon className="h-4 w-4" /> |
|||
编辑 |
|||
</Button> |
|||
<Button |
|||
variant="outline" |
|||
size="sm" |
|||
onClick={() => onDelete(operation)} |
|||
> |
|||
<TrashIcon className="h-4 w-4" /> |
|||
删除 |
|||
</Button> |
|||
</div> |
|||
</TableCell> |
|||
); |
|||
default: |
|||
return ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
{(operation as any)[column.key]} |
|||
</TableCell> |
|||
); |
|||
} |
|||
})} |
|||
</TableRow> |
|||
))} |
|||
</TableBody> |
|||
</Table> |
|||
); |
|||
|
|||
if (hideCard) { |
|||
return tableContent; |
|||
} |
|||
|
|||
return ( |
|||
<div className="rounded-md border"> |
|||
{tableContent} |
|||
</div> |
|||
); |
|||
} |
|||
@ -0,0 +1 @@ |
|||
|
|||
@ -0,0 +1,188 @@ |
|||
import React, { useEffect } from 'react'; |
|||
import { useForm } from 'react-hook-form'; |
|||
import { zodResolver } from '@hookform/resolvers/zod'; |
|||
import * as z from 'zod'; |
|||
import { Button } from '@/components/ui/button'; |
|||
import { Input } from '@/components/ui/input'; |
|||
import { Label } from '@/components/ui/label'; |
|||
import { Textarea } from '@/components/ui/textarea'; |
|||
import { Switch } from '@/components/ui/switch'; |
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; |
|||
import { CreateAtOperationRequest, UpdateAtOperationRequest, AtOperation } from '@/services/atOperationService'; |
|||
import { Device } from '@/services/deviceService'; |
|||
|
|||
const createAtOperationSchema = z.object({ |
|||
deviceId: z.string().min(1, '请选择设备'), |
|||
port: z.number().min(1, '端口不能小于1').max(65535, '端口不能超过65535'), |
|||
command: z.string().min(1, 'AT命令不能为空').max(500, 'AT命令不能超过500个字符'), |
|||
description: z.string().max(500, '描述不能超过500个字符').optional(), |
|||
isEnabled: z.boolean().optional(), |
|||
}); |
|||
|
|||
const updateAtOperationSchema = z.object({ |
|||
id: z.string(), |
|||
deviceId: z.string().min(1, '请选择设备'), |
|||
port: z.number().min(1, '端口不能小于1').max(65535, '端口不能超过65535'), |
|||
command: z.string().min(1, 'AT命令不能为空').max(500, 'AT命令不能超过500个字符'), |
|||
description: z.string().max(500, '描述不能超过500个字符').optional(), |
|||
isEnabled: z.boolean().optional(), |
|||
}); |
|||
|
|||
type CreateFormData = z.infer<typeof createAtOperationSchema>; |
|||
type UpdateFormData = z.infer<typeof updateAtOperationSchema>; |
|||
|
|||
interface AtOperationFormProps { |
|||
onSubmit: (data: CreateAtOperationRequest | UpdateAtOperationRequest) => void; |
|||
initialData?: Partial<AtOperation>; |
|||
isEdit?: boolean; |
|||
isSubmitting?: boolean; |
|||
devices?: Device[]; |
|||
} |
|||
|
|||
export default function AtOperationForm({ |
|||
onSubmit, |
|||
initialData, |
|||
isEdit = false, |
|||
isSubmitting = false, |
|||
devices = [] |
|||
}: AtOperationFormProps) { |
|||
const schema = isEdit ? updateAtOperationSchema : createAtOperationSchema; |
|||
|
|||
const { |
|||
register, |
|||
handleSubmit, |
|||
setValue, |
|||
watch, |
|||
formState: { errors }, |
|||
} = useForm<CreateFormData | UpdateFormData>({ |
|||
resolver: zodResolver(schema), |
|||
defaultValues: { |
|||
deviceId: initialData?.deviceId || '', |
|||
port: initialData?.port || 8080, |
|||
command: initialData?.command || '', |
|||
description: initialData?.description || '', |
|||
isEnabled: initialData?.isEnabled ?? true, |
|||
}, |
|||
}); |
|||
|
|||
const watchedDeviceId = watch('deviceId'); |
|||
|
|||
useEffect(() => { |
|||
if (initialData) { |
|||
setValue('deviceId', initialData.deviceId || ''); |
|||
setValue('port', initialData.port || 8080); |
|||
setValue('command', initialData.command || ''); |
|||
setValue('description', initialData.description || ''); |
|||
if (isEdit && 'isEnabled' in initialData) { |
|||
setValue('isEnabled', initialData.isEnabled); |
|||
} |
|||
} |
|||
}, [initialData, setValue, isEdit]); |
|||
|
|||
const handleFormSubmit = (data: CreateFormData | UpdateFormData) => { |
|||
if (isEdit) { |
|||
const updateData = data as UpdateFormData; |
|||
onSubmit({ |
|||
id: updateData.id, |
|||
deviceId: updateData.deviceId, |
|||
port: updateData.port, |
|||
command: updateData.command, |
|||
description: updateData.description, |
|||
isEnabled: updateData.isEnabled, |
|||
}); |
|||
} else { |
|||
const createData = data as CreateFormData; |
|||
onSubmit({ |
|||
deviceId: createData.deviceId, |
|||
port: createData.port, |
|||
command: createData.command, |
|||
description: createData.description, |
|||
isEnabled: createData.isEnabled, |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
return ( |
|||
<form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-4"> |
|||
<div className="space-y-2"> |
|||
<Label htmlFor="deviceId">设备 *</Label> |
|||
<Select |
|||
value={watchedDeviceId} |
|||
onValueChange={(value) => setValue('deviceId', value)} |
|||
> |
|||
<SelectTrigger className={errors.deviceId ? 'border-red-500' : ''}> |
|||
<SelectValue placeholder="请选择设备" /> |
|||
</SelectTrigger> |
|||
<SelectContent> |
|||
{devices.map((device) => ( |
|||
<SelectItem key={device.deviceId} value={device.deviceId}> |
|||
{device.deviceName} |
|||
</SelectItem> |
|||
))} |
|||
</SelectContent> |
|||
</Select> |
|||
{errors.deviceId && ( |
|||
<p className="text-sm text-red-500">{errors.deviceId.message}</p> |
|||
)} |
|||
</div> |
|||
|
|||
<div className="space-y-2"> |
|||
<Label htmlFor="port">端口 *</Label> |
|||
<Input |
|||
id="port" |
|||
type="number" |
|||
{...register('port', { valueAsNumber: true })} |
|||
placeholder="请输入端口,如:8080" |
|||
className={errors.port ? 'border-red-500' : ''} |
|||
/> |
|||
{errors.port && ( |
|||
<p className="text-sm text-red-500">{errors.port.message}</p> |
|||
)} |
|||
</div> |
|||
|
|||
<div className="space-y-2"> |
|||
<Label htmlFor="command">AT命令 *</Label> |
|||
<Textarea |
|||
id="command" |
|||
{...register('command')} |
|||
placeholder="请输入AT命令,如:AT+CGATT=1" |
|||
rows={3} |
|||
className={errors.command ? 'border-red-500' : ''} |
|||
/> |
|||
{errors.command && ( |
|||
<p className="text-sm text-red-500">{errors.command.message}</p> |
|||
)} |
|||
</div> |
|||
|
|||
<div className="space-y-2"> |
|||
<Label htmlFor="description">描述</Label> |
|||
<Textarea |
|||
id="description" |
|||
{...register('description')} |
|||
placeholder="请输入描述(可选)" |
|||
rows={3} |
|||
className={errors.description ? 'border-red-500' : ''} |
|||
/> |
|||
{errors.description && ( |
|||
<p className="text-sm text-red-500">{errors.description.message}</p> |
|||
)} |
|||
</div> |
|||
|
|||
<div className="flex items-center space-x-2"> |
|||
<Switch |
|||
id="isEnabled" |
|||
{...register('isEnabled')} |
|||
checked={initialData?.isEnabled ?? true} |
|||
onCheckedChange={(checked) => setValue('isEnabled', checked)} |
|||
/> |
|||
<Label htmlFor="isEnabled">启用操作</Label> |
|||
</div> |
|||
|
|||
<div className="flex justify-end space-x-2 pt-4"> |
|||
<Button type="submit" disabled={isSubmitting}> |
|||
{isSubmitting ? '提交中...' : (isEdit ? '更新' : '创建')} |
|||
</Button> |
|||
</div> |
|||
</form> |
|||
); |
|||
} |
|||
@ -0,0 +1,173 @@ |
|||
import React from 'react'; |
|||
import { AtOperation } from '@/services/atOperationService'; |
|||
import { Button } from '@/components/ui/button'; |
|||
import { Badge } from '@/components/ui/badge'; |
|||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; |
|||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; |
|||
import { MoreHorizontal, Edit, Trash2 } from 'lucide-react'; |
|||
|
|||
interface AtOperationsTableProps { |
|||
atOperations: AtOperation[]; |
|||
loading: boolean; |
|||
onEdit: (atOperation: AtOperation) => void; |
|||
onDelete: (atOperation: AtOperation) => void; |
|||
page: number; |
|||
pageSize: number; |
|||
total: number; |
|||
onPageChange: (page: number) => void; |
|||
hideCard?: boolean; |
|||
density?: 'compact' | 'default' | 'comfortable'; |
|||
columns?: Array<{ key: string; title: string; visible: boolean }>; |
|||
} |
|||
|
|||
export default function AtOperationsTable({ |
|||
atOperations, |
|||
loading, |
|||
onEdit, |
|||
onDelete, |
|||
page, |
|||
pageSize, |
|||
total, |
|||
onPageChange, |
|||
hideCard = false, |
|||
density = 'default', |
|||
columns = [] |
|||
}: AtOperationsTableProps) { |
|||
const defaultColumns = [ |
|||
{ key: 'id', title: '操作ID', visible: false }, |
|||
{ key: 'deviceName', title: '设备名称', visible: true }, |
|||
{ key: 'port', title: '端口', visible: true }, |
|||
{ key: 'command', title: 'AT命令', visible: true }, |
|||
{ key: 'description', title: '描述', visible: true }, |
|||
{ key: 'isEnabled', title: '状态', visible: true }, |
|||
{ key: 'createdAt', title: '创建时间', visible: true }, |
|||
{ key: 'actions', title: '操作', visible: true }, |
|||
]; |
|||
|
|||
const visibleColumns = columns.length > 0 ? columns : defaultColumns; |
|||
|
|||
const getDensityClass = () => { |
|||
switch (density) { |
|||
case 'compact': |
|||
return 'text-xs'; |
|||
case 'comfortable': |
|||
return 'text-base'; |
|||
default: |
|||
return 'text-sm'; |
|||
} |
|||
}; |
|||
|
|||
const getStatusBadge = (isEnabled: boolean) => { |
|||
return ( |
|||
<Badge variant={isEnabled ? 'default' : 'secondary'}> |
|||
{isEnabled ? '启用' : '禁用'} |
|||
</Badge> |
|||
); |
|||
}; |
|||
|
|||
const renderCell = (atOperation: AtOperation, columnKey: string) => { |
|||
switch (columnKey) { |
|||
case 'deviceName': |
|||
return atOperation.deviceName; |
|||
case 'port': |
|||
return atOperation.port; |
|||
case 'command': |
|||
return ( |
|||
<div className="max-w-xs truncate" title={atOperation.command}> |
|||
{atOperation.command} |
|||
</div> |
|||
); |
|||
case 'description': |
|||
return ( |
|||
<div className="max-w-xs truncate" title={atOperation.description || ''}> |
|||
{atOperation.description || '-'} |
|||
</div> |
|||
); |
|||
case 'isEnabled': |
|||
return getStatusBadge(atOperation.isEnabled); |
|||
case 'createdAt': |
|||
return new Date(atOperation.createdAt).toLocaleString('zh-CN'); |
|||
case 'actions': |
|||
return ( |
|||
<DropdownMenu> |
|||
<DropdownMenuTrigger asChild> |
|||
<Button variant="ghost" className="h-8 w-8 p-0"> |
|||
<span className="sr-only">打开菜单</span> |
|||
<MoreHorizontal className="h-4 w-4" /> |
|||
</Button> |
|||
</DropdownMenuTrigger> |
|||
<DropdownMenuContent align="end"> |
|||
<DropdownMenuItem onClick={() => onEdit(atOperation)}> |
|||
<Edit className="mr-2 h-4 w-4" /> |
|||
编辑 |
|||
</DropdownMenuItem> |
|||
<DropdownMenuItem |
|||
onClick={() => onDelete(atOperation)} |
|||
className="text-red-600" |
|||
> |
|||
<Trash2 className="mr-2 h-4 w-4" /> |
|||
删除 |
|||
</DropdownMenuItem> |
|||
</DropdownMenuContent> |
|||
</DropdownMenu> |
|||
); |
|||
default: |
|||
return atOperation[columnKey as keyof AtOperation]; |
|||
} |
|||
}; |
|||
|
|||
if (loading) { |
|||
return ( |
|||
<div className="flex items-center justify-center h-32"> |
|||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
if (atOperations.length === 0) { |
|||
return ( |
|||
<div className="flex items-center justify-center h-32 text-muted-foreground"> |
|||
暂无AT操作数据 |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
const tableContent = ( |
|||
<Table> |
|||
<TableHeader> |
|||
<TableRow> |
|||
{visibleColumns |
|||
.filter(column => column.visible) |
|||
.map(column => ( |
|||
<TableHead key={column.key} className={getDensityClass()}> |
|||
{column.title} |
|||
</TableHead> |
|||
))} |
|||
</TableRow> |
|||
</TableHeader> |
|||
<TableBody> |
|||
{atOperations.map((atOperation) => ( |
|||
<TableRow key={atOperation.id}> |
|||
{visibleColumns |
|||
.filter(column => column.visible) |
|||
.map(column => ( |
|||
<TableCell key={column.key} className={getDensityClass()}> |
|||
{renderCell(atOperation, column.key)} |
|||
</TableCell> |
|||
))} |
|||
</TableRow> |
|||
))} |
|||
</TableBody> |
|||
</Table> |
|||
); |
|||
|
|||
if (hideCard) { |
|||
return tableContent; |
|||
} |
|||
|
|||
return ( |
|||
<div className="rounded-md border"> |
|||
{tableContent} |
|||
</div> |
|||
); |
|||
} |
|||
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||
import React, { useState, useEffect } from 'react'; |
|||
@ -0,0 +1,100 @@ |
|||
import { httpClient } from '@/lib/http-client'; |
|||
import { OperationResult } from '@/types/auth'; |
|||
|
|||
// ADB操作接口定义 - 匹配后端AdbOperationDto
|
|||
export interface AdbOperation { |
|||
id: string; |
|||
command: string; |
|||
description: string; |
|||
path: string; |
|||
useAbsolutePath: boolean; |
|||
isEnabled: boolean; |
|||
waitTimeMs: number; |
|||
} |
|||
|
|||
// 获取ADB操作列表请求接口 - 匹配后端GetAdbOperationsQuery
|
|||
export interface GetAdbOperationsRequest { |
|||
pageNumber?: number; |
|||
pageSize?: number; |
|||
keyword?: string; |
|||
} |
|||
|
|||
// 获取ADB操作列表响应接口 - 匹配后端GetAdbOperationsResponse
|
|||
export interface GetAdbOperationsResponse { |
|||
totalCount: number; |
|||
pageNumber: number; |
|||
pageSize: number; |
|||
totalPages: number; |
|||
items: AdbOperation[]; |
|||
} |
|||
|
|||
// 创建ADB操作请求接口 - 匹配后端CreateAdbOperationCommand
|
|||
export interface CreateAdbOperationRequest { |
|||
command: string; |
|||
description: string; |
|||
path: string; |
|||
useAbsolutePath: boolean; |
|||
isEnabled: boolean; |
|||
waitTimeMs: number; |
|||
} |
|||
|
|||
// 创建ADB操作响应接口 - 匹配后端CreateAdbOperationResponse
|
|||
export interface CreateAdbOperationResponse { |
|||
id: string; |
|||
command: string; |
|||
description: string; |
|||
path: string; |
|||
useAbsolutePath: boolean; |
|||
isEnabled: boolean; |
|||
waitTimeMs: number; |
|||
} |
|||
|
|||
// 更新ADB操作请求接口 - 匹配后端UpdateAdbOperationCommand
|
|||
export interface UpdateAdbOperationRequest { |
|||
id: string; |
|||
command: string; |
|||
description: string; |
|||
path: string; |
|||
useAbsolutePath: boolean; |
|||
isEnabled: boolean; |
|||
waitTimeMs: number; |
|||
} |
|||
|
|||
// 更新ADB操作响应接口 - 匹配后端UpdateAdbOperationResponse
|
|||
export interface UpdateAdbOperationResponse { |
|||
id: string; |
|||
command: string; |
|||
description: string; |
|||
path: string; |
|||
useAbsolutePath: boolean; |
|||
isEnabled: boolean; |
|||
waitTimeMs: number; |
|||
} |
|||
|
|||
// API路径常量
|
|||
const ADB_OPERATIONS_API = '/api/adb-operations'; |
|||
|
|||
// 获取ADB操作列表
|
|||
export function getAdbOperations(params: GetAdbOperationsRequest = {}): Promise<OperationResult<GetAdbOperationsResponse>> { |
|||
return httpClient.get(ADB_OPERATIONS_API, { params }); |
|||
} |
|||
|
|||
// 获取ADB操作详情
|
|||
export function getAdbOperationById(id: string): Promise<OperationResult<AdbOperation>> { |
|||
return httpClient.get(`${ADB_OPERATIONS_API}/${id}`); |
|||
} |
|||
|
|||
// 创建ADB操作
|
|||
export function createAdbOperation(data: CreateAdbOperationRequest): Promise<OperationResult<CreateAdbOperationResponse>> { |
|||
return httpClient.post(ADB_OPERATIONS_API, data); |
|||
} |
|||
|
|||
// 更新ADB操作
|
|||
export function updateAdbOperation(id: string, data: UpdateAdbOperationRequest): Promise<OperationResult<UpdateAdbOperationResponse>> { |
|||
return httpClient.put(`${ADB_OPERATIONS_API}/${id}`, data); |
|||
} |
|||
|
|||
// 删除ADB操作
|
|||
export function deleteAdbOperation(id: string): Promise<OperationResult<boolean>> { |
|||
return httpClient.delete(`${ADB_OPERATIONS_API}/${id}`); |
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
import { httpClient } from '@/lib/http-client'; |
|||
import { OperationResult } from '@/types/auth'; |
|||
import { API_PATHS } from '@/constants/api'; |
|||
|
|||
// AT操作信息接口
|
|||
export interface AtOperation { |
|||
id: string; |
|||
deviceId: string; |
|||
deviceName: string; |
|||
port: number; |
|||
command: string; |
|||
description?: string; |
|||
isEnabled: boolean; |
|||
createdAt: string; |
|||
updatedAt: string; |
|||
} |
|||
|
|||
// 获取AT操作列表请求接口
|
|||
export interface GetAtOperationsRequest { |
|||
pageNumber?: number; |
|||
pageSize?: number; |
|||
keyword?: string; |
|||
deviceId?: string; |
|||
} |
|||
|
|||
// 获取AT操作列表响应接口
|
|||
export interface GetAtOperationsResponse { |
|||
totalCount: number; |
|||
pageNumber: number; |
|||
pageSize: number; |
|||
totalPages: number; |
|||
hasPreviousPage: boolean; |
|||
hasNextPage: boolean; |
|||
items: AtOperation[]; |
|||
} |
|||
|
|||
// 获取AT操作详情响应接口
|
|||
export interface GetAtOperationByIdResponse { |
|||
id: string; |
|||
deviceId: string; |
|||
deviceName: string; |
|||
port: number; |
|||
command: string; |
|||
description?: string; |
|||
isEnabled: boolean; |
|||
createdAt: string; |
|||
updatedAt: string; |
|||
} |
|||
|
|||
// 创建AT操作请求接口
|
|||
export interface CreateAtOperationRequest { |
|||
deviceId: string; |
|||
port: number; |
|||
command: string; |
|||
description?: string; |
|||
isEnabled?: boolean; |
|||
} |
|||
|
|||
// 创建AT操作响应接口
|
|||
export interface CreateAtOperationResponse { |
|||
id: string; |
|||
deviceId: string; |
|||
deviceName: string; |
|||
port: number; |
|||
command: string; |
|||
description?: string; |
|||
isEnabled: boolean; |
|||
createdAt: string; |
|||
updatedAt: string; |
|||
} |
|||
|
|||
// 更新AT操作请求接口
|
|||
export interface UpdateAtOperationRequest { |
|||
id: string; |
|||
deviceId: string; |
|||
port: number; |
|||
command: string; |
|||
description?: string; |
|||
isEnabled?: boolean; |
|||
} |
|||
|
|||
// 更新AT操作响应接口
|
|||
export interface UpdateAtOperationResponse { |
|||
id: string; |
|||
deviceId: string; |
|||
deviceName: string; |
|||
port: number; |
|||
command: string; |
|||
description?: string; |
|||
isEnabled: boolean; |
|||
createdAt: string; |
|||
updatedAt: string; |
|||
} |
|||
|
|||
class AtOperationService { |
|||
private readonly baseUrl = API_PATHS.AT_OPERATIONS; |
|||
|
|||
// 获取AT操作列表
|
|||
async getAtOperations(params: GetAtOperationsRequest = {}): Promise<OperationResult<GetAtOperationsResponse>> { |
|||
const queryParams = new URLSearchParams(); |
|||
|
|||
if (params.pageNumber) queryParams.append('pageNumber', params.pageNumber.toString()); |
|||
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString()); |
|||
if (params.keyword) queryParams.append('keyword', params.keyword); |
|||
if (params.deviceId) queryParams.append('deviceId', params.deviceId); |
|||
|
|||
const url = `${this.baseUrl}?${queryParams.toString()}`; |
|||
return httpClient.get<GetAtOperationsResponse>(url); |
|||
} |
|||
|
|||
// 根据ID获取AT操作详情
|
|||
async getAtOperationById(id: string): Promise<OperationResult<GetAtOperationByIdResponse>> { |
|||
return httpClient.get<GetAtOperationByIdResponse>(`${this.baseUrl}/${id}`); |
|||
} |
|||
|
|||
// 创建AT操作
|
|||
async createAtOperation(data: CreateAtOperationRequest): Promise<OperationResult<CreateAtOperationResponse>> { |
|||
return httpClient.post<CreateAtOperationResponse>(this.baseUrl, data); |
|||
} |
|||
|
|||
// 更新AT操作
|
|||
async updateAtOperation(id: string, data: UpdateAtOperationRequest): Promise<OperationResult<UpdateAtOperationResponse>> { |
|||
return httpClient.put<UpdateAtOperationResponse>(`${this.baseUrl}/${id}`, data); |
|||
} |
|||
|
|||
// 删除AT操作
|
|||
async deleteAtOperation(id: string): Promise<OperationResult<boolean>> { |
|||
return httpClient.delete<boolean>(`${this.baseUrl}/${id}`); |
|||
} |
|||
|
|||
// 获取AT操作状态的可读描述
|
|||
getAtOperationStatusDescription(isEnabled: boolean): string { |
|||
return isEnabled ? '启用' : '禁用'; |
|||
} |
|||
|
|||
// 获取AT操作状态的颜色
|
|||
getAtOperationStatusColor(isEnabled: boolean): string { |
|||
return isEnabled ? 'success' : 'error'; |
|||
} |
|||
} |
|||
|
|||
export const atOperationService = new AtOperationService(); |
|||
@ -0,0 +1,129 @@ |
|||
import { httpClient } from './axiosConfig'; |
|||
import { API_PATHS } from '@/constants/api'; |
|||
import { OperationResult } from '@/types/common'; |
|||
|
|||
// 终端设备接口定义
|
|||
export interface TerminalDevice { |
|||
id: string; |
|||
name: string; |
|||
serialNumber: string; |
|||
deviceCode: string; |
|||
description: string; |
|||
agentPort: number; |
|||
ipAddress: string; |
|||
isEnabled: boolean; |
|||
createdAt: string; |
|||
updatedAt?: string; |
|||
} |
|||
|
|||
// 获取终端设备列表请求参数
|
|||
export interface GetTerminalDevicesRequest { |
|||
pageNumber?: number; |
|||
pageSize?: number; |
|||
searchTerm?: string; |
|||
isEnabled?: boolean; |
|||
} |
|||
|
|||
// 获取终端设备列表响应
|
|||
export interface GetTerminalDevicesResponse { |
|||
terminalDevices: TerminalDevice[]; |
|||
totalCount: number; |
|||
pageNumber: number; |
|||
pageSize: number; |
|||
totalPages: number; |
|||
} |
|||
|
|||
// 获取终端设备详情响应
|
|||
export interface GetTerminalDeviceByIdResponse { |
|||
id: string; |
|||
name: string; |
|||
serialNumber: string; |
|||
deviceCode: string; |
|||
description: string; |
|||
agentPort: number; |
|||
ipAddress: string; |
|||
isEnabled: boolean; |
|||
createdAt: string; |
|||
updatedAt?: string; |
|||
} |
|||
|
|||
// 创建终端设备请求
|
|||
export interface CreateTerminalDeviceRequest { |
|||
deviceName: string; |
|||
ipAddress: string; |
|||
agentPort: number; |
|||
description?: string; |
|||
} |
|||
|
|||
// 创建终端设备响应
|
|||
export interface CreateTerminalDeviceResponse { |
|||
deviceId: string; |
|||
deviceName: string; |
|||
serialNumber: string; |
|||
deviceCode: string; |
|||
createdAt: string; |
|||
} |
|||
|
|||
// 更新终端设备请求
|
|||
export interface UpdateTerminalDeviceRequest { |
|||
deviceId: string; |
|||
deviceName: string; |
|||
description?: string; |
|||
isEnabled?: boolean; |
|||
} |
|||
|
|||
// 更新终端设备响应
|
|||
export interface UpdateTerminalDeviceResponse { |
|||
deviceId: string; |
|||
deviceName: string; |
|||
updatedAt: string; |
|||
} |
|||
|
|||
class TerminalDeviceService { |
|||
private readonly baseUrl = API_PATHS.TERMINAL_DEVICES; |
|||
|
|||
// 获取终端设备列表
|
|||
async getTerminalDevices(params: GetTerminalDevicesRequest = {}): Promise<OperationResult<GetTerminalDevicesResponse>> { |
|||
const queryParams = new URLSearchParams(); |
|||
|
|||
if (params.pageNumber) queryParams.append('pageNumber', params.pageNumber.toString()); |
|||
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString()); |
|||
if (params.searchTerm) queryParams.append('searchTerm', params.searchTerm); |
|||
if (params.isEnabled !== undefined) queryParams.append('isEnabled', params.isEnabled.toString()); |
|||
|
|||
const url = `${this.baseUrl}?${queryParams.toString()}`; |
|||
return httpClient.get<GetTerminalDevicesResponse>(url); |
|||
} |
|||
|
|||
// 根据ID获取终端设备详情
|
|||
async getTerminalDeviceById(deviceId: string): Promise<OperationResult<GetTerminalDeviceByIdResponse>> { |
|||
return httpClient.get<GetTerminalDeviceByIdResponse>(`${this.baseUrl}/${deviceId}`); |
|||
} |
|||
|
|||
// 创建终端设备
|
|||
async createTerminalDevice(data: CreateTerminalDeviceRequest): Promise<OperationResult<CreateTerminalDeviceResponse>> { |
|||
return httpClient.post<CreateTerminalDeviceResponse>(this.baseUrl, data); |
|||
} |
|||
|
|||
// 更新终端设备
|
|||
async updateTerminalDevice(deviceId: string, data: UpdateTerminalDeviceRequest): Promise<OperationResult<UpdateTerminalDeviceResponse>> { |
|||
return httpClient.put<UpdateTerminalDeviceResponse>(`${this.baseUrl}/${deviceId}`, data); |
|||
} |
|||
|
|||
// 删除终端设备
|
|||
async deleteTerminalDevice(deviceId: string): Promise<OperationResult<boolean>> { |
|||
return httpClient.delete<boolean>(`${this.baseUrl}/${deviceId}`); |
|||
} |
|||
|
|||
// 获取终端设备状态的可读描述
|
|||
getTerminalDeviceStatusDescription(isEnabled: boolean): string { |
|||
return isEnabled ? '启用' : '禁用'; |
|||
} |
|||
|
|||
// 获取终端设备状态的颜色
|
|||
getTerminalDeviceStatusColor(isEnabled: boolean): string { |
|||
return isEnabled ? 'success' : 'error'; |
|||
} |
|||
} |
|||
|
|||
export const terminalDeviceService = new TerminalDeviceService(); |
|||
Loading…
Reference in new issue