Browse Source

view

feature/x1-web-request
root 4 months ago
parent
commit
a843ec6c12
  1. 1
      src/X1.BackendServices/X1.BackendServices.csproj
  2. 3
      src/X1.WebAPI/Program.cs
  3. 1
      src/X1.WebAPI/X1.WebAPI.csproj
  4. 4
      src/X1.WebUI/src/constants/api.ts
  5. 27
      src/X1.WebUI/src/constants/menuConfig.ts
  6. 6
      src/X1.WebUI/src/contexts/AuthContext.tsx
  7. 146
      src/X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx
  8. 183
      src/X1.WebUI/src/pages/adb-operations/AdbOperationsTable.tsx
  9. 1
      src/X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx
  10. 188
      src/X1.WebUI/src/pages/at-operations/AtOperationForm.tsx
  11. 173
      src/X1.WebUI/src/pages/at-operations/AtOperationsTable.tsx
  12. 0
      src/X1.WebUI/src/pages/at-operations/AtOperationsView.tsx
  13. BIN
      src/X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx
  14. BIN
      src/X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx
  15. 1
      src/X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx
  16. 81
      src/X1.WebUI/src/routes/AppRouter.tsx
  17. 100
      src/X1.WebUI/src/services/adbOperationsService.ts
  18. 142
      src/X1.WebUI/src/services/atOperationService.ts
  19. 129
      src/X1.WebUI/src/services/terminalDeviceService.ts
  20. 134
      src/modify.md

1
src/X1.BackendServices/X1.BackendServices.csproj

@ -14,7 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\X1.Domain\X1.Domain.csproj" />
<ProjectReference Include="..\X1.DynamicClientCore\X1.DynamicClientCore.csproj" />
</ItemGroup>
</Project>

3
src/X1.WebAPI/Program.cs

@ -21,9 +21,8 @@ using Microsoft.IdentityModel.Tokens;
using System.Text;
using CellularManagement.Domain.Options;
using X1.WebAPI.Extensions;
using X1.DynamicClientCore.Extensions;
using X1.BackendServices;
using X1.DynamicClientCore.Extensions;
// 创建 Web 应用程序构建器
var builder = WebApplication.CreateBuilder(args);

1
src/X1.WebAPI/X1.WebAPI.csproj

@ -29,6 +29,7 @@
<ItemGroup>
<ProjectReference Include="..\X1.Application\X1.Application.csproj" />
<ProjectReference Include="..\X1.DynamicClientCore\X1.DynamicClientCore.csproj" />
<ProjectReference Include="..\X1.Infrastructure\X1.Infrastructure.csproj" />
<ProjectReference Include="..\X1.Presentation\X1.Presentation.csproj" />
<ProjectReference Include="..\X1.WebSocket\X1.WebSocket.csproj" />

4
src/X1.WebUI/src/constants/api.ts

@ -3,6 +3,10 @@ export const API_PATHS = {
// 设备相关
DEVICES: '/devices',
DEVICE_RUNTIMES: '/device-runtimes',
TERMINAL_DEVICES: '/terminal-devices',
// AT操作相关
AT_OPERATIONS: '/at-operations',
// 协议相关
PROTOCOLS: '/protocolversions',

27
src/X1.WebUI/src/constants/menuConfig.ts

@ -1,4 +1,4 @@
import { LucideIcon, LayoutDashboard, Users, Settings, FolderOpen, TestTube, BarChart3, Gauge, FileText, ClipboardList, Network } from 'lucide-react';
import { LucideIcon, LayoutDashboard, Users, Settings, FolderOpen, TestTube, BarChart3, Gauge, FileText, ClipboardList, Network, Smartphone } from 'lucide-react';
// 定义权限类型
export type Permission =
@ -54,6 +54,12 @@ export type Permission =
| 'corenetworkconfigs.manage'
| 'networkstackconfigs.view'
| 'networkstackconfigs.manage'
// 终端设备管理权限
| 'terminaldevices.view'
| 'terminaldevices.manage'
// ADB操作管理权限
| 'adboperations.view'
| 'adboperations.manage'
// 设备运行时管理权限
| 'deviceruntimes.view'
| 'deviceruntimes.manage'
@ -199,7 +205,24 @@ export const menuItems: MenuItem[] = [
href: '/dashboard/instruments/device-runtimes/list',
permission: 'deviceruntimes.view',
},
],
},
{
title: '终端管理',
icon: Smartphone,
href: '/dashboard/terminal-devices',
permission: 'terminaldevices.view',
children: [
{
title: '终端设备',
href: '/dashboard/terminal-devices',
permission: 'terminaldevices.view',
},
{
title: 'ADB命令配置',
href: '/dashboard/terminal-devices/adb-operations',
permission: 'adboperations.view',
},
],
},
{

6
src/X1.WebUI/src/contexts/AuthContext.tsx

@ -87,6 +87,12 @@ const getDefaultPermissions = (userPermissions: Record<string, boolean> = {}) =>
// 协议日志管理权限
'protocollogs.view',
'protocollogs.manage',
// 终端设备管理权限
'terminaldevices.view',
'terminaldevices.manage',
// ADB操作管理权限
'adboperations.view',
'adboperations.manage',
])
];

146
src/X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx

@ -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>
);
}

183
src/X1.WebUI/src/pages/adb-operations/AdbOperationsTable.tsx

@ -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>
);
}

1
src/X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx

@ -0,0 +1 @@

188
src/X1.WebUI/src/pages/at-operations/AtOperationForm.tsx

@ -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>
);
}

173
src/X1.WebUI/src/pages/at-operations/AtOperationsTable.tsx

@ -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>
);
}

0
src/X1.WebUI/src/pages/at-operations/AtOperationsView.tsx

BIN
src/X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx

Binary file not shown.

BIN
src/X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx

Binary file not shown.

1
src/X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx

@ -0,0 +1 @@
import React, { useState, useEffect } from 'react';

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

@ -31,6 +31,10 @@ const UEAnalysisView = lazy(() => import('@/pages/analysis/UEAnalysisView'));
// 设备管理页面
const DevicesView = lazy(() => import('@/pages/instruments/DevicesView'));
// ADB操作管理页面
const AdbOperationsView = lazy(() => import('@/pages/adb-operations/AdbOperationsView'));
// 终端设备管理页面
const TerminalDevicesView = lazy(() => import('@/pages/terminal-devices/TerminalDevicesView'));
// 设备运行时管理页面
const DeviceRuntimesView = lazy(() => import('@/pages/device-runtimes/DeviceRuntimesView'));
// 协议管理页面
@ -197,36 +201,55 @@ export function AppRouter() {
} />
</Route>
{/* 仪表管理路由 */}
<Route path="instruments">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="devices.view">
<AnimatedContainer>
<DevicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="protocols" element={
<ProtectedRoute requiredPermission="protocols.view">
<AnimatedContainer>
<ProtocolsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="device-runtimes">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="deviceruntimes.view">
<AnimatedContainer>
<DeviceRuntimesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
</Route>
{/* 仪表管理路由 */}
<Route path="instruments">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="devices.view">
<AnimatedContainer>
<DevicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="protocols" element={
<ProtectedRoute requiredPermission="protocols.view">
<AnimatedContainer>
<ProtocolsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="device-runtimes">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="deviceruntimes.view">
<AnimatedContainer>
<DeviceRuntimesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
</Route>
{/* 终端设备管理路由 */}
<Route path="terminal-devices">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="terminaldevices.view">
<AnimatedContainer>
<TerminalDevicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="adb-operations" element={
<ProtectedRoute requiredPermission="adboperations.view">
<AnimatedContainer>
<AdbOperationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 信令分析路由 */}
{/* 信令分析路由 */}
<Route path="protocol-logs">
<Route index element={<Navigate to="online-logs" replace />} />
<Route path="online-logs" element={

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

@ -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}`);
}

142
src/X1.WebUI/src/services/atOperationService.ts

@ -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();

129
src/X1.WebUI/src/services/terminalDeviceService.ts

@ -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();

134
src/modify.md

@ -475,6 +475,78 @@
---
### 终端设备管理前端视图实现
#### 修改文件:
1. `X1.WebUI/src/services/terminalDeviceService.ts`
2. `X1.WebUI/src/constants/api.ts`
3. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesTable.tsx`
4. `X1.WebUI/src/pages/terminal-devices/TerminalDeviceForm.tsx`
5. `X1.WebUI/src/pages/terminal-devices/TerminalDevicesView.tsx`
6. `X1.WebUI/src/routes/AppRouter.tsx`
7. `X1.WebUI/src/constants/menuConfig.ts`
#### 修改内容:
1. **终端设备服务层**
- 创建了 `terminalDeviceService.ts` 服务文件
- 定义了完整的终端设备接口类型
- 实现了CRUD操作的API调用方法
- 提供了状态描述和颜色映射方法
2. **API路径配置**
- 在 `api.ts` 中添加了 `TERMINAL_DEVICES: '/terminal-devices'` 路径
- 与后端控制器路径保持一致
3. **终端设备表格组件**
- 创建了 `TerminalDevicesTable.tsx` 表格组件
- 支持设备状态徽章显示
- 实现了编辑和删除操作按钮
- 支持表格密度调整和列显示控制
- 提供日期格式化功能
4. **终端设备表单组件**
- 创建了 `TerminalDeviceForm.tsx` 表单组件
- 使用 React Hook Form 和 Zod 进行表单验证
- 支持创建和编辑两种模式
- 包含设备名称、IP地址、Agent端口、描述等字段
- 编辑模式下支持启用/禁用状态切换
5. **终端设备主视图**
- 创建了 `TerminalDevicesView.tsx` 主视图组件
- 实现了完整的CRUD操作功能
- 支持搜索、分页、排序等功能
- 集成了表格工具栏和分页组件
- 提供Toast提示和错误处理
6. **路由配置**
- 在 `AppRouter.tsx` 中添加了终端设备路由
- 路径为 `/dashboard/terminal-devices`
- 使用权限控制 `terminaldevices.view`
7. **菜单配置**
- 在 `menuConfig.ts` 中添加了终端设备菜单项
- 作为独立的顶级菜单项
- 标题为"终端管理"
- 使用 Smartphone 图标
- 配置了相应的权限要求
8. **功能特性**
- 完整的终端设备管理功能
- 支持按设备名称、编码搜索
- 支持按启用状态筛选
- 表格密度和列显示可配置
- 响应式设计和现代化UI
- 完整的错误处理和用户反馈
#### 修改时间:
2024年
#### 修改原因:
需要为终端设备管理提供完整的前端用户界面,参考现有的设备管理页面实现,确保用户体验的一致性和功能的完整性。
---
### 终端设备控制器创建
#### 修改文件:
@ -1154,4 +1226,64 @@
- ✅ 控制器已完善,参考ProtocolVersionsController模式
- ✅ 统一的错误处理和日志记录
- ✅ 符合DDD和CQRS架构模式
- ✅ 支持完整的CRUD操作
- ✅ 支持完整的CRUD操作
## 2025-01-19 - ADB命令配置功能实现
### 新增功能
1. **ADB操作服务** (`X1.WebUI/src/services/adbOperationsService.ts`)
- 创建了完整的 ADB 操作 API 服务
- 包含获取列表、创建、更新、删除等操作
- 定义了完整的 TypeScript 接口类型
2. **ADB操作管理页面** (`X1.WebUI/src/pages/adb-operations/AdbOperationsView.tsx`)
- 实现了 ADB 命令配置的主页面
- 支持搜索、分页、表格密度调整等功能
- 包含创建、编辑、删除 ADB 操作的功能
3. **ADB操作表格组件** (`X1.WebUI/src/pages/adb-operations/AdbOperationsTable.tsx`)
- 实现了 ADB 操作的表格显示
- 支持列显示控制、表格密度调整
- 包含状态徽章和操作按钮
4. **ADB操作表单组件** (`X1.WebUI/src/pages/adb-operations/AdbOperationForm.tsx`)
- 实现了 ADB 操作的创建和编辑表单
- 包含命令、描述、路径、绝对路径、启用状态、等待时间等字段
- 支持表单验证和提交
### 菜单和路由配置
1. **菜单配置更新** (`X1.WebUI/src/constants/menuConfig.ts`)
- 将 ADB 命令配置添加到终端管理菜单下
- 添加了 `adboperations.view``adboperations.manage` 权限
- 更新了菜单路径为 `/dashboard/terminal-devices/adb-operations`
2. **路由配置更新** (`X1.WebUI/src/routes/AppRouter.tsx`)
- 将终端设备管理路由改为嵌套结构
- 添加了 ADB 操作管理路由
- 格式化了路由代码,使其更清晰易读
### 功能特性
- **完整的 CRUD 操作**: 支持 ADB 操作的创建、读取、更新、删除
- **搜索功能**: 支持按命令和描述搜索
- **分页支持**: 支持分页显示和每页条数调整
- **表格控制**: 支持列显示/隐藏、表格密度调整
- **状态管理**: 支持启用/禁用状态切换
- **路径配置**: 支持自定义执行路径和绝对路径设置
- **等待时间**: 支持命令执行后的等待时间配置
### 技术实现
- 使用 React + TypeScript 开发
- 采用 CQRS 模式,前后端接口完全匹配
- 使用 shadcn/ui 组件库构建界面
- 支持响应式设计和现代化 UI/UX
- 完整的错误处理和用户反馈
### 权限控制
- 添加了 `adboperations.view` 权限用于查看 ADB 操作列表
- 添加了 `adboperations.manage` 权限用于管理 ADB 操作
- 集成到现有的权限系统中
### 后端接口
- 基于现有的 `AdbOperationsController` 实现
- 支持完整的 RESTful API 操作
- 包含日志记录和错误处理
Loading…
Cancel
Save